nothings / stb

stb single-file public domain libraries for C/C++
https://twitter.com/nothings
Other
26.89k stars 7.72k forks source link

In stb_image's PNM reader, loading a specially constructed valid 16-bit PGM file with 4 channels can cause a crash due to an out-of-bounds read #1225

Closed NBickford-NV closed 1 year ago

NBickford-NV commented 3 years ago

Summary stb_image's PNM loader in version v2.27 incorrectly interpreted 16-bit PGM files as 8-bit when converting to RGBA, leading to buffer overflow when later reinterpreting the result as a 16-bit buffer. An attacker could potentially have crashed a service using stb_image, or read up to 1024 bytes of non-consecutive heap data without control over the read location.

CVE number: CVE-2021-42716

Describe the bug In stb_image's PNM reader, loading a valid 16-bit PGM file that is large enough with the number of components set to 4 can cause a crash in stbi__convert_16_to_8() due to an out-of-bounds read.

This issue includes a fix in pull request #1223, and a proof of concept file that can be used to reproduce the crash. We're reporting this on GitHub Issues following the guidance in issue #1213.

This appears to be due to how when stbi__pnm_load() loads a 16-bit PGM file with N bytes of data, it incorrectly calls stbi__convert_format() instead of stbi__convert_format16(), returning a buffer that is 2N bytes long instead of 4N bytes. Since ri.bits_per_channel is still 16 when control returns to stbi__load_and_postprocess_8bit(), stbi__convert_16_to_8() attempts to read img_len=4N bytes of data from this buffer, resulting in out-of-bounds reads. When N is large enough, this results in an access violation.

To Reproduce This .zip contains a 513 KB .pgm file, 16_to_8_oob_poc.pgm, which reproduces this issue: 16_to_8_oob_poc.zip

Calling stbi_load() with a path to this file and with a req_comp of 4 produces a crash. I was able to verify this using tests/image_test.c (modified slightly in order to build) on Windows version 20H2 with Microsoft Visual Studio 2019, and I expect it should reproduce on other systems as well.

This file was generated using the following Python script, and should be a valid PGM file:

f = open('16_to_8_oob_poc.pgm', 'wb')
f.write(b'P5\n\n512 512\n65535\n')
f.write(bytearray([1]*512*512*2))
f.close()

It was derived from an example found using the Radamsa fuzzer.

Interestingly, tests/pbm/basi0g16.pgm is also a 16-bit PGM file, but reading it doesn't cause a crash! I believe the reason is because this is a 32 x 32 image, so it only includes N=2048 bytes of image data, and as a result the out-of-bounds reads don't cross a page boundary that would result in an access violation.

Expected behavior The example file should be loaded without crashing.

Screenshots

Here's a screenshot showing where the crash occurs and the call stack, when run with the example file in image_test.c. (Note that the call in image_test.c may be off by 2 lines; the specific callsite is stbi_load(argv[i], &w, &h, &n, 4).)

example_pgm_access_violation_and_call_stack

Thanks!

NBickford-NV commented 3 years ago

Ah, I think this might be a duplicate of issue #1166.

rygorous commented 1 year ago

Fix is merged into dev branch, will be in the next release.

rygorous commented 1 year ago

Fixed in 2.28.