diff --git a/index.d.ts b/index.d.ts index 0142bcd..a52c3bd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -33,6 +33,7 @@ export interface PtyOptions { dir?: string; size?: Size; cgroupPath?: string; + newCgroupNamespace?: boolean; apparmorProfile?: string; interactive?: boolean; sandbox?: SandboxOptions; diff --git a/src/lib.rs b/src/lib.rs index 8397c50..c9584aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,6 +68,7 @@ struct PtyOptions { pub dir: Option, pub size: Option, pub cgroup_path: Option, + pub new_cgroup_namespace: Option, pub apparmor_profile: Option, pub interactive: Option, pub sandbox: Option, @@ -127,6 +128,22 @@ impl Pty { )); } + #[cfg(not(target_os = "linux"))] + if opts.new_cgroup_namespace.unwrap_or(false) { + return Err(napi::Error::new( + napi::Status::GenericFailure, + "new_cgroup_namespace is only supported on Linux", + )); + } + + #[cfg(target_os = "linux")] + if opts.new_cgroup_namespace.unwrap_or(false) && opts.cgroup_path.is_none() { + return Err(napi::Error::new( + napi::Status::GenericFailure, + "cannot enable new_cgroup_namespace without cgroup_path", + )); + } + #[cfg(target_os = "linux")] if opts.sandbox.is_some() && opts.cgroup_path.is_none() { return Err(napi::Error::new( @@ -188,10 +205,19 @@ impl Pty { #[cfg(target_os = "linux")] if let Some(cgroup_path) = &opts.cgroup_path { let pid = libc::getpid(); - let cgroup_path = format!("{}/cgroup.procs", cgroup_path); - let mut cgroup_file = File::create(cgroup_path)?; + let cgroup_procs = format!("{}/cgroup.procs", cgroup_path); + let mut cgroup_file = File::create(cgroup_procs)?; cgroup_file.write_all(format!("{}", pid).as_bytes())?; + // Unshare the cgroup namespace, rooting it at the scope we just joined. + // This prevents the child from seeing or writing to any cgroup outside its own subtree. + if opts.new_cgroup_namespace.unwrap_or(false) { + let ret = libc::unshare(libc::CLONE_NEWCGROUP); + if ret != 0 { + return Err(Error::last_os_error()); + } + } + // also set the sandbox if specified. It's important for it to be in a cgroup so that we don't // accidentally leak processes if something went wrong. if let Some(sandbox_opts) = &opts.sandbox {