Headline
CVE-2021-42715: In stb_image's HDR reader, loading a specially constructed invalid HDR file can result in an infinite loop within the RLE decoder · Issue #1224 · nothings/stb
An issue was discovered in stb stb_image.h 1.33 through 2.27. The HDR loader parsed truncated end-of-file RLE scanlines as an infinite sequence of zero-length runs. An attacker could potentially have caused denial of service in applications using stb_image by submitting crafted HDR files.
Summary
stb_image’s HDR loader in versions 1.33 to and including v2.27 parsed truncated end-of-file RLE scanlines as an infinite sequence of zero-length runs. An attacker could potentially have caused denial of service in applications using stb_image by submitting specially constructed HDR files.
CVE number: CVE-2021-42715
Describe the bug
In stb_image’s HDR reader, loading a specially constructed invalid HDR file can
result in an infinite loop within the RLE decoder.
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.
The issue occurs in this loop within stbi__hdr_load():
while ((nleft = width - i) > 0) {
count = stbi__get8(s);
if (count > 128) {
// Run
value = stbi__get8(s);
count -= 128;
if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = value;
} else {
// Dump
if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = stbi__get8(s);
}
}
The proof of concept file manages to get this part of the decoder into a state
where:
- nleft is equal to 11;
- s is at the end of the file.
Because s is at the end of the file, stbi__get8() always returns 0, since
s->img_buffer == s->img_buffer_end here:
stbi_inline static stbi_uc stbi__get8(stbi__context *s)
{
if (s->img_buffer < s->img_buffer_end)
return *s->img_buffer++;
if (s->read_from_callbacks) {
stbi__refill_buffer(s);
return *s->img_buffer++;
}
return 0;
}
This means that count is always set to 0; this passes the error check, but
doesn’t affect any program state, meaning that the loop runs forever, an
availability issue.
To Reproduce
This .zip contains a 222 KB .hdr file, rle_iloop_poc.hdr, which reproduces this issue:
rle_iloop_poc.zip
Calling stbi_load() with a path to this file never returns. 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 found using the Radamsa fuzzer.
I think this particular file works by setting the RLE flags on the last scanline
in the file and being truncated in just the right place, but I’m not 100% sure.
Expected behavior
stbi_load should eventually return.
Based on Bruce Walter’s
https://www.graphics.cornell.edu/~bjw/rgbe/rgbe.c HDR reader,
it seems like the intended behavior is that a run length of 0 should be treated
as invalid, which is the approach the pull request takes. However, other
solutions are possible (e.g. detecting when the end of the file has been reached) - I don’t have any preference either way.
Thanks!
Related news
Gentoo Linux Security Advisory 202409-15 - Multiple vulnerabilities have been discovered in stb, the worst of which lead to a denial of service. Versions greater than or equal to 20240201 are affected.