Skip to content

Commit cd0ba4c

Browse files
committed
archive/tar: make Reader error handling consistent
The tar.Reader guarantees stickiness of errors. Ensuring this property means that the methods of Reader need to be consistent about whose responsibility it is to actually ensure that errors are sticky. In this CL, we make it only the responsibility of the exported methods (Next and Read) to store tr.err. All other methods just return the error as is. As part of this change, we also check the error value of mergePAX (and test that it properly detects invalid PAX files). Since the value of mergePAX was never used before, we change it such that it always returns ErrHeader instead of strconv.SyntaxError. This keeps it consistent with other usages of strconv in the same tar package. Change-Id: Ia1c31da71f1de4c175da89a385dec665d3edd167 Reviewed-on: https://go-review.googlesource.com/28215 Run-TryBot: Joe Tsai <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent d1a1923 commit cd0ba4c

File tree

4 files changed

+80
-104
lines changed

4 files changed

+80
-104
lines changed

src/archive/tar/reader.go

+72-104
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@ const maxNanoSecondIntSize = 9
3030
// and then it can be treated as an io.Reader to access the file's data.
3131
type Reader struct {
3232
r io.Reader
33-
err error
3433
pad int64 // amount of padding (ignored) after current file entry
3534
curr numBytesReader // reader for current file entry
3635
blk block // buffer to use as temporary local storage
36+
37+
// err is a persistent error.
38+
// It is only the responsibility of every exported method of Reader to
39+
// ensure that this error is sticky.
40+
err error
3741
}
3842

