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