Skip to content

Commit 567d8b0

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 ac717ad commit 567d8b0

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(
@@ -510,6 +513,8 @@ void mb_shell::context_menu_hooks::install_NtUserTrackPopupMenuEx_hook() {
510513
mb_shell::context_menu_hooks::block_js_reload.fetch_sub(1);
511514
return (int32_t)selected_menu.value_or(0);
512515
});
516+
hook_registry::register_uninstaller(
517+
[]() { NtUserTrackHook->uninstall(); });
513518
}
514519

515520
void mb_shell::context_menu_hooks::install_menu_mutation_hooks() {
@@ -773,6 +778,8 @@ void mb_shell::context_menu_hooks::install_SHCreateDefaultContextMenu_hook() {
773778
}
774779
return res;
775780
});
781+
hook_registry::register_uninstaller(
782+
[]() { CreateWindowExWHook->uninstall(); });
776783

777784
/**
778785
prototype: SHSTDAPI SHCreateDefaultContextMenu(
@@ -839,6 +846,8 @@ void mb_shell::context_menu_hooks::install_SHCreateDefaultContextMenu_hook() {
839846

840847
return res;
841848
});
849+
hook_registry::register_uninstaller(
850+
[]() { SHCreateDefaultContextMenuHook->uninstall(); });
842851
}
843852

844853
#pragma optimize("", off)
@@ -962,5 +971,7 @@ HRESULT GetUIObjectOf(
962971

963972
return res;
964973
});
974+
hook_registry::register_uninstaller(
975+
[]() { GetUIObjectOfHook->uninstall(); });
965976
spdlog::info("GetUIObjectOf hook installed");
966977
}

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)