Repository: python-pillow/Pillow

Issue:
segfault: ImageCmsProfile
``` Python
>>> from PIL import ImageCms
>>> ImageCms.ImageCmsProfile(0).tobytes()
python: cmsio0.c:1301: cmsSaveProfileToIOhandler: Assertion '(hProfile != ((void *)0))' failed.
>>> ImageCms.ImageCmsProfile(1).tobytes()
Segmentation fault
```

segfault: ImageCmsProfile
``` Python
>>> from PIL import ImageCms
>>> ImageCms.ImageCmsProfile(0).tobytes()
python: cmsio0.c:1301: cmsSaveProfileToIOhandler: Assertion '(hProfile != ((void *)0))' failed.
>>> ImageCms.ImageCmsProfile(1).tobytes()
Segmentation fault
```



Discussion:
It also accepts `None` and returns a 132-bytes ICC header.

Looks like any arbitrary python object is getting passed into cms_profile_tobytes, where it's cast to a CmsProfileObject and then used. Looks like we need some type checks in the python level at  ImageCmsProfile.**init** and in the c layer in cms_profile_tobytes.

```
    def __init__(self, profile):
        """
        :param profile: Either a string representing a filename,
            a file like object containing a profile or a
            low-level profile object

        """

        if isStringType(profile):
            self._set(core.profile_open(profile), profile)
        elif hasattr(profile, "read"):
            self._set(core.profile_frombytes(profile.read()))
        else:
            # UNDONE -- could be anything, and is passed into _tobytes directly
            self._set(profile)  # assume it's already a profile
```

```
static PyObject*
cms_profile_tobytes(PyObject* self, PyObject* args)
{
    char *pProfile =NULL;
    cmsUInt32Number nProfile;
    PyObject* CmsProfile;

    cmsHPROFILE *profile;

    PyObject* ret;
    if (!PyArg_ParseTuple(args, "O", &CmsProfile)){
        return NULL;
    }
    /* UNDONE -- need to check type and success here.
       This can be any python type, and could very well be something else.
    */
    profile = ((CmsProfileObject*)CmsProfile)->profile;

```

It also accepts `None` and returns a 132-bytes ICC header.

Looks like any arbitrary python object is getting passed into cms_profile_tobytes, where it's cast to a CmsProfileObject and then used. Looks like we need some type checks in the python level at  ImageCmsProfile.**init** and in the c layer in cms_profile_tobytes.

```
    def __init__(self, profile):
        """
        :param profile: Either a string representing a filename,
            a file like object containing a profile or a
            low-level profile object

        """

        if isStringType(profile):
            self._set(core.profile_open(profile), profile)
        elif hasattr(profile, "read"):
            self._set(core.profile_frombytes(profile.read()))
        else:
            # UNDONE -- could be anything, and is passed into _tobytes directly
            self._set(profile)  # assume it's already a profile
```

```
static PyObject*
cms_profile_tobytes(PyObject* self, PyObject* args)
{
    char *pProfile =NULL;
    cmsUInt32Number nProfile;
    PyObject* CmsProfile;

    cmsHPROFILE *profile;

    PyObject* ret;
    if (!PyArg_ParseTuple(args, "O", &CmsProfile)){
        return NULL;
    }
    /* UNDONE -- need to check type and success here.
       This can be any python type, and could very well be something else.
    */
    profile = ((CmsProfileObject*)CmsProfile)->profile;

```


Resolving patch:
diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py
--- a/PIL/ImageCms.py
+++ b/PIL/ImageCms.py
@@ -162,8 +162,11 @@ def __init__(self, profile):
             self._set(core.profile_open(profile), profile)
         elif hasattr(profile, "read"):
             self._set(core.profile_frombytes(profile.read()))
+        elif isinstance(profile, _imagingcms.CmsProfile):
+            self._set(profile)
         else:
-            self._set(profile)  # assume it's already a profile
+            raise TypeError("Invalid type for Profile")
+        
 
     def _set(self, profile, filename=None):
         self.profile = profile


Test patch:
diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py
--- a/Tests/test_imagecms.py
+++ b/Tests/test_imagecms.py
@@ -321,5 +321,17 @@ def truncate_tuple(tuple_or_float):
         self.assertEqual(p.viewing_condition, 'Reference Viewing Condition in IEC 61966-2-1')
         self.assertEqual(p.xcolor_space, 'RGB ')
 
+    def test_profile_typesafety(self):
+        """ Profile init type safety
+
+        prepatch, these would segfault, postpatch they should emit a typeerror
+        """
+        
+        with self.assertRaises(TypeError):
+            ImageCms.ImageCmsProfile(0).tobytes()
+        with self.assertRaises(TypeError):
+            ImageCms.ImageCmsProfile(1).tobytes()
+    
+
 if __name__ == '__main__':
     unittest.main()