tool_doswin: increase allowable length of path sanitizer

- Use 32767-1 instead of PATH_MAX-1 (260-1) as the maximum allowable
  length of a path in Windows.

Prior to this change the path sanitizer in Windows used 32767-1 as the
maximum length only for paths that had the "\\" prefix like
"\\?\longpath". Since then we added some workarounds to open longer
paths without "\\?\" prefix by normalizing the path and adding that
prefix, and the sanitizer is called before the prefix is added.

Bug: https://github.com/curl/curl/issues/20044
Reported-by: Viktor Szakats

Closes https://github.com/curl/curl/pull/20046
This commit is contained in:
Jay Satiro 2025-12-20 04:08:09 -05:00
parent 52ac8104e1
commit 1068d048ec
2 changed files with 40 additions and 32 deletions

View file

@ -88,8 +88,11 @@ SANITIZE_ALLOW_PATH: Allow path separators and colons.
Without this flag path separators and colons are sanitized.
SANITIZE_ALLOW_RESERVED: Allow reserved device names.
Without this flag a reserved device name is renamed (COM1 => _COM1) unless it
is in a UNC prefixed path.
Without this flag a reserved device name is renamed (COM1 => _COM1).
To fully block reserved device names requires not passing either flag. Some
less common path styles are allowed to use reserved device names. For example,
a "\\" prefixed path may use reserved device names if paths are allowed.
Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.
Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.
@ -110,15 +113,13 @@ SANITIZEcode sanitize_file_name(char ** const sanitized, const char *file_name,
if(!file_name)
return SANITIZE_ERR_BAD_ARGUMENT;
if(flags & SANITIZE_ALLOW_PATH) {
#ifndef MSDOS
if(file_name[0] == '\\' && file_name[1] == '\\')
/* UNC prefixed path \\ (eg \\?\C:\foo) */
max_sanitized_len = 32767 - 1;
else
if(flags & SANITIZE_ALLOW_PATH)
#ifdef MSDOS
max_sanitized_len = PATH_MAX - 1;
#else
/* Windows extended-length path max */
max_sanitized_len = 32767 - 1;
#endif
max_sanitized_len = PATH_MAX - 1;
}
else
/* The maximum length of a filename. FILENAME_MAX is often the same as
PATH_MAX, in other words it is 260 and does not discount the path
@ -135,7 +136,7 @@ SANITIZEcode sanitize_file_name(char ** const sanitized, const char *file_name,
#ifndef MSDOS
if((flags & SANITIZE_ALLOW_PATH) && !strncmp(target, "\\\\?\\", 4))
/* Skip the literal path prefix \\?\ */
/* Skip the literal-path prefix \\?\ */
p = target + 4;
else
#endif
@ -438,20 +439,18 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized,
/* We could have a file whose name is a device on MS-DOS. Trying to
* retrieve such a file would fail at best and wedge us at worst. We need
* to rename such files. */
char *p, *base;
char fname[PATH_MAX];
char *p, *base, *buffer;
#ifdef MSDOS
struct_stat st_buf;
#endif
size_t len;
size_t len, bufsize;
if(!sanitized || !file_name)
return SANITIZE_ERR_BAD_ARGUMENT;
*sanitized = NULL;
len = strlen(file_name);
/* Ignore UNC prefixed paths, they are allowed to contain a reserved name. */
/* Ignore "\\" prefixed paths, they are allowed to use reserved names. */
#ifndef MSDOS
if((flags & SANITIZE_ALLOW_PATH) &&
file_name[0] == '\\' && file_name[1] == '\\') {
@ -462,19 +461,25 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized,
}
#endif
if(len > PATH_MAX - 1)
return SANITIZE_ERR_INVALID_PATH;
/* The buffer contains two extra bytes to allow for path expansion that
occurs if reserved name(s) need an underscore prepended. */
len = strlen(file_name);
bufsize = len + 2 + 1;
memcpy(fname, file_name, len);
fname[len] = '\0';
base = basename(fname);
buffer = curlx_malloc(bufsize);
if(!buffer)
return SANITIZE_ERR_OUT_OF_MEMORY;
memcpy(buffer, file_name, len + 1);
base = basename(buffer);
/* Rename reserved device names that are known to be accessible without \\.\
Examples: CON => _CON, CON.EXT => CON_EXT, CON:ADS => CON_ADS
https://web.archive.org/web/20160314141551/support.microsoft.com/en-us/kb/74496
https://learn.microsoft.com/windows/win32/fileio/naming-a-file
*/
for(p = fname; p; p = (p == fname && fname != base ? base : NULL)) {
for(p = buffer; p; p = (p == buffer && buffer != base ? base : NULL)) {
size_t p_len;
int x = (curl_strnequal(p, "CON", 3) ||
curl_strnequal(p, "PRN", 3) ||
@ -511,15 +516,18 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized,
p_len = strlen(p);
/* Prepend a '_' */
if(strlen(fname) == PATH_MAX - 1)
if(len == bufsize - 1) {
curlx_free(buffer);
return SANITIZE_ERR_INVALID_PATH;
}
memmove(p + 1, p, p_len + 1);
p[0] = '_';
++p_len;
++len;
/* if fname was just modified then the basename pointer must be updated */
if(p == fname)
base = basename(fname);
/* the basename pointer must be updated since the path has expanded */
if(p == buffer)
base = basename(buffer);
}
/* This is the legacy portion from rename_if_dos_device_name that checks for
@ -534,16 +542,19 @@ static SANITIZEcode rename_if_reserved_dos(char ** const sanitized,
/* Prepend a '_' */
size_t blen = strlen(base);
if(blen) {
if(strlen(fname) >= PATH_MAX - 1)
if(len == bufsize - 1) {
curlx_free(buffer);
return SANITIZE_ERR_INVALID_PATH;
}
memmove(base + 1, base, blen + 1);
base[0] = '_';
++len;
}
}
#endif
*sanitized = curlx_strdup(fname);
return *sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY;
*sanitized = buffer;
return SANITIZE_ERR_OK;
}
#ifdef __DJGPP__

View file

@ -178,9 +178,6 @@ static CURLcode test_tool1604(const char *arg)
{ "COM56", 0,
"COM56", SANITIZE_ERR_OK
},
/* At the moment we expect a maximum path length of 259. I assume MS-DOS
has variable max path lengths depending on compiler that are shorter
so currently these "good" truncate tests will not run on MS-DOS */
{ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"