Repository: python-pillow/Pillow

Issue:
Segmentation Fault while saving jpeg2000 image using wrong param
### What did you do?
Save a jpeg2000 image.
`Image.fromarray(np.zeros((64,64), dtype=np.uint8)).save('img.jp2', quality_layers='40')`
### What did you expect to happen?
Throw an exception.
### What actually happened?
`[1]    18024 segmentation fault (core dumped)  python3`
### What are your OS, Python and Pillow versions?

* OS: Ubuntu 16.04
* Python: 3.5.2
* Pillow: 5.3.0

I know that I should pass a number to `quality_layers` instead of a string. But it should not cause python segmentation fault. It is better to throw a exception in python.

```python
import numpy as np
from PIL import Image
Image.fromarray(np.zeros((64,64), dtype=np.uint8)).save('img.jp2', quality_layers='40')
```



Discussion:
Thanks for the suggestion. I have created PR #3464

For the record, it is seg faulting at https://github.com/python-pillow/Pillow/blob/e3d5494a513b7454efc7eb10241b1ce2b28f939d/src/libImaging/Jpeg2KEncode.c#L492

Also, the docs say that `quality_layers` should be a sequence of numbers, not just a number - https://pillow.readthedocs.io/en/5.3.x/handbook/image-file-formats.html?highlight=quality_layers#jpeg-2000 -
> quality_layers
> A sequence of numbers, each of which represents either an approximate size reduction (if quality mode is “rates”) or a signal to noise ratio value in decibels. If not specified, defaults to a single layer of full quality.

Resolving patch:
diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py
--- a/src/PIL/Jpeg2KImagePlugin.py
+++ b/src/PIL/Jpeg2KImagePlugin.py
@@ -232,6 +232,13 @@ def _save(im, fp, filename):
     tile_size = info.get('tile_size', None)
     quality_mode = info.get('quality_mode', 'rates')
     quality_layers = info.get('quality_layers', None)
+    if quality_layers is not None and not (
+        isinstance(quality_layers, (list, tuple)) and
+        all([isinstance(quality_layer, (int, float))
+             for quality_layer in quality_layers])
+    ):
+        raise ValueError('quality_layers must be a sequence of numbers')
+
     num_resolutions = info.get('num_resolutions', 0)
     cblk_size = info.get('codeblock_size', None)
     precinct_size = info.get('precinct_size', None)


Test patch:
diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py
--- a/Tests/test_file_jpeg2k.py
+++ b/Tests/test_file_jpeg2k.py
@@ -105,6 +105,22 @@ def test_reduce(self):
         im.load()
         self.assertEqual(im.size, (160, 120))
 
+    def test_layers_type(self):
+        outfile = self.tempfile('temp_layers.jp2')
+        for quality_layers in [
+            [100, 50, 10],
+            (100, 50, 10),
+            None
+        ]:
+            test_card.save(outfile, quality_layers=quality_layers)
+
+        for quality_layers in [
+            'quality_layers',
+            ('100', '50', '10')
+        ]:
+            self.assertRaises(ValueError, test_card.save, outfile,
+                              quality_layers=quality_layers)
+
     def test_layers(self):
         out = BytesIO()
         test_card.save(out, 'JPEG2000', quality_layers=[100, 50, 10],