Windows local-privilege findings from design/security-review-2026-06-28.md. These are #[cfg(windows)] paths (verify in CI / on the box; this Linux dev VM can't compile MSVC). They follow the existing write_secret_file/icacls patterns; the cross-platform parts are cargo check/clippy/test green. - #2 [HIGH]: route the mgmt bearer token write through the shared write_secret_file so it gets the SAME Windows DACL (SYSTEM/Administrators) as the host key — it was cfg(unix)-only and left Users-readable, leaking full mgmt admin authority to any local user. - #3 [HIGH]: create_private_dir now applies a restrictive DACL to the %ProgramData%\punktfunk config directory (re-owns to Administrators to defeat a pre-creation, strips inheritance, SYSTEM/Admins/OWNER full + Users read-only) so a local user can't plant host.env/apps.json that the SYSTEM service trusts (env/arg-injection LPE). host.env is now written DACL-locked via write_secret_file; the config + logs dirs go through create_private_dir. - #8 [LOW]: write the web-console password file empty, icacls-lock it, THEN write the secret — closes the brief write-then-icacls TOCTOU window. - #11 [LOW]: the SYSTEM logs dir is DACL-locked (Users read-only, no create), so a local user can't pre-plant host.log as a reparse/hardlink to redirect SYSTEM's writes (subsumed by the #3 dir lockdown). Deferred: #5 (host<->UMDF gamepad/IDD shared-section Everyone:GENERIC_ALL). The section SDDL is intentionally permissive because the UMDF driver opens it under a restricted token of unknown SID/integrity; scoping it blind would likely break the live-validated gamepad/IDD pipeline, so it needs on-box validation first. Tracked in the report. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -271,8 +271,11 @@ fn set_web_password(pw_path: &Path, pw_file: Option<&str>) {
|
||||
}
|
||||
});
|
||||
if let Some(pw) = password {
|
||||
if std::fs::write(pw_path, format!("PUNKTFUNK_UI_PASSWORD={pw}\n")).is_err() {
|
||||
eprintln!("warning: could not write {}", pw_path.display());
|
||||
// Create the file EMPTY first, lock its DACL, THEN write the secret — so the cleartext
|
||||
// password is never present at the inherited (Users-readable) %ProgramData% ACL, even for
|
||||
// the brief window before icacls runs (security-review 2026-06-28 #8).
|
||||
if std::fs::write(pw_path, b"").is_err() {
|
||||
eprintln!("warning: could not create {}", pw_path.display());
|
||||
return;
|
||||
}
|
||||
// Lock down: drop inheritance, grant only Administrators (S-1-5-32-544) + SYSTEM (S-1-5-18).
|
||||
@@ -287,6 +290,10 @@ fn set_web_password(pw_path: &Path, pw_file: Option<&str>) {
|
||||
"*S-1-5-18:F",
|
||||
],
|
||||
);
|
||||
// Now write the secret into the already-locked file (truncate keeps the explicit DACL).
|
||||
if std::fs::write(pw_path, format!("PUNKTFUNK_UI_PASSWORD={pw}\n")).is_err() {
|
||||
eprintln!("warning: could not write {}", pw_path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,13 +114,15 @@ pub fn main(args: &[String]) -> Result<()> {
|
||||
/// stdout/stderr are redirected to `host.log` in the same dir.
|
||||
pub fn service_log_path() -> PathBuf {
|
||||
let dir = crate::gamestream::config_dir().join("logs");
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
// DACL-locked (Users read-only, no create) so a local user can't pre-plant SYSTEM log files as
|
||||
// reparse points / hardlinks to redirect the SYSTEM service's writes (security-review #11).
|
||||
let _ = crate::gamestream::create_private_dir(&dir);
|
||||
dir.join("service.log")
|
||||
}
|
||||
|
||||
fn host_log_path() -> PathBuf {
|
||||
let dir = crate::gamestream::config_dir().join("logs");
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let _ = crate::gamestream::create_private_dir(&dir);
|
||||
dir.join("host.log")
|
||||
}
|
||||
|
||||
@@ -684,7 +686,9 @@ fn ensure_default_host_env() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(dir) = path.parent() {
|
||||
std::fs::create_dir_all(dir).ok();
|
||||
// DACL-lock the config dir on creation so a local user can't pre-create it and plant a
|
||||
// host.env (which feeds the SYSTEM service's env + command line) — security-review #3.
|
||||
crate::gamestream::create_private_dir(dir).ok();
|
||||
}
|
||||
let default = "# punktfunk host configuration (read by the Windows service).\n\
|
||||
# KEY=VALUE per line; '#' comments. Restart the service after editing:\n\
|
||||
@@ -707,7 +711,11 @@ fn ensure_default_host_env() -> Result<()> {
|
||||
\n\
|
||||
# Force a specific render GPU by name substring (multi-GPU boxes only):\n\
|
||||
# PUNKTFUNK_RENDER_ADAPTER=4090\n";
|
||||
std::fs::write(&path, default).with_context(|| format!("write {}", path.display()))?;
|
||||
// Write host.env DACL-locked to SYSTEM/Administrators: it controls the SYSTEM service's
|
||||
// environment + launched command line, so a local user must not be able to read or tamper with
|
||||
// it (security-review 2026-06-28 #3).
|
||||
crate::gamestream::write_secret_file(&path, default.as_bytes())
|
||||
.with_context(|| format!("write {}", path.display()))?;
|
||||
println!("Wrote default config: {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user