11//! Building the `bwrap` command line, and the cgroup wrapper around it.
22//!
3- //! Pure functions, as with the Docker backend: what a sandbox binds, unshares,
4- //! and limits *is* the security boundary, so every one of those decisions
5- //! should be a value a test can assert on rather than something that only
6- //! happens at runtime.
3+ //! Command construction, as with the Docker backend: what a sandbox binds,
4+ //! unshares, and limits *is* the security boundary, so every one of those
5+ //! decisions should be a value a test can assert on rather than something that
6+ //! only happens at runtime. Runtime capability probes are kept separate from
7+ //! the pure argument builders.
78
89use std:: path:: Path ;
9-
10- use tinybox_core:: { BoxSpec , Error , ExecRequest , Resources , Result , WorkspaceSource } ;
10+ use tinybox_core:: { BoxSpec , Capability , Error , ExecRequest , Resources , Result , WorkspaceSource } ;
1111
1212use super :: NAME ;
1313
@@ -21,6 +21,7 @@ pub const WORKSPACE_MOUNT: &str = "/workspace";
2121/// user can read; this exposes the system's programs and libraries and nothing
2222/// else.
2323const USR : & str = "/usr" ;
24+ const SYSTEMD_EXPAND_ENVIRONMENT_FLAG : & [ u8 ] = b"--expand-environment" ;
2425
2526/// Files a normal command expects to exist.
2627///
@@ -72,15 +73,20 @@ fn unsupported(kind: &'static str) -> Error {
7273/// Returns [`Error::EmptyCommand`] when the request names no program, and
7374/// [`Error::UnsupportedWorkspaceSource`] when the spec names a source this
7475/// backend cannot bind.
75- pub ( super ) fn exec ( spec : & BoxSpec , request : & ExecRequest , limits : bool ) -> Result < Vec < String > > {
76+ pub ( super ) fn exec (
77+ spec : & BoxSpec ,
78+ request : & ExecRequest ,
79+ limits : bool ,
80+ supports_expand_environment : bool ,
81+ ) -> Result < Vec < String > > {
7682 if request. argv . is_empty ( ) {
7783 return Err ( Error :: EmptyCommand {
7884 sandbox : NAME . to_owned ( ) ,
7985 } ) ;
8086 }
8187
8288 let mut argv = if limits {
83- cgroup_prefix ( & spec. resources )
89+ cgroup_prefix ( & spec. resources , supports_expand_environment )
8490 } else {
8591 Vec :: new ( )
8692 } ;
@@ -95,8 +101,11 @@ pub(super) fn exec(spec: &BoxSpec, request: &ExecRequest, limits: bool) -> Resul
95101/// also why limits are opt-in: a machine with no systemd user session cannot
96102/// provide them, and a sandbox that accepted a memory cap it could not apply
97103/// would be claiming something untrue.
98- fn cgroup_prefix ( resources : & Resources ) -> Vec < String > {
99- vec ! [
104+ pub ( super ) fn cgroup_prefix (
105+ resources : & Resources ,
106+ supports_expand_environment : bool ,
107+ ) -> Vec < String > {
108+ let mut argv = vec ! [
100109 "systemd-run" . to_owned( ) ,
101110 "--user" . to_owned( ) ,
102111 "--scope" . to_owned( ) ,
@@ -120,7 +129,34 @@ fn cgroup_prefix(resources: &Resources) -> Vec<String> {
120129 "--property" . to_owned( ) ,
121130 format!( "TasksMax={}" , resources. pids_max) ,
122131 "--" . to_owned( ) ,
123- ]
132+ ] ;
133+ if supports_expand_environment {
134+ // The shell receives the command as an argument, but newer systemd
135+ // releases expand `${VAR}` references before the shell can interpret
136+ // its own quoting. Keep arbitrary user commands byte-for-byte intact.
137+ // This option was added in v254; older releases do not expand scope
138+ // command arguments by default, so omitting it preserves compatibility.
139+ argv. splice ( 3 ..3 , [ "--expand-environment=no" . to_owned ( ) ] ) ;
140+ }
141+ argv
142+ }
143+
144+ pub ( super ) fn parse_expand_environment_help (
145+ success : bool ,
146+ stdout : & [ u8 ] ,
147+ stderr : & [ u8 ] ,
148+ ) -> Result < bool > {
149+ if !success {
150+ return Err ( Error :: Unsupported {
151+ sandbox : NAME . to_owned ( ) ,
152+ capability : Capability :: ResourceLimits ,
153+ } ) ;
154+ }
155+ Ok ( [ stdout, stderr] . into_iter ( ) . any ( |stream| {
156+ stream
157+ . windows ( SYSTEMD_EXPAND_ENVIRONMENT_FLAG . len ( ) )
158+ . any ( |window| window == SYSTEMD_EXPAND_ENVIRONMENT_FLAG )
159+ } ) )
124160}
125161
126162/// The `bwrap` invocation itself.
0 commit comments