Skip to content

Commit 0398955

Browse files
committed
estargz: Fix panic in go 1.27RC by emitting footer manually
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
1 parent 5909768 commit 0398955

3 files changed

Lines changed: 51 additions & 26 deletions

File tree

estargz/externaltoc/externaltoc.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,20 @@ const FooterSize = 46
126126

127127
// gzipFooterBytes returns the 104 bytes footer.
128128
func gzipFooterBytes() ([]byte, error) {
129-
buf := bytes.NewBuffer(make([]byte, 0, FooterSize))
130-
gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) // MUST be NoCompression to keep 51 bytes
131-
132-
// Extra header indicating the offset of TOCJSON
129+
// Extra header indicating the external toc
133130
// https://tools.ietf.org/html/rfc1952#section-2.3.1.1
134131
header := make([]byte, 4)
135132
header[0], header[1] = 'S', 'G'
136133
subfield := "STARGZEXTERNALTOC" // len("STARGZEXTERNALTOC") = 17
137134
binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952
138-
gz.Extra = append(header, []byte(subfield)...)
139-
if err := gz.Close(); err != nil {
140-
return nil, err
141-
}
142-
if buf.Len() != FooterSize {
143-
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize))
135+
extra := append(header, []byte(subfield)...)
136+
137+
buf := estargz.CreateGzipFooter(extra)
138+
139+
if len(buf) != FooterSize {
140+
panic(fmt.Sprintf("footer buffer = %d, not %d", len(buf), FooterSize))
144141
}
145-
return buf.Bytes(), nil
142+
return buf, nil
146143
}
147144

148145
func NewGzipDecompressor(provideTOCFunc func() ([]byte, error)) *GzipDecompressor {

estargz/gzip.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,21 +100,52 @@ func (gc *GzipCompressor) WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, d
100100

101101
// gzipFooterBytes returns the 51 bytes footer.
102102
func gzipFooterBytes(tocOff int64) []byte {
103-
buf := bytes.NewBuffer(make([]byte, 0, FooterSize))
104-
gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) // MUST be NoCompression to keep 51 bytes
105-
106103
// Extra header indicating the offset of TOCJSON
107104
// https://tools.ietf.org/html/rfc1952#section-2.3.1.1
108105
header := make([]byte, 4)
109106
header[0], header[1] = 'S', 'G'
110107
subfield := fmt.Sprintf("%016xSTARGZ", tocOff)
111108
binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952
112-
gz.Extra = append(header, []byte(subfield)...)
113-
gz.Close()
114-
if buf.Len() != FooterSize {
115-
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize))
109+
extra := append(header, []byte(subfield)...)
110+
111+
buf := CreateGzipFooter(extra)
112+
if len(buf) != FooterSize {
113+
panic(fmt.Sprintf("footer buffer = %d, not %d", len(buf), FooterSize))
116114
}
117-
return buf.Bytes()
115+
return buf
116+
}
117+
118+
// CreateGzipFooter creates eStargz's footer with the specified extra field.
119+
func CreateGzipFooter(extra []byte) []byte {
120+
// Gzip header (10 bytes)
121+
// https://datatracker.ietf.org/doc/html/rfc1952
122+
buf := make([]byte, 10)
123+
buf[0] = 0x1f // ID1
124+
buf[1] = 0x8b // ID2
125+
buf[2] = 8 // "deflate"
126+
buf[3] = 0x4 // FEXTRA
127+
// MTIME = 0
128+
// XFL = 0
129+
buf[9] = 255 // unknown
130+
131+
// Extra field
132+
xlen := make([]byte, 2)
133+
binary.LittleEndian.PutUint16(xlen, uint16(len(extra)))
134+
buf = append(buf, append(xlen, extra...)...)
135+
136+
// Flate header (5 bytes)
137+
// https://datatracker.ietf.org/doc/html/rfc1951
138+
flateHeader := []byte{
139+
1, // BFINAL = 1 (last block), BFTYPE = 0 (no compression), remaining bits are ignored
140+
0, 0, // LEN = 0
141+
0xff, 0xff, // NLEN (the one's complement of LEN)
142+
}
143+
buf = append(buf, flateHeader...)
144+
145+
// Gzip footer (8 bytes): CRC32 = 0, ISIZE = 0
146+
buf = append(buf, make([]byte, 8)...)
147+
148+
return buf
118149
}
119150

120151
type GzipDecompressor struct{}

estargz/gzip_test.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,9 @@ func TestGzipParseFooterInvalidExtra(t *testing.T) {
151151
}
152152

153153
func legacyFooterBytes(tocOff int64) []byte {
154-
buf := bytes.NewBuffer(make([]byte, 0, legacyFooterSize))
155-
gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression)
156-
gz.Extra = fmt.Appendf(nil, "%016xSTARGZ", tocOff)
157-
gz.Close()
158-
if buf.Len() != legacyFooterSize {
159-
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), legacyFooterSize))
154+
buf := CreateGzipFooter(fmt.Appendf(nil, "%016xSTARGZ", tocOff))
155+
if len(buf) != legacyFooterSize {
156+
panic(fmt.Sprintf("footer buffer = %d, not %d", len(buf), legacyFooterSize))
160157
}
161-
return buf.Bytes()
158+
return buf
162159
}

0 commit comments

Comments
 (0)