github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/report/linux_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 report
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/google/syzkaller/pkg/mgrconfig"
    17  	"github.com/google/syzkaller/pkg/osutil"
    18  	"github.com/google/syzkaller/pkg/symbolizer"
    19  	"github.com/google/syzkaller/pkg/vminfo"
    20  	"github.com/google/syzkaller/sys/targets"
    21  	"github.com/stretchr/testify/assert"
    22  )
    23  
    24  func TestLinuxIgnores(t *testing.T) {
    25  	cfg := &mgrconfig.Config{
    26  		Derived: mgrconfig.Derived{
    27  			TargetOS:   targets.Linux,
    28  			TargetArch: targets.AMD64,
    29  		},
    30  	}
    31  	reporter, err := NewReporter(cfg)
    32  	if err != nil {
    33  		t.Fatal(err)
    34  	}
    35  	cfg.Ignores = []string{"BUG: bug3"}
    36  	reporter1, err := NewReporter(cfg)
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  	cfg.Ignores = []string{"BUG: bug3", "BUG: bug1"}
    41  	reporter2, err := NewReporter(cfg)
    42  	if err != nil {
    43  		t.Fatal(err)
    44  	}
    45  	cfg.Ignores = []string{"BUG: bug3", "BUG: bug1", "BUG: bug2"}
    46  	reporter3, err := NewReporter(cfg)
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  
    51  	const log = `
    52  [    0.000000] BUG: bug1
    53  [    0.000000] BUG: bug2
    54  	`
    55  	if !reporter.ContainsCrash([]byte(log)) {
    56  		t.Fatalf("no crash")
    57  	}
    58  	if rep := reporter.Parse([]byte(log)); rep.Title != "BUG: bug1" {
    59  		t.Fatalf("want `BUG: bug1`, found `%v`", rep.Title)
    60  	}
    61  
    62  	if !reporter1.ContainsCrash([]byte(log)) {
    63  		t.Fatalf("no crash")
    64  	}
    65  	if rep := reporter1.Parse([]byte(log)); rep.Title != "BUG: bug1" {
    66  		t.Fatalf("want `BUG: bug1`, found `%v`", rep.Title)
    67  	}
    68  
    69  	if !reporter2.ContainsCrash([]byte(log)) {
    70  		t.Fatalf("no crash")
    71  	}
    72  	if rep := reporter2.Parse([]byte(log)); rep.Title != "BUG: bug2" {
    73  		t.Fatalf("want `BUG: bug2`, found `%v`", rep.Title)
    74  	}
    75  
    76  	if reporter3.ContainsCrash([]byte(log)) {
    77  		t.Fatalf("found crash, should be ignored")
    78  	}
    79  	if rep := reporter3.Parse([]byte(log)); rep != nil {
    80  		t.Fatalf("found `%v`, should be ignored", rep.Title)
    81  	}
    82  }
    83  
    84  func TestLinuxSymbolizeLine(t *testing.T) {
    85  	tests := []struct {
    86  		line   string
    87  		result string
    88  	}{
    89  		// Normal symbolization.
    90  		{
    91  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x101/0x185\n",
    92  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x101/0x185 foo.c:555\n",
    93  		},
    94  		{
    95  			"RIP: 0010:[<ffffffff8188c0e6>]  [<ffffffff8188c0e6>]  foo+0x101/0x185\n",
    96  			"RIP: 0010:[<ffffffff8188c0e6>]  [<ffffffff8188c0e6>]  foo+0x101/0x185 foo.c:550\n",
    97  		},
    98  		// Strip "./" file prefix.
    99  		{
   100  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x111/0x185\n",
   101  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x111/0x185 foo.h:111\n",
   102  		},
   103  		// Needs symbolization, but symbolizer returns nothing.
   104  		{
   105  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x121/0x185\n",
   106  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x121/0x185\n",
   107  		},
   108  		// Needs symbolization, but symbolizer returns error.
   109  		{
   110  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x131/0x185\n",
   111  			"[ 2713.153531]  [<ffffffff82d1b1d9>] foo+0x131/0x185\n",
   112  		},
   113  		// Needs symbolization, but symbol is missing.
   114  		{
   115  			"[ 2713.153531]  [<ffffffff82d1b1d9>] bar+0x131/0x185\n",
   116  			"[ 2713.153531]  [<ffffffff82d1b1d9>] bar+0x131/0x185\n",
   117  		},
   118  		// Bad offset.
   119  		{
   120  			"[ 2713.153531]  [<ffffffff82d1b1d9>] bar+0xffffffffffffffffffff/0x185\n",
   121  			"[ 2713.153531]  [<ffffffff82d1b1d9>] bar+0xffffffffffffffffffff/0x185\n",
   122  		},
   123  		// Should not be symbolized.
   124  		{
   125  			"WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 foo+0x101/0x185\n",
   126  			"WARNING: CPU: 2 PID: 2636 at ipc/shm.c:162 foo+0x101/0x185 foo.c:555\n",
   127  		},
   128  		// Tricky function name.
   129  		{
   130  			"    [<ffffffff84e5bea0>] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 \n",
   131  			"    [<ffffffff84e5bea0>] do_ipv6_setsockopt.isra.7.part.3+0x101/0x2830 net.c:111 \n",
   132  		},
   133  		// Old KASAN frame format (with tab).
   134  		{
   135  			"[   50.419727] 	baz+0x101/0x200\n",
   136  			"[   50.419727] 	baz+0x101/0x200 baz.c:100\n",
   137  		},
   138  		// Inlined frames.
   139  		{
   140  			"    [<ffffffff84e5bea0>] foo+0x141/0x185\n",
   141  			"    [<ffffffff84e5bea0>] inlined1 net.c:111 [inline]\n" +
   142  				"    [<ffffffff84e5bea0>] inlined2 mm.c:222 [inline]\n" +
   143  				"    [<ffffffff84e5bea0>] foo+0x141/0x185 kasan.c:333\n",
   144  		},
   145  		// Several symbols with the same name.
   146  		{
   147  			"[<ffffffff82d1b1d9>] baz+0x101/0x200\n",
   148  			"[<ffffffff82d1b1d9>] baz+0x101/0x200 baz.c:100\n",
   149  		},
   150  		// Frame format with module+offset.
   151  		{
   152  			"[   50.419727][ T3822] baz+0x101/0x200 [beep]\n",
   153  			"[   50.419727][ T3822] baz+0x101/0x200 baz.c:100 [beep]\n",
   154  		},
   155  		// Frame format with module+offset and stracktrace_build_id.
   156  		{
   157  			"[   50.419727][ T3822] baz+0x101/0x200 [beep b31b29679ab712c360bddd861f655ab24898b4db]\n",
   158  			"[   50.419727][ T3822] baz+0x101/0x200 baz.c:100 [beep]\n",
   159  		},
   160  
   161  		// Frame format with module+offset for invalid module.
   162  		{
   163  			"[   50.419727][ T3822] baz+0x101/0x200 [invalid_module]\n",
   164  			"[   50.419727][ T3822] baz+0x101/0x200 [invalid_module]\n",
   165  		},
   166  		// Frame format with module+offset for missing symbol.
   167  		{
   168  			"[   50.419727][ T3822] missing_symbol+0x101/0x200 [beep]\n",
   169  			"[   50.419727][ T3822] missing_symbol+0x101/0x200 [beep]\n",
   170  		},
   171  		// Frame format with module+offset for invalid offset.
   172  		{
   173  			"[   50.419727][ T3822] baz+0x300/0x200 [beep]\n",
   174  			"[   50.419727][ T3822] baz+0x300/0x200 [beep]\n",
   175  		},
   176  	}
   177  	symbols := map[string]map[string][]symbolizer.Symbol{
   178  		"": {
   179  			"foo": {
   180  				{Addr: 0x1000000, Size: 0x190},
   181  			},
   182  			"do_ipv6_setsockopt.isra.7.part.3": {
   183  				{Addr: 0x2000000, Size: 0x2830},
   184  			},
   185  			"baz": {
   186  				{Addr: 0x3000000, Size: 0x100},
   187  				{Addr: 0x4000000, Size: 0x200},
   188  				{Addr: 0x5000000, Size: 0x300},
   189  			},
   190  		},
   191  		"beep": {
   192  			"baz": {
   193  				{Addr: 0x4000000, Size: 0x200},
   194  			},
   195  		},
   196  	}
   197  	symb := func(bin string, pc uint64) ([]symbolizer.Frame, error) {
   198  		if bin == "beep" {
   199  			switch pc {
   200  			case 0x4000100:
   201  				return []symbolizer.Frame{
   202  					{
   203  						File: "/linux/baz.c",
   204  						Line: 100,
   205  					},
   206  				}, nil
   207  			default:
   208  				return nil, fmt.Errorf("unknown pc 0x%x", pc)
   209  			}
   210  		}
   211  		if bin != "vmlinux" {
   212  			return nil, fmt.Errorf("unknown pc 0x%x", pc)
   213  		}
   214  		switch pc {
   215  		case 0x1000100:
   216  			return []symbolizer.Frame{
   217  				{
   218  					File: "/linux/foo.c",
   219  					Line: 555,
   220  				},
   221  			}, nil
   222  		case 0x1000101:
   223  			return []symbolizer.Frame{
   224  				{
   225  					File: "/linux/foo.c",
   226  					Line: 550,
   227  				},
   228  			}, nil
   229  		case 0x1000110:
   230  			return []symbolizer.Frame{
   231  				{
   232  					File: "/linux/./foo.h",
   233  					Line: 111,
   234  				},
   235  			}, nil
   236  		case 0x1000120:
   237  			return nil, nil
   238  		case 0x1000130:
   239  			return nil, fmt.Errorf("unknown pc 0x%x", pc)
   240  		case 0x2000100:
   241  			return []symbolizer.Frame{
   242  				{
   243  					File: "/linux/net.c",
   244  					Line: 111,
   245  				},
   246  			}, nil
   247  		case 0x1000140:
   248  			return []symbolizer.Frame{
   249  				{
   250  					Func:   "inlined1",
   251  					File:   "/linux/net.c",
   252  					Line:   111,
   253  					Inline: true,
   254  				},
   255  				{
   256  					Func:   "inlined2",
   257  					File:   "/linux/mm.c",
   258  					Line:   222,
   259  					Inline: true,
   260  				},
   261  				{
   262  					Func:   "noninlined3",
   263  					File:   "/linux/kasan.c",
   264  					Line:   333,
   265  					Inline: false,
   266  				},
   267  			}, nil
   268  		case 0x4000100:
   269  			return []symbolizer.Frame{
   270  				{
   271  					File: "/linux/baz.c",
   272  					Line: 100,
   273  				},
   274  			}, nil
   275  		default:
   276  			return nil, fmt.Errorf("unknown pc 0x%x", pc)
   277  		}
   278  	}
   279  	modules := []*vminfo.KernelModule{
   280  		{
   281  			Name: "",
   282  			Path: "vmlinux",
   283  		},
   284  		{
   285  			Name: "beep",
   286  			Path: "beep",
   287  		},
   288  	}
   289  
   290  	cfg := &config{
   291  		kernelDirs: mgrconfig.KernelDirs{
   292  			Obj: "/linux",
   293  		},
   294  		kernelModules: modules,
   295  	}
   296  	ctx := &linux{
   297  		config:  cfg,
   298  		vmlinux: "vmlinux",
   299  		symbols: symbols,
   300  	}
   301  	for i, test := range tests {
   302  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   303  			rep := &Report{
   304  				Report: []byte(test.line),
   305  			}
   306  			err := ctx.symbolize(rep, symb)
   307  			assert.NoError(t, err)
   308  			assert.Equal(t, test.result, string(rep.Report))
   309  		})
   310  	}
   311  }
   312  
   313  func prepareLinuxReporter(t *testing.T, arch string) (*Reporter, *linux) {
   314  	cfg := &mgrconfig.Config{
   315  		Derived: mgrconfig.Derived{
   316  			TargetOS:   targets.Linux,
   317  			TargetArch: arch,
   318  			SysTarget:  targets.Get(targets.Linux, arch),
   319  		},
   320  	}
   321  	reporter, err := NewReporter(cfg)
   322  	if err != nil {
   323  		t.Errorf("failed to create a reporter instance for %#v: %v", arch, err)
   324  	}
   325  	return reporter, reporter.impl.(*linux)
   326  }
   327  
   328  func TestParseLinuxOpcodes(t *testing.T) {
   329  	type opcodeTest struct {
   330  		arch   string
   331  		input  string
   332  		output *parsedOpcodes
   333  	}
   334  
   335  	tests := []opcodeTest{
   336  		// LE tests.
   337  		{
   338  			arch:  targets.AMD64,
   339  			input: "31 c0 <e8> f5 bf f7 ff",
   340  			output: &parsedOpcodes{
   341  				rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xff},
   342  				offset:   2,
   343  			},
   344  		},
   345  		{
   346  			arch:  targets.AMD64,
   347  			input: "c031 <f5e8> f7bf fff7 00ff",
   348  			output: &parsedOpcodes{
   349  				rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xf7, 0xff, 0xff, 0x00},
   350  				offset:   2,
   351  			},
   352  		},
   353  		{
   354  			arch:  targets.AMD64,
   355  			input: "(33221100) 77665544",
   356  			output: &parsedOpcodes{
   357  				rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},
   358  				offset:   0,
   359  			},
   360  		},
   361  		// BE tests.
   362  		{
   363  			arch:  targets.S390x,
   364  			input: "31 c0 <e8> f5 bf f7 ff",
   365  			output: &parsedOpcodes{
   366  				rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf7, 0xff},
   367  				offset:   2,
   368  			},
   369  		},
   370  		{
   371  			arch:  targets.S390x,
   372  			input: "31c0 <e8f5> bff5 f7ff ff00",
   373  			output: &parsedOpcodes{
   374  				rawBytes: []byte{0x31, 0xc0, 0xe8, 0xf5, 0xbf, 0xf5, 0xf7, 0xff, 0xff, 0x00},
   375  				offset:   2,
   376  			},
   377  		},
   378  		{
   379  			arch:  targets.S390x,
   380  			input: "<00112233> 44556677",
   381  			output: &parsedOpcodes{
   382  				rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},
   383  				offset:   0,
   384  			},
   385  		},
   386  		// ARM Thumb tests.
   387  		{
   388  			arch:  targets.ARM,
   389  			input: "0011 (2233) 4455",
   390  			output: &parsedOpcodes{
   391  				rawBytes:       []byte{0x11, 0x00, 0x33, 0x22, 0x55, 0x44},
   392  				decompileFlags: FlagForceArmThumbMode,
   393  				offset:         2,
   394  			},
   395  		},
   396  		{
   397  			arch:  targets.ARM,
   398  			input: "(33221100) 77665544",
   399  			output: &parsedOpcodes{
   400  				rawBytes: []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},
   401  				offset:   0,
   402  			},
   403  		},
   404  		// Bad input tests.
   405  		{
   406  			arch:   targets.AMD64,
   407  			input:  "00 11 22 33",
   408  			output: nil,
   409  		},
   410  		{
   411  			arch:   targets.AMD64,
   412  			input:  "aa bb <cc> zz",
   413  			output: nil,
   414  		},
   415  		{
   416  			arch:   targets.AMD64,
   417  			input:  "<00> 11 22 <33>",
   418  			output: nil,
   419  		},
   420  		{
   421  			arch:   targets.AMD64,
   422  			input:  "aa  <bb>",
   423  			output: nil,
   424  		},
   425  		{
   426  			arch:   targets.AMD64,
   427  			input:  "001122 334455",
   428  			output: nil,
   429  		},
   430  		{
   431  			arch:   targets.AMD64,
   432  			input:  "0011223344556677",
   433  			output: nil,
   434  		},
   435  	}
   436  
   437  	for idx, test := range tests {
   438  		t.Run(fmt.Sprintf("%s/%v", test.arch, idx), func(t *testing.T) {
   439  			t.Parallel()
   440  			_, linuxReporter := prepareLinuxReporter(t, test.arch)
   441  			ret, err := linuxReporter.parseOpcodes(test.input)
   442  			if test.output == nil && err == nil {
   443  				t.Errorf("expected an error on input %#v", test)
   444  			} else if test.output != nil && err != nil {
   445  				t.Errorf("unexpected error %v on input %#v", err, test.input)
   446  			} else if test.output != nil && !reflect.DeepEqual(ret, *test.output) {
   447  				t.Errorf("expected: %#v, got: %#v", test.output, ret)
   448  			}
   449  		})
   450  	}
   451  }
   452  
   453  func TestDisassemblyInReports(t *testing.T) {
   454  	if runtime.GOOS != targets.Linux {
   455  		t.Skipf("the test is meant to be run only under Linux")
   456  	}
   457  
   458  	archPath := filepath.Join("testdata", "linux", "decompile")
   459  	subFolders, err := os.ReadDir(archPath)
   460  	if err != nil {
   461  		t.Fatalf("disassembly reports failed: %v", err)
   462  	}
   463  
   464  	for _, obj := range subFolders {
   465  		if !obj.IsDir() {
   466  			continue
   467  		}
   468  		reporter, linuxReporter := prepareLinuxReporter(t, obj.Name())
   469  		if linuxReporter.target.BrokenCompiler != "" {
   470  			t.Skip("skipping the test due to broken cross-compiler:\n" + linuxReporter.target.BrokenCompiler)
   471  		}
   472  
   473  		testPath := filepath.Join(archPath, obj.Name())
   474  		testFiles, err := os.ReadDir(testPath)
   475  		if err != nil {
   476  			t.Fatalf("failed to list tests for %v: %v", obj.Name(), err)
   477  		}
   478  
   479  		for _, file := range testFiles {
   480  			if !strings.HasSuffix(file.Name(), ".in") {
   481  				continue
   482  			}
   483  			filePath := filepath.Join(testPath, strings.TrimSuffix(file.Name(), ".in"))
   484  			t.Run(obj.Name()+"/"+file.Name(), func(t *testing.T) {
   485  				testDisassembly(t, reporter, linuxReporter, filePath)
   486  			})
   487  		}
   488  	}
   489  }
   490  
   491  func testDisassembly(t *testing.T, reporter *Reporter, linuxReporter *linux, testFilePrefix string) {
   492  	t.Parallel()
   493  
   494  	input, err := os.ReadFile(testFilePrefix + ".in")
   495  	if err != nil {
   496  		t.Fatalf("failed to read input file: %v", err)
   497  	}
   498  
   499  	report := reporter.Parse(input)
   500  	if report == nil {
   501  		t.Fatalf("no bug report was found")
   502  	}
   503  
   504  	result := linuxReporter.decompileOpcodes(input, report)
   505  	if *flagUpdate {
   506  		osutil.WriteFile(testFilePrefix+".out", result)
   507  	}
   508  
   509  	output, err := os.ReadFile(testFilePrefix + ".out")
   510  	if err != nil {
   511  		t.Fatalf("failed to read output file: %v", err)
   512  	}
   513  
   514  	if !bytes.Equal(output, result) {
   515  		t.Fatalf("expected:\n%s\ngot:\n%s", output, result)
   516  	}
   517  }