@@ -140,7 +140,7 @@ impl TryFrom<&Sandbox> for Confinement {
140140 if !sandbox. extra_deny_syscalls . is_empty ( ) { unsupported. push ( "extra_deny_syscalls" ) ; }
141141 if !sandbox. net_allow . is_empty ( ) { unsupported. push ( "net_allow" ) ; }
142142 if !sandbox. net_deny . is_empty ( ) { unsupported. push ( "net_deny" ) ; }
143- if !sandbox. net_allow_bind . is_empty ( ) { unsupported. push ( "net_allow_bind" ) ; }
143+ if !sandbox. net_allow_bind . is_default ( ) { unsupported. push ( "net_allow_bind" ) ; }
144144 if !sandbox. net_deny_bind . is_empty ( ) { unsupported. push ( "net_deny_bind" ) ; }
145145 if sandbox. allows_sysv_ipc ( ) { unsupported. push ( "extra_allow_syscalls=[\" sysv_ipc\" ]" ) ; }
146146 if !sandbox. http_allow . is_empty ( ) { unsupported. push ( "http_allow" ) ; }
@@ -281,6 +281,34 @@ enum RuntimeState {
281281 Stopped ( crate :: result:: ExitStatus ) ,
282282}
283283
284+ /// TCP bind allowlist (`--net-allow-bind`).
285+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
286+ pub enum BindPorts {
287+ /// Allow binding only the listed ports. Empty means no bind is
288+ /// permitted while the NetTcp protection is active (the default).
289+ Ports ( Vec < u16 > ) ,
290+ /// `--net-allow-bind '*'`: any TCP port may be bound.
291+ All ,
292+ }
293+
294+ impl Default for BindPorts {
295+ fn default ( ) -> Self {
296+ BindPorts :: Ports ( Vec :: new ( ) )
297+ }
298+ }
299+
300+ impl BindPorts {
301+ /// True when no allowlist was configured (the default deny-all state).
302+ pub fn is_default ( & self ) -> bool {
303+ matches ! ( self , BindPorts :: Ports ( p) if p. is_empty( ) )
304+ }
305+
306+ /// True for the `'*'` wildcard (any port may be bound).
307+ pub fn is_all ( & self ) -> bool {
308+ matches ! ( self , BindPorts :: All )
309+ }
310+ }
311+
284312/// Sandbox configuration.
285313#[ derive( Serialize , Deserialize ) ]
286314pub struct Sandbox {
@@ -333,8 +361,10 @@ pub struct Sandbox {
333361 /// Mutually exclusive with `net_allow`.
334362 pub net_deny : Vec < NetDeny > ,
335363 /// `--net-allow-bind`: TCP ports the sandbox may bind (default-deny
336- /// allowlist, Landlock-enforced). Mutually exclusive with `net_deny_bind`.
337- pub net_allow_bind : Vec < u16 > ,
364+ /// allowlist, Landlock-enforced; `All` leaves Landlock's `BIND_TCP`
365+ /// hook unhandled so any port may be bound). Mutually exclusive with
366+ /// `net_deny_bind`.
367+ pub net_allow_bind : BindPorts ,
338368 /// `--net-deny-bind`: TCP ports the sandbox may NOT bind (default-allow
339369 /// denylist, enforced on the on-behalf `bind()` path). Mutually
340370 /// exclusive with `net_allow_bind`.
@@ -2332,6 +2362,23 @@ fn validate_syscall_names(names: &[String]) -> Result<(), SandboxError> {
23322362 }
23332363}
23342364
2365+ /// Parse `--net-allow-bind` specs. Accepts the `*` wildcard (any port),
2366+ /// which cannot be combined with port lists; repeating the bare wildcard
2367+ /// is idempotent.
2368+ fn parse_allow_bind_ports ( specs : & [ String ] , label : & str ) -> Result < BindPorts , SandboxError > {
2369+ let mut parts = specs. iter ( ) . flat_map ( |s| s. split ( ',' ) ) . map ( str:: trim) ;
2370+ if !parts. clone ( ) . any ( |part| part == "*" ) {
2371+ return Ok ( BindPorts :: Ports ( parse_bind_ports ( specs, label) ?) ) ;
2372+ }
2373+ if !parts. all ( |part| part == "*" ) {
2374+ return Err ( SandboxError :: Invalid ( format ! (
2375+ "{}: wildcard `*` cannot be combined with port lists" ,
2376+ label
2377+ ) ) ) ;
2378+ }
2379+ Ok ( BindPorts :: All )
2380+ }
2381+
23352382/// Expand `--net-allow-bind` specs into a sorted, deduplicated port list.
23362383/// Each spec is a comma-separated list of single ports (`8080`) or inclusive
23372384/// `lo-hi` ranges (`8000-8010`). Mirrors the Python SDK's `parse_ports`.
@@ -2346,6 +2393,12 @@ fn parse_bind_ports(specs: &[String], label: &str) -> Result<Vec<u16>, SandboxEr
23462393 label, spec
23472394 ) ) ) ;
23482395 }
2396+ if part == "*" {
2397+ return Err ( SandboxError :: Invalid ( format ! (
2398+ "{}: wildcard `*` is only supported for --net-allow-bind" ,
2399+ label
2400+ ) ) ) ;
2401+ }
23492402 match part. split_once ( '-' ) {
23502403 Some ( ( lo, hi) ) => {
23512404 let lo: u16 = lo. trim ( ) . parse ( ) . map_err ( |_| {
0 commit comments