Skip to content

Commit 2ef1912

Browse files
author
Ethan Graham
committed
pkg/kfuzztest: add a golden tests for description generation
1 parent ef822cd commit 2ef1912

File tree

8 files changed

+303
-0
lines changed

8 files changed

+303
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
package kfuzztest
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"path"
9+
"testing"
10+
"time"
11+
12+
"github.com/google/go-cmp/cmp"
13+
"github.com/google/syzkaller/pkg/osutil"
14+
"github.com/google/syzkaller/sys/targets"
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
type testData struct {
19+
dir string
20+
desc string
21+
}
22+
23+
func TestBuildDescriptions(t *testing.T) {
24+
testCases, err := readTestCases("./testdata")
25+
assert.NoError(t, err)
26+
27+
target := targets.Get(targets.Linux, targets.AMD64)
28+
for _, tc := range testCases {
29+
runTest(t, target, tc)
30+
}
31+
}
32+
33+
// Tests that the description inferred from a compiled binary matches an
34+
// expected description.
35+
func runTest(t *testing.T, target *targets.Target, tc testData) {
36+
// Compile the C binary containing the metadata.
37+
cmd := flags(tc.dir)
38+
out, err := osutil.RunCmd(time.Hour, "", target.CCompiler, cmd...)
39+
assert.NoErrorf(t, err, "Failed to compile: %s", string(out))
40+
// Cleanup the compiled binary.
41+
defer func() {
42+
out, err := osutil.RunCmd(time.Hour, "", "rm", path.Join(tc.dir, "bin"))
43+
if err != nil {
44+
assert.NoErrorf(t, err, "Failed to cleanup: %s", string(out))
45+
}
46+
}()
47+
48+
binaryPath := path.Join(tc.dir, "bin")
49+
desc, err := ExtractDescription(binaryPath)
50+
assert.NoError(t, err)
51+
52+
if diffDesc := cmp.Diff(tc.desc, desc); diffDesc != "" {
53+
fmt.Print(diffDesc)
54+
t.Fail()
55+
return
56+
}
57+
}
58+
59+
func flags(testDir string) []string {
60+
return []string{
61+
"-g",
62+
"-T",
63+
path.Join(testDir, "..", "linker.ld"),
64+
"-o",
65+
path.Join(testDir, "bin"),
66+
path.Join(testDir, "prog.c"),
67+
}
68+
}
69+
70+
func readTestCases(dir string) ([]testData, error) {
71+
var testCases []testData
72+
testDirs, err := os.ReadDir(dir)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
for _, subDir := range testDirs {
78+
if !subDir.IsDir() {
79+
continue
80+
}
81+
testData, err := readTestdata(path.Join(dir, subDir.Name()))
82+
if err != nil {
83+
return nil, err
84+
}
85+
testCases = append(testCases, testData)
86+
}
87+
88+
return testCases, nil
89+
}
90+
91+
func readTestdata(testDir string) (testData, error) {
92+
content, err := os.ReadFile(path.Join(testDir, "desc.txt"))
93+
if err != nil {
94+
return testData{}, err
95+
}
96+
97+
return testData{
98+
dir: testDir,
99+
desc: string(content),
100+
}, nil
101+
}

pkg/kfuzztest/testdata/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*bin

pkg/kfuzztest/testdata/1/desc.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This description was automatically generated with tools/kfuzztest-gen
2+
pkcs7_parse_message_arg {
3+
data ptr[in, array[int8]]
4+
datalen len[data, int64]
5+
}
6+
7+
syz_kfuzztest_run$test_pkcs7_parse_message(name ptr[in, string["test_pkcs7_parse_message"]], data ptr[in, pkcs7_parse_message_arg], len bytesize[data]) (kfuzz_test)

pkg/kfuzztest/testdata/1/prog.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
#include "../common.h"
4+
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
8+
struct pkcs7_parse_message_arg {
9+
const void *data;
10+
size_t datalen;
11+
};
12+
13+
DEFINE_FUZZ_TARGET(test_pkcs7_parse_message, struct pkcs7_parse_message_arg);
14+
/* Expect data != NULL. */
15+
DEFINE_CONSTRAINT(pkcs7_parse_message_arg, data, NULL, NULL, EXPECT_NE);
16+
/* Expect datalen == len(data). */
17+
DEFINE_ANNOTATION(pkcs7_parse_message_arg, datalen, data, ATTRIBUTE_LEN);
18+
19+
/* Define a main function, otherwise the compiler complains. */
20+
int main(void)
21+
{
22+
}

pkg/kfuzztest/testdata/2/desc.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This description was automatically generated with tools/kfuzztest-gen
2+
bar {
3+
a int32
4+
b int32
5+
}
6+
7+
foo {
8+
b ptr[inout, bar]
9+
str ptr[in, string]
10+
data ptr[in, array[int8]]
11+
datalen len[data, int64]
12+
numbers ptr[in, array[uint64_t]]
13+
}
14+
15+
syz_kfuzztest_run$some_target(name ptr[in, string["some_target"]], data ptr[in, foo], len bytesize[data]) (kfuzz_test)

pkg/kfuzztest/testdata/2/prog.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
#include "../common.h"
4+
5+
#include <stdlib.h>
6+
7+
struct bar {
8+
int a;
9+
int b;
10+
};
11+
12+
struct foo {
13+
struct bar *b;
14+
const char *str;
15+
const char *data;
16+
size_t datalen;
17+
uint64_t *numbers;
18+
};
19+
20+
DEFINE_FUZZ_TARGET(some_target, struct foo);
21+
/* Expect foo.bar != NULL. */
22+
DEFINE_CONSTRAINT(foo, bar, NULL, NULL, EXPECT_NE);
23+
/* Expect foo.str != NULL. */
24+
DEFINE_CONSTRAINT(foo, str, NULL, NULL, EXPECT_NE);
25+
/* Annotate foo.str as a string. */
26+
DEFINE_ANNOTATION(foo, str, , ATTRIBUTE_STRING);
27+
/* Expect foo.data != NULL. */
28+
DEFINE_CONSTRAINT(foo, data, NULL, NULL, EXPECT_NE);
29+
/* Annotate foo.datalen == len(foo.data). */
30+
DEFINE_ANNOTATION(foo, datalen, data, ATTRIBUTE_LEN);
31+
/* Annotate foo.numbers as an array. */
32+
DEFINE_ANNOTATION(foo, numbers, , ATTRIBUTE_ARRAY);
33+
34+
/* Define a main function, otherwise the compiler complains. */
35+
int main(void)
36+
{
37+
}

pkg/kfuzztest/testdata/common.h

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
// Common struct definitions that ressemble those sound in the kernel source
5+
// under /include/linux/kfuzztest.h. For testing purposes, it is only required
6+
// that these have the same sizes and emitted metadata as the kernel
7+
// definitions, and therefore there is no strict requirement that their fields
8+
// match one-to-one.
9+
#ifndef COMMON_H
10+
#define COMMON_H
11+
12+
#include <stdint.h>
13+
14+
struct kfuzztest_target {
15+
const char *name;
16+
const char *arg_type_name;
17+
uintptr_t write_input_cb;
18+
} __attribute__((aligned(32)));
19+
20+
enum kfuzztest_constraint_type {
21+
EXPECT_EQ,
22+
EXPECT_NE,
23+
EXPECT_LT,
24+
EXPECT_LE,
25+
EXPECT_GT,
26+
EXPECT_GE,
27+
EXPECT_IN_RANGE,
28+
};
29+
30+
struct kfuzztest_constraint {
31+
const char *input_type;
32+
const char *field_name;
33+
uintptr_t value1;
34+
uintptr_t value2;
35+
enum kfuzztest_constraint_type type;
36+
} __attribute__((aligned(64)));
37+
38+
enum kfuzztest_annotation_attribute : uint8_t {
39+
ATTRIBUTE_LEN,
40+
ATTRIBUTE_STRING,
41+
ATTRIBUTE_ARRAY,
42+
};
43+
44+
struct kfuzztest_annotation {
45+
const char *input_type;
46+
const char *field_name;
47+
const char *linked_field_name;
48+
enum kfuzztest_annotation_attribute attrib;
49+
} __attribute__((aligned(32)));
50+
51+
#define DEFINE_FUZZ_TARGET(test_name, test_arg_type) \
52+
struct kfuzztest_target __fuzz_test__##test_name \
53+
__attribute__((section(".kfuzztest_target"), __used__)) = { \
54+
.name = #test_name, \
55+
.arg_type_name = #test_arg_type, \
56+
}; \
57+
/* Avoid the compiler optimizing out the struct definition. */ \
58+
static test_arg_type arg;
59+
60+
#define DEFINE_CONSTRAINT(arg_type, field, val1, val2, tpe) \
61+
static struct kfuzztest_constraint __constraint_##arg_type##_##field \
62+
__attribute__((section(".kfuzztest_constraint"), \
63+
__used__)) = { \
64+
.input_type = "struct " #arg_type, \
65+
.field_name = #field, \
66+
.value1 = (uintptr_t)val1, \
67+
.value2 = (uintptr_t)val2, \
68+
.type = tpe, \
69+
}
70+
71+
#define DEFINE_ANNOTATION(arg_type, field, linked_field, attribute) \
72+
static struct kfuzztest_annotation __annotation_##arg_type##_##field \
73+
__attribute__((section(".kfuzztest_annotation"), \
74+
__used__)) = { \
75+
.input_type = "struct " #arg_type, \
76+
.field_name = #field, \
77+
.linked_field_name = #linked_field, \
78+
.attrib = attribute, \
79+
}
80+
81+
#endif /* COMMON_H */

pkg/kfuzztest/testdata/linker.ld

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* Copyright 2025 syzkaller project authors. All rights reserved. */
2+
/* Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. */
3+
4+
/* Defines a basic linkage script for building kernel-like KFuzzTest metadata into a binary. */
5+
PAGE_SIZE = 0x1000;
6+
7+
PHDRS
8+
{
9+
text PT_LOAD FLAGS(5); /* R, X */
10+
data PT_LOAD FLAGS(6); /* R, W */
11+
}
12+
13+
SECTIONS
14+
{
15+
.text : { *(.text) } :text
16+
17+
.rodata : {
18+
*(.rodata*)
19+
20+
. = ALIGN(PAGE_SIZE);
21+
__kfuzztest_targets_start = .;
22+
KEEP(*(.kfuzztest_target));
23+
__kfuzztest_targets_end = .;
24+
25+
. = ALIGN(PAGE_SIZE);
26+
__kfuzztest_constraints_start = .;
27+
KEEP(*(.kfuzztest_constraint));
28+
__kfuzztest_constraints_end = .;
29+
30+
. = ALIGN(PAGE_SIZE);
31+
__kfuzztest_annotations_start = .;
32+
KEEP(*(.kfuzztest_annotation));
33+
__kfuzztest_annotations_end = .;
34+
35+
} :text
36+
37+
.data : { *(.data) } :data
38+
.bss : { *(.bss) } :data
39+
}

0 commit comments

Comments
 (0)