Skip to content

Commit 9e34f98

Browse files
committed
fix: uninstall all inline hooks on DLL_PROCESS_DETACH
Add a hook_registry that collects uninstall callbacks from every hook site. On DLL_PROCESS_DETACH the registry restores all patched call-sites in LIFO order before the DLL image is unmapped, preventing the 0xC0000005 crash observed in OneCommander on system shutdown.
1 parent 22eff89 commit 9e34f98

5 files changed

Lines changed: 53 additions & 0 deletions

File tree

src/shell/contextmenu/hooks.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "nanovg.h"
1111
#include "shell/config.h"
1212
#include "shell/entry.h"
13+
#include "shell/hook_registry.h"
1314
#include "shell/utils.h"
1415

1516
#include "blook/blook.h"
@@ -343,6 +344,8 @@ void sync_native_menu_item_update(HMENU hMenu, UINT item, BOOL fByPosition,
343344
BODY; \
344345
return result; \
345346
}); \
347+
hook_registry::register_uninstaller( \
348+
[]() { HOOK_NAME##Hook->uninstall(); }); \
346349
} while (false)
347350

348351
void mb_shell::context_menu_hooks::set_active_root_menu_widget(
@@ -541,6 +544,8 @@ void mb_shell::context_menu_hooks::install_NtUserTrackPopupMenuEx_hook() {
541544
mb_shell::context_menu_hooks::block_js_reload.fetch_sub(1);
542545
return (int32_t)selected_menu.value_or(0);
543546
});
547+
hook_registry::register_uninstaller(
548+
[]() { NtUserTrackHook->uninstall(); });
544549
}
545550

546551
void mb_shell::context_menu_hooks::install_menu_mutation_hooks() {
@@ -804,6 +809,8 @@ void mb_shell::context_menu_hooks::install_SHCreateDefaultContextMenu_hook() {
804809
}
805810
return res;
806811
});
812+
hook_registry::register_uninstaller(
813+
[]() { CreateWindowExWHook->uninstall(); });
807814

808815
/**
809816
prototype: SHSTDAPI SHCreateDefaultContextMenu(
@@ -870,6 +877,8 @@ void mb_shell::context_menu_hooks::install_SHCreateDefaultContextMenu_hook() {
870877

871878
return res;
872879
});
880+
hook_registry::register_uninstaller(
881+
[]() { SHCreateDefaultContextMenuHook->uninstall(); });
873882
}
874883

875884
#pragma optimize("", off)
@@ -993,5 +1002,7 @@ HRESULT GetUIObjectOf(
9931002

9941003
return res;
9951004
});
1005+
hook_registry::register_uninstaller(
1006+
[]() { GetUIObjectOfHook->uninstall(); });
9961007
spdlog::info("GetUIObjectOf hook installed");
9971008
}

src/shell/entry.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "contextmenu/hooks.h"
88
#include "entry.h"
99
#include "error_handler.h"
10+
#include "hook_registry.h"
1011
#include "res_string_loader.h"
1112
#include "script/binding_types.hpp"
1213
#include "breeze-js/quickjspp.hpp"
@@ -220,6 +221,13 @@ int APIENTRY DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved) {
220221
mb_shell::main();
221222
break;
222223
}
224+
case DLL_PROCESS_DETACH: {
225+
// Restore all inline-hooked call-sites before the DLL image is
226+
// unmapped, otherwise the patched code jumps into freed memory and
227+
// crashes (observed as 0xC0000005 in OneCommander on shutdown).
228+
mb_shell::hook_registry::uninstall_all();
229+
break;
230+
}
223231
}
224232
return 1;
225233
}

src/shell/hook_registry.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include "hook_registry.h"
2+
3+
namespace mb_shell {
4+
static std::vector<std::function<void()>> g_uninstallers;
5+
6+
void hook_registry::register_uninstaller(std::function<void()> fn) {
7+
g_uninstallers.push_back(std::move(fn));
8+
}
9+
10+
void hook_registry::uninstall_all() {
11+
// Uninstall in reverse order (LIFO) so dependent hooks are removed first.
12+
for (auto it = g_uninstallers.rbegin(); it != g_uninstallers.rend(); ++it) {
13+
(*it)();
14+
}
15+
g_uninstallers.clear();
16+
}
17+
} // namespace mb_shell

src/shell/hook_registry.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
#include <functional>
3+
#include <vector>
4+
5+
namespace mb_shell {
6+
// Collects hook-uninstall callbacks so DLL_PROCESS_DETACH can restore every
7+
// patched call-site before the DLL image is unmapped.
8+
struct hook_registry {
9+
static void register_uninstaller(std::function<void()> fn);
10+
static void uninstall_all();
11+
};
12+
} // namespace mb_shell

src/shell/res_string_loader.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <unordered_set>
99

1010
#include "config.h"
11+
#include "hook_registry.h"
1112
#include "utils.h"
1213

1314
#include "blook/blook.h"
@@ -75,6 +76,8 @@ void res_string_loader::init_hook() {
7576
}
7677
return res;
7778
});
79+
hook_registry::register_uninstaller(
80+
[]() { LoadStringWHook->uninstall(); });
7881

7982
static auto LoadStringAHook =
8083
kernelbase->exports("LoadStringA")->inline_hook();
@@ -95,6 +98,8 @@ void res_string_loader::init_hook() {
9598

9699
return res;
97100
});
101+
hook_registry::register_uninstaller(
102+
[]() { LoadStringAHook->uninstall(); });
98103
}
99104
std::string res_string_loader::string_to_id_string(std::wstring str) {
100105
auto id = string_to_id(str);

0 commit comments

Comments
 (0)