aboutsummaryrefslogtreecommitdiffstats
path: root/prog
diff options
context:
space:
mode:
authorEthan Graham <ethangraham@google.com>2025-09-19 15:44:59 +0000
committerAleksandr Nogikh <nogikh@google.com>2025-09-22 09:11:54 +0000
commit490f32238051336d5a498cbc3ecc47140052b502 (patch)
treeb2baa30374181952a39000373a0cf1c5952d21fe /prog
parentc9f0a99247f6d9a6df877720609cbce3dca73b55 (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.go59
-rw-r--r--prog/kfuzztest.go293
-rw-r--r--prog/kfuzztest_test.go262
-rw-r--r--prog/types.go1
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 $.