From 13b6a6036cacef0523e1968a080403437d413de1 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Sun, 26 Apr 2026 13:38:47 +0200 Subject: [PATCH] tool_dirhie: fix to create drive-relative directory Fix to create the top directory `foo` when specified as `X:foo\bar\filename`, on Windows and MS-DOS. Add test to verify. Caught by Codex Security Follow-up to 787ee935acd5867bdac836b2043b6095eed2c29e #16566 Closes #21449 --- src/tool_dirhie.c | 29 ++++++++++++--- src/tool_dirhie.h | 4 ++ tests/data/Makefile.am | 1 + tests/data/test1720 | 24 ++++++++++++ tests/tunit/Makefile.inc | 3 +- tests/tunit/tool1720.c | 79 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 tests/data/test1720 create mode 100644 tests/tunit/tool1720.c diff --git a/src/tool_dirhie.c b/src/tool_dirhie.c index 894c638034..82377a718e 100644 --- a/src/tool_dirhie.c +++ b/src/tool_dirhie.c @@ -26,7 +26,9 @@ #include "tool_dirhie.h" #include "tool_msgs.h" -#ifdef _WIN32 +#ifdef UNITTESTS +# define toolx_mkdir(x, y) create_dir_hierarchy_trace_mkdir(x) +#elif defined(_WIN32) # include # define toolx_mkdir(x, y) _mkdir(x) #elif defined(MSDOS) && !defined(__DJGPP__) @@ -35,6 +37,22 @@ # define toolx_mkdir mkdir #endif +#ifdef UNITTESTS +static struct dynbuf mkdir_results; + +UNITTEST struct dynbuf *create_dir_hierarchy_trace_dynres(void) +{ + return &mkdir_results; +} + +static int create_dir_hierarchy_trace_mkdir(const char *dir) +{ + return !dir || + curlx_dyn_add(&mkdir_results, dir) || + curlx_dyn_add(&mkdir_results, "|") ? -1 : 0; +} +#endif + static void show_dir_errno(const char *name) { switch(errno) { @@ -105,12 +123,11 @@ CURLcode create_dir_hierarchy(const char *outfile) #if defined(_WIN32) || defined(MSDOS) if(!curlx_dyn_len(&dirbuf)) { - /* Skip creating a drive's current directory. It may seem as though that - would harmlessly fail but it could be a corner case if X: did not - exist, since we would be creating it erroneously. eg if outfile is - X:\foo\bar\filename then do not mkdir X: This logic takes into + /* Skip creating a standalone Windows/MS-DOS drive letter 'X:', e.g. + if outfile is X:\foo\bar\filename. Do create drive-relative + directories e.g. in outfile X:foo\bar\filename. This logic takes into account unsupported drives !:, 1:, etc. */ - if(len > 1 && (outfile[1] == ':')) + if(len == 2 && (outfile[1] == ':')) skip = TRUE; } #endif diff --git a/src/tool_dirhie.h b/src/tool_dirhie.h index d48615a0c0..60b9470a57 100644 --- a/src/tool_dirhie.h +++ b/src/tool_dirhie.h @@ -25,6 +25,10 @@ ***************************************************************************/ #include "tool_setup.h" +#ifdef UNITTESTS +UNITTEST struct dynbuf *create_dir_hierarchy_trace_dynres(void); +#endif + CURLcode create_dir_hierarchy(const char *outfile); #endif /* HEADER_CURL_TOOL_DIRHIE_H */ diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 00d3e4166e..5a517df9f3 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -230,6 +230,7 @@ test1680 test1681 test1682 test1683 test1684 test1685 \ \ test1700 test1701 test1702 test1703 test1704 test1705 test1706 test1707 \ test1708 test1709 test1710 test1711 test1712 test1713 test1714 test1715 \ +test1720 \ \ test1800 test1801 test1802 test1847 test1848 test1849 test1850 test1851 \ \ diff --git a/tests/data/test1720 b/tests/data/test1720 new file mode 100644 index 0000000000..22edb19baf --- /dev/null +++ b/tests/data/test1720 @@ -0,0 +1,24 @@ + + + + +unittest + + + +# Client-side + + +unittest + + +create_dir_hierarchy() + + +tool%TESTNUMBER + + + + + + diff --git a/tests/tunit/Makefile.inc b/tests/tunit/Makefile.inc index 1f3c8cb9b7..27a6163173 100644 --- a/tests/tunit/Makefile.inc +++ b/tests/tunit/Makefile.inc @@ -34,4 +34,5 @@ TESTS_C = \ tool1604.c \ tool1621.c \ tool1622.c \ - tool1623.c + tool1623.c \ + tool1720.c diff --git a/tests/tunit/tool1720.c b/tests/tunit/tool1720.c new file mode 100644 index 0000000000..80e3c157ba --- /dev/null +++ b/tests/tunit/tool1720.c @@ -0,0 +1,79 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "unitcheck.h" +#include "tool_dirhie.h" + +static CURLcode test_tool1720(const char *arg) +{ + UNITTEST_BEGIN_SIMPLE + + static const char *check[] = { + "", + "(null)", + "filename", + "(null)", + "foo/bar/", + "foo|foo/bar|", + "foo/bar/filename", + "foo|foo/bar|", + "/foo/bar/filename", + "/foo|/foo/bar|", +#if defined(_WIN32) || defined(MSDOS) + "C:/foo/bar/filename", + "C:/foo|C:/foo/bar|", + "C:foo/bar/filename", + "C:foo|C:foo/bar|", + "foo\\bar\\filename", + "foo|foo\\bar|", + "\\foo\\bar\\filename", + "\\foo|\\foo\\bar|", + "C:\\foo\\bar\\filename", + "C:\\foo|C:\\foo\\bar|", + "C:foo\\bar\\filename", + "C:foo|C:foo\\bar|", +#endif + }; + + size_t i; + struct dynbuf *res = create_dir_hierarchy_trace_dynres(); + + curlx_dyn_init(res, 256); + + for(i = 0; i < CURL_ARRAYSIZE(check); i += 2) { + const char *actual; + curlx_dyn_reset(res); + create_dir_hierarchy(check[i]); + actual = curlx_dyn_ptr(res); + if(!actual) + actual = "(null)"; + if(strcmp(check[i + 1], actual)) { + curl_mprintf("Expected '%s' got '%s'\n", check[i + 1], actual); + unitfail++; + } + } + + curlx_dyn_free(res); + + UNITTEST_END_SIMPLE +}