imagecat,kernelcat: bound staged download, hash before extract
Both Fetch flows previously streamed resp.Body straight into zstd → tar → on-disk extractor with the SHA256 check tacked on at the END. A bad mirror or an attacker that's compromised the catalog host could ship a multi-gigabyte tarball, watch banger expand it to disk, and only THEN see the helpful "sha256 mismatch" message — having already filled the host filesystem. Reorder the operations: stage the compressed tarball to a temp file under the destination directory through an io.LimitReader (cap +1 bytes), hash on the way in, refuse to decompress if either the cap trips or the SHA mismatches. Worst-case disk use is bounded by the cap, not by the source. Cap is exposed as a package var (MaxFetchedBundleBytes, MaxFetchedKernelBytes) so callers can tune per-deployment and tests can squeeze it down to provoke the rejection. Default 8 GiB — generous enough for a 4 GiB rootfs (which compresses to ~1-2 GiB), tight enough to make a "fill the host disk" attack expensive. The temp file lives in the destination dir so extraction stays on the same filesystem and we don't pay for cross-FS rename. defer os.Remove cleans up; the existing per-package cleanup() handler still removes any partial extraction on hash mismatch / extraction failure. Tests: each package gets a TestFetchRejectsOversizedTarballBefore Extraction that sets the cap to 64 bytes, points Fetch at a multi-KB tarball, and asserts (a) error mentions "cap", (b) destination dir is left clean (no leaked rootfs / manifest / kernel tree). All existing tests still pass — happy path, hash mismatch, missing files, path traversal, HTTP error, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3805b093b4
commit
4004ce2e7e
4 changed files with 172 additions and 28 deletions
|
|
@ -140,6 +140,39 @@ func TestFetchRejectsShaMismatch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestFetchRejectsOversizedTarballBeforeExtraction pins the new
|
||||
// disk-bound cap: with MaxFetchedKernelBytes set artificially low the
|
||||
// staged download trips the limit and refuses to decompress, so a
|
||||
// compromised mirror can't fill the host disk before the SHA check
|
||||
// fires.
|
||||
func TestFetchRejectsOversizedTarballBeforeExtraction(t *testing.T) {
|
||||
body, sum := buildTestTarball(t, []tarballFile{
|
||||
{name: "vmlinux", data: bytes.Repeat([]byte("k"), 4096)},
|
||||
})
|
||||
srv := serveTarball(t, body)
|
||||
|
||||
prev := MaxFetchedKernelBytes
|
||||
MaxFetchedKernelBytes = 64
|
||||
t.Cleanup(func() { MaxFetchedKernelBytes = prev })
|
||||
|
||||
kernelsDir := t.TempDir()
|
||||
_, err := Fetch(context.Background(), nil, kernelsDir, CatEntry{
|
||||
Name: "void-6.12",
|
||||
TarballURL: srv.URL + "/pkg.tar.zst",
|
||||
TarballSHA256: sum,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Fetch succeeded against oversized tarball; want size-cap rejection")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "cap") {
|
||||
t.Fatalf("err = %v, want size-cap message", err)
|
||||
}
|
||||
// targetDir should be cleaned up by the existing cleanup() path.
|
||||
if _, statErr := os.Stat(filepath.Join(kernelsDir, "void-6.12")); !os.IsNotExist(statErr) {
|
||||
t.Fatalf("target dir should be removed on size-cap rejection: %v", statErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchRejectsMissingKernel(t *testing.T) {
|
||||
t.Parallel()
|
||||
body, sum := buildTestTarball(t, []tarballFile{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue