Skip to content

Commit b67065b

Browse files
Add newCgroupNamespace option to isolate shell cgroup subtree (#98)
This allows to optionally make a PTY unshare to a new cgroup namespaces. This allows better isolation that prevents "breakout" attempts where a shell session attempts joining another cgroup scope out of its own subtree.
1 parent db9ac0a commit b67065b

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface PtyOptions {
3333
dir?: string;
3434
size?: Size;
3535
cgroupPath?: string;
36+
newCgroupNamespace?: boolean;
3637
apparmorProfile?: string;
3738
interactive?: boolean;
3839
sandbox?: SandboxOptions;

src/lib.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ struct PtyOptions {
6868
pub dir: Option<String>,
6969
pub size: Option<Size>,
7070
pub cgroup_path: Option<String>,
71+
pub new_cgroup_namespace: Option<bool>,
7172
pub apparmor_profile: Option<String>,
7273
pub interactive: Option<bool>,
7374
pub sandbox: Option<SandboxOptions>,
@@ -127,6 +128,22 @@ impl Pty {
127128
));
128129
}
129130

131+
#[cfg(not(target_os = "linux"))]
132+
if opts.new_cgroup_namespace.unwrap_or(false) {
133+
return Err(napi::Error::new(
134+
napi::Status::GenericFailure,
135+
"new_cgroup_namespace is only supported on Linux",
136+
));
137+
}
138+
139+
#[cfg(target_os = "linux")]
140+
if opts.new_cgroup_namespace.unwrap_or(false) && opts.cgroup_path.is_none() {
141+
return Err(napi::Error::new(
142+
napi::Status::GenericFailure,
143+
"cannot enable new_cgroup_namespace without cgroup_path",
144+
));
145+
}
146+
130147
#[cfg(target_os = "linux")]
131148
if opts.sandbox.is_some() && opts.cgroup_path.is_none() {
132149
return Err(napi::Error::new(
@@ -188,10 +205,19 @@ impl Pty {
188205
#[cfg(target_os = "linux")]
189206
if let Some(cgroup_path) = &opts.cgroup_path {
190207
let pid = libc::getpid();
191-
let cgroup_path = format!("{}/cgroup.procs", cgroup_path);
192-
let mut cgroup_file = File::create(cgroup_path)?;
208+
let cgroup_procs = format!("{}/cgroup.procs", cgroup_path);
209+
let mut cgroup_file = File::create(cgroup_procs)?;
193210
cgroup_file.write_all(format!("{}", pid).as_bytes())?;
194211

212+
// Unshare the cgroup namespace, rooting it at the scope we just joined.
213+
// This prevents the child from seeing or writing to any cgroup outside its own subtree.
214+
if opts.new_cgroup_namespace.unwrap_or(false) {
215+
let ret = libc::unshare(libc::CLONE_NEWCGROUP);
216+
if ret != 0 {
217+
return Err(Error::last_os_error());
218+
}
219+
}
220+
195221
// also set the sandbox if specified. It's important for it to be in a cgroup so that we don't
196222
// accidentally leak processes if something went wrong.
197223
if let Some(sandbox_opts) = &opts.sandbox {

0 commit comments

Comments
 (0)