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  }