3943
type parser struct {
@@ -108,9 +112,12 @@ func (tr *Reader) Next() (*Header, error) {
108112
if tr.err != nil {
109113
return nil, tr.err
110114
}
115+
hdr, err := tr.next()
116+
tr.err = err
117+
return hdr, err
118+
}
111119

112-
var hdr *Header
113-
var rawHdr *block
120+
func (tr *Reader) next() (*Header, error) {
114121
var extHdrs map[string]string
115122

116123
// Externally, Next iterates through the tar archive as if it is a series of
@@ -120,34 +127,29 @@ func (tr *Reader) Next() (*Header, error) {
120127
// one or more "header files" until it finds a "normal file".
121128
loop:
122129
for {
123-
tr.err = tr.skipUnread()
124-
if tr.err != nil {
125-
return nil, tr.err
130+
if err := tr.skipUnread(); err != nil {
131+
return nil, err
126132
}
127-
128-
hdr, rawHdr = tr.readHeader()
129-
if tr.err != nil {
130-
return nil, tr.err
133+
hdr, rawHdr, err := tr.readHeader()
134+
if err != nil {
135+
return nil, err
131136
}
132-
133-
tr.err = tr.handleRegularFile(hdr)
134-
if tr.err != nil {
135-
return nil, tr.err
137+
if err := tr.handleRegularFile(hdr); err != nil {
138+
return nil, err
136139
}
137140

138141
// Check for PAX/GNU special headers and files.
139142
switch hdr.Typeflag {
140143
case TypeXHeader:
141-
extHdrs, tr.err = parsePAX(tr)
142-
if tr.err != nil {
143-
return nil, tr.err
144+
extHdrs, err = parsePAX(tr)
145+
if err != nil {
146+
return nil, err
144147
}
145148
continue loop // This is a meta header affecting the next header
146149
case TypeGNULongName, TypeGNULongLink:
147-
var realname []byte
148-
realname, tr.err = ioutil.ReadAll(tr)
149-
if tr.err != nil {
150-
return nil, tr.err
150+
realname, err := ioutil.ReadAll(tr)
151+
if err != nil {
152+
return nil, err
151153
}
152154

153155
// Convert GNU extensions to use PAX headers.
@@ -162,30 +164,28 @@ loop:
162164
extHdrs[paxLinkpath] = p.parseString(realname)
163165
}
164166
if p.err != nil {
165-
tr.err = p.err
166-
return nil, tr.err
167+
return nil, p.err
167168
}
168169
continue loop // This is a meta header affecting the next header
169170
default:
170171
// The old GNU sparse format is handled here since it is technically
171172
// just a regular file with additional attributes.
172173

173-
// TODO(dsnet): We should handle errors reported by mergePAX.
174-
mergePAX(hdr, extHdrs)
174+
if err := mergePAX(hdr, extHdrs); err != nil {
175+
return nil, err
176+
}
175177

176178
// TODO(dsnet): The extended headers may have updated the size.
177179
// Thus, we must setup the regFileReader again here.
178180
//
179181
// See golang.org/issue/15573
180182

181-
tr.err = tr.handleSparseFile(hdr, rawHdr, extHdrs)
182-
if tr.err != nil {
183-
return nil, tr.err
183+
if err := tr.handleSparseFile(hdr, rawHdr, extHdrs); err != nil {
184+
return nil, err
184185
}
185-
break loop // This is a file, so stop
186+
return hdr, nil // This is a file, so stop
186187
}
187188
}
188-
return hdr, nil
189189
}
190190

191191
// handleRegularFile sets up the current file reader and padding such that it
@@ -217,9 +217,9 @@ func (tr *Reader) handleSparseFile(hdr *Header, rawHdr *block, extHdrs map[strin
217217
return p.err
218218
}
219219

220-
sp = tr.readOldGNUSparseMap(rawHdr)
221-
if tr.err != nil {
222-
return tr.err
220+
sp, err = tr.readOldGNUSparseMap(rawHdr)
221+
if err != nil {
222+
return err
223223
}
224224
} else {
225225
sp, err = tr.checkForGNUSparsePAXHeaders(hdr, extHdrs)
@@ -302,53 +302,32 @@ func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]st
302302
// in the header struct overwrite those found in the header
303303
// struct with higher precision or longer values. Esp. useful
304304
// for name and linkname fields.
305-
func mergePAX(hdr *Header, headers map[string]string) error {
305+
func mergePAX(hdr *Header, headers map[string]string) (err error) {
306+
var id64 int64
306307
for k, v := range headers {
307308
switch k {
308309
case paxPath:
309310
hdr.Name = v
310311
case paxLinkpath:
311312
hdr.Linkname = v
312-
case paxGname:
313-
hdr.Gname = v
314313
case paxUname:
315314
hdr.Uname = v
315+
case paxGname:
316+
hdr.Gname = v
316317
case paxUid:
317-
uid, err := strconv.ParseInt(v, 10, 0)
318-
if err != nil {
319-
return err
320-
}
321-
hdr.Uid = int(uid)
318+
id64, err = strconv.ParseInt(v, 10, 0)
319+
hdr.Uid = int(id64)
322320
case paxGid:
323-
gid, err := strconv.ParseInt(v, 10, 0)
324-
if err != nil {
325-
return err
326-
}
327-
hdr.Gid = int(gid)
321+
id64, err = strconv.ParseInt(v, 10, 0)
322+
hdr.Gid = int(id64)
328323
case paxAtime:
329-
t, err := parsePAXTime(v)
330-
if err != nil {
331-
return err
332-
}
333-
hdr.AccessTime = t
324+
hdr.AccessTime, err = parsePAXTime(v)
334325
case paxMtime:
335-
t, err := parsePAXTime(v)
336-
if err != nil {
337-
return err
338-
}
339-
hdr.ModTime = t
326+
hdr.ModTime, err = parsePAXTime(v)
340327
case paxCtime:
341-
t, err := parsePAXTime(v)
342-
if err != nil {
343-
return err
344-
}
345-
hdr.ChangeTime = t
328+
hdr.ChangeTime, err = parsePAXTime(v)
346329
case paxSize:
347-
size, err := strconv.ParseInt(v, 10, 0)
348-
if err != nil {
349-
return err
350-
}
351-
hdr.Size = size
330+
hdr.Size, err = strconv.ParseInt(v, 10, 0)
352331
default:
353332
if strings.HasPrefix(k, paxXattr) {
354333
if hdr.Xattrs == nil {
@@ -357,6 +336,9 @@ func mergePAX(hdr *Header, headers map[string]string) error {
357336
hdr.Xattrs[k[len(paxXattr):]] = v
358337
}
359338
}
339+
if err != nil {
340+
return ErrHeader
341+
}
360342
}
361343
return nil
362344
}
@@ -569,19 +551,17 @@ func (tr *Reader) skipUnread() error {
569551
// Seek seems supported, so perform the real Seek.
570552
pos2, err := sr.Seek(dataSkip-1, io.SeekCurrent)
571553
if err != nil {
572-
tr.err = err
573-
return tr.err
554+
return err
574555
}
575556
seekSkipped = pos2 - pos1
576557
}
577558
}
578559

579-
var copySkipped int64 // Number of bytes skipped via CopyN
580-
copySkipped, tr.err = io.CopyN(ioutil.Discard, tr.r, totalSkip-seekSkipped)
581-
if tr.err == io.EOF && seekSkipped+copySkipped < dataSkip {
582-
tr.err = io.ErrUnexpectedEOF
560+
copySkipped, err := io.CopyN(ioutil.Discard, tr.r, totalSkip-seekSkipped)
561+
if err == io.EOF && seekSkipped+copySkipped < dataSkip {
562+
err = io.ErrUnexpectedEOF
583563
}
584-
return tr.err
564+
return err
585565
}
586566

587567
// readHeader reads the next block header and assumes that the underlying reader
@@ -592,29 +572,25 @@ func (tr *Reader) skipUnread() error {
592572
// * Exactly 0 bytes are read and EOF is hit.
593573
// * Exactly 1 block of zeros is read and EOF is hit.
594574
// * At least 2 blocks of zeros are read.
595-
func (tr *Reader) readHeader() (*Header, *block) {
596-
if _, tr.err = io.ReadFull(tr.r, tr.blk[:]); tr.err != nil {
597-
return nil, nil // io.EOF is okay here
598-
}
599-
575+
func (tr *Reader) readHeader() (*Header, *block, error) {
600576
// Two blocks of zero bytes marks the end of the archive.
577+
if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
578+
return nil, nil, err // EOF is okay here; exactly 0 bytes read
579+
}
601580
if bytes.Equal(tr.blk[:], zeroBlock[:]) {
602-
if _, tr.err = io.ReadFull(tr.r, tr.blk[:]); tr.err != nil {
603-
return nil, nil // io.EOF is okay here
581+
if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil {
582+
return nil, nil, err // EOF is okay here; exactly 1 block of zeros read
604583
}
605584
if bytes.Equal(tr.blk[:], zeroBlock[:]) {
606-
tr.err = io.EOF
607-
} else {
608-
tr.err = ErrHeader // zero block and then non-zero block
585+
return nil, nil, io.EOF // normal EOF; exactly 2 block of zeros read
609586
}
610-
return nil, nil
587+
return nil, nil, ErrHeader // Zero block and then non-zero block
611588
}
612589

613590
// Verify the header matches a known format.
614591
format := tr.blk.GetFormat()
615592
if format == formatUnknown {
616-
tr.err = ErrHeader
617-
return nil, nil
593+
return nil, nil, ErrHeader
618594
}
619595

620596
var p parser
@@ -658,28 +634,21 @@ func (tr *Reader) readHeader() (*Header, *block) {
658634
hdr.Name = prefix + "/" + hdr.Name
659635
}
660636
}
661-
662-
// Check for parsing errors.
663-
if p.err != nil {
664-
tr.err = p.err
665-
return nil, nil
666-
}
667-
return hdr, &tr.blk
637+
return hdr, &tr.blk, p.err
668638
}
669639

670640
// readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format.
671641
// The sparse map is stored in the tar header if it's small enough. If it's larger than four entries,
672642
// then one or more extension headers are used to store the rest of the sparse map.
673-
func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry {
643+
func (tr *Reader) readOldGNUSparseMap(blk *block) ([]sparseEntry, error) {
674644
var p parser
675645
var s sparseArray = blk.GNU().Sparse()
676646
var sp = make([]sparseEntry, 0, s.MaxEntries())
677647
for i := 0; i < s.MaxEntries(); i++ {
678648
offset := p.parseOctal(s.Entry(i).Offset())
679649
numBytes := p.parseOctal(s.Entry(i).NumBytes())
680650
if p.err != nil {
681-
tr.err = p.err
682-
return nil
651+
return nil, p.err
683652
}
684653
if offset == 0 && numBytes == 0 {
685654
break
@@ -690,25 +659,24 @@ func (tr *Reader) readOldGNUSparseMap(blk *block) []sparseEntry {
690659
for s.IsExtended()[0] > 0 {
691660
// There are more entries. Read an extension header and parse its entries.
692661
var blk block
693-
if _, tr.err = io.ReadFull(tr.r, blk[:]); tr.err != nil {
694-
return nil
662+
if _, err := io.ReadFull(tr.r, blk[:]); err != nil {
663+
return nil, err
695664
}
696665
s = blk.Sparse()
697666

698667
for i := 0; i < s.MaxEntries(); i++ {
699668
offset := p.parseOctal(s.Entry(i).Offset())
700669
numBytes := p.parseOctal(s.Entry(i).NumBytes())
701670
if p.err != nil {
702-
tr.err = p.err
703-
return nil
671+
return nil, p.err
704672
}
705673
if offset == 0 && numBytes == 0 {
706674
break
707675
}
708676
sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes})
709677
}
710678
}
711-
return sp
679+
return sp, nil
712680
}
713681

714682
// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format
@@ -836,19 +804,19 @@ func (tr *Reader) numBytes() int64 {
836804
// Calling Read on special types like TypeLink, TypeSymLink, TypeChar,
837805
// TypeBlock, TypeDir, and TypeFifo returns 0, io.EOF regardless of what
838806
// the Header.Size claims.
839-
func (tr *Reader) Read(b []byte) (n int, err error) {
807+
func (tr *Reader) Read(b []byte) (int, error) {
840808
if tr.err != nil {
841809
return 0, tr.err
842810
}
843811
if tr.curr == nil {
844812
return 0, io.EOF
845813
}
846814

847-
n, err = tr.curr.Read(b)
815+
n, err := tr.curr.Read(b)
848816
if err != nil && err != io.EOF {
849817
tr.err = err
850818
}
851-
return
819+
return n, err
852820
}
853821

854822
func (rfr *regFileReader) Read(b []byte) (n int, err error) {

src/archive/tar/reader_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ var untarTests = []*untarTest{
229229
},
230230
},
231231
},
232+
{
233+
file: "testdata/pax-bad-hdr-file.tar",
234+
err: ErrHeader,
235+
},
236+
{
237+
file: "testdata/pax-bad-mtime-file.tar",
238+
err: ErrHeader,
239+
},
232240
{
233241
file: "testdata/nil-uid.tar", // golang.org/issue/5290
234242
headers: []*Header{
2.5 KB
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)