diff --git a/pkg/types/alpine/fuzz_test.go b/pkg/types/alpine/fuzz_test.go index 3a4ca3fed..326ae9022 100644 --- a/pkg/types/alpine/fuzz_test.go +++ b/pkg/types/alpine/fuzz_test.go @@ -16,20 +16,290 @@ package alpine import ( + "archive/tar" "bytes" + "compress/gzip" + "io" + "strings" "testing" fuzz "github.com/AdaLogics/go-fuzz-headers" ) +// Allows the fuzzer to create a .SIGN filename +func getSignFilename(ff *fuzz.ConsumeFuzzer) (string, error) { + keyName, err := ff.GetString() + if err != nil { + return "", err + } + var b strings.Builder + b.WriteString(".SIGN.RSA.") + b.WriteString(keyName) + b.WriteString(".rsa.pub") + return b.String(), nil +} + +// createPkgInfoFileContents creates a structured pkginfo file +// +// .PKGINFO files look like this: +// +// # Generated by abuild 3.9.0-r2 +// # using fakeroot version 1.25.3 +// # Wed Jul 6 19:09:49 UTC 2022 +// pkgname = busybox +// pkgver = 1.35.0-r18 +// pkgdesc = Size optimized toolbox of many common UNIX utilities +// url = https://busybox.net/ +// builddate = 1657134589 +// packager = Buildozer +// size = 958464 +// arch = x86_64 +// origin = busybox +// commit = 332d2fff53cd4537d415e15e55e8ceb6fe6eaedb +// maintainer = Sören Tempel +// provider_priority = 100 +// license = GPL-2.0-only +// replaces = busybox-initscripts +// provides = /bin/sh +// triggers = /bin /usr/bin /sbin /usr/sbin /lib/modules/* +// # automatically detected: +// provides = cmd:busybox=1.35.0-r18 +// provides = cmd:sh=1.35.0-r18 +// depend = so:libc.musl-x86_64.so.1 +// datahash = 7d3351ac6c3ebaf18182efb5390061f50d077ce5ade60a15909d91278f70ada7 +func createPkgInfoFileContents(ff *fuzz.ConsumeFuzzer) ([]byte, error) { + var b strings.Builder + noOfRows, err := ff.GetInt() + if err != nil { + return []byte(""), err + } + + // Comments at the top of the pkginfo file + header, err := ff.GetBytes() + if err != nil { + return []byte(""), err + } + b.Write(header) + + for i := 0; i < noOfRows; i++ { + key, err := ff.GetBytes() + if err != nil { + return []byte(""), err + } + value, err := ff.GetBytes() + if err != nil { + return []byte(""), err + } + b.Write(key) + b.Write([]byte(" = ")) + b.Write(value) + b.WriteString("\n") + } + return []byte(b.String()), nil +} + +// Copies files from structured tarBytes to the tw +// This is used when adding files to tarBytes +func copyTarFiles(tw *tar.Writer, tarBytes []byte) error { + tr := tar.NewReader(bytes.NewReader(tarBytes)) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + fileContents, err := io.ReadAll(tr) + if err != nil { + return err + } + tw.WriteHeader(hdr) + tw.Write(fileContents) + } + return nil +} + +// Adds a .SIGN file to tarBytes +func addSignFile(tw *tar.Writer, ff *fuzz.ConsumeFuzzer, tarBytes []byte) error { + err := copyTarFiles(tw, tarBytes) + if err != nil { + return err + } + SIGNFileContents, err := ff.GetBytes() + if err != nil { + return err + } + SIGNFileName, err := getSignFilename(ff) + if err != nil { + return err + } + tw.WriteHeader(&tar.Header{ + Name: SIGNFileName, + Mode: 0644, + Size: int64(len(SIGNFileContents)), + Typeflag: tar.TypeReg, + Gid: 0, + Uid: 0, + }) + tw.Write(SIGNFileContents) + return nil +} + +// Allows the fuzzer to randomize whether a .SIGN file should +// be added to tarBytes +func shouldAddSignFile(ff *fuzz.ConsumeFuzzer, tarBytes []byte) bool { + shouldRequireSIGNFile, err := ff.GetBool() + if err != nil { + return false + } + if shouldRequireSIGNFile { + tr := tar.NewReader(bytes.NewReader(tarBytes)) + for { + hdr, err := tr.Next() + if err == io.EOF { + return false + } + if err != nil { + return false + } + if strings.HasPrefix(hdr.Name, ".SIGN") { + return true + } + } + } + return false +} + +// Allows the fuzzer to randomize whether a .PKGINFO file should +// be added to tarBytes +func shouldAddPkgInfoFile(ff *fuzz.ConsumeFuzzer, tarBytes []byte) bool { + shouldRequirePKGINFOFile, err := ff.GetBool() + if err != nil { + return false + } + if shouldRequirePKGINFOFile { + tr := tar.NewReader(bytes.NewReader(tarBytes)) + for { + hdr, err := tr.Next() + if err == io.EOF { + return false + } + if err != nil { + return false + } + if hdr.Name == ".PKGINFO" { + return true + } + } + } + return false +} + +// Adds the .PKGINFO file to tarBytes +func addPkgInfoFile(tw *tar.Writer, ff *fuzz.ConsumeFuzzer, tarBytes []byte) error { + tr := tar.NewReader(bytes.NewReader(tarBytes)) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + fileContents, err := io.ReadAll(tr) + if err != nil { + return err + } + tw.WriteHeader(hdr) + tw.Write(fileContents) + } + PKGINFOFileContents, err := createPkgInfoFileContents(ff) //nolint:all + if err != nil { + return err + } + tw.WriteHeader(&tar.Header{ + Name: ".PKGINFO", + Mode: 0644, + Size: int64(len(PKGINFOFileContents)), + Typeflag: tar.TypeReg, + Gid: 0, + Uid: 0, + }) + tw.Write(PKGINFOFileContents) + return nil +} + +// FuzzPackageUnmarshal implements the fuzz test func FuzzPackageUnmarshal(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { ff := fuzz.NewConsumer(data) + + // signature segment tarBytes, err := ff.TarBytes() if err != nil { return } + + if shouldAddSignFile(ff, tarBytes) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + err := addSignFile(tw, ff, tarBytes) + if err != nil { + tw.Close() + t.Skip() + } + tw.Close() + tarBytes = buf.Bytes() + } + + // control segment + tarBytes2, err := ff.TarBytes() + if err != nil { + t.Skip() + } + + if shouldAddPkgInfoFile(ff, tarBytes2) { + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + err := addPkgInfoFile(tw, ff, tarBytes) + if err != nil { + tw.Close() + t.Skip() + } + tw.Close() + tarBytes2 = buf.Bytes() + } + + concatenated, err := concatenateTarBytes(tarBytes, tarBytes2) + if err != nil { + t.Skip() + } + p := &Package{} - p.Unmarshal(bytes.NewReader(tarBytes)) + p.Unmarshal(bytes.NewReader(concatenated)) }) } + +// Concatenates two tar archives. +func concatenateTarBytes(tarBytes []byte, tarBytes2 []byte) ([]byte, error) { + var b1 bytes.Buffer + w1 := gzip.NewWriter(&b1) + defer w1.Close() + _, err := w1.Write(tarBytes) + if err != nil { + return []byte(""), err + } + w1.Close() + + var b2 bytes.Buffer + w2 := gzip.NewWriter(&b2) + defer w2.Close() + _, err = w2.Write(tarBytes2) + if err != nil { + return []byte(""), err + } + w2.Close() + concatenated := append(b1.Bytes(), b2.Bytes()...) + return concatenated, nil +}