fix(peer): reject unsafe game IDs in state marker paths
Scanner finding #12 ("state marker path escape"). The per-game state helpers in `state_paths.rs` joined a raw game ID below `<state_dir>/games/`. The public `setup_done_path` was therefore usable with an absolute or parent-containing ID by an embedding caller, and the legacy migration discovered IDs from directory names in the user's games folder and joined them unconditionally. Every shipping caller today validates its ID or takes it from the catalog, so this was a footgun rather than an exploited hole, but the fix is small and removes the reliance on every future caller remembering the rule. Add `validate_game_state_id`, which rejects separators and NUL and then delegates to `lanspread_db::content_manifest::validate_portable_component` (the catalog's own rules: no `.`/`..`, no trailing dot or space, no control or Windows-reserved characters, no Windows device names). Reusing the catalog validator rather than a private copy guarantees that any ID the catalog can publish is accepted here and that the two cannot drift apart. `setup_done_path` now returns `eyre::Result<PathBuf>`; it is the only state path the embedding application calls with an ID that may originate from UI input. `launch_settings_applied_path` leaves the public API and becomes `pub(crate)`; the two public launch-settings entry points (`apply_launch_settings_once`, `mark_launch_settings_applied`) validate the ID before any filesystem work. `game_state_dir` carries a `debug_assert!` documenting the contract for internal callers without turning a bad ID into a release-build panic; the migration test suite exercises that assertion in debug builds. Behaviour changes: - Legacy migration logs a warning, counts a failure and leaves the legacy marker in place for a games-folder directory whose name is not a portable game ID, instead of creating state below it. A new test covers a trailing-dot directory name. - The Windows launcher ignores a run request whose ID `setup_done_path` rejects, with a warning, mirroring the existing invalid-ID early return. Tests cover catalog-valid IDs that must remain accepted (embedded dots, spaces, `console.txt`, `com10`, non-ASCII) and unsafe IDs that must be rejected (empty, `.`, `..`, separators, NUL, trailing dot or space, device names, `a:b`). This ports the fallible API from the parallel security branch (lanspread2 commits 4146a0e and 9f26c63) onto the validator this branch already exports from `lanspread-db`. Test plan: - `cargo test -p lanspread-peer --lib`: 491 passed. - `just clippy`: clean. - On Windows, launch a game with a valid ID and confirm the setup marker is still written under `<app-data>/games/<id>/setup_done`. Claude-Session: https://claude.ai/code/session_01QRkCv4a4GqkajyamxmbSuA
This commit is contained in:
1 parent
63aa4bc77c
commit
0a38dfbb19
7 files changed
+145
-13
No files matched your search
@@ -1907,7 +1907,13 @@ async fn run_game_windows(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let setup_done_file = lanspread_peer::setup_done_path(&state_dir, &id);
|
||||
let setup_done_file = match lanspread_peer::setup_done_path(&state_dir, &id) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
log::warn!("Ignoring run request for unsafe game id {id}: {error}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if !setup_done_file.exists() && game_setup_bin.exists() {
|
||||
if !local_install_is_present(&game_path) {
|
||||
log::warn!(
|
||||
|
||||
Reference in new issue
Block a user