Source: wand
Version: 0.7.1-1
Severity: normal
Tags: patch upstream

Dear Maintainer,

I discovered an upstream bug[1] which is causing migration issues on
Ubuntu[2].

Essentially, if the OS and Python file cursors ever get desynced, then
wand will pass the incorrect offset to ImageMagick. This was caused by
the move from RawIOBase to IOBase in the fix for upstream issue 675[3].

This problem can be reproduced on Debian by running a Python script
which first performs a buffered read and then seeks back to the
beginning:

```
from wand.image import Image

GIF = (b'GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00!\xf9'

b'\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
open("test.gif", "wb").write(GIF)

f = open("test.gif", "rb")
f.read(16)
f.seek(0)
Image(file=f)
```

Running this script on Debian will produce the error:

```
$ python3 test.py
Traceback (most recent call last):
  File "/root/wand/test.py", line 10, in <module>
    Image(file=f)
    ~~~~~^^^^^^^^
  File "/usr/lib/python3/dist-packages/wand/image.py", line 9632, in
__init__
    self.read(file=file)
    ~~~~~~~~~^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/wand/image.py", line 10488, in read
    self.raise_exception()
    ~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib/python3/dist-packages/wand/resource.py", line 211, in
raise_exception
    raise e
wand.exceptions.MissingDelegateError: no decode delegate for this image
format `/tmp/magick-dnTm7mNPKK3qbsxOo1cdKU1ziBT5zdc8' @
error/constitute.c/ReadImage/755
```

I have added a patch which fixes this issue. I have also forwarded the
fix upstream[4], so this delta will eventually be dropped.

[1]: https://github.com/emcconville/wand/issues/688
[2]: https://bugs.launchpad.net/ubuntu/+source/wand/+bug/2158013
[3]: https://github.com/emcconville/wand/issues/675
[4]: https://github.com/emcconville/wand/pull/689
Description: Sync file offsets before reading
 A buffered Python file object can advance both the OS and the Python file
 cursor offsets. seek() can rewind the Python cursor without moving the OS
 cursor. wand cannot handle this situation and will pass the incorrect offset to
 ImageMagick.
 .
 This patch fixes this situation by forcing the OS cursor to match the object's
 logical position.
 .
 Non-seekable streams return OSError and closed objects return ValueError when
 attempting to lseek; in these cases the offset is left alone.
Author: Max Gilmour <[email protected]>
Bug: https://github.com/emcconville/wand/issues/688
Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/wand/+bug/2158013
Forwarded: https://github.com/emcconville/wand/pull/689
Last-Update: 2026-06-23
---
--- a/wand/image.py
+++ b/wand/image.py
@@ -13,6 +13,7 @@
 import ctypes
 import functools
 import numbers
+import os
 import weakref
 from collections import abc
 from io import IOBase
@@ -9879,6 +9880,12 @@
                      callable(getattr(file, 'fileno', None)),
                      hasattr(file, 'mode'))
             if all(is_fd):
+                try:
+                    # Resync the fd offset before reading
+                    os.lseek(file.fileno(), file.tell(), os.SEEK_SET)
+                except (OSError, ValueError):
+                    # Do nothing if stream isn't seekable
+                    pass
                 fd = libc.fdopen(file.fileno(), binary(file.mode))
                 r = library.MagickPingImageFile(instance.wand, fd)
             elif not callable(getattr(file, 'read', None)):
@@ -10465,6 +10472,12 @@
                      callable(getattr(file, 'fileno', None)),
                      hasattr(file, 'mode'))
             if all(is_fd):
+                try:
+                    # Resync the fd offset before reading
+                    os.lseek(file.fileno(), file.tell(), os.SEEK_SET)
+                except (OSError, ValueError):
+                    # Do nothing if stream isn't seekable
+                    pass
                 fd = libc.fdopen(file.fileno(), binary(file.mode))
                 r = library.MagickReadImageFile(self.wand, fd)
             elif not callable(getattr(file, 'read', None)):

Reply via email to