Skip to main content

scx_forge_agent/
sudo.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: GPL-2.0-only
3//! Resolve how the harness gains root to load the scheduler, and build the
4//! privileged commands. Ported from the Python harness's `setup_sudo`.
5//!
6//! Precedence:
7//!   1. already root            -> run directly, no sudo
8//!   2. `$SUDO_ASKPASS` set      -> `sudo -A` (use the caller's askpass)
9//!   3. `$SCX_SUDO_PASSWORD_FILE` -> generate an askpass shim that prints the
10//!      file's contents and use `sudo -A`; the password stays in the file and
11//!      never appears in argv or the process table
12//!   4. otherwise               -> `sudo -n` (passwordless / cached credentials)
13
14use std::os::unix::fs::PermissionsExt;
15use std::path::PathBuf;
16use std::process::Command;
17
18use anyhow::{Context, Result};
19
20/// A generated askpass shim, removed from disk when dropped (replaces atexit).
21struct TempShim {
22    path: PathBuf,
23}
24
25impl Drop for TempShim {
26    fn drop(&mut self) {
27        let _ = std::fs::remove_file(&self.path);
28    }
29}
30
31/// Resolved sudo strategy: the argv prefix plus any owned askpass shim.
32pub struct Sudo {
33    /// e.g. `[]` (root), `["sudo", "-A"]`, or `["sudo", "-n"]`.
34    prefix: Vec<String>,
35    _shim: Option<TempShim>,
36}
37
38/// Single-quote a path for safe embedding in a /bin/sh script.
39fn shell_quote(s: &str) -> String {
40    format!("'{}'", s.replace('\'', "'\\''"))
41}
42
43impl Sudo {
44    /// Decide the sudo strategy from euid and the environment.
45    pub fn resolve() -> Result<Sudo> {
46        if unsafe { libc::geteuid() } == 0 {
47            return Ok(Sudo {
48                prefix: Vec::new(),
49                _shim: None,
50            });
51        }
52        if std::env::var_os("SUDO_ASKPASS").is_some() {
53            return Ok(Sudo {
54                prefix: vec!["sudo".into(), "-A".into()],
55                _shim: None,
56            });
57        }
58        if let Some(pass_file) = std::env::var_os("SCX_SUDO_PASSWORD_FILE") {
59            let pf = PathBuf::from(&pass_file);
60            if !pf.is_file() {
61                anyhow::bail!("SCX_SUDO_PASSWORD_FILE not found: {}", pf.display());
62            }
63            let pf_abs = std::fs::canonicalize(&pf)
64                .with_context(|| format!("canonicalize {}", pf.display()))?;
65            // askpass helper: sudo runs this and reads the password from stdout.
66            let shim = std::env::temp_dir().join(format!("scx-askpass-{}.sh", std::process::id()));
67            std::fs::write(
68                &shim,
69                format!(
70                    "#!/bin/sh\nexec cat {}\n",
71                    shell_quote(&pf_abs.to_string_lossy())
72                ),
73            )
74            .with_context(|| format!("write askpass shim {}", shim.display()))?;
75            std::fs::set_permissions(&shim, std::fs::Permissions::from_mode(0o700))
76                .with_context(|| format!("chmod askpass shim {}", shim.display()))?;
77            std::env::set_var("SUDO_ASKPASS", &shim);
78            return Ok(Sudo {
79                prefix: vec!["sudo".into(), "-A".into()],
80                _shim: Some(TempShim { path: shim }),
81            });
82        }
83        Ok(Sudo {
84            prefix: vec!["sudo".into(), "-n".into()],
85            _shim: None,
86        })
87    }
88
89    /// Build a `Command` running `program` (with `args`) as root.
90    pub fn command(&self, program: &str, args: &[String]) -> Command {
91        if self.prefix.is_empty() {
92            let mut c = Command::new(program);
93            c.args(args);
94            c
95        } else {
96            let mut c = Command::new(&self.prefix[0]);
97            c.args(&self.prefix[1..]);
98            c.arg(program);
99            c.args(args);
100            c
101        }
102    }
103
104    /// Validate that sudo authenticates now (`sudo <prefix> -v`). No-op as root.
105    /// Returns Err with sudo's stderr on failure.
106    pub fn authenticate(&self) -> Result<()> {
107        if self.prefix.is_empty() {
108            return Ok(());
109        }
110        let out = Command::new(&self.prefix[0])
111            .args(&self.prefix[1..])
112            .arg("-v")
113            .output()
114            .context("spawn sudo -v")?;
115        if out.status.success() {
116            Ok(())
117        } else {
118            let err = String::from_utf8_lossy(&out.stderr);
119            anyhow::bail!("{}", err.trim());
120        }
121    }
122}