From feb15b4dfed12c721d200eaece5d7bfc498da584 Mon Sep 17 00:00:00 2001 From: Matthew Grossman Date: Fri, 14 Aug 2026 09:50:33 -0700 Subject: [PATCH] refactor(sandbox): defer dmesg hardening Signed-off-by: Matthew Grossman --- crates/openshell-core/src/container_paths.rs | 1 + .../openshell-driver-podman/src/container.rs | 5 ++ .../src/bypass_monitor/mod.rs | 62 +++---------------- 3 files changed, 14 insertions(+), 54 deletions(-) diff --git a/crates/openshell-core/src/container_paths.rs b/crates/openshell-core/src/container_paths.rs index 6e41c3060..24a8a5880 100644 --- a/crates/openshell-core/src/container_paths.rs +++ b/crates/openshell-core/src/container_paths.rs @@ -25,6 +25,7 @@ pub const NETNS_IPROUTE2_ROOT: &str = "/var/run/netns"; /// /// TODO: Make the supervisor's helper functionality self-contained so it no /// longer depends on executable and library paths shared with the image. +/// pub const FORBIDDEN_WORKSPACE_ROOTS: &[&str] = &[ // Kernel-managed OCI runtime mounts. "/proc", diff --git a/crates/openshell-driver-podman/src/container.rs b/crates/openshell-driver-podman/src/container.rs index 15b7cc7ae..4c18d4a2a 100644 --- a/crates/openshell-driver-podman/src/container.rs +++ b/crates/openshell-driver-podman/src/container.rs @@ -1036,6 +1036,11 @@ pub fn build_container_spec_for_image( // /openshell-sandbox, so it appears at /opt/openshell/bin/openshell-sandbox. image_volumes, hostname: format!("sandbox-{}", sandbox.name), + // This is the supervisor's startup cwd, not the workload cwd. Starting + // at the image-selected workspace would let a malformed or inaccessible + // WORKDIR prevent the OCI runtime from launching the supervisor before + // it can validate the mounted workspace and report readiness. Direct and + // SSH children use the resolved workspace passed through `--workdir`. work_dir: "/".to_string(), // Override the image's ENTRYPOINT so the supervisor binary runs // directly. Sandbox images (e.g. the community base image) set diff --git a/crates/openshell-supervisor-process/src/bypass_monitor/mod.rs b/crates/openshell-supervisor-process/src/bypass_monitor/mod.rs index afb11fcca..44847b0d1 100644 --- a/crates/openshell-supervisor-process/src/bypass_monitor/mod.rs +++ b/crates/openshell-supervisor-process/src/bypass_monitor/mod.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -//! Bypass detection monitor — follows kernel log messages with `dmesg` to +//! Bypass detection monitor — reads kernel log messages from `/dev/kmsg` to //! detect and report direct connection attempts that bypass the HTTP CONNECT //! proxy. //! @@ -12,9 +12,9 @@ //! //! ## Graceful degradation //! -//! If an approved `dmesg` binary is unavailable, the monitor logs a one-time -//! warning and returns. The nftables reject rules still provide fast-fail UX — -//! the monitor only adds diagnostic visibility. +//! If `/dev/kmsg` cannot be opened (e.g., restricted container environment), +//! the monitor logs a one-time warning and returns. The nftables reject rules +//! still provide fast-fail UX — the monitor only adds diagnostic visibility. mod procfs; @@ -29,21 +29,7 @@ use std::sync::atomic::{AtomicU32, Ordering}; use tokio::sync::mpsc; use tracing::debug; -const DMESG_PATHS: &[&str] = &[ - "/usr/bin/dmesg", - "/bin/dmesg", - "/usr/sbin/dmesg", - "/sbin/dmesg", -]; - -fn find_dmesg_binary<'a>(candidates: &'a [&'a str]) -> Option<&'a str> { - candidates.iter().copied().find(|candidate| { - let path = std::path::Path::new(candidate); - path.is_absolute() && path.is_file() - }) -} - -/// A parsed nftables kernel log entry. +/// A parsed nftables log entry from `/dev/kmsg`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct BypassEvent { /// Destination IP address. @@ -195,23 +181,8 @@ pub fn spawn( use std::io::BufRead; use std::process::{Command, Stdio}; - // Use only known system locations. The sandbox image's PATH is controlled - // by the image author and may include the writable workspace. - let Some(dmesg_path) = find_dmesg_binary(DMESG_PATHS) else { - let event = NetworkActivityBuilder::new(openshell_ocsf::ctx::ctx()) - .activity(ActivityId::Other) - .severity(SeverityId::Low) - .message( - "dmesg not available at an approved system path; bypass detection monitor will not run. \ - Bypass REJECT rules still provide fast-fail behavior.", - ) - .build(); - ocsf_emit!(event); - return None; - }; - - // Verify dmesg works before spawning the monitor. - let dmesg_check = Command::new(dmesg_path) + // Verify dmesg is available before spawning the monitor. + let dmesg_check = Command::new("dmesg") .arg("--version") .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -238,7 +209,7 @@ pub fn spawn( let handle = tokio::task::spawn_blocking(move || { // Start dmesg in follow mode to tail new kernel messages. - let mut child = match Command::new(dmesg_path) + let mut child = match Command::new("dmesg") .args(["--follow", "--notime"]) .stdout(Stdio::piped()) .stderr(Stdio::null()) @@ -411,23 +382,6 @@ fn resolve_process_identity(entrypoint_pid: u32, src_port: u16) -> (String, Stri mod tests { use super::*; - #[test] - fn dmesg_lookup_uses_only_existing_absolute_candidates() { - let dir = tempfile::tempdir().unwrap(); - let binary = dir.path().join("dmesg"); - std::fs::write(&binary, "test").unwrap(); - let binary = binary.to_str().unwrap(); - - assert_eq!( - find_dmesg_binary(&["relative/dmesg", "/definitely/missing/dmesg", binary]), - Some(binary) - ); - assert_eq!( - find_dmesg_binary(&["relative/dmesg", "/definitely/missing/dmesg"]), - None - ); - } - #[test] fn parse_kmsg_line_tcp_bypass() { let line = "6,1234,5678,-;openshell:bypass:sandbox-abcd1234:IN= OUT=veth-s-abcd1234 \