src/archive/tar/format.go | 6 ++++++ src/archive/tar/reader.go | 28 ++++++++++++++++++++++++---- src/archive/tar/reader_test.go | 11 +++++++++++ diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go index 9954b4d9f5548a9f074ec6895a251ee1e65a6f9d..32e58a9d9b4e1e7419ebcc1c144d166f4b0f72df 100644 --- a/src/archive/tar/format.go +++ b/src/archive/tar/format.go @@ -147,6 +147,12 @@ // Max length of a special file (PAX header, GNU long name or link). // This matches the limit used by libarchive. maxSpecialFileSize = 1 << 20 + + // Maximum number of sparse file entries. + // We should never actually hit this limit + // (every sparse encoding will first be limited by maxSpecialFileSize), + // but this adds an additional layer of defense. + maxSparseFileEntries = 1 << 20 ) // blockPadding computes the number of bytes needed to pad offset up to the diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go index 16ac2f5b17c28b4ec4214bd7ba8be2c2634a333a..f85b9986259528ab727b9c986cf063bda9980284 100644 --- a/src/archive/tar/reader.go +++ b/src/archive/tar/reader.go @@ -490,7 +490,8 @@ return nil, p.err } s := blk.toGNU().sparse() spd := make(sparseDatas, 0, s.maxEntries()) - for { + totalSize := len(s) + for totalSize < maxSpecialFileSize { for i := 0; i < s.maxEntries(); i++ { // This termination condition is identical to GNU and BSD tar. if s.entry(i).offset()[0] == 0x00 { @@ -501,7 +502,11 @@ length := p.parseNumeric(s.entry(i).length()) if p.err != nil { return nil, p.err } - spd = append(spd, sparseEntry{Offset: offset, Length: length}) + var err error + spd, err = appendSparseEntry(spd, sparseEntry{Offset: offset, Length: length}) + if err != nil { + return nil, err + } } if s.isExtended()[0] > 0 { @@ -510,10 +515,12 @@ if _, err := mustReadFull(tr.r, blk[:]); err != nil { return nil, err } s = blk.toSparse() + totalSize += len(s) continue } return spd, nil // Done } + return nil, errSparseTooLong } // readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format @@ -586,7 +593,10 @@ length, err2 := strconv.ParseInt(nextToken(), 10, 64) if err1 != nil || err2 != nil { return nil, ErrHeader } - spd = append(spd, sparseEntry{Offset: offset, Length: length}) + spd, err = appendSparseEntry(spd, sparseEntry{Offset: offset, Length: length}) + if err != nil { + return nil, err + } } return spd, nil } @@ -620,10 +630,20 @@ length, err2 := strconv.ParseInt(sparseMap[1], 10, 64) if err1 != nil || err2 != nil { return nil, ErrHeader } - spd = append(spd, sparseEntry{Offset: offset, Length: length}) + spd, err = appendSparseEntry(spd, sparseEntry{Offset: offset, Length: length}) + if err != nil { + return nil, err + } sparseMap = sparseMap[2:] } return spd, nil +} + +func appendSparseEntry(spd sparseDatas, ent sparseEntry) (sparseDatas, error) { + if len(spd) >= maxSparseFileEntries { + return nil, errSparseTooLong + } + return append(spd, ent), nil } // Read reads from the current file in the tar archive. diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go index c7611ca044c3f51e6cbcef512a281544ba96cecc..a324674cb7bb1921e8b5a6d3a8ed0c51af286042 100644 --- a/src/archive/tar/reader_test.go +++ b/src/archive/tar/reader_test.go @@ -1145,6 +1145,17 @@ }, { input: makeInput(FormatGNU, "", makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...), wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}}, + }, { + input: makeInput(FormatGNU, "", + makeSparseStrings(func() sparseDatas { + var datas sparseDatas + // This is more than enough entries to exceed our limit. + for i := range int64(1 << 20) { + datas = append(datas, sparseEntry{i * 2, (i * 2) + 1}) + } + return datas + }())...), + wantErr: errSparseTooLong, }} for i, v := range vectors {