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.
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).)
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 callsstbi__convert_format()
instead ofstbi__convert_format16()
, returning a buffer that is 2N bytes long instead of 4N bytes. Sinceri.bits_per_channel
is still 16 when control returns tostbi__load_and_postprocess_8bit()
,stbi__convert_16_to_8()
attempts to readimg_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:
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)
.)Thanks!