diff options
| author | Ethan Graham <ethangraham@google.com> | 2025-09-19 15:44:59 +0000 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2025-09-22 09:11:54 +0000 |
| commit | 490f32238051336d5a498cbc3ecc47140052b502 (patch) | |
| tree | b2baa30374181952a39000373a0cf1c5952d21fe /prog | |
| parent | c9f0a99247f6d9a6df877720609cbce3dca73b55 (diff) | |
kfuzztest: introduce syz_kfuzztest_run pseudo-syscall
Add syz_kfuzztest_run pseudo-syscall, KFuzzTest attribute, and encoding
logic.
KFuzzTest targets, which are invoked in the executor with the new
syz_kfuzztest_run pseudo-syscall, require specialized encoding. To
differentiate KFuzzTest calls from standard syzkaller calls, we
introduce a new attribute called KFuzzTest or "kfuzz_test" in syzkaller
descriptions that can be used to annotate calls.
Signed-off-by: Ethan Graham <ethangraham@google.com>
Diffstat (limited to 'prog')
| -rw-r--r-- | prog/encodingexec.go | 59 | ||||
| -rw-r--r-- | prog/kfuzztest.go | 293 | ||||
| -rw-r--r-- | prog/kfuzztest_test.go | 262 | ||||
| -rw-r--r-- | prog/types.go | 1 |
4 files changed, 615 insertions, 0 deletions
diff --git a/prog/encodingexec.go b/prog/encodingexec.go index fb8e5fbaf..f3b48924c 100644 --- a/prog/encodingexec.go +++ b/prog/encodingexec.go @@ -88,6 +88,14 @@ func (p *Prog) SerializeForExec() ([]byte, error) { } func (w *execContext) serializeCall(c *Call) { + // We introduce special serialization logic for kfuzztest targets, which + // require special handling due to their use of relocation tables to copy + // entire blobs of data into the kenrel. + if c.Meta.Attrs.KFuzzTest { + w.serializeKFuzzTestCall(c) + return + } + // Calculate arg offsets within structs. // Generate copyin instructions that fill in data into pointer arguments. w.writeCopyin(c) @@ -119,6 +127,57 @@ func (w *execContext) serializeCall(c *Call) { w.writeCopyout(c) } +// KFuzzTest targets require special handling due to their use of relocation +// tables for serializing all data (including pointed-to data) into a +// continuous blob that can be passed into the kernel. +func (w *execContext) serializeKFuzzTestCall(c *Call) { + if !c.Meta.Attrs.KFuzzTest { + // This is a specialized function that shouldn't be called on anything + // other than an instance of a syz_kfuzztest_run$* syscall + panic("serializeKFuzzTestCall called on an invalid syscall") + } + + // Write the initial string argument (test name) normally. + w.writeCopyin(&Call{Meta: c.Meta, Args: []Arg{c.Args[0]}}) + + // Args[1] is the second argument to syz_kfuzztest_run, which is a pointer + // to some struct input. This is the data that must be flattened and sent + // to the fuzzing driver with a relocation table. + dataArg := c.Args[1].(*PointerArg) + finalBlob := MarshallKFuzztestArg(dataArg.Res) + + // Reuse the memory address that was pre-allocated for the original struct + // argument. This avoids needing to hook into the memory allocation which + // is done at a higher level than the serialization. This relies on the + // original buffer being large enough + blobAddress := w.target.PhysicalAddr(dataArg) - w.target.DataOffset + + // Write the entire marshalled blob as a raw byte array. + w.write(execInstrCopyin) + w.write(blobAddress) + w.write(execArgData) + w.write(uint64(len(finalBlob))) + w.buf = append(w.buf, finalBlob...) + + // Update the value of the length arg which should now match the length of + // the byte array that we created. Previously, it contained the bytesize + // of the struct argument passed into the pseudo-syscall. + lenArg := c.Args[2].(*ConstArg) + lenArg.Val = uint64(len(finalBlob)) + + // Generate the final syscall instruction with the update arguments. + kFuzzTestRunID, err := w.target.KFuzzTestRunID() + if err != nil { + panic(err) + } + w.write(uint64(kFuzzTestRunID)) + w.write(ExecNoCopyout) + w.write(uint64(len(c.Args))) + for _, arg := range c.Args { + w.writeArg(arg) + } +} + type execContext struct { target *Target buf []byte diff --git a/prog/kfuzztest.go b/prog/kfuzztest.go new file mode 100644 index 000000000..1ff65e29a --- /dev/null +++ b/prog/kfuzztest.go @@ -0,0 +1,293 @@ +// Copyright 2025 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. +package prog + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +const ( + kFuzzTestRegionIDNull uint32 = ^uint32(0) + kFuzzTestPoisonSize uint64 = 0x8 + + kFuzzTestVersion uint32 = 0 + kFuzzTestMagic uint32 = 0xBFACE + kFuzzTestPrefixSize = 8 + + // Minimum region alignment required by KFuzzTest. This is exposed by the + // /sys/kernel/debug/kfuzztest/_config/minalign debugfs file. This value + // always equals MAX(ARCH_KMALLOC_MINALIGN, KFUZZTEST_POISON_SIZE) = 8 on + // x86_64, so we hardcode it for now. A more robust solution would involve + // reading this from the debugfs entry at boot before fuzzing begins. + kFuzzTestMinalign uint64 = 8 +) + +func kFuzzTestWritePrefix(buf *bytes.Buffer) { + prefix := (uint64(kFuzzTestVersion) << 32) | uint64(kFuzzTestMagic) + binary.Write(buf, binary.LittleEndian, prefix) +} + +func isPowerOfTwo(n uint64) bool { + return n > 0 && (n&(n-1) == 0) +} + +func roundUpPowerOfTwo(x, n uint64) uint64 { + if !isPowerOfTwo(n) { + panic("n was not a power of 2") + } + return (x + n - 1) &^ (n - 1) +} + +// Pad b so that it's length is a multiple of alignment, with at least +// minPadding bytes of padding, where alignment is a power of 2. +func padWithAlignment(b *bytes.Buffer, alignment, minPadding uint64) { + var newSize uint64 + if alignment == 0 { + newSize = uint64(b.Len()) + minPadding + } else { + newSize = roundUpPowerOfTwo(uint64(b.Len())+minPadding, alignment) + } + + paddingBytes := newSize - uint64(b.Len()) + for range paddingBytes { + b.WriteByte(byte(0)) + } +} + +type sliceQueue[T any] struct { + q []T +} + +func (sq *sliceQueue[T]) push(elem T) { + sq.q = append(sq.q, elem) +} + +func (sq *sliceQueue[T]) pop() T { + ret := sq.q[0] + sq.q = sq.q[1:] + return ret +} + +func (sq *sliceQueue[T]) isEmpty() bool { + return len(sq.q) == 0 +} + +func newSliceQueue[T any]() *sliceQueue[T] { + return &sliceQueue[T]{q: make([]T, 0)} +} + +type kFuzzTestRelocation struct { + offset uint32 + srcRegion Arg + dstRegion Arg +} + +type kFuzzTestRegion struct { + offset uint32 + size uint32 +} + +// The following helpers and definitions follow directly from the C-struct +// definitions in <include/linux/kfuzztest.h>. +const kFuzzTestRegionSize = 8 + +func kFuzzTestRegionArraySize(numRegions int) int { + return 4 + kFuzzTestRegionSize*numRegions +} + +func kFuzzTestWriteRegion(buf *bytes.Buffer, region kFuzzTestRegion) { + binary.Write(buf, binary.LittleEndian, region.offset) + binary.Write(buf, binary.LittleEndian, region.size) +} + +func kFuzzTestWriteRegionArray(buf *bytes.Buffer, regions []kFuzzTestRegion) { + binary.Write(buf, binary.LittleEndian, uint32(len(regions))) + for _, reg := range regions { + kFuzzTestWriteRegion(buf, reg) + } +} + +const kFuzzTestRelocationSize = 12 + +func kFuzzTestRelocTableSize(numRelocs int) int { + return 8 + kFuzzTestRelocationSize*numRelocs +} + +func kFuzzTestWriteReloc(buf *bytes.Buffer, regToID *map[Arg]int, reloc kFuzzTestRelocation) { + binary.Write(buf, binary.LittleEndian, uint32((*regToID)[reloc.srcRegion])) + binary.Write(buf, binary.LittleEndian, reloc.offset) + if reloc.dstRegion == nil { + binary.Write(buf, binary.LittleEndian, kFuzzTestRegionIDNull) + } else { + binary.Write(buf, binary.LittleEndian, uint32((*regToID)[reloc.dstRegion])) + } +} + +func kFuzzTestWriteRelocTable(buf *bytes.Buffer, regToID *map[Arg]int, + relocations []kFuzzTestRelocation, paddingBytes uint64) { + binary.Write(buf, binary.LittleEndian, uint32(len(relocations))) + binary.Write(buf, binary.LittleEndian, uint32(paddingBytes)) + for _, reloc := range relocations { + kFuzzTestWriteReloc(buf, regToID, reloc) + } + buf.Write(make([]byte, paddingBytes)) +} + +const kFuzzTestPlaceHolderPtr uint64 = 0xFFFFFFFFFFFFFFFF + +// Expands a region, and returns a list of relocations that need to be made. +func kFuzzTestExpandRegion(reg Arg) ([]byte, []kFuzzTestRelocation) { + relocations := []kFuzzTestRelocation{} + var encoded bytes.Buffer + queue := newSliceQueue[Arg]() + queue.push(reg) + + for !queue.isEmpty() { + arg := queue.pop() + padWithAlignment(&encoded, arg.Type().Alignment(), 0) + + switch a := arg.(type) { + case *PointerArg: + offset := uint32(encoded.Len()) + binary.Write(&encoded, binary.LittleEndian, kFuzzTestPlaceHolderPtr) + relocations = append(relocations, kFuzzTestRelocation{offset, reg, a.Res}) + case *GroupArg: + for _, inner := range a.Inner { + queue.push(inner) + } + case *DataArg: + data := a.data + buffer, ok := a.ArgCommon.Type().(*BufferType) + if !ok { + panic("DataArg should be a BufferType") + } + // Unlike length fields whose incorrectness can be prevented easily, + // it is an invasive change to prevent generation of + // non-null-terminated strings. Forcibly null-terminating them + // during encoding allows us to centralize this easily and prevent + // false positive buffer overflows in KFuzzTest targets. + if buffer.Kind == BufferString && (len(data) == 0 || data[len(data)-1] != byte(0)) { + data = append(data, byte(0)) + } + encoded.Write(data) + case *ConstArg: + val, _ := a.Value() + switch a.Size() { + case 1: + binary.Write(&encoded, binary.LittleEndian, uint8(val)) + case 2: + binary.Write(&encoded, binary.LittleEndian, uint16(val)) + case 4: + binary.Write(&encoded, binary.LittleEndian, uint32(val)) + case 8: + binary.Write(&encoded, binary.LittleEndian, val) + default: + panic(fmt.Sprintf("unsupported constant size: %d", a.Size())) + } + // TODO: handle union args. + default: + panic(fmt.Sprintf("tried to serialize unsupported type: %s", a.Type().Name())) + } + } + + return encoded.Bytes(), relocations +} + +// MarshallKFuzzTestArg serializes a syzkaller Arg into a flat binary format +// understood by the KFuzzTest kernel interface (see `include/linux/kfuzztest.h`). +// +// The goal is to represent a tree-like structure of arguments (which may contain +// pointers and cycles) as a single byte slice that the kernel can deserialize +// into a set of distinct heap allocations. +// +// The binary format consists of three contiguous parts, in this order: +// +// 1. Region Array: A header describing all logical memory regions that will be +// allocated by the kernel. Each `relocRegion` defines a region's unique `id`, +// its `size`, its `alignment`, and its `start` offset within the payload. +// The kernel uses this table to create one distinct heap allocation per region. +// +// 2. Relocation Table: A header containing a list of `relocationEntry` structs. +// Each entry identifies the location of a pointer field within the payload +// (via a `regionID` and `regionOffset`) and maps it to the logical region +// it points to (via a `value` which holds the pointee's `regionID`). +// A NULL pointer is identified by the special value `kFuzzTestNilPtrVal`. +// +// 3. Payload: The raw, serialized data for all arguments, laid out as a single +// contiguous block of memory with padded regions as per the KFuzzTest input +// format's specification defined in `Documentation/dev-tools/kfuzztest.rst`. +// +// Cycles are handled by tracking visited arguments, ensuring that a region is +// only visited and encoded once. +// +// For a concrete example of the final binary layout, see the test cases for this +// function in `prog/kfuzztest_test.go`. +func MarshallKFuzztestArg(topLevel Arg) []byte { + regions := []kFuzzTestRegion{} + allRelocations := []kFuzzTestRelocation{} + visitedRegions := make(map[Arg]int) + queue := newSliceQueue[Arg]() + var payload bytes.Buffer + queue.push(topLevel) + maxAlignment := uint64(8) + + if topLevel == nil { + return []byte{} + } + +Loop: + for { + if queue.isEmpty() { + break Loop + } + + reg := queue.pop() + if _, visited := visitedRegions[reg]; visited { + continue Loop + } + + alignment := max(kFuzzTestMinalign, reg.Type().Alignment()) + maxAlignment = max(maxAlignment, alignment) + + regionData, relocations := kFuzzTestExpandRegion(reg) + for _, reloc := range relocations { + if reloc.dstRegion == nil { + continue + } + if _, visited := visitedRegions[reloc.dstRegion]; !visited { + queue.push(reloc.dstRegion) + } + } + allRelocations = append(allRelocations, relocations...) + + padWithAlignment(&payload, alignment, 0) + regions = append(regions, kFuzzTestRegion{ + offset: uint32(payload.Len()), + size: uint32(len(regionData))}, + ) + visitedRegions[reg] = len(regions) - 1 + payload.Write(regionData) + // The end of the payload should have at least kFuzzTestPoisonSize bytes + // of padding, and be aligned to kFuzzTestPoisonSize. + padWithAlignment(&payload, kFuzzTestPoisonSize, kFuzzTestPoisonSize) + } + + headerLen := 0x8 // Two integer values - the magic value, and the version number. + regionArrayLen := kFuzzTestRegionArraySize(len(regions)) + relocTableLen := kFuzzTestRelocTableSize(len(allRelocations)) + metadataLen := headerLen + regionArrayLen + relocTableLen + + // The payload needs to be aligned to max alignment to ensure that all + // nested structs are properly aligned, and there should be enough padding + // so that the region before the payload can be poisoned with a redzone. + paddingBytes := roundUpPowerOfTwo(uint64(metadataLen)+kFuzzTestPoisonSize, maxAlignment) - uint64(metadataLen) + + var out bytes.Buffer + kFuzzTestWritePrefix(&out) + kFuzzTestWriteRegionArray(&out, regions) + kFuzzTestWriteRelocTable(&out, &visitedRegions, allRelocations, paddingBytes) + out.Write(payload.Bytes()) + return out.Bytes() +} diff --git a/prog/kfuzztest_test.go b/prog/kfuzztest_test.go new file mode 100644 index 000000000..5f1b3d9c4 --- /dev/null +++ b/prog/kfuzztest_test.go @@ -0,0 +1,262 @@ +// Copyright 2025 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. +package prog + +import ( + "bytes" + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" +) + +type testCase struct { + prog string + extractArg func(*Prog) Arg + regionArray []any + relocationTable []any + payload []any +} + +func TestRoundUpPowerOfTwo(t *testing.T) { + if res := roundUpPowerOfTwo(9, 8); res != 16 { + t.Fatalf("expected 16, got %d", res) + } + if res := roundUpPowerOfTwo(21, 4); res != 24 { + t.Fatalf("expected 24, got %d", res) + } + if res := roundUpPowerOfTwo(113, 16); res != 128 { + t.Fatalf("expected 24, got %d", res) + } +} + +func createBuffer(data []any) []byte { + var buf bytes.Buffer + + for _, d := range data { + switch val := d.(type) { + case uint8, uint16, uint32, uint64: + binary.Write(&buf, binary.LittleEndian, val) + case []byte: + buf.Write(val) + } + } + + return buf.Bytes() +} + +func createPrefix() []byte { + var prefix bytes.Buffer + binary.Write(&prefix, binary.LittleEndian, kFuzzTestMagic) + binary.Write(&prefix, binary.LittleEndian, uint32(0)) + return prefix.Bytes() +} + +//nolint:all +func TestMarshallKFuzzTestArg(t *testing.T) { + testCases := []testCase{ + // This test case validates the encoding of the following structure: + // msg: ptr[in, msghdr_netlink[netlink_msg_xfrm]] { + // msghdr_netlink[netlink_msg_xfrm] { + // addr: nil + // addrlen: len = 0x0 (4 bytes) + // pad = 0x0 (4 bytes) + // vec: ptr[in, iovec[in, netlink_msg_xfrm]] { + // iovec[in, netlink_msg_xfrm] { + // addr: ptr[inout, array[ANYUNION]] { + // array[ANYUNION] { + // } + // } + // len: len = 0x33fe0 (8 bytes) + // } + // } + // vlen: const = 0x1 (8 bytes) + // ctrl: const = 0x0 (8 bytes) + // ctrllen: const = 0x0 (8 bytes) + // f: send_flags = 0x0 (4 bytes) + // pad = 0x0 (4 bytes) + // } + // } + { + `r0 = openat$cgroup_ro(0xffffffffffffff9c, &(0x7f00000003c0)='cpuacct.stat\x00', 0x26e1, 0x0) +sendmsg$nl_xfrm(r0, &(0x7f0000000240)={0x0, 0x0, &(0x7f0000000080)={&(0x7f00000001c0)=ANY=[], 0x33fe0}}, 0x0)`, + func(p *Prog) Arg { + sendMsgCall := p.Calls[1] + msgHdr := sendMsgCall.Args[1].(*PointerArg).Res + return msgHdr + }, + []any{ + uint32(3), // Num regions. + + // Region definitions: (offset, size) pairs. + uint32(0), uint32(0x38), + uint32(0x40), uint32(0x10), + uint32(0x58), uint32(0x0), + }, + []any{ + uint32(3), // Num entries. + uint32(0x8), // Bytes of padding. + + // Relocation definitions: (source region, offset, dest region) triplets. + uint32(0), uint32(0x00), kFuzzTestRegionIDNull, + uint32(0), uint32(0x10), uint32(1), + uint32(1), uint32(0x00), uint32(2), + uint64(0x0), // 8 bytes of padding. + }, + []any{ + // Region 0 data. + kFuzzTestPlaceHolderPtr, // `addr` field, placeholder pointer. + uint32(0x0), // `addrlen`. + uint32(0x0), // `pad[4]`. + kFuzzTestPlaceHolderPtr, // `vec` field, placeholder pointer. + uint64(0x1), // `vlen`. + uint64(0x0), // `ctrl`. + uint64(0x0), // `ctrllen`. + uint32(0x0), // `f`. + uint32(0x0), // `pad[4]`. + + uint64(0x0), // 8 bytes of padding between regions. + + // Region 1 data. + kFuzzTestPlaceHolderPtr, // `addr` field, placeholder pointer. + uint64(0x033fe0), // `len`. + + make([]byte, kFuzzTestPoisonSize), // Inter-region padding. + + []byte{}, // Region 2 data (empty). + + make([]byte, kFuzzTestPoisonSize), // Tail padding. + }, + }, + // This test case validates the encoding of the following structure: + // loop_info64 { + // lo_device: const = 0x0 (8 bytes) + // lo_inode: const = 0x0 (8 bytes) + // lo_rdevice: const = 0x0 (8 bytes) + // lo_offset: int64 = 0x1 (8 bytes) + // lo_sizelimit: int64 = 0x8005 (8 bytes) + // lo_number: const = 0x0 (4 bytes) + // lo_enc_type: lo_encrypt_type = 0x0 (4 bytes) + // lo_enc_key_size: int32 = 0x19 (4 bytes) + // lo_flags: lo_flags = 0x1c (4 bytes) + // lo_file_name: buffer: {ef 35 9f 41 3b b9 38 52 f7 d6 a4 ae 6d dd fb + // d1 ce 5d 29 c2 ee 5e 5c a9 00 0f f8 ee 09 e7 37 ff 0e df 11 0f f4 11 + // 76 39 c2 eb 4b 78 c6 60 e6 77 df 70 19 05 b9 aa fa b4 af aa f7 55 a3 + // f6 a0 04} (length 0x40) lo_crypt_name: buffer: {03 6c 47 c6 78 08 20 + // d1 cb f7 96 6d 61 fd cf 33 52 63 bd 9b ff bc c2 54 2d ed 71 03 82 59 + // ca 17 1c e1 a3 11 ef 54 ec 32 d7 1e 14 ef 3d c1 77 e9 b4 8b 00 00 00 + // 00 00 00 00 00 00 00 00 00 00 00} (length 0x40) lo_enc_key: buffer: + // {f2 83 59 73 8e 22 9a 4c 66 81 00 00 00 00 00 d3 00 e6 d6 02 00 00 + // 00 00 00 00 00 00 00 00 00 01} (length 0x20) lo_init: array[int64] { + // int64 = 0x204 (8 bytes) + // int64 = 0x0 (8 bytes) + // } + // } + // } + // ] + { + `r0 = open(&(0x7f0000000000)='./bus\x00', 0x0, 0x0) +ioctl$LOOP_SET_STATUS64(r0, 0x4c04, &(0x7f0000000540)={0x0, 0x0, 0x0, 0x1, 0x8005, 0x0, 0x0, 0x19, 0x1c, "ef359f413bb93852f7d6a4ae6dddfbd1ce5d29c2ee5e5ca9000ff8ee09e737ff0edf110ff4117639c2eb4b78c660e677df701905b9aafab4afaaf755a3f6a004", "036c47c6780820d1cbf7966d61fdcf335263bd9bffbcc2542ded71038259ca171ce1a311ef54ec32d71e14ef3dc177e9b48b00", "f28359738e229a4c66810000000000d300e6d602000000000000000000000001", [0x204]})`, + func(p *Prog) Arg { + ioctlCall := p.Calls[1] + ptrArg := ioctlCall.Args[2].(*PointerArg) + ret := ptrArg.Res + return ret + }, + []any{ + uint32(1), // Num regions. + + // Region definitions: (offset, size) pairs. + uint32(0), uint32(0xe8), + }, + []any{ + uint32(0), // Num entries. + uint32(12), // Number of bytes of padding. + make([]byte, 12), // Padding. + }, + []any{ + uint64(0x0), // `lo_device`. + uint64(0x0), // `lo_inode`. + uint64(0x0), // `lo_rdevice`. + uint64(0x1), // `lo_offset`. + uint64(0x8005), // `lo_sizelimit`. + uint32(0x0), // `lo_number`. + uint32(0x0), // `lo_enc_type`. + uint32(0x19), // `lo_enc_key_size`. + uint32(0x1c), // `lo_flags`. + []byte{ + 0xef, 0x35, 0x9f, 0x41, 0x3b, 0xb9, 0x38, 0x52, + 0xf7, 0xd6, 0xa4, 0xae, 0x6d, 0xdd, 0xfb, 0xd1, + 0xce, 0x5d, 0x29, 0xc2, 0xee, 0x5e, 0x5c, 0xa9, + 0x00, 0x0f, 0xf8, 0xee, 0x09, 0xe7, 0x37, 0xff, + 0x0e, 0xdf, 0x11, 0x0f, 0xf4, 0x11, 0x76, 0x39, + 0xc2, 0xeb, 0x4b, 0x78, 0xc6, 0x60, 0xe6, 0x77, + 0xdf, 0x70, 0x19, 0x05, 0xb9, 0xaa, 0xfa, 0xb4, + 0xaf, 0xaa, 0xf7, 0x55, 0xa3, 0xf6, 0xa0, 0x04, + }, // `lo_file_name`. + []byte{ + 0x03, 0x6c, 0x47, 0xc6, 0x78, 0x08, 0x20, 0xd1, + 0xcb, 0xf7, 0x96, 0x6d, 0x61, 0xfd, 0xcf, 0x33, + 0x52, 0x63, 0xbd, 0x9b, 0xff, 0xbc, 0xc2, 0x54, + 0x2d, 0xed, 0x71, 0x03, 0x82, 0x59, 0xca, 0x17, + 0x1c, 0xe1, 0xa3, 0x11, 0xef, 0x54, 0xec, 0x32, + 0xd7, 0x1e, 0x14, 0xef, 0x3d, 0xc1, 0x77, 0xe9, + 0xb4, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }, // `lo_crypt_name`. + []byte{ + 0xf2, 0x83, 0x59, 0x73, 0x8e, 0x22, 0x9a, 0x4c, + 0x66, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd3, + 0x00, 0xe6, 0xd6, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, // `lo_enc_key`. + uint64(0x204), // `lo_init[0]. + uint64(0x0), // `lo_init[1]. + + make([]byte, kFuzzTestPoisonSize), // Tail padding. + }, + }, + } + + for _, tc := range testCases { + testOne(t, tc) + } +} + +func testOne(t *testing.T, tc testCase) { + target, err := GetTarget("linux", "amd64") + if err != nil { + t.Fatal(err) + } + p, err := target.Deserialize([]byte(tc.prog), NonStrict) + if err != nil { + t.Fatal(err) + } + + arg := tc.extractArg(p) + encoded := MarshallKFuzztestArg(arg) + + wantPrefix := createPrefix() + wantRegionArray := createBuffer(tc.regionArray) + wantRelocTable := createBuffer(tc.relocationTable) + wantPayload := createBuffer(tc.payload) + + regionArrayLen := len(wantRegionArray) + relocTableLen := len(wantRelocTable) + payloadLen := len(wantPayload) + + if len(encoded) != kFuzzTestPrefixSize+regionArrayLen+relocTableLen+payloadLen { + t.Fatalf("encoded output has wrong total length: got %d, want %d", + len(encoded), regionArrayLen+relocTableLen+payloadLen) + } + + gotPrefix := encoded[:kFuzzTestPrefixSize] + gotRegionArray := encoded[kFuzzTestPrefixSize : kFuzzTestPrefixSize+regionArrayLen] + gotRelocTable := encoded[kFuzzTestPrefixSize+regionArrayLen : kFuzzTestPrefixSize+regionArrayLen+relocTableLen] + gotPayload := encoded[kFuzzTestPrefixSize+regionArrayLen+relocTableLen:] + + assert.Equal(t, wantPrefix, gotPrefix) + assert.Equal(t, wantRegionArray, gotRegionArray) + assert.Equal(t, wantRelocTable, gotRelocTable) + assert.Equal(t, wantPayload, gotPayload) +} diff --git a/prog/types.go b/prog/types.go index 5a170dad1..cb64603fe 100644 --- a/prog/types.go +++ b/prog/types.go @@ -48,6 +48,7 @@ type SyscallAttrs struct { RemoteCover bool Automatic bool AutomaticHelper bool + KFuzzTest bool Fsck string // Filesystem is used in tools/syz-imagegen when fs name cannot be deduced from // the part after $. |
