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
test: Fuzzing siphash against reference implementation [Request for feedback] #19920
Conversation
I think including a full other reference implementation is overkill. Every byte array as input has exactly one hash, regardless of how it is split up in chucks and hashed. You can verify the "hash everything at once" code path using test vectors. These are usually published by the same body that standardized the function, and are picked to give good coverage. By the nature of hashing, it tends to be sufficient to test things with a random inputs for a variety of lengths, so that the padding code gets exercised - but beyond that, the actual hashed data is of little relevance, as tiny errors in the actual hashing logic should mean a completely different hash anyway. As you point out, the buffering logic is often far more error-prone, and harder to test. I think it's a useful target for fuzzing, but all you need for that is a fuzzer that takes as input a number of chunks, and then hashes them both concatenated at once, and split up as separate writes, and compares the result. |
Concept ACK This is excellent! BTW, if you're interested in differential cryptography fuzzing you should check out @guidovranken's really cool Cryptofuzz project. The list of bug trophies for Cryptofuzz is amazing! Cryptofuzz has found bugs in OpenSSL, LibreSSL, BoringSSL, libgcrypt, Crypto++, NSS, Botan, wolfCrypt, sjcl, SymCrypt, mbed TLS, etc -- more or less everything under test :) @catenacyber's elliptic-curve-differential-fuzzer project is really cool too. |
I thought about this, but even if you test chuncks vs single write, you still go through the same writer with the same logic, so it might not find an off by one bug in the |
That should be caught using test vectors, as the code path is identical for all inputs of the same length |
You can see in the paper I've linked that some times the test vectors don't cover all cases (a bug that returned the same value everytime a message of size BLOCK_SIZE was written, no matter the content, they had only 1 vector with a message of that length, so no one noticed). but I do agree that most bugs will be covered by test vectors + fuzzing hashing by chunks vs hashing in one shot |
The nice thing about differential fuzzing as suggested by @elichai is that we don't have to make any assumptions about the implementation details of the function being fuzzed. Personally I think that benefit is worth the cost (in the form of an extra file in |
So include an test vector for every length, up to some multiple of the block size. That's still far less effort than including/integrating a whole separate implementation and maintain it. |
@practicalswift I think that spending time on blindly writing fuzzers without making any assumptions about the code you're testing is often a waste of time. You can be far more effective by focussing on things that aren't guaranteed by the source code, or are likely to trigger edge cases. And this isn't just your time, but also the time of people reviewing and maintaining the codebase later. Fuzzing is good at finding combinations of inputs that trigger various code paths, so use it for that. |
FWIW the fuzzers that found sipa/miniscript#7, sipa/miniscript#12, sipa/miniscript#13, sipa/miniscript#14, sipa/miniscript#15 and sipa/miniscript#25 were all written without making any assumptions about the code being tested :) Don't trust: fuzz! :) |
Don't put words in my mouth. I'm not saying that you can't find issues that way. I'm saying you can be more effective at it if you understand the code better. |
Thanks for the heads up @practicalswift . I've implemented differential testing of Bitcoin's Siphash. No bugs found. @sipa Untested but the use of |
I don't have any background in implementing hash functions, so I have no opinion on whether to only test that different chunk sizes on the same input reveal the same output or to test against a full reference impl. Maintaining a reference impl for hash functions should hopefully be of little overhead because we can assume they are well tested, bug free and stable, right? Though, a requirement for merging this is that all tests pass. Right now they fail:
|
a563a2e
to
7f80381
Compare
Yes, a signed integer overflow takes place in
|
Thanks! fixed. |
Tested ACK 7f80381 -- the fuzzing harness is written in such a way that it is able to trigger a signed integer overflow:
Thanks for doing this @elichai. Don't trust: fuzz! :) |
Concept ACK - really cool to see differential fuzzing in action. |
Using the fuzzer to find new test vectors and add them manually is a good idea. the problem is that every change in the logic (ie #18014) will require to re-run and generate new test vectors (and obviously test those against a reference implementation and against single full writes) |
Well, I'd normally agree, but looking at the actual code it's really small and placed in the test directory in any case. So, assuming there are no other problems such as licensing, I have no problems with it in that regard. |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
Needs rebase @elichai :) |
7f80381
to
e0d8090
Compare
cr ACK e0d8090: patch still looks correct Personally I think this fuzzing harness has already proven itself since it was able to trigger a now fixed signed integer overflow. No need to theorize when we have empirical evidence: practical is better than theoretical :) Thanks @elichai for raising the quality bar! |
How is this different from google/oss-fuzz#5717 ? |
I think we can close this for now. I'm also ~0 on adding another implementation to this repository. |
There is differential fuzzing in |
Hash functions are complex, and even though the implementations look simple they can easily go wrong, usually in the buffer handling around the hash function itself(the "Writer") examples:
Rewriting the last byte that was written, processing the buffer too "early" when
Write
was called with exactly a full buffer(different hash functions require different behavior in these cases), the wrong amount of zeros were written in the case the buffer was exactly full when finalizing, and more.I've personally found bugs in an implementation of a hash function via fuzzing against a reference implementation (the bug was actually in the ref impl), and there's a lot of precedent (See https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=924038 for the amount of bugs just in the reference implementations submitted to NIST's SHA3 competition)
This can also make PRs like #18014 and future SHA256 optimizations easier to review and be confident in.
I started with siphash specifically because it's small and simple so I can get feedback on this before continuing to SHA256 etc.
The downside of this method is that it means committing another implementation of the same thing separately.
About the implementation itself:
I've used a constant seperator so it would write each time different sizes, and sometimes even empty writes. and it's constant so coverage based fuzzers can easily figure this out and make the inputs cover all the branches.
Any feedback is welcome :)