-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Win32: map.c integer overflow crashes PIL in tobytes() #5221
Description
What did you do?
Running some tests using PIL test images, my program quit silently. Narrowed down:
import traceback
from PIL import Image
print(Image.__version__)
try:
with Image.open(r'Pillow-master/Tests/images/l2rgb_read.bmp') as im:
print(f'mode: {im.mode} size: {im.size}')
im.tobytes()
except Exception as e:
print(f'Exception: {e.args} {e}')
traceback.print_exc()
print('ok')What did you expect to happen?
I expected an Exception and traceback, because this is a test image of a truncated BMP that is supposed to be a huge image. It should then print 'ok'. For example, in WSL2, (Python 3.8.5, PIL 7.0.0) I do get "ValueError: buffer is not large enough" and the 'ok' prints.
What actually happened?
15:14:17.99 e:\rdg\pil>testit
8.1.0
E:\rdg\pil\PIL\Image.py:2847: DecompressionBombWarning: Image size (151587072 pixels) exceeds limit of 89478485 pixels,
could be decompression bomb DOS attack.
warnings.warn(
mode: L size: (3158064, 48)
15:14:52.54 e:\rdg\pil>
No exception, no 'ok' printed.
What are your OS, Python and Pillow versions?
- OS: Windows 10 Pro 1909
- Python: Python 3.9
- Pillow: PIL 8.1.0, or a build from the latest source.
Discussion
Program dies in tobytes(). After considerable effort, I found that the test in map.c, mapping_readimage() line 214:
if (mapper->offset + size > mapper->size) {
PyErr_SetString(PyExc_OSError, "image file truncated");
return NULL;
}fails (should test true and raise the OSError) due to integer overflow: mapper->offset is 2147483440, size is 75793536; the 32-bit signed sum is -2071690320.
The file is mapped, and eventually tobytes() goes to ImagingRawEncode() in RawEncode.c, which calls state->shuffle() to move the bytes using a "packer" that is simply memcpy(). The first attempt to fetch from the (non-existent) source causes the program to fail, returning to the command prompt silently, no fault or other message. Nasty.
I poke around and find that the image apparently exists for a test test_overflow() in test_map.py. This test is skipped for Win32 because "Win32 does not call map_buffer". But Win32 does call mapping_readimage() and the same file causes this crash!
Resolution:
The "image file truncated" test in mapping_readimage() needs to be guarded against integer overflow. As Victor Stinner writes, "Check if an operation will overflow is hard, especially for signed numbers." I've written a bit of C, but I'll leave this one to you folks for now. My quick and dirty fix is to use:
if (mapper->offset + size > mapper->size || mapper->offset + size < 0)but I suspect this is not a good fix.