Example #1
0
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
}
Example #2
0
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
}