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
+14 -5
View File
@@ -3,6 +3,8 @@ use std::process::Command;
use rand::RngExt;
use crate::util::trusted_command::resolve_trusted_helper;
/// Options for the fire-and-forget init command.
#[derive(Debug, Clone)]
pub struct InitOptions {
@@ -165,11 +167,14 @@ pub fn run_init(opts: InitOptions) -> Result<(), Box<dyn std::error::Error>> {
eprintln!("[+] Service started");
std::thread::sleep(std::time::Duration::from_secs(1));
let status = Command::new("systemctl")
.args(["is-active", "telemt.service"])
.output();
let status = resolve_trusted_helper("systemctl").and_then(|command_path| {
Command::new(command_path)
.args(["is-active", "telemt.service"])
.output()
.ok()
});
match status {
Ok(out) if out.status.success() => {
Some(out) if out.status.success() => {
eprintln!("[+] Service is running");
}
_ => {
@@ -329,7 +334,11 @@ weight = 10
}
fn run_cmd(cmd: &str, args: &[&str]) {
match Command::new(cmd).args(args).output() {
let Some(command_path) = resolve_trusted_helper(cmd) else {
eprintln!("[!] Refusing unavailable or untrusted command: {}", cmd);
return;
};
match Command::new(command_path).args(args).output() {
Ok(output) => {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
+2
View File
@@ -117,12 +117,14 @@ impl UserAuthSnapshot {
self.entries.get(idx)
}
/// Returns the stable credential identity for an exact configured username.
pub(crate) fn credential_id_by_name(&self, user: &str) -> Option<[u8; 16]> {
self.user_id_by_name(user)
.and_then(|user_id| self.entry_by_id(user_id))
.map(|entry| entry.credential_id)
}
/// Returns every bounded authentication candidate sharing a stable hint key.
pub(crate) fn candidate_ids_by_hint_key(&self, hint_key: u64) -> Option<&[u32]> {
self.by_hint_key.get(&hint_key).map(Vec::as_slice)
}
@@ -4,6 +4,7 @@ use std::path::Path;
use super::*;
/// Builds a bounded static-site snapshot on platforms without directory descriptors.
pub(super) fn load_static_site_by_path(
root: &Path,
limits: &WebLimitsConfig,
+8
View File
@@ -13,6 +13,7 @@ use crate::util::trusted_command::resolve_trusted_helper;
use super::{ConntrackRuntimeSupport, NetfilterBackend};
/// Reconciles kernel NOTRACK rules with the active listener policy.
pub(super) async fn reconcile_rules(
cfg: &ProxyConfig,
runtime_support: ConntrackRuntimeSupport,
@@ -45,6 +46,7 @@ pub(super) async fn reconcile_rules(
}
}
/// Probes the effective firewall backend and conntrack deletion capability.
pub(super) fn probe_runtime_support(
configured_backend: ConntrackBackend,
) -> ConntrackRuntimeSupport {
@@ -55,6 +57,7 @@ pub(super) fn probe_runtime_support(
}
}
/// Resolves whether conntrack close publication is usable for this runtime.
pub(super) fn effective_conntrack_enabled(
cfg: &ProxyConfig,
runtime_support: ConntrackRuntimeSupport,
@@ -317,12 +320,17 @@ async fn clear_notrack_rules_all_backends() {
let _ = run_command("ip6tables", &["-t", "raw", "-X", "TELEMT_NOTRACK"], None).await;
}
/// Result of one best-effort kernel conntrack deletion.
pub(super) enum DeleteOutcome {
/// The kernel reported successful deletion.
Deleted,
/// No matching conntrack entry existed.
NotFound,
/// The helper was unavailable or returned an unexpected failure.
Error,
}
/// Deletes the exact TCP tuple represented by one close event.
pub(super) async fn delete_conntrack_entry(event: ConntrackCloseEvent) -> DeleteOutcome {
if !command_exists("conntrack") {
return DeleteOutcome::Error;
+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 {
+9
View File
@@ -12,6 +12,7 @@ use crate::transport::middle_proxy::{
use super::print_maestro_line;
/// Prints configured MTProxy links through the direct MAESTRO output channel.
pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) {
print_maestro_line(format!("Proxy links ({host})"));
for user_name in config
@@ -94,6 +95,7 @@ pub(crate) fn print_web_proxy_links(config: &ProxyConfig) {
}
}
/// Durably replaces one Beobachten snapshot without following Unix symlinks.
pub(crate) async fn write_beobachten_snapshot(path: &str, payload: &str) -> std::io::Result<()> {
#[cfg(unix)]
{
@@ -115,10 +117,12 @@ pub(crate) async fn write_beobachten_snapshot(path: &str, payload: &str) -> std:
}
}
/// Selects a singular or plural display label for one integer value.
pub(crate) fn unit_label(value: u64, singular: &'static str, plural: &'static str) -> &'static str {
if value == 1 { singular } else { plural }
}
/// Formats process uptime into bounded human-readable units and exact seconds.
pub(crate) fn format_uptime(total_secs: u64) -> String {
const SECS_PER_MINUTE: u64 = 60;
const SECS_PER_HOUR: u64 = 60 * SECS_PER_MINUTE;
@@ -172,6 +176,7 @@ pub(crate) fn format_uptime(total_secs: u64) -> String {
}
#[allow(dead_code)]
/// Waits until admission opens or its watch channel closes.
pub(crate) async fn wait_until_admission_open(admission_rx: &mut watch::Receiver<bool>) -> bool {
loop {
if *admission_rx.borrow() {
@@ -183,10 +188,12 @@ pub(crate) async fn wait_until_admission_open(admission_rx: &mut watch::Receiver
}
}
/// Classifies peer closure that is expected during an incomplete handshake.
pub(crate) fn is_expected_handshake_eof(err: &crate::error::ProxyError) -> bool {
expected_handshake_close_description(err).is_some()
}
/// Returns a stable diagnostic description for transport-level peer closure.
pub(crate) fn peer_close_description(err: &crate::error::ProxyError) -> Option<&'static str> {
fn from_kind(kind: std::io::ErrorKind) -> Option<&'static str> {
match kind {
@@ -209,6 +216,7 @@ pub(crate) fn peer_close_description(err: &crate::error::ProxyError) -> Option<&
}
}
/// Returns a stable diagnostic description for expected handshake closure.
pub(crate) fn expected_handshake_close_description(
err: &crate::error::ProxyError,
) -> Option<&'static str> {
@@ -243,6 +251,7 @@ pub(crate) fn expected_handshake_close_description(
}
}
/// Loads a non-empty startup endpoint snapshot with bounded cache fallback.
pub(crate) async fn load_startup_proxy_config_snapshot(
url: &str,
cache_path: Option<&str>,
+1
View File
@@ -2,6 +2,7 @@ use std::fmt::Write;
use crate::transport::middle_proxy::MeApiHardswapSnapshot;
/// Renders fixed-cardinality hardswap and writer-replacement gauges.
pub(super) fn render(
out: &mut String,
snapshot: Option<&MeApiHardswapSnapshot>,
+3 -5
View File
@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::ffi::OsString;
#[cfg(all(test, unix))]
use std::fs::OpenOptions;
use std::io::Write;
use std::net::SocketAddr;
@@ -33,7 +34,7 @@ use nix::fcntl::{Flock, FlockArg, OFlag, openat};
#[cfg(unix)]
use nix::sys::stat::Mode;
#[cfg(unix)]
#[cfg(all(test, unix))]
use std::os::unix::fs::OpenOptionsExt;
// Direct relay lifecycle and conntrack publication.
@@ -178,10 +179,7 @@ fn open_unknown_dc_log_append_anchored(
) -> std::io::Result<std::fs::File> {
#[cfg(unix)]
{
let parent = OpenOptions::new()
.read(true)
.custom_flags(libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC)
.open(&path.allowed_parent)?;
let parent = crate::util::secure_fs::open_dir_nofollow(&path.allowed_parent)?;
let oflags = OFlag::O_CREAT
| OFlag::O_APPEND
+15
View File
@@ -1,5 +1,6 @@
use super::*;
/// Records one deterministic authentication failure against an isolated shared state.
pub(crate) fn auth_probe_record_failure_for_testing(
shared: &ProxySharedState,
peer_ip: IpAddr,
@@ -8,6 +9,7 @@ pub(crate) fn auth_probe_record_failure_for_testing(
auth_probe_record_failure_in(shared, peer_ip, now);
}
/// Returns the normalized peer failure streak from an isolated shared state.
pub(crate) fn auth_probe_fail_streak_for_testing_in_shared(
shared: &ProxySharedState,
peer_ip: IpAddr,
@@ -20,6 +22,7 @@ pub(crate) fn auth_probe_fail_streak_for_testing_in_shared(
.map(|entry| entry.fail_streak)
}
/// Clears probe entries, exact capacity accounting, and saturation state together.
pub(crate) fn clear_auth_probe_state_for_testing_in_shared(shared: &ProxySharedState) {
let removed = shared.handshake.auth_probe.len();
assert_eq!(shared.handshake.auth_probe_slots.used(), removed);
@@ -37,6 +40,7 @@ pub(crate) fn clear_auth_probe_state_for_testing_in_shared(shared: &ProxySharedS
}
}
/// Inserts one fixture entry while preserving exact registry capacity accounting.
pub(crate) fn insert_auth_probe_state_for_testing_in_shared(
shared: &ProxySharedState,
peer_ip: IpAddr,
@@ -59,22 +63,26 @@ pub(crate) fn insert_auth_probe_state_for_testing_in_shared(
}
}
/// Exposes the isolated probe registry to adversarial tests.
pub(crate) fn auth_probe_state_for_testing_in_shared(
shared: &ProxySharedState,
) -> &DashMap<IpAddr, AuthProbeState> {
&shared.handshake.auth_probe
}
/// Returns exact committed probe slots for capacity assertions.
pub(crate) fn auth_probe_slots_for_testing_in_shared(shared: &ProxySharedState) -> usize {
shared.handshake.auth_probe_slots.used()
}
/// Exposes the isolated saturation state mutex to tests.
pub(crate) fn auth_probe_saturation_state_for_testing_in_shared(
shared: &ProxySharedState,
) -> &Mutex<Option<AuthProbeSaturationState>> {
&shared.handshake.auth_probe_saturation
}
/// Locks isolated saturation state while recovering poisoned test fixtures.
pub(crate) fn auth_probe_saturation_state_lock_for_testing_in_shared(
shared: &ProxySharedState,
) -> std::sync::MutexGuard<'_, Option<AuthProbeSaturationState>> {
@@ -85,6 +93,7 @@ pub(crate) fn auth_probe_saturation_state_lock_for_testing_in_shared(
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
/// Resets the isolated unknown-SNI warning rate limiter.
pub(crate) fn clear_unknown_sni_warn_state_for_testing_in_shared(shared: &ProxySharedState) {
let mut guard = shared
.handshake
@@ -94,6 +103,7 @@ pub(crate) fn clear_unknown_sni_warn_state_for_testing_in_shared(shared: &ProxyS
*guard = None;
}
/// Evaluates unknown-SNI warning admission at a deterministic instant.
pub(crate) fn should_emit_unknown_sni_warn_for_testing_in_shared(
shared: &ProxySharedState,
now: Instant,
@@ -101,18 +111,21 @@ pub(crate) fn should_emit_unknown_sni_warn_for_testing_in_shared(
should_emit_unknown_sni_warn_in(shared, now)
}
/// Clears the isolated invalid-secret warning deduplication set.
pub(crate) fn clear_warned_secrets_for_testing_in_shared(shared: &ProxySharedState) {
if let Ok(mut guard) = shared.handshake.invalid_secret_warned.lock() {
guard.clear();
}
}
/// Exposes the isolated invalid-secret warning set to tests.
pub(crate) fn warned_secrets_for_testing_in_shared(
shared: &ProxySharedState,
) -> &Mutex<HashSet<(String, String)>> {
&shared.handshake.invalid_secret_warned
}
/// Evaluates peer throttling against the current test clock.
pub(crate) fn auth_probe_is_throttled_for_testing_in_shared(
shared: &ProxySharedState,
peer_ip: IpAddr,
@@ -120,12 +133,14 @@ pub(crate) fn auth_probe_is_throttled_for_testing_in_shared(
auth_probe_is_throttled_in(shared, peer_ip, Instant::now())
}
/// Evaluates global saturation throttling against the current test clock.
pub(crate) fn auth_probe_saturation_is_throttled_for_testing_in_shared(
shared: &ProxySharedState,
) -> bool {
auth_probe_saturation_is_throttled_in(shared, Instant::now())
}
/// Evaluates global saturation throttling at a deterministic instant.
pub(crate) fn auth_probe_saturation_is_throttled_at_for_testing_in_shared(
shared: &ProxySharedState,
now: Instant,
+8
View File
@@ -56,17 +56,25 @@ pub(crate) enum ConntrackClosePolicy {
pub(crate) struct HandshakeSharedState {
pub(crate) auth_probe: DashMap<IpAddr, AuthProbeState>,
/// Exact capacity authority for the authentication probe registry.
pub(crate) auth_probe_slots: SlotBudget,
pub(crate) auth_probe_saturation: Mutex<Option<AuthProbeSaturationState>>,
pub(crate) auth_probe_eviction_hasher: RandomState,
pub(crate) invalid_secret_warned: Mutex<HashSet<(String, String)>>,
pub(crate) unknown_sni_warn_next_allowed: Mutex<Option<Instant>>,
/// Stable credential hints keyed by exact peer IP.
pub(crate) sticky_user_by_ip: DashMap<IpAddr, u64>,
/// Exact capacity authority for peer-IP credential hints.
pub(crate) sticky_user_by_ip_slots: SlotBudget,
/// Stable credential hints keyed by bounded peer network prefix.
pub(crate) sticky_user_by_ip_prefix: DashMap<u64, u64>,
/// Exact capacity authority for peer-prefix credential hints.
pub(crate) sticky_user_by_ip_prefix_slots: SlotBudget,
/// Stable credential hints keyed by normalized SNI hash.
pub(crate) sticky_user_by_sni_hash: DashMap<u64, u64>,
/// Exact capacity authority for SNI credential hints.
pub(crate) sticky_user_by_sni_hash_slots: SlotBudget,
/// Bounded recent credential-hint ring used as an authentication fallback.
pub(crate) recent_user_ring: Box<[AtomicU64]>,
pub(crate) recent_user_ring_seq: AtomicU64,
pub(crate) auth_expensive_checks_total: AtomicU64,
@@ -72,6 +72,51 @@ fn adversarial_parent_swap_after_check_is_blocked_by_anchored_open() {
);
}
#[cfg(unix)]
#[test]
fn adversarial_intermediate_parent_swap_is_blocked_by_component_walk() {
use std::os::unix::fs::symlink;
let directory = tempfile::tempdir().expect("temporary directory must be creatable");
let parent = directory.path().join("parent");
let moved = directory.path().join("moved");
let outside = directory.path().join("outside");
fs::create_dir_all(parent.join("nested"))
.expect("original nested directory must be creatable");
fs::create_dir_all(outside.join("nested"))
.expect("outside nested directory must be creatable");
let candidate = parent.join("nested/unknown-dc.log");
let sanitized = sanitize_unknown_dc_log_path(
candidate
.to_str()
.expect("temporary path must be valid UTF-8"),
)
.expect("candidate must sanitize before intermediate parent swap");
assert!(
unknown_dc_log_path_is_still_safe(&sanitized),
"precondition: target should initially pass revalidation"
);
fs::rename(&parent, &moved).expect("intermediate parent must be movable");
symlink(&outside, &parent).expect("intermediate parent symlink must be creatable");
let err = open_unknown_dc_log_append_anchored(&sanitized)
.expect_err("anchored open must reject a swapped intermediate component");
let raw = err.raw_os_error();
assert!(
matches!(
raw,
Some(libc::ELOOP) | Some(libc::ENOTDIR) | Some(libc::ENOENT)
),
"component walk must fail closed on intermediate swap, got raw_os_error={raw:?}"
);
assert!(
!outside.join("nested/unknown-dc.log").exists(),
"component walk must not create a log through a swapped intermediate directory"
);
}
#[cfg(unix)]
#[test]
fn anchored_open_nix_path_writes_expected_lines() {
+2 -1
View File
@@ -1115,7 +1115,8 @@ async fn tls_unknown_sni_reject_handshake_policy_emits_unrecognized_name_alert()
// Drain what the server wrote. We expect exactly one TLS alert record:
// 0x15 0x03 0x03 0x00 0x02 0x02 0x70
// (ContentType.alert, TLS 1.2, length=2, fatal, unrecognized_name)
drop(result); // drops the server-side writer so peer_side sees EOF
// Drop the server-side writer so `peer_side` observes EOF.
drop(result);
let mut buf = Vec::new();
peer_side.read_to_end(&mut buf).await.unwrap();
assert_eq!(
+11
View File
@@ -266,16 +266,22 @@ pub struct RoutingCore {
pub(super) writers: Arc<WritersState>,
pub(super) rr: AtomicU64,
pub(super) writer_epoch: watch::Sender<u64>,
/// Coherent immutable authority for endpoint maps and reverse indexes.
pub(super) endpoint_snapshot: ArcSwap<EndpointSnapshot>,
}
/// Immutable endpoint routing authority published as one coherent revision.
#[derive(Clone, Debug)]
pub(super) struct EndpointSnapshot {
/// Monotonic revision covering every endpoint-derived index in this snapshot.
pub(super) revision: u64,
/// IPv4 endpoint map by Telegram DC.
pub(super) map_v4: HashMap<i32, Vec<(IpAddr, u16)>>,
/// IPv6 endpoint map by Telegram DC.
pub(super) map_v6: HashMap<i32, Vec<(IpAddr, u16)>>,
/// Reverse lookup from an endpoint to its optional Telegram DC.
pub(super) endpoint_dc_map: HashMap<SocketAddr, Option<i32>>,
/// Ordered endpoint candidates used for per-DC writer selection.
pub(super) preferred_endpoints_by_dc: HashMap<i32, Vec<SocketAddr>>,
}
@@ -312,6 +318,7 @@ pub(super) struct ReinitPendingState {
pub(super) generation: u64,
pub(super) started_at_epoch_secs: u64,
pub(super) map_hash: u64,
/// Endpoint authority revision targeted by the pending generation.
pub(super) endpoint_revision: u64,
}
@@ -319,6 +326,7 @@ pub(super) struct ReinitPendingState {
pub(super) struct ReinitAttemptState {
pub(super) generation: u64,
pub(super) map_hash: u64,
/// Endpoint authority revision captured by this attempt.
pub(super) endpoint_revision: u64,
pub(super) hardswap: bool,
pub(super) committed: bool,
@@ -328,6 +336,7 @@ pub(super) struct ReinitCoordinatorState {
pub(super) next_attempt_id: u64,
pub(super) active_generation: u64,
pub(super) desired_map_hash: u64,
/// Latest endpoint authority revision accepted by the coordinator.
pub(super) endpoint_revision: u64,
pub(super) pending: Option<ReinitPendingState>,
pub(super) attempts: HashMap<u64, ReinitAttemptState>,
@@ -492,8 +501,10 @@ pub struct MePool {
pub(super) next_writer_id: AtomicU64,
pub(super) writer_connect_active_reserved: AtomicUsize,
pub(super) writer_connect_warm_reserved: AtomicUsize,
/// Replacement connections opened but not yet committed to writer visibility.
pub(super) writer_replacement_open_reserved: AtomicUsize,
pub(super) rtt_stats: Arc<Mutex<HashMap<u64, (f64, f64)>>>,
/// Coalesced refill state keyed by exact generation and contour ownership.
pub(super) refill_states: Arc<ParkingMutex<HashMap<RefillTargetKey, RefillTargetState>>>,
pub(super) refill_running: AtomicUsize,
pub(super) refill_pending: AtomicUsize,
@@ -144,6 +144,7 @@ impl MePool {
}
}
/// Builds all endpoint-derived indexes under one immutable revision.
pub(in crate::transport::middle_proxy) fn build_endpoint_snapshot(
decision: &NetworkDecision,
mut map_v4: HashMap<i32, Vec<(IpAddr, u16)>>,
@@ -272,6 +273,7 @@ impl MePool {
endpoint_dc_map
}
/// Removes runtime endpoint state absent from the current coherent snapshot.
pub(in crate::transport::middle_proxy) async fn prune_endpoint_runtime_state(&self) {
let configured_endpoints = self
.endpoint_snapshot
@@ -307,6 +307,7 @@ impl MePool {
self.desired_dc_endpoints_from_snapshot(&endpoint_snapshot)
}
/// Projects desired per-DC endpoint sets from one immutable endpoint revision.
pub(super) fn desired_dc_endpoints_from_snapshot(
&self,
endpoint_snapshot: &EndpointSnapshot,
@@ -30,6 +30,7 @@ impl MePool {
self.api_hardswap_snapshot_for_reinit(reinit.as_ref()).await
}
/// Builds the bounded projection from one coherent reinitialization snapshot.
pub(super) async fn api_hardswap_snapshot_for_reinit(
&self,
reinit: &ReinitStatusSnapshot,
+1 -1
View File
@@ -9,7 +9,7 @@ mod write;
pub(crate) use path::{
AnchoredPath, chdir_nofollow_or_create, open_dir_nofollow,
open_dir_nofollow_or_create, open_trusted_dir_nofollow_or_create,
open_trusted_dir_nofollow_or_create,
};
pub(crate) use write::{
atomic_replace, atomic_replace_async, open_append_regular, open_append_regular_at,
+2 -1
View File
@@ -182,10 +182,11 @@ fn validate_trusted_directory(descriptor: &OwnedFd, allow_sticky_parent: bool) -
/// Creates missing components and changes cwd to the exact opened directory inode.
pub(crate) fn chdir_nofollow_or_create(path: &Path, mode: u32) -> io::Result<()> {
let descriptor = open_or_create_dir_nofollow(path, mode)?;
let descriptor = open_dir_nofollow_or_create(path, mode)?;
nix::unistd::fchdir(&descriptor).map_err(errno_to_io)
}
/// Converts one `nix` errno without discarding its platform error code.
pub(super) fn errno_to_io(error: nix::errno::Errno) -> io::Error {
io::Error::from_raw_os_error(error as i32)
}
+12 -1
View File
@@ -2,7 +2,18 @@ use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};
const TRUSTED_HELPER_DIRS: [&str; 4] = ["/usr/sbin", "/usr/bin", "/sbin", "/bin"];
const TRUSTED_HELPERS: [&str; 5] = ["nft", "iptables", "ip6tables", "conntrack", "pfctl"];
const TRUSTED_HELPERS: [&str; 10] = [
"nft",
"iptables",
"ip6tables",
"conntrack",
"pfctl",
"systemctl",
"rc-update",
"rc-service",
"sysrc",
"service",
];
/// Resolves a privileged helper only through the fixed system allowlist.
pub(crate) fn resolve_trusted_helper(binary: &str) -> Option<PathBuf> {