Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freeze Unfreeze inside of EnableHookLL/EnableAllHooksLL causes crash #132

Open
mallgrab opened this issue Jul 29, 2024 · 10 comments
Open

Freeze Unfreeze inside of EnableHookLL/EnableAllHooksLL causes crash #132

mallgrab opened this issue Jul 29, 2024 · 10 comments

Comments

@mallgrab
Copy link

Certain multithreaded games have tendencies to crash if you were to create and destroy hooks.
It seems like minhook does not handle the case where SuspendThread returns "Access violation", which leads into a crash when ResumeThread then interacts with the thread.

The bug is similar to this issue from SafetyHook
cursey/safetyhook#52 (comment)

Commenting out SuspendThread and ResumeThread fixes the crashes but I don't know how well that will work when ProcessThreadIPs interacts with the context of the thread, where it expects the thread to be sleeping.

@FransBouma
Copy link
Contributor

Thanks for the heads up! I had crashes appear when I started hooking a multithreaded piece of code and couldn't figure out why.

I've altered Freeze/Unfreeze to anticipate on whether a thread could be suspended. This isn't 100% failsafe but I think nothing really is.

//-------------------------------------------------------------------------
static VOID Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action)
{
    pThreads->pItems   = NULL;
    pThreads->capacity = 0;
    pThreads->size     = 0;
    EnumerateThreads(pThreads);

    if (pThreads->pItems != NULL)
    {
        for (UINT i = 0; i < pThreads->size; ++i)
        {
            HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]);
            if (hThread != NULL)
            {
                DWORD result = SuspendThread(hThread);
                if(result == 0xFFFFFFFF)
                {
                    // mark thread as not suspended, so it's not resumed later on.
                    pThreads->pItems[i] = 0;
                }
                else
                {
                    ProcessThreadIPs(hThread, pos, action);
                }
                CloseHandle(hThread);
            }
        }
    }
}

//-------------------------------------------------------------------------
static VOID Unfreeze(PFROZEN_THREADS pThreads)
{
    if (pThreads->pItems != NULL)
    {
        for (UINT i = 0; i < pThreads->size; ++i)
        {
            DWORD threadId = pThreads->pItems[i];
            if(threadId > 0)
            {
                HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, threadId);
                if(hThread != NULL)
                {
                    ResumeThread(hThread);
                    CloseHandle(hThread);
                }
            }
        }

        HeapFree(g_hHeap, 0, pThreads->pItems);
    }
}

So it basically comes down to: if SuspendThread reports an error, mark the thread as not suspended so unfreeze will skip it for the resume loop.

@FransBouma
Copy link
Contributor

@m417z is this project still active, as in: would you consider a PR for this change? It's not a failsafe fix but it mitigates a 100% guaranteed crash

@m417z
Copy link
Collaborator

m417z commented Aug 5, 2024

Yes, looks great, go ahead, I'll merge it.

@FransBouma
Copy link
Contributor

Right after you posted this, I ran into a crash again, so this isn't sufficient either. :( It mitigates it, but it's not a solution to the crashes one will run into. I'll mention this with the PR, so you can close it if you think it gives the wrong impression that it is fixed

@m417z
Copy link
Collaborator

m417z commented Aug 5, 2024

Too bad, but I think that a check for whether SuspendThread succeeds is an improvement in any case.

@FransBouma
Copy link
Contributor

FransBouma commented Aug 5, 2024

Too bad, but I think that a check for whether SuspendThread succeeds is an improvement in any case.

yeah it definitely mitigates it a lot. Will send you a PR tomorrow.

Another area of improvement perhaps is the way EnableHookLL writes the code bytes: after this line: https://github.com/TsudaKageyu/minhook/blob/master/src/hook.c#L403 there's a race condition. A CPU can write 8 bytes atomically, so it might be better to write this out as a 64bit value in 1 go (e.g. using WriteProcessMemory or memcpy). If a thread isn't frozen it can still go wrong if the E8 byte has been written but not the operand. Of course, if all threads are frozen there's no race condition, but as there might be threads still running, that's not a given

@m417z
Copy link
Collaborator

m417z commented Aug 5, 2024

Do you have a crash dump? Does the crash happen at the patched instructions?

A CPU can write 8 bytes atomically

Yes, but only if it's an aligned write. Also, it won't help if the CPU is executing one of the patched instructions, regardless of whether the write is atomic.

@FransBouma
Copy link
Contributor

Do you have a crash dump? Does the crash happen at the patched instructions?

I sadly don't have a crash dump, and it only happened once yesterday in maybe 20-30 starts of my test game, so it's very hard to track down. (Unreal Engine 4.27 compiled small level with a couple of characters which starts in 2 seconds).

A CPU can write 8 bytes atomically

Yes, but only if it's an aligned write. Also, it won't help if the CPU is executing one of the patched instructions, regardless of whether the write is atomic.

That's a very good point, didn't know that! So there's no need to pursue that then :) 👍

FransBouma added a commit to FransBouma/minhook that referenced this issue Aug 6, 2024
Changes to mitigate the issue where SuspendThread() returns -1 (error occurred) which would then crash the host because the ResumeThread() call in Unfreeze would crash.
@m417z
Copy link
Collaborator

m417z commented Aug 8, 2024

it's very hard to track down

You can enable crash dumps, and then just grab a crash the next time it happens.

  • Open regedit
  • Go to: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting
  • Create a key named LocalDumps
  • Create a DWORD value named DumpType with value 2
  • Wait for the crash
  • Go to the %LocalAppData%\CrashDumps folder, you should see a dump file in there

@FransBouma
Copy link
Contributor

it's very hard to track down

You can enable crash dumps, and then just grab a crash the next time it happens.

* Open regedit

* Go to: `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting`

* Create a key named `LocalDumps`

* Create a DWORD value named `DumpType` with value `2`

* Wait for the crash

* Go to the `%LocalAppData%\CrashDumps` folder, you should see a dump file in there

Thanks will try that. The UE game I am using for testing does dump its own crashdump at times but these crashes apparently don't make it that far, the dump isn't written.

I've added a Sleep(150) after the Freeze in EnableHookLL now and I have had 0 crashes even after 20+ starts, but I'll add the key just in case next time it crashes :) 👍

m417z added a commit that referenced this issue Aug 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants