Skip to content

"close" syscall run twice on same file descriptor when loading binary LZW-compressed TIFF #7042

@philip-bl

Description

@philip-bl

If I open an LZW-compressed binary TIFF file using pillow and load its data, pillow calls the "close" syscall on one of the two relevant file descriptors twice. The second call results in a "EBADF (Bad file descriptor)" error. The program doesn't crash and everything seems to work ok. But if multithreading is used and a race condition happens, this might crash the program.

In this example, the expected behaviour would be that there is only one "close" syscall on each of the two relevant file descriptors and that there is no "EBADF" error. Below, I show how to reproduce the problem.

$ cat create_tiff_file.py
#!/usr/bin/env python

import numpy as np
import PIL.Image as Image

Image.fromarray(np.random.rand(500, 600) > 0.9) \
    .save("example.tif", format="TIFF", compression="tiff_lzw")
$ cat read_tiff_file.py
#!/usr/bin/env python

import PIL.Image as Image

print("Right before the with-block", flush=True)
with Image.open("example.tif") as mask:
    print("Right in the beginning of the with-block", flush=True)
    mask.load()
    print("Right after load()", flush=True)
print("Right after the with-block", flush=True)
$ mamba create -n pillow_double_close_env --override-channels --channel anaconda python=3.11  # omitting the output
$ mamba activate pillow_double_close_env  # omitting the output
$ pip install numpy pillow  # omitting the output
$ conda list
# packages in environment at /home/philip/soft/mambaforge/envs/pillow_double_close_env:
#
# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                        main    anaconda
_openmp_mutex             5.1                       1_gnu    anaconda
bzip2                     1.0.8                h7b6447c_0    anaconda
ca-certificates           2023.01.10           h06a4308_0    anaconda
certifi                   2022.9.24       py311h06a4308_0    anaconda
ld_impl_linux-64          2.38                 h1181459_1    anaconda
libffi                    3.4.2                h6a678d5_6    anaconda
libgcc-ng                 11.2.0               h1234567_1    anaconda
libgomp                   11.2.0               h1234567_1    anaconda
libstdcxx-ng              11.2.0               h1234567_1    anaconda
libuuid                   1.41.5               h5eee18b_0    anaconda
ncurses                   6.4                  h6a678d5_0    anaconda
numpy                     1.24.2                   pypi_0    pypi
openssl                   1.1.1s               h7f8727e_0    anaconda
pillow                    9.4.0                    pypi_0    pypi
pip                       22.2.2          py311h06a4308_0    anaconda
python                    3.11.0               h7a1cb2a_2    anaconda
readline                  8.2                  h5eee18b_0    anaconda
setuptools                65.5.0          py311h06a4308_0    anaconda
sqlite                    3.40.1               h5082296_0    anaconda
tk                        8.6.12               h1ccaba5_0    anaconda
tzdata                    2022a                hda174b7_0    anaconda
wheel                     0.37.1             pyhd3eb1b0_0    anaconda
xz                        5.2.10               h5eee18b_1    anaconda
zlib                      1.2.13               h5eee18b_0    anaconda
$ pip freeze
certifi @ file:///croot/certifi_1669749558946/work/certifi
numpy==1.24.2
Pillow==9.4.0
$ python create_tiff_file.py
$ strace -e decode-fds=path -e trace=openat,close ./read_tiff_file.py
# omitting all the output except for the few last lines, which are the most relevant
Right in the beginning of the with-block
close(4</pillow_double_close_bug_report/example.tif>) = 0
close(4)                                = -1 EBADF (Bad file descriptor)
close(3</pillow_double_close_bug_report/example.tif>) = 0
Right after load()
Right after the with-block
+++ exited with 0 +++

Also, I want to note that this caused my training of a neural network to crash once. It happened because Pytorch's DataLoader uses multithreading and a race condition happened.

Metadata

Metadata

Assignees

No one assigned

    Labels

    AnacondaIssues with Anaconda's PillowTIFF

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions