github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/prog/minimization_test.go (about)

     1  // Copyright 2018 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 prog
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/google/syzkaller/pkg/hash"
    13  )
    14  
    15  // nolint:gocyclo
    16  func TestMinimize(t *testing.T) {
    17  	attempt := 0
    18  	// nolint: lll
    19  	tests := []struct {
    20  		os              string
    21  		arch            string
    22  		mode            MinimizeMode
    23  		orig            string
    24  		callIndex       int
    25  		pred            func(*Prog, int) bool
    26  		result          string
    27  		resultCallIndex int
    28  	}{
    29  		// Predicate always returns false, so must get the same program.
    30  		{
    31  			"linux", "amd64", MinimizeCorpus,
    32  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    33  				"sched_yield()\n" +
    34  				"pipe2(&(0x7f0000000000), 0x0)\n",
    35  			2,
    36  			func(p *Prog, callIndex int) bool {
    37  				if len(p.Calls) == 0 {
    38  					t.Fatalf("got an empty program")
    39  				}
    40  				if p.Calls[len(p.Calls)-1].Meta.Name != "pipe2" {
    41  					t.Fatalf("last call is removed")
    42  				}
    43  				return false
    44  			},
    45  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    46  				"sched_yield()\n" +
    47  				"pipe2(&(0x7f0000000000), 0x0)\n",
    48  			2,
    49  		},
    50  		// Remove a call.
    51  		{
    52  			"linux", "amd64", MinimizeCorpus,
    53  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    54  				"sched_yield()\n" +
    55  				"pipe2(&(0x7f0000000000)={0xffffffffffffffff, 0xffffffffffffffff}, 0x0)\n",
    56  			2,
    57  			func(p *Prog, callIndex int) bool {
    58  				// Aim at removal of sched_yield.
    59  				return len(p.Calls) == 2 && p.Calls[0].Meta.Name == "mmap" && p.Calls[1].Meta.Name == "pipe2"
    60  			},
    61  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    62  				"pipe2(0x0, 0x0)\n",
    63  			1,
    64  		},
    65  		// Remove two dependent calls.
    66  		{
    67  			"linux", "amd64", MinimizeCorpus,
    68  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    69  				"pipe2(&(0x7f0000000000)={0x0, 0x0}, 0x0)\n" +
    70  				"sched_yield()\n",
    71  			2,
    72  			func(p *Prog, callIndex int) bool {
    73  				// Aim at removal of pipe2 and then mmap.
    74  				if len(p.Calls) == 2 && p.Calls[0].Meta.Name == "mmap" && p.Calls[1].Meta.Name == "sched_yield" {
    75  					return true
    76  				}
    77  				if len(p.Calls) == 1 && p.Calls[0].Meta.Name == "sched_yield" {
    78  					return true
    79  				}
    80  				return false
    81  			},
    82  			"sched_yield()\n",
    83  			0,
    84  		},
    85  		// Remove a call and replace results.
    86  		{
    87  			"linux", "amd64", MinimizeCorpus,
    88  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    89  				"pipe2(&(0x7f0000000000)={<r0=>0x0, 0x0}, 0x0)\n" +
    90  				"write(r0, &(0x7f0000000000)=\"1155\", 0x2)\n" +
    91  				"sched_yield()\n",
    92  			3,
    93  			func(p *Prog, callIndex int) bool {
    94  				return p.String() == "mmap-write-sched_yield"
    95  			},
    96  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
    97  				"write(0xffffffffffffffff, 0x0, 0x0)\n" +
    98  				"sched_yield()\n",
    99  			2,
   100  		},
   101  		// Remove a call and replace results.
   102  		{
   103  			"linux", "amd64", MinimizeCorpus,
   104  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
   105  				"r0=open(&(0x7f0000000000)=\"1155\", 0x0, 0x0)\n" +
   106  				"write(r0, &(0x7f0000000000)=\"1155\", 0x2)\n" +
   107  				"sched_yield()\n",
   108  			-1,
   109  			func(p *Prog, callIndex int) bool {
   110  				return p.String() == "mmap-write-sched_yield"
   111  			},
   112  			"mmap(&(0x7f0000000000/0x1000)=nil, 0x1000, 0x3, 0x32, 0xffffffffffffffff, 0x0)\n" +
   113  				"write(0xffffffffffffffff, 0x0, 0x0)\n" +
   114  				"sched_yield()\n",
   115  			-1,
   116  		},
   117  		// Minimize pointer.
   118  		{
   119  			"linux", "amd64", MinimizeCorpus,
   120  			"pipe2(&(0x7f0000001000)={0xffffffffffffffff, 0xffffffffffffffff}, 0x0)\n",
   121  			-1,
   122  			func(p *Prog, callIndex int) bool {
   123  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2"
   124  			},
   125  			"pipe2(0x0, 0x0)\n",
   126  			-1,
   127  		},
   128  		// Minimize pointee.
   129  		{
   130  			"linux", "amd64", MinimizeCorpus,
   131  			"pipe2(&(0x7f0000001000)={0xffffffffffffffff, 0xffffffffffffffff}, 0x0)\n",
   132  			-1,
   133  			func(p *Prog, callIndex int) bool {
   134  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Args[0].(*PointerArg).Address != 0
   135  			},
   136  			"pipe2(&(0x7f0000001000), 0x0)\n",
   137  			-1,
   138  		},
   139  		// Make sure we don't hang when minimizing resources.
   140  		{
   141  			"test", "64", MinimizeCorpus,
   142  			"r0 = test$res0()\n" +
   143  				"test$res1(r0)\n",
   144  			-1,
   145  			func(p *Prog, callIndex int) bool {
   146  				return false
   147  			},
   148  			"r0 = test$res0()\n" +
   149  				"test$res1(r0)\n",
   150  			-1,
   151  		},
   152  		{
   153  			"test", "64", MinimizeCorpus,
   154  			"minimize$0(0x1, 0x1)\n",
   155  			-1,
   156  			func(p *Prog, callIndex int) bool { return len(p.Calls) == 1 },
   157  			"minimize$0(0x1, 0x1)\n",
   158  			-1,
   159  		},
   160  		// Clear unneeded fault injection.
   161  		{
   162  			"linux", "amd64", MinimizeCorpus,
   163  			"pipe2(0x0, 0x0) (fail_nth: 5)\n",
   164  			-1,
   165  			func(p *Prog, callIndex int) bool {
   166  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2"
   167  			},
   168  			"pipe2(0x0, 0x0)\n",
   169  			-1,
   170  		},
   171  		// Keep important fault injection.
   172  		{
   173  			"linux", "amd64", MinimizeCorpus,
   174  			"pipe2(0x0, 0x0) (fail_nth: 5)\n",
   175  			-1,
   176  			func(p *Prog, callIndex int) bool {
   177  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Props.FailNth == 5
   178  			},
   179  			"pipe2(0x0, 0x0) (fail_nth: 5)\n",
   180  			-1,
   181  		},
   182  		// Clear unneeded async flag.
   183  		{
   184  			"linux", "amd64", MinimizeCorpus,
   185  			"pipe2(0x0, 0x0) (async)\n",
   186  			-1,
   187  			func(p *Prog, callIndex int) bool {
   188  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2"
   189  			},
   190  			"pipe2(0x0, 0x0)\n",
   191  			-1,
   192  		},
   193  		// Keep important async flag.
   194  		{
   195  			"linux", "amd64", MinimizeCorpus,
   196  			"pipe2(0x0, 0x0) (async)\n",
   197  			-1,
   198  			func(p *Prog, callIndex int) bool {
   199  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Props.Async
   200  			},
   201  			"pipe2(0x0, 0x0) (async)\n",
   202  			-1,
   203  		},
   204  		// Clear unneeded rerun.
   205  		{
   206  			"linux", "amd64", MinimizeCorpus,
   207  			"pipe2(0x0, 0x0) (rerun: 100)\n",
   208  			-1,
   209  			func(p *Prog, callIndex int) bool {
   210  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2"
   211  			},
   212  			"pipe2(0x0, 0x0)\n",
   213  			-1,
   214  		},
   215  		// Keep important rerun.
   216  		{
   217  			"linux", "amd64", MinimizeCorpus,
   218  			"pipe2(0x0, 0x0) (rerun: 100)\n",
   219  			-1,
   220  			func(p *Prog, callIndex int) bool {
   221  				return len(p.Calls) == 1 && p.Calls[0].Meta.Name == "pipe2" && p.Calls[0].Props.Rerun >= 100
   222  			},
   223  			"pipe2(0x0, 0x0) (rerun: 100)\n",
   224  			-1,
   225  		},
   226  		// Undo target.SpecialFileLenghts mutation (reduce file name length).
   227  		{
   228  			"test", "64", MinimizeCrash,
   229  			"mutate9(&(0x7f0000000000)='./file0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\x00')\n",
   230  			0,
   231  			func(p *Prog, callIndex int) bool {
   232  				return p.Calls[0].Args[0].(*PointerArg).Res != nil
   233  			},
   234  			"mutate9(&(0x7f0000000000)='./file0\\x00')\n",
   235  			0,
   236  		},
   237  		// Ensure `no_minimize` calls are untouched.
   238  		{
   239  			"linux", "amd64", MinimizeCorpus,
   240  			"syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x0, &(0x7f0000010020), 0x1, 0x15, &(0x7f0000000200)=\"$eJwqrqzKTszJSS0CBAAA//8TyQPi\")\n",
   241  			0,
   242  			func(p *Prog, callIndex int) bool {
   243  				// Anything is allowed except removing a call.
   244  				return len(p.Calls) > 0
   245  			},
   246  			"syz_mount_image$ext4(&(0x7f0000000000)='ext4\\x00', &(0x7f0000000100)='./file0\\x00', 0x0, &(0x7f0000010020), 0x1, 0x15, &(0x7f0000000200)=\"$eJwqrqzKTszJSS0CBAAA//8TyQPi\")\n",
   247  			0,
   248  		},
   249  		// Test for removeUnrelatedCalls.
   250  		// We test exact candidates we get on each step.
   251  		// First candidate should be removal of the trailing calls, which we reject.
   252  		// Next candidate is removal of unrelated calls, which we accept.
   253  		{
   254  			"linux", "amd64", MinimizeCorpus,
   255  			`
   256  getpid()
   257  r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0)
   258  r1 = open(&(0x7f0000000040)='./file1', 0x0, 0x0)
   259  getuid()
   260  read(r1, &(0x7f0000000040), 0x10)
   261  read(r0, &(0x7f0000000040), 0x10)
   262  pipe(&(0x7f0000000040)={<r2=>0x0, <r3=>0x0})
   263  creat(&(0x7f0000000040)='./file0', 0x0)
   264  close(r1)
   265  sendfile(r0, r2, &(0x7f0000000040), 0x1)
   266  getgid()
   267  fcntl$getflags(r0, 0x0)
   268  getpid()
   269  close(r3)
   270  getuid()
   271  			`,
   272  			11,
   273  			func(p *Prog, callIndex int) bool {
   274  				pp := strings.TrimSpace(string(p.Serialize()))
   275  				switch attempt {
   276  				case 0:
   277  					if pp == strings.TrimSpace(`
   278  getpid()
   279  r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0)
   280  r1 = open(&(0x7f0000000040)='./file1', 0x0, 0x0)
   281  getuid()
   282  read(r1, &(0x7f0000000040), 0x10)
   283  read(r0, &(0x7f0000000040), 0x10)
   284  pipe(&(0x7f0000000040)={<r2=>0x0, 0x0})
   285  creat(&(0x7f0000000040)='./file0', 0x0)
   286  close(r1)
   287  sendfile(r0, r2, &(0x7f0000000040), 0x1)
   288  getgid()
   289  fcntl$getflags(r0, 0x0)
   290  					`) {
   291  						return false
   292  					}
   293  				case 1:
   294  					if pp == strings.TrimSpace(`
   295  r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0)
   296  read(r0, &(0x7f0000000040), 0x10)
   297  pipe(&(0x7f0000000040)={<r1=>0x0, <r2=>0x0})
   298  creat(&(0x7f0000000040)='./file0', 0x0)
   299  sendfile(r0, r1, &(0x7f0000000040), 0x1)
   300  fcntl$getflags(r0, 0x0)
   301  close(r2)
   302  					`) {
   303  						return true
   304  					}
   305  				default:
   306  					return false
   307  				}
   308  				panic(fmt.Sprintf("unexpected candidate on attempt %v:\n%v", attempt, pp))
   309  			},
   310  			`
   311  r0 = open(&(0x7f0000000040)='./file0', 0x0, 0x0)
   312  read(r0, &(0x7f0000000040), 0x10)
   313  pipe(&(0x7f0000000040)={<r1=>0x0, <r2=>0x0})
   314  creat(&(0x7f0000000040)='./file0', 0x0)
   315  sendfile(r0, r1, &(0x7f0000000040), 0x1)
   316  fcntl$getflags(r0, 0x0)
   317  close(r2)
   318  			`,
   319  			5,
   320  		},
   321  	}
   322  	t.Parallel()
   323  	for ti, test := range tests {
   324  		t.Run(fmt.Sprint(ti), func(t *testing.T) {
   325  			target, err := GetTarget(test.os, test.arch)
   326  			if err != nil {
   327  				t.Fatal(err)
   328  			}
   329  			p, err := target.Deserialize([]byte(strings.TrimSpace(test.orig)), Strict)
   330  			if err != nil {
   331  				t.Fatalf("failed to deserialize original program #%v: %v", ti, err)
   332  			}
   333  			attempt = 0
   334  			pred := func(p *Prog, callIndex int) bool {
   335  				res := test.pred(p, callIndex)
   336  				attempt++
   337  				return res
   338  			}
   339  			p1, ci := Minimize(p, test.callIndex, test.mode, pred)
   340  			res := strings.TrimSpace(string(p1.Serialize()))
   341  			expect := strings.TrimSpace(test.result)
   342  			if res != expect {
   343  				t.Fatalf("minimization produced wrong result #%v\norig:\n%v\nexpect:\n%v\ngot:\n%v",
   344  					ti, test.orig, expect, res)
   345  			}
   346  			if ci != test.resultCallIndex {
   347  				t.Fatalf("minimization broke call index #%v: got %v, want %v",
   348  					ti, ci, test.resultCallIndex)
   349  			}
   350  		})
   351  	}
   352  }
   353  
   354  func TestMinimizeRandom(t *testing.T) {
   355  	target, rs, iters := initTest(t)
   356  	iters /= 10 // Long test.
   357  	ct := target.DefaultChoiceTable()
   358  	r := rand.New(rs)
   359  	for i := 0; i < iters; i++ {
   360  		for _, mode := range []MinimizeMode{MinimizeCorpus, MinimizeCrash} {
   361  			p := target.Generate(rs, 5, ct)
   362  			copyP := p.Clone()
   363  			seen := make(map[string]bool)
   364  			seen[hash.String(p.Serialize())] = true
   365  			minP, _ := Minimize(p, len(p.Calls)-1, mode, func(p1 *Prog, callIndex int) bool {
   366  				id := hash.String(p1.Serialize())
   367  				if seen[id] {
   368  					t.Fatalf("program:\n%s\nobserved the same candidate twice:\n%s",
   369  						p.Serialize(), p1.Serialize())
   370  				}
   371  				seen[id] = true
   372  				if r.Intn(2) == 0 {
   373  					return false
   374  				}
   375  				copyP = p1.Clone()
   376  				return true
   377  			})
   378  			got := string(minP.Serialize())
   379  			want := string(copyP.Serialize())
   380  			if got != want {
   381  				t.Fatalf("program:\n%s\ngot:\n%s\nwant:\n%s", p.Serialize(), got, want)
   382  			}
   383  		}
   384  	}
   385  }
   386  
   387  func TestMinimizeCallIndex(t *testing.T) {
   388  	target, rs, iters := initTest(t)
   389  	ct := target.DefaultChoiceTable()
   390  	r := rand.New(rs)
   391  	for i := 0; i < iters; i++ {
   392  		p := target.Generate(rs, 5, ct)
   393  		ci := r.Intn(len(p.Calls))
   394  		mode := MinimizeCorpus
   395  		if r.Intn(2) == 0 {
   396  			mode = MinimizeCrash
   397  		}
   398  		p1, ci1 := Minimize(p, ci, mode, func(p1 *Prog, callIndex int) bool {
   399  			return r.Intn(2) == 0
   400  		})
   401  		if ci1 < 0 || ci1 >= len(p1.Calls) || p.Calls[ci].Meta.Name != p1.Calls[ci1].Meta.Name {
   402  			t.Fatalf("bad call index after minimization")
   403  		}
   404  	}
   405  }