main
Drew Owens 0.11.2 source 784acbf 5d ago
//! ADVERSARIAL REVIEW OF 0.6.6 — pass nineteen.
//!
//! Sign-in recovery RUNS now: my pass-18 positive case passes, and the root
//! cause the Builder found (the walk took the first sldworks.exe, which had no
//! parent) is genuinely fixed.
//!
//! But "ANY running SOLIDWORKS descends from our chain" is the wrong
//! proposition, and it is a consent bug. The attempt is marked attributed and
//! `attachedAfterSignIn` on the strength of lineage to one process, while COM —
//! which is what every subsequent verb acts through — is attached to a
//! DIFFERENT one. Nothing binds the claim to the pid it was proved for.

mod common;

use common::{Bridge, TempDir};
use serde_json::{json, Value};

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

/// `reattribute_after_attach` orders the candidates with the COM-attached pid
/// first and then takes the FIRST ONE THAT DESCENDS:
///
/// ```text
/// for cand in &ordered {
///     if let Some(why) = descends_from_our_chain(&census, cand.pid, &a) {
///         found = Some((cand, why));
///         break;
/// ```
///
/// `attached_pid` is used only to order the list. There is no check that the
/// process it settled on IS the attached one. So with more than one
/// `SLDWORKS.exe` running — ours, descended from our launcher, plus another COM
/// happens to be attached to — the attempt is claimed as ours on the strength of
/// a process nobody is talking to.
///
/// What follows is the consent problem. `quit-gate.py::require_bridge_launched`
/// reads `launch.attributed` and `runBeginSeen`; the quit ladder then builds its
/// `Identity` from the COM `processId`. So the ladder becomes willing to end a
/// session whose only qualification is that a DIFFERENT process descended from
/// our chain. `swx_close_all_documents` acts through the same COM connection.
///
/// The claim has to bind to the pid it was proved for: succeed only when the
/// descending process IS the COM-attached one, and otherwise refuse with a named
/// reason (`attached_pid_not_ours`) so a caller can see why.
#[test]
fn re_attribution_claims_only_the_process_com_is_attached_to() {
    let dir = TempDir::new("adv18-twosw");
    std::fs::write(
        dir.join("routes.json"),
        json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0,
                                "sldworksAfterMs": 0, "attachAfterMs": 6000}})
        .to_string(),
    )
    .unwrap();
    std::fs::write(dir.join("preexisting.json"), json!([]).to_string()).unwrap();
    let lnk = format!("{}/SOLIDWORKS Design.lnk", dir.path());
    std::fs::write(&lnk, "x").unwrap();

    let b = Bridge::start_in(
        TempDir::new("adv18-scratch"),
        &[
            ("SWX_SOLIDWORKS_SHORTCUT", &lnk),
            ("SWX_LAUNCH_FAKE_DIR", &dir.path()),
            ("SWX_LAUNCH_RUN_BEGIN_SECS", "1"),
            ("SWX_LAUNCH_SLDWORKS_SECS", "2"),
            ("SWX_LAUNCH_ATTACH_SECS", "2"),
            ("SWX_LAUNCH_POLL_MS", "100"),
            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),
            ("SWX_LAUNCH_BACKOFF_SECS", "0"),
        ],
    );
    b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
    // A SECOND SOLIDWORKS appears while we are launching — the user's, with a
    // parent that was never ours. (One already present would make swx_start
    // refuse outright, which is why it has to arrive mid-flight.)
    std::thread::sleep(std::time::Duration::from_millis(300));
    std::fs::write(
        dir.join("census-overrides.json"),
        json!([{"pid": 88556, "image": "SLDWORKS.exe", "ageSeconds": 0, "ppid": 4242}]).to_string(),
    )
    .unwrap();

    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
    loop {
        let st = b.call("swx_status", json!({}));
        if st["launch"]["finished"] == true || std::time::Instant::now() > deadline {
            break;
        }
        std::thread::sleep(std::time::Duration::from_millis(150));
    }
    std::thread::sleep(std::time::Duration::from_millis(6000));
    b.call("swx_start", json!({"reason": "polling after the sign-in"}));
    let st = b.call("swx_status", json!({}));

    // NOT VACUOUS: more than one SOLIDWORKS must really be running, and the
    // attempt must really have been claimed.
    let procs = st["solidworks"]["processes"].as_array().cloned().unwrap_or_default();
    assert!(
        procs.len() >= 2,
        "precondition: more than one SLDWORKS.exe is running: {}",
        s(&st["solidworks"])
    );
    if st["launch"]["outcome"] != "attachedAfterSignIn" {
        return; // it refused; nothing to prove here.
    }

    // THE CLAIM MUST BE ABOUT THE PROCESS COM IS TALKING TO.
    let attached = st["com"]["processId"].as_u64();
    let chain: Vec<u64> = st["launch"]["chainPids"]
        .as_array()
        .map(|a| a.iter().filter_map(|p| p["pid"].as_u64()).collect())
        .unwrap_or_default();
    let attached_descends = attached
        .and_then(|want| {
            procs
                .iter()
                .find(|p| p["pid"].as_u64() == Some(want))
                .and_then(|p| p["ppid"].as_u64())
        })
        .map(|ppid| chain.contains(&ppid))
        .unwrap_or(false);
    assert!(
        attached_descends,
        "the attempt was claimed as ours (`attachedAfterSignIn`, attributed:true) while COM is \
         attached to pid {attached:?}, which does NOT descend from this attempt's chain \
         {chain:?}. Lineage was proved for a different SOLIDWORKS. Every verb downstream acts \
         through this COM connection — the quit ladder builds its Identity from this very \
         processId, and quit-gate's require_bridge_launched reads only `attributed` and \
         `runBeginSeen` — so the ladder becomes willing to end a session nothing proved was \
         ours.\ncom: {}\nsolidworks: {}\nlaunch: {}",
        s(&st["com"]),
        s(&st["solidworks"]),
        s(&st["launch"])
    );
}

