Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzzing: improve alpine fuzzer #1273

Merged
merged 1 commit into from
Jan 15, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 271 additions & 1 deletion pkg/types/alpine/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <developer@email.org>
// size = 958464
// arch = x86_64
// origin = busybox
// commit = 332d2fff53cd4537d415e15e55e8ceb6fe6eaedb
// maintainer = Sören Tempel <soeren+alpine@soeren-tempel.net>
// 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
}