Skip to content

Commit 8ad41da

Browse files
committed
initramfs: Mount /sysroot readonly for composefs by default
This implements readonly mounting of /sysroot for composefs systems, matching the behavior that ostree systems already have. Previously, composefs left /sysroot mounted read-write, which was inconsistent and meant the readonly tests had to be skipped for composefs. The implementation uses a direct `libc::syscall` wrapper for `mount_setattr` since rustix doesn't yet provide this API. The `MOUNT_ATTR_RDONLY` flag is applied recursively to three mount points during initramfs setup: - The composefs rootfs image mount (becomes `/` after switch-root) - The test root filesystem mount (used in testing scenarios) - The sysroot clone mount (becomes `/sysroot` in the booted system) With this change, the readonly /sysroot tests in test-status.nu now run for both ostree and composefs systems without conditional checks. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters <[email protected]>
1 parent c0b9cde commit 8ad41da

File tree

4 files changed

+78
-22
lines changed

4 files changed

+78
-22
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/initramfs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ publish = false
88
[dependencies]
99
anyhow.workspace = true
1010
clap = { workspace = true, features = ["std", "help", "usage", "derive"] }
11+
libc.workspace = true
1112
rustix.workspace = true
1213
serde = { workspace = true, features = ["derive"] }
1314
composefs.workspace = true

crates/initramfs/src/lib.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
ffi::OsString,
55
fmt::Debug,
66
io::ErrorKind,
7-
os::fd::{AsFd, OwnedFd},
7+
os::fd::{AsFd, AsRawFd, OwnedFd},
88
path::{Path, PathBuf},
99
};
1010

@@ -31,6 +31,38 @@ use composefs_boot::cmdline::get_cmdline_composefs;
3131

3232
use fn_error_context::context;
3333

34+
// mount_setattr syscall support
35+
const MOUNT_ATTR_RDONLY: u64 = 0x00000001;
36+
const AT_RECURSIVE: libc::c_int = 0x8000;
37+
38+
#[repr(C)]
39+
struct MountAttr {
40+
attr_set: u64,
41+
attr_clr: u64,
42+
propagation: u64,
43+
userns_fd: u64,
44+
}
45+
46+
/// Set mount attributes using mount_setattr syscall
47+
#[context("Setting mount attributes")]
48+
#[allow(unsafe_code)]
49+
fn mount_setattr(fd: impl AsFd, flags: libc::c_int, attr: &MountAttr) -> Result<()> {
50+
let ret = unsafe {
51+
libc::syscall(
52+
libc::SYS_mount_setattr,
53+
fd.as_fd().as_raw_fd(),
54+
c"".as_ptr(),
55+
flags,
56+
attr as *const MountAttr,
57+
std::mem::size_of::<MountAttr>(),
58+
)
59+
};
60+
if ret == -1 {
61+
Err(std::io::Error::last_os_error())?;
62+
}
63+
Ok(())
64+
}
65+
3466
// Config file
3567
#[derive(Clone, Copy, Debug, Deserialize)]
3668
#[serde(rename_all = "lowercase")]
@@ -193,8 +225,14 @@ fn open_root_fs(path: &Path) -> Result<OwnedFd> {
193225
OpenTreeFlags::OPEN_TREE_CLONE | OpenTreeFlags::OPEN_TREE_CLOEXEC,
194226
)?;
195227

196-
// https://github.com/bytecodealliance/rustix/issues/975
197-
// mount_setattr(rootfs.as_fd()), ..., { ... MountAttrFlags::MOUNT_ATTR_RDONLY ... }, ...)?;
228+
// Set the rootfs to readonly
229+
let attr = MountAttr {
230+
attr_set: MOUNT_ATTR_RDONLY,
231+
attr_clr: 0,
232+
propagation: 0,
233+
userns_fd: 0,
234+
};
235+
mount_setattr(&rootfs, libc::AT_EMPTY_PATH | AT_RECURSIVE, &attr)?;
198236

