func (s *Sonar) Visit(n ast.Node) ast.Visitor { // TODO: detect "x&mask==0", emit sonar(x, x&^mask) switch nn := n.(type) { case *ast.BinaryExpr: break case *ast.GenDecl: if nn.Tok != token.VAR { return nil // constants and types are not interesting } return s case *ast.SelectorExpr: return nil case *ast.SwitchStmt: if nn.Tag == nil || nn.Body == nil { return s // recurse } // Replace: // switch a := foo(); bar(a) { // case x: ... // case y: ... // } // with: // switch { // default: // a := foo() // __tmp := bar(a) // switch { // case __tmp == x: ... // case __tmp == y: ... // } // } // The == comparisons will be instrumented later when we recurse. sw := new(ast.SwitchStmt) *sw = *nn var stmts []ast.Stmt if sw.Init != nil { stmts = append(stmts, sw.Init) sw.Init = nil } const tmpvar = "__go_fuzz_tmp" tmp := &ast.Ident{Name: tmpvar} typ := s.info.Types[sw.Tag] s.info.Types[tmp] = typ stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{tmp}, Tok: token.DEFINE, Rhs: []ast.Expr{sw.Tag}}) stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{&ast.Ident{Name: "_"}}, Tok: token.ASSIGN, Rhs: []ast.Expr{tmp}}) sw.Tag = nil stmts = append(stmts, sw) for _, cas1 := range sw.Body.List { cas := cas1.(*ast.CaseClause) for i, expr := range cas.List { tmp := &ast.Ident{Name: tmpvar, NamePos: expr.Pos()} s.info.Types[tmp] = typ cas.List[i] = &ast.BinaryExpr{X: tmp, Op: token.EQL, Y: expr} } } nn.Tag = nil nn.Init = nil nn.Body = &ast.BlockStmt{List: []ast.Stmt{&ast.CaseClause{Body: stmts}}} return s // recurse case *ast.ForStmt: // For condition is usually uninteresting, but produces lots of samples. // So we skip it if it looks boring. if nn.Init != nil { ast.Walk(s, nn.Init) } if nn.Post != nil { ast.Walk(s, nn.Post) } ast.Walk(s, nn.Body) if nn.Cond != nil { // Look for the following pattern: // for foo := ...; foo ? ...; ... { ... } boring := false if nn.Init != nil { if init, ok1 := nn.Init.(*ast.AssignStmt); ok1 && init.Tok == token.DEFINE && len(init.Lhs) == 1 { if id, ok2 := init.Lhs[0].(*ast.Ident); ok2 { if bex, ok3 := nn.Cond.(*ast.BinaryExpr); ok3 { if x, ok4 := bex.X.(*ast.Ident); ok4 && x.Name == id.Name { boring = true } if x, ok4 := bex.Y.(*ast.Ident); ok4 && x.Name == id.Name { boring = true } } } } } if !boring { ast.Walk(s, nn.Cond) } } return nil default: return s // recurse } // TODO: handle map index expressions (especially useful for strings). // E.g. when code matches a read in identifier against a set of known identifiers. // For the record, it looks as follows. However, it is tricky to distinguish // from slice/array index and map assignments... //. . . . . . . *ast.IndexExpr { //. . . . . . . . X: *ast.Ident { //. . . . . . . . . Name: "m" //. . . . . . . . } //. . . . . . . . Index: *ast.Ident { //. . . . . . . . . Name: "s" //. . . . . . . . } //. . . . . . . } // TODO: transform expressions so that lhs expression contains a variable // and rhs contains all constant operands. For example, for (real code from vp8 codec): // cf := (b[0]>>4)&7 == 5 // we would like to transform it to: // b[0] & (7<<4) == 5<<4 // and then to: // b[0] == 5<<4 | b & ^(7<<4) // and emit: // Sonar(b[0], 5<<4 | b & ^(7<<4), SonarEQL) // This will allow the fuzzer to figure out what bytes it needs to replace // with what bytes in order to crack this condition. // Similarly, for: // x/3 == 100 // we would like to emit: // Sonar(x, 100*3, SonarEQL) // TODO: intercept strings.Index/HasPrefix and similar functions. nn := n.(*ast.BinaryExpr) var flags uint8 switch nn.Op { case token.EQL: flags = SonarEQL break case token.NEQ: flags = SonarNEQ break case token.LSS: flags = SonarLSS break case token.GTR: flags = SonarGTR break case token.LEQ: flags = SonarLEQ break case token.GEQ: flags = SonarGEQ break default: return s // recurse } // Replace: // x != y // with: // func() bool { v1 := x; v2 := y; go-fuzz-dep.Sonar(v1, v2, flags); return v1 != v2 }() == true v1 := nn.X v2 := nn.Y ast.Walk(s, v1) ast.Walk(s, v2) if isCap(v1) || isCap(v2) { // Haven't seen useful cases yet. return s } if isLen(v1) || isLen(v2) { // TODO: we could pass both length value and the len argument. // For example, if the code is: // name := ... // obtained from input // if len(name) > 5 { ... } // If we would have the name value at runtime, we will know // what part of the input to alter to affect len result. flags |= SonarLength } checkType := func(tv types.TypeAndValue) bool { // Comparisons of pointers, maps, chans and bool are not interesting. if _, ok := tv.Type.(*types.Pointer); ok { return false } if _, ok := tv.Type.(*types.Map); ok { return false } if _, ok := tv.Type.(*types.Chan); ok { return false } s := tv.Type.Underlying().String() if s == "bool" || s == "ideal bool" || s == "error" || s == "untyped nil" || s == "unsafe.Pointer" { return false } return true } if !checkType(s.info.Types[v1]) || !checkType(s.info.Types[v2]) { return nil } var tv types.TypeAndValue if isConstExpr(s.info, v1) { flags |= SonarConst1 } else { tv = s.info.Types[v1] } if isConstExpr(s.info, v2) { flags |= SonarConst2 } else { tv = s.info.Types[v2] } if flags&SonarConst1 != 0 && flags&SonarConst2 != 0 { return nil } id := int(flags) | sonarSeq<<8 startPos := s.fset.Position(nn.Pos()) endPos := s.fset.Position(nn.End()) *s.blocks = append(*s.blocks, CoverBlock{sonarSeq, s.name, startPos.Line, startPos.Column, endPos.Line, endPos.Column, int(flags)}) sonarSeq++ block := &ast.BlockStmt{} typstr := tv.Type.String() if strings.HasPrefix(typstr, s.pkg+".") { typstr = typstr[len(s.pkg)+1:] } conv := func(name string, v ast.Expr) ast.Expr { // Convert const to the type of the other expr. isConst := isConstExpr(s.info, v) badConst := false if isConst { c := s.info.Types[v].Value if c.Kind() == exact.Int { if v, ok := exact.Int64Val(c); !ok || int64(int(v)) != v { // Such const can't be used outside of its current context, // because it will be converted to int and that will fail. badConst = true } } } if badConst || isWeirdShift(s.info, v) { v = &ast.CallExpr{ Fun: &ast.Ident{Name: typstr}, Args: []ast.Expr{v}, } s.info.Types[v] = tv } if !isConst { // Assign to a temp to avoid double side-effects. tmp := ast.NewIdent(name) block.List = append(block.List, &ast.AssignStmt{Tok: token.DEFINE, Lhs: []ast.Expr{tmp}, Rhs: []ast.Expr{v}}) v = tmp s.info.Types[v] = tv } return v } v1 = conv("v1", v1) v2 = conv("v2", v2) block.List = append(block.List, &ast.ExprStmt{ X: &ast.CallExpr{ Fun: &ast.SelectorExpr{X: &ast.Ident{Name: fuzzdepPkg}, Sel: &ast.Ident{Name: "Sonar"}}, Args: []ast.Expr{v1, v2, &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(id)}}, }, }, &ast.ReturnStmt{Results: []ast.Expr{&ast.BinaryExpr{Op: nn.Op, X: v1, Y: v2}}}, ) nn.X = &ast.CallExpr{ Fun: &ast.FuncLit{ Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.Ident{Name: "bool"}}}}}, Body: block, }, } nn.Y = &ast.BasicLit{Kind: token.INT, Value: "true"} nn.Op = token.EQL return nil }
func (s *Sonar) Visit(n ast.Node) ast.Visitor { // TODO: detect "x&mask==0", emit sonar(x, x&^mask) switch nn := n.(type) { case *ast.BinaryExpr: break case *ast.GenDecl: if nn.Tok != token.VAR { return nil // constants and types are not interesting } return s case *ast.FuncDecl: if s.pkg == "math" && (nn.Name.Name == "Y0" || nn.Name.Name == "Y1" || nn.Name.Name == "Yn" || nn.Name.Name == "J0" || nn.Name.Name == "J1" || nn.Name.Name == "Jn" || nn.Name.Name == "Pow") { // Can't handle code there: // math/j0.go:93: constant 680564733841876926926749214863536422912 overflows int return nil } return s // recurse case *ast.SwitchStmt: if nn.Tag == nil || nn.Body == nil { return s // recurse } // Replace: // switch a := foo(); bar(a) { // case x: ... // case y: ... // } // with: // switch { // default: // a := foo() // __tmp := bar(a) // switch { // case __tmp == x: ... // case __tmp == y: ... // } // } // The == comparisons will be instrumented later when we recurse. sw := new(ast.SwitchStmt) *sw = *nn var stmts []ast.Stmt if sw.Init != nil { stmts = append(stmts, sw.Init) sw.Init = nil } stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{&ast.Ident{Name: "__go_fuzz_tmp"}}, Tok: token.DEFINE, Rhs: []ast.Expr{sw.Tag}}) sw.Tag = nil stmts = append(stmts, sw) for _, cas1 := range sw.Body.List { cas := cas1.(*ast.CaseClause) for i, expr := range cas.List { cas.List[i] = &ast.BinaryExpr{X: &ast.Ident{Name: "__go_fuzz_tmp", NamePos: expr.Pos()}, Op: token.EQL, Y: expr} } } nn.Tag = nil nn.Init = nil nn.Body = &ast.BlockStmt{List: []ast.Stmt{&ast.CaseClause{Body: stmts}}} return s // recurse case *ast.ForStmt: // For condition is usually uninteresting, but produces lots of samples. // So we skip it if it looks boring. if nn.Init != nil { ast.Walk(s, nn.Init) } if nn.Post != nil { ast.Walk(s, nn.Post) } ast.Walk(s, nn.Body) if nn.Cond != nil { // Look for the following pattern: // for foo := ...; foo ? ...; ... { ... } boring := false if nn.Init != nil { if init, ok1 := nn.Init.(*ast.AssignStmt); ok1 && init.Tok == token.DEFINE && len(init.Lhs) == 1 { if id, ok2 := init.Lhs[0].(*ast.Ident); ok2 { if bex, ok3 := nn.Cond.(*ast.BinaryExpr); ok3 { if x, ok4 := bex.X.(*ast.Ident); ok4 && x.Name == id.Name { boring = true } if x, ok4 := bex.Y.(*ast.Ident); ok4 && x.Name == id.Name { boring = true } } } } } if !boring { ast.Walk(s, nn.Cond) } } return nil default: return s // recurse } // TODO: handle map index expressions (especially useful for strings). // E.g. when code matches a read in identifier against a set of known identifiers. // For the record, it looks as follows. However, it is tricky to distinguish // from slice/array index and map assignments... //. . . . . . . *ast.IndexExpr { //. . . . . . . . X: *ast.Ident { //. . . . . . . . . Name: "m" //. . . . . . . . } //. . . . . . . . Index: *ast.Ident { //. . . . . . . . . Name: "s" //. . . . . . . . } //. . . . . . . } // TODO: transform expressions so that lhs expression contains a variable // and rhs contains all constant operands. For example, for (real code from vp8 codec): // cf := (b[0]>>4)&7 == 5 // we would like to transform it to: // b[0] & (7<<4) == 5<<4 // and then to: // b[0] == 5<<4 | b & ^(7<<4) // and emit: // Sonar(b[0], 5<<4 | b & ^(7<<4), SonarEQL) // This will allow the fuzzer to figure out what bytes it needs to replace // with what bytes in order to crack this condition. // Similarly, for: // x/3 == 100 // we would like to emit: // Sonar(x, 100*3, SonarEQL) // TODO: intercept strings.Index/HasPrefix and similar functions. nn := n.(*ast.BinaryExpr) var flags uint8 switch nn.Op { case token.EQL: flags = SonarEQL break case token.NEQ: flags = SonarNEQ break case token.LSS: flags = SonarLSS break case token.GTR: flags = SonarGTR break case token.LEQ: flags = SonarLEQ break case token.GEQ: flags = SonarGEQ break default: return s // recurse } // Replace: // x != y // with: // func() bool { v1 := x; v2 := y; go-fuzz-dep.Sonar(v1, v2, flags); return v1 != v2 }() == true v1 := nn.X v2 := nn.Y ast.Walk(s, v1) ast.Walk(s, v2) if isUninterestingLiteral(v1) || isUninterestingLiteral(v2) { return s } if isCap(v1) || isCap(v2) { // Haven't seen useful cases yet. return s } if isLen(v1) || isLen(v2) { // TODO: we could pass both length value and the len argument. // For example, if the code is: // name := ... // obtained from input // if len(name) > 5 { ... } // If we would have the name value at runtime, we will know // what part of the input to alter to affect len result. flags |= SonarLength } if isConstExpr(v1) { flags |= SonarConst1 } if isConstExpr(v2) { flags |= SonarConst2 } id := int(flags) | sonarSeq<<8 startPos := s.fset.Position(nn.Pos()) endPos := s.fset.Position(nn.End()) *s.blocks = append(*s.blocks, CoverBlock{sonarSeq, s.name, startPos.Line, startPos.Column, endPos.Line, endPos.Column, int(flags)}) sonarSeq++ block := &ast.BlockStmt{} if !isSimpleExpr(v1) { tmp := ast.NewIdent("v1") block.List = append(block.List, &ast.AssignStmt{Tok: token.DEFINE, Lhs: []ast.Expr{tmp}, Rhs: []ast.Expr{v1}}) v1 = tmp } if !isSimpleExpr(v2) { tmp := ast.NewIdent("v2") block.List = append(block.List, &ast.AssignStmt{Tok: token.DEFINE, Lhs: []ast.Expr{tmp}, Rhs: []ast.Expr{v2}}) v2 = tmp } block.List = append(block.List, &ast.ExprStmt{ X: &ast.CallExpr{ Fun: &ast.SelectorExpr{X: &ast.Ident{Name: fuzzdepPkg}, Sel: &ast.Ident{Name: "Sonar"}}, Args: []ast.Expr{v1, v2, &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(id)}}, }, }, &ast.ReturnStmt{Results: []ast.Expr{&ast.BinaryExpr{Op: nn.Op, X: v1, Y: v2}}}, ) nn.X = &ast.CallExpr{ Fun: &ast.FuncLit{ Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: &ast.Ident{Name: "bool"}}}}}, Body: block, }, } nn.Y = &ast.BasicLit{Kind: token.INT, Value: "true"} nn.Op = token.EQL return nil }