mirror of
https://github.com/telemt/telemt.git
synced 2026-09-28 21:45:57 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 226f9443e1 |
+5
-6
@@ -1,9 +1,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
|
|
||||||
use crate::util::trusted_command::resolve_trusted_helper;
|
use crate::util::trusted_command::trusted_helper_command;
|
||||||
|
|
||||||
/// Options for the fire-and-forget init command.
|
/// Options for the fire-and-forget init command.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -167,8 +166,8 @@ pub fn run_init(opts: InitOptions) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
eprintln!("[+] Service started");
|
eprintln!("[+] Service started");
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
let status = resolve_trusted_helper("systemctl").and_then(|command_path| {
|
let status = trusted_helper_command("systemctl").and_then(|mut command| {
|
||||||
Command::new(command_path)
|
command
|
||||||
.args(["is-active", "telemt.service"])
|
.args(["is-active", "telemt.service"])
|
||||||
.output()
|
.output()
|
||||||
.ok()
|
.ok()
|
||||||
@@ -334,11 +333,11 @@ weight = 10
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_cmd(cmd: &str, args: &[&str]) {
|
fn run_cmd(cmd: &str, args: &[&str]) {
|
||||||
let Some(command_path) = resolve_trusted_helper(cmd) else {
|
let Some(mut command) = trusted_helper_command(cmd) else {
|
||||||
eprintln!("[!] Refusing unavailable or untrusted command: {}", cmd);
|
eprintln!("[!] Refusing unavailable or untrusted command: {}", cmd);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match Command::new(command_path).args(args).output() {
|
match command.args(args).output() {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use tokio::io::AsyncWriteExt;
|
|||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use crate::util::trusted_command::resolve_trusted_helper;
|
use crate::util::trusted_command::{resolve_trusted_helper, trusted_helper_command};
|
||||||
|
|
||||||
const COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
|
const COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
@@ -133,13 +133,13 @@ impl FirewallCommandRunner for SystemCommandRunner {
|
|||||||
}
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
let Some(command_path) = resolve_trusted_helper(spec.binary) else {
|
let Some(command) = trusted_helper_command(spec.binary) else {
|
||||||
return Err(CommandError {
|
return Err(CommandError {
|
||||||
kind: CommandErrorKind::Missing,
|
kind: CommandErrorKind::Missing,
|
||||||
message: format!("{} is not available", spec.binary),
|
message: format!("{} is not available", spec.binary),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let mut command = Command::new(command_path);
|
let mut command = Command::from(command);
|
||||||
command.args(&spec.args);
|
command.args(&spec.args);
|
||||||
command.env("LC_ALL", "C");
|
command.env("LC_ALL", "C");
|
||||||
if spec.stdin.is_some() {
|
if spec.stdin.is_some() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::util::trusted_command::resolve_trusted_helper;
|
use crate::util::trusted_command::trusted_helper_command;
|
||||||
|
|
||||||
const COMMAND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
const COMMAND_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||||
|
|
||||||
@@ -10,10 +10,10 @@ pub(super) async fn run_command(
|
|||||||
args: &[&str],
|
args: &[&str],
|
||||||
stdin: Option<String>,
|
stdin: Option<String>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let Some(command_path) = resolve_trusted_helper(binary) else {
|
let Some(command) = trusted_helper_command(binary) else {
|
||||||
return Err(format!("{binary} is not available"));
|
return Err(format!("{binary} is not available"));
|
||||||
};
|
};
|
||||||
let mut command = Command::new(command_path);
|
let mut command = Command::from(command);
|
||||||
command.args(args);
|
command.args(args);
|
||||||
if stdin.is_some() {
|
if stdin.is_some() {
|
||||||
command.stdin(std::process::Stdio::piped());
|
command.stdin(std::process::Stdio::piped());
|
||||||
@@ -52,10 +52,10 @@ pub(super) async fn run_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn run_command_stdout(binary: &str, args: &[&str]) -> Result<String, String> {
|
pub(super) async fn run_command_stdout(binary: &str, args: &[&str]) -> Result<String, String> {
|
||||||
let Some(command_path) = resolve_trusted_helper(binary) else {
|
let Some(command) = trusted_helper_command(binary) else {
|
||||||
return Err(format!("{binary} is not available"));
|
return Err(format!("{binary} is not available"));
|
||||||
};
|
};
|
||||||
let mut command = Command::new(command_path);
|
let mut command = Command::from(command);
|
||||||
command.args(args).kill_on_drop(true);
|
command.args(args).kill_on_drop(true);
|
||||||
let output = tokio::time::timeout(COMMAND_TIMEOUT, command.output())
|
let output = tokio::time::timeout(COMMAND_TIMEOUT, command.output())
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
const TRUSTED_HELPER_DIRS: [&str; 4] = ["/usr/sbin", "/usr/bin", "/sbin", "/bin"];
|
const TRUSTED_HELPER_DIRS: [&str; 4] = ["/usr/sbin", "/usr/bin", "/sbin", "/bin"];
|
||||||
const TRUSTED_HELPERS: [&str; 12] = [
|
const TRUSTED_HELPERS: [&str; 12] = [
|
||||||
@@ -29,6 +31,19 @@ pub(crate) fn resolve_trusted_helper(binary: &str) -> Option<PathBuf> {
|
|||||||
.find_map(|candidate| trusted_executable(&candidate))
|
.find_map(|candidate| trusted_executable(&candidate))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds a trusted helper command with its allowlisted invocation name.
|
||||||
|
pub(crate) fn trusted_helper_command(binary: &str) -> Option<Command> {
|
||||||
|
let command_path = resolve_trusted_helper(binary)?;
|
||||||
|
Some(command_for_resolved_helper(binary, command_path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_for_resolved_helper(binary: &str, command_path: PathBuf) -> Command {
|
||||||
|
let mut command = Command::new(command_path);
|
||||||
|
// Multi-call helpers dispatch from argv[0], which canonical path resolution discards.
|
||||||
|
command.arg0(binary);
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
fn trusted_executable(candidate: &Path) -> Option<PathBuf> {
|
fn trusted_executable(candidate: &Path) -> Option<PathBuf> {
|
||||||
let canonical = std::fs::canonicalize(candidate).ok()?;
|
let canonical = std::fs::canonicalize(candidate).ok()?;
|
||||||
let metadata = std::fs::metadata(&canonical).ok()?;
|
let metadata = std::fs::metadata(&canonical).ok()?;
|
||||||
@@ -78,6 +93,27 @@ mod tests {
|
|||||||
assert!(!TRUSTED_HELPERS.contains(&"iptables-restore-wrapper"));
|
assert!(!TRUSTED_HELPERS.contains(&"iptables-restore-wrapper"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trusted_multicall_command_preserves_logical_argv0() {
|
||||||
|
let command_path = std::fs::canonicalize("/bin/sh").unwrap();
|
||||||
|
for binary in [
|
||||||
|
"iptables",
|
||||||
|
"ip6tables",
|
||||||
|
"iptables-restore",
|
||||||
|
"ip6tables-restore",
|
||||||
|
] {
|
||||||
|
let mut command = command_for_resolved_helper(binary, command_path.clone());
|
||||||
|
assert_eq!(command.get_program(), command_path.as_os_str());
|
||||||
|
|
||||||
|
let output = command
|
||||||
|
.args(["-c", "printf '%s' \"$0\""])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert_eq!(String::from_utf8(output.stdout).unwrap(), binary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn writable_executable_is_not_trusted() {
|
fn writable_executable_is_not_trusted() {
|
||||||
let directory = tempfile::tempdir().unwrap();
|
let directory = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user