199237
Ok(rootfs)
200238
}
@@ -209,7 +247,20 @@ fn open_root_fs(path: &Path) -> Result<OwnedFd> {
209247
pub fn mount_composefs_image(sysroot: &OwnedFd, name: &str, insecure: bool) -> Result<OwnedFd> {
210248
let mut repo = Repository::<Sha512HashValue>::open_path(sysroot, "composefs")?;
211249
repo.set_insecure(insecure);
212-
repo.mount(name).context("Failed to mount composefs image")
250+
let rootfs = repo
251+
.mount(name)
252+
.context("Failed to mount composefs image")?;
253+
254+
// Set the rootfs to readonly
255+
let attr = MountAttr {
256+
attr_set: MOUNT_ATTR_RDONLY,
257+
attr_clr: 0,
258+
propagation: 0,
259+
userns_fd: 0,
260+
};
261+
mount_setattr(&rootfs, libc::AT_EMPTY_PATH | AT_RECURSIVE, &attr)?;
262+
263+
Ok(rootfs)
213264
}
214265

215266
#[context("Mounting subdirectory")]
@@ -292,6 +343,15 @@ pub fn setup_root(args: Args) -> Result<()> {
292343
// we need to clone this before the next step to make sure we get the old one
293344
let sysroot_clone = bind_mount(&sysroot, "")?;
294345

346+
// Set the sysroot clone to readonly
347+
let attr = MountAttr {
348+
attr_set: MOUNT_ATTR_RDONLY,
349+
attr_clr: 0,
350+
propagation: 0,
351+
userns_fd: 0,
352+
};
353+
mount_setattr(&sysroot_clone, libc::AT_EMPTY_PATH | AT_RECURSIVE, &attr)?;
354+
295355
// Ideally we build the new root filesystem together before we mount it, but that only works on
296356
// 6.15 and later. Before 6.15 we can't mount into a floating tree, so mount it first. This
297357
// will leave an abandoned clone of the sysroot mounted under it, but that's OK for now.

tmt/tests/booted/readonly/001-test-status.nu

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,19 @@ use tap.nu
33

44
tap begin "verify bootc status output formats"
55

6+
# Verify /sysroot is not writable initially (read-only operations should not make it writable)
7+
let is_writable = (do -i { /bin/test -w /sysroot } | complete | get exit_code) == 0
8+
assert (not $is_writable) "/sysroot should not be writable initially"
9+
10+
# Double-check with findmnt
11+
let mnt = (findmnt /sysroot -J | from json)
12+
let opts = ($mnt.filesystems.0.options | split row ",")
13+
assert ($opts | any { |o| $o == "ro" }) "/sysroot should be mounted read-only"
14+
615
let st = bootc status --json | from json
716
# Detect composefs by checking if composefs field is present
817
let is_composefs = ($st.status.booted.composefs? != null)
918

10-
# FIXME: Should be mounting /sysroot readonly in composefs by default
11-
if not $is_composefs {
12-
# Verify /sysroot is not writable initially (read-only operations should not make it writable)
13-
let is_writable = (do -i { /bin/test -w /sysroot } | complete | get exit_code) == 0
14-
assert (not $is_writable) "/sysroot should not be writable initially"
15-
16-
# Double-check with findmnt
17-
let mnt = (findmnt /sysroot -J | from json)
18-
let opts = ($mnt.filesystems.0.options | split row ",")
19-
assert ($opts | any { |o| $o == "ro" }) "/sysroot should be mounted read-only"
20-
}
21-
2219
let st = bootc status --json | from json
2320
assert equal $st.apiVersion org.containers.bootc/v1
2421

@@ -43,11 +40,8 @@ if not $is_composefs {
4340
assert (($st.status | get rollback | default null) == null)
4441
assert (($st.status | get staged | default null) == null)
4542

46-
# FIXME: See above re /sysroot ro
47-
if not $is_composefs {
48-
# Verify /sysroot is still not writable after bootc status (regression test for PR #1718)
49-
let is_writable = (do -i { /bin/test -w /sysroot } | complete | get exit_code) == 0
50-
assert (not $is_writable) "/sysroot should remain read-only after bootc status"
51-
}
43+
# Verify /sysroot is still not writable after bootc status (regression test for PR #1718)
44+
let is_writable = (do -i { /bin/test -w /sysroot } | complete | get exit_code) == 0
45+
assert (not $is_writable) "/sysroot should remain read-only after bootc status"
5246

5347
tap ok

0 commit comments

Comments
 (0)