test(kicad): poisoned-attribution lever — the laundering scenario, red/green
kicadTestFiberParkStartSecond/PokeSecond: a second coroutine started while the first body is asyncify-parked reproduces the misattributed jump that launders the parked fiber past the C++ guard (the v0.1.21 prod bypass). Spec scenario 2 stages it and asserts the JS stale-rewind guard quarantines the laundered resume (exactly one fiber-resume-refused beacon), the parked body completes undisturbed, and both coroutines finish cleanly. Doc: async/16 rounds 2 + WSOD section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
This commit is contained in:
parent
a8adfa9843
commit
5333099810
5 changed files with 204 additions and 3 deletions
|
|
@ -47,6 +47,13 @@ struct State
|
|||
int phase = 0;
|
||||
int parkMs = 0;
|
||||
int pokes = 0;
|
||||
// Second coroutine (poisoned-attribution scenario): 0 idle · 1 yielded ·
|
||||
// 2 completed. Starting it while the FIRST body is asyncify-parked makes
|
||||
// libcontext attribute the jump's old side to that parked fiber
|
||||
// (g_current_context is stale), writing a fresh suspension into its
|
||||
// struct — the exact laundering that let the prod resume bypass the
|
||||
// swap_suspended guard.
|
||||
int phase2 = 0;
|
||||
};
|
||||
|
||||
inline State& state()
|
||||
|
|
@ -114,13 +121,59 @@ inline bool poke()
|
|||
return co()->Resume();
|
||||
}
|
||||
|
||||
inline COROUTINE<int, int>*& co2()
|
||||
{
|
||||
static COROUTINE<int, int>* s_co2 = nullptr;
|
||||
return s_co2;
|
||||
}
|
||||
|
||||
inline int fiberBody2( int )
|
||||
{
|
||||
state().phase2 = 1;
|
||||
co2()->KiYield();
|
||||
state().phase2 = 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a SECOND coroutine while the first body is asyncify-parked. Because
|
||||
* g_current_context still points at the parked fiber, libcontext attributes
|
||||
* this jump's old side to it: the swap writes a fresh (foreign) suspension
|
||||
* into the PARKED fiber's struct and re-marks it swap_suspended — the
|
||||
* laundering that lets a later Resume bypass the C++ guard. The JS
|
||||
* stale-rewind guard (handlesleep.js) must still quarantine it.
|
||||
*/
|
||||
inline bool startSecond()
|
||||
{
|
||||
if( !co() )
|
||||
return false; // scenario needs the first coroutine in flight
|
||||
|
||||
if( co2() && co2()->Running() )
|
||||
return false;
|
||||
|
||||
delete co2();
|
||||
state().phase2 = 0;
|
||||
co2() = new COROUTINE<int, int>( fiberBody2 );
|
||||
co2()->Call( 0 );
|
||||
return state().phase2 == 1;
|
||||
}
|
||||
|
||||
/** Resume the second coroutine past its yield (cleanup / completion). */
|
||||
inline bool pokeSecond()
|
||||
{
|
||||
if( !co2() )
|
||||
return false;
|
||||
|
||||
return co2()->Resume();
|
||||
}
|
||||
|
||||
inline std::string stateJson()
|
||||
{
|
||||
char buf[112];
|
||||
char buf[144];
|
||||
snprintf( buf, sizeof( buf ),
|
||||
"{\"phase\":%d,\"pokes\":%d,\"parkMs\":%d,\"running\":%s}",
|
||||
"{\"phase\":%d,\"pokes\":%d,\"parkMs\":%d,\"running\":%s,\"phase2\":%d}",
|
||||
state().phase, state().pokes, state().parkMs,
|
||||
( co() && co()->Running() ) ? "true" : "false" );
|
||||
( co() && co()->Running() ) ? "true" : "false", state().phase2 );
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,16 @@ static std::string kicadTestFiberParkState()
|
|||
return pcbjam_fiber_park::stateJson();
|
||||
}
|
||||
|
||||
static bool kicadTestFiberParkStartSecond()
|
||||
{
|
||||
return pcbjam_fiber_park::startSecond();
|
||||
}
|
||||
|
||||
static bool kicadTestFiberParkPokeSecond()
|
||||
{
|
||||
return pcbjam_fiber_park::pokeSecond();
|
||||
}
|
||||
|
||||
|
||||
// Canvas-only chrome toggle (features/mobile): hide/show every AUI pane
|
||||
// except the central draw canvas, plus the menubar and status bar, so the GAL
|
||||
|
|
@ -571,6 +581,8 @@ EMSCRIPTEN_BINDINGS(kicad_editor) {
|
|||
function("kicadTestFiberParkPrime", &kicadTestFiberParkPrime);
|
||||
function("kicadTestFiberParkPoke", &kicadTestFiberParkPoke);
|
||||
function("kicadTestFiberParkState", &kicadTestFiberParkState);
|
||||
function("kicadTestFiberParkStartSecond", &kicadTestFiberParkStartSecond);
|
||||
function("kicadTestFiberParkPokeSecond", &kicadTestFiberParkPokeSecond);
|
||||
|
||||
// Canvas-only mobile mode (features/mobile).
|
||||
function("kicadSetChrome", &kicadSetChrome);
|
||||
|
|
|
|||
|
|
@ -160,6 +160,16 @@ std::string kicadTestFiberParkState()
|
|||
return pcbjam_fiber_park::stateJson();
|
||||
}
|
||||
|
||||
bool kicadTestFiberParkStartSecond()
|
||||
{
|
||||
return pcbjam_fiber_park::startSecond();
|
||||
}
|
||||
|
||||
bool kicadTestFiberParkPokeSecond()
|
||||
{
|
||||
return pcbjam_fiber_park::pokeSecond();
|
||||
}
|
||||
|
||||
// Read-only viewer lock (read-only-viewer): flips the process-global
|
||||
// PCBJAM_READ_ONLY flag consumed by TOOL_MANAGER (view-only action allowlist)
|
||||
// and the selection tools (nothing selectable), and mirrors it onto the
|
||||
|
|
@ -2428,6 +2438,8 @@ EMSCRIPTEN_BINDINGS(pcbnew) {
|
|||
function("kicadTestFiberParkPrime", &kicadTestFiberParkPrime);
|
||||
function("kicadTestFiberParkPoke", &kicadTestFiberParkPoke);
|
||||
function("kicadTestFiberParkState", &kicadTestFiberParkState);
|
||||
function("kicadTestFiberParkStartSecond", &kicadTestFiberParkStartSecond);
|
||||
function("kicadTestFiberParkPokeSecond", &kicadTestFiberParkPokeSecond);
|
||||
function("kicadCollabFiberBusy", &kicadCollabFiberBusyProbe);
|
||||
// Read-only viewer lock (read-only-viewer).
|
||||
function("kicadSetReadOnly", &kicadSetReadOnly);
|
||||
|
|
|
|||
Loading…
Reference in a new issue