-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
testing: add fuzz test support #44551
Comments
How can we keep the fuzzing corpora small, and the fuzzing process efficient and on point? Although fuzzing is good at finding certain classes of bugs, it is very expensive in CPU and storage and cost/benefit ratio remains unclear. I worry about wasting energy and filling up git repos with testdata noise. Is there a way to make the testing focused and result-driven? Also, will we wake up one day and find fuzzing is a mandatory part of all builds? That would be terrifying. Apologies if I'm late to the party; I did not comment on the original proposal. |
The track record of go-fuzz provides pretty amazing evidence that fuzzing is good at finding bugs that humans had not found. In my experience, just a few CPU minutes of fuzzing can be extremely effective at the margin. There aren't a lot of other ways to turn fixed amounts of CPUs (as opposed to dynamic, runtime checks) into bugs caught.
Yeah, filling repositories with random-looking stuff is definitely undesirable. To be clear, in this design the mutator-generated corpus is not in testdata, we agree that it would be way too noisy to check in. Samples are only placed in testdata when they resulted in a test failure, and then it's up to the developer to check them in as a regression test, or turn them into a more full-fledged unit test.
I'm not sure what you mean with being a part of all builds. If you mean running as part of The expensive, open-ended part of fuzzing won't even be a default part of |
Thanks, Filo. Your point about how we store stuff is important, and I missed that. I understand the value of fuzzing, and my grumpy old man concerns are not an attempt to reduce that. (I will say that some of the bugs it finds are not worth fixing, and certainly not the cost of finding, although it's very hard to make that case.) But I have three general concerns:
Go build will surely not run it always, but organizations will. Being automated, companies will require it, just as they require zero lint errors even as we protest that lint is not perfect. I am just expressing caution, care, and an eye on the big picture here. I am not saying we shouldn't do this, just that we should do it with all considerations in mind. |
This is awesome - really excited to see native Go fuzzing in the proposal stage now. Hopefully it's not too early to make a suggestion for the actual implementation of this, but it would be super useful if the I think fuzzing is in sort of a unique spot among the supported testing types in that there's no one correct way to fuzz test your code. Having the ability to build a type that implements the There's already been interest in something like this from the original Reddit thread mentioned on the proposal document (like this comment here), and I think it would be a missed opportunity to not allow developers to hook these functions into property-based testing tools (like Gopter) to get some minimal testing done at the There are probably details I've missed here and I'd like to hear some other opinions on this, but given all the potential use cases I think it's worth some discussion. |
Agreed, I like to say that fuzzing finds bugs at the margin. It's why we are interested in it as the Security team: bugs caught at the margin are ones that don't make it into production to become vulnerabilities. Just like it finds bugs that are often not found in other ways, it can't find bugs that other ways can. We'll do our best to communicate to developers in the coming months about what fuzzing can (and can't) do for them, and we'd like to hear it if that content doesn't push in the right direction.
Indeed, this is the hardest part: creating a culture in the ecosystem that makes the practice successful. We want fuzzing to be part of the development—not build or security—process: make a change to the relevant code, run There is probably also space for fuzzing in CI, like OSS-Fuzz demonstrated and so that the corpus can be centralized, but since it's non-deterministic it can't meaningfully block a build. Hopefully the CLI choices, like not having a fixed fuzzing time, nudge in that direction. Likewise, I think writing will be the best tool we have to shape usage, and welcome feedback on future drafts! |
Why would such an interface type need to be defined in the |
Absolute wild wild dream, but... wouldn't it be amazing if there would be an option to transform bugs found with fuzzing into unit test cases automatically? It could be as simple as copying the function, give is a unique name, and replace This would make it very clear that fuzzing is generating data that make your test fail, and in itself not a way to test your code. Its a random edg-case generator, not a validator. |
The flow of control in the design doc seems a bit off to me. The
Making But then I wonder if there is a cleaner alternative, especially given the I'm imagining something like: package testing
func (*T) NewFuzzer(ff interface{}) *F
func (*F) Add(args interface{}) That would make the example in the doc more like: func TestFuzzMarshalFoo(t *testing.T) {
f := t.NewFuzzer(func(t *testing.T, a string, num *big.Int) {
t.Parallel() // seed corpus tests can run in parallel
if num.Sign() <= 0 {
t.Skip() // only test positive numbers
}
val, err := MarshalFoo(a, num)
if err != nil {
t.Skip()
}
if val == nil {
t.Fatal("MarshalFoo: val == nil, err == nil")
}
a2, num2, err := UnmarshalFoo(val)
if err != nil {
t.Fatalf("failed to unmarshal valid Foo: %v", err)
}
if a2 == nil || num2 == nil {
t.Error("UnmarshalFoo: a==nil, num==nil, err==nil")
}
if a2 != a || !num2.Equal(num) {
t.Error("UnmarshalFoo does not match the provided input")
}
})
// Seed the initial corpus
f.Add("cat", big.NewInt(1341))
f.Add("!mouse", big.NewInt(0))
} If the |
There were discussions about code generation in some of the first designs, but it was eventually decided that it was a complexity without much gain, especially since we can gain the same benefits in a simpler way. With this design, new crashes are put into testdata and will automatically run as a regression test on the next And if someone would prefer to put this crash into the code instead of using the one in testdata, they have a couple options:
|
Fuzz functions will change over time, as all code does. If/when they change, the crashes in testdata may no longer be effectively testing against regressions. It seems to me that crashes need to be reified in regular tests. That said, a trained corpus (not just crashes) can make fuzzing much more effective at finding new issues. I understand not wanting to pollute a repo with a corpus, but I think we should design an intended way for people to keep and share corpora. I have used git submodules in the past reasonably hapiily, but that's git-specific (and submodules are generally frustrating to work with). I'm not sure what the answer is here, but I think at least trying for an answer is an important part of the fuzzing big picture. |
I think part of the attraction is not just it finds bugs, but also how it integrates with developer workflow. The nice thing about fuzzing is that each bug has a witness input. That input, and the ability to debug with your own tools, makes fuzzing-as-a-process quite nice. That being said, definitely no silver bullets here.
This also relates to the proposal "This type of testing works best when able to run more mutations quickly, rather than fewer mutations intelligently.". AFL/libfuzzer really focus on iteration speed, but there are other approaches like symbolic execution which are slow, but generate new coverage (more or less) by definition on each iteration. Less fast million monkeys; more like applied model checking using heavy-weight SMT/SAT solvers. Most research, and some products, pair the two together. I know for a fact Mayhem and Mechanical Phish did this in the cyber grand challenge, for instance. The great thing about the proposal is the interface and build process is standardized. I view this proposal as relevant beyond fuzzing to the broader set of dynamic analysis tools. |
Putting the interface directly into the testing package gives any codebase that uses the default Go fuzzing tooling significant flexibility when it comes to changing up the testing methodology. In the world of C++ fuzzing, the fuzz interface has been de-facto standardized on the LLVMFuzzerTestOneInput function, originally created by libFuzzer. Because this is just a function that expects a value, every other C++ fuzzing tool works with the same interface, allowing users to take a codebase full of fuzz functions and use any of the available open-source tools with no code changes. This ability has been put to great use by tools like OSS-fuzz/clusterfuzz, which allow users to write one fuzz test and have it attacked by 3 different fuzzing engines without any more developer effort. We constantly see evidence (like this example) of new fuzzing techniques uncovering bugs in code that had previously been thoroughly fuzzed. Making testing.F a concrete type locks down any method that uses it to the standard Go fuzzing tooling, and trying out a different tool would require significant developer effort on any codebase with a healthy collection of fuzz tests. It would feel like an oversight to hamstring Go fuzzing right out of the gate by limiting available testing methodologies to the default - the fuzzing community consistently develops more and more effective techniques and algorithms, and it would be a shame to not take advantage of that.
I might be misunderstanding you here, but how would turning testing.F into an interface preclude adding more functions in the future? |
This proposal has been added to the active column of the proposals project |
I think what @bcmills is saying here is that if we define I am almost positive that we will come up with more ways to extend the
This is a fair point. What do you think about supporting different modes with |
@bcmills I can see your point that it has some type-state invariants. However, I'm not as concerned by that. A few things: The initial designs looked like this: I also think that if someone calls I also think The main reason I like
Yes and no. Pre-work should be done before calling f.Fuzz (ie. before fuzzing). But if there is cleanup work that needs to be done, then they should call f.Cleanup before f.Fuzz, or t.Cleanup inside the fuzz function, depending on what they want to happen. For example, someone could do this:
|
It doesn't seem necessary to me to have In other words, it doesn't seem necessary to do the virtual dispatch on the |
In that case, could we gate it behind a build tag or |
@FiloSottile Well, I recommend the on line great book: The Fuzzing Book (Tools and Techniques for Generating Software Tests) |
@jayconrod and I discussed this, and ended up deciding that there wasn't much to gain from gating it. Writing a fuzz target is already opt-in, since a developer would need to choose to write a target into their own source code. It's not out of the question if we feel that not gating the feature could cause problems, but we couldn't come up with a compelling need for it. LMK if we are missing something here. |
About In our experimental implementation, |
@katiehockman, ideally a Go user ought to be able to run If the fuzzing API is stable as of Go 1.17, then users who opt in to fuzzing can guard some of their So I would prefer that we put the unstable parts of the API behind an explicit build tag — perhaps |
@bcmills That's a good point about |
No change in consensus, so accepted. 🎉 |
Uses the fuzzer of the Go tool chain in [branch dev.fuzz](https://github.com/golang/go/tree/dev.fuzz). See also golang/go#44551 and https://go.googlesource.com/proposal/+/master/design/draft-fuzzing.md
What is the difference between this proposal and |
I expect you've never actually heard of or looked up the term fuzzing/fuzz-testing; you should start by reading something like: https://en.wikipedia.org/wiki/Fuzz_testing |
The code has been merged to master, and will be landing in Go 1.18. 🎉 |
How to continue fuzzing after a crasher was found? The call to package main_test
import (
"errors"
"testing"
)
func Double(i int) (int, error) {
if i == 42 {
return 0, errors.New("42")
}
return i * 2, nil
}
func FuzzDouble(f *testing.F) {
f.Add(1)
f.Fuzz(func(t *testing.T, i int) {
_, err := Double(i)
if err != nil {
t.Errorf("failed to double %d: %v", i, err)
}
})
} $ go version
go version devel go1.18-becaeea119 Wed Dec 15 12:23:36 2021 +0100 darwin/amd64
$ go test -v -fuzz .
=== FUZZ FuzzDouble
fuzz: elapsed: 0s, gathering baseline coverage: 0/1 completed
fuzz: elapsed: 0s, gathering baseline coverage: 1/1 completed, now fuzzing with 8 workers
fuzz: elapsed: 2s, execs: 42 (28/sec), new interesting: 0 (total: 0)
--- FAIL: FuzzDouble (1.95s)
--- FAIL: FuzzDouble (0.00s)
main_test.go:22: failed to double 42: 42
Failing input written to testdata/fuzz/FuzzDouble/901ae842a79a0767746cec1401e69655708099ce968b7f89b4dea9ed92a85603
To re-run:
go test -run=FuzzDouble/901ae842a79a0767746cec1401e69655708099ce968b7f89b4dea9ed92a85603
FAIL
exit status 1
FAIL fuzztest 1.955s |
|
Well, that option unfortunately doesn't exist in |
#48127 mentions what happened to that option. |
@leonklingele this issue has been closed, so please file a new issue if you experience any problems. For questions, I encourage either filing an issue (e.g. to ask for additional documentation), or ask on the #fuzzing channel in the Gophers slack. |
This proposal is to add fuzz test support to Go. This will add a new
testing.F
type, supportFuzzFoo
functions in_test.go
files, and add newgo
command behavior.A design draft has already been published and iterated on based on feedback from the Go community. This is the next step to propose that this design draft become a language feature.
This feature will be considered experimental in Go 1.18, and the API will not be covered by the Go 1 compatibility promise yet. The functionality that goes into this release is expected to have bugs and be missing features, but should serve as a proof-of-concept for which Go developers can experiment and provide feedback. Since this will be an experimental feature to start, we also expect there will be room for growth for the mutator and fuzzing engine in future Go releases.
Below are the parts of the design draft which will not make it into 1.18, and will be considered later on:
-race
and-msan
-keepfuzzing
-keepfuzzing
(to reduce noise)[]byte
(e.g. string, int, float64)Edit: This previously said 1.17, but it was not merged to master to make it into 1.17, so this has been updated to say "1.18" instead.
The text was updated successfully, but these errors were encountered: