Skip to content
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

Add basic image parser fuzzing #1834

Closed
wants to merge 35 commits into from
Closed

Add basic image parser fuzzing #1834

wants to merge 35 commits into from

Conversation

@pauldreik
Copy link
Contributor

pauldreik commented Jul 18, 2019

Hi!
I wrote a fuzzer, and it found some issues. Asking for it to be merged even if the problems are not fixed.
I wrote a unit test to have a place to put the bad input I found, so it's easier to reproduce and possible to prevent the problem from appearing in the future. Unfortunately, the unit test works differently on posix and non-posix, since I did not want to demand C++17 or boost filesystem to recurse the input directory.

These problems were found, undefined behaviour sanitizer is needed to reproduce. I also could not reproduce all problems with gcc, clang was needed.

integer overflow when allocating data

this has been fixed, by adding the overflow check (integer_utils.h). There is a binary file which triggers the error, but since it's fixed now the file passes without problems. To reproduce, build the unit test with clang and address sanitizer.
Status: fixed

out of range value casted to unsigned char

This is commented in the pixel.h file, and there is an example file.
Status: unfixed

reference binding to null pointer of type 'dlib::rgb_pixel

I believe this is because the data pointer is empty or the index is invalid, but I'm not sure since I did not look into it. I just added an example file integer_overflow_and_nullpointer.bin that provokes it, and added an assert in the pixel.h file (maybe insufficient).
Status: unfixed

signed integer overflow in image_loader.h

0 - biHeight overflows if biHeight is INT_MIN. added a comment, and there is an example file to provoke it.
Status: unfixed

large allocations

I think it's problematic if a small input file can result in a (unreasonably) large allocation. One of these is inside giflib, so that is not dlib's problem. But there are other places where it would make sense to be a bit more skeptic to the input, I wrote a comment about it in serialize.h
Fuzzing will find these allocations quite quick if using address sanitizer. I think there's a problematic middle ground between reasonably small allocations and ridiculously large ones. The former will be fast anyway and the latter will cause an exception std::bad_alloc to be thrown which is good. However, inbetween a lot of memory will be used and if it's initialized, it may slow down the process a lot. This warrants a more pessimistic attitude against the input data, IMHO.

Fuzzing

I had to modify the code here and there to make it go faster - fuzzing this was extremely slow (one case per second or so) but with the fixes, it is at least 50 a second or so, which also is slow. You are probably the best person to point out what could be changed to get the speed up.
The HangChecker class could be adjusted a bit, to account for evil input not hogging the parser. For now, it's only limited in fuzz mode and does nothing in production mode.

Cheers,
Paul

pauldreik added 20 commits Jul 16, 2019
/home/paul/code/delaktig/dlib/dlib/../dlib/image_loader/image_loader.h:108:30: runtime error: signed integer overflow: 0 - -2147483648 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/paul/code/delaktig/dlib/dlib/../dlib/image_loader/image_loader.h:108:30 in
/home/paul/code/delaktig/dlib/dlib/../dlib/array2d/array2d_kernel.h:115:24: runtime error: reference binding to null pointer of type 'dlib::rgb_pixel'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/paul/code/delaktig/dlib/dlib/../dlib/array2d/array2d_kernel.h:115:24 in
Executed ../cmin/04b6f7208f050ff07d61f4d2f9d9b23463bf5803 in 2 ms
@pauldreik

This comment has been minimized.

Copy link
Contributor Author

pauldreik commented Jul 18, 2019

Hmm, I don't get why appveyour doesn't like check_c_source_compiles ?

Unknown CMake command "check_c_source_compiles"

I used cmake 3.10 on Ubuntu while developing, and appveyour has 3.13.3 according to the output.
I'm too tired to troubleshoot this now, sorry.

last = data + nr_*nc_ - 1;
const auto prod = dlib::detail::safe_multiply(nr_,nc_);
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if(prod>200*200) {

This comment has been minimized.

Copy link
@davisking

davisking Jul 19, 2019

Owner

It's very reasonable to allocate arrays larger than 200x200.

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 19, 2019

Author Contributor

Yes, I agree! This is only during fuzzing, to speed it up. That's why it's inside the ifdef check.

@@ -1223,6 +1223,8 @@ namespace dlib
c1.b = src.blue/255.0;
c2 = RGB2HSL(c1);

// Bug: undefined behaviour trigged. This is what ubsan says:
// runtime error: -14.6786 is outside the range of representable values of type 'unsigned char'

This comment has been minimized.

Copy link
@davisking

davisking Jul 19, 2019

Owner

Did this actually happen? What input causes this?

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 19, 2019

Author Contributor

Yes, try running the newly added unit test with the inputs in the problematic directory, and you will see it. (compiling with clang and undefined behaviour sanitizer)

This comment has been minimized.

Copy link
@davisking

davisking Jul 19, 2019

Owner

Can you find out the exact pixel value that triggers it? That would make fixing and testing it easier for me :)

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 19, 2019

Author Contributor

This will trigger the undefined behaviour, but it won't be detected by DLIB_TEST, because the UB is inside the function. I put this inside the unit test in pixel.cpp

            rgb_pixel src;
            src.red=246;
            src.green=170;
            src.blue=245;
            hsi_pixel dest;
            assign_pixel(dest,src);
            DLIB_TEST(dest.h>=0);
            DLIB_TEST(dest.h<=360);

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 20, 2019

Author Contributor

I think the missing part is the check for hue<0, it should be wrapped around by adding 360. Looking at the code, it seems like it gives -60<hue<300

Looking at the wikipedia entry, it seems like the function could be simplified a bit:
https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 20, 2019

Author Contributor

I pushed a minimal fix.

@@ -1080,10 +1080,23 @@ namespace dlib
try
{
unsigned long size;
deserialize(size,in);
deserialize(size,in);
// Possible bug:

This comment has been minimized.

Copy link
@davisking

davisking Jul 19, 2019

Owner

I'm not worried about this kind of thing. If someone makes a file that has crazy stuff in it then that's a user error.

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 19, 2019

Author Contributor

Ok, that indirectly means dlib should never be used on untrusted input?

This comment has been minimized.

Copy link
@davisking

davisking Jul 19, 2019

Owner

Yes, don't call deserialize() on inputs you don't trust. There shouldn't ever be arbitrary code execution type of errors, but if someone wants to try and deserialize a 100GB block of data they are free to do that. But most users would not be happy with the results, although some would.

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 19, 2019

Author Contributor

I get your argument regarding allocations, but what about a mismatch on the size of the buffer and what is actually read? If the file is short, shouldn't that be flagged as an error? Maybe the input file has been truncated, and it would be nice to alert the user about that.

This comment has been minimized.

Copy link
@davisking

davisking Jul 20, 2019

Owner

Most definitely. I think this is already caught by existing code. If it isn’t it should be fixed.

dlib/test/image_load.cpp Outdated Show resolved Hide resolved
the counter exceeds some threshold.
!*/

struct HangChecker {

This comment has been minimized.

Copy link
@davisking

davisking Jul 19, 2019

Owner

I would rather keep code like this out of the main dlib repo, or potentially in a branch. But not on master. This includes any #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION type of thing. That stuff should be in a branch or somewhere outside maser, and when errors are surfaced, fixed on master.

This comment has been minimized.

Copy link
@pauldreik

pauldreik Jul 19, 2019

Author Contributor

I understand, but that makes the fuzzing prone to bit rot.
The macro is the defacto standard, see https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode

Here's an example from fmt: https://github.com/fmtlib/fmt/blob/6a497e1d061993cce54c2d71506a90155a3725e6/include/fmt/format.h#L599

Here are some recommendations from oss-fuzz:
https://github.com/google/oss-fuzz/blob/master/docs/ideal_integration.md#tldr

This comment has been minimized.

Copy link
@davisking

davisking Jul 20, 2019

Owner

I hear you. But you are talking about parts of dlib that have not changed in many years, and are not likely to change. I doubt such bit rot will be a problem, and if this fuzzing ends up being used long term rather than being eventually abandoned and such problems do arise they can be handled then, maybe by merging into main.

@robertramey

This comment has been minimized.

Copy link

robertramey commented Jul 19, 2019

Hmm - have you considered using boost/safe_numerics to address these sorts of problems? https://github.com/boostorg/safe_numerics

@pauldreik

This comment has been minimized.

Copy link
Contributor Author

pauldreik commented Jul 19, 2019

Hmm - have you considered using boost/safe_numerics to address these sorts of problems? https://github.com/boostorg/safe_numerics

@robertramey Yes, I did not want to bring in a dependency on boost (especially when safe_numerics is so recently added to boost, it takes a while until new boost versions trickle down to end users).

@davisking

This comment has been minimized.

Copy link
Owner

davisking commented Jul 19, 2019

@pauldreik

This comment has been minimized.

Copy link
Contributor Author

pauldreik commented Jul 19, 2019

I fixed some of the issues, but I really would like you to reconsider having the fuzzing constructs in the master branch. Having a separate branch for fuzzing leads to manual maintenance - every time you make a change on master, someone needs to update that branch. I have done that during development of the fuzzers in fmt, and it's not a long term solution.
The fuzzer itself lives in a separate directory, just like the tests, so it should not be interfering with anyone.
I think the results above, from a very basic shot at fuzzing, show that it is indeed useful and worth a bit of uglification of the source code.

pauldreik added 4 commits Jul 20, 2019
@davisking

This comment has been minimized.

Copy link
Owner

davisking commented Jul 20, 2019

I fixed some of the issues, but I really would like you to reconsider having the fuzzing constructs in the master branch. Having a separate branch for fuzzing leads to manual maintenance - every time you make a change on master, someone needs to update that branch. I have done that during development of the fuzzers in fmt, and it's not a long term solution.
The fuzzer itself lives in a separate directory, just like the tests, so it should not be interfering with anyone.
I think the results above, from a very basic shot at fuzzing, show that it is indeed useful and worth a bit of uglification of the source code.

I do a lot of this kind of testing myself, but I don't commit the code that involves intrusive changes to the actual library. So I'm already accustomed to the churn, and normally just throw away the code.

Can you break this PR up into specific PRs targeted at fixing the issues surfaced by fuzzing while keeping any instrumentation code used for finding them in a separate fork? I'm cool with stuff like safe multiplies being in dlib main though. That's all to the good.

@pauldreik

This comment has been minimized.

Copy link
Contributor Author

pauldreik commented Jul 26, 2019

I submitted the bugfixes on separate pull requests as instructed.

Would you be ok with add the fuzzers in the fuzzer/ directory on master? They won't interfere since it's just a subdirectory not being referenced. The constructs for preventing slow input could be kept on another branch, preferably in this repo instead of mine. That means people wanting to use or contribute to fuzzing, could find them in this repo.

Closing this pull request anyhow.

@pauldreik pauldreik closed this Jul 26, 2019
@davisking

This comment has been minimized.

Copy link
Owner

davisking commented Jul 27, 2019

Yeah I'm happy with that, so long as no intrusive code is in master. But certainly fuzzer tooling could be in master, in tools/fuzzer, dlib/testing/fuzzer, or some similar location. I'm not totally sure why the intrusive code is needed at all though. Why can't a timeout at a higher level be used? That's what I typically do when I do this kind of testing. This keeps any fuzzing code out of the actual codebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.