Free what is allocated, restore what is saved - #210
Draft
doomedraven wants to merge 1 commit into
Draft
doomedraven wants to merge 1 commit into
doomedraven wants to merge 1 commit into
Conversation
Resource leaks on hot and error paths, a free() of a stack address, two
log handles that stay poisoned after a failed open, and four places
where the last-error save/restore discipline is broken.
CAPE/CAPE.c MapFile - free() of the caller's stack slot
Buffer is an unsigned char **. Three error paths call free(Buffer),
which hands the CRT the address of the caller's stack variable rather
than the allocation. Heap corruption or an immediate CRT abort,
triggered by any short or failed ReadFile. Now free(*Buffer) with
*Buffer = NULL.
CAPE/CAPE.c GetName - two leaks on early returns
FullPathName leaks when the calloc fails; both FullPathName and
OutputFilename leak when rand() returns 0.
CAPE/Output.c DebuggerOutput - the largest leak in the tree
GetResultsPath (calloc plus a CreateDirectory syscall) and a
calloc(MAX_PATH) ran on every call, and FullPathName was never freed.
This function fires one to four times per single-stepped instruction,
so a one-million-step trace performed about a million CreateDirectory
syscalls and leaked roughly 260 MB.
The path is only needed while opening the log, so it now lives inside
the `if (!DebuggerLog)` block and is freed there. A failed CreateFile
also used to leave DebuggerLog set to INVALID_HANDLE_VALUE, so every
later call issued a WriteFile against it forever; it is now reset to
NULL so the open is retried. The two error returns were also missing
va_end.
CAPE/Output.c StringsOutput - same shape
StringsFile is a global that DumpStrings reads later, so it has to
persist, but it was reallocated and the previous value leaked on every
call. Now built once, with the old value freed on replacement, plus
the same INVALID_HANDLE_VALUE and va_end fixes.
hook_tls.c LogTls / LogTls13
The tlsdump path was rebuilt and leaked for every secret captured.
log.c / pipe.c
GetResultsPath results never freed. The pipe.c one is per pipe() call
in standalone mode.
These four also gain the NULL check that GetResultsPath has always
needed: it returns 0 on allocation failure, on a path longer than
MAX_PATH, and on a CreateDirectory error, and every caller passed the
result straight to PathAppend.
Last-error discipline
- hook_sleep.c NtDelayExecution: two early returns after
get_lasterrors with no matching set_lasterrors.
- hook_misc.c SetupDiGetClassDevsA/W: the restore sits inside
`if (ClassGuid)`, so a NULL ClassGuid skips it.
- hook_misc.c SetupDiGetClassDevsW additionally captures the error
before calling the original, so the restore overwrites whatever the
API set.
- hook_misc.c WNetGetProviderNameW: the anti-VM path fills in a
lasterror_t and never applies it, so the faked ERROR_NO_NETWORK
return was contradicted by GetLastError(). Applied after the free(),
which can clobber the last error itself.
Behaviour changes: DebuggerOutput and StringsOutput retry the log file
open after a failure instead of writing to INVALID_HANDLE_VALUE forever,
and GetLastError() after WNetGetProviderNameW now agrees with the
returned ERROR_NO_NETWORK.
TAG=agy
CONV=b3280e17-abe0-4fed-ad0b-c2e7f65da90f
This was referenced Sep 15, 2026
Contributor
Author
|
Correction to the CI note in the description above: the diagnosis there was wrong. The MSBuild workflow is On top of that it targets the retired #212 fixes the workflow file for all three. Re-enabling the workflow in Settings → Actions is the part that has to be done by hand. |
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Fifth PR in the review series. Resource leaks, one wrong
free(), two poisoned log handles, and theget_lasterrors/set_lasterrorsdiscipline.Back to no intended change in behaviour-log contents — #209 was the one that changes output. Three small behaviour changes are listed at the bottom.
Are these related?
Independent of each other. Two files share a shape (
DebuggerOutputandStringsOutputare the same function twice) but nothing here is coupled.The coupled case is still the
NDEBUGPR near the end of the series: you cannot defineNDEBUGwithout simultaneously converting theVirtualProtectinsideassert()atalloc.c:85and thepre_trampbound check athooking_32.c:267,378/hooking_64.c:542,826into real runtime checks.The fixes
CAPE/CAPE.cMapFile—free()of a stack addressThree error paths do this.
Bufferis the address of the caller's stack variable, not the allocation. Passing it to the CRT allocator is heap corruption at best and an immediate abort at worst. It fires on any failed or shortReadFile.→
free(*Buffer); *Buffer = NULL;CAPE/Output.cDebuggerOutput— the largest leak in the treeAll of that ran on every call, and
DebuggerOutputfires one to four times per single-stepped instruction. A one-million-step trace does roughly a millionCreateDirectorysyscalls and leaks about 260 MB.The path is only needed while opening the log, so it now lives inside the
if (!DebuggerLog)block and is freed there.Two more problems in the same function:
CreateFileleavesDebuggerLog == INVALID_HANDLE_VALUE. Every later call then doesWriteFile(INVALID_HANDLE_VALUE, ...)forever, and the open is never retried. Reset toNULL.va_end.StringsOutputis the same function with different names, plus one extra wrinkle:StringsFileis a global thatDumpStrings(CAPE.c:1833) reads later, so it has to persist — but it was reallocated and the previous value leaked on every call. Now built once, with the old value freed on replacement.hook_tls.c,log.c,pipe.c—GetResultsPathresults never freedLogTlsandLogTls13rebuilt and leaked the tlsdump path for every captured secret.log.c:1470leaks once at init.pipe.c:193leaks perpipe()call in standalone mode — 260 bytes each.While in there, all four gain the NULL check
GetResultsPathhas always needed. It returns0on allocation failure, on a path longer thanMAX_PATH, and on aCreateDirectoryerror, and every caller handed the result straight toPathAppend.Last-error discipline
hook_sleep.cNtDelayExecutionget_lasterrorswith no matchingset_lasterrorshook_misc.cSetupDiGetClassDevsAif (ClassGuid), so a NULLClassGuidskips ithook_misc.cSetupDiGetClassDevsWhook_misc.cWNetGetProviderNameWlasterror_tand never applies itThe
WNetGetProviderNameWone is the visible bug: the handler fakes anERROR_NO_NETWORKreturn to hide VirtualBox's network redirector, butGetLastError()still reported whatever the real call set. A sample that checks both sees the inconsistency. Theset_lasterrorsis placed after thefree(), sinceHeapFreecan clobber the last error itself.Behaviour changes
DebuggerOutputandStringsOutputretry the log-file open after a failure instead of writing toINVALID_HANDLE_VALUEforever.GetLastError()afterWNetGetProviderNameWnow agrees with the returnedERROR_NO_NETWORK.GetResultsPathcallers now bail out instead of callingPathAppend(NULL, ...)when the results directory cannot be created.Deliberately not in this PR
P2unchecked-allocation cluster (~25 sites acrosshook_file.c,hook_reg.c,hook_reg_native.c,hook_services.c,log.c). Those are all themalloc(32768 * sizeof(wchar_t))path buffers, and the next PR replaces them with a per-thread scratch buffer — adding NULL checks now would be churn that PR then deletes, plus a guaranteed conflict on every one of those lines.hooking_32.c:844-855—hookdataleaked on theHOOK_SAFESTretry and on bothrestore_protectpaths. That one also leaves a stale pointer that poisonsinside_hook, so it belongs with the hooking-engine changes.Not verified by build
No MSVC here. CI has not run on #206–#209 —
gh pr checksreports no checks on any branch. Either fork PRs need your approval before workflows run, or.github/workflows/msbuild.yml'sruns-on: windows-2019no longer schedules since GitHub retired that image. Tell me which and I will send a prerequisite PR bumping the runner and theactions/checkout@v3/upload-artifact@v3steps.Series
P2cluster)inside_hookranges,lookupbuckets, single backtrace walk)NDEBUG+ assert conversion +/O2 /Oy-, the coupled one)hook_apilock, arena free list, breakpoint lists)