github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/csource/csource_test.go (about) 1 // Copyright 2015 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 package csource 5 6 import ( 7 "fmt" 8 "math/rand" 9 "os" 10 "regexp" 11 "runtime" 12 "strings" 13 "sync/atomic" 14 "testing" 15 16 "github.com/google/syzkaller/executor" 17 "github.com/google/syzkaller/pkg/testutil" 18 "github.com/google/syzkaller/prog" 19 _ "github.com/google/syzkaller/sys" 20 "github.com/google/syzkaller/sys/targets" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 func init() { 25 // csource tests consume too much memory under race detector (>1GB), 26 // and periodically timeout on Travis. So we skip them. 27 if testutil.RaceEnabled { 28 for _, arg := range os.Args[1:] { 29 if strings.Contains(arg, "-test.short") { 30 fmt.Printf("skipping race testing in short mode\n") 31 os.Exit(0) 32 } 33 } 34 } 35 } 36 37 func TestGenerate(t *testing.T) { 38 t.Parallel() 39 checked := make(map[string]bool) 40 for _, target := range prog.AllTargets() { 41 // Auto-generated descriptions currently do not properly mark arch-specific syscalls, see 42 // https://github.com/google/syzkaller/issues/5410#issuecomment-3570190241. 43 // Until it's fixed, let's remove these syscalls from csource tests. 44 ct := target.NoAutoChoiceTable() 45 sysTarget := targets.Get(target.OS, target.Arch) 46 if runtime.GOOS != sysTarget.BuildOS { 47 continue 48 } 49 t.Run(target.OS+"/"+target.Arch, func(t *testing.T) { 50 if err := sysTarget.BrokenCompiler; err != "" { 51 t.Skipf("target compiler is broken: %v", err) 52 } 53 full := !checked[target.OS] 54 if full || !testing.Short() { 55 checked[target.OS] = true 56 t.Parallel() 57 testTarget(t, target, full, ct) 58 } 59 testPseudoSyscalls(t, target, ct) 60 }) 61 } 62 } 63 64 func testPseudoSyscalls(t *testing.T, target *prog.Target, ct *prog.ChoiceTable) { 65 // Use options that are as minimal as possible. 66 // We want to ensure that the code can always be compiled. 67 opts := Options{ 68 Slowdown: 1, 69 } 70 rs := testutil.RandSource(t) 71 for _, meta := range target.PseudoSyscalls() { 72 p := target.GenSampleProg(meta, rs, ct) 73 t.Run(fmt.Sprintf("single_%s", meta.CallName), func(t *testing.T) { 74 t.Parallel() 75 testOne(t, p, opts) 76 }) 77 } 78 } 79 80 func testTarget(t *testing.T, target *prog.Target, full bool, ct *prog.ChoiceTable) { 81 rs := testutil.RandSource(t) 82 p := target.Generate(rs, 10, ct) 83 // Turns out that fully minimized program can trigger new interesting warnings, 84 // e.g. about NULL arguments for functions that require non-NULL arguments in syz_ functions. 85 // We could append both AllSyzProg as-is and a minimized version of it, 86 // but this makes the NULL argument warnings go away (they showed up in ".constprop" versions). 87 // Testing 2 programs takes too long since we have lots of options permutations and OS/arch. 88 // So we use the as-is in short tests and minimized version in full tests. 89 syzProg := target.GenerateAllSyzProg(rs) 90 var opts []Options 91 if !full || testing.Short() { 92 p.Calls = append(p.Calls, syzProg.Calls...) 93 opts = allOptionsSingle(target.OS) 94 opts = append(opts, ExecutorOpts) 95 } else { 96 minimized, _ := prog.Minimize(syzProg, -1, prog.MinimizeCorpus, func(p *prog.Prog, call int) bool { 97 return len(p.Calls) == len(syzProg.Calls) 98 }) 99 p.Calls = append(p.Calls, minimized.Calls...) 100 opts = allOptionsPermutations(target.OS) 101 } 102 // Test various call properties. 103 if len(p.Calls) > 0 { 104 p.Calls[0].Props.FailNth = 1 105 } 106 if len(p.Calls) > 1 { 107 p.Calls[1].Props.Async = true 108 } 109 if len(p.Calls) > 2 { 110 p.Calls[2].Props.Rerun = 4 111 } 112 for opti, opts := range opts { 113 if testing.Short() && opts.HandleSegv { 114 // HandleSegv can radically increase compilation time/memory consumption on large programs. 115 // For example, for one program captured from this test enabling HandleSegv increases 116 // compilation time from 1.94s to 104.73s and memory consumption from 136MB to 8116MB. 117 continue 118 } 119 t.Run(fmt.Sprintf("%v", opti), func(t *testing.T) { 120 t.Parallel() 121 testOne(t, p, opts) 122 }) 123 } 124 } 125 126 var failedTests uint32 127 128 func testOne(t *testing.T, p *prog.Prog, opts Options) { 129 // Each failure produces lots of output (including full C source). 130 // Frequently lots of tests fail at the same, which produces/tmp/log 131 // tens of thounds of lines of output. Limit amount of output. 132 maxFailures := uint32(10) 133 if os.Getenv("CI") != "" { 134 maxFailures = 1 135 } 136 if atomic.LoadUint32(&failedTests) > maxFailures { 137 return 138 } 139 src, err := Write(p, opts) 140 if err != nil { 141 if atomic.AddUint32(&failedTests, 1) > maxFailures { 142 t.Fatal() 143 } 144 t.Logf("opts: %+v\nprogram:\n%s", opts, p.Serialize()) 145 t.Fatalf("%v", err) 146 } 147 // Executor headers are embedded into the C source. Make sure there are no leftover include guards. 148 if matches := regexp.MustCompile(`(?m)^#define\s+\S+_H\s*\n`).FindAllString(string(src), -1); len(matches) > 0 { 149 t.Fatalf("source contains leftover include guards: %v\nopts: %+v\nprogram:\n%s", 150 matches, opts, p.Serialize()) 151 } 152 bin, err := Build(p.Target, src) 153 if err != nil { 154 if atomic.AddUint32(&failedTests, 1) > maxFailures { 155 t.Fatal() 156 } 157 t.Logf("opts: %+v\nprogram:\n%s", opts, p.Serialize()) 158 t.Fatalf("%v", err) 159 } 160 defer os.Remove(bin) 161 } 162 163 func TestExecutorMacros(t *testing.T) { 164 // Ensure that executor does not mis-spell any of the SYZ_* macros. 165 target, _ := prog.GetTarget(targets.TestOS, targets.TestArch64) 166 p := target.Generate(rand.NewSource(0), 1, target.DefaultChoiceTable()) 167 expected := commonDefines(p, Options{}) 168 expected["SYZ_EXECUTOR"] = true 169 expected["SYZ_HAVE_SETUP_LOOP"] = true 170 expected["SYZ_HAVE_RESET_LOOP"] = true 171 expected["SYZ_HAVE_SETUP_TEST"] = true 172 expected["SYZ_TEST_COMMON_EXT_EXAMPLE"] = true 173 macros := regexp.MustCompile("SYZ_[A-Za-z0-9_]+").FindAllString(string(executor.CommonHeader), -1) 174 for _, macro := range macros { 175 if strings.HasPrefix(macro, "SYZ_HAVE_") { 176 continue 177 } 178 if _, ok := expected[macro]; !ok { 179 t.Errorf("unexpected macro: %v", macro) 180 } 181 } 182 } 183 184 func TestSource(t *testing.T) { 185 t.Parallel() 186 187 target32, err := prog.GetTarget(targets.TestOS, targets.TestArch32) 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 target64, err := prog.GetTarget(targets.TestOS, targets.TestArch64) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 type Test struct { 198 input string 199 output string 200 target *prog.Target // target64 by default. 201 } 202 tests := []Test{ 203 { 204 input: ` 205 r0 = csource0(0x1) 206 csource1(r0) 207 `, 208 output: ` 209 res = syscall(SYS_csource0, /*num=*/1); 210 if (res != -1) 211 r[0] = res; 212 syscall(SYS_csource1, /*fd=*/r[0]); 213 `, 214 }, 215 { 216 input: ` 217 csource2(&AUTO="12345678") 218 csource3(&AUTO) 219 csource4(&AUTO) 220 csource5(&AUTO) 221 csource6(&AUTO) 222 `, 223 output: fmt.Sprintf(` 224 NONFAILING(memcpy((void*)0x%x, "\x12\x34\x56\x78", 4)); 225 syscall(SYS_csource2, /*buf=*/0x%xul); 226 NONFAILING(memset((void*)0x%x, 0, 10)); 227 syscall(SYS_csource3, /*buf=*/0x%xul); 228 NONFAILING(memset((void*)0x%x, 48, 10)); 229 syscall(SYS_csource4, /*buf=*/0x%xul); 230 NONFAILING(memcpy((void*)0x%x, "0101010101", 10)); 231 syscall(SYS_csource5, /*buf=*/0x%xul); 232 NONFAILING(memcpy((void*)0x%x, "101010101010", 12)); 233 syscall(SYS_csource6, /*buf=*/0x%xul); 234 `, 235 target64.DataOffset+0x40, target64.DataOffset+0x40, 236 target64.DataOffset+0x80, target64.DataOffset+0x80, 237 target64.DataOffset+0xc0, target64.DataOffset+0xc0, 238 target64.DataOffset+0x100, target64.DataOffset+0x100, 239 target64.DataOffset+0x140, target64.DataOffset+0x140), 240 }, 241 { 242 input: ` 243 csource7(0x0) 244 csource7(0x1) 245 csource7(0x2) 246 csource7(0x3) 247 csource7(0x4) 248 csource7(0x5) 249 `, 250 output: ` 251 syscall(SYS_csource7, /*flag=*/0ul); 252 syscall(SYS_csource7, /*flag=BIT_0*/1ul); 253 syscall(SYS_csource7, /*flag=BIT_1*/2ul); 254 syscall(SYS_csource7, /*flag=BIT_0_AND_1*/3ul); 255 syscall(SYS_csource7, /*flag=*/4ul); 256 syscall(SYS_csource7, /*flag=BIT_0|0x4*/5ul); 257 `, 258 }, 259 260 { 261 input: ` 262 csource0(0xffffffff) 263 csource8(0xffffffffffffffff) 264 `, 265 output: ` 266 syscall(SYS_csource0, /*num=*/(intptr_t)-1); 267 syscall(SYS_csource8, /*num=*/(intptr_t)-1); 268 `, 269 }, 270 { 271 input: ` 272 csource0(0xffffffff) 273 csource8(0xffffffffffffffff) 274 `, 275 output: ` 276 syscall(SYS_csource0, /*num=*/(intptr_t)-1); 277 syscall(SYS_csource8, /*num=*/(intptr_t)-1); 278 `, 279 target: target32, 280 }, 281 } 282 for i, test := range tests { 283 t.Run(fmt.Sprint(i), func(t *testing.T) { 284 if test.target == nil { 285 test.target = target64 286 } 287 p, err := test.target.Deserialize([]byte(test.input), prog.Strict) 288 if err != nil { 289 t.Fatal(err) 290 } 291 ctx := &context{ 292 p: p, 293 target: test.target, 294 sysTarget: targets.Get(test.target.OS, test.target.Arch), 295 } 296 // Disable comment generation, as it's not the focus of these tests. 297 // This simplifies the expected output. For tests covering comments, see 298 // /pkg/csource/syscall_generation_test.go. 299 calls, _, err := ctx.generateProgCalls(p, false, false) 300 if err != nil { 301 t.Fatal(err) 302 } 303 got := regexp.MustCompile(`(\n|^)\t`).ReplaceAllString(strings.Join(calls, ""), "\n") 304 if test.output != got { 305 t.Fatalf("input:\n%v\nwant:\n%v\ngot:\n%v", test.input, test.output, got) 306 } 307 }) 308 } 309 } 310 311 func generateSandboxFunctionSignatureTestCase(t *testing.T, sandbox string, sandboxArg int, expected, message string) { 312 actual := generateSandboxFunctionSignature(sandbox, sandboxArg) 313 assert.Equal(t, actual, expected, message) 314 } 315 316 func TestGenerateSandboxFunctionSignature(t *testing.T) { 317 // This test-case intentionally omits the following edge cases: 318 // - sandbox name as whitespaces, tabs 319 // - control chars \r, \n and unprintables 320 // - unsuitable chars - punctuation, emojis, '#', '*', etc 321 // - character case mismatching function prototype defined in common_linux.h. 322 // For example 'do_sandbox_android' and 'AnDroid'. 323 // - non english letters, unicode compound characters 324 // and focuses on correct handling of sandboxes supporting and not 'sandbox_arg' 325 // config setting. 326 generateSandboxFunctionSignatureTestCase(t, 327 "", // sandbox name 328 0, // sandbox arg 329 "loop();", // expected 330 "Empty sandbox name should produce 'loop();'") 331 332 generateSandboxFunctionSignatureTestCase(t, 333 "abrakadabra", // sandbox name 334 0, // sandbox arg 335 "do_sandbox_abrakadabra();", // expected 336 "Empty sandbox name should produce 'loop();'") 337 338 generateSandboxFunctionSignatureTestCase(t, 339 "android", // sandbox name 340 -1234, // sandbox arg 341 "do_sandbox_android(-1234);", // expected 342 "Android sandbox function requires an argument") 343 }