Repository: python-pillow/Pillow

Problem statement:
Large BMP files crash (i.e. more than 2**32 pixels)
I started asking on StackOverflow about my original problem, and the code is pasted over there:
https://stackoverflow.com/questions/59353721/python-pillow-opening-1-bit-depth-files-with-8-bits

but after moving to a larger machine (256 GB RAM) I moved on from the original issue and onto an Exception in the code.

Basically I'm trying to save a 59GigaPixel single-channel (1 bit depth) BMP file, which is yielding this error:
```
Traceback (most recent call last):
  File "stitch_bmps.py", line 75, in <module>
    blank.save("59GP.bmp", "BMP")
  File "/home/nmz787/.local/lib/python3.7/site-packages/PIL/Image.py", line 2084, in save
    save_handler(self, fp, filename)
  File "/home/nmz787/.local/lib/python3.7/site-packages/PIL/BmpImagePlugin.py", line 332, in _save
    + o32(offset)  # reserved
  File "/home/nmz787/.local/lib/python3.7/site-packages/PIL/_binary.py", line 91, in o32le
    return pack("<I", i)
struct.error: 'I' format requires 0 <= number <= 4294967295
```

Here's a small example to reproduce easily (requires at least 61GB of RAM, or RAM+swap):
```python
from PIL import Image
a = Image.new("1", (247246, 247246))
a.save("blank_59GP.bmp", "BMP")
```

### What are your OS, Python and Pillow versions?

* OS: Linux version 4.9.164-vs2.3.9.8-beng (root@ex64-jessie) (gcc version 4.9.2 (Debian 4.9.2-10+deb8u2) ) #1 SMP Wed Mar 20 20:18:21 GMT 2019
* PIP: 18.1
* Python: 3.7.3


Hints:
Pillow Version: 6.2.1
Looks like BMP max filesize is limited to 4GiB. The second field in BMF file header is filesize in bytes, which is 4 bytes int, which can not exceed 2^32 (4GiB). Since your image is 1bpp image, with given dimensions image size would have to be 7,641,879,368 bytes, which exceeds 4 byte int capacity.
Thanks for the clarification, can we get this notice added to the code during the `save` call, so it's absolutely clear what the problem is (user error).

Patch:
diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py
--- a/src/PIL/BmpImagePlugin.py
+++ b/src/PIL/BmpImagePlugin.py
@@ -321,12 +321,15 @@ def _save(im, fp, filename, bitmap_header=True):
     # bitmap header
     if bitmap_header:
         offset = 14 + header + colors * 4
+        file_size = offset + image
+        if file_size > 2 ** 32 - 1:
+            raise ValueError("File size is too large for the BMP format")
         fp.write(
-            b"BM"
-            + o32(offset + image)  # file type (magic)
-            + o32(0)  # file size
-            + o32(offset)  # reserved
-        )  # image data offset
+            b"BM"  # file type (magic)
+            + o32(file_size)  # file size
+            + o32(0)  # reserved
+            + o32(offset)  # image data offset
+        )
 
     # bitmap info header
     fp.write(


Test patch:
diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py
--- a/Tests/test_file_bmp.py
+++ b/Tests/test_file_bmp.py
@@ -41,6 +41,13 @@ def test_save_to_bytes(self):
             self.assertEqual(im.size, reloaded.size)
             self.assertEqual(reloaded.format, "BMP")
 
+    def test_save_too_large(self):
+        outfile = self.tempfile("temp.bmp")
+        with Image.new("RGB", (1, 1)) as im:
+            im._size = (37838, 37838)
+            with self.assertRaises(ValueError):
+                im.save(outfile)
+
     def test_dpi(self):
         dpi = (72, 72)