fix 5.2 audit findings on Windows: F-PROC-1 injection, F-MEM-5 UAF, F-PROC-2 restart race, F-PROC-3, F5, F6

This commit is contained in:
2026-08-16 12:29:42 +07:00
parent 54b7a7a6ac
commit 761b48e41f
9 changed files with 134 additions and 69 deletions
+57 -55
View File
@@ -73,11 +73,11 @@ static void disable_ime_contexts(HWND w) {
}
}
// G1.3: bo pump gate cu (g_juceOwnerTids / g_ownerTid /
// is_teardown_window): teardown (close/LOAD job) va message pump cung chay tren
// 1 thread (UiThread) — job chay thi pump dung, job xong thi editor window da
// destroy nen USER32 tu huy message toi window chet. Khong con "2 thread trong
// 1 DLL" khi teardown (multi-worker cu).
// G1.3: bo pump gate cu (g_juceOwnerTids / g_ownerTid /
// is_teardown_window): teardown (close/LOAD job) va message pump cung chay tren
// 1 thread (UiThread) — job chay thi pump dung, job xong thi editor window da
// destroy nen USER32 tu huy message toi window chet. Khong con "2 thread trong
// 1 DLL" khi teardown (multi-worker cu).
// CBT hook: strip the IMC the moment any JUCE_* window is born (JUCE message
// window AND editor child) — the post-attach disable_ime_contexts runs too
@@ -92,8 +92,8 @@ static LRESULT CALLBACK ImeCbtHookProc(int nCode, WPARAM wParam, LPARAM lParam)
HWND w = (HWND)wParam;
char cls[64] = {0};
if (GetClassNameA(w, cls, 63) > 0 && std::strncmp(cls, "JUCE_", 5) == 0) {
ImmAssociateContext(w, nullptr);
ImmAssociateContext(w, nullptr);
}
}
return CallNextHookEx(g_cbtHook, nCode, wParam, lParam);
@@ -108,13 +108,13 @@ static LRESULT CALLBACK ImeCbtHookProc(int nCode, WPARAM wParam, LPARAM lParam)
class ChannelWorker; // fwd — WM_DESTROY posts closeGUI() to the channel worker
static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker
static void post_resize_view(uint32_t ch, int w, int h); // defined after ChannelWorker
static InstrumentEngineManager* g_engine = nullptr;
static std::mutex g_guiMutex;
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
static ChannelWorker* g_uiWorker = nullptr;
// Same-plugin-DLL reentrancy guards: two threads inside one VST3 DLL (Nexus)
// crash or deadlock. g_attachPaths = lowercase plugin paths whose reload/
// createView is running on some worker — a same-path close job must not unmute
@@ -125,7 +125,7 @@ static ChannelWorker* g_uiWorker = nullptr;
static std::mutex g_attachMutex;
static std::vector<std::string> g_attachPaths;
static bool g_closeInFlight[16] = { false };
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_CLOSE) {
uint32_t ch = UINT32_MAX;
@@ -263,7 +263,7 @@ public:
#endif
th_ = std::thread([this] {
#ifdef _WIN32
OleInitialize(nullptr);
OleInitialize(nullptr);
OleInitialize(nullptr);
// Default IMC = none on this thread: new editor windows get
// no IME context (see disable_ime_contexts).
@@ -295,11 +295,11 @@ public:
for (int pumped = 0; pumped < 128; ++pumped) {
if (!PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) break;
hadMessages = true;
// G1.3: bo pump gate cu — teardown (close/LOAD job)
// va pump cung 1 thread (UiThread): job chay thi pump dung;
// het job thi editor window da destroy -> message chet bi
// USER32 huy tu dong. Khong can drop theo close_in_flight.
// G1.3: bo pump gate cu — teardown (close/LOAD job)
// va pump cung 1 thread (UiThread): job chay thi pump dung;
// het job thi editor window da destroy -> message chet bi
// USER32 huy tu dong. Khong can drop theo close_in_flight.
TranslateMessage(&msg);
// CRASH FIX (0xc000041d STATUS_FATAL_USER_CALLBACK_EXCEPTION):
// a plugin window proc (Nexus throws nlohmann::json::out_of_range
@@ -328,7 +328,7 @@ public:
}
}
#ifdef _WIN32
OleUninitialize();
OleUninitialize();
OleUninitialize();
#endif
});
@@ -364,7 +364,7 @@ public:
#endif
cv_.notify_all();
}
private:
std::thread th_;
@@ -421,24 +421,24 @@ static bool close_in_flight(uint32_t ch) {
return g_closeInFlight[ch];
}
// G1.3: 1 UiThread — editor windows tao trong attach job (UiThread), destroy
// chi goi tu UiThread job (close_editor_now / LOAD) -> ownerTid luon == current
// tid -> destroy truc tiep. Neu owner khac thread (khong con xay ra) -> bo
// (leak > crash): cross-thread DestroyWindow khong an toan.
static void destroy_window_on_owner(HWND hwnd) {
if (!hwnd || !IsWindow(hwnd)) return;
DWORD ownerTid = GetWindowThreadProcessId(hwnd, nullptr);
DWORD curTid = GetCurrentThreadId();
if (ownerTid == 0 || ownerTid == curTid) {
if (IsWindow(hwnd)) DestroyWindow(hwnd);
return;
}
std::cerr << "[NativeBridge] destroy_window_on_owner: owner tid=" << ownerTid
<< " != current " << curTid << " - leaving window " << hwnd << std::endl;
}
// G1.3: 1 UiThread — editor windows tao trong attach job (UiThread), destroy
// chi goi tu UiThread job (close_editor_now / LOAD) -> ownerTid luon == current
// tid -> destroy truc tiep. Neu owner khac thread (khong con xay ra) -> bo
// (leak > crash): cross-thread DestroyWindow khong an toan.
static void destroy_window_on_owner(HWND hwnd) {
if (!hwnd || !IsWindow(hwnd)) return;
DWORD ownerTid = GetWindowThreadProcessId(hwnd, nullptr);
DWORD curTid = GetCurrentThreadId();
if (ownerTid == 0 || ownerTid == curTid) {
if (IsWindow(hwnd)) DestroyWindow(hwnd);
return;
}
std::cerr << "[NativeBridge] destroy_window_on_owner: owner tid=" << ownerTid
<< " != current " << curTid << " - leaving window " << hwnd << std::endl;
}
// Unmute y unless its own close job is still inside createInstance — that job
// performs the unmute once its fresh instance is loaded (or, if an attach for
@@ -594,9 +594,9 @@ int main(int argc, char* argv[]) {
// without an exe manifest. Ignore failure (already aware).
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
// Default IMC = none on the main thread (IME recursion fix).
// Default IMC = none on the main thread (IME recursion fix).
ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT);
// Default IMC = none on the main thread (IME recursion fix).
ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT);
#endif
// 1. Shared memory name: argv --shm <name> | env SF_SHM_NAME | default
@@ -781,7 +781,7 @@ int main(int argc, char* argv[]) {
~AttachInFlightGuard() { if (ipc) ipc->attachInFlight = 0; }
};
auto handleOpenGui = [&](uint32_t guiCh, uintptr_t arg1, const std::string& pluginId) {
if (!instruments.get(guiCh)) {
if (!instruments.has(guiCh)) {
std::cerr << "[NativeBridge] GUI deferred ch=" << guiCh
<< " plugin=" << pluginId << " (no instrument yet) - queued" << std::endl;
std::lock_guard<std::mutex> lock(g_pendingGuiMutex);
@@ -791,10 +791,10 @@ int main(int argc, char* argv[]) {
// Dedupe OPEN_GUI spam (frontend openVstGuiRetry): view dang
// attached -> GUI da mo, khong post attach job lan nua. Re-attach
// tren view dang attached lam plugin loi (Nexus createView null).
if (auto* i0 = instruments.get(guiCh)) {
if (i0->hasAttachedView()) {
return;
}
if (instruments.withInstrument(guiCh, [](INativeInstrument* i0) {
return i0 && i0->hasAttachedView();
})) {
return;
}
// G0.1 (BUG_REPORT): mo GUI khi dang PLAY -> attachView chay tren worker
// thread trong luc audio loop process() cung DLL (2 thread 1 plugin) ->
@@ -1022,9 +1022,9 @@ int main(int argc, char* argv[]) {
<< " plugin=" << arg2 << std::endl;
#ifdef _WIN32
// Attach that — khong co view (VD: channel la SF2/SFZ hoac plugin
// loi). Dong ngay cua so vo nghia de khong con window treo trong
// registry; WM_CLOSE -> UiThread pump destroy (owner = UiThread).
// PostMessage an toan cross-thread (khong nhu DestroyWindow).
// loi). Dong ngay cua so vo nghia de khong con window treo trong
// registry; WM_CLOSE -> UiThread pump destroy (owner = UiThread).
// PostMessage an toan cross-thread (khong nhu DestroyWindow).
PostMessageA((HWND)hwnd, WM_CLOSE, 0, 0);
#endif
@@ -1070,7 +1070,7 @@ int main(int argc, char* argv[]) {
std::cout << "[NativeBridge] restoring " << snap.instruments.size()
<< " instruments from " << stateFile << std::endl;
for (const auto& e : snap.instruments) {
uint32_t ch = e.channel & 0xF;
uint32_t ch = e.channel & 0xF;
if (e.path.empty() || instruments.has(ch)) continue; // guard: empty channel only
InstrumentType t = (InstrumentType)e.type;
std::string path = e.path;
@@ -1191,11 +1191,11 @@ int main(int argc, char* argv[]) {
// DONG cua so editor dang mo cua channel TRUOC khi assign():
// thay the inst (VST3 -> SF2/inst khac) ma editor con song ->
// old inst destructor goi view->removed() tren HWND con hoat
// dong -> plugin block -> treo bridge.
// G1.2/G1.3: window tao tren UiThread (handleOpenGui post attach
// job; create_native_vst_window chay trong job). LOAD job cung
// chay tren UiThread -> khong cross-thread destroy. An window,
// giu registry de reuse; editor detach + reload o duoi.
// dong -> plugin block -> treo bridge.
// G1.2/G1.3: window tao tren UiThread (handleOpenGui post attach
// job; create_native_vst_window chay trong job). LOAD job cung
// chay tren UiThread -> khong cross-thread destroy. An window,
// giu registry de reuse; editor detach + reload o duoi.
HWND hToHide = nullptr;
{
@@ -1280,7 +1280,9 @@ int main(int argc, char* argv[]) {
// Release sustain pedal TRUOC (CC64=0) — nhieu VSTi giu note
// khi pedal con down -> note-off cua allNotesOff bi bo qua
// -> am treo loop (V8 bug 3).
for (uint32_t ch = 0; ch < 16; ++ch) {
for (uint32_t ch = 0; ch < 16; ++ch) {
instruments.withInstrument(ch, [&](INativeInstrument* inst) {
if (inst) inst->controlChange(ch, 64, 0);
});
}
instruments.allNotesOff();
@@ -1315,7 +1317,7 @@ int main(int argc, char* argv[]) {
{
std::lock_guard<std::mutex> lock(g_pendingGuiMutex);
for (auto it = g_pendingGui.begin(); it != g_pendingGui.end(); ) {
// G0.1: giu entry khi dang PLAY - handleOpenGui se defer lai
// G0.1: giu entry khi dang PLAY - handleOpenGui se defer lai
if (transportStopped && instruments.has(it->first)) {
readyGui.push_back({ it->first, it->second });
it = g_pendingGui.erase(it);
@@ -1334,7 +1336,7 @@ int main(int argc, char* argv[]) {
auto nowMark = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(nowMark - lastMark).count() >= 2000) {
lastMark = nowMark;
for (uint32_t ch = 0; ch < 16; ++ch) {
for (uint32_t ch = 0; ch < 16; ++ch) {
if (instruments.has(ch)) { g_stateDirty = true; break; }
}
}