16 ms·
Internals of Go's new fuzzing system
- derekperkins 5y agoGreat description
- gorgonzolachz 5y agoI'm cautiously excited/optimistic for this. That said, I wonder if this feature will end up seeing all that much usage? Once your inputs are sanitized/bounded in a Golang application, it's pretty hard to get the language to do things it wasn't meant to do - and if the fuzzing system is built like the unit testing system, the number of useful fuzzing cases you can run won't be that large. I've always thought that fuzzing primarily benefits E2E/integration testing, and that with modern languages' type systems and lack of pointer arithmetic the usage of fuzz testing is useful for niche cases (embedded programming and cryptography come to mind). The examples in the article (integer overflows, truncated input, invalid unicode) may be issues, but they won't break the integrity of the program or cause catastrophic failure (assuming there are no panic() calls in the code) due to Go's type system.
- potamic 5y agoI have the same question as well. Dare say it seems to be anti-go to package such niche capabilities in the stdlib, but maybe their direction is changing?
- TheDong 5y ago> seems to be anti-go to package such niche capabilities in the stdlib Go seems to tend heavily on the side of packaging things into the stdlib. It has stuff like "net/rpc/jsonrpc" (a json-rpc 1.0 impl, even though you should use grpc or json-rpc 2.0, both of which are not in the stdlib). It has crap like net/smtp and archive/tar. It has image/jpeg for some unknown reason. If you look at rust, basically half of go's stdlib exists as external crates (even if they're officially maintained, akin to the golang.org/x/ packages in go). Everything from json, to http, to image support in rust is outside the stdlib, and honestly that has ultimately let rust evolve better support for each of those things. C, C++, (to a lesser extent) java, D, etc.. basically every language in a similar space to Go has a much smaller and more compact stdlib. The go stdlib clearly doesn't err on the side of being small.
- isabellat 5y agoDeno is another project that is trying to add more to a stdlib. In some ways this is really fantastic because in the JavaScript ecosystem there is so much fatigue in deciding what libraries to use. On the flip side, if you couple too much into the stdlib then you’re limiting the huge benefit of an open market.
- vsnf 5y ago> It has image/jpeg for some unknown reason. I don't know why they added this and the other image packages specifically, but I used this the other day and it was nice to not have to bring in a separate package.
- TheDong 5y agoI mean, they realized their mistake for other image formats, hence: golang.org/x/image: https://pkg.go.dev/golang.org/x/image https://pkg.go.dev/golang.org/x/image Now it's really confusing to have to figure out to import stdlib image for jpeg, but x/image for bmp. For bonus points, check out "image/draw" vs "golang.org/x/image/draw".
- masklinn 5y agoSeems completely in line to me: - go is a broad stdlib langage - go has a bespoke toolchain with little to no hooks, meaning anything which needs to integrate directly into the build process or to instrument the runtime benefits extensively from being brought in-tree e.g. profilers, sanitisers, and (guided) fuzzers - finally go’s “niche” are mainly network daemons and CLI utilities, so lots of interacting with network streams and file processing, which are the main use cases for fuzzing
- jrockway 5y agoFuzz testing is probably the #1 software security innovation of the last 10 years. It's "niche" only because it's currently hard to do. For people that have set it up, it's essential. With Go 1.18, it's easy for anyone to set it up, and a lot of people are going to find a lot of dumb bugs. Pretty much everyone that has written fuzz tests for their software has found at least one crash. I found one in a program with 100% test coverage within minutes of writing the first fuzz test. Sometimes you miss dumb things, and the human-written "I'm going to trick my program into malfunctioning" tests can simply forget things. (In my case, I had a few branches with similar logic. The unit tests did run all the code in the branches, but only tested the boundary case for two of the three. The fuzz testing found the third case immediately.) Crashes in Go vary in severity (memory isn't usually corrupted, code isn't usually modified, the other goroutines don't stop serving requests because you probably have "recover" somewhere up the chain), but at the very least, by identifying the input that can cause crashes, you gain the ability to turn invalid input into an easy-to-understand error message, saving users and operators time and frustration. Users can retry the request with valid input. Operators don't have to freak out about log spam. And, of course, sometimes the crashes are a big deal; maybe you missed the "recover", or maybe you're calling out to unsafe code that DOES corrupt memory. To me, fuzz testing is the headline feature in the 1.18 release. And it's the release that introduces generics.
- nemo1618 5y agoThe type system can't catch nil dereferences or out-of-bounds accesses, which (in my experience) are the most common causes of runtime panics in Go. I assure you, there are many ways to make a Go program crash. :) If your program is handling untrusted input -- and almost every useful program does -- then you really should be fuzzing your input handlers.
- deleted 5y ago[deleted]
- chmike 5y agoThere is a limit to the errors a compiler can detect. Here is an example where fuzzing is required. I'm the author of [qjson](https://github.com/qjson https://github.com/qjson). It converts a human readable json text (qjson) into json. The intended use is for configuration files or data input. For instance, numeric values may be simple math expressions. I used go-fuzz and it detected right away that I forgot to deal with division by zero in these math expressions. It is impossible for the compiler to detect such errors in the program. It fully depends on the data, and when it is complex, the risk of errors are high.
- tialaramex 5y ago> I forgot to deal with division by zero in these math expressions. It is impossible for the compiler to detect such errors in the program. I'm sure there's a general class of problem where it really is impossible, but you haven't found it. All you needed to prevent this is dependent types, which Go doesn't have. With dependent types the compiler sees a = b / c and it immediately can conclude that c's type is non-zero, since if it was zero that's a divide-by-zero error. Having refined the type to non-zero, an attempt earlier to load it with a value despite not being sure if that value is zero will fail. The buggy qjson won't compile.
- stingraycharles 5y agoI have been a big proponent of generative testing / fuzzing, where rather than hard-coding inputs you simply always generate them. You can see it in Haskell’s QuickCheck or Clojure’s test.check, but there are probably many others. It is absolutely useful outside of just a few parsers; a type system is not able to catch all bugs. Just a recent example was a scheduling system I was working on, where the input fuzzing was able to put the system in states that I did not anticipate. Practically speaking, with fuzzing, you’re changing tests from manually crafting certain inputs and validating the results, to defining “laws” about how your system should behave. Here’s a good resource on the types of tests you can develop using this technique: https://fsharpforfunandprofit.com/posts/property-based-testing-2/ https://fsharpforfunandprofit.com/posts/property-based-testi...
- nsajko 5y ago> rather than hard-coding inputs you simply always generate them Why not both? In any case, a rudimentary implementation is actually in Go's standard library already for a long time: https://pkg.go.dev/testing/quick@master https://pkg.go.dev/testing/quick@master Though, note: > The testing/quick package is frozen and is not accepting new features.
- stingraycharles 5y ago> Why not both? Absolutely fine and often good enough, these tests are typically very simple and easier to reason about, and make more sense in a whole range of situations (eg regression tests). However, I would take a single input fuzzing test over one that uses hard coded inputs.
- morelisp 5y agotesting/quick is not coverage-driven and so not good at truly "interesting" input cases. It's OK for simple invariants (e.g. anything arithmetic with an inverse) but I would not trust it to tell me anything interesting about a parser. At this stage I might even rely on the fuzzer to test simple invariants because the tooling is nicer.
- jrockway 5y ago
- unboxingelf 5y agoExcellent write up
- fulafel 5y agoIn case you miss it, the link to found bug list: https://github.com/golang/go/wiki/Fuzzing-trophy-case https://github.com/golang/go/wiki/Fuzzing-trophy-case
- hit8run 5y agoNice feature. But that string reverse example is exactly one thing that annoys me in Go: the lack of higher level batterie functions for strings, maps and slices.
- mseepgood 5y agoThey are in the works: https://pkg.go.dev/golang.org/x/exp/slices https://pkg.go.dev/golang.org/x/exp/slices https://pkg.go.dev/golang.org/x/exp/maps https://pkg.go.dev/golang.org/x/exp/maps However, reversing a string is not a commonly useful operation.
- ezo 5y ago> However, reversing a string is not a commonly useful operation. But not for an interview :)
- mseepgood 5y agoYou wouldn't be allowed to use it in an interview anyway, so its absence a feature.
- tialaramex 5y agoIf you ask me to reverse a string at interview, you're going to get my opinion about at least: * Why a string isn't (shouldn't be treated as) really just a sequence of characters (even if yes, internally it's probably some structure like a vector of bytes, or 16-bit unsigned integers, or whatever) and so "reversing" it is probably nonsense. * Dangers that fall out of that, starting with: Oops my reverse function actually produces invalid trash because that's not how text works. * Bad interview code exercises. Do you actually reverse strings here? No? Then why are you wasting my time?
- joppy 5y agoSo reverse a list/slice of anything else then?
- ParetoOptimal 5y agoNot a fan of Go, but I'll give credit where it's due: This is a good feature for the language to afford.
- mettamage 5y agoWhy are you not a fan? Asking as someone who likes a few things and dislikes a few things about it. So basically asking out of curiosity.
- mseepgood 5y agoProbably something about generics, which is no longer an issue if you use the Go 1.18 Release Candidate today or the final release in four weeks.
- vsnf 5y agoIt's about a lot more than just generics, at least for me. I'm not interested in derailing this thread to into one bashing Go, but there's a bunch of things that are distasteful about the language. Go appeals to certain kinds of programmers, and is actively repulsive to different kinds. This division breeds animosity when the kind of programmers who are repulsed are forced to interact with it. Also, the community has a big attitude problem.
- zibzab 5y agoI think that is a good thing. Most people trying to "fix" go are basically trying to make it into that other language they are more comfortable with. If I wanted to use ruby, I would use ruby. I use go because even its presumed limitations are an advantage (for me).
- ParetoOptimal 5y agoEnums and iota weirdness made my love for Go start to crack. I learned about abstract data types and discriminated unions, then wondered why Go didn't have those. The prevalence and overuse of interface{} undermined type safety at jobs I had writing Go and errors from this apparently popular style of Go programming filled the logs. Then I saw someone say "Go's error handling on values is good, but it's better in a language with Either". I looked up examples, and indeed it seemed nicer and retained the value proposition of errors as values. I wish Go had either. Then bugs started happening in code that was copy pasted... but why was it copy pasted? Lack of generics were the final straw and I started playing with Haskell in the evenings. It gave me the type safety I wanted and more. It's not perfect, but it's based on a philosophy I believe is more correct than the worse is better values of Go that seek micro-level simplicity at the cost of macro-level complexity.
- siscia 5y agoCan we get the minimisation features also for the generative/property based testing? I found that property base testing is so much better than unit testing and only marginally harder to write, after some practice at least. However the property base testing in the go standard library is so unadvanced that it is basically impractical. Hopefully with generics the status will improve.
- kodablah 5y agoI, probably like many others, want struct-based params. I am thinking of a lib that can fuzz a func w/ struct param(s). It takes the struct params, extracts fields into a deterministic list, builds a dynamic function via reflect.MakeFunc where each field is an arg and the body of the puts args on structs and invokes the original function. That dynamically created function is then passed to f.Fuzz. Does a lib like this exist?
- morelisp 5y agotesting/quick contains an interface and basic implementation for randomly generating arbitrary structs. https://pkg.go.dev/testing/quick#Value https://pkg.go.dev/testing/quick#Value For traditional fuzzing you usually want a "blacker" barrier between the input and processing. E.g. in an algorithm with exponential growth it's not helpful to know that a raw 1000000 element input slice runs out of memory; it is helpful to know if a mere 100 bytes over the wire can somehow trigger that. If you do need arbitrary input, it's easy enough to treat the fuzz input as JSON or whatever. But most code that you're interested in testing starting from a truly arbitrary state takes only a small number of scalars or a byte stream, not complex structures. If you have complex structures it's probably because you have complex invariants arbitrary data won't satisfy. (And you're probably as much or more interested in verifying your code maintains those invariants during whatever preparatory steps you have which outputs those structures, not just their use as inputs.)
- kodablah 5y ago> For traditional fuzzing you usually want a "blacker" barrier between the input and processing. They clearly didn't want it that opaque and bytes only since they accept primitives and accept arbitrary param count. Why not just accept byte slice only and make people binary encode even primitives? The reason is it takes a long time to build a corpus when unnecessarily making the fuzzer figure out UTF-8, JSON, little-endian ints, etc. Using structured typing is a reasonable approach for an in-language fuzzer. They currently have a user-defined length of user-defined types, so it's only natural to translate from more complex types and save the generator time.
- gilgad13 5y ago> The coordinator communicates with each worker using an improvised JSON-based RPC protocol over a pair of pipes. The protocol is pretty basic because we didn't need anything sophisticated like gRPC, and we didn't want to introduce anything new into the standard library. Interesting that this does not use `encoding/gob` by these criteria. I think `encoding/gob` is a nice example of what is possible with reflection, and I've certainly learned techniques from reading its implementation, but I haven't seen very many uses in the wild and this certainly would seem like a vote of no-confidence.