/// ...and the claim should be auditable. `reattributionEvidence` is the ROUTE
/// NAME (`"shellExecute"`); it does not say which pid's lineage was proved, so
/// nothing in the report lets a reader notice that the proved pid and the
/// attached pid differ.
#[test]
fn the_reattribution_evidence_names_the_process_it_proved() {
    let dir = TempDir::new("adv18-evidence");
    std::fs::write(
        dir.join("routes.json"),
        json!({"shellExecute": {"chain": true, "runBeginAfterMs": 0,
                                "sldworksAfterMs": 0, "attachAfterMs": 6000}})
        .to_string(),
    )
    .unwrap();
    std::fs::write(dir.join("preexisting.json"), json!([]).to_string()).unwrap();
    let lnk = format!("{}/SOLIDWORKS Design.lnk", dir.path());
    std::fs::write(&lnk, "x").unwrap();
    let b = Bridge::start_in(
        TempDir::new("adv18-scratch2"),
        &[
            ("SWX_SOLIDWORKS_SHORTCUT", &lnk),
            ("SWX_LAUNCH_FAKE_DIR", &dir.path()),
            ("SWX_LAUNCH_RUN_BEGIN_SECS", "1"),
            ("SWX_LAUNCH_SLDWORKS_SECS", "2"),
            ("SWX_LAUNCH_ATTACH_SECS", "2"),
            ("SWX_LAUNCH_POLL_MS", "100"),
            ("SWX_LAUNCH_RETRY_SETTLE_MS", "400"),
            ("SWX_LAUNCH_BACKOFF_SECS", "0"),
        ],
    );
    b.call("swx_start", json!({"reason": "starting SOLIDWORKS", "waitSeconds": 0}));
    let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
    loop {
        let st = b.call("swx_status", json!({}));
        if st["launch"]["finished"] == true || std::time::Instant::now() > deadline {
            break;
        }
        std::thread::sleep(std::time::Duration::from_millis(150));
    }
    std::thread::sleep(std::time::Duration::from_millis(6000));
    b.call("swx_start", json!({"reason": "polling"}));
    let st = b.call("swx_status", json!({}));
    if st["launch"]["outcome"] != "attachedAfterSignIn" {
        return;
    }
    let ev = st["launch"]["reattributionEvidence"].as_str().unwrap_or("");
    assert!(
        ev.chars().any(|c| c.is_ascii_digit()),
        "`reattributionEvidence` is {ev:?} — the route name. A claim this consequential has to \
         name the process it was proved for and the parent it descended from, or a reader cannot \
         check it against the pid COM is attached to.\n{}",
        s(&st["launch"])
    );
}