Skip to content

Process.cs RetrieveProcessUserName: double-free when token buffer realloc fails #28037

Description

Summary

RetrieveProcessUserName in src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs (the Windows code path behind Get-Process -IncludeUserName) has a free-then-realloc sequence for the token info buffer that leaves the local holding a stale pointer if the reallocation throws.

The problem

Current code on master (commit 84a93015104945c16b7aa69ee67a82cb1e6499dc), lines 731-744 plus the finally at 776-781:

int tokenInfoLength = 256;
tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength);
if (!Win32Native.GetTokenInformation(processTokenHandler, Win32Native.TOKEN_INFORMATION_CLASS.TokenUser, tokenUserInfo, tokenInfoLength, out tokenInfoLength))
{
    error = Marshal.GetLastWin32Error();
    if (error == Win32Native.ERROR_INSUFFICIENT_BUFFER)
    {
        Marshal.FreeHGlobal(tokenUserInfo);
        tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength);  // if this throws, tokenUserInfo still holds the freed pointer
        ...
finally
{
    if (tokenUserInfo != IntPtr.Zero)
    {
        Marshal.FreeHGlobal(tokenUserInfo);  // frees the same block a second time
    }
    ...
}

If the second Marshal.AllocHGlobal(tokenInfoLength) throws (out-of-memory is the realistic case), the generic catch (Exception) block in the same method swallows the exception and control reaches the finally, which frees tokenUserInfo again — although the pointer was already freed on the line above the failed allocation. The result is a double free and undefined heap behavior in the PowerShell process.

Same shape was fixed in PdhHelper.cs

#28008 fixed the identical free-realloc shape in src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs (LookupPerfNameByIndex) by zeroing the local immediately after the free:

Marshal.FreeHGlobal(localizedPathPtr);

// Set the value to 'IntPtr.Zero' so a reallocation failure won't cause the stale pointer to be double-freed in the finally block below.
localizedPathPtr = IntPtr.Zero;
localizedPathPtr = Marshal.AllocHGlobal(strSize * sizeof(char));

The sibling site in Process.cs was not covered by that change; it still has the unfixed shape on master and in the current release branches.

Suggested fix

Mirror the #28008 pattern — reset the local right after the free, before the reallocation:

Marshal.FreeHGlobal(tokenUserInfo);
tokenUserInfo = IntPtr.Zero;
tokenUserInfo = Marshal.AllocHGlobal(tokenInfoLength);

Notes

  • The code path is easy to reach: every elevated Get-Process -IncludeUserName call runs it, e.g. Get-Process -IncludeUserName -Id $PID | Format-List ProcessName,UserName prints ProcessName : pwsh and UserName : <DOMAIN>\<user>. The double free itself additionally requires the reallocation to throw, so it is not triggerable on demand here; the report is based on the code shape, which is the same shape Improve pointer lifecycle handling in PdhHelper #28008 addressed.
  • Scanning the other Marshal.FreeHGlobal sites in the tree, the free-realloc ones (PdhHelper.cs after Improve pointer lifecycle handling in PdhHelper #28008, WSManNativeAPI.cs, AclCommands.cs) reset the local or allocate only once; this site in Process.cs is the remaining one without the reset.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions