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 0f8ae9442a2c4ab2993069e1e270bd726c31c3b4) 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](/uploads/ba127b1fb589cc225de8fc1859105b3c/empty.tif)
_**In-Depth Details**_
Tiffcrop's main() function tries to close the output file at the end: [Line 2923](https://gitlab.com/libtiff/libtiff/-/blob/0f8ae9442a2c4ab2993069e1e270bd726c31c3b4/tools/tiffcrop.c#L2923)
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](/uploads/ec54aac0631a2e795d0dd37fdea97428/tiffcrop.patch)
For `libtiff/tif_close.c`: [tif_close.patch](/uploads/1fc42f6d29a5c7bad0b2792fa6067eed/tif_close.patch)
_**Notes**_
We can create a CVE entry after the fixed version is released since we are a CNA (JFrog Ltd).
issue