From e73ee978c159a23603e5103428fb3988018dbe43 Mon Sep 17 00:00:00 2001 From: CMGS Date: Tue, 26 May 2026 14:36:09 +0800 Subject: [PATCH] fix(oci): guard scanBootFiles short namePrefix slice (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- images/oci/boot.go | 9 ++--- images/oci/boot_test.go | 74 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 images/oci/boot_test.go diff --git a/images/oci/boot.go b/images/oci/boot.go index 42f092ae..93990f6d 100644 --- a/images/oci/boot.go +++ b/images/oci/boot.go @@ -84,7 +84,7 @@ func recoverBootFiles(ctx context.Context, layer v1.Layer, workDir string, idx i return kp, ip } -func scanBootFiles(ctx context.Context, r io.Reader, workDir, digestHex string) (kernelPath, initrdPath string, err error) { +func scanBootFiles(ctx context.Context, r io.Reader, workDir, namePrefix string) (kernelPath, initrdPath string, err error) { logger := log.WithFunc("oci.scanBootFiles") tr := tar.NewReader(r) @@ -121,9 +121,9 @@ func scanBootFiles(ctx context.Context, r io.Reader, workDir, digestHex string) var dstPath string if isKernel { - dstPath = filepath.Join(workDir, digestHex+".vmlinuz") + dstPath = filepath.Join(workDir, namePrefix+".vmlinuz") } else { - dstPath = filepath.Join(workDir, digestHex+".initrd.img") + dstPath = filepath.Join(workDir, namePrefix+".initrd.img") } f, createErr := os.Create(dstPath) //nolint:gosec @@ -141,7 +141,8 @@ func scanBootFiles(ctx context.Context, r io.Reader, workDir, digestHex string) } else { initrdPath = dstPath } - logger.Debugf(ctx, "Layer %s: extracted %s", digestHex[:12], base) + // namePrefix is a short placeholder on the import path, not always a digest. + logger.Debugf(ctx, "Layer %s: extracted %s", namePrefix[:min(len(namePrefix), 12)], base) } return kernelPath, initrdPath, nil } diff --git a/images/oci/boot_test.go b/images/oci/boot_test.go new file mode 100644 index 00000000..c5e85bd7 --- /dev/null +++ b/images/oci/boot_test.go @@ -0,0 +1,74 @@ +package oci + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" +) + +// Regression for #63: scanBootFiles must not panic on a sub-12-char namePrefix. +func TestScanBootFilesNamePrefixLengths(t *testing.T) { + tests := []struct { + name string + namePrefix string + }{ + {"empty prefix", ""}, + {"import placeholder under 12 chars", "import-0"}, + {"exactly 12 chars", "twelve_chars"}, + {"real sha256 hex", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir := t.TempDir() + tr := writeBootTar(t, "boot/vmlinuz-6.8.0", "boot/initrd.img-6.8.0") + kernel, initrd, err := scanBootFiles(t.Context(), tr, dir, tt.namePrefix) + if err != nil { + t.Fatalf("scanBootFiles: %v", err) + } + if want := filepath.Join(dir, tt.namePrefix+".vmlinuz"); kernel != want { + t.Errorf("kernel = %q, want %q", kernel, want) + } + if want := filepath.Join(dir, tt.namePrefix+".initrd.img"); initrd != want { + t.Errorf("initrd = %q, want %q", initrd, want) + } + for _, p := range []string{kernel, initrd} { + if _, statErr := os.Stat(p); statErr != nil { + t.Errorf("expected extracted file %s: %v", p, statErr) + } + } + }) + } +} + +func TestScanBootFilesSkipsNonBoot(t *testing.T) { + dir := t.TempDir() + tr := writeBootTar(t, "usr/bin/vmlinuz-decoy", "boot/vmlinuz-6.8.0.old", "etc/initrd.img") + kernel, initrd, err := scanBootFiles(t.Context(), tr, dir, "import-0") + if err != nil { + t.Fatalf("scanBootFiles: %v", err) + } + if kernel != "" || initrd != "" { + t.Errorf("expected no boot files extracted, got kernel=%q initrd=%q", kernel, initrd) + } +} + +func writeBootTar(t *testing.T, paths ...string) *bytes.Reader { + t.Helper() + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + for _, p := range paths { + body := []byte("payload:" + p) + if err := tw.WriteHeader(&tar.Header{Name: p, Typeflag: tar.TypeReg, Mode: 0o644, Size: int64(len(body))}); err != nil { + t.Fatalf("write header %s: %v", p, err) + } + if _, err := tw.Write(body); err != nil { + t.Fatalf("write body %s: %v", p, err) + } + } + if err := tw.Close(); err != nil { + t.Fatalf("close tar: %v", err) + } + return bytes.NewReader(buf.Bytes()) +}