Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/initramfs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ publish = false
[dependencies]
anyhow.workspace = true
clap = { workspace = true, features = ["std", "help", "usage", "derive"] }
libc.workspace = true
rustix.workspace = true
serde = { workspace = true, features = ["derive"] }
composefs.workspace = true
Expand Down
58 changes: 54 additions & 4 deletions crates/initramfs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
ffi::OsString,
fmt::Debug,
io::ErrorKind,
os::fd::{AsFd, OwnedFd},
os::fd::{AsFd, AsRawFd, OwnedFd},
path::{Path, PathBuf},
};

Expand All @@ -31,6 +31,49 @@ use composefs_boot::cmdline::get_cmdline_composefs;

use fn_error_context::context;

// mount_setattr syscall support
const MOUNT_ATTR_RDONLY: u64 = 0x00000001;

#[repr(C)]
struct MountAttr {
attr_set: u64,
attr_clr: u64,
propagation: u64,
userns_fd: u64,
}

/// Set mount attributes using mount_setattr syscall
#[context("Setting mount attributes")]
#[allow(unsafe_code)]
fn mount_setattr(fd: impl AsFd, flags: libc::c_int, attr: &MountAttr) -> Result<()> {
let ret = unsafe {
libc::syscall(
libc::SYS_mount_setattr,
fd.as_fd().as_raw_fd(),
c"".as_ptr(),
flags,
attr as *const MountAttr,
std::mem::size_of::<MountAttr>(),
)
};
if ret == -1 {
Err(std::io::Error::last_os_error())?;
}
Ok(())
}

/// Set mount to readonly recursively
#[context("Setting mount readonly recursively")]
fn set_mount_readonly_recursive(fd: impl AsFd) -> Result<()> {
let attr = MountAttr {
attr_set: MOUNT_ATTR_RDONLY,
attr_clr: 0,
propagation: 0,
userns_fd: 0,
};
mount_setattr(fd, libc::AT_EMPTY_PATH | libc::AT_RECURSIVE, &attr)
}

// Config file
#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
Expand Down Expand Up @@ -193,8 +236,7 @@ fn open_root_fs(path: &Path) -> Result<OwnedFd> {
OpenTreeFlags::OPEN_TREE_CLONE | OpenTreeFlags::OPEN_TREE_CLOEXEC,
)?;

// https://github.com/bytecodealliance/rustix/issues/975
// mount_setattr(rootfs.as_fd()), ..., { ... MountAttrFlags::MOUNT_ATTR_RDONLY ... }, ...)?;
set_mount_readonly_recursive(&rootfs)?;

Ok(rootfs)
}
Expand All @@ -209,7 +251,13 @@ fn open_root_fs(path: &Path) -> Result<OwnedFd> {
pub fn mount_composefs_image(sysroot: &OwnedFd, name: &str, insecure: bool) -> Result<OwnedFd> {
let mut repo = Repository::<Sha512HashValue>::open_path(sysroot, "composefs")?;
repo.set_insecure(insecure);
repo.mount(name).context("Failed to mount composefs image")
let rootfs = repo
.mount(name)
.context("Failed to mount composefs image")?;

set_mount_readonly_recursive(&rootfs)?;

Ok(rootfs)
}

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

set_mount_readonly_recursive(&sysroot_clone)?;

// Ideally we build the new root filesystem together before we mount it, but that only works on
// 6.15 and later. Before 6.15 we can't mount into a floating tree, so mount it first. This
// will leave an abandoned clone of the sysroot mounted under it, but that's OK for now.
Expand Down
31 changes: 12 additions & 19 deletions tmt/tests/booted/readonly/001-test-status.nu
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,19 @@ use tap.nu

tap begin "verify bootc status output formats"

# Verify /sysroot is not writable initially (read-only operations should not make it writable)
let is_writable = (do -i { /bin/test -w /sysroot } | complete | get exit_code) == 0
assert (not $is_writable) "/sysroot should not be writable initially"

# Double-check with findmnt
let mnt = (findmnt /sysroot -J | from json)
let opts = ($mnt.filesystems.0.options | split row ",")
assert ($opts | any { |o| $o == "ro" }) "/sysroot should be mounted read-only"

let st = bootc status --json | from json
# Detect composefs by checking if composefs field is present
let is_composefs = ($st.status.booted.composefs? != null)

# FIXME: Should be mounting /sysroot readonly in composefs by default
if not $is_composefs {
# Verify /sysroot is not writable initially (read-only operations should not make it writable)
let is_writable = (do -i { /bin/test -w /sysroot } | complete | get exit_code) == 0
assert (not $is_writable) "/sysroot should not be writable initially"

# Double-check with findmnt
let mnt = (findmnt /sysroot -J | from json)
let opts = ($mnt.filesystems.0.options | split row ",")
assert ($opts | any { |o| $o == "ro" }) "/sysroot should be mounted read-only"
}

let st = bootc status --json | from json
assert equal $st.apiVersion org.containers.bootc/v1

let st = bootc status --json --format-version=0 | from json
Expand All @@ -43,11 +39,8 @@ if not $is_composefs {
assert (($st.status | get rollback | default null) == null)
assert (($st.status | get staged | default null) == null)

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

tap ok