mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-08-11 03:09:56 +00:00
fix(sandbox): force PolyChrome DSP plugins out-of-process (in-process WndProc DEP crash) (#34)
* fix(sandbox): force PolyChrome DSP plugins out-of-process (in-process WndProc DEP crash) A tester crash dump (feedback.exe 0.3.0, Windows) showed an execute-DEP access violation (0xC0000005) killing the app while McRocklin Suite.vst3 (PolyChrome DSP) was loaded IN-PROCESS: Rax == Rip == McRocklin Suite.vst3 + 0x1D57050 (non-executable module data) caller [Rsp] = USER32.dll+0xEF5C WndProc(hwnd=0x51CCA, msg=0x1C WM_ACTIVATEAPP, wParam=1, lParam=0x1838) crash thread = the addon's background JUCE MessageManager thread (unnamed; start frame slopsmith_audio.node), NOT Electron's CrBrowserMain. PolyChrome creates a top-level window during in-process init on JUCE's *background* message thread. Its WndProc lands in non-executable memory there, so when Windows broadcasts WM_ACTIVATEAPP the OS message pump executes it → DEP AV. The plugin assumes a real host main UI thread (STA/main); the sandbox child provides exactly that, so routing it out-of-process both isolates the fault and gives the plugin the environment it needs. Crucially this crash cannot be caught by the SignalChain in-process fault guard: it arrives asynchronously via USER32→WndProc with NO host frame on the stack, so guarding prepareToPlay/processBlock (or even instantiation) never sees it. Under the current in-process-by-default policy (#24) the only fix is to not host these plugins in-process. Graphene (same vendor) was already pre-seeded; this extends the pre-seed to the whole PolyChrome vendor via a path-fragment match so McRocklin Suite and any other PolyChrome product route to the sandbox too. - Add kDefaultNeedsSandboxPathFragments (vendor/path match) + the loop in shouldSandbox; seed it with "PolyChrome". - Refresh the stale kDefaultNeedsSandboxFilenames comment (it still claimed sandbox-by-default; #24 made the list authoritative again). - e2e_test: add testShouldSandboxRouting() — pure shouldSandbox assertions (PolyChrome→sandbox, clean VST3→in-process, non-VST3→in-process). Verified: audio addon builds clean; sandbox_e2e_test green (16/16, routing assertions included). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * review: tighten PolyChrome match + dedupe path + harden routing test Local high-effort review of the pre-seed fix surfaced four items; addressed: - False-positive risk (no in-process fallback exists here — loadVstSandboxAware hard-fails a force-sandboxed load that can't spawn the child): narrow the fragment from the bare brand word "PolyChrome" to the vendor install folder "PolyChrome DSP", so an unrelated path (e.g. a username "polychrome") no longer forces the sandbox. Still matches McRocklin Suite + Graphene, which ship under Common Files/VST3/PolyChrome DSP/. - Dedupe: getFullPathName() was computed twice (blocklist `canonical` + vendor `fullPath`); hoist one `fullPath` above the mutex block and reuse it. - Test isolation: assert McRocklin Suite (NOT in the filename pre-seed) on both Windows- and POSIX-style paths so the case can only pass via the new vendor match; drop the redundant Graphene-in-folder line (Graphene already routes via the filename list). - Test specificity + exit-code masking: add a negative proving a bare "polychrome" path is NOT sandboxed (guards the tightening), and surface routing CHECK failures on the no-args path (return 1, not the usage code 2) so a regression isn't masked on a manual/argless run. Addon builds clean; sandbox_e2e_test 17/17 green (routing included). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4d19398a50
commit
7f8975641e
@@ -17,12 +17,11 @@ namespace slopsmith::sandbox {
|
||||
|
||||
namespace {
|
||||
|
||||
// Historical pre-seed of plugins known to fail in-process. With the
|
||||
// sandbox-by-default policy in shouldSandbox() below, every VST3 routes to the
|
||||
// sandbox regardless of this list, so it no longer determines routing on its
|
||||
// own. It survives as (a) documentation of *why* each plugin originally needed
|
||||
// the sandbox, (b) diagnostic tagging in shouldSandbox's VST_TRACE output, and
|
||||
// (c) forward-looking infrastructure for a future per-plugin opt-out.
|
||||
// Pre-seed of plugins known to fail when hosted in-process. Under the current
|
||||
// in-process-by-default policy (see shouldSandbox() below) this list DOES drive
|
||||
// routing: a filename matched here is forced to the out-of-process sandbox
|
||||
// instead of loading in-process. Matched against the plugin's basename
|
||||
// (case-insensitive prefix).
|
||||
const juce::StringArray kDefaultNeedsSandboxFilenames = {
|
||||
"Guitar Rig",
|
||||
"Graphene",
|
||||
@@ -30,6 +29,32 @@ const juce::StringArray kDefaultNeedsSandboxFilenames = {
|
||||
"AmpliTube",
|
||||
};
|
||||
|
||||
// Vendor/path fragments that force the sandbox regardless of filename — matched
|
||||
// (case-insensitive substring) against the full plugin path. Use this for a
|
||||
// whole vendor whose plugins share an install folder rather than enumerating
|
||||
// every product. Prefer the most specific reliable fragment (the vendor's
|
||||
// install-folder name, not a bare brand word): there is NO in-process fallback
|
||||
// here — loadVstSandboxAware hard-fails the load if a force-sandboxed plugin
|
||||
// can't spawn the sandbox child — so a false-positive match on an unrelated
|
||||
// path would turn a fine in-process plugin into a load failure on a machine
|
||||
// with a broken sandbox host.
|
||||
//
|
||||
// PolyChrome DSP (McRocklin Suite, Graphene, …) creates a top-level window on
|
||||
// the host message thread during in-process init. On Electron's BACKGROUND JUCE
|
||||
// message thread that window's WndProc ends up in non-executable memory, so when
|
||||
// Windows broadcasts WM_ACTIVATEAPP the OS message pump executes it → an
|
||||
// execute-DEP access violation (0xC0000005) that kills the app. That crash
|
||||
// arrives via USER32→WndProc with NO host frame on the stack, so the SignalChain
|
||||
// fault guard cannot catch it and the runtime blocklist never gets to record it.
|
||||
// The sandbox child hosts the plugin on a real top-level message thread, which
|
||||
// both isolates the fault and is the environment the plugin actually needs.
|
||||
// (Diagnosed from crash dump a06f48e1: Rax==Rip==McRocklin Suite.vst3 WndProc,
|
||||
// caller USER32+0xEF5C, msg=WM_ACTIVATEAPP. The vendor ships to
|
||||
// Common Files/VST3/PolyChrome DSP/, so that folder name is the reliable match.)
|
||||
const juce::StringArray kDefaultNeedsSandboxPathFragments = {
|
||||
"PolyChrome DSP",
|
||||
};
|
||||
|
||||
// Runtime crash blocklist: full plugin paths that crashed the app on a previous
|
||||
// run, supplied by the renderer's VST crash guard via setCrashedPlugins().
|
||||
std::mutex g_crashedPluginsMutex;
|
||||
@@ -56,12 +81,14 @@ bool shouldSandbox(const juce::PluginDescription& desc)
|
||||
if (!path.getFileName().endsWithIgnoreCase(".vst3"))
|
||||
return false;
|
||||
|
||||
// Canonical path, computed once and reused by the blocklist + vendor checks.
|
||||
const auto fullPath = path.getFullPathName();
|
||||
|
||||
// Runtime crash blocklist: a plugin that previously faulted in-process is
|
||||
// forced back to the out-of-process sandbox on every subsequent load.
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_crashedPluginsMutex);
|
||||
const auto canonical = path.getFullPathName();
|
||||
if (g_crashedPlugins.contains(canonical, /*ignoreCase*/ true))
|
||||
if (g_crashedPlugins.contains(fullPath, /*ignoreCase*/ true))
|
||||
{
|
||||
VST_TRACE("shouldSandbox: %s — on the runtime crash blocklist",
|
||||
desc.fileOrIdentifier.toRawUTF8());
|
||||
@@ -81,6 +108,19 @@ bool shouldSandbox(const juce::PluginDescription& desc)
|
||||
}
|
||||
}
|
||||
|
||||
// Vendor/path pre-seed: force whole vendors known to fail in-process (e.g.
|
||||
// PolyChrome DSP — see kDefaultNeedsSandboxPathFragments) to the sandbox,
|
||||
// even if their individual filenames aren't enumerated above.
|
||||
for (auto& fragment : kDefaultNeedsSandboxPathFragments)
|
||||
{
|
||||
if (fullPath.containsIgnoreCase(fragment))
|
||||
{
|
||||
VST_TRACE("shouldSandbox: %s — path contains '%s' (vendor needs sandbox)",
|
||||
desc.fileOrIdentifier.toRawUTF8(), fragment.toRawUTF8());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: load in-process for PLAYBACK. A plugin reaches a chain only after
|
||||
// it scanned cleanly (the sandbox's real job is crash-isolating the SCAN of
|
||||
// unknown plugins), so the common case is known-good and the out-of-process
|
||||
|
||||
Reference in New Issue
Block a user