From: lily11111222 <2564065295@qq.com> search_path() copies normalized paths, path components, executable names, and PATHEXT entries into fixed-size buffers without consistently checking their lengths. A long URL can therefore overflow the buffers and cause an access violation before it reaches ShellExecuteExW. Validate each string before copying or concatenating it, and handle paths without a backslash. If an argument cannot be represented by the path search buffers, leave it unchanged for ShellExecuteExW to handle. Add a cmd.exe test which registers a temporary protocol and verifies that start can launch a URL longer than MAX_PATH. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=57229 Signed-off-by: lily11111222 <2564065295@qq.com> --- programs/cmd/tests/test_builtins.bat | 21 +++++++++++++++++ programs/cmd/tests/test_builtins.bat.exp | 1 + programs/cmd/tests/test_builtins.cmd | 21 +++++++++++++++++ programs/cmd/tests/test_builtins.cmd.exp | 1 + programs/start/start.c | 30 +++++++++++++++++++----- 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/programs/cmd/tests/test_builtins.bat b/programs/cmd/tests/test_builtins.bat index 7711aa917e3..995c1fa4042 100644 --- a/programs/cmd/tests/test_builtins.bat +++ b/programs/cmd/tests/test_builtins.bat @@ -97,6 +97,27 @@ set "FOO_PATH=%cd%" > NUL cd .. call :setError 666 & (start /B /WAIT /d "%FOO_PATH%" cmd /s /c "if /I \"%%cd%%\"==\"%FOO_PATH%\" (exit 0) else (exit 1)" >nul &&echo !errorlevel!) rd /q /s foo + +rem Ensure START handles a protocol URL longer than MAX_PATH. +set "WINE_START_PROTOCOL=wineteststart%random%%random%" +set "WINE_START_URL=%WINE_START_PROTOCOL%://callback/" +for /l %%i in (1,1,512) do set "WINE_START_URL=!WINE_START_URL!a" +set "WINE_START_RESULT=%cd%\start_result.txt" +del /q "%WINE_START_RESULT%" >nul 2>&1 +reg add "HKCU\Software\Classes\%WINE_START_PROTOCOL%" /ve /d "URL:%WINE_START_PROTOCOL%" /f >nul +reg add "HKCU\Software\Classes\%WINE_START_PROTOCOL%" /v "URL Protocol" /d "" /f >nul +reg add "HKCU\Software\Classes\%WINE_START_PROTOCOL%\shell\open\command" /ve ^ + /d "%comspec% /d /c type nul > %WINE_START_RESULT%" /f >nul +reg add "HKCR\%WINE_START_PROTOCOL%" /ve /d "URL:%WINE_START_PROTOCOL%" /f >nul 2>&1 +reg add "HKCR\%WINE_START_PROTOCOL%" /v "URL Protocol" /d "" /f >nul 2>&1 +reg add "HKCR\%WINE_START_PROTOCOL%\shell\open\command" /ve ^ + /d "%comspec% /d /c type nul > %WINE_START_RESULT%" /f >nul 2>&1 +start "" /b /wait !WINE_START_URL! >nul 2>&1 +if exist "%WINE_START_RESULT%" (echo long protocol URL handled) else echo protocol handler was not invoked +reg delete "HKCR\%WINE_START_PROTOCOL%" /f >nul 2>&1 +reg delete "HKCU\Software\Classes\%WINE_START_PROTOCOL%" /f >nul 2>&1 +del /q "%WINE_START_RESULT%" >nul 2>&1 + echo --- success/failure for TYPE command mkdir foo & cd foo mkdir bar diff --git a/programs/cmd/tests/test_builtins.bat.exp b/programs/cmd/tests/test_builtins.bat.exp index 77ca71c8778..002bc6b3ed7 100644 --- a/programs/cmd/tests/test_builtins.bat.exp +++ b/programs/cmd/tests/test_builtins.bat.exp @@ -63,6 +63,7 @@ foo@space@ SUCCESS 1024 @todo_wine@SUCCESS 666 0 +long protocol URL handled --- success/failure for TYPE command FAILURE 1 SUCCESS 0 diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index 34d1f076485..344368a0896 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -680,6 +680,27 @@ set "FOO_PATH=%cd%" > NUL cd .. call :setError 666 & (start /B /WAIT /d "%FOO_PATH%" cmd /s /c "if /I \"%%cd%%\"==\"%FOO_PATH%\" (exit 0) else (exit 1)" >nul &&echo !errorlevel!) rd /q /s foo + +rem Ensure START handles a protocol URL longer than MAX_PATH. +set "WINE_START_PROTOCOL=wineteststart%random%%random%" +set "WINE_START_URL=%WINE_START_PROTOCOL%://callback/" +for /l %%i in (1,1,512) do set "WINE_START_URL=!WINE_START_URL!a" +set "WINE_START_RESULT=%cd%\start_result.txt" +del /q "%WINE_START_RESULT%" >nul 2>&1 +reg add "HKCU\Software\Classes\%WINE_START_PROTOCOL%" /ve /d "URL:%WINE_START_PROTOCOL%" /f >nul +reg add "HKCU\Software\Classes\%WINE_START_PROTOCOL%" /v "URL Protocol" /d "" /f >nul +reg add "HKCU\Software\Classes\%WINE_START_PROTOCOL%\shell\open\command" /ve ^ + /d "%comspec% /d /c type nul > %WINE_START_RESULT%" /f >nul +reg add "HKCR\%WINE_START_PROTOCOL%" /ve /d "URL:%WINE_START_PROTOCOL%" /f >nul 2>&1 +reg add "HKCR\%WINE_START_PROTOCOL%" /v "URL Protocol" /d "" /f >nul 2>&1 +reg add "HKCR\%WINE_START_PROTOCOL%\shell\open\command" /ve ^ + /d "%comspec% /d /c type nul > %WINE_START_RESULT%" /f >nul 2>&1 +start "" /b /wait !WINE_START_URL! >nul 2>&1 +if exist "%WINE_START_RESULT%" (echo long protocol URL handled) else echo protocol handler was not invoked +reg delete "HKCR\%WINE_START_PROTOCOL%" /f >nul 2>&1 +reg delete "HKCU\Software\Classes\%WINE_START_PROTOCOL%" /f >nul 2>&1 +del /q "%WINE_START_RESULT%" >nul 2>&1 + echo --- success/failure for TYPE command mkdir foo & cd foo mkdir bar diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index dc6420e1432..274495a4cb7 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -582,6 +582,7 @@ foo@space@ SUCCESS 1024 @todo_wine@SUCCESS 666 0 +long protocol URL handled --- success/failure for TYPE command FAILURE 1 SUCCESS 0 diff --git a/programs/start/start.c b/programs/start/start.c index 38aafe870a9..2940f44640d 100644 --- a/programs/start/start.c +++ b/programs/start/start.c @@ -290,19 +290,21 @@ static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path) lstrcpyW (pathtosearch, L"."); } if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE; - if (lstrlenW(firstParam) >= MAX_PATH) { + if (lstrlenW(firstParam) >= ARRAY_SIZE(stemofsearch)) return FALSE; - } - lstrcpyW(stemofsearch, firstParam); } else { /* Convert eg. ..\fred to include a directory by removing file part */ - GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL); + len = GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL); + if (!len || len >= ARRAY_SIZE(pathtosearch)) + return FALSE; lastSlash = wcsrchr(pathtosearch, '\\'); if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE; - lstrcpyW(stemofsearch, lastSlash+1); + if (lstrlenW(lastSlash ? lastSlash + 1 : firstParam) >= ARRAY_SIZE(stemofsearch)) + return FALSE; + lstrcpyW(stemofsearch, lastSlash ? lastSlash + 1 : firstParam); /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and c:\windows\a.bat syntax */ @@ -336,10 +338,14 @@ static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path) } if (*pos) { /* Reached semicolon */ + if (pos - pathposn >= ARRAY_SIZE(thisDir)) + return FALSE; memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR)); thisDir[(pos-pathposn)] = 0x00; pathposn = pos+1; } else { /* Reached string end */ + if (lstrlenW(pathposn) >= ARRAY_SIZE(thisDir)) + return FALSE; lstrcpyW(thisDir, pathposn); pathposn = NULL; } @@ -356,7 +362,12 @@ static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path) /* Since you can have eg. ..\.. on the path, need to expand to full information */ - GetFullPathNameW(temp, MAX_PATH, thisDir, NULL); + len = GetFullPathNameW(temp, ARRAY_SIZE(thisDir), thisDir, NULL); + if (!len || len >= ARRAY_SIZE(thisDir)) + return FALSE; + + if (lstrlenW(thisDir) + 1 + lstrlenW(stemofsearch) + 2 >= ARRAY_SIZE(thisDir)) + return FALSE; /* 1. If extension supplied, see if that file exists */ if (thisDir[lstrlenW(thisDir) - 1] != '\\') lstrcatW(thisDir, L"\\"); @@ -385,6 +396,13 @@ static BOOL search_path(const WCHAR *firstParam, WCHAR **full_path) /* 3. Yes - Try each path ext */ while (thisExt) { WCHAR *nextExt = wcschr(thisExt, ';'); + SIZE_T ext_len = nextExt ? nextExt - thisExt : lstrlenW(thisExt); + + if (pos + ext_len >= thisDir + ARRAY_SIZE(thisDir)) + { + thisExt = nextExt ? nextExt + 1 : NULL; + continue; + } if (nextExt) { memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR)); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11387