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, the container runtime's seccomp \
352 profile is the usual blocker: it refuses unshare(CLONE_NEWUSER) and setns whatever \
353 capabilities the container holds. Run the container with a seccomp profile that permits \
354 them (Docker: `--security-opt seccomp=<file>`; Kubernetes: `seccompProfile: \
355 Localhost`), or create the network namespace outside the container and run mur inside \
356 it.";
357 match (userns_grant, attachment) {
358 (UsernsGrant::ApparmorAbsent, _) => format!(
359 "Likely cause: not AppArmor, which is not enabled on this host — a container \
360 runtime's seccomp profile, or another security module's policy. \
361 {CONTAINER_REMEDY} On a bare host, check the policy of the security module in use."
362 ),
363 (_, ProfileAttachment::Other { profile }) => format!(
364 "Likely cause: the AppArmor profile `{profile}` confines mur. \
365 kernel.apparmor_restrict_unprivileged_userns does not govern a process another \
366 profile confines: that profile, and a container runtime's seccomp profile, decide \
367 whether it may create a user namespace. Run the container with an AppArmor profile \
368 and a seccomp profile that permit unshare(CLONE_NEWUSER) and setns (Docker: \
369 `--security-opt apparmor=<profile> --security-opt seccomp=<file>`; Kubernetes: \
370 `appArmorProfile` and `seccompProfile: Localhost`), or create the network namespace \
371 outside the container and run mur inside it."
372 ),
373 (UsernsGrant::ProfileConfining, _) | (_, ProfileAttachment::Sealed { .. }) => format!(
374 "Likely cause: the {SEALED_APPARMOR_PROFILE_NAME} AppArmor profile confines mur and \
375 the creation still failed, so the profile the kernel has loaded may be stale. Reload \
376 it with `sudo apparmor_parser -r {SEALED_APPARMOR_PROFILE_PATH}` (for a checkout \
377 build, re-run `scripts/install-dev-apparmor.sh`), and check `mur doctor`'s \
378 comparison of the installed profile with the one this build ships."
379 ),
380 (UsernsGrant::RestrictionDisabledHostWide, _) => format!(
381 "Likely cause: not AppArmor, whose unprivileged-userns restriction is off on this host \
382 — a container runtime's seccomp profile, or another security module's policy. \
383 {CONTAINER_REMEDY}"
384 ),
385 (UsernsGrant::Withheld, _) => format!(
386 "Likely cause: AppArmor's unprivileged-userns restriction \
387 (kernel.apparmor_restrict_unprivileged_userns=1) applies to this binary, which no \
388 {SEALED_APPARMOR_PROFILE_NAME} profile confines. Install and load the profile shipped \
389 with mur, `sudo install -m 644 packaging/apparmor/{SEALED_APPARMOR_PROFILE_NAME} \
390 {SEALED_APPARMOR_PROFILE_PATH} && sudo apparmor_parser -r \
391 {SEALED_APPARMOR_PROFILE_PATH}` (or re-run the mur installer as root), then re-run. \
392 Building out of a checkout, where the binary sits at ./target/{{debug,release}}/mur \
393 and no shipped profile attaches to it: run `scripts/install-dev-apparmor.sh`, which \
394 generates and loads the same grant for those two paths. LAST RESORT, only where a \
395 profile genuinely cannot be loaded: `sudo sysctl -w \
396 kernel.apparmor_restrict_unprivileged_userns=0` — this removes unprivileged-userns \
397 hardening from every program on the machine, not just from mur, and is not the \
398 configuration murmur ships."
399 ),
400 }
401}
402
403fn kernel_cause(errno: Option<i32>) -> &'static str {
405 match errno {
406 Some(libc::ENOSPC) => {
407 "Likely cause: the kernel's limit on user namespaces — user.max_user_namespaces is 0, \
408 or every namespace it allows is in use. Raise it where it is set, on the host or in \
409 the user namespace mur runs in (`sudo sysctl -w user.max_user_namespaces=10000`), \
410 then re-run."
411 }
412 Some(libc::EUSERS) => {
413 "Likely cause: the kernel's nesting limit for user namespaces — mur already runs \
414 inside as many nested user namespaces as the kernel allows. Run it fewer levels \
415 deep."
416 }
417 _ => {
418 "Likely cause: this kernel does not provide user namespaces (built with \
419 CONFIG_USER_NS=n). Run mur on a kernel built with CONFIG_USER_NS=y."
420 }
421 }
422}
423
424pub(crate) fn errno_name(errno: i32) -> Option<&'static str> {
426 Some(match errno {
427 libc::EPERM => "EPERM",
428 libc::ENOENT => "ENOENT",
429 libc::EACCES => "EACCES",
430 libc::EINVAL => "EINVAL",
431 libc::ENOSPC => "ENOSPC",
432 libc::ENOSYS => "ENOSYS",
433 libc::EUSERS => "EUSERS",
434 libc::ENOMEM => "ENOMEM",
435 libc::EAGAIN => "EAGAIN",
436 libc::EADDRINUSE => "EADDRINUSE",
437 libc::EMFILE => "EMFILE",
438 libc::ENFILE => "ENFILE",
439 libc::EROFS => "EROFS",
440 libc::ENOTDIR => "ENOTDIR",
441 libc::EBUSY => "EBUSY",
442 _ => return None,
443 })
444}
445
446pub(crate) fn errno_label(errno: i32) -> String {
448 errno_name(errno).map_or_else(|| format!("errno {errno}"), str::to_string)
449}
450
451pub(crate) fn errno_words(errno: i32) -> String {
453 let rendered = std::io::Error::from_raw_os_error(errno).to_string();
454 rendered
455 .strip_suffix(&format!(" (os error {errno})"))
456 .map_or_else(|| rendered.clone(), str::to_string)
457}
458
459pub(crate) fn egress_namespace_failure(
465 is_linux: bool,
466 attempt: Result<(), NamespaceAttemptError>,
467 userns_grant: crate::sealed::UsernsGrant,
468 attachment: crate::sealed::ProfileAttachment,
469) -> Option<EgressNamespaceFailure> {
470 if !is_linux {
475 return None;
476 }
477 attempt
478 .err()
479 .map(|error| failure_of(error, userns_grant, attachment))
480}
481
482fn failure_of(
484 error: NamespaceAttemptError,
485 userns_grant: crate::sealed::UsernsGrant,
486 attachment: crate::sealed::ProfileAttachment,
487) -> EgressNamespaceFailure {
488 let (blocker, failed_call, errno) = match error {
489 NamespaceAttemptError::Step { step, errno } => {
490 let blocker = match (step, errno) {
491 (
492 CreationStep::Unshare,
493 libc::ENOSPC | libc::EINVAL | libc::ENOSYS | libc::EUSERS,
494 ) => EgressNamespaceBlocker::KernelSupportMissing,
495 _ => EgressNamespaceBlocker::CapabilityGrantMissing,
496 };
497 (blocker, step.describe().to_string(), Some(errno))
498 }
499 NamespaceAttemptError::Creator(detail) => {
500 (EgressNamespaceBlocker::CreatorUnavailable, detail, None)
501 }
502 };
503 EgressNamespaceFailure {
504 blocker,
505 failed_call,
506 errno,
507 userns_grant,
508 attachment,
509 }
510}
511
512#[must_use]
520pub fn detect_egress_namespace_failure() -> Option<EgressNamespaceFailure> {
521 crate::sandbox::HostProbe::probe().egress_namespace_failure()
522}
523
524pub fn skip_without_egress_namespace(test_name: &str) -> bool {
535 match detect_egress_namespace_failure() {
536 Some(failure) => {
537 crate::runtime_err!("[SKIP-HOST] {test_name}: {}", failure.reason());
538 true
539 }
540 None => false,
541 }
542}
543
544pub fn check_egress_namespace(
555 can_spawn_subprocess: bool,
556 failure: Option<&EgressNamespaceFailure>,
557) -> Result<(), crate::errors::RuntimeError> {
558 match failure {
559 Some(failure) if can_spawn_subprocess => Err(unavailable(failure)),
560 _ => Ok(()),
561 }
562}
563
564fn unavailable(failure: &EgressNamespaceFailure) -> crate::errors::RuntimeError {
566 crate::errors::RuntimeError::EgressNamespaceUnavailable {
567 blocker: failure.blocker,
568 reason: failure.reason(),
569 required_by: Vec::new(),
570 }
571}
572
573fn session_netns_creation_error(
579 error: NamespaceAttemptError,
580 userns_grant: crate::sealed::UsernsGrant,
581 attachment: crate::sealed::ProfileAttachment,
582) -> String {
583 match error {
584 NamespaceAttemptError::Creator(detail) => {
585 format!("sandbox: could not create this session's network namespace: {detail}")
586 }
587 step => format!(
588 "sandbox: E-CAP-005: {}",
589 unavailable(&failure_of(step, userns_grant, attachment))
590 ),
591 }
592}
593
594#[derive(Debug, Clone)]
603pub(crate) struct CapsuleNetnsPlan {
604 pub(crate) tcp_ports: Vec<u16>,
611 pub(crate) uid: u32,
617 pub(crate) gid: u32,
618}
619
620impl CapsuleNetnsPlan {
621 #[cfg(target_os = "linux")]
623 #[allow(unsafe_code)]
624 pub(crate) fn resolve(tcp_ports: Vec<u16>) -> Self {
625 let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) };
627 Self {
628 tcp_ports,
629 uid,
630 gid,
631 }
632 }
633
634 pub(crate) fn socket_count(&self) -> usize {
636 self.tcp_ports.len() + 1
637 }
638}
639
640#[derive(Debug, Default)]
653pub(crate) struct SessionNetns {
654 state: std::sync::Mutex<SessionNetnsState>,
655 #[cfg(test)]
657 creations: std::sync::atomic::AtomicUsize,
658}
659
660#[derive(Debug, Default)]
661enum SessionNetnsState {
662 #[default]
663 NotYetCreated,
664 #[cfg(target_os = "linux")]
665 Established(linux::EstablishedNetns),
666 Ended,
667}
668
669#[cfg(target_os = "linux")]
676#[derive(Debug, Clone)]
677pub(crate) struct SessionNetnsJoin {
678 user_ns: std::sync::Arc<std::os::fd::OwnedFd>,
679 net_ns: std::sync::Arc<std::os::fd::OwnedFd>,
680}
681
682#[cfg(target_os = "linux")]
683impl SessionNetnsJoin {
684 pub(crate) fn raw_fds(&self) -> (std::os::fd::RawFd, std::os::fd::RawFd) {
686 use std::os::fd::AsRawFd;
687 (self.user_ns.as_raw_fd(), self.net_ns.as_raw_fd())
688 }
689}
690
691impl SessionNetns {
692 pub(crate) fn new() -> Self {
694 Self::default()
695 }
696
697 #[cfg(target_os = "linux")]
709 pub(crate) fn establish(
710 &self,
711 tcp_ports: &[u16],
712 policy: crate::egress_proxy::EgressPolicy,
713 ) -> Result<SessionNetnsJoin, String> {
714 let mut state = self
715 .state
716 .lock()
717 .unwrap_or_else(std::sync::PoisonError::into_inner);
718 match &*state {
719 SessionNetnsState::Established(established) => return Ok(established.join()),
720 SessionNetnsState::Ended => {
721 return Err(
722 "sandbox: this session has ended, and its network namespace was released \
723 with it; no further command runs in it"
724 .to_string(),
725 )
726 }
727 SessionNetnsState::NotYetCreated => {}
728 }
729 let plan = CapsuleNetnsPlan::resolve(tcp_ports.to_vec());
730 let created = linux::create_session_netns(&plan).map_err(|error| {
733 session_netns_creation_error(
734 error,
735 crate::sealed::userns_grant(),
736 crate::sealed::inspect_profile_attachment(),
737 )
738 })?;
739 let established = created.serve(policy).map_err(|detail| {
740 format!("sandbox: could not create this session's network namespace: {detail}")
741 })?;
742 let join = established.join();
743 *state = SessionNetnsState::Established(established);
744 #[cfg(test)]
745 self.creations
746 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
747 Ok(join)
748 }
749
750 pub(crate) fn end(&self) {
756 let previous = std::mem::replace(
757 &mut *self
758 .state
759 .lock()
760 .unwrap_or_else(std::sync::PoisonError::into_inner),
761 SessionNetnsState::Ended,
762 );
763 #[cfg(target_os = "linux")]
764 if let SessionNetnsState::Established(established) = previous {
765 established.release();
766 }
767 #[cfg(not(target_os = "linux"))]
768 drop(previous);
769 }
770
771 #[cfg(test)]
773 pub(crate) fn creations(&self) -> usize {
774 self.creations.load(std::sync::atomic::Ordering::SeqCst)
775 }
776}
777
778impl Drop for SessionNetns {
779 fn drop(&mut self) {
782 self.end();
783 }
784}
785
786pub(crate) const STEP_RECORD_LEN: usize = 8;
789
790pub(crate) fn encode_step_record(code: u32, errno: i32) -> [u8; STEP_RECORD_LEN] {
792 let mut record = [0u8; STEP_RECORD_LEN];
793 record[..4].copy_from_slice(&code.to_ne_bytes());
794 record[4..].copy_from_slice(&errno.to_ne_bytes());
795 record
796}
797
798pub(crate) fn decode_step_record(record: &[u8; STEP_RECORD_LEN]) -> (u32, i32) {
800 (
801 u32::from_ne_bytes([record[0], record[1], record[2], record[3]]),
802 i32::from_ne_bytes([record[4], record[5], record[6], record[7]]),
803 )
804}
805
806#[cfg(target_os = "linux")]
807pub(crate) use linux::{create_session_netns, join_session_netns, reap_child};
808
809#[cfg(target_os = "linux")]
810#[allow(unsafe_code)]
811mod linux {
812 use std::io;
813 use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
814 use std::sync::Arc;
815
816 use super::{
817 decode_step_record, encode_step_record, CapsuleNetnsPlan, CreationStep,
818 NamespaceAttemptError, SessionNetnsJoin, MAX_NAMESPACE_SOCKETS, MAX_SESSION_NETNS_FDS,
819 STEP_RECORD_LEN,
820 };
821
822 const RTM_NEWROUTE: u16 = 24;
829 const NLM_F_REQUEST: u16 = 0x001;
830 const NLM_F_ACK: u16 = 0x004;
831 const NLM_F_EXCL: u16 = 0x200;
832 const NLM_F_CREATE: u16 = 0x400;
833 const NLMSG_ERROR: u16 = 2;
834 const RT_TABLE_LOCAL: u8 = 255;
837 const RTPROT_BOOT: u8 = 3;
838 const RT_SCOPE_HOST: u8 = 254;
839 const RTN_LOCAL: u8 = 2;
840 const RTA_OIF: u16 = 4;
841 const LOOPBACK_IFINDEX: u32 = 1;
844
845 const NLMSGHDR_LEN: usize = 16;
846 const RTMSG_LEN: usize = 12;
847 const RTATTR_OIF_LEN: usize = 8;
848 const ROUTE_REQUEST_LEN: usize = NLMSGHDR_LEN + RTMSG_LEN + RTATTR_OIF_LEN;
849
850 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
856 pub(super) struct CreationFailure {
857 step: CreationStep,
858 errno: i32,
859 }
860
861 impl CreationFailure {
862 unsafe fn at(step: CreationStep) -> Self {
867 let errno = unsafe { *libc::__errno_location() };
870 Self { step, errno }
871 }
872
873 fn encode(self) -> [u8; STEP_RECORD_LEN] {
874 encode_step_record(self.step as u32, self.errno)
875 }
876
877 fn decode(record: &[u8; STEP_RECORD_LEN]) -> Option<Self> {
878 let (code, errno) = decode_step_record(record);
879 let step = CreationStep::from_code(code)?;
880 Some(Self { step, errno })
881 }
882 }
883
884 impl From<CreationFailure> for NamespaceAttemptError {
885 fn from(failure: CreationFailure) -> Self {
886 NamespaceAttemptError::Step {
887 step: failure.step,
888 errno: failure.errno,
889 }
890 }
891 }
892
893 unsafe fn create_capsule_netns(
909 plan: &CapsuleNetnsPlan,
910 sock_fd: RawFd,
911 ) -> Result<(), CreationFailure> {
912 unsafe {
914 if libc::unshare(libc::CLONE_NEWUSER | libc::CLONE_NEWNET) != 0 {
915 return Err(CreationFailure::at(CreationStep::Unshare));
916 }
917 if write_identity_maps(plan.uid, plan.gid).is_err() {
918 return Err(CreationFailure::at(CreationStep::IdentityMaps));
919 }
920 if bring_loopback_up().is_err() {
921 return Err(CreationFailure::at(CreationStep::Loopback));
922 }
923 if add_local_default_route().is_err() {
924 return Err(CreationFailure::at(CreationStep::LocalRoute));
925 }
926
927 let mut fds = [-1 as RawFd; MAX_SESSION_NETNS_FDS];
931 let mut count = 0usize;
932
933 for port in &plan.tcp_ports {
934 let fd = bind_tcp_listener(*port);
935 if fd < 0 {
936 let failure = CreationFailure::at(CreationStep::TcpListener);
937 close_all(&fds[..count]);
938 return Err(failure);
939 }
940 fds[count] = fd;
941 count += 1;
942 }
943
944 let resolver = bind_dns_socket();
945 if resolver < 0 {
946 let failure = CreationFailure::at(CreationStep::Resolver);
947 close_all(&fds[..count]);
948 return Err(failure);
949 }
950 fds[count] = resolver;
951 count += 1;
952
953 for namespace in [c"/proc/self/ns/user", c"/proc/self/ns/net"] {
955 let fd = libc::open(namespace.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC);
956 if fd < 0 {
957 let failure = CreationFailure::at(CreationStep::NamespaceHandles);
958 close_all(&fds[..count]);
959 return Err(failure);
960 }
961 fds[count] = fd;
962 count += 1;
963 }
964
965 let sent = send_fds(sock_fd, &fds[..count]);
966 let failure = CreationFailure::at(CreationStep::HandOff);
967 close_all(&fds[..count]);
968 sent.map_err(|()| failure)
969 }
970 }
971
972 pub(crate) unsafe fn join_session_netns(user_fd: RawFd, net_fd: RawFd) -> io::Result<()> {
984 unsafe {
987 if libc::setns(user_fd, libc::CLONE_NEWUSER) != 0 {
988 return Err(io::Error::last_os_error());
989 }
990 if libc::setns(net_fd, libc::CLONE_NEWNET) != 0 {
991 return Err(io::Error::last_os_error());
992 }
993 }
994 Ok(())
995 }
996
997 pub(crate) struct CreatedNetns {
1000 sockets: Vec<OwnedFd>,
1001 user_ns: OwnedFd,
1002 net_ns: OwnedFd,
1003 }
1004
1005 impl CreatedNetns {
1006 pub(crate) fn namespace_handles(&self) -> (RawFd, RawFd) {
1008 (self.user_ns.as_raw_fd(), self.net_ns.as_raw_fd())
1009 }
1010
1011 pub(super) fn serve(
1016 self,
1017 policy: crate::egress_proxy::EgressPolicy,
1018 ) -> Result<EstablishedNetns, String> {
1019 let proxy = crate::egress_proxy::start_egress_proxy(self.sockets, policy)
1020 .map_err(|error| format!("starting the session's egress proxy failed: {error}"))?;
1021 Ok(EstablishedNetns {
1022 user_ns: Arc::new(self.user_ns),
1023 net_ns: Arc::new(self.net_ns),
1024 proxy,
1025 })
1026 }
1027 }
1028
1029 #[derive(Debug)]
1032 pub(super) struct EstablishedNetns {
1033 user_ns: Arc<OwnedFd>,
1034 net_ns: Arc<OwnedFd>,
1035 proxy: crate::egress_proxy::EgressProxyHandle,
1036 }
1037
1038 impl EstablishedNetns {
1039 pub(super) fn join(&self) -> SessionNetnsJoin {
1040 SessionNetnsJoin {
1041 user_ns: Arc::clone(&self.user_ns),
1042 net_ns: Arc::clone(&self.net_ns),
1043 }
1044 }
1045
1046 pub(super) fn release(self) {
1048 self.proxy.shutdown();
1049 }
1050 }
1051
1052 pub(crate) fn create_session_netns(
1062 plan: &CapsuleNetnsPlan,
1063 ) -> Result<CreatedNetns, NamespaceAttemptError> {
1064 if plan.socket_count() > MAX_NAMESPACE_SOCKETS {
1067 return Err(NamespaceAttemptError::Creator(format!(
1068 "{} namespace sockets requested, more than the {MAX_NAMESPACE_SOCKETS} one \
1069 hand-off carries",
1070 plan.socket_count()
1071 )));
1072 }
1073
1074 let mut pair = [0i32; 2];
1075 let rc = unsafe {
1077 libc::socketpair(
1078 libc::AF_UNIX,
1079 libc::SOCK_DGRAM | libc::SOCK_CLOEXEC,
1080 0,
1081 pair.as_mut_ptr(),
1082 )
1083 };
1084 if rc != 0 {
1085 return Err(NamespaceAttemptError::Creator(format!(
1086 "socketpair() for the namespace hand-off failed: {}",
1087 io::Error::last_os_error()
1088 )));
1089 }
1090 let (parent_end, creator_end) =
1093 unsafe { (OwnedFd::from_raw_fd(pair[0]), OwnedFd::from_raw_fd(pair[1])) };
1094 let creator_fd = creator_end.as_raw_fd();
1095
1096 let pid = unsafe { libc::fork() };
1102 if pid < 0 {
1103 return Err(NamespaceAttemptError::Creator(format!(
1104 "fork() for the namespace creator failed: {}",
1105 io::Error::last_os_error()
1106 )));
1107 }
1108 if pid == 0 {
1109 unsafe {
1112 match create_capsule_netns(plan, creator_fd) {
1113 Ok(()) => libc::_exit(0),
1114 Err(failure) => {
1115 let record = failure.encode();
1116 libc::send(creator_fd, record.as_ptr().cast(), record.len(), 0);
1117 libc::_exit(1);
1118 }
1119 }
1120 }
1121 }
1122 drop(creator_end);
1123
1124 let status = reap_child(pid).map_err(|error| {
1128 NamespaceAttemptError::Creator(format!(
1129 "waiting for the namespace creator failed: {error}"
1130 ))
1131 })?;
1132 match receive_session_netns(parent_end.as_raw_fd(), plan.socket_count())
1133 .map_err(NamespaceAttemptError::Creator)?
1134 {
1135 Received::Handles(mut fds) => {
1136 let net_ns = fds.pop().expect("the count was checked on receipt");
1137 let user_ns = fds.pop().expect("the count was checked on receipt");
1138 Ok(CreatedNetns {
1139 sockets: fds,
1140 user_ns,
1141 net_ns,
1142 })
1143 }
1144 Received::Failure(failure) => Err(failure.into()),
1145 Received::Nothing => Err(NamespaceAttemptError::Creator(format!(
1146 "the namespace creator exited without handing anything over ({status})"
1147 ))),
1148 }
1149 }
1150
1151 pub(crate) fn reap_child(pid: libc::pid_t) -> io::Result<String> {
1153 let mut status: libc::c_int = 0;
1154 loop {
1155 let waited = unsafe { libc::waitpid(pid, &mut status, 0) };
1157 if waited == pid {
1158 break;
1159 }
1160 let error = io::Error::last_os_error();
1161 if error.kind() != io::ErrorKind::Interrupted {
1162 return Err(error);
1163 }
1164 }
1165 Ok(if libc::WIFEXITED(status) {
1166 format!("exit status {}", libc::WEXITSTATUS(status))
1167 } else if libc::WIFSIGNALED(status) {
1168 format!("killed by signal {}", libc::WTERMSIG(status))
1169 } else {
1170 format!("wait status {status}")
1171 })
1172 }
1173
1174 #[cfg(test)]
1177 pub(super) fn decode_failure_record(step: u32, errno: i32) -> Option<NamespaceAttemptError> {
1178 let record = encode_step_record(step, errno);
1179 CreationFailure::decode(&record).map(|failure| {
1180 assert_eq!(failure.encode(), record);
1181 failure.into()
1182 })
1183 }
1184
1185 enum Received {
1187 Handles(Vec<OwnedFd>),
1189 Failure(CreationFailure),
1190 Nothing,
1191 }
1192
1193 unsafe fn write_identity_maps(uid: u32, gid: u32) -> Result<(), ()> {
1206 unsafe {
1208 let previous = crate::sealed::make_dumpable_for_map_writes();
1209 let _ = crate::sealed::write_decimal_map(c"/proc/self/setgroups", None, 0);
1210 let mapped = crate::sealed::write_decimal_map(c"/proc/self/uid_map", Some(uid), uid)
1211 .is_ok()
1212 && crate::sealed::write_decimal_map(c"/proc/self/gid_map", Some(gid), gid).is_ok();
1213 crate::sealed::restore_dumpable(previous);
1214 if mapped {
1215 Ok(())
1216 } else {
1217 Err(())
1218 }
1219 }
1220 }
1221
1222 unsafe fn bring_loopback_up() -> Result<(), ()> {
1231 unsafe {
1234 let sock = libc::socket(libc::AF_INET, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0);
1235 if sock < 0 {
1236 return Err(());
1237 }
1238 let mut ifr: libc::ifreq = std::mem::zeroed();
1239 ifr.ifr_name[0] = b'l' as libc::c_char;
1240 ifr.ifr_name[1] = b'o' as libc::c_char;
1241 if libc::ioctl(sock, libc::SIOCGIFFLAGS, &mut ifr) < 0 {
1242 libc::close(sock);
1243 return Err(());
1244 }
1245 ifr.ifr_ifru.ifru_flags |= (libc::IFF_UP | libc::IFF_RUNNING) as libc::c_short;
1246 let rc = libc::ioctl(sock, libc::SIOCSIFFLAGS, &ifr);
1247 libc::close(sock);
1248 if rc < 0 {
1249 return Err(());
1250 }
1251 Ok(())
1252 }
1253 }
1254
1255 unsafe fn add_local_default_route() -> Result<(), ()> {
1272 unsafe {
1275 let mut request = [0u8; ROUTE_REQUEST_LEN];
1276 request[0..4].copy_from_slice(&(ROUTE_REQUEST_LEN as u32).to_ne_bytes());
1277 request[4..6].copy_from_slice(&RTM_NEWROUTE.to_ne_bytes());
1278 request[6..8].copy_from_slice(
1279 &(NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL).to_ne_bytes(),
1280 );
1281 request[8..12].copy_from_slice(&1u32.to_ne_bytes()); request[12..16].copy_from_slice(&0u32.to_ne_bytes()); let rtm = NLMSGHDR_LEN;
1285 request[rtm] = libc::AF_INET as u8;
1286 request[rtm + 1] = 0; request[rtm + 2] = 0; request[rtm + 3] = 0; request[rtm + 4] = RT_TABLE_LOCAL;
1290 request[rtm + 5] = RTPROT_BOOT;
1291 request[rtm + 6] = RT_SCOPE_HOST;
1292 request[rtm + 7] = RTN_LOCAL;
1293 request[rtm + 8..rtm + 12].copy_from_slice(&0u32.to_ne_bytes()); let attr = rtm + RTMSG_LEN;
1296 request[attr..attr + 2].copy_from_slice(&(RTATTR_OIF_LEN as u16).to_ne_bytes());
1297 request[attr + 2..attr + 4].copy_from_slice(&RTA_OIF.to_ne_bytes());
1298 request[attr + 4..attr + 8].copy_from_slice(&LOOPBACK_IFINDEX.to_ne_bytes());
1299
1300 let sock = libc::socket(
1301 libc::AF_NETLINK,
1302 libc::SOCK_RAW | libc::SOCK_CLOEXEC,
1303 libc::NETLINK_ROUTE,
1304 );
1305 if sock < 0 {
1306 return Err(());
1307 }
1308 let mut address: libc::sockaddr_nl = std::mem::zeroed();
1309 address.nl_family = libc::AF_NETLINK as u16;
1310 let sent = libc::sendto(
1311 sock,
1312 request.as_ptr().cast(),
1313 ROUTE_REQUEST_LEN,
1314 0,
1315 std::ptr::addr_of!(address).cast(),
1316 std::mem::size_of::<libc::sockaddr_nl>() as libc::socklen_t,
1317 );
1318 if sent < 0 {
1319 libc::close(sock);
1320 return Err(());
1321 }
1322
1323 let mut response = [0u8; 256];
1327 let received = libc::recv(sock, response.as_mut_ptr().cast(), response.len(), 0);
1328 libc::close(sock);
1329 if received < (NLMSGHDR_LEN + 4) as isize {
1330 return Err(());
1331 }
1332 if u16::from_ne_bytes([response[4], response[5]]) == NLMSG_ERROR {
1333 let code = i32::from_ne_bytes([
1334 response[NLMSGHDR_LEN],
1335 response[NLMSGHDR_LEN + 1],
1336 response[NLMSGHDR_LEN + 2],
1337 response[NLMSGHDR_LEN + 3],
1338 ]);
1339 if code != 0 {
1341 return Err(());
1342 }
1343 }
1344 Ok(())
1345 }
1346 }
1347
1348 unsafe fn bind_tcp_listener(port: u16) -> RawFd {
1357 unsafe {
1359 let fd = libc::socket(libc::AF_INET, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0);
1360 if fd < 0 {
1361 return -1;
1362 }
1363 let mut address: libc::sockaddr_in = std::mem::zeroed();
1364 address.sin_family = libc::AF_INET as libc::sa_family_t;
1365 address.sin_port = port.to_be();
1366 address.sin_addr.s_addr = libc::INADDR_ANY.to_be();
1367 if libc::bind(
1368 fd,
1369 std::ptr::addr_of!(address).cast(),
1370 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
1371 ) != 0
1372 || libc::listen(fd, 128) != 0
1373 {
1374 libc::close(fd);
1375 return -1;
1376 }
1377 fd
1378 }
1379 }
1380
1381 unsafe fn bind_dns_socket() -> RawFd {
1394 unsafe {
1396 let fd = libc::socket(libc::AF_INET, libc::SOCK_DGRAM | libc::SOCK_CLOEXEC, 0);
1397 if fd < 0 {
1398 return -1;
1399 }
1400 let enable: libc::c_int = 1;
1401 if libc::setsockopt(
1402 fd,
1403 libc::IPPROTO_IP,
1404 libc::IP_PKTINFO,
1405 std::ptr::addr_of!(enable).cast(),
1406 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
1407 ) != 0
1408 {
1409 libc::close(fd);
1410 return -1;
1411 }
1412 let mut address: libc::sockaddr_in = std::mem::zeroed();
1413 address.sin_family = libc::AF_INET as libc::sa_family_t;
1414 address.sin_port = super::DNS_PORT_BE;
1415 address.sin_addr.s_addr = libc::INADDR_ANY.to_be();
1416 if libc::bind(
1417 fd,
1418 std::ptr::addr_of!(address).cast(),
1419 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
1420 ) != 0
1421 {
1422 libc::close(fd);
1423 return -1;
1424 }
1425 fd
1426 }
1427 }
1428
1429 unsafe fn close_all(fds: &[RawFd]) {
1432 for fd in fds {
1433 if *fd >= 0 {
1434 unsafe { libc::close(*fd) };
1436 }
1437 }
1438 }
1439
1440 const CMSG_BUFFER_LEN: usize = 128;
1442
1443 const _: () = assert!(
1449 unsafe {
1453 libc::CMSG_SPACE((MAX_SESSION_NETNS_FDS * std::mem::size_of::<RawFd>()) as u32) as usize
1454 } <= CMSG_BUFFER_LEN,
1455 "CMSG_BUFFER_LEN is too small to hold MAX_SESSION_NETNS_FDS descriptors plus the \
1456 cmsghdr; raise it to match"
1457 );
1458
1459 #[repr(C, align(8))]
1465 struct CmsgBuffer([u8; CMSG_BUFFER_LEN]);
1466
1467 unsafe fn send_fds(sock_fd: RawFd, fds: &[RawFd]) -> Result<(), ()> {
1478 unsafe {
1481 let mut payload = [0u8; 1];
1482 let mut iov = libc::iovec {
1483 iov_base: payload.as_mut_ptr().cast(),
1484 iov_len: payload.len(),
1485 };
1486 let mut control = CmsgBuffer([0u8; CMSG_BUFFER_LEN]);
1487 let bytes = std::mem::size_of_val(fds) as u32;
1488
1489 let mut msg: libc::msghdr = std::mem::zeroed();
1490 msg.msg_iov = &mut iov;
1491 msg.msg_iovlen = 1;
1492 msg.msg_control = control.0.as_mut_ptr().cast();
1493 msg.msg_controllen = libc::CMSG_SPACE(bytes) as _;
1494
1495 let cmsg = libc::CMSG_FIRSTHDR(&msg);
1496 if cmsg.is_null() {
1497 return Err(());
1498 }
1499 (*cmsg).cmsg_level = libc::SOL_SOCKET;
1500 (*cmsg).cmsg_type = libc::SCM_RIGHTS;
1501 (*cmsg).cmsg_len = libc::CMSG_LEN(bytes) as _;
1502 std::ptr::copy_nonoverlapping(
1503 fds.as_ptr().cast::<u8>(),
1504 libc::CMSG_DATA(cmsg),
1505 bytes as usize,
1506 );
1507
1508 if libc::sendmsg(sock_fd, &msg, 0) < 0 {
1509 return Err(());
1510 }
1511 Ok(())
1512 }
1513 }
1514
1515 fn receive_session_netns(sock_fd: RawFd, expected_sockets: usize) -> Result<Received, String> {
1523 unsafe {
1526 let mut payload = [0u8; STEP_RECORD_LEN];
1527 let mut iov = libc::iovec {
1528 iov_base: payload.as_mut_ptr().cast(),
1529 iov_len: payload.len(),
1530 };
1531 let mut control = CmsgBuffer([0u8; CMSG_BUFFER_LEN]);
1532 let mut msg: libc::msghdr = std::mem::zeroed();
1533 msg.msg_iov = &mut iov;
1534 msg.msg_iovlen = 1;
1535 msg.msg_control = control.0.as_mut_ptr().cast();
1536 msg.msg_controllen = control.0.len() as _;
1537
1538 let received = libc::recvmsg(
1539 sock_fd,
1540 &mut msg,
1541 libc::MSG_DONTWAIT | libc::MSG_CMSG_CLOEXEC,
1542 );
1543 if received < 0 {
1544 let error = io::Error::last_os_error();
1545 if error.kind() == io::ErrorKind::WouldBlock {
1546 return Ok(Received::Nothing);
1547 }
1548 return Err(format!("receiving the namespace hand-off failed: {error}"));
1549 }
1550
1551 let cmsg = libc::CMSG_FIRSTHDR(&msg);
1552 if cmsg.is_null() {
1553 if received as usize == STEP_RECORD_LEN {
1554 if let Some(failure) = CreationFailure::decode(&payload) {
1555 return Ok(Received::Failure(failure));
1556 }
1557 }
1558 return Ok(Received::Nothing);
1559 }
1560 if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
1561 return Err(
1562 "the namespace hand-off did not arrive as an SCM_RIGHTS message".to_string(),
1563 );
1564 }
1565 let bytes = (*cmsg).cmsg_len - libc::CMSG_LEN(0) as usize;
1566 let count = bytes / std::mem::size_of::<RawFd>();
1567 let data = libc::CMSG_DATA(cmsg).cast::<RawFd>();
1568 let mut fds = Vec::with_capacity(count);
1571 for index in 0..count {
1572 fds.push(OwnedFd::from_raw_fd(std::ptr::read_unaligned(
1573 data.add(index),
1574 )));
1575 }
1576 if msg.msg_flags & libc::MSG_CTRUNC != 0 {
1577 return Err("the namespace hand-off was truncated".to_string());
1578 }
1579 if count != expected_sockets + 2 {
1580 return Err(format!(
1581 "the namespace creator handed over {count} descriptors; {} were expected \
1582 ({expected_sockets} sockets and 2 namespace handles)",
1583 expected_sockets + 2
1584 ));
1585 }
1586 Ok(Received::Handles(fds))
1587 }
1588 }
1589}
1590
1591#[cfg(target_os = "linux")]
1594const DNS_PORT_BE: u16 = crate::egress_proxy::EGRESS_DNS_PORT.to_be();
1595
1596#[cfg(test)]
1597mod tests {
1598 use super::*;
1599 use crate::sealed::{ProfileAttachment, UsernsGrant};
1600
1601 fn attachments() -> [ProfileAttachment; 4] {
1602 [
1603 ProfileAttachment::Sealed {
1604 profile: "mur-sealed".to_string(),
1605 },
1606 ProfileAttachment::Other {
1607 profile: "docker-default".to_string(),
1608 },
1609 ProfileAttachment::Unconfined,
1610 ProfileAttachment::NotReported,
1611 ]
1612 }
1613
1614 fn step(step: CreationStep, errno: i32) -> Result<(), NamespaceAttemptError> {
1615 Err(NamespaceAttemptError::Step { step, errno })
1616 }
1617
1618 fn failure(
1619 attempt: Result<(), NamespaceAttemptError>,
1620 grant: UsernsGrant,
1621 attachment: ProfileAttachment,
1622 ) -> EgressNamespaceFailure {
1623 egress_namespace_failure(true, attempt, grant, attachment)
1624 .expect("a failed attempt is a failure")
1625 }
1626
1627 #[test]
1632 fn a_created_namespace_is_never_refused_whatever_the_readings() {
1633 for grant in UsernsGrant::ALL {
1634 for attachment in attachments() {
1635 assert_eq!(
1636 egress_namespace_failure(true, Ok(()), *grant, attachment.clone()),
1637 None,
1638 "{grant:?} / {attachment:?}"
1639 );
1640 }
1641 }
1642 }
1643
1644 #[test]
1647 fn a_failed_creation_is_refused_whatever_the_readings() {
1648 let attempts = [
1649 (
1650 step(CreationStep::Unshare, libc::EPERM),
1651 EgressNamespaceBlocker::CapabilityGrantMissing,
1652 ),
1653 (
1654 step(CreationStep::Unshare, libc::ENOSPC),
1655 EgressNamespaceBlocker::KernelSupportMissing,
1656 ),
1657 (
1658 Err(NamespaceAttemptError::Creator("fork() failed".to_string())),
1659 EgressNamespaceBlocker::CreatorUnavailable,
1660 ),
1661 ];
1662 for (attempt, blocker) in attempts {
1663 for grant in UsernsGrant::ALL {
1664 for attachment in attachments() {
1665 let failure =
1666 egress_namespace_failure(true, attempt.clone(), *grant, attachment.clone())
1667 .unwrap_or_else(|| {
1668 panic!("{attempt:?} under {grant:?} / {attachment:?}")
1669 });
1670 assert_eq!(failure.blocker, blocker, "{grant:?} / {attachment:?}");
1671 }
1672 }
1673 }
1674 }
1675
1676 #[test]
1677 fn only_an_unshare_the_kernel_cannot_honour_is_missing_kernel_support() {
1678 for errno in [libc::ENOSPC, libc::EINVAL, libc::ENOSYS, libc::EUSERS] {
1679 assert_eq!(
1680 failure(
1681 step(CreationStep::Unshare, errno),
1682 UsernsGrant::Withheld,
1683 ProfileAttachment::Unconfined
1684 )
1685 .blocker,
1686 EgressNamespaceBlocker::KernelSupportMissing,
1687 "errno {errno}"
1688 );
1689 }
1690 for (failed, errno) in [
1691 (CreationStep::Unshare, libc::EPERM),
1692 (CreationStep::Unshare, libc::EACCES),
1693 (CreationStep::IdentityMaps, libc::EPERM),
1694 (CreationStep::Loopback, libc::EPERM),
1695 (CreationStep::LocalRoute, libc::EINVAL),
1696 (CreationStep::Resolver, libc::ENOSPC),
1697 ] {
1698 assert_eq!(
1699 failure(
1700 step(failed, errno),
1701 UsernsGrant::Withheld,
1702 ProfileAttachment::Unconfined
1703 )
1704 .blocker,
1705 EgressNamespaceBlocker::CapabilityGrantMissing,
1706 "{failed:?} errno {errno}"
1707 );
1708 }
1709 }
1710
1711 #[test]
1712 fn a_creator_that_could_not_run_is_unavailable_and_keeps_its_detail() {
1713 let detail = "fork() for the namespace creator failed: Resource temporarily unavailable \
1714 (os error 11)";
1715 let failure = failure(
1716 Err(NamespaceAttemptError::Creator(detail.to_string())),
1717 UsernsGrant::Withheld,
1718 ProfileAttachment::Unconfined,
1719 );
1720 assert_eq!(failure.blocker, EgressNamespaceBlocker::CreatorUnavailable);
1721 assert_eq!(failure.errno, None);
1722 let reason = failure.reason();
1723 assert!(reason.starts_with(detail), "{reason}");
1724 assert!(!reason.contains("apparmor_parser"), "{reason}");
1725 assert!(!reason.contains("CONFIG_USER_NS"), "{reason}");
1726 assert!(!reason.contains("user.max_user_namespaces"), "{reason}");
1727 }
1728
1729 #[test]
1730 fn non_linux_is_never_refused_for_a_mechanism_it_never_had() {
1731 for attempt in [
1732 Ok(()),
1733 step(CreationStep::Unshare, libc::EPERM),
1734 Err(NamespaceAttemptError::Creator("fork() failed".to_string())),
1735 ] {
1736 assert_eq!(
1737 egress_namespace_failure(
1738 false,
1739 attempt,
1740 UsernsGrant::Withheld,
1741 ProfileAttachment::NotReported
1742 ),
1743 None
1744 );
1745 }
1746 #[cfg(not(target_os = "linux"))]
1747 assert_eq!(detect_egress_namespace_failure(), None);
1748 }
1749
1750 #[test]
1751 fn the_check_refuses_exactly_a_capsule_that_can_spawn_on_a_failing_host() {
1752 let failure = failure(
1753 step(CreationStep::Unshare, libc::EPERM),
1754 UsernsGrant::Withheld,
1755 ProfileAttachment::Unconfined,
1756 );
1757 match check_egress_namespace(true, Some(&failure)) {
1758 Err(crate::errors::RuntimeError::EgressNamespaceUnavailable {
1759 blocker,
1760 reason,
1761 ..
1762 }) => {
1763 assert_eq!(blocker, failure.blocker);
1764 assert_eq!(reason, failure.reason());
1765 }
1766 other => panic!("expected the E-CAP-005 refusal, got {other:?}"),
1767 }
1768 assert!(check_egress_namespace(false, Some(&failure)).is_ok());
1769 assert!(check_egress_namespace(true, None).is_ok());
1770 assert!(check_egress_namespace(false, None).is_ok());
1771 }
1772
1773 #[test]
1776 fn an_unconfined_binary_on_a_restricted_host_is_told_the_profile_before_the_sysctl() {
1777 let reason = failure(
1778 step(CreationStep::Unshare, libc::EPERM),
1779 UsernsGrant::Withheld,
1780 ProfileAttachment::Unconfined,
1781 )
1782 .reason();
1783 assert!(
1784 reason.starts_with(
1785 "unshare(CLONE_NEWUSER | CLONE_NEWNET) failed with EPERM (Operation not \
1786 permitted)."
1787 ),
1788 "{reason}"
1789 );
1790 assert!(
1791 reason.contains("kernel.apparmor_restrict_unprivileged_userns=1"),
1792 "{reason}"
1793 );
1794 let profile = reason
1795 .find("apparmor_parser -r")
1796 .expect("the profile install command must be named");
1797 let sysctl = reason
1798 .find("kernel.apparmor_restrict_unprivileged_userns=0")
1799 .expect("the fallback must still be named for hosts that cannot load a profile");
1800 assert!(
1801 profile < sysctl,
1802 "the profile install must come first: {reason}"
1803 );
1804 assert!(reason.contains("LAST RESORT"), "{reason}");
1805 assert!(reason.contains("every program on the machine"), "{reason}");
1806 assert!(
1807 reason.contains("scripts/install-dev-apparmor.sh"),
1808 "{reason}"
1809 );
1810 assert!(
1811 reason.ends_with(
1812 "Host readings: kernel.apparmor_restrict_unprivileged_userns=1; AppArmor profile \
1813 confining mur: unconfined."
1814 ),
1815 "{reason}"
1816 );
1817 }
1818
1819 #[test]
1820 fn a_binary_another_profile_confines_is_given_the_container_remedy_not_the_sysctl() {
1821 let reason = failure(
1822 step(CreationStep::Unshare, libc::EPERM),
1823 UsernsGrant::Withheld,
1824 ProfileAttachment::Other {
1825 profile: "docker-default".to_string(),
1826 },
1827 )
1828 .reason();
1829 assert!(reason.contains("`docker-default`"), "{reason}");
1830 assert!(!reason.contains("--cap-add"), "{reason}");
1831 assert!(reason.contains("seccomp profile"), "{reason}");
1832 assert!(
1833 reason.contains("unshare(CLONE_NEWUSER) and setns"),
1834 "{reason}"
1835 );
1836 assert!(reason.contains("seccompProfile: Localhost"), "{reason}");
1837 assert!(
1838 !reason.contains("kernel.apparmor_restrict_unprivileged_userns=0"),
1839 "{reason}"
1840 );
1841 assert!(!reason.contains("apparmor_parser"), "{reason}");
1842 assert!(
1843 reason.ends_with("AppArmor profile confining mur: docker-default."),
1844 "{reason}"
1845 );
1846 }
1847
1848 #[test]
1849 fn a_failure_under_the_sealed_profile_points_at_a_stale_load() {
1850 let reason = failure(
1851 step(CreationStep::Unshare, libc::EPERM),
1852 UsernsGrant::ProfileConfining,
1853 ProfileAttachment::Sealed {
1854 profile: "mur-sealed".to_string(),
1855 },
1856 )
1857 .reason();
1858 assert!(
1859 reason.contains("sudo apparmor_parser -r /etc/apparmor.d/mur-sealed"),
1860 "{reason}"
1861 );
1862 assert!(reason.contains("stale"), "{reason}");
1863 assert!(!reason.contains("LAST RESORT"), "{reason}");
1864 }
1865
1866 #[test]
1867 fn without_the_restriction_the_cause_is_not_apparmor() {
1868 for (grant, readings) in [
1869 (
1870 UsernsGrant::RestrictionDisabledHostWide,
1871 "kernel.apparmor_restrict_unprivileged_userns=0",
1872 ),
1873 (UsernsGrant::ApparmorAbsent, "AppArmor not enabled"),
1874 ] {
1875 for attachment in [
1876 ProfileAttachment::Unconfined,
1877 ProfileAttachment::NotReported,
1878 ] {
1879 let reason =
1880 failure(step(CreationStep::Unshare, libc::EPERM), grant, attachment).reason();
1881 assert!(
1882 !reason.contains("apparmor_restrict_unprivileged_userns=1"),
1883 "{reason}"
1884 );
1885 assert!(!reason.contains("apparmor_parser"), "{reason}");
1886 assert!(!reason.contains("--cap-add"), "{reason}");
1887 assert!(!reason.contains("CAP_SYS_ADMIN"), "{reason}");
1888 assert!(
1889 reason.contains("seccomp profile is the usual blocker"),
1890 "{reason}"
1891 );
1892 assert!(
1893 reason.contains("unshare(CLONE_NEWUSER) and setns"),
1894 "{reason}"
1895 );
1896 assert!(reason.contains("--security-opt seccomp=<file>"), "{reason}");
1897 assert!(
1898 reason.contains(&format!("Host readings: {readings};")),
1899 "{reason}"
1900 );
1901 }
1902 }
1903 }
1904
1905 #[test]
1908 fn another_security_modules_label_is_not_blamed_as_an_apparmor_profile() {
1909 let reason = failure(
1910 step(CreationStep::Unshare, libc::EPERM),
1911 UsernsGrant::ApparmorAbsent,
1912 ProfileAttachment::Other {
1913 profile: "unconfined_u:unconfined_r:unconfined_t:s0".to_string(),
1914 },
1915 )
1916 .reason();
1917 assert!(reason.contains("not AppArmor"), "{reason}");
1918 assert!(!reason.contains("the AppArmor profile `"), "{reason}");
1919 }
1920
1921 #[test]
1922 fn a_namespace_created_and_then_refused_says_which_half_failed() {
1923 let configure = failure(
1924 step(CreationStep::Loopback, libc::EPERM),
1925 UsernsGrant::RestrictionDisabledHostWide,
1926 ProfileAttachment::Unconfined,
1927 )
1928 .reason();
1929 assert!(
1930 configure.starts_with("bringing lo up failed with EPERM"),
1931 "{configure}"
1932 );
1933 assert!(configure.contains("CAP_NET_ADMIN"), "{configure}");
1934
1935 let owned = failure(
1936 step(CreationStep::IdentityMaps, libc::EACCES),
1937 UsernsGrant::RestrictionDisabledHostWide,
1938 ProfileAttachment::Unconfined,
1939 )
1940 .reason();
1941 assert!(
1942 owned.starts_with("writing uid_map/gid_map failed with EACCES (Permission denied)."),
1943 "{owned}"
1944 );
1945 assert!(owned.contains("could not be owned"), "{owned}");
1946 }
1947
1948 #[test]
1949 fn missing_kernel_support_names_the_limit_or_the_config_symbol_and_no_apparmor() {
1950 for (errno, named) in [
1951 (libc::ENOSPC, "user.max_user_namespaces"),
1952 (libc::EINVAL, "CONFIG_USER_NS"),
1953 (libc::ENOSYS, "CONFIG_USER_NS"),
1954 (libc::EUSERS, "nesting limit"),
1955 ] {
1956 let reason = failure(
1957 step(CreationStep::Unshare, errno),
1958 UsernsGrant::Withheld,
1959 ProfileAttachment::Unconfined,
1960 )
1961 .reason();
1962 assert!(reason.contains(named), "{reason}");
1963 assert!(
1964 reason.contains(errno_name(errno).expect("a named errno")),
1965 "{reason}"
1966 );
1967 assert!(!reason.contains("apparmor_parser"), "{reason}");
1968 assert!(!reason.contains("LAST RESORT"), "{reason}");
1969 }
1970 }
1971
1972 #[test]
1973 fn an_unnamed_errno_is_rendered_by_number() {
1974 assert_eq!(errno_name(libc::EPERM), Some("EPERM"));
1975 assert_eq!(errno_name(-7), None);
1976 let failure = failure(
1977 step(CreationStep::HandOff, 4095),
1978 UsernsGrant::Withheld,
1979 ProfileAttachment::Unconfined,
1980 );
1981 assert_eq!(
1982 failure.summary(),
1983 "handing the namespace to the runtime failed with errno 4095"
1984 );
1985 }
1986
1987 #[test]
1988 fn the_summary_names_the_call_and_the_errno() {
1989 let failed = failure(
1990 step(CreationStep::Unshare, libc::EPERM),
1991 UsernsGrant::Withheld,
1992 ProfileAttachment::Unconfined,
1993 );
1994 assert_eq!(
1995 failed.summary(),
1996 "unshare(CLONE_NEWUSER | CLONE_NEWNET) failed with EPERM"
1997 );
1998 let creator = failure(
1999 Err(NamespaceAttemptError::Creator("fork() failed".to_string())),
2000 UsernsGrant::Withheld,
2001 ProfileAttachment::Unconfined,
2002 );
2003 assert_eq!(creator.summary(), "fork() failed");
2004 }
2005
2006 #[test]
2007 fn every_step_round_trips_through_its_code() {
2008 for step in CreationStep::ALL {
2009 assert_eq!(CreationStep::from_code(step as u32), Some(step));
2010 }
2011 assert_eq!(CreationStep::from_code(0), None);
2012 assert_eq!(CreationStep::from_code(99), None);
2013 }
2014
2015 #[test]
2020 fn a_session_creation_failure_is_refused_in_the_staging_words() {
2021 for (failed, errno, grant, attachment) in [
2022 (
2023 CreationStep::Unshare,
2024 libc::EPERM,
2025 UsernsGrant::Withheld,
2026 ProfileAttachment::Unconfined,
2027 ),
2028 (
2029 CreationStep::LocalRoute,
2030 libc::EPERM,
2031 UsernsGrant::Withheld,
2032 ProfileAttachment::Other {
2033 profile: "docker-default".to_string(),
2034 },
2035 ),
2036 (
2037 CreationStep::Unshare,
2038 libc::ENOSPC,
2039 UsernsGrant::RestrictionDisabledHostWide,
2040 ProfileAttachment::Unconfined,
2041 ),
2042 ] {
2043 let staging =
2044 egress_namespace_failure(true, step(failed, errno), grant, attachment.clone());
2045 let staging_refusal = check_egress_namespace(true, staging.as_ref())
2046 .expect_err("staging refuses")
2047 .to_string();
2048 let establish = session_netns_creation_error(
2049 NamespaceAttemptError::Step {
2050 step: failed,
2051 errno,
2052 },
2053 grant,
2054 attachment,
2055 );
2056 assert_eq!(establish, format!("sandbox: E-CAP-005: {staging_refusal}"));
2057 assert!(
2058 establish.contains(&staging.expect("a failure").reason()),
2059 "{establish}"
2060 );
2061 }
2062 }
2063
2064 #[test]
2065 fn a_session_creator_that_could_not_run_keeps_the_runtime_account() {
2066 let error = session_netns_creation_error(
2067 NamespaceAttemptError::Creator("fork() for the namespace creator failed".to_string()),
2068 UsernsGrant::Withheld,
2069 ProfileAttachment::Unconfined,
2070 );
2071 assert_eq!(
2072 error,
2073 "sandbox: could not create this session's network namespace: fork() for the \
2074 namespace creator failed"
2075 );
2076 }
2077
2078 #[test]
2081 fn the_socket_count_is_one_per_port_plus_the_resolver() {
2082 let plan = CapsuleNetnsPlan {
2083 tcp_ports: vec![80, 443],
2084 uid: 1000,
2085 gid: 1000,
2086 };
2087 assert_eq!(plan.socket_count(), 3);
2088 }
2089
2090 #[test]
2091 fn a_capsule_with_no_tcp_egress_still_binds_the_resolver() {
2092 let plan = CapsuleNetnsPlan {
2095 tcp_ports: Vec::new(),
2096 uid: 1000,
2097 gid: 1000,
2098 };
2099 assert_eq!(plan.socket_count(), 1);
2100 }
2101
2102 #[test]
2103 fn the_widest_allowlist_still_fits_one_scm_rights_message() {
2104 let plan = CapsuleNetnsPlan {
2105 tcp_ports: (0..crate::egress_proxy::MAX_EGRESS_TCP_PORTS as u16).collect(),
2106 uid: 1000,
2107 gid: 1000,
2108 };
2109 assert!(plan.socket_count() <= MAX_NAMESPACE_SOCKETS);
2110 assert_eq!(MAX_SESSION_NETNS_FDS, MAX_NAMESPACE_SOCKETS + 2);
2112 }
2113
2114 #[cfg(target_os = "linux")]
2118 #[test]
2119 fn an_ended_session_refuses_to_create_a_namespace() {
2120 let netns = SessionNetns::new();
2121 netns.end();
2122 let refusal = netns
2123 .establish(
2124 &[],
2125 crate::egress_proxy::EgressPolicy::new(Vec::new(), Vec::new()),
2126 )
2127 .expect_err("an ended session creates nothing");
2128 assert!(refusal.contains("session has ended"), "{refusal}");
2129 assert_eq!(netns.creations(), 0);
2130 }
2131
2132 #[cfg(target_os = "linux")]
2135 #[test]
2136 fn a_session_creates_its_namespace_once_and_ends_it_for_good() {
2137 if skip_without_egress_namespace(
2138 "a_session_creates_its_namespace_once_and_ends_it_for_good",
2139 ) {
2140 return;
2141 }
2142 let policy = || crate::egress_proxy::EgressPolicy::new(Vec::new(), Vec::new());
2143 let netns = SessionNetns::new();
2144 assert_eq!(
2145 netns.creations(),
2146 0,
2147 "nothing is created before it is needed"
2148 );
2149
2150 let first = netns
2151 .establish(&[], policy())
2152 .expect("the namespace is created");
2153 let second = netns
2154 .establish(&[], policy())
2155 .expect("the namespace is reused");
2156 assert_eq!(netns.creations(), 1);
2157 let inode = |fd: std::os::fd::RawFd| {
2158 std::fs::metadata(format!("/proc/self/fd/{fd}"))
2159 .map(|metadata| std::os::unix::fs::MetadataExt::ino(&metadata))
2160 .unwrap()
2161 };
2162 assert_eq!(inode(first.raw_fds().1), inode(second.raw_fds().1));
2163 let host_netns = std::fs::metadata("/proc/self/ns/net")
2164 .map(|metadata| std::os::unix::fs::MetadataExt::ino(&metadata))
2165 .unwrap();
2166 assert_ne!(
2167 inode(first.raw_fds().1),
2168 host_netns,
2169 "the session's namespace is not the runtime's own"
2170 );
2171
2172 netns.end();
2173 netns.end();
2174 assert!(netns.establish(&[], policy()).is_err());
2175 assert_eq!(
2176 netns.creations(),
2177 1,
2178 "an ended namespace is never recreated"
2179 );
2180 }
2181
2182 #[cfg(target_os = "linux")]
2185 #[test]
2186 fn a_creation_failure_names_its_step_and_errno() {
2187 assert_eq!(
2188 linux::decode_failure_record(4, libc::EPERM),
2189 Some(NamespaceAttemptError::Step {
2190 step: CreationStep::LocalRoute,
2191 errno: libc::EPERM,
2192 })
2193 );
2194 assert_eq!(linux::decode_failure_record(99, 0), None);
2195 }
2196
2197 #[cfg(target_os = "linux")]
2201 #[test]
2202 fn the_probe_and_a_session_creation_agree_about_this_host() {
2203 let probe = detect_egress_namespace_failure();
2204 let session = linux::create_session_netns(&CapsuleNetnsPlan::resolve(Vec::new())).map(drop);
2205 match (&probe, &session) {
2206 (None, Ok(())) => {}
2207 (Some(failure), Err(NamespaceAttemptError::Step { step, errno })) => {
2208 assert_eq!(failure.failed_call, step.describe());
2209 assert_eq!(failure.errno, Some(*errno));
2210 }
2211 other => panic!("the probe and a session creation disagree: {other:?}"),
2212 }
2213 }
2214}