Skip to content

Commit fba8f4d

Browse files
committed
go/internal/gcimporter: fail gracefully on export format skew
Port of changes made to compiler in https://go-review.googlesource.com/27814. Correctly handle export format version 0 (we only do this in x/tools/gcimporter15 at the moment - this is a backport of that code for struct fields). Added tests for version handling and detection of corrupted export data. Fixes #16881. Change-Id: I246553c689c89ef5c7fedd1e43717504c2838804 Reviewed-on: https://go-review.googlesource.com/27816 Reviewed-by: Matthew Dempsky <[email protected]>
1 parent bee4206 commit fba8f4d

File tree

6 files changed

+158
-30
lines changed

6 files changed

+158
-30
lines changed

src/go/internal/gcimporter/bimport.go

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"go/token"
1212
"go/types"
1313
"sort"
14+
"strconv"
1415
"strings"
1516
"unicode"
1617
"unicode/utf8"
@@ -21,6 +22,7 @@ type importer struct {
2122
data []byte
2223
path string
2324
buf []byte // for reading strings
25+
version int // export format version
2426

2527
// object lists
2628
strList []string // in order of appearance
@@ -40,17 +42,28 @@ type importer struct {
4042

4143
// BImportData imports a package from the serialized package data
4244
// and returns the number of bytes consumed and a reference to the package.
43-
// If data is obviously malformed, an error is returned but in
44-
// general it is not recommended to call BImportData on untrusted data.
45-
func BImportData(imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
45+
// If the export data version is not recognized or the format is otherwise
46+
// compromised, an error is returned.
47+
func BImportData(imports map[string]*types.Package, data []byte, path string) (_ int, _ *types.Package, err error) {
48+
// catch panics and return them as errors
49+
defer func() {
50+
if e := recover(); e != nil {
51+
// The package (filename) causing the problem is added to this
52+
// error by a wrapper in the caller (Import in gcimporter.go).
53+
err = fmt.Errorf("cannot import, possibly version skew (%v) - reinstall package", e)
54+
}
55+
}()
56+
4657
p := importer{
4758
imports: imports,
4859
data: data,
4960
path: path,
61+
version: -1, // unknown version
5062
strList: []string{""}, // empty string is mapped to 0
5163
}
5264

5365
// read version info
66+
var versionstr string
5467
if b := p.rawByte(); b == 'c' || b == 'd' {
5568
// Go1.7 encoding; first byte encodes low-level
5669
// encoding format (compact vs debug).
@@ -63,19 +76,34 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i
6376
}
6477
p.trackAllTypes = p.rawByte() == 'a'
6578
p.posInfoFormat = p.int() != 0
66-
const go17version = "v1"
67-
if s := p.string(); s != go17version {
68-
return p.read, nil, fmt.Errorf("importer: unknown export data format: %s (imported package compiled with old compiler?)", s)
79+
versionstr = p.string()
80+
if versionstr == "v1" {
81+
p.version = 0
6982
}
7083
} else {
7184
// Go1.8 extensible encoding
72-
const exportVersion = "version 1"
73-
if s := p.rawStringln(b); s != exportVersion {
74-
return p.read, nil, fmt.Errorf("importer: unknown export data format: %s (imported package compiled with old compiler?)", s)
85+
// read version string and extract version number (ignore anything after the version number)
86+
versionstr = p.rawStringln(b)
87+
if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" {
88+
if v, err := strconv.Atoi(s[1]); err == nil && v > 0 {
89+
p.version = v
90+
}
7591
}
92+
}
93+
94+
// read version specific flags - extend as necessary
95+
switch p.version {
96+
// case 2:
97+
// ...
98+
// fallthrough
99+
case 1:
76100
p.debugFormat = p.rawStringln(p.rawByte()) == "debug"
77101
p.trackAllTypes = p.int() != 0
78102
p.posInfoFormat = p.int() != 0
103+
case 0:
104+
// Go1.7 encoding format - nothing to do here
105+
default:
106+
errorf("unknown export format version %d (%q)", p.version, versionstr)
79107
}
80108

81109
// --- generic export data ---
@@ -99,7 +127,7 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i
99127

100128
// self-verification
101129
if count := p.int(); count != objcount {
102-
panic(fmt.Sprintf("got %d objects; want %d", objcount, count))
130+
errorf("got %d objects; want %d", objcount, count)
103131
}
104132

105133
// ignore compiler-specific import data
@@ -127,6 +155,10 @@ func BImportData(imports map[string]*types.Package, data []byte, path string) (i
127155
return p.read, pkg, nil
128156
}
129157

158+
func errorf(format string, args ...interface{}) {
159+
panic(fmt.Sprintf(format, args...))
160+
}
161+
130162
func (p *importer) pkg() *types.Package {
131163
// if the package was seen before, i is its index (>= 0)
132164
i := p.tagOrIndex()
@@ -136,7 +168,7 @@ func (p *importer) pkg() *types.Package {
136168

137169
// otherwise, i is the package tag (< 0)
138170
if i != packageTag {
139-
panic(fmt.Sprintf("unexpected package tag %d", i))
171+
errorf("unexpected package tag %d", i)
140172
}
141173

142174
// read package data
@@ -145,13 +177,13 @@ func (p *importer) pkg() *types.Package {
145177

146178
// we should never see an empty package name
147179
if name == "" {
148-
panic("empty package name in import")
180+
errorf("empty package name in import")
149181
}
150182

151183
// an empty path denotes the package we are currently importing;
152184
// it must be the first package we see
153185
if (path == "") != (len(p.pkgList) == 0) {
154-
panic(fmt.Sprintf("package path %q for pkg index %d", path, len(p.pkgList)))
186+
errorf("package path %q for pkg index %d", path, len(p.pkgList))
155187
}
156188

157189
// if the package was imported before, use that one; otherwise create a new one
@@ -163,7 +195,7 @@ func (p *importer) pkg() *types.Package {
163195
pkg = types.NewPackage(path, name)
164196
p.imports[path] = pkg
165197
} else if pkg.Name() != name {
166-
panic(fmt.Sprintf("conflicting names %s and %s for package %q", pkg.Name(), name, path))
198+
errorf("conflicting names %s and %s for package %q", pkg.Name(), name, path)
167199
}
168200
p.pkgList = append(p.pkgList, pkg)
169201

@@ -180,7 +212,7 @@ func (p *importer) declare(obj types.Object) {
180212
// imported.
181213
// (See also the comment in cmd/compile/internal/gc/bimport.go importer.obj,
182214
// switch case importing functions).
183-
panic(fmt.Sprintf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", alt, obj))
215+
errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", alt, obj)
184216
}
185217
}
186218

@@ -211,7 +243,7 @@ func (p *importer) obj(tag int) {
211243
p.declare(types.NewFunc(token.NoPos, pkg, name, sig))
212244

213245
default:
214-
panic(fmt.Sprintf("unexpected object tag %d", tag))
246+
errorf("unexpected object tag %d", tag)
215247
}
216248
}
217249

@@ -283,7 +315,7 @@ func (p *importer) typ(parent *types.Package) types.Type {
283315
}
284316

285317
if _, ok := obj.(*types.TypeName); !ok {
286-
panic(fmt.Sprintf("pkg = %s, name = %s => %s", parent, name, obj))
318+
errorf("pkg = %s, name = %s => %s", parent, name, obj)
287319
}
288320

289321
// associate new named type with obj if it doesn't exist yet
@@ -390,7 +422,7 @@ func (p *importer) typ(parent *types.Package) types.Type {
390422

391423
// no embedded interfaces with gc compiler
392424
if p.int() != 0 {
393-
panic("unexpected embedded interface")
425+
errorf("unexpected embedded interface")
394426
}
395427

396428
t := types.NewInterface(p.methodList(parent), nil)
@@ -426,14 +458,15 @@ func (p *importer) typ(parent *types.Package) types.Type {
426458
case 3 /* Cboth */ :
427459
dir = types.SendRecv
428460
default:
429-
panic(fmt.Sprintf("unexpected channel dir %d", d))
461+
errorf("unexpected channel dir %d", d)
430462
}
431463
val := p.typ(parent)
432464
*t = *types.NewChan(dir, val)
433465
return t
434466

435467
default:
436-
panic(fmt.Sprintf("unexpected type tag %d", i))
468+
errorf("unexpected type tag %d", i)
469+
panic("unreachable")
437470
}
438471
}
439472

@@ -464,7 +497,7 @@ func (p *importer) field(parent *types.Package) *types.Var {
464497
case *types.Named:
465498
name = typ.Obj().Name()
466499
default:
467-
panic("anonymous field expected")
500+
errorf("anonymous field expected")
468501
}
469502
anonymous = true
470503
}
@@ -498,6 +531,10 @@ func (p *importer) fieldName(parent *types.Package) (*types.Package, string) {
498531
// use the imported package instead
499532
pkg = p.pkgList[0]
500533
}
534+
if p.version == 0 && name == "_" {
535+
// version 0 didn't export a package for _ fields
536+
return pkg, name
537+
}
501538
if name != "" && !exported(name) {
502539
if name == "?" {
503540
name = ""
@@ -539,7 +576,7 @@ func (p *importer) param(named bool) (*types.Var, bool) {
539576
if named {
540577
name = p.string()
541578
if name == "" {
542-
panic("expected named parameter")
579+
errorf("expected named parameter")
543580
}
544581
if name != "_" {
545582
pkg = p.pkg()
@@ -577,7 +614,8 @@ func (p *importer) value() constant.Value {
577614
case stringTag:
578615
return constant.MakeString(p.string())
579616
default:
580-
panic(fmt.Sprintf("unexpected value tag %d", tag))
617+
errorf("unexpected value tag %d", tag)
618+
panic("unreachable")
581619
}
582620
}
583621

@@ -640,7 +678,7 @@ func (p *importer) tagOrIndex() int {
640678
func (p *importer) int() int {
641679
x := p.int64()
642680
if int64(int(x)) != x {
643-
panic("exported integer too large")
681+
errorf("exported integer too large")
644682
}
645683
return int(x)
646684
}
@@ -679,20 +717,20 @@ func (p *importer) string() string {
679717

680718
func (p *importer) marker(want byte) {
681719
if got := p.rawByte(); got != want {
682-
panic(fmt.Sprintf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read))
720+
errorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read)
683721
}
684722

685723
pos := p.read
686724
if n := int(p.rawInt64()); n != pos {
687-
panic(fmt.Sprintf("incorrect position: got %d; want %d", n, pos))
725+
errorf("incorrect position: got %d; want %d", n, pos)
688726
}
689727
}
690728

691729
// rawInt64 should only be used by low-level decoders.
692730
func (p *importer) rawInt64() int64 {
693731
i, err := binary.ReadVarint(p)
694732
if err != nil {
695-
panic(fmt.Sprintf("read error: %v", err))
733+
errorf("read error: %v", err)
696734
}
697735
return i
698736
}
@@ -727,7 +765,7 @@ func (p *importer) rawByte() byte {
727765
case '|':
728766
// nothing to do
729767
default:
730-
panic("unexpected escape sequence in export data")
768+
errorf("unexpected escape sequence in export data")
731769
}
732770
}
733771
p.data = p.data[r:]

src/go/internal/gcimporter/gcimporter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func Import(packages map[string]*types.Package, path, srcDir string) (pkg *types
106106
f.Close()
107107
if err != nil {
108108
// add file name to error
109-
err = fmt.Errorf("reading export data: %s: %v", filename, err)
109+
err = fmt.Errorf("%s: %v", filename, err)
110110
}
111111
}()
112112

@@ -118,7 +118,7 @@ func Import(packages map[string]*types.Package, path, srcDir string) (pkg *types
118118

119119
switch hdr {
120120
case "$$\n":
121-
err = fmt.Errorf("cannot import %s: old export format no longer supported (recompile library)", path)
121+
err = fmt.Errorf("import %q: old export format no longer supported (recompile library)", path)
122122
case "$$B\n":
123123
var data []byte
124124
data, err = ioutil.ReadAll(buf)

src/go/internal/gcimporter/gcimporter_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package gcimporter
66

77
import (
8+
"bytes"
89
"fmt"
910
"internal/testenv"
1011
"io/ioutil"
@@ -118,6 +119,70 @@ func TestImportTestdata(t *testing.T) {
118119
}
119120
}
120121

122+
func TestVersionHandling(t *testing.T) {
123+
skipSpecialPlatforms(t) // we really only need to exclude nacl platforms, but this is fine
124+
125+
// This package only handles gc export data.
126+
if runtime.Compiler != "gc" {
127+
t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
128+
return
129+
}
130+
131+
const dir = "./testdata/versions"
132+
list, err := ioutil.ReadDir(dir)
133+
if err != nil {
134+
t.Fatal(err)
135+
}
136+
137+
for _, f := range list {
138+
name := f.Name()
139+
if !strings.HasSuffix(name, ".a") {
140+
continue // not a package file
141+
}
142+
if strings.Contains(name, "corrupted") {
143+
continue // don't process a leftover corrupted file
144+
}
145+
pkgpath := "./" + name[:len(name)-2]
146+
147+
// test that export data can be imported
148+
_, err := Import(make(map[string]*types.Package), pkgpath, dir)
149+
if err != nil {
150+
t.Errorf("import %q failed: %v", pkgpath, err)
151+
continue
152+
}
153+
154+
// create file with corrupted export data
155+
// 1) read file
156+
data, err := ioutil.ReadFile(filepath.Join(dir, name))
157+
if err != nil {
158+
t.Fatal(err)
159+
}
160+
// 2) find export data
161+
i := bytes.Index(data, []byte("\n$$B\n")) + 5
162+
j := bytes.Index(data[i:], []byte("\n$$\n")) + i
163+
if i < 0 || j < 0 || i > j {
164+
t.Fatalf("export data section not found (i = %d, j = %d)", i, j)
165+
}
166+
// 3) corrupt the data (increment every 7th byte)
167+
for k := j - 13; k >= i; k -= 7 {
168+
data[k]++
169+
}
170+
// 4) write the file
171+
pkgpath += "_corrupted"
172+
filename := filepath.Join(dir, pkgpath) + ".a"
173+
ioutil.WriteFile(filename, data, 0666)
174+
defer os.Remove(filename)
175+
176+
// test that importing the corrupted file results in an error
177+
_, err = Import(make(map[string]*types.Package), pkgpath, dir)
178+
if err == nil {
179+
t.Errorf("import corrupted %q succeeded", pkgpath)
180+
} else if msg := err.Error(); !strings.Contains(msg, "version skew") {
181+
t.Errorf("import %q error incorrect (%s)", pkgpath, msg)
182+
}
183+
}
184+
}
185+
121186
func TestImportStdLib(t *testing.T) {
122187
skipSpecialPlatforms(t)
123188

0 commit comments

Comments
 (0)