main
Drew Owens 0.11.2 source 784acbf 5d ago
//! ADVERSARIAL REVIEW OF 0.4.2 — round three, "the attribution law".
//!
//! The law itself holds. I could not find a way to make the bridge terminate a
//! process it did not start: attribution now comes only from the invoke's own
//! handle, the walk descends and cannot invent a root, seeds are live
//! identity-confirmed processes, and the kill re-verifies on the open handle.
//! Rounds one and two are closed at the mechanism.
//!
//! What is left is the OTHER half of the same question. The bridge no longer
//! adopts a stranger's PROCESSES — but it still adopts a stranger's EVIDENCE.
//! A launcher log is attributed to us on "newer than the newest log that
//! existed before our invoke", with no link to the chain we started, so a
//! launch the user begins while we watch supplies our stage, our `Run Begin`
//! and, at the end, the route we remember as the one that works.
//!
//! Every test here FAILS on f818f14.

mod common;

use common::{Bridge, TempDir};
use serde_json::{json, Value};
use std::time::{Duration, Instant};
use swx_bridge::launch::{self, Attribution};
use swx_bridge::launchos::{FakeOs, ProcInfo};

fn s(v: &Value) -> String {
    serde_json::to_string_pretty(v).unwrap_or_default()
}

fn p(pid: u32, image: &str, created: u64, ppid: Option<u32>) -> ProcInfo {
    ProcInfo { pid, image: image.to_string(), created_at: Some(created), ppid }
}

/// A simulated machine on which NONE of the bridge's four routes starts
/// anything — and on which the user then starts SOLIDWORKS themselves.
struct Sim {
    dir: TempDir,
    lnk: String,
}

impl Sim {
    fn new(tag: &str, routes: Value) -> Sim {
        let dir = TempDir::new(tag);
        std::fs::write(dir.join("routes.json"), routes.to_string()).unwrap();
        std::fs::write(
            dir.join("preexisting.json"),
            json!([
                {"pid": 6076, "image": "3DEXPERIENCELauncher.exe", "ageSeconds": 30000},
                {"pid": 90552, "image": "3DEXPERIENCELauncherSysTray.exe", "ageSeconds": 30000}
            ])
            .to_string(),
        )
        .unwrap();
        let lnk = format!("{}/SOLIDWORKS Design.lnk", dir.path());
        std::fs::write(&lnk, "not really a shortcut").unwrap();
        Sim { dir, lnk }
    }

    /// The user starts SOLIDWORKS from their Start Menu. The simulator records
    /// it exactly as it records one of ours — because from the outside, that is
    /// precisely how indistinguishable the two are.
    fn user_starts_solidworks(&self) {
        FakeOs::record_invoke(std::path::Path::new(&self.dir.path()), "userStartMenu", &self.lnk);
    }

    fn terminated(&self) -> Vec<u64> {
        std::fs::read_to_string(self.dir.join("terminated.jsonl"))
            .unwrap_or_default()
            .lines()
            .filter_map(|l| serde_json::from_str::<Value>(l).ok())
            .filter_map(|v| v.get("pid")?.as_u64())
            .collect()
    }

    fn boot(&self) -> Bridge {
        let env: Vec<(&str, &str)> = vec![
            ("SWX_SOLIDWORKS_SHORTCUT", Box::leak(self.lnk.clone().into_boxed_str())),
            ("SWX_LAUNCH_FAKE_DIR", Box::leak(self.dir.path().into_boxed_str())),
            ("SWX_LAUNCH_RUN_BEGIN_SECS", "3"),
            ("SWX_LAUNCH_SLDWORKS_SECS", "6"),
            ("SWX_LAUNCH_ATTACH_SECS", "12"),
            ("SWX_LAUNCH_POLL_MS", "100"),
            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),
            ("SWX_LAUNCH_ATTACH_POLL_MS", "150"),
            ("SWX_LAUNCH_INFLIGHT_SECS", "120"),
            ("SWX_LAUNCH_BACKOFF_SECS", "0"),
        ];
        Bridge::start_in(TempDir::new("adv3-scratch"), &env)
    }
}

/// Every route runs and starts nothing; the user's own launch works.
fn nothing_of_ours_starts() -> Value {
    json!({
        "shellExecute": {"chain": false},
        "powershell": {"chain": false},
        "explorer": {"chain": false},
        "cmdStart": {"chain": false},
        "userStartMenu": {"chain": true, "runBeginAfterMs": 0, "helperAfterMs": 50,
                          "sldworksAfterMs": 100, "attachAfterMs": 600}
    })
}

fn wait_for_launch(b: &Bridge, secs: u64) -> Value {
    let deadline = Instant::now() + Duration::from_secs(secs);
    loop {
        let last = b.call("swx_status", json!({}));
        if last["launch"]["finished"] == true || Instant::now() > deadline {
            return last;
        }
        std::thread::sleep(Duration::from_millis(150));
    }
}

/// Drive the shared scenario: our route starts nothing, the user starts
/// SOLIDWORKS 350ms later, and it comes up.
fn someone_elses_launch(tag: &str) -> (Sim, Bridge, Value) {
    let m = Sim::new(tag, nothing_of_ours_starts());
    let b = m.boot();
    b.call("swx_start", json!({"reason": "modelling a bracket", "waitSeconds": 0}));
    std::thread::sleep(Duration::from_millis(350));
    m.user_starts_solidworks();
    let st = wait_for_launch(&b, 30);
    (m, b, st)
}

// ════════════════════════════════════════════════════════════════════════════
// THE EVIDENCE IS STILL ADOPTED, EVEN THOUGH THE PROCESSES ARE NOT
// ════════════════════════════════════════════════════════════════════════════

/// `watch_route` attributes a launcher log on ONE test — is it newer than the
/// newest log that existed before our invoke? That answers "this log is not
/// pre-existing". It does not answer "this log is ours", and nothing else is
/// asked: the log is not required to belong to the chain the invoke handed
/// back, nor is any attribution required to exist at all.
///
/// So on a run where our route started nothing — which this bridge detects,
/// names, and (rightly) keeps going after — a launch the user starts while we
/// are watching writes the log we then read as our own, and `Run Begin` in
/// THEIR log becomes our `runBeginSeen`.
///
/// The same rule that stopped the bridge adopting the user's PROCESSES has to
/// be applied to their EVIDENCE: a log is ours when our attributed chain is
/// alive to have written it, or the state is `unattributedLog` and says so.
#[test]
fn a_launcher_log_from_someone_elses_launch_is_not_our_run_begin() {
    let (_m, b, st) = someone_elses_launch("adv3-userlog");
    assert!(
        st["launch"]["outcome"] == "attached",
        "precondition: the user's launch succeeded and the bridge attached to it: {}",
        s(&st["launch"])
    );
    assert!(
        st["launch"]["attributed"] == false,
        "precondition: our own route started nothing and attributed nothing: {}",
        s(&st["launch"])
    );
    assert!(
        st["launch"]["runBeginSeen"] != json!(true),
        "the bridge recorded that it saw 'Run Begin' — from a launcher log written by a launch \
         the USER started. Its own route started nothing at all: there is no chain of ours that \
         could have written a log. 'Newer than the baseline' means the log is not pre-existing; \
         it does not mean it is ours.\n{}\n--- server log ---\n{}",
        s(&st["launch"]),
        b.log()
    );
}

