Skip to content

Commit d250000

Browse files
committed
cli: Gate IPC namespace entrance on CAP_SYS_ADMIN
Extract the IPC namespace joining logic into a join_host_ipc_namespace() helper that checks the effective capability set for CAP_SYS_ADMIN first, since setns() requires it. Without the capability we skip the operation entirely. Fixes: #2090 Assisted-by: OpenCode (Claude Opus 4) Signed-off-by: Colin Walters <walters@verbum.org>
1 parent f450cf3 commit d250000

2 files changed

Lines changed: 35 additions & 10 deletions

File tree

crates/lib/src/cli.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,16 +1531,23 @@ async fn usroverlay(access_mode: FilesystemOverlayAccessMode) -> Result<()> {
15311531
Err(Command::new("ostree").args(args).exec().into())
15321532
}
15331533

1534-
/// Perform process global initialization. This should be called as early as possible
1535-
/// in the standard `main` function.
1536-
#[allow(unsafe_code)]
1537-
pub fn global_init() -> Result<()> {
1538-
// Join the host IPC namespace if we're in an isolated one. Inside a
1539-
// container with a separate IPC namespace (the podman/docker default),
1540-
// udevd on the host cannot see the container's semaphores, causing
1541-
// cryptsetup operations to deadlock on semop(). The primary fix is to
1542-
// run the install container with --ipc=host; this is defense-in-depth
1543-
// for cases where the caller forgets that flag.
1534+
/// Join the host IPC namespace if we're in an isolated one and have
1535+
/// sufficient privileges. The default for `podman run` is a separate IPC
1536+
/// namespace, which for e.g. `bootc install` can cause failures where tools
1537+
/// like udev/cryptsetup expect semaphores to be in sync with the host.
1538+
/// While we do want callers to pass `--ipc=host`, we don't want to force
1539+
/// them to need to either.
1540+
///
1541+
/// Requires `CAP_SYS_ADMIN` (needed for `setns()`); silently skipped when
1542+
/// running unprivileged (e.g. during RPM build for manpage generation).
1543+
fn join_host_ipc_namespace() -> Result<()> {
1544+
let caps = rustix::thread::capabilities(None).context("capget")?;
1545+
if !caps
1546+
.effective
1547+
.contains(rustix::thread::CapabilitySet::SYS_ADMIN)
1548+
{
1549+
return Ok(());
1550+
}
15441551
let ns_pid1 = std::fs::read_link("/proc/1/ns/ipc").context("reading /proc/1/ns/ipc")?;
15451552
let ns_self = std::fs::read_link("/proc/self/ns/ipc").context("reading /proc/self/ns/ipc")?;
15461553
if ns_pid1 != ns_self {
@@ -1552,6 +1559,14 @@ pub fn global_init() -> Result<()> {
15521559
.context("setns(ipc)")?;
15531560
tracing::debug!("Joined pid1 IPC namespace");
15541561
}
1562+
Ok(())
1563+
}
1564+
1565+
/// Perform process global initialization. This should be called as early as possible
1566+
/// in the standard `main` function.
1567+
#[allow(unsafe_code)]
1568+
pub fn global_init() -> Result<()> {
1569+
join_host_ipc_namespace()?;
15551570
// In some cases we re-exec with a temporary binary,
15561571
// so ensure that the syslog identifier is set.
15571572
ostree::glib::set_prgname(bootc_utils::NAME.into());
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use std assert
2+
use tap.nu
3+
4+
tap begin "verify bootc works as non-root user"
5+
6+
# Verify that basic CLI operations succeed when run as an unprivileged
7+
# dynamic user (regression test for the CAP_SYS_ADMIN gate in global_init).
8+
systemd-run -qP -p DynamicUser=yes bootc --help
9+
10+
tap ok

0 commit comments

Comments
 (0)