mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-30 07:33:59 +08:00
refactor(sandbox): defer dmesg hardening
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
This commit is contained in:
@@ -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.
|
||||
/// <https://github.com/NVIDIA/OpenShell/pull/2715>
|
||||
pub const FORBIDDEN_WORKSPACE_ROOTS: &[&str] = &[
|
||||
// Kernel-managed OCI runtime mounts.
|
||||
"/proc",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user