TOCTOU and lifecycle races across runtime boundaries fixes

This commit is contained in:
Alexey
2026-09-19 22:37:22 +03:00
parent acad414cc7
commit 89dacbd17e
20 changed files with 194 additions and 24 deletions
+37 -10
View File
@@ -1,7 +1,7 @@
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::{self, ErrorKind, Read, Write};
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
#[cfg(target_os = "linux")]
use std::os::fd::{FromRawFd, OwnedFd};
use std::path::{Path, PathBuf};
@@ -42,7 +42,7 @@ impl FileIdentity {
impl PidFile {
/// Creates a new PID file manager for the given path.
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let path = path.as_ref().to_path_buf();
let path = normalize_pid_path(path.as_ref());
let lock_path = sibling_lock_path(&path);
Self {
path,
@@ -209,6 +209,33 @@ fn sibling_lock_path(path: &Path) -> PathBuf {
lock_path.into()
}
fn normalize_pid_path(path: &Path) -> PathBuf {
let legacy_run = Path::new("/var/run");
let Ok(remainder) = path.strip_prefix(legacy_run) else {
return path.to_path_buf();
};
let Ok(var_metadata) = fs::metadata("/var") else {
return path.to_path_buf();
};
let Ok(link_metadata) = fs::symlink_metadata(legacy_run) else {
return path.to_path_buf();
};
let Ok(target) = fs::read_link(legacy_run) else {
return path.to_path_buf();
};
let trusted_var = var_metadata.is_dir()
&& var_metadata.uid() == 0
&& var_metadata.permissions().mode() & 0o022 == 0;
let trusted_alias = link_metadata.file_type().is_symlink()
&& link_metadata.uid() == 0
&& (target == Path::new("/run") || target == Path::new("../run"));
if trusted_var && trusted_alias {
Path::new("/run").join(remainder)
} else {
path.to_path_buf()
}
}
fn open_file_at(
anchor: &AnchoredPath,
name: &OsStr,
@@ -343,8 +370,8 @@ fn validate_regular_single_link(
/// Reads a PID from a PID file.
#[allow(dead_code)]
pub fn read_pid_file<P: AsRef<Path>>(path: P) -> Result<i32, DaemonError> {
let path = path.as_ref();
read_pid_file_if_exists(path)?.ok_or_else(|| {
let path = normalize_pid_path(path.as_ref());
read_pid_file_if_exists(&path)?.ok_or_else(|| {
DaemonError::PidFile(format!(
"cannot read {}: file does not exist",
path.display()
@@ -358,11 +385,11 @@ pub fn signal_pid_file<P: AsRef<Path>>(
path: P,
signal: nix::sys::signal::Signal,
) -> Result<(), DaemonError> {
let path = path.as_ref();
let pid = read_pid_file(path)?;
let path = normalize_pid_path(path.as_ref());
let pid = read_pid_file(&path)?;
#[cfg(target_os = "linux")]
let pidfd = open_pidfd(pid)?;
if !daemon_lock_is_held(path)? {
if !daemon_lock_is_held(&path)? {
return Err(DaemonError::PidFile(format!(
"refusing to signal unlocked or stale PID file {}",
path.display()
@@ -390,10 +417,10 @@ pub enum DaemonStatus {
/// Checks daemon status without modifying the PID or lock file.
#[allow(dead_code)]
pub fn check_status<P: AsRef<Path>>(path: P) -> DaemonStatus {
let path = path.as_ref();
match read_pid_file_if_exists(path) {
let path = normalize_pid_path(path.as_ref());
match read_pid_file_if_exists(&path) {
Ok(Some(pid))
if daemon_lock_is_held(path).unwrap_or(false) && is_process_running(pid) =>
if daemon_lock_is_held(&path).unwrap_or(false) && is_process_running(pid) =>
{
DaemonStatus::Running(pid)
}
+19
View File
@@ -38,6 +38,25 @@ fn pid_file_remains_send_and_sync() {
assert_send_sync::<PidFile>();
}
#[test]
fn system_var_run_alias_keeps_the_default_pid_path_usable() {
let Ok(metadata) = fs::symlink_metadata("/var/run") else {
return;
};
let Ok(target) = fs::read_link("/var/run") else {
return;
};
if !metadata.file_type().is_symlink()
|| (target != Path::new("/run") && target != Path::new("../run"))
{
return;
}
let pid_file = PidFile::new("/var/run/telemt.pid");
assert_eq!(pid_file.path(), Path::new("/run/telemt.pid"));
}
#[test]
fn lock_holder_subprocess() {
let Some(pid_path) = std::env::var_os(HELPER_PID_PATH) else {