diff --git a/src/archive/tar/common.go b/src/archive/tar/common.go index ad31bbb64aaa5c..19b8b9fac3dca7 100644 --- a/src/archive/tar/common.go +++ b/src/archive/tar/common.go @@ -24,9 +24,8 @@ import ( "time" ) -// BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit -// architectures. If a large value is encountered when decoding, the result -// stored in Header will be the truncated version. +// BUG: Use of the Uid and Gid fields in Header cannot represent values above +// int on 32-bit architectures when writing. var tarinsecurepath = godebug.New("tarinsecurepath") diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go index e3083b2b0d9c25..ea5b5e09773dde 100644 --- a/src/archive/tar/reader.go +++ b/src/archive/tar/reader.go @@ -391,8 +391,8 @@ func (tr *Reader) readHeader() (*Header, *block, error) { hdr.Linkname = p.parseString(v7.linkName()) hdr.Size = p.parseNumeric(v7.size()) hdr.Mode = p.parseNumeric(v7.mode()) - hdr.Uid = int(p.parseNumeric(v7.uid())) - hdr.Gid = int(p.parseNumeric(v7.gid())) + hdr.Uid = p.parseInt(v7.uid()) + hdr.Gid = p.parseInt(v7.gid()) hdr.ModTime = time.Unix(p.parseNumeric(v7.modTime()), 0) // Unpack format specific fields. diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go index 621cf29f3e3cd6..e007f70cdf85b9 100644 --- a/src/archive/tar/reader_test.go +++ b/src/archive/tar/reader_test.go @@ -1749,3 +1749,70 @@ func TestMergePAXIntegerOverflow(t *testing.T) { } } +func TestHeaderNumericIDIntegerOverflow(t *testing.T) { + makeHeader := func(uid, gid int64) []byte { + var blk block + var f formatter + v7 := blk.toV7() + copy(v7.name(), "testfile") + v7.typeFlag()[0] = TypeReg + f.formatOctal(v7.mode(), 0644) + f.formatNumeric(v7.uid(), uid) + f.formatNumeric(v7.gid(), gid) + f.formatOctal(v7.size(), 0) + f.formatOctal(v7.modTime(), 0) + blk.setFormat(FormatGNU) + return blk[:] + } + + vectors := []struct { + name string + uid int64 + gid int64 + wantErr bool + }{ + {"Normal", 1000, 1000, false}, + {"BoundaryMaxInt32", 1<<31 - 1, 1000, false}, + {"OverflowUID_1<<31", 1 << 31, 1000, math.MaxInt < 1<<31}, + {"OverflowGID_1<<31", 1000, 1 << 31, math.MaxInt < 1<<31}, + {"OverflowUID_1<<32", 1 << 32, 1000, math.MaxInt < 1<<32}, + {"OverflowGID_1<<32", 1000, 1 << 32, math.MaxInt < 1<<32}, + {"BoundaryMinInt32", -1 << 31, 1000, false}, + {"UnderflowUID_-1<<31-1", -1<<31 - 1, 1000, math.MinInt > -1<<31-1}, + {"UnderflowGID_-1<<31-1", 1000, -1<<31 - 1, math.MinInt > -1<<31-1}, + {"UnderflowUID_-1<<40", -1 << 40, 1000, math.MinInt > -1<<40}, + } + + for _, tt := range vectors { + t.Run(tt.name, func(t *testing.T) { + raw := makeHeader(tt.uid, tt.gid) + var buf bytes.Buffer + buf.Write(raw) + buf.Write(make([]byte, 1024)) + + tr := NewReader(&buf) + hdr, err := tr.Next() + if tt.wantErr { + if err == nil { + t.Fatal("Expected non-nil error") + } + if !errors.Is(err, ErrHeader) { + t.Fatalf("Expected error of type ErrHeader, got %v", err) + } + if hdr != nil { + t.Fatalf("Expected nil header on error, got %+v", hdr) + } + } else { + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if hdr.Uid != int(tt.uid) { + t.Fatalf("hdr.Uid = %d, want %d", hdr.Uid, tt.uid) + } + if hdr.Gid != int(tt.gid) { + t.Fatalf("hdr.Gid = %d, want %d", hdr.Gid, tt.gid) + } + } + }) + } +} diff --git a/src/archive/tar/strconv.go b/src/archive/tar/strconv.go index d3c28a8c4e3bce..835479a9b8a0dc 100644 --- a/src/archive/tar/strconv.go +++ b/src/archive/tar/strconv.go @@ -7,6 +7,7 @@ package tar import ( "bytes" "fmt" + "math" "strconv" "strings" "time" @@ -134,6 +135,17 @@ func (p *parser) parseNumeric(b []byte) int64 { return p.parseOctal(b) } +// parseInt is like parseNumeric but reports ErrHeader if the value does not +// fit in the platform's int. +func (p *parser) parseInt(b []byte) int { + n := p.parseNumeric(b) + if n > math.MaxInt || n < math.MinInt { + p.err = ErrHeader + return 0 + } + return int(n) +} + // formatNumeric encodes x into b using base-8 (octal) encoding if possible. // Otherwise it will attempt to use base-256 (binary) encoding. func (f *formatter) formatNumeric(b []byte, x int64) {