github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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  		target := target
    42  		sysTarget := targets.Get(target.OS, target.Arch)
    43  		if runtime.GOOS != sysTarget.BuildOS {
    44  			continue
    45  		}
    46  		t.Run(target.OS+"/"+target.Arch, func(t *testing.T) {
    47  			full := !checked[target.OS]
    48  			if !full && testing.Short() {
    49  				return
    50  			}
    51  			if err := sysTarget.BrokenCompiler; err != "" {
    52  				t.Skipf("target compiler is broken: %v", err)
    53  			}
    54  			checked[target.OS] = true
    55  			t.Parallel()
    56  			testTarget(t, target, full)
    57  			testPseudoSyscalls(t, target)
    58  		})
    59  	}
    60  }
    61  
    62  func testPseudoSyscalls(t *testing.T, target *prog.Target) {
    63  	// Use options that are as minimal as possible.
    64  	// We want to ensure that the code can always be compiled.
    65  	opts := Options{
    66  		Slowdown: 1,
    67  	}
    68  	rs := testutil.RandSource(t)
    69  	for _, meta := range target.PseudoSyscalls() {
    70  		p := target.GenSampleProg(meta, rs)
    71  		t.Run(fmt.Sprintf("single_%s", meta.CallName), func(t *testing.T) {
    72  			t.Parallel()
    73  			testOne(t, p, opts)
    74  		})
    75  	}
    76  }
    77  
    78  func testTarget(t *testing.T, target *prog.Target, full bool) {
    79  	rs := testutil.RandSource(t)
    80  	p := target.Generate(rs, 10, target.DefaultChoiceTable())
    81  	// Turns out that fully minimized program can trigger new interesting warnings,
    82  	// e.g. about NULL arguments for functions that require non-NULL arguments in syz_ functions.
    83  	// We could append both AllSyzProg as-is and a minimized version of it,
    84  	// but this makes the NULL argument warnings go away (they showed up in ".constprop" versions).
    85  	// Testing 2 programs takes too long since we have lots of options permutations and OS/arch.
    86  	// So we use the as-is in short tests and minimized version in full tests.
    87  	syzProg := target.GenerateAllSyzProg(rs)
    88  	var opts []Options
    89  	if !full || testing.Short() {
    90  		p.Calls = append(p.Calls, syzProg.Calls...)
    91  		opts = allOptionsSingle(target.OS)
    92  		opts = append(opts, ExecutorOpts)
    93  	} else {
    94  		minimized, _ := prog.Minimize(syzProg, -1, false, func(p *prog.Prog, call int) bool {
    95  			return len(p.Calls) == len(syzProg.Calls)
    96  		})
    97  		p.Calls = append(p.Calls, minimized.Calls...)
    98  		opts = allOptionsPermutations(target.OS)
    99  	}
   100  	// Test various call properties.
   101  	if len(p.Calls) > 0 {
   102  		p.Calls[0].Props.FailNth = 1
   103  	}
   104  	if len(p.Calls) > 1 {
   105  		p.Calls[1].Props.Async = true
   106  	}
   107  	if len(p.Calls) > 2 {
   108  		p.Calls[2].Props.Rerun = 4
   109  	}
   110  	for opti, opts := range opts {
   111  		if testing.Short() && opts.HandleSegv {
   112  			// HandleSegv can radically increase compilation time/memory consumption on large programs.
   113  			// For example, for one program captured from this test enabling HandleSegv increases
   114  			// compilation time from 1.94s to 104.73s and memory consumption from 136MB to 8116MB.
   115  			continue
   116  		}
   117  		opts := opts
   118  		t.Run(fmt.Sprintf("%v", opti), func(t *testing.T) {
   119  			t.Parallel()
   120  			testOne(t, p, opts)
   121  		})
   122  	}
   123  }
   124  
   125  var failedTests uint32
   126  
   127  func testOne(t *testing.T, p *prog.Prog, opts Options) {
   128  	// Each failure produces lots of output (including full C source).
   129  	// Frequently lots of tests fail at the same, which produces/tmp/log
   130  	// tens of thounds of lines of output. Limit amount of output.
   131  	maxFailures := uint32(10)
   132  	if os.Getenv("CI") != "" {
   133  		maxFailures = 1
   134  	}
   135  	if atomic.LoadUint32(&failedTests) > maxFailures {
   136  		return
   137  	}
   138  	src, err := Write(p, opts)
   139  	if err != nil {
   140  		if atomic.AddUint32(&failedTests, 1) > maxFailures {
   141  			t.Fatal()
   142  		}
   143  		t.Logf("opts: %+v\nprogram:\n%s", opts, p.Serialize())
   144  		t.Fatalf("%v", err)
   145  	}
   146  	bin, err := Build(p.Target, src)
   147  	if err != nil {
   148  		if atomic.AddUint32(&failedTests, 1) > maxFailures {
   149  			t.Fatal()
   150  		}
   151  		t.Logf("opts: %+v\nprogram:\n%s", opts, p.Serialize())
   152  		t.Fatalf("%v", err)
   153  	}
   154  	defer os.Remove(bin)
   155  }
   156  
   157  func TestExecutorMacros(t *testing.T) {
   158  	// Ensure that executor does not mis-spell any of the SYZ_* macros.
   159  	target, _ := prog.GetTarget(targets.TestOS, targets.TestArch64)
   160  	p := target.Generate(rand.NewSource(0), 1, target.DefaultChoiceTable())
   161  	expected := commonDefines(p, Options{})
   162  	expected["SYZ_EXECUTOR"] = true
   163  	expected["SYZ_HAVE_SETUP_LOOP"] = true
   164  	expected["SYZ_HAVE_RESET_LOOP"] = true
   165  	expected["SYZ_HAVE_SETUP_TEST"] = true
   166  	expected["SYZ_TEST_COMMON_EXT_EXAMPLE"] = true
   167  	macros := regexp.MustCompile("SYZ_[A-Za-z0-9_]+").FindAllString(string(executor.CommonHeader), -1)
   168  	for _, macro := range macros {
   169  		if strings.HasPrefix(macro, "SYZ_HAVE_") {
   170  			continue
   171  		}
   172  		if _, ok := expected[macro]; !ok {
   173  			t.Errorf("unexpected macro: %v", macro)
   174  		}
   175  	}
   176  }
   177  
   178  func TestSource(t *testing.T) {
   179  	t.Parallel()
   180  	target, err := prog.GetTarget(targets.TestOS, targets.TestArch64)
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  	type Test struct {
   185  		input  string
   186  		output string
   187  	}
   188  	tests := []Test{
   189  		{
   190  			input: `
   191  r0 = csource0(0x1)
   192  csource1(r0)
   193  `,
   194  			output: `
   195  res = syscall(SYS_csource0, /*num=*/1);
   196  if (res != -1)
   197  	r[0] = res;
   198  syscall(SYS_csource1, /*fd=*/r[0]);
   199  `,
   200  		},
   201  		{
   202  			input: `
   203  csource2(&AUTO="12345678")
   204  csource3(&AUTO)
   205  csource4(&AUTO)
   206  csource5(&AUTO)
   207  csource6(&AUTO)
   208  `,
   209  			output: fmt.Sprintf(`
   210  NONFAILING(memcpy((void*)0x%x, "\x12\x34\x56\x78", 4));
   211  syscall(SYS_csource2, /*buf=*/0x%xul);
   212  NONFAILING(memset((void*)0x%x, 0, 10));
   213  syscall(SYS_csource3, /*buf=*/0x%xul);
   214  NONFAILING(memset((void*)0x%x, 48, 10));
   215  syscall(SYS_csource4, /*buf=*/0x%xul);
   216  NONFAILING(memcpy((void*)0x%x, "0101010101", 10));
   217  syscall(SYS_csource5, /*buf=*/0x%xul);
   218  NONFAILING(memcpy((void*)0x%x, "101010101010", 12));
   219  syscall(SYS_csource6, /*buf=*/0x%xul);
   220  `,
   221  				target.DataOffset+0x40, target.DataOffset+0x40,
   222  				target.DataOffset+0x80, target.DataOffset+0x80,
   223  				target.DataOffset+0xc0, target.DataOffset+0xc0,
   224  				target.DataOffset+0x100, target.DataOffset+0x100,
   225  				target.DataOffset+0x140, target.DataOffset+0x140),
   226  		},
   227  		{
   228  			input: `
   229  csource7(0x0)
   230  csource7(0x1)
   231  csource7(0x2)
   232  csource7(0x3)
   233  csource7(0x4)
   234  csource7(0x5)
   235  `,
   236  			output: `
   237  syscall(SYS_csource7, /*flag=*/0ul);
   238  syscall(SYS_csource7, /*flag=BIT_0*/1ul);
   239  syscall(SYS_csource7, /*flag=BIT_1*/2ul);
   240  syscall(SYS_csource7, /*flag=BIT_0_AND_1*/3ul);
   241  syscall(SYS_csource7, /*flag=*/4ul);
   242  syscall(SYS_csource7, /*flag=BIT_0|0x4*/5ul);
   243  `,
   244  		},
   245  	}
   246  	for i, test := range tests {
   247  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   248  			p, err := target.Deserialize([]byte(test.input), prog.Strict)
   249  			if err != nil {
   250  				t.Fatal(err)
   251  			}
   252  			ctx := &context{
   253  				p:         p,
   254  				target:    target,
   255  				sysTarget: targets.Get(target.OS, target.Arch),
   256  			}
   257  			calls, _, err := ctx.generateProgCalls(p, false)
   258  			if err != nil {
   259  				t.Fatal(err)
   260  			}
   261  			got := regexp.MustCompile(`(\n|^)\t`).ReplaceAllString(strings.Join(calls, ""), "\n")
   262  			if test.output != got {
   263  				t.Fatalf("input:\n%v\nwant:\n%v\ngot:\n%v", test.input, test.output, got)
   264  			}
   265  		})
   266  	}
   267  }
   268  
   269  func generateSandboxFunctionSignatureTestCase(t *testing.T, sandbox string, sandboxArg int, expected, message string) {
   270  	actual := generateSandboxFunctionSignature(sandbox, sandboxArg)
   271  	assert.Equal(t, actual, expected, message)
   272  }
   273  
   274  func TestGenerateSandboxFunctionSignature(t *testing.T) {
   275  	// This test-case intentionally omits the following edge cases:
   276  	// - sandbox name as whitespaces, tabs
   277  	// - control chars \r, \n and unprintables
   278  	// - unsuitable chars - punctuation, emojis, '#', '*', etc
   279  	// - character case mismatching function prototype defined in common_linux.h.
   280  	//   For example 'do_sandbox_android' and 'AnDroid'.
   281  	// - non english letters, unicode compound characters
   282  	// and focuses on correct handling of sandboxes supporting and not 'sandbox_arg'
   283  	// config setting.
   284  	generateSandboxFunctionSignatureTestCase(t,
   285  		"",        // sandbox name
   286  		0,         // sandbox arg
   287  		"loop();", // expected
   288  		"Empty sandbox name should produce 'loop();'")
   289  
   290  	generateSandboxFunctionSignatureTestCase(t,
   291  		"abrakadabra",               // sandbox name
   292  		0,                           // sandbox arg
   293  		"do_sandbox_abrakadabra();", // expected
   294  		"Empty sandbox name should produce 'loop();'")
   295  
   296  	generateSandboxFunctionSignatureTestCase(t,
   297  		"android",                    // sandbox name
   298  		-1234,                        // sandbox arg
   299  		"do_sandbox_android(-1234);", // expected
   300  		"Android sandbox function requires an argument")
   301  }