Skip to content

Commit fd49360

Browse files
committed
Add cap-std, use in grubconfigs
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 <[email protected]>
1 parent 7ae681f commit fd49360

File tree

4 files changed

+165
-18
lines changed

4 files changed

+165
-18
lines changed

Cargo.lock

Lines changed: 113 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ path = "src/main.rs"
2121
[dependencies]
2222
anyhow = "1.0"
2323
bincode = "1.3.2"
24+
cap-std-ext = "4.0.0"
2425
chrono = { version = "0.4.38", features = ["serde"] }
2526
clap = { version = "3.2", default-features = false, features = ["cargo", "derive", "std", "suggestions"] }
2627
env_logger = "0.10"

src/grubconfigs.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ use std::fmt::Write;
22
use std::path::{Path, PathBuf};
33

44
use anyhow::{anyhow, Context, Result};
5+
use cap_std::fs::{Dir, DirBuilder, DirBuilderExt, MetadataExt};
6+
use cap_std_ext::cap_std;
7+
use cap_std_ext::cap_std::fs::{Permissions, PermissionsExt};
8+
use cap_std_ext::dirext::CapStdExtDirExt;
59
use fn_error_context::context;
6-
use openat_ext::OpenatDirExt;
10+
11+
use crate::util;
712

813
/// The subdirectory of /boot we use
914
const GRUB2DIR: &str = "grub2";
@@ -17,27 +22,34 @@ pub(crate) fn install(
1722
installed_efi_vendor: Option<&str>,
1823
write_uuid: bool,
1924
) -> Result<()> {
20-
let bootdir = &target_root.sub_dir("boot").context("Opening /boot")?;
25+
let target_root = &util::reopen_dir(target_root)?;
26+
let bootdir = &target_root.open_dir("boot").context("Opening /boot")?;
2127
let boot_is_mount = {
22-
let root_dev = target_root.self_metadata()?.stat().st_dev;
23-
let boot_dev = bootdir.self_metadata()?.stat().st_dev;
28+
let root_dev = target_root.dir_metadata()?.dev();
29+
let boot_dev = bootdir.dir_metadata()?.dev();
2430
log::debug!("root_dev={root_dev} boot_dev={boot_dev}");
2531
root_dev != boot_dev
2632
};
2733

28-
if !bootdir.exists(GRUB2DIR)? {
29-
bootdir.create_dir(GRUB2DIR, 0o700)?;
34+
if !bootdir.try_exists(GRUB2DIR)? {
35+
let mut db = DirBuilder::new();
36+
db.mode(0o700);
37+
bootdir.create_dir_with(GRUB2DIR, &db)?;
3038
}
3139

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

34-
let dropindir = openat::Dir::open(&Path::new(CONFIGDIR).join(DROPINDIR))?;
42+
let dropindir = Dir::open_ambient_dir(
43+
&Path::new(CONFIGDIR).join(DROPINDIR),
44+
cap_std::ambient_authority(),
45+
)?;
3546
// Sort the files for reproducibility
3647
let mut entries = dropindir
37-
.list_dir(".")?
48+
.entries()?
3849
.map(|e| e.map_err(anyhow::Error::msg))
3950
.collect::<Result<Vec<_>>>()?;
40-
entries.sort_by(|a, b| a.file_name().cmp(b.file_name()));
51+
// cc https://github.com/rust-lang/rust/issues/85573#issuecomment-2195271304
52+
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
4153
for ent in entries {
4254
let name = ent.file_name();
4355
let name = name
@@ -49,7 +61,7 @@ pub(crate) fn install(
4961
}
5062
writeln!(config, "source $prefix/{name}")?;
5163
dropindir
52-
.copy_file_at(name, bootdir, format!("{GRUB2DIR}/{name}"))
64+
.copy(name, bootdir, format!("{GRUB2DIR}/{name}"))
5365
.with_context(|| format!("Copying {name}"))?;
5466
println!("Installed {name}");
5567
}
@@ -59,21 +71,27 @@ pub(crate) fn install(
5971
config.push_str(post.as_str());
6072
}
6173

74+
let rperms = Permissions::from_mode(0o644);
6275
bootdir
63-
.write_file_contents(format!("{GRUB2DIR}/grub.cfg"), 0o644, config.as_bytes())
76+
.atomic_write_with_perms(
77+
format!("{GRUB2DIR}/grub.cfg"),
78+
config.as_bytes(),
79+
rperms.clone(),
80+
)
6481
.context("Copying grub-static.cfg")?;
6582
println!("Installed: grub.cfg");
6683

6784
let uuid_path = if write_uuid {
6885
let target_fs = if boot_is_mount { bootdir } else { target_root };
69-
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs, ".")?;
86+
let target_fs_dir = &util::reopen_legacy_dir(target_fs)?;
87+
let bootfs_meta = crate::filesystem::inspect_filesystem(target_fs_dir, ".")?;
7088
let bootfs_uuid = bootfs_meta
7189
.uuid
7290
.ok_or_else(|| anyhow::anyhow!("Failed to find UUID for boot"))?;
7391
let grub2_uuid_contents = format!("set BOOT_UUID=\"{bootfs_uuid}\"\n");
7492
let uuid_path = format!("{GRUB2DIR}/bootuuid.cfg");
7593
bootdir
76-
.write_file_contents(&uuid_path, 0o644, grub2_uuid_contents)
94+
.atomic_write_with_perms(&uuid_path, grub2_uuid_contents, rperms)
7795
.context("Writing bootuuid.cfg")?;
7896
Some(uuid_path)
7997
} else {
@@ -85,19 +103,23 @@ pub(crate) fn install(
85103
let vendor = PathBuf::from(vendordir);
86104
let target = &vendor.join("grub.cfg");
87105
let dest_efidir = target_root
88-
.sub_dir_optional("boot/efi/EFI")
106+
.open_dir_optional("boot/efi/EFI")
89107
.context("Opening /boot/efi/EFI")?;
90108
if let Some(efidir) = dest_efidir {
91109
efidir
92-
.copy_file(&Path::new(CONFIGDIR).join("grub-static-efi.cfg"), target)
110+
.copy(
111+
&Path::new(CONFIGDIR).join("grub-static-efi.cfg"),
112+
&efidir,
113+
target,
114+
)
93115
.context("Copying static EFI")?;
94116
println!("Installed: {target:?}");
95117
if let Some(uuid_path) = uuid_path {
96118
// SAFETY: we always have a filename
97119
let filename = Path::new(&uuid_path).file_name().unwrap();
98120
let target = &vendor.join(filename);
99121
bootdir
100-
.copy_file_at(uuid_path, &efidir, target)
122+
.copy(uuid_path, &efidir, target)
101123
.context("Writing bootuuid.cfg to efi dir")?;
102124
}
103125
}
@@ -109,6 +131,7 @@ pub(crate) fn install(
109131
#[cfg(test)]
110132
mod tests {
111133
use super::*;
134+
use openat_ext::OpenatDirExt;
112135

113136
#[test]
114137
#[ignore]

src/util.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::collections::HashSet;
2+
use std::os::fd::{AsRawFd, BorrowedFd};
23
use std::path::Path;
34
use std::process::Command;
45

56
use anyhow::{bail, Context, Result};
7+
use cap_std_ext::cap_std::fs::Dir;
68
use openat_ext::OpenatDirExt;
79

810
pub(crate) trait CommandRunExt {
@@ -99,3 +101,13 @@ pub(crate) fn cmd_output(cmd: &mut Command) -> Result<String> {
99101
String::from_utf8(result.stdout)
100102
.with_context(|| format!("decoding as UTF-8 output of `{:#?}`", cmd))
101103
}
104+
105+
// Re-open an [`openat::Dir`] via the cap-std version.
106+
pub(crate) fn reopen_dir(d: &openat::Dir) -> Result<Dir> {
107+
Dir::reopen_dir(&unsafe { BorrowedFd::borrow_raw(d.as_raw_fd()) }).map_err(Into::into)
108+
}
109+
110+
// Re-open an [`cap_std::fs::Dir`] as a legacy openat::Dir.
111+
pub(crate) fn reopen_legacy_dir(d: &Dir) -> Result<openat::Dir> {
112+
openat::Dir::open(format!("/proc/self/fd/{}", d.as_raw_fd())).map_err(Into::into)
113+
}

0 commit comments

Comments
 (0)