1#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
105
106pub(crate) const MAX_NAMESPACE_SOCKETS: usize = crate::egress_proxy::MAX_EGRESS_TCP_PORTS + 1;
108
109pub(crate) const MAX_SESSION_NETNS_FDS: usize = MAX_NAMESPACE_SOCKETS + 2;
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum CreationStep {
124 Unshare = 1,
125 IdentityMaps = 2,
126 Loopback = 3,
127 LocalRoute = 4,
128 TcpListener = 5,
129 Resolver = 6,
130 NamespaceHandles = 7,
131 HandOff = 8,
132}
133
134impl CreationStep {
135 const ALL: [CreationStep; 8] = [
136 Self::Unshare,
137 Self::IdentityMaps,
138 Self::Loopback,
139 Self::LocalRoute,
140 Self::TcpListener,
141 Self::Resolver,
142 Self::NamespaceHandles,
143 Self::HandOff,
144 ];
145
146 pub(crate) fn from_code(code: u32) -> Option<Self> {
148 Self::ALL.into_iter().find(|step| *step as u32 == code)
149 }
150
151 #[must_use]
153 pub fn describe(self) -> &'static str {
154 match self {
155 Self::Unshare => "unshare(CLONE_NEWUSER | CLONE_NEWNET)",
156 Self::IdentityMaps => "writing uid_map/gid_map",
157 Self::Loopback => "bringing lo up",
158 Self::LocalRoute => "installing the local default route",
159 Self::TcpListener => "binding a TCP listener inside the namespace",
160 Self::Resolver => "binding UDP 53 inside the namespace",
161 Self::NamespaceHandles => "opening /proc/self/ns/user and /proc/self/ns/net",
162 Self::HandOff => "handing the namespace to the runtime",
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub enum NamespaceAttemptError {
170 Step { step: CreationStep, errno: i32 },
172 Creator(String),
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
186#[serde(rename_all = "snake_case")]
187pub enum EgressNamespaceBlocker {
188 CapabilityGrantMissing,
193 KernelSupportMissing,
197 CreatorUnavailable,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct EgressNamespaceFailure {
211 pub blocker: EgressNamespaceBlocker,
213 pub failed_call: String,
218 pub errno: Option<i32>,
221 pub userns_grant: crate::sealed::UsernsGrant,
224 pub attachment: crate::sealed::ProfileAttachment,
226}
227
228impl EgressNamespaceFailure {
229 #[must_use]
232 pub fn summary(&self) -> String {
233 match self.errno {
234 Some(errno) => format!("{} failed with {}", self.failed_call, errno_label(errno)),
235 None => self.failed_call.clone(),
236 }
237 }
238
239 #[must_use]
243 pub fn reason(&self) -> String {
244 let what = match self.errno {
245 Some(errno) => format!(
246 "{} failed with {} ({})",
247 self.failed_call,
248 errno_label(errno),
249 errno_words(errno)
250 ),
251 None => self.failed_call.clone(),
252 };
253 format!("{what}. {} {}", self.cause(), self.readings())
254 }
255
256 fn step(&self) -> Option<CreationStep> {
258 self.errno?;
259 CreationStep::ALL
260 .into_iter()
261 .find(|step| step.describe() == self.failed_call)
262 }
263
264 fn cause(&self) -> String {
265 match self.blocker {
266 EgressNamespaceBlocker::KernelSupportMissing => kernel_cause(self.errno).to_string(),
267 EgressNamespaceBlocker::CreatorUnavailable => {
268 "Likely cause: the namespace creator could not run or stopped before reporting — \
269 a process or descriptor limit, or memory, ran out, or a policy killed it (its \
270 account then reads `killed by signal`) — so whether this host can create \
271 the namespace is unknown, and the capsule is refused rather than run without \
272 it. Re-run once the limit is relieved, or allow the creator's syscalls."
273 .to_string()
274 }
275 EgressNamespaceBlocker::CapabilityGrantMissing => {
276 let cause = grant_cause(self.userns_grant, &self.attachment);
277 match self.step().and_then(step_context) {
278 Some(context) => format!("{context} {cause}"),
279 None => cause,
280 }
281 }
282 }
283 }
284
285 fn readings(&self) -> String {
286 host_readings(self.userns_grant, &self.attachment)
287 }
288}
289
290pub(crate) fn host_readings(
294 userns_grant: crate::sealed::UsernsGrant,
295 attachment: &crate::sealed::ProfileAttachment,
296) -> String {
297 use crate::sealed::{ProfileAttachment, UsernsGrant};
298 let restriction = match userns_grant {
299 UsernsGrant::ApparmorAbsent => "AppArmor not enabled",
300 UsernsGrant::RestrictionDisabledHostWide => {
301 "kernel.apparmor_restrict_unprivileged_userns=0"
302 }
303 UsernsGrant::ProfileConfining | UsernsGrant::Withheld => {
304 "kernel.apparmor_restrict_unprivileged_userns=1"
305 }
306 };
307 let profile = match attachment {
308 ProfileAttachment::Sealed { profile } | ProfileAttachment::Other { profile } => {
309 profile.as_str()
310 }
311 ProfileAttachment::Unconfined => "unconfined",
312 ProfileAttachment::NotReported => "not reported",
313 };
314 format!("Host readings: {restriction}; AppArmor profile confining mur: {profile}.")
315}
316
317fn step_context(step: CreationStep) -> Option<&'static str> {
319 match step {
320 CreationStep::Unshare => None,
321 CreationStep::IdentityMaps => Some(
322 "The namespace was created and could not be owned: writing its identity map was \
323 refused.",
324 ),
325 CreationStep::Loopback | CreationStep::LocalRoute => Some(
326 "The namespace was created and owned, and configuring it was refused — the mark of a \
327 confinement that withholds CAP_NET_ADMIN inside the namespace.",
328 ),
329 CreationStep::TcpListener
330 | CreationStep::Resolver
331 | CreationStep::NamespaceHandles
332 | CreationStep::HandOff => {
333 Some("The namespace was created and configured, and handing it to the runtime failed.")
334 }
335 }
336}
337
338fn grant_cause(
345 userns_grant: crate::sealed::UsernsGrant,
346 attachment: &crate::sealed::ProfileAttachment,
347) -> String {
348 use crate::sealed::{
349 ProfileAttachment, UsernsGrant, SEALED_APPARMOR_PROFILE_NAME, SEALED_APPARMOR_PROFILE_PATH,
350 };
351 const CONTAINER_REMEDY: &str = "Inside a container, run it with a seccomp policy that \
352 permits unshare(CLONE_NEWUSER), or with `--cap-add SYS_ADMIN`, or create the network \
353 namespace outside the container and run mur inside it.";
354 match (userns_grant, attachment) {
355 (UsernsGrant::ApparmorAbsent, _) => format!(
356 "Likely cause: not AppArmor, which is not enabled on this host — a container \
357 runtime's seccomp filter or a missing CAP_SYS_ADMIN, or another security module's \
358 policy. {CONTAINER_REMEDY} On a bare host, check the policy of the security module \
359 in use."
360 ),
361 (_, ProfileAttachment::Other { profile }) => format!(
362 "Likely cause: the AppArmor profile `{profile}` confines mur. \
363 kernel.apparmor_restrict_unprivileged_userns does not govern a process another \
364 profile confines: that profile, and a container runtime's seccomp filter and \
365 capabilities, decide whether it may create a user namespace. Run the container with \
366 an AppArmor profile and seccomp policy that permit unshare(CLONE_NEWUSER), or with \
367 `--cap-add SYS_ADMIN`, or create the network namespace outside the container and run \
368 mur inside it."
369 ),
370 (UsernsGrant::ProfileConfining, _) | (_, ProfileAttachment::Sealed { .. }) => format!(
371 "Likely cause: the {SEALED_APPARMOR_PROFILE_NAME} AppArmor profile confines mur and \
372 the creation still failed, so the profile the kernel has loaded may be stale. Reload \
373 it with `sudo apparmor_parser -r {SEALED_APPARMOR_PROFILE_PATH}` (for a checkout \
374 build, re-run `scripts/install-dev-apparmor.sh`), and check `mur doctor`'s \
375 comparison of the installed profile with the one this build ships."
376 ),
377 (UsernsGrant::RestrictionDisabledHostWide, _) => format!(
378 "Likely cause: not AppArmor, whose unprivileged-userns restriction is off on this host \
379 — a container runtime's seccomp filter or a missing CAP_SYS_ADMIN, or another \
380 security module's policy. {CONTAINER_REMEDY}"
381 ),
382 (UsernsGrant::Withheld, _) => format!(
383 "Likely cause: AppArmor's unprivileged-userns restriction \
384 (kernel.apparmor_restrict_unprivileged_userns=1) applies to this binary, which no \
385 {SEALED_APPARMOR_PROFILE_NAME} profile confines. Install and load the profile shipped \
386 with mur, `sudo install -m 644 packaging/apparmor/{SEALED_APPARMOR_PROFILE_NAME} \
387 {SEALED_APPARMOR_PROFILE_PATH} && sudo apparmor_parser -r \
388 {SEALED_APPARMOR_PROFILE_PATH}` (or re-run the mur installer as root), then re-run. \
389 Building out of a checkout, where the binary sits at ./target/{{debug,release}}/mur \
390 and no shipped profile attaches to it: run `scripts/install-dev-apparmor.sh`, which \
391 generates and loads the same grant for those two paths. LAST RESORT, only where a \
392 profile genuinely cannot be loaded: `sudo sysctl -w \
393 kernel.apparmor_restrict_unprivileged_userns=0` — this removes unprivileged-userns \
394 hardening from every program on the machine, not just from mur, and is not the \
395 configuration murmur ships."
396 ),
397 }
398}
399
400fn kernel_cause(errno: Option<i32>) -> &'static str {
402 match errno {
403 Some(libc::ENOSPC) => {
404 "Likely cause: the kernel's limit on user namespaces — user.max_user_namespaces is 0, \
405 or every namespace it allows is in use. Raise it where it is set, on the host or in \
406 the user namespace mur runs in (`sudo sysctl -w user.max_user_namespaces=10000`), \
407 then re-run."
408 }
409 Some(libc::EUSERS) => {
410 "Likely cause: the kernel's nesting limit for user namespaces — mur already runs \
411 inside as many nested user namespaces as the kernel allows. Run it fewer levels \
412 deep."
413 }
414 _ => {
415 "Likely cause: this kernel does not provide user namespaces (built with \
416 CONFIG_USER_NS=n). Run mur on a kernel built with CONFIG_USER_NS=y."
417 }
418 }
419}
420
421pub(crate) fn errno_name(errno: i32) -> Option<&'static str> {
423 Some(match errno {
424 libc::EPERM => "EPERM",
425 libc::ENOENT => "ENOENT",
426 libc::EACCES => "EACCES",
427 libc::EINVAL => "EINVAL",
428 libc::ENOSPC => "ENOSPC",
429 libc::ENOSYS => "ENOSYS",
430 libc::EUSERS => "EUSERS",
431 libc::ENOMEM => "ENOMEM",
432 libc::EAGAIN => "EAGAIN",
433 libc::EADDRINUSE => "EADDRINUSE",
434 libc::EMFILE => "EMFILE",
435 libc::ENFILE => "ENFILE",
436 _ => return None,
437 })
438}
439
440pub(crate) fn errno_label(errno: i32) -> String {
442 errno_name(errno).map_or_else(|| format!("errno {errno}"), str::to_string)
443}
444
445pub(crate) fn errno_words(errno: i32) -> String {
447 let rendered = std::io::Error::from_raw_os_error(errno).to_string();
448 rendered
449 .strip_suffix(&format!(" (os error {errno})"))
450 .map_or_else(|| rendered.clone(), str::to_string)
451}
452
453pub(crate) fn egress_namespace_failure(
459 is_linux: bool,
460 attempt: Result<(), NamespaceAttemptError>,
461 userns_grant: crate::sealed::UsernsGrant,
462 attachment: crate::sealed::ProfileAttachment,
463) -> Option<EgressNamespaceFailure> {
464 if !is_linux {
469 return None;
470 }
471 attempt
472 .err()
473 .map(|error| failure_of(error, userns_grant, attachment))
474}
475
476fn failure_of(
478 error: NamespaceAttemptError,
479 userns_grant: crate::sealed::UsernsGrant,
480 attachment: crate::sealed::ProfileAttachment,
481) -> EgressNamespaceFailure {
482 let (blocker, failed_call, errno) = match error {
483 NamespaceAttemptError::Step { step, errno } => {
484 let blocker = match (step, errno) {
485 (
486 CreationStep::Unshare,
487 libc::ENOSPC | libc::EINVAL | libc::ENOSYS | libc::EUSERS,
488 ) => EgressNamespaceBlocker::KernelSupportMissing,
489 _ => EgressNamespaceBlocker::CapabilityGrantMissing,
490 };
491 (blocker, step.describe().to_string(), Some(errno))
492 }
493 NamespaceAttemptError::Creator(detail) => {
494 (EgressNamespaceBlocker::CreatorUnavailable, detail, None)
495 }
496 };
497 EgressNamespaceFailure {
498 blocker,
499 failed_call,
500 errno,
501 userns_grant,
502 attachment,
503 }
504}
505
506#[must_use]
514pub fn detect_egress_namespace_failure() -> Option<EgressNamespaceFailure> {
515 crate::sandbox::HostProbe::probe().egress_namespace_failure()
516}
517
518pub fn skip_without_egress_namespace(test_name: &str) -> bool {
529 match detect_egress_namespace_failure() {
530 Some(failure) => {
531 crate::runtime_err!("[SKIP-HOST] {test_name}: {}", failure.reason());
532 true
533 }
534 None => false,
535 }
536}
537
538pub fn check_egress_namespace(
549 can_spawn_subprocess: bool,
550 failure: Option<&EgressNamespaceFailure>,
551) -> Result<(), crate::errors::RuntimeError> {
552 match failure {
553 Some(failure) if can_spawn_subprocess => Err(unavailable(failure)),
554 _ => Ok(()),
555 }
556}
557
558fn unavailable(failure: &EgressNamespaceFailure) -> crate::errors::RuntimeError {
560 crate::errors::RuntimeError::EgressNamespaceUnavailable {
561 blocker: failure.blocker,
562 reason: failure.reason(),
563 }
564}
565
566fn session_netns_creation_error(
572 error: NamespaceAttemptError,
573 userns_grant: crate::sealed::UsernsGrant,
574 attachment: crate::sealed::ProfileAttachment,
575) -> String {
576 match error {
577 NamespaceAttemptError::Creator(detail) => {
578 format!("sandbox: could not create this session's network namespace: {detail}")
579 }
580 step => format!(
581 "sandbox: E-CAP-005: {}",
582 unavailable(&failure_of(step, userns_grant, attachment))
583 ),
584 }
585}
586
587#[derive(Debug, Clone)]
596pub(crate) struct CapsuleNetnsPlan {
597 pub(crate) tcp_ports: Vec<u16>,
604 pub(crate) uid: u32,
610 pub(crate) gid: u32,
611}
612
613impl CapsuleNetnsPlan {
614 #[cfg(target_os = "linux")]
616 #[allow(unsafe_code)]
617 pub(crate) fn resolve(tcp_ports: Vec<u16>) -> Self {
618 let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) };
620 Self {
621 tcp_ports,
622 uid,
623 gid,
624 }
625 }
626
627 pub(crate) fn socket_count(&self) -> usize {
629 self.tcp_ports.len() + 1
630 }
631}
632
633#[derive(Debug, Default)]
646pub(crate) struct SessionNetns {
647 state: std::sync::Mutex<SessionNetnsState>,
648 #[cfg(test)]
650 creations: std::sync::atomic::AtomicUsize,
651}
652
653#[derive(Debug, Default)]
654enum SessionNetnsState {
655 #[default]
656 NotYetCreated,
657 #[cfg(target_os = "linux")]
658 Established(linux::EstablishedNetns),
659 Ended,
660}
661
662#[cfg(target_os = "linux")]
669#[derive(Debug, Clone)]
670pub(crate) struct SessionNetnsJoin {
671 user_ns: std::sync::Arc<std::os::fd::OwnedFd>,
672 net_ns: std::sync::Arc<std::os::fd::OwnedFd>,
673}
674
675#[cfg(target_os = "linux")]
676impl SessionNetnsJoin {
677 pub(crate) fn raw_fds(&self) -> (std::os::fd::RawFd, std::os::fd::RawFd) {
679 use std::os::fd::AsRawFd;
680 (self.user_ns.as_raw_fd(), self.net_ns.as_raw_fd())
681 }
682}
683
684impl SessionNetns {
685 pub(crate) fn new() -> Self {
687 Self::default()
688 }
689
690 #[cfg(target_os = "linux")]
702 pub(crate) fn establish(
703 &self,
704 tcp_ports: &[u16],
705 policy: crate::egress_proxy::EgressPolicy,
706 ) -> Result<SessionNetnsJoin, String> {
707 let mut state = self
708 .state
709 .lock()
710 .unwrap_or_else(std::sync::PoisonError::into_inner);
711 match &*state {
712 SessionNetnsState::Established(established) => return Ok(established.join()),
713 SessionNetnsState::Ended => {
714 return Err(
715 "sandbox: this session has ended, and its network namespace was released \
716 with it; no further command runs in it"
717 .to_string(),
718 )
719 }
720 SessionNetnsState::NotYetCreated => {}
721 }
722 let plan = CapsuleNetnsPlan::resolve(tcp_ports.to_vec());
723 let created = linux::create_session_netns(&plan).map_err(|error| {
726 session_netns_creation_error(
727 error,
728 crate::sealed::userns_grant(),
729 crate::sealed::inspect_profile_attachment(),
730 )
731 })?;
732 let established = created.serve(policy).map_err(|detail| {
733 format!("sandbox: could not create this session's network namespace: {detail}")
734 })?;
735 let join = established.join();
736 *state = SessionNetnsState::Established(established);
737 #[cfg(test)]
738 self.creations
739 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
740 Ok(join)
741 }
742
743 pub(crate) fn end(&self) {
749 let previous = std::mem::replace(
750 &mut *self
751 .state
752 .lock()
753 .unwrap_or_else(std::sync::PoisonError::into_inner),
754 SessionNetnsState::Ended,
755 );
756 #[cfg(target_os = "linux")]
757 if let SessionNetnsState::Established(established) = previous {
758 established.release();
759 }
760 #[cfg(not(target_os = "linux"))]
761 drop(previous);
762 }
763
764 #[cfg(test)]
766 pub(crate) fn creations(&self) -> usize {
767 self.creations.load(std::sync::atomic::Ordering::SeqCst)
768 }
769}
770
771impl Drop for SessionNetns {
772 fn drop(&mut self) {
775 self.end();
776 }
777}
778
779pub(crate) const STEP_RECORD_LEN: usize = 8;
782
783pub(crate) fn encode_step_record(code: u32, errno: i32) -> [u8; STEP_RECORD_LEN] {
785 let mut record = [0u8; STEP_RECORD_LEN];
786 record[..4].copy_from_slice(&code.to_ne_bytes());
787 record[4..].copy_from_slice(&errno.to_ne_bytes());
788 record
789}
790
791pub(crate) fn decode_step_record(record: &[u8; STEP_RECORD_LEN]) -> (u32, i32) {
793 (
794 u32::from_ne_bytes([record[0], record[1], record[2], record[3]]),
795 i32::from_ne_bytes([record[4], record[5], record[6], record[7]]),
796 )
797}
798
799#[cfg(target_os = "linux")]
800pub(crate) use linux::{create_session_netns, join_session_netns, reap_child};
801
802#[cfg(target_os = "linux")]
803#[allow(unsafe_code)]
804mod linux {
805 use std::io;
806 use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
807 use std::sync::Arc;
808
809 use super::{
810 decode_step_record, encode_step_record, CapsuleNetnsPlan, CreationStep,
811 NamespaceAttemptError, SessionNetnsJoin, MAX_NAMESPACE_SOCKETS, MAX_SESSION_NETNS_FDS,
812 STEP_RECORD_LEN,
813 };
814
815 const RTM_NEWROUTE: u16 = 24;
822 const NLM_F_REQUEST: u16 = 0x001;
823 const NLM_F_ACK: u16 = 0x004;
824 const NLM_F_EXCL: u16 = 0x200;
825 const NLM_F_CREATE: u16 = 0x400;
826 const NLMSG_ERROR: u16 = 2;
827 const RT_TABLE_LOCAL: u8 = 255;
830 const RTPROT_BOOT: u8 = 3;
831 const RT_SCOPE_HOST: u8 = 254;
832 const RTN_LOCAL: u8 = 2;
833 const RTA_OIF: u16 = 4;
834 const LOOPBACK_IFINDEX: u32 = 1;
837
838 const NLMSGHDR_LEN: usize = 16;
839 const RTMSG_LEN: usize = 12;
840 const RTATTR_OIF_LEN: usize = 8;
841 const ROUTE_REQUEST_LEN: usize = NLMSGHDR_LEN + RTMSG_LEN + RTATTR_OIF_LEN;
842
843 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
849 pub(super) struct CreationFailure {
850 step: CreationStep,
851 errno: i32,
852 }
853
854 impl CreationFailure {
855 unsafe fn at(step: CreationStep) -> Self {
860 let errno = unsafe { *libc::__errno_location() };
863 Self { step, errno }
864 }
865
866 fn encode(self) -> [u8; STEP_RECORD_LEN] {
867 encode_step_record(self.step as u32, self.errno)
868 }
869
870 fn decode(record: &[u8; STEP_RECORD_LEN]) -> Option<Self> {
871 let (code, errno) = decode_step_record(record);
872 let step = CreationStep::from_code(code)?;
873 Some(Self { step, errno })
874 }
875 }
876
877 impl From<CreationFailure> for NamespaceAttemptError {
878 fn from(failure: CreationFailure) -> Self {
879 NamespaceAttemptError::Step {
880 step: failure.step,
881 errno: failure.errno,
882 }
883 }
884 }
885
886 unsafe fn create_capsule_netns(
902 plan: &CapsuleNetnsPlan,
903 sock_fd: RawFd,
904 ) -> Result<(), CreationFailure> {
905 unsafe {
907 if libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWNET) != 0 {
908 return Err(CreationFailure::at(CreationStep::Unshare));
909 }
910 if write_identity_maps(plan.uid, plan.gid).is_err() {
911 return Err(CreationFailure::at(CreationStep::IdentityMaps));
912 }
913 if bring_loopback_up().is_err() {
914 return Err(CreationFailure::at(CreationStep::Loopback));
915 }
916 if add_local_default_route().is_err() {
917 return Err(CreationFailure::at(CreationStep::LocalRoute));
918 }
919
920 let mut fds = [-1 as RawFd; MAX_SESSION_NETNS_FDS];
924 let mut count = 0usize;
925
926 for port in &plan.tcp_ports {
927 let fd = bind_tcp_listener(*port);
928 if fd < 0 {
929 let failure = CreationFailure::at(CreationStep::TcpListener);
930 close_all(&fds[..count]);
931 return Err(failure);
932 }
933 fds[count] = fd;
934 count += 1;
935 }
936
937 let resolver = bind_dns_socket();
938 if resolver < 0 {
939 let failure = CreationFailure::at(CreationStep::Resolver);
940 close_all(&fds[..count]);
941 return Err(failure);
942 }
943 fds[count] = resolver;
944 count += 1;
945
946 for namespace in [c"/proc/self/ns/user", c"/proc/self/ns/net"] {
948 let fd = libc::open(namespace.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC);
949 if fd < 0 {
950 let failure = CreationFailure::at(CreationStep::NamespaceHandles);
951 close_all(&fds[..count]);
952 return Err(failure);
953 }
954 fds[count] = fd;
955 count += 1;
956 }
957
958 let sent = send_fds(sock_fd, &fds[..count]);
959 let failure = CreationFailure::at(CreationStep::HandOff);
960 close_all(&fds[..count]);
961 sent.map_err(|()| failure)
962 }
963 }
964
965 pub(crate) unsafe fn join_session_netns(user_fd: RawFd, net_fd: RawFd) -> io::Result<()> {
977 unsafe {
980 if libc::setns(user_fd, libc::CLONE_NEWUSER) != 0 {
981 return Err(io::Error::last_os_error());
982 }
983 if libc::setns(net_fd, libc::CLONE_NEWNET) != 0 {
984 return Err(io::Error::last_os_error());
985 }
986 }
987 Ok(())
988 }
989
990 pub(crate) struct CreatedNetns {
993 sockets: Vec<OwnedFd>,
994 user_ns: OwnedFd,
995 net_ns: OwnedFd,
996 }
997
998 impl CreatedNetns {
999 pub(crate) fn namespace_handles(&self) -> (RawFd, RawFd) {
1001 (self.user_ns.as_raw_fd(), self.net_ns.as_raw_fd())
1002 }
1003
1004 pub(super) fn serve(
1009 self,
1010 policy: crate::egress_proxy::EgressPolicy,
1011 ) -> Result<EstablishedNetns, String> {
1012 let proxy = crate::egress_proxy::start_egress_proxy(self.sockets, policy)
1013 .map_err(|error| format!("starting the session's egress proxy failed: {error}"))?;
1014 Ok(EstablishedNetns {
1015 user_ns: Arc::new(self.user_ns),
1016 net_ns: Arc::new(self.net_ns),
1017 proxy,
1018 })
1019 }
1020 }
1021
1022 #[derive(Debug)]
1025 pub(super) struct EstablishedNetns {
1026 user_ns: Arc<OwnedFd>,
1027 net_ns: Arc<OwnedFd>,
1028 proxy: crate::egress_proxy::EgressProxyHandle,
1029 }
1030
1031 impl EstablishedNetns {
1032 pub(super) fn join(&self) -> SessionNetnsJoin {
1033 SessionNetnsJoin {
1034 user_ns: Arc::clone(&self.user_ns),
1035 net_ns: Arc::clone(&self.net_ns),
1036 }
1037 }
1038
1039 pub(super) fn release(self) {
1041 self.proxy.shutdown();
1042 }
1043 }
1044
1045 pub(crate) fn create_session_netns(
1055 plan: &CapsuleNetnsPlan,
1056 ) -> Result<CreatedNetns, NamespaceAttemptError> {
1057 if plan.socket_count() > MAX_NAMESPACE_SOCKETS {
1060 return Err(NamespaceAttemptError::Creator(format!(
1061 "{} namespace sockets requested, more than the {MAX_NAMESPACE_SOCKETS} one \
1062 hand-off carries",
1063 plan.socket_count()
1064 )));
1065 }
1066
1067 let mut pair = [0i32; 2];
1068 let rc = unsafe {
1070 libc::socketpair(
1071 libc::AF_UNIX,
1072 libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
1073 0,
1074 pair.as_mut_ptr(),
1075 )
1076 };
1077 if rc != 0 {
1078 return Err(NamespaceAttemptError::Creator(format!(
1079 "socketpair() for the namespace hand-off failed: {}",
1080 io::Error::last_os_error()
1081 )));
1082 }
1083 let (parent_end, creator_end) =
1086 unsafe { (OwnedFd::from_raw_fd(pair[0]), OwnedFd::from_raw_fd(pair[1])) };
1087 let creator_fd = creator_end.as_raw_fd();
1088
1089 let pid = unsafe { libc::fork() };
1095 if pid < 0 {
1096 return Err(NamespaceAttemptError::Creator(format!(
1097 "fork() for the namespace creator failed: {}",
1098 io::Error::last_os_error()
1099 )));
1100 }
1101 if pid == 0 {
1102 unsafe {
1105 match create_capsule_netns(plan, creator_fd) {
1106 Ok(()) => libc::_exit(0),
1107 Err(failure) => {
1108 let record = failure.encode();
1109 libc::send(creator_fd, record.as_ptr().cast(), record.len(), 0);
1110 libc::_exit(1);
1111 }
1112 }
1113 }
1114 }
1115 drop(creator_end);
1116
1117 let status = reap_child(pid).map_err(|error| {
1121 NamespaceAttemptError::Creator(format!(
1122 "waiting for the namespace creator failed: {error}"
1123 ))
1124 })?;
1125 match receive_session_netns(parent_end.as_raw_fd(), plan.socket_count())
1126 .map_err(NamespaceAttemptError::Creator)?
1127 {
1128 Received::Handles(mut fds) => {
1129 let net_ns = fds.pop().expect("the count was checked on receipt");
1130 let user_ns = fds.pop().expect("the count was checked on receipt");
1131 Ok(CreatedNetns {
1132 sockets: fds,
1133 user_ns,
1134 net_ns,
1135 })
1136 }
1137 Received::Failure(failure) => Err(failure.into()),
1138 Received::Nothing => Err(NamespaceAttemptError::Creator(format!(
1139 "the namespace creator exited without handing anything over ({status})"
1140 ))),
1141 }
1142 }
1143
1144 pub(crate) fn reap_child(pid: libc::pid_t) -> io::Result<String> {
1146 let mut status: libc::c_int = 0;
1147 loop {
1148 let waited = unsafe { libc::waitpid(pid, &mut status, 0) };
1150 if waited == pid {
1151 break;
1152 }
1153 let error = io::Error::last_os_error();
1154 if error.kind() != io::ErrorKind::Interrupted {
1155 return Err(error);
1156 }
1157 }
1158 Ok(if libc::WIFEXITED(status) {
1159 format!("exit status {}", libc::WEXITSTATUS(status))
1160 } else if libc::WIFSIGNALED(status) {
1161 format!("killed by signal {}", libc::WTERMSIG(status))
1162 } else {
1163 format!("wait status {status}")
1164 })
1165 }
1166
1167 #[cfg(test)]
1170 pub(super) fn decode_failure_record(step: u32, errno: i32) -> Option<NamespaceAttemptError> {
1171 let record = encode_step_record(step, errno);
1172 CreationFailure::decode(&record).map(|failure| {
1173 assert_eq!(failure.encode(), record);
1174 failure.into()
1175 })
1176 }
1177
1178 enum Received {
1180 Handles(Vec<OwnedFd>),
1182 Failure(CreationFailure),
1183 Nothing,
1184 }
1185
1186 unsafe fn write_identity_maps(uid: u32, gid: u32) -> Result<(), ()> {
1199 unsafe {
1201 let previous = crate::sealed::make_dumpable_for_map_writes();
1202 let _ = crate::sealed::write_decimal_map(c"/proc/self/setgroups", None, 0);
1203 let mapped = crate::sealed::write_decimal_map(c"/proc/self/uid_map", Some(uid), uid)
1204 .is_ok()
1205 && crate::sealed::write_decimal_map(c"/proc/self/gid_map", Some(gid), gid).is_ok();
1206 crate::sealed::restore_dumpable(previous);
1207 if mapped {
1208 Ok(())
1209 } else {
1210 Err(())
1211 }
1212 }
1213 }
1214
1215 unsafe fn bring_loopback_up() -> Result<(), ()> {
1224 unsafe {
1227 let sock = libc::socket(libc::AF_INET, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0);
1228 if sock < 0 {
1229 return Err(());
1230 }
1231 let mut ifr: libc::ifreq = std::mem::zeroed();
1232 ifr.ifr_name[0] = b'l' as libc::c_char;
1233 ifr.ifr_name[1] = b'o' as libc::c_char;
1234 if libc::ioctl(sock, libc::SIOCGIFFLAGS, &mut ifr) < 0 {
1235 libc::close(sock);
1236 return Err(());
1237 }
1238 ifr.ifr_ifru.ifru_flags |= (libc::IFF_UP | libc::IFF_RUNNING) as libc::c_short;
1239 let rc = libc::ioctl(sock, libc::SIOCSIFFLAGS, &ifr);
1240 libc::close(sock);
1241 if rc < 0 {
1242 return Err(());
1243 }
1244 Ok(())
1245 }
1246 }
1247
1248 unsafe fn add_local_default_route() -> Result<(), ()> {
1265 unsafe {
1268 let mut request = [0u8; ROUTE_REQUEST_LEN];
1269 request[0..4].copy_from_slice(&(ROUTE_REQUEST_LEN as u32).to_ne_bytes());
1270 request[4..6].copy_from_slice(&RTM_NEWROUTE.to_ne_bytes());
1271 request[6..8].copy_from_slice(
1272 &(NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL).to_ne_bytes(),
1273 );
1274 request[8..12].copy_from_slice(&1u32.to_ne_bytes()); request[12..16].copy_from_slice(&0u32.to_ne_bytes()); let rtm = NLMSGHDR_LEN;
1278 request[rtm] = libc::AF_INET as u8;
1279 request[rtm + 1] = 0; request[rtm + 2] = 0; request[rtm + 3] = 0; request[rtm + 4] = RT_TABLE_LOCAL;
1283 request[rtm + 5] = RTPROT_BOOT;
1284 request[rtm + 6] = RT_SCOPE_HOST;
1285 request[rtm + 7] = RTN_LOCAL;
1286 request[rtm + 8..rtm + 12].copy_from_slice(&0u32.to_ne_bytes()); let attr = rtm + RTMSG_LEN;
1289 request[attr..attr + 2].copy_from_slice(&(RTATTR_OIF_LEN as u16).to_ne_bytes());
1290 request[attr + 2..attr + 4].copy_from_slice(&RTA_OIF.to_ne_bytes());
1291 request[attr + 4..attr + 8].copy_from_slice(&LOOPBACK_IFINDEX.to_ne_bytes());
1292
1293 let sock = libc::socket(
1294 libc::AF_NETLINK,
1295 libc::SOCK_RAW | libc::SOCK_CLOEXEC,
1296 libc::NETLINK_ROUTE,
1297 );
1298 if sock < 0 {
1299 return Err(());
1300 }
1301 let mut address: libc::sockaddr_nl = std::mem::zeroed();
1302 address.nl_family = libc::AF_NETLINK as u16;
1303 let sent = libc::sendto(
1304 sock,
1305 request.as_ptr().cast(),
1306 ROUTE_REQUEST_LEN,
1307 0,
1308 std::ptr::addr_of!(address).cast(),
1309 std::mem::size_of::<libc::sockaddr_nl>() as libc::socklen_t,
1310 );
1311 if sent < 0 {
1312 libc::close(sock);
1313 return Err(());
1314 }
1315
1316 let mut response = [0u8; 256];
1320 let received = libc::recv(sock, response.as_mut_ptr().cast(), response.len(), 0);
1321 libc::close(sock);
1322 if received < (NLMSGHDR_LEN + 4) as isize {
1323 return Err(());
1324 }
1325 if u16::from_ne_bytes([response[4], response[5]]) == NLMSG_ERROR {
1326 let code = i32::from_ne_bytes([
1327 response[NLMSGHDR_LEN],
1328 response[NLMSGHDR_LEN + 1],
1329 response[NLMSGHDR_LEN + 2],
1330 response[NLMSGHDR_LEN + 3],
1331 ]);
1332 if code != 0 {
1334 return Err(());
1335 }
1336 }
1337 Ok(())
1338 }
1339 }
1340
1341 unsafe fn bind_tcp_listener(port: u16) -> RawFd {
1350 unsafe {
1352 let fd = libc::socket(libc::AF_INET, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0);
1353 if fd < 0 {
1354 return -1;
1355 }
1356 let mut address: libc::sockaddr_in = std::mem::zeroed();
1357 address.sin_family = libc::AF_INET as libc::sa_family_t;
1358 address.sin_port = port.to_be();
1359 address.sin_addr.s_addr = libc::INADDR_ANY.to_be();
1360 if libc::bind(
1361 fd,
1362 std::ptr::addr_of!(address).cast(),
1363 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
1364 ) != 0
1365 || libc::listen(fd, 128) != 0
1366 {
1367 libc::close(fd);
1368 return -1;
1369 }
1370 fd
1371 }
1372 }
1373
1374 unsafe fn bind_dns_socket() -> RawFd {
1387 unsafe {
1389 let fd = libc::socket(libc::AF_INET, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0);
1390 if fd < 0 {
1391 return -1;
1392 }
1393 let enable: libc::c_int = 1;
1394 if libc::setsockopt(
1395 fd,
1396 libc::IPPROTO_IP,
1397 libc::IP_PKTINFO,
1398 std::ptr::addr_of!(enable).cast(),
1399 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
1400 ) != 0
1401 {
1402 libc::close(fd);
1403 return -1;
1404 }
1405 let mut address: libc::sockaddr_in = std::mem::zeroed();
1406 address.sin_family = libc::AF_INET as libc::sa_family_t;
1407 address.sin_port = super::DNS_PORT_BE;
1408 address.sin_addr.s_addr = libc::INADDR_ANY.to_be();
1409 if libc::bind(
1410 fd,
1411 std::ptr::addr_of!(address).cast(),
1412 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
1413 ) != 0
1414 {
1415 libc::close(fd);
1416 return -1;
1417 }
1418 fd
1419 }
1420 }
1421
1422 unsafe fn close_all(fds: &[RawFd]) {
1425 for fd in fds {
1426 if *fd >= 0 {
1427 unsafe { libc::close(*fd) };
1429 }
1430 }
1431 }
1432
1433 const CMSG_BUFFER_LEN: usize = 128;
1435
1436 const _: () = assert!(
1442 unsafe {
1446 libc::CMSG_SPACE((MAX_SESSION_NETNS_FDS * std::mem::size_of::<RawFd>()) as u32) as usize
1447 } <= CMSG_BUFFER_LEN,
1448 "CMSG_BUFFER_LEN is too small to hold MAX_SESSION_NETNS_FDS descriptors plus the \
1449 cmsghdr; raise it to match"
1450 );
1451
1452 #[repr(C, align(8))]
1458 struct CmsgBuffer([u8; CMSG_BUFFER_LEN]);
1459
1460 unsafe fn send_fds(sock_fd: RawFd, fds: &[RawFd]) -> Result<(), ()> {
1471 unsafe {
1474 let mut payload = [0u8; 1];
1475 let mut iov = libc::iovec {
1476 iov_base: payload.as_mut_ptr().cast(),
1477 iov_len: payload.len(),
1478 };
1479 let mut control = CmsgBuffer([0u8; CMSG_BUFFER_LEN]);
1480 let bytes = std::mem::size_of_val(fds) as u32;
1481
1482 let mut msg: libc::msghdr = std::mem::zeroed();
1483 msg.msg_iov = &mut iov;
1484 msg.msg_iovlen = 1;
1485 msg.msg_control = control.0.as_mut_ptr().cast();
1486 msg.msg_controllen = libc::CMSG_SPACE(bytes) as _;
1487
1488 let cmsg = libc::CMSG_FIRSTHDR(&msg);
1489 if cmsg.is_null() {
1490 return Err(());
1491 }
1492 (*cmsg).cmsg_level = libc::SOL_SOCKET;
1493 (*cmsg).cmsg_type = libc::SCM_RIGHTS;
1494 (*cmsg).cmsg_len = libc::CMSG_LEN(bytes) as _;
1495 std::ptr::copy_nonoverlapping(
1496 fds.as_ptr().cast::<u8>(),
1497 libc::CMSG_DATA(cmsg),
1498 bytes as usize,
1499 );
1500
1501 if libc::sendmsg(sock_fd, &msg, 0) < 0 {
1502 return Err(());
1503 }
1504 Ok(())
1505 }
1506 }
1507
1508 fn receive_session_netns(sock_fd: RawFd, expected_sockets: usize) -> Result<Received, String> {
1516 unsafe {
1519 let mut payload = [0u8; STEP_RECORD_LEN];
1520 let mut iov = libc::iovec {
1521 iov_base: payload.as_mut_ptr().cast(),
1522 iov_len: payload.len(),
1523 };
1524 let mut control = CmsgBuffer([0u8; CMSG_BUFFER_LEN]);
1525 let mut msg: libc::msghdr = std::mem::zeroed();
1526 msg.msg_iov = &mut iov;
1527 msg.msg_iovlen = 1;
1528 msg.msg_control = control.0.as_mut_ptr().cast();
1529 msg.msg_controllen = control.0.len() as _;
1530
1531 let received = libc::recvmsg(
1532 sock_fd,
1533 &mut msg,
1534 libc::MSG_DONTWAIT | libc::MSG_CMSG_CLOEXEC,
1535 );
1536 if received < 0 {
1537 let error = io::Error::last_os_error();
1538 if error.kind() == io::ErrorKind::WouldBlock {
1539 return Ok(Received::Nothing);
1540 }
1541 return Err(format!("receiving the namespace hand-off failed: {error}"));
1542 }
1543
1544 let cmsg = libc::CMSG_FIRSTHDR(&msg);
1545 if cmsg.is_null() {
1546 if received as usize == STEP_RECORD_LEN {
1547 if let Some(failure) = CreationFailure::decode(&payload) {
1548 return Ok(Received::Failure(failure));
1549 }
1550 }
1551 return Ok(Received::Nothing);
1552 }
1553 if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
1554 return Err(
1555 "the namespace hand-off did not arrive as an SCM_RIGHTS message".to_string(),
1556 );
1557 }
1558 let bytes = (*cmsg).cmsg_len - libc::CMSG_LEN(0) as usize;
1559 let count = bytes / std::mem::size_of::<RawFd>();
1560 let data = libc::CMSG_DATA(cmsg).cast::<RawFd>();
1561 let mut fds = Vec::with_capacity(count);
1564 for index in 0..count {
1565 fds.push(OwnedFd::from_raw_fd(std::ptr::read_unaligned(
1566 data.add(index),
1567 )));
1568 }
1569 if msg.msg_flags & libc::MSG_CTRUNC != 0 {
1570 return Err("the namespace hand-off was truncated".to_string());
1571 }
1572 if count != expected_sockets + 2 {
1573 return Err(format!(
1574 "the namespace creator handed over {count} descriptors; {} were expected \
1575 ({expected_sockets} sockets and 2 namespace handles)",
1576 expected_sockets + 2
1577 ));
1578 }
1579 Ok(Received::Handles(fds))
1580 }
1581 }
1582}
1583
1584#[cfg(target_os = "linux")]
1587const DNS_PORT_BE: u16 = crate::egress_proxy::EGRESS_DNS_PORT.to_be();
1588
1589#[cfg(test)]
1590mod tests {
1591 use super::*;
1592 use crate::sealed::{ProfileAttachment, UsernsGrant};
1593
1594 fn attachments() -> [ProfileAttachment; 4] {
1595 [
1596 ProfileAttachment::Sealed {
1597 profile: "mur-sealed".to_string(),
1598 },
1599 ProfileAttachment::Other {
1600 profile: "docker-default".to_string(),
1601 },
1602 ProfileAttachment::Unconfined,
1603 ProfileAttachment::NotReported,
1604 ]
1605 }
1606
1607 fn step(step: CreationStep, errno: i32) -> Result<(), NamespaceAttemptError> {
1608 Err(NamespaceAttemptError::Step { step, errno })
1609 }
1610
1611 fn failure(
1612 attempt: Result<(), NamespaceAttemptError>,
1613 grant: UsernsGrant,
1614 attachment: ProfileAttachment,
1615 ) -> EgressNamespaceFailure {
1616 egress_namespace_failure(true, attempt, grant, attachment)
1617 .expect("a failed attempt is a failure")
1618 }
1619
1620 #[test]
1625 fn a_created_namespace_is_never_refused_whatever_the_readings() {
1626 for grant in UsernsGrant::ALL {
1627 for attachment in attachments() {
1628 assert_eq!(
1629 egress_namespace_failure(true, Ok(()), *grant, attachment.clone()),
1630 None,
1631 "{grant:?} / {attachment:?}"
1632 );
1633 }
1634 }
1635 }
1636
1637 #[test]
1640 fn a_failed_creation_is_refused_whatever_the_readings() {
1641 let attempts = [
1642 (
1643 step(CreationStep::Unshare, libc::EPERM),
1644 EgressNamespaceBlocker::CapabilityGrantMissing,
1645 ),
1646 (
1647 step(CreationStep::Unshare, libc::ENOSPC),
1648 EgressNamespaceBlocker::KernelSupportMissing,
1649 ),
1650 (
1651 Err(NamespaceAttemptError::Creator("fork() failed".to_string())),
1652 EgressNamespaceBlocker::CreatorUnavailable,
1653 ),
1654 ];
1655 for (attempt, blocker) in attempts {
1656 for grant in UsernsGrant::ALL {
1657 for attachment in attachments() {
1658 let failure =
1659 egress_namespace_failure(true, attempt.clone(), *grant, attachment.clone())
1660 .unwrap_or_else(|| {
1661 panic!("{attempt:?} under {grant:?} / {attachment:?}")
1662 });
1663 assert_eq!(failure.blocker, blocker, "{grant:?} / {attachment:?}");
1664 }
1665 }
1666 }
1667 }
1668
1669 #[test]
1670 fn only_an_unshare_the_kernel_cannot_honour_is_missing_kernel_support() {
1671 for errno in [libc::ENOSPC, libc::EINVAL, libc::ENOSYS, libc::EUSERS] {
1672 assert_eq!(
1673 failure(
1674 step(CreationStep::Unshare, errno),
1675 UsernsGrant::Withheld,
1676 ProfileAttachment::Unconfined
1677 )
1678 .blocker,
1679 EgressNamespaceBlocker::KernelSupportMissing,
1680 "errno {errno}"
1681 );
1682 }
1683 for (failed, errno) in [
1684 (CreationStep::Unshare, libc::EPERM),
1685 (CreationStep::Unshare, libc::EACCES),
1686 (CreationStep::IdentityMaps, libc::EPERM),
1687 (CreationStep::Loopback, libc::EPERM),
1688 (CreationStep::LocalRoute, libc::EINVAL),
1689 (CreationStep::Resolver, libc::ENOSPC),
1690 ] {
1691 assert_eq!(
1692 failure(
1693 step(failed, errno),
1694 UsernsGrant::Withheld,
1695 ProfileAttachment::Unconfined
1696 )
1697 .blocker,
1698 EgressNamespaceBlocker::CapabilityGrantMissing,
1699 "{failed:?} errno {errno}"
1700 );
1701 }
1702 }
1703
1704 #[test]
1705 fn a_creator_that_could_not_run_is_unavailable_and_keeps_its_detail() {
1706 let detail = "fork() for the namespace creator failed: Resource temporarily unavailable \
1707 (os error 11)";
1708 let failure = failure(
1709 Err(NamespaceAttemptError::Creator(detail.to_string())),
1710 UsernsGrant::Withheld,
1711 ProfileAttachment::Unconfined,
1712 );
1713 assert_eq!(failure.blocker, EgressNamespaceBlocker::CreatorUnavailable);
1714 assert_eq!(failure.errno, None);
1715 let reason = failure.reason();
1716 assert!(reason.starts_with(detail), "{reason}");
1717 assert!(!reason.contains("apparmor_parser"), "{reason}");
1718 assert!(!reason.contains("CONFIG_USER_NS"), "{reason}");
1719 assert!(!reason.contains("user.max_user_namespaces"), "{reason}");
1720 }
1721
1722 #[test]
1723 fn non_linux_is_never_refused_for_a_mechanism_it_never_had() {
1724 for attempt in [
1725 Ok(()),
1726 step(CreationStep::Unshare, libc::EPERM),
1727 Err(NamespaceAttemptError::Creator("fork() failed".to_string())),
1728 ] {
1729 assert_eq!(
1730 egress_namespace_failure(
1731 false,
1732 attempt,
1733 UsernsGrant::Withheld,
1734 ProfileAttachment::NotReported
1735 ),
1736 None
1737 );
1738 }
1739 #[cfg(not(target_os = "linux"))]
1740 assert_eq!(detect_egress_namespace_failure(), None);
1741 }
1742
1743 #[test]
1744 fn the_check_refuses_exactly_a_capsule_that_can_spawn_on_a_failing_host() {
1745 let failure = failure(
1746 step(CreationStep::Unshare, libc::EPERM),
1747 UsernsGrant::Withheld,
1748 ProfileAttachment::Unconfined,
1749 );
1750 match check_egress_namespace(true, Some(&failure)) {
1751 Err(crate::errors::RuntimeError::EgressNamespaceUnavailable { blocker, reason }) => {
1752 assert_eq!(blocker, failure.blocker);
1753 assert_eq!(reason, failure.reason());
1754 }
1755 other => panic!("expected the E-CAP-005 refusal, got {other:?}"),
1756 }
1757 assert!(check_egress_namespace(false, Some(&failure)).is_ok());
1758 assert!(check_egress_namespace(true, None).is_ok());
1759 assert!(check_egress_namespace(false, None).is_ok());
1760 }
1761
1762 #[test]
1765 fn an_unconfined_binary_on_a_restricted_host_is_told_the_profile_before_the_sysctl() {
1766 let reason = failure(
1767 step(CreationStep::Unshare, libc::EPERM),
1768 UsernsGrant::Withheld,
1769 ProfileAttachment::Unconfined,
1770 )
1771 .reason();
1772 assert!(
1773 reason.starts_with(
1774 "unshare(CLONE_NEWUSER | CLONE_NEWNET) failed with EPERM (Operation not \
1775 permitted)."
1776 ),
1777 "{reason}"
1778 );
1779 assert!(
1780 reason.contains("kernel.apparmor_restrict_unprivileged_userns=1"),
1781 "{reason}"
1782 );
1783 let profile = reason
1784 .find("apparmor_parser -r")
1785 .expect("the profile install command must be named");
1786 let sysctl = reason
1787 .find("kernel.apparmor_restrict_unprivileged_userns=0")
1788 .expect("the fallback must still be named for hosts that cannot load a profile");
1789 assert!(
1790 profile < sysctl,
1791 "the profile install must come first: {reason}"
1792 );
1793 assert!(reason.contains("LAST RESORT"), "{reason}");
1794 assert!(reason.contains("every program on the machine"), "{reason}");
1795 assert!(
1796 reason.contains("scripts/install-dev-apparmor.sh"),
1797 "{reason}"
1798 );
1799 assert!(
1800 reason.ends_with(
1801 "Host readings: kernel.apparmor_restrict_unprivileged_userns=1; AppArmor profile \
1802 confining mur: unconfined."
1803 ),
1804 "{reason}"
1805 );
1806 }
1807
1808 #[test]
1809 fn a_binary_another_profile_confines_is_given_the_container_remedy_not_the_sysctl() {
1810 let reason = failure(
1811 step(CreationStep::Unshare, libc::EPERM),
1812 UsernsGrant::Withheld,
1813 ProfileAttachment::Other {
1814 profile: "docker-default".to_string(),
1815 },
1816 )
1817 .reason();
1818 assert!(reason.contains("`docker-default`"), "{reason}");
1819 assert!(reason.contains("--cap-add SYS_ADMIN"), "{reason}");
1820 assert!(reason.contains("seccomp"), "{reason}");
1821 assert!(
1822 !reason.contains("kernel.apparmor_restrict_unprivileged_userns=0"),
1823 "{reason}"
1824 );
1825 assert!(!reason.contains("apparmor_parser"), "{reason}");
1826 assert!(
1827 reason.ends_with("AppArmor profile confining mur: docker-default."),
1828 "{reason}"
1829 );
1830 }
1831
1832 #[test]
1833 fn a_failure_under_the_sealed_profile_points_at_a_stale_load() {
1834 let reason = failure(
1835 step(CreationStep::Unshare, libc::EPERM),
1836 UsernsGrant::ProfileConfining,
1837 ProfileAttachment::Sealed {
1838 profile: "mur-sealed".to_string(),
1839 },
1840 )
1841 .reason();
1842 assert!(
1843 reason.contains("sudo apparmor_parser -r /etc/apparmor.d/mur-sealed"),
1844 "{reason}"
1845 );
1846 assert!(reason.contains("stale"), "{reason}");
1847 assert!(!reason.contains("LAST RESORT"), "{reason}");
1848 }
1849
1850 #[test]
1851 fn without_the_restriction_the_cause_is_not_apparmor() {
1852 for (grant, readings) in [
1853 (
1854 UsernsGrant::RestrictionDisabledHostWide,
1855 "kernel.apparmor_restrict_unprivileged_userns=0",
1856 ),
1857 (UsernsGrant::ApparmorAbsent, "AppArmor not enabled"),
1858 ] {
1859 for attachment in [
1860 ProfileAttachment::Unconfined,
1861 ProfileAttachment::NotReported,
1862 ] {
1863 let reason =
1864 failure(step(CreationStep::Unshare, libc::EPERM), grant, attachment).reason();
1865 assert!(
1866 !reason.contains("apparmor_restrict_unprivileged_userns=1"),
1867 "{reason}"
1868 );
1869 assert!(!reason.contains("apparmor_parser"), "{reason}");
1870 assert!(reason.contains("--cap-add SYS_ADMIN"), "{reason}");
1871 assert!(
1872 reason.contains(&format!("Host readings: {readings};")),
1873 "{reason}"
1874 );
1875 }
1876 }
1877 }
1878
1879 #[test]
1882 fn another_security_modules_label_is_not_blamed_as_an_apparmor_profile() {
1883 let reason = failure(
1884 step(CreationStep::Unshare, libc::EPERM),
1885 UsernsGrant::ApparmorAbsent,
1886 ProfileAttachment::Other {
1887 profile: "unconfined_u:unconfined_r:unconfined_t:s0".to_string(),
1888 },
1889 )
1890 .reason();
1891 assert!(reason.contains("not AppArmor"), "{reason}");
1892 assert!(!reason.contains("the AppArmor profile `"), "{reason}");
1893 }
1894
1895 #[test]
1896 fn a_namespace_created_and_then_refused_says_which_half_failed() {
1897 let configure = failure(
1898 step(CreationStep::Loopback, libc::EPERM),
1899 UsernsGrant::RestrictionDisabledHostWide,
1900 ProfileAttachment::Unconfined,
1901 )
1902 .reason();
1903 assert!(
1904 configure.starts_with("bringing lo up failed with EPERM"),
1905 "{configure}"
1906 );
1907 assert!(configure.contains("CAP_NET_ADMIN"), "{configure}");
1908
1909 let owned = failure(
1910 step(CreationStep::IdentityMaps, libc::EACCES),
1911 UsernsGrant::RestrictionDisabledHostWide,
1912 ProfileAttachment::Unconfined,
1913 )
1914 .reason();
1915 assert!(
1916 owned.starts_with("writing uid_map/gid_map failed with EACCES (Permission denied)."),
1917 "{owned}"
1918 );
1919 assert!(owned.contains("could not be owned"), "{owned}");
1920 }
1921
1922 #[test]
1923 fn missing_kernel_support_names_the_limit_or_the_config_symbol_and_no_apparmor() {
1924 for (errno, named) in [
1925 (libc::ENOSPC, "user.max_user_namespaces"),
1926 (libc::EINVAL, "CONFIG_USER_NS"),
1927 (libc::ENOSYS, "CONFIG_USER_NS"),
1928 (libc::EUSERS, "nesting limit"),
1929 ] {
1930 let reason = failure(
1931 step(CreationStep::Unshare, errno),
1932 UsernsGrant::Withheld,
1933 ProfileAttachment::Unconfined,
1934 )
1935 .reason();
1936 assert!(reason.contains(named), "{reason}");
1937 assert!(
1938 reason.contains(errno_name(errno).expect("a named errno")),
1939 "{reason}"
1940 );
1941 assert!(!reason.contains("apparmor_parser"), "{reason}");
1942 assert!(!reason.contains("LAST RESORT"), "{reason}");
1943 }
1944 }
1945
1946 #[test]
1947 fn an_unnamed_errno_is_rendered_by_number() {
1948 assert_eq!(errno_name(libc::EPERM), Some("EPERM"));
1949 assert_eq!(errno_name(-7), None);
1950 let failure = failure(
1951 step(CreationStep::HandOff, 4095),
1952 UsernsGrant::Withheld,
1953 ProfileAttachment::Unconfined,
1954 );
1955 assert_eq!(
1956 failure.summary(),
1957 "handing the namespace to the runtime failed with errno 4095"
1958 );
1959 }
1960
1961 #[test]
1962 fn the_summary_names_the_call_and_the_errno() {
1963 let failed = failure(
1964 step(CreationStep::Unshare, libc::EPERM),
1965 UsernsGrant::Withheld,
1966 ProfileAttachment::Unconfined,
1967 );
1968 assert_eq!(
1969 failed.summary(),
1970 "unshare(CLONE_NEWUSER | CLONE_NEWNET) failed with EPERM"
1971 );
1972 let creator = failure(
1973 Err(NamespaceAttemptError::Creator("fork() failed".to_string())),
1974 UsernsGrant::Withheld,
1975 ProfileAttachment::Unconfined,
1976 );
1977 assert_eq!(creator.summary(), "fork() failed");
1978 }
1979
1980 #[test]
1981 fn every_step_round_trips_through_its_code() {
1982 for step in CreationStep::ALL {
1983 assert_eq!(CreationStep::from_code(step as u32), Some(step));
1984 }
1985 assert_eq!(CreationStep::from_code(0), None);
1986 assert_eq!(CreationStep::from_code(99), None);
1987 }
1988
1989 #[test]
1994 fn a_session_creation_failure_is_refused_in_the_staging_words() {
1995 for (failed, errno, grant, attachment) in [
1996 (
1997 CreationStep::Unshare,
1998 libc::EPERM,
1999 UsernsGrant::Withheld,
2000 ProfileAttachment::Unconfined,
2001 ),
2002 (
2003 CreationStep::LocalRoute,
2004 libc::EPERM,
2005 UsernsGrant::Withheld,
2006 ProfileAttachment::Other {
2007 profile: "docker-default".to_string(),
2008 },
2009 ),
2010 (
2011 CreationStep::Unshare,
2012 libc::ENOSPC,
2013 UsernsGrant::RestrictionDisabledHostWide,
2014 ProfileAttachment::Unconfined,
2015 ),
2016 ] {
2017 let staging =
2018 egress_namespace_failure(true, step(failed, errno), grant, attachment.clone());
2019 let staging_refusal = check_egress_namespace(true, staging.as_ref())
2020 .expect_err("staging refuses")
2021 .to_string();
2022 let establish = session_netns_creation_error(
2023 NamespaceAttemptError::Step {
2024 step: failed,
2025 errno,
2026 },
2027 grant,
2028 attachment,
2029 );
2030 assert_eq!(establish, format!("sandbox: E-CAP-005: {staging_refusal}"));
2031 assert!(
2032 establish.contains(&staging.expect("a failure").reason()),
2033 "{establish}"
2034 );
2035 }
2036 }
2037
2038 #[test]
2039 fn a_session_creator_that_could_not_run_keeps_the_runtime_account() {
2040 let error = session_netns_creation_error(
2041 NamespaceAttemptError::Creator("fork() for the namespace creator failed".to_string()),
2042 UsernsGrant::Withheld,
2043 ProfileAttachment::Unconfined,
2044 );
2045 assert_eq!(
2046 error,
2047 "sandbox: could not create this session's network namespace: fork() for the \
2048 namespace creator failed"
2049 );
2050 }
2051
2052 #[test]
2055 fn the_socket_count_is_one_per_port_plus_the_resolver() {
2056 let plan = CapsuleNetnsPlan {
2057 tcp_ports: vec![80, 443],
2058 uid: 1000,
2059 gid: 1000,
2060 };
2061 assert_eq!(plan.socket_count(), 3);
2062 }
2063
2064 #[test]
2065 fn a_capsule_with_no_tcp_egress_still_binds_the_resolver() {
2066 let plan = CapsuleNetnsPlan {
2069 tcp_ports: Vec::new(),
2070 uid: 1000,
2071 gid: 1000,
2072 };
2073 assert_eq!(plan.socket_count(), 1);
2074 }
2075
2076 #[test]
2077 fn the_widest_allowlist_still_fits_one_scm_rights_message() {
2078 let plan = CapsuleNetnsPlan {
2079 tcp_ports: (0..crate::egress_proxy::MAX_EGRESS_TCP_PORTS as u16).collect(),
2080 uid: 1000,
2081 gid: 1000,
2082 };
2083 assert!(plan.socket_count() <= MAX_NAMESPACE_SOCKETS);
2084 assert_eq!(MAX_SESSION_NETNS_FDS, MAX_NAMESPACE_SOCKETS + 2);
2086 }
2087
2088 #[cfg(target_os = "linux")]
2092 #[test]
2093 fn an_ended_session_refuses_to_create_a_namespace() {
2094 let netns = SessionNetns::new();
2095 netns.end();
2096 let refusal = netns
2097 .establish(
2098 &[],
2099 crate::egress_proxy::EgressPolicy::new(Vec::new(), Vec::new()),
2100 )
2101 .expect_err("an ended session creates nothing");
2102 assert!(refusal.contains("session has ended"), "{refusal}");
2103 assert_eq!(netns.creations(), 0);
2104 }
2105
2106 #[cfg(target_os = "linux")]
2109 #[test]
2110 fn a_session_creates_its_namespace_once_and_ends_it_for_good() {
2111 if skip_without_egress_namespace(
2112 "a_session_creates_its_namespace_once_and_ends_it_for_good",
2113 ) {
2114 return;
2115 }
2116 let policy = || crate::egress_proxy::EgressPolicy::new(Vec::new(), Vec::new());
2117 let netns = SessionNetns::new();
2118 assert_eq!(
2119 netns.creations(),
2120 0,
2121 "nothing is created before it is needed"
2122 );
2123
2124 let first = netns
2125 .establish(&[], policy())
2126 .expect("the namespace is created");
2127 let second = netns
2128 .establish(&[], policy())
2129 .expect("the namespace is reused");
2130 assert_eq!(netns.creations(), 1);
2131 let inode = |fd: std::os::fd::RawFd| {
2132 std::fs::metadata(format!("/proc/self/fd/{fd}"))
2133 .map(|metadata| std::os::unix::fs::MetadataExt::ino(&metadata))
2134 .unwrap()
2135 };
2136 assert_eq!(inode(first.raw_fds().1), inode(second.raw_fds().1));
2137 let host_netns = std::fs::metadata("/proc/self/ns/net")
2138 .map(|metadata| std::os::unix::fs::MetadataExt::ino(&metadata))
2139 .unwrap();
2140 assert_ne!(
2141 inode(first.raw_fds().1),
2142 host_netns,
2143 "the session's namespace is not the runtime's own"
2144 );
2145
2146 netns.end();
2147 netns.end();
2148 assert!(netns.establish(&[], policy()).is_err());
2149 assert_eq!(
2150 netns.creations(),
2151 1,
2152 "an ended namespace is never recreated"
2153 );
2154 }
2155
2156 #[cfg(target_os = "linux")]
2159 #[test]
2160 fn a_creation_failure_names_its_step_and_errno() {
2161 assert_eq!(
2162 linux::decode_failure_record(4, libc::EPERM),
2163 Some(NamespaceAttemptError::Step {
2164 step: CreationStep::LocalRoute,
2165 errno: libc::EPERM,
2166 })
2167 );
2168 assert_eq!(linux::decode_failure_record(99, 0), None);
2169 }
2170
2171 #[cfg(target_os = "linux")]
2175 #[test]
2176 fn the_probe_and_a_session_creation_agree_about_this_host() {
2177 let probe = detect_egress_namespace_failure();
2178 let session = linux::create_session_netns(&CapsuleNetnsPlan::resolve(Vec::new())).map(drop);
2179 match (&probe, &session) {
2180 (None, Ok(())) => {}
2181 (Some(failure), Err(NamespaceAttemptError::Step { step, errno })) => {
2182 assert_eq!(failure.failed_call, step.describe());
2183 assert_eq!(failure.errno, Some(*errno));
2184 }
2185 other => panic!("the probe and a session creation disagree: {other:?}"),
2186 }
2187 }
2188}