From 55ac37392ea1cc1be658e6ddc7d4e607e9f9d289 Mon Sep 17 00:00:00 2001 From: Ovidiu Dan Date: Tue, 24 Jun 2025 17:11:40 -0700 Subject: [PATCH] feat: add rlimits (resource limits) support for sandboxes - Add rlimits field to sandbox configuration and CLI arguments - Support resource limits in format RLIMIT_NAME=soft:hard (e.g., RLIMIT_NOFILE=16384:65536) - Update API documentation with rlimits examples - Add rlimits support to MCP server - Extend server handler to process rlimits configuration - Update all relevant CLI binaries and configuration builders WARNING: I am not a Rust developer and this is vibe coded. It works, but it needs review. --- docs/references/api.md | 2 ++ microsandbox-cli/bin/msb/handlers.rs | 2 ++ microsandbox-cli/bin/msb/main.rs | 3 ++- microsandbox-cli/bin/msbrun.rs | 20 ++++++++++++++++++- microsandbox-cli/lib/args/msb.rs | 4 ++++ microsandbox-cli/lib/args/msbrun.rs | 8 ++++++++ .../lib/config/microsandbox/builder.rs | 11 ++++++++++ .../lib/config/microsandbox/config.rs | 5 +++++ microsandbox-core/lib/management/config.rs | 15 ++++++++++++++ microsandbox-core/lib/management/sandbox.rs | 5 +++++ microsandbox-server/lib/handler.rs | 12 +++++++++++ microsandbox-server/lib/mcp.rs | 5 +++++ microsandbox-server/lib/payload.rs | 6 ++++++ 13 files changed, 96 insertions(+), 2 deletions(-) diff --git a/docs/references/api.md b/docs/references/api.md index 1b0fa41e..7b56db01 100644 --- a/docs/references/api.md +++ b/docs/references/api.md @@ -118,6 +118,7 @@ Start a new sandbox with specified configuration. | `volumes` | `array[string]` | No | Volume mounts (format: `host:container`) | | `ports` | `array[string]` | No | Port mappings (format: `host:container`) | | `envs` | `array[string]` | No | Environment variables (format: `KEY=VALUE`) | +| `rlimits` | `array[string]` | No | Resource limits (format: `RLIMIT_NAME=soft:hard`, e.g., `RLIMIT_NOFILE=16384:65536` for file handles, `RLIMIT_NPROC=2048:4096` for processes/threads) | | `depends_on` | `array[string]` | No | Dependencies on other sandboxes | | `workdir` | `string` | No | Working directory | | `shell` | `string` | No | Shell to use | @@ -137,6 +138,7 @@ Start a new sandbox with specified configuration. "memory": 1024, "cpus": 2, "envs": ["DEBUG=true"], + "rlimits": ["RLIMIT_NOFILE=16384:65536", "RLIMIT_NPROC=2048:4096"], "workdir": "/workspace" } }, diff --git a/microsandbox-cli/bin/msb/handlers.rs b/microsandbox-cli/bin/msb/handlers.rs index ce795510..3f7e722b 100644 --- a/microsandbox-cli/bin/msb/handlers.rs +++ b/microsandbox-cli/bin/msb/handlers.rs @@ -58,6 +58,7 @@ pub async fn add_subcommand( volumes: Vec, ports: Vec, envs: Vec, + rlimits: Vec, env_file: Option, depends_on: Vec, workdir: Option, @@ -89,6 +90,7 @@ pub async fn add_subcommand( volumes, ports, envs, + rlimits, env_file, depends_on, workdir, diff --git a/microsandbox-cli/bin/msb/main.rs b/microsandbox-cli/bin/msb/main.rs index 023a5348..ae41a4d4 100644 --- a/microsandbox-cli/bin/msb/main.rs +++ b/microsandbox-cli/bin/msb/main.rs @@ -48,6 +48,7 @@ async fn main() -> MicrosandboxCliResult<()> { volumes, ports, envs, + rlimits, env_file, depends_on, workdir, @@ -61,7 +62,7 @@ async fn main() -> MicrosandboxCliResult<()> { }) => { let (path, config) = handlers::parse_file_path(file); handlers::add_subcommand( - sandbox, build, group, names, image, memory, cpus, volumes, ports, envs, env_file, + sandbox, build, group, names, image, memory, cpus, volumes, ports, envs, rlimits, env_file, depends_on, workdir, shell, scripts, start, imports, exports, scope, path, config, ) .await?; diff --git a/microsandbox-cli/bin/msbrun.rs b/microsandbox-cli/bin/msbrun.rs index ec97f526..f280d6e6 100644 --- a/microsandbox-cli/bin/msbrun.rs +++ b/microsandbox-cli/bin/msbrun.rs @@ -63,7 +63,7 @@ use microsandbox_cli::{McrunArgs, McrunSubcommand}; use microsandbox_core::{ config::{EnvPair, PathPair, PortPair}, runtime::MicroVmMonitor, - vm::{MicroVm, Rootfs}, + vm::{LinuxRlimit, MicroVm, Rootfs}, }; use microsandbox_utils::runtime::Supervisor; @@ -86,6 +86,7 @@ async fn main() -> Result<()> { workdir_path, exec_path, env, + rlimit, mapped_dir, port_map, scope, @@ -103,6 +104,7 @@ async fn main() -> Result<()> { tracing::debug!("workdir_path: {:#?}", workdir_path); tracing::debug!("exec_path: {:#?}", exec_path); tracing::debug!("env: {:#?}", env); + tracing::debug!("rlimit: {:#?}", rlimit); tracing::debug!("mapped_dir: {:#?}", mapped_dir); tracing::debug!("port_map: {:#?}", port_map); tracing::debug!("scope: {:#?}", scope); @@ -139,6 +141,9 @@ async fn main() -> Result<()> { // Parse environment variables let env: Vec = env.iter().map(|s| s.parse()).collect::>()?; + // Parse resource limits + let rlimit: Vec = rlimit.iter().map(|s| s.parse()).collect::>()?; + // Create and configure MicroVM let mut builder = MicroVm::builder().rootfs(rootfs).exec_path(exec_path); @@ -192,6 +197,11 @@ async fn main() -> Result<()> { builder = builder.env(env); } + // Set rlimits if provided + if !rlimit.is_empty() { + builder = builder.rlimits(rlimit); + } + // Set args if provided if !args.is_empty() { builder = builder.args(args.iter().map(|s| s.as_str())); @@ -218,6 +228,7 @@ async fn main() -> Result<()> { workdir_path, exec_path, env, + rlimit, mapped_dir, port_map, scope, @@ -296,6 +307,13 @@ async fn main() -> Result<()> { } } + // Set rlimits if provided + if !rlimit.is_empty() { + for rlimit in rlimit { + child_args.push(format!("--rlimit={}", rlimit)); + } + } + // Set mapped dirs if provided if !mapped_dir.is_empty() { for dir in mapped_dir { diff --git a/microsandbox-cli/lib/args/msb.rs b/microsandbox-cli/lib/args/msb.rs index f4a7a050..8133e24c 100644 --- a/microsandbox-cli/lib/args/msb.rs +++ b/microsandbox-cli/lib/args/msb.rs @@ -96,6 +96,10 @@ pub enum MicrosandboxSubcommand { #[arg(long = "env", name = "ENV")] envs: Vec, + /// Resource limits, format: =: + #[arg(long = "rlimit", name = "RLIMIT")] + rlimits: Vec, + /// Environment file #[arg(long)] env_file: Option, diff --git a/microsandbox-cli/lib/args/msbrun.rs b/microsandbox-cli/lib/args/msbrun.rs index cb75e747..fad95f2d 100644 --- a/microsandbox-cli/lib/args/msbrun.rs +++ b/microsandbox-cli/lib/args/msbrun.rs @@ -55,6 +55,10 @@ pub enum McrunSubcommand { #[arg(long)] env: Vec, + /// Resource limits (RLIMIT_RESOURCE=SOFT:HARD format) + #[arg(long)] + rlimit: Vec, + /// Directory mappings (host:guest format) #[arg(long)] mapped_dir: Vec, @@ -139,6 +143,10 @@ pub enum McrunSubcommand { #[arg(long)] env: Vec, + /// Resource limits (RLIMIT_RESOURCE=SOFT:HARD format) + #[arg(long)] + rlimit: Vec, + /// Directory mappings (host:guest format) #[arg(long)] mapped_dir: Vec, diff --git a/microsandbox-core/lib/config/microsandbox/builder.rs b/microsandbox-core/lib/config/microsandbox/builder.rs index bf72c83b..30e5f0a7 100644 --- a/microsandbox-core/lib/config/microsandbox/builder.rs +++ b/microsandbox-core/lib/config/microsandbox/builder.rs @@ -6,6 +6,7 @@ use typed_path::Utf8UnixPathBuf; use crate::{ config::{EnvPair, PathPair, PortPair, ReferenceOrPath}, + vm::LinuxRlimit, MicrosandboxResult, }; @@ -65,6 +66,7 @@ pub struct SandboxBuilder { volumes: Vec, ports: Vec, envs: Vec, + rlimits: Vec, env_file: Option, groups: HashMap, depends_on: Vec, @@ -155,6 +157,7 @@ impl SandboxBuilder { volumes: self.volumes, ports: self.ports, envs: self.envs, + rlimits: self.rlimits, env_file: self.env_file, groups: self.groups, depends_on: self.depends_on, @@ -198,6 +201,12 @@ impl SandboxBuilder { self } + /// Sets the resource limits for the sandbox + pub fn rlimits(mut self, rlimits: impl IntoIterator) -> SandboxBuilder { + self.rlimits = rlimits.into_iter().collect(); + self + } + /// Sets the environment file for the sandbox pub fn env_file(mut self, env_file: impl Into) -> SandboxBuilder { self.env_file = Some(env_file.into()); @@ -283,6 +292,7 @@ impl SandboxBuilder { volumes: self.volumes, ports: self.ports, envs: self.envs, + rlimits: self.rlimits, groups: self.groups, depends_on: self.depends_on, workdir: self.workdir, @@ -311,6 +321,7 @@ impl Default for SandboxBuilder<()> { volumes: Vec::new(), ports: Vec::new(), envs: Vec::new(), + rlimits: Vec::new(), env_file: None, groups: HashMap::new(), depends_on: Vec::new(), diff --git a/microsandbox-core/lib/config/microsandbox/config.rs b/microsandbox-core/lib/config/microsandbox/config.rs index 26cce688..f257a999 100644 --- a/microsandbox-core/lib/config/microsandbox/config.rs +++ b/microsandbox-core/lib/config/microsandbox/config.rs @@ -16,6 +16,7 @@ use typed_path::Utf8UnixPathBuf; use crate::{ config::{EnvPair, PathPair, PortPair, ReferenceOrPath}, + vm::LinuxRlimit, // Add LinuxRlimit import MicrosandboxError, MicrosandboxResult, }; @@ -289,6 +290,10 @@ pub struct Sandbox { #[serde(skip_serializing_if = "Vec::is_empty", default)] pub(crate) envs: Vec, + /// The resource limits to use. + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub(crate) rlimits: Vec, + /// The groups to run in. #[serde(skip_serializing_if = "HashMap::is_empty", default)] pub(crate) groups: HashMap, diff --git a/microsandbox-core/lib/management/config.rs b/microsandbox-core/lib/management/config.rs index 8b455be4..87641aa6 100644 --- a/microsandbox-core/lib/management/config.rs +++ b/microsandbox-core/lib/management/config.rs @@ -48,6 +48,9 @@ pub enum Component { /// The environment variables to use. envs: Vec, + /// The resource limits to use. + rlimits: Vec, + /// The environment file to use. env_file: Option, @@ -133,6 +136,7 @@ pub async fn add( volumes, ports, envs, + rlimits, env_file, depends_on, workdir, @@ -222,6 +226,17 @@ pub async fn add( } } + // Add rlimits if any + if !rlimits.is_empty() { + let mut rlimits_sequence = sandbox_mapping + .insert("rlimits", yaml::Separator::Auto) + .make_sequence(); + + for rlimit in rlimits { + rlimits_sequence.push_string(rlimit); + } + } + // Add env_file if provided if let Some(env_file_path) = env_file { sandbox_mapping.insert_str("env_file", env_file_path.to_string()); diff --git a/microsandbox-core/lib/management/sandbox.rs b/microsandbox-core/lib/management/sandbox.rs index a510a54a..64787b86 100644 --- a/microsandbox-core/lib/management/sandbox.rs +++ b/microsandbox-core/lib/management/sandbox.rs @@ -272,6 +272,11 @@ pub async fn prepare_run( command.arg("--env").arg(env.to_string()); } + // Rlimits + for rlimit in sandbox_config.get_rlimits() { + command.arg("--rlimit").arg(rlimit.to_string()); + } + // Ports for port in sandbox_config.get_ports() { command.arg("--port-map").arg(port.to_string()); diff --git a/microsandbox-server/lib/handler.rs b/microsandbox-server/lib/handler.rs index 1e8726e4..9e7f15f2 100644 --- a/microsandbox-server/lib/handler.rs +++ b/microsandbox-server/lib/handler.rs @@ -584,6 +584,18 @@ pub async fn sandbox_start_impl( ); } + if !config.rlimits.is_empty() { + let rlimits_array = config + .rlimits + .iter() + .map(|r| serde_yaml::Value::String(r.to_string())) + .collect::>(); + sandbox_map.insert( + serde_yaml::Value::String("rlimits".to_string()), + serde_yaml::Value::Sequence(rlimits_array), + ); + } + // Replace or add the sandbox in the config sandboxes_map.insert( serde_yaml::Value::String(sandbox.clone()), diff --git a/microsandbox-server/lib/mcp.rs b/microsandbox-server/lib/mcp.rs index 02890147..315ac1cf 100644 --- a/microsandbox-server/lib/mcp.rs +++ b/microsandbox-server/lib/mcp.rs @@ -121,6 +121,11 @@ pub async fn handle_mcp_list_tools( "type": "array", "items": {"type": "string"}, "description": "Environment variables" + }, + "rlimits": { + "type": "array", + "items": {"type": "string"}, + "description": "Resource limits in format RLIMIT_NAME=soft:hard (e.g., RLIMIT_NOFILE=16384:65536)" } } } diff --git a/microsandbox-server/lib/payload.rs b/microsandbox-server/lib/payload.rs index fafe7eb1..e7c728f2 100644 --- a/microsandbox-server/lib/payload.rs +++ b/microsandbox-server/lib/payload.rs @@ -12,6 +12,7 @@ //! - Success message formatting for sandbox operations //! - Detailed error information handling +use microsandbox_core::vm::LinuxRlimit; // Import LinuxRlimit use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -190,6 +191,11 @@ pub struct SandboxConfig { /// The exec command to run pub exec: Option, + + /// Resource limits for the sandbox process + #[serde(default)] + pub rlimits: Vec, + // SECURITY: Needs networking namespacing to be implemented // /// The network scope for the sandbox // pub scope: Option,