Skip to content

Commit

Permalink
Add cap-std, use in grubconfigs
Browse files Browse the repository at this point in the history
Starting on coreos#449

`openat` is pretty dead upstream, and especially going forward
as we start to do more nontrivial file things, it's really
useful to have cap-std's additional verification that we
aren't accidentally escaping the root.

Signed-off-by: Colin Walters <walters@verbum.org>
  • Loading branch information
cgwalters committed Jun 28, 2024
1 parent 7ae681f commit fd49360
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 18 deletions.
115 changes: 113 additions & 2 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ path = "src/main.rs"
[dependencies]
anyhow = "1.0"
bincode = "1.3.2"
cap-std-ext = "4.0.0"
chrono = { version = "0.4.38", features = ["serde"] }
clap = { version = "3.2", default-features = false, features = ["cargo", "derive", "std", "suggestions"] }
env_logger = "0.10"
Expand Down
55 changes: 39 additions & 16 deletions src/grubconfigs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ use std::fmt::Write;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Context, Result};
use cap_std::fs::{Dir, DirBuilder, DirBuilderExt, MetadataExt};
use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::{Permissions, PermissionsExt};
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;
use openat_ext::OpenatDirExt;

use crate::util;

/// The subdirectory of /boot we use
const GRUB2DIR: &str = "grub2";
Expand All @@ -17,27 +22,34 @@ pub(crate) fn install(
installed_efi_vendor: Option<&str>,
write_uuid: bool,
) -> Result<()> {
let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
let target_root = &util::reopen_dir(target_root)?;
let bootdir = &target_root.open_dir("boot").context("Opening /boot")?;
let boot_is_mount = {
let root_dev = target_root.self_metadata()?.stat().st_dev;
let boot_dev = bootdir.self_metadata()?.stat().st_dev;
let root_dev = target_root.dir_metadata()?.dev();
let boot_dev = bootdir.dir_metadata()?.dev();
log::debug!("root_dev={root_dev} boot_dev={boot_dev}");
root_dev != boot_dev
};

if !bootdir.exists(GRUB2DIR)? {
bootdir.create_dir(GRUB2DIR, 0o700)?;
if !bootdir.try_exists(GRUB2DIR)? {
let mut db = DirBuilder::new();
db.mode(0o700);
bootdir.create_dir_with(GRUB2DIR, &db)?;
}

let mut config = std::fs::read_to_string(Path::new(CONFIGDIR).join("grub-static-pre.cfg"))?;

let dropindir = openat::Dir::open(&Path::new(CONFIGDIR).join(DROPINDIR))?;
let dropindir = Dir::open_ambient_dir(
&Path::new(CONFIGDIR).join(DROPINDIR),
cap_std::ambient_authority(),
)?;
// Sort the files for reproducibility
let mut entries = dropindir
.list_dir(".")?
.entries()?
.map(|e| e.map_err(anyhow::Error::msg))
.collect::<Result<Vec<_>>>()?;
entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
// cc https://github.com/rust-lang/rust/issues/85573#issuecomment-2195271304
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
for ent in entries {
let name = ent.file_name();
let name = name
Expand All @@ -49,7 +61,7 @@ pub(crate) fn install(
}
writeln!(config, "source $prefix/{name}")?;
dropindir
.copy_file_at(name, bootdir, format!("{GRUB2DIR}/{name}"))
.copy(name, bootdir, format!("{GRUB2DIR}/{name}"))
.with_context(|| format!("Copying {name}"))?;
println!("Installed {name}");
}
Expand All @@ -59,21 +71,27 @@ pub(crate) fn install(
config.push_str(post.as_str());
}

let rperms = Permissions::from_mode(0o644);
bootdir
.write_file_contents(format!("{GRUB2DIR}/grub.cfg"), 0o644, config.as_bytes())
.atomic_write_with_perms(
format!("{GRUB2DIR}/grub.cfg"),
config.as_bytes(),
rperms.clone(),
)
.context("Copying grub-static.cfg")?;
println!("Installed: grub.cfg");

let uuid_path = if write_uuid {
let target_fs = if boot_is_mount { bootdir } else { target_root };
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs, ".")?;
let target_fs_dir = &util::reopen_legacy_dir(target_fs)?;
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs_dir, ".")?;
let bootfs_uuid = bootfs_meta
.uuid
.ok_or_else(|| anyhow::anyhow!("Failed to find UUID for boot"))?;
let grub2_uuid_contents = format!("set BOOT_UUID=\"{bootfs_uuid}\"\n");
let uuid_path = format!("{GRUB2DIR}/bootuuid.cfg");
bootdir
.write_file_contents(&uuid_path, 0o644, grub2_uuid_contents)
.atomic_write_with_perms(&uuid_path, grub2_uuid_contents, rperms)
.context("Writing bootuuid.cfg")?;
Some(uuid_path)
} else {
Expand All @@ -85,19 +103,23 @@ pub(crate) fn install(
let vendor = PathBuf::from(vendordir);
let target = &vendor.join("grub.cfg");
let dest_efidir = target_root
.sub_dir_optional("boot/efi/EFI")
.open_dir_optional("boot/efi/EFI")
.context("Opening /boot/efi/EFI")?;
if let Some(efidir) = dest_efidir {
efidir
.copy_file(&Path::new(CONFIGDIR).join("grub-static-efi.cfg"), target)
.copy(
&Path::new(CONFIGDIR).join("grub-static-efi.cfg"),
&efidir,
target,
)
.context("Copying static EFI")?;
println!("Installed: {target:?}");
if let Some(uuid_path) = uuid_path {
// SAFETY: we always have a filename
let filename = Path::new(&uuid_path).file_name().unwrap();
let target = &vendor.join(filename);
bootdir
.copy_file_at(uuid_path, &efidir, target)
.copy(uuid_path, &efidir, target)
.context("Writing bootuuid.cfg to efi dir")?;
}
}
Expand All @@ -109,6 +131,7 @@ pub(crate) fn install(
#[cfg(test)]
mod tests {
use super::*;
use openat_ext::OpenatDirExt;

#[test]
#[ignore]
Expand Down
12 changes: 12 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::collections::HashSet;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::path::Path;
use std::process::Command;

use anyhow::{bail, Context, Result};
use cap_std_ext::cap_std::fs::Dir;
use openat_ext::OpenatDirExt;

pub(crate) trait CommandRunExt {
Expand Down Expand Up @@ -99,3 +101,13 @@ pub(crate) fn cmd_output(cmd: &mut Command) -> Result<String> {
String::from_utf8(result.stdout)
.with_context(|| format!("decoding as UTF-8 output of `{:#?}`", cmd))
}

// Re-open an [`openat::Dir`] via the cap-std version.
pub(crate) fn reopen_dir(d: &openat::Dir) -> Result<Dir> {
Dir::reopen_dir(&unsafe { BorrowedFd::borrow_raw(d.as_raw_fd()) }).map_err(Into::into)
}

// Re-open an [`cap_std::fs::Dir`] as a legacy openat::Dir.
pub(crate) fn reopen_legacy_dir(d: &Dir) -> Result<openat::Dir> {
openat::Dir::open(format!("/proc/self/fd/{}", d.as_raw_fd())).map_err(Into::into)
}

0 comments on commit fd49360

Please sign in to comment.