/// The consequence, and the reason this is not cosmetic.
///
/// `finish("attached")` guards the route memory with `run_begin_seen` — added
/// in round two precisely so that "a user who starts SOLIDWORKS from their
/// Start Menu mid-flight" could not teach the bridge that whatever route it
/// happened to be trying is the one that works. That guard is defeated by the
/// hole above: the user's `Run Begin` sets `run_begin_seen`, so the bridge
/// writes `preferredRoute` for a route that started NOTHING — and then tries
/// that route first on every future cold start, on this machine, for ever.
#[test]
fn a_route_that_started_nothing_is_never_remembered_as_the_one_that_works() {
    let (_m, b, st) = someone_elses_launch("adv3-preferred");
    assert!(
        st["launch"]["outcome"] == "attached" && st["launch"]["attributed"] == false,
        "precondition: attached, with nothing attributed to our own route: {}",
        s(&st["launch"])
    );
    assert!(
        st["launch"]["preferredRoute"].is_null(),
        "the bridge learned '{}' as the route that works on this machine — from a run in which \
         that route started nothing whatsoever and the USER launched SOLIDWORKS. It is now \
         first in the plan for every future cold start. The round-two guard (`run_begin_seen`) \
         was supposed to prevent exactly this, and it is satisfied by somebody else's log.\n{}\n\
         --- server log ---\n{}",
        st["launch"]["preferredRoute"].as_str().unwrap_or("?"),
        s(&st["launch"]),
        b.log()
    );
}

/// The one piece of good news worth pinning down so it cannot regress: the
/// user's PROCESSES are still safe. Nothing descends from an attributed root,
/// so nothing is killed. This test passes today; it is here so that a future
/// change to the log rule cannot quietly re-open the process side.
#[test]
fn the_users_own_launcher_chain_is_still_never_terminated() {
    let (m, b, st) = someone_elses_launch("adv3-userprocs");
    let killed = m.terminated();
    assert!(
        killed.is_empty(),
        "the bridge terminated {killed:?} — processes belonging to a launch it did not start.\n\
         {}\n--- server log ---\n{}",
        s(&st["launch"]),
        b.log()
    );
}

// ════════════════════════════════════════════════════════════════════════════
// THE WALK CANNOT CROSS A ROOT THE CENSUS DOES NOT LIST
// ════════════════════════════════════════════════════════════════════════════

/// `attributed_chain` confirms the root by finding it IN THE CENSUS — and the
/// census is filtered to the six `WATCHED_IMAGES`. `ShellExecuteExW` hands back
/// whatever process the shell created for the `.lnk`, and the release is
/// explicit that no real reply has ever been observed (§9). If that process is
/// anything other than `CATSTART.exe` or `SWXDesktopLauncher.exe` — a shim, a
/// wrapper, a future Platform build, `rundll32` — the root cannot be confirmed,
/// the seed list is empty, the walk has nothing to descend from, and the real
/// CATSTART it started is never attributed.
///
/// The bridge then reports that stall as `cleanup:"nothingToClean"` with
/// `attributed:true` — a CLEAN recovery — and goes on to the next route, with a
/// launcher chain of its own still running. The parent link is right there: the
/// orphan's ppid is the handle-proven pid we were given.
#[test]
fn a_root_the_census_cannot_see_still_carries_its_children() {
    let invoked = 1_000_000u64;
    let root = Attribution {
        pid: 500,
        created_at: Some(invoked + 1),
        source: "shellExecuteHandle".into(),
    };
    // The root itself is not one of the six watched images, so it is not in the
    // census at all. The chain it started IS.
    let census = vec![
        p(501, "CATSTART.exe", invoked + 2, Some(500)),
        p(502, "SWXDesktopLauncher.exe", invoked + 3, Some(501)),
    ];
    let chain = launch::attributed_chain(&census, Some(&root), &[], &[], invoked);
    let pids: Vec<u32> = chain.clone().unwrap_or_default().iter().map(|x| x.pid).collect();
    assert!(
        pids.contains(&501) && pids.contains(&502),
        "the invoke handed back pid 500 as the root it started, and pid 501 records 500 as its \
         parent — but the walk found nothing ({pids:?}) because 500 is not itself a terminable \
         image, so it never became a seed. The supervisor reads the empty result as \
         'nothingToClean' and moves on, leaving the chain running. A handle-proven root is \
         proof of lineage whether or not the root is a process we may terminate."
    );
}