fix(install): skip and reject links when extracting .eti archives

Security audit findings EXP2-SEC-01, SEC-IPC-04 and Codex #3 (symlink
redirection through externally extracted archives).

The ordinary install path hands every root `.eti` archive to an external
`unrar x` process and promotes the resulting staging directory to
`local/` as soon as the extractor exits 0. Nothing inspected what unrar
materialised. A symbolic link (or, on Windows, a junction) inside an
archive would survive promotion and later redirect the launch-time
settings rewrite in `apply_launch_settings_once`, the uninstall path,
or any script the game ships. The archives themselves are BLAKE3
verified against the bundled catalog before they can be installed, so a
hostile link would have to be published by the catalog operator; this
is defense in depth rather than a live remote exploit.

Two independent layers now guard promotion:

- Both `unrar` invocations (Tauri sidecar and peer-cli external
  unpacker) pass `-ol-`, which makes unrar 7.x skip symbolic-link
  entries entirely. The bundled 7.10 sidecar was checked against a
  fixture archive.
- `install_inner`/`update_inner` walk the staging tree without
  following links and refuse to promote it if any entry is a symlink or
  (on Windows) carries the reparse-point attribute. The normal rollback
  then removes staging and clears the install intent.

The audit's `-sl-` suggestion does not exist in unrar; `-sl<size>` is a
size filter. Post-extraction digest verification of every extracted
file remains out of scope for the ordinary path; Stream Install already
verifies each output entry.

Test plan: `just test` (new unix test installs with a fake unpacker
that plants a symlink and asserts install fails, `local/` is absent and
the intent is cleared; the peer-cli controlled-unrar test checks the
new argument position). Manual: install a fixture game via peer-cli.

Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg
This commit is contained in:
ddidderr committed 2026-09-02 22:32:43 +02:00
1 parent 887e245638
commit 84cbabfeba
3 files changed
+99 -5

No files matched your search

+5 -1
View File
@@ -347,6 +347,9 @@ impl Unpacker for ExternalUnrarUnpacker {
std::ffi::OsString::from("x"),
std::ffi::OsString::from("-o+"),
std::ffi::OsString::from("-p-"),
// Skip symbolic links inside the archive; the install
// transaction also audits the extracted tree for links.
std::ffi::OsString::from("-ol-"),
archive.as_os_str().to_owned(),
dest.as_os_str().to_owned(),
],
@@ -649,7 +652,8 @@ mod tests {
r#"#!/bin/sh
set -eu
[ "$3" = "-p-" ]
dest=$5
[ "$4" = "-ol-" ]
dest=$6
printf '%s' "$$" > "$dest/child.pid"
printf 'started' > "$dest/started"
while [ ! -e "$dest/release" ]; do :; done