Null Pointer Dereference in TIFFClose()
Summary
A NULL pointer dereference in TIFFClose()
that is caused by a failure to open an output file (non-existent path or a path that requires permissions like /dev/null
) while specifying zones.
Version
v4.5.0, master (commit 0f8ae944) and every version since v3.9.0 Beta from 2007.
Steps to reproduce
$ git clone https://gitlab.com/libtiff/libtiff.git
$ cd libtiff/
$ ./autogen.sh
$ ./configure && make
$ tools/tiffcrop -Z 1:1 empty.tif /non-existent-path
TIFFOpen: /non-existent-path: Permission denied.
update_output_file: Unable to open output file /non-existent-path.
Segmentation fault
$ dmesg
[255894.797214] tiffcrop[20792]: segfault at 488 ip 00007f6a8f60578a sp 00007ffe57a7d7b0 error 4 in libtiff.so.6.0.0[7f6a8f603000+41000]
[255894.798588] Code: 49 8b 75 20 48 89 ef e8 b4 ed 02 00 4c 89 ee 48 89 ef e8 a9 ed 02 00 e9 40 ff ff ff 0f 1f 40 00 f3 0f 1e fa 55 53 48 83 ec 08 <48> 8b 9f 88 04 00 00 48 8b af 68 04 00 00 e8 33 ea ff ff 48 83 c4
[255894.800019] potentially unexpected fatal signal 11.
[255894.800360] CPU: 6 PID: 20792 Comm: tiffcrop Not tainted 5.15.79.1
[255894.801010] RIP: 0033:0x7f6a8f60578a
[255894.801315] Code: 49 8b 75 20 48 89 ef e8 b4 ed 02 00 4c 89 ee 48 89 ef e8 a9 ed 02 00 e9 40 ff ff ff 0f 1f 40 00 f3 0f 1e fa 55 53 48 83 ec 08 <48> 8b 9f 88 04 00 00 48 8b af 68 04 00 00 e8 33 ea ff ff 48 83 c4
[255894.802838] RSP: 002b:00007ffe57a7d7b0 EFLAGS: 00010202
[255894.803306] RAX: 0000000000000001 RBX: 0000000000000001 RCX: 0000000000000032
[255894.804013] RDX: 000000055eae8010 RSI: 000055eae7ffb010 RDI: 0000000000000000
[255894.804645] RBP: 00007ffe57a7d9e8 R08: 000055eae80109f0 R09: 000000007fffffff
[255894.805338] R10: 00007f6a8f3d9f20 R11: e3f53316e5ab21bb R12: 00007ffe57a8432a
[255894.806034] R13: 0000000000000001 R14: 000055eae80109f0 R15: 000055eae7ffb2d0
[255894.806707] FS: 00007f6a8f299740 GS: 0000000000000000
Platform
user@user-pc:~/libtiff/libtiff$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
user@user-pc:~/libtiff/libtiff$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
Reproducer file
Any valid TIFF would work: empty.tif
In-Depth Details
Tiffcrop's main() function tries to close the output file at the end: Line 2923
The null-dereference happens inside the TIFFClose()
functions:
void TIFFClose(TIFF *tif)
{
TIFFCloseProc closeproc = tif->tif_closeproc; ----------------> Null-dereference
thandle_t fd = tif->tif_clientdata;
TIFFCleanup(tif);
(void)(*closeproc)(fd);
}
We can see that it dereferences a TIFF struct, to get the close function pointer:
struct tiff
{
char *tif_name; /* name of open file */
int tif_fd; /* open file descriptor */
int tif_mode; /* open mode (O_*) */
uint32_t tif_flags;
....................
TIFFCloseProc tif_closeproc; /* close method */ -----------> on master +0x488, on v4.5.0 +0x498
....................
};
TIFFClose()
will then execute this pointer at the last line.
However, it is important to note that modern Linux systems do not allow the allocation of memory around the NULL
(0x0
) address.
Root-Cause Analysis
The root cause of this is this code inside tiffcrop's main():
................................
/* Format and write selected image parts to output file(s). */
if (page.mode == PAGE_MODE_NONE)
{ /* Whole image or sections not based on output page size */
if (crop.selections > 0)
{
writeSelections(in, &out, &crop, &image, &dump, seg_buffs,
mp, argv[argc - 1], &next_page,
total_pages);
................................
writeSelections()
is responsible to open the output file:
case FILE_PER_SELECTION:
autoindex = 1;
page_count = 1;
for (i = 0; i < crop->selections; i++)
{
if (update_output_file(out, mp, autoindex, filename, page))
return (1);
.............
}
But then, update_output_file()
will fail when trying to open one:
................................
if (*tiffout == NULL)
{
TIFFError("update_output_file", "Unable to open output file %s",
exportname);
return 1;
}
................................
Proposed Patches
For libtiff/tiffcrop.c
: tiffcrop.patch
For libtiff/tif_close.c
: tif_close.patch
Notes
We can create a CVE entry after the fixed version is released since we are a CNA (JFrog Ltd).