PowerRename: fix STATUS_STACK_BUFFER_OVERRUN crash with 30,000+ files#47401
PowerRename: fix STATUS_STACK_BUFFER_OVERRUN crash with 30,000+ files#47401Copilot wants to merge 3 commits into
Conversation
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/180b1774-d07f-4deb-89fa-183dd95e770d Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes a PowerRename crash (STATUS_STACK_BUFFER_OVERRUN) when launching with very large selections (30,000+ files) by hardening the file-ingestion pipeline and avoiding problematic large Shell API calls.
Changes:
- Updated stdin/pipe parsing to accumulate across
ReadFilechunk boundaries so?-delimited paths aren’t split into invalid fragments. - Fixed
CreateShellItemArrayFromPathsto only count/store valid PIDLs and to free PIDLs on both success and failure paths. - Batched Shell item creation/enumeration in
MainWindowto avoid Windows Shell stack exhaustion with huge item arrays.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp | Batches shell item enumeration; fixes PIDL counting and frees PIDLs on failure. |
| src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/App.xaml.cpp | Makes pipe parsing delimiter-safe across read chunk boundaries; discards unterminated trailing fragment. |
| else if (itemsCnt == 0) | ||
| { | ||
| Logger::error(L"Parsing path list display names failed."); | ||
| Logger::error(L"No valid file paths could be resolved; all entries either do not exist or could not be parsed by the shell."); | ||
| hr = E_FAIL; | ||
| } |
There was a problem hiding this comment.
Fixed: Added SUCCEEDED(hr) guard before the itemsCnt == 0 check, so prior allocation failures are preserved instead of being overwritten with E_FAIL.
| const DWORD BUFSIZE = 4096; | ||
| TCHAR buffer[BUFSIZE] = TEXT(""); | ||
| auto retval = GetFullPathName(file.c_str(), BUFSIZE, buffer, NULL); | ||
| if (retval != 0 && PathFileExists(buffer)) | ||
| { | ||
| PIDLIST_ABSOLUTE pidl = nullptr; | ||
| if (SUCCEEDED(SHParseDisplayName(buffer, nullptr, &pidl, 0, nullptr))) |
There was a problem hiding this comment.
Fixed: Added dynamic buffer allocation when GetFullPathName indicates the buffer is too small, so long paths are no longer truncated.
…RESULT Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot please re-review the latest changes. |
Reviewed the latest changes in
No further issues found in these changes. |
PowerRename crashes with exception
0xc000027b(STATUS_STACK_BUFFER_OVERRUN) when opening with 30,000+ selected files. Four bugs combine to cause this.Summary of the Pull Request
Four bugs fixed in the PowerRename file-loading pipeline that together cause a hard crash when 30,000+ files are selected.
PR Checklist
Detailed Description of the Pull Request / Additional comments
Bug 1 — Pipe reading splits paths across chunk boundaries (
App.xaml.cpp)ReadFilewas called in 32 KB chunks and each chunk was immediately split on?. A path straddling two reads produced two invalid fragments ing_files. Fixed with an accumulator that carries leftover bytes across reads; entries are only emitted when a complete?-delimited token is available. Trailing data without a terminator (sender crashed mid-write) is discarded with aLogger::warn.Bug 2 — Null/invalid PIDLs included in shell item array (
CreateShellItemArrayFromPaths)itemsCntwas incremented unconditionally regardless of whetherSHParseDisplayNamesucceeded, so uninitializedPIDLIST_ABSOLUTEslots could be passed toSHCreateShellItemArrayFromIDLists. Also, PIDLs were never freed on theSHCreateShellItemArrayFromIDListsfailure path (memory leak). Fixed: only store and count PIDLs onSUCCEEDED(SHParseDisplayName(...)), and free them on both success and failure paths.Bug 3 — Single
SHCreateShellItemArrayFromIDListscall with 35,000+ items (MainWindow.xaml.cpp)Passing all 35,000 PIDLs in one call triggers internal Windows Shell processing that exhausts the thread stack →
STATUS_STACK_BUFFER_OVERRUN. Fixed by processing in batches of 5,000: each batch callsCreateShellItemArrayFromPaths+EnumerateShellItemsindependently, and all items land in the sameCPowerRenameManager.Bug 4 —
GetFullPathNametruncates long paths (CreateShellItemArrayFromPaths)GetFullPathNamewas called with a fixed 4096-character stack buffer. When the required path length equals or exceedsBUFSIZE, the function returns the needed size without writing a complete path. The previous code treated any non-zero return as success, silently dropping long-path files. Fixed by re-callingGetFullPathNamewith a dynamically allocated buffer sized to the required length when the initial call indicates the buffer is too small. Additionally, theHRESULTfrom a failed allocation (E_OUTOFMEMORY) is now correctly preserved — the "no valid paths" error branch is guarded withSUCCEEDED(hr)so allocation failures are no longer silently overwritten withE_FAIL.Validation Steps Performed
itemsCntonly ever indexes validPIDLIST_ABSOLUTEpointers after the fix.totalFiles % BATCH_SIZE != 0(last partial batch) correctly viastd::min.GetFullPathNamedynamic retry correctly handles paths exceeding 4096 characters.E_OUTOFMEMORYfromnew (std::nothrow)is propagated correctly and not overwritten.