Skip to content

Commit e56fa36

Browse files
authored
fix(oci): guard scanBootFiles short namePrefix slice (#63) (#64)
The import path passes a short placeholder ("import-0") to scanBootFiles since the layer digest isn't known until the stream is hashed, but the extract-log sliced [:12] assuming a digest — panicking on any layer that carries /boot/vmlinuz* or /boot/initrd.img*. Rename the param to namePrefix (it's a filename prefix, not always a digest) and bound the slice with min(). Other [:12] sites are safe — they all receive the real 64-char hash.
1 parent 49818f8 commit e56fa36

2 files changed

Lines changed: 79 additions & 4 deletions

File tree

images/oci/boot.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func recoverBootFiles(ctx context.Context, layer v1.Layer, workDir string, idx i
8484
return kp, ip
8585
}
8686

87-
func scanBootFiles(ctx context.Context, r io.Reader, workDir, digestHex string) (kernelPath, initrdPath string, err error) {
87+
func scanBootFiles(ctx context.Context, r io.Reader, workDir, namePrefix string) (kernelPath, initrdPath string, err error) {
8888
logger := log.WithFunc("oci.scanBootFiles")
8989

9090
tr := tar.NewReader(r)
@@ -121,9 +121,9 @@ func scanBootFiles(ctx context.Context, r io.Reader, workDir, digestHex string)
121121

122122
var dstPath string
123123
if isKernel {
124-
dstPath = filepath.Join(workDir, digestHex+".vmlinuz")
124+
dstPath = filepath.Join(workDir, namePrefix+".vmlinuz")
125125
} else {
126-
dstPath = filepath.Join(workDir, digestHex+".initrd.img")
126+
dstPath = filepath.Join(workDir, namePrefix+".initrd.img")
127127
}
128128

129129
f, createErr := os.Create(dstPath) //nolint:gosec
@@ -141,7 +141,8 @@ func scanBootFiles(ctx context.Context, r io.Reader, workDir, digestHex string)
141141
} else {
142142
initrdPath = dstPath
143143
}
144-
logger.Debugf(ctx, "Layer %s: extracted %s", digestHex[:12], base)
144+
// namePrefix is a short placeholder on the import path, not always a digest.
145+
logger.Debugf(ctx, "Layer %s: extracted %s", namePrefix[:min(len(namePrefix), 12)], base)
145146
}
146147
return kernelPath, initrdPath, nil
147148
}

images/oci/boot_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package oci
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
)
10+
11+
// Regression for #63: scanBootFiles must not panic on a sub-12-char namePrefix.
12+
func TestScanBootFilesNamePrefixLengths(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
namePrefix string
16+
}{
17+
{"empty prefix", ""},
18+
{"import placeholder under 12 chars", "import-0"},
19+
{"exactly 12 chars", "twelve_chars"},
20+
{"real sha256 hex", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"},
21+
}
22+
for _, tt := range tests {
23+
t.Run(tt.name, func(t *testing.T) {
24+
dir := t.TempDir()
25+
tr := writeBootTar(t, "boot/vmlinuz-6.8.0", "boot/initrd.img-6.8.0")
26+
kernel, initrd, err := scanBootFiles(t.Context(), tr, dir, tt.namePrefix)
27+
if err != nil {
28+
t.Fatalf("scanBootFiles: %v", err)
29+
}
30+
if want := filepath.Join(dir, tt.namePrefix+".vmlinuz"); kernel != want {
31+
t.Errorf("kernel = %q, want %q", kernel, want)
32+
}
33+
if want := filepath.Join(dir, tt.namePrefix+".initrd.img"); initrd != want {
34+
t.Errorf("initrd = %q, want %q", initrd, want)
35+
}
36+
for _, p := range []string{kernel, initrd} {
37+
if _, statErr := os.Stat(p); statErr != nil {
38+
t.Errorf("expected extracted file %s: %v", p, statErr)
39+
}
40+
}
41+
})
42+
}
43+
}
44+
45+
func TestScanBootFilesSkipsNonBoot(t *testing.T) {
46+
dir := t.TempDir()
47+
tr := writeBootTar(t, "usr/bin/vmlinuz-decoy", "boot/vmlinuz-6.8.0.old", "etc/initrd.img")
48+
kernel, initrd, err := scanBootFiles(t.Context(), tr, dir, "import-0")
49+
if err != nil {
50+
t.Fatalf("scanBootFiles: %v", err)
51+
}
52+
if kernel != "" || initrd != "" {
53+
t.Errorf("expected no boot files extracted, got kernel=%q initrd=%q", kernel, initrd)
54+
}
55+
}
56+
57+
func writeBootTar(t *testing.T, paths ...string) *bytes.Reader {
58+
t.Helper()
59+
var buf bytes.Buffer
60+
tw := tar.NewWriter(&buf)
61+
for _, p := range paths {
62+
body := []byte("payload:" + p)
63+
if err := tw.WriteHeader(&tar.Header{Name: p, Typeflag: tar.TypeReg, Mode: 0o644, Size: int64(len(body))}); err != nil {
64+
t.Fatalf("write header %s: %v", p, err)
65+
}
66+
if _, err := tw.Write(body); err != nil {
67+
t.Fatalf("write body %s: %v", p, err)
68+
}
69+
}
70+
if err := tw.Close(); err != nil {
71+
t.Fatalf("close tar: %v", err)
72+
}
73+
return bytes.NewReader(buf.Bytes())
74+
}

0 commit comments

Comments
 (0)