Skip to content

Ensure ESP is mounted #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 5, 2021
Merged
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ clap = "~2.33"
env_logger = "^0.8"
fs2 = "0.4.3"
hex = "0.4.2"
lazy_static = "1.4.0"
libc = "^0.2"
libsystemd = "^0.2"
log = "^0.4"
Expand Down
60 changes: 47 additions & 13 deletions src/efi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use anyhow::{bail, Context, Result};
use openat_ext::OpenatDirExt;

use chrono::prelude::*;
use lazy_static::lazy_static;

use crate::component::*;
use crate::filetree;
Expand All @@ -22,27 +23,64 @@ use crate::ostreeutil;
use crate::util;
use crate::util::CommandRunExt;

/// The path to the ESP mount
pub(crate) const MOUNT_PATH: &str = "boot/efi";
/// The ESP partition label
pub(crate) const ESP_PART_LABEL: &str = "EFI-SYSTEM";

#[macro_use]
lazy_static! {
/// The path to a temporary ESP mount
static ref MOUNT_PATH: PathBuf = {
// Create new directory in /tmp with randomly generated name at runtime for ESP mount path.
tempfile::tempdir_in("/tmp").expect("Failed to create temp dir for EFI mount").into_path()
};
}

#[derive(Default)]
pub(crate) struct EFI {}

impl EFI {
fn esp_path(&self) -> PathBuf {
Path::new(MOUNT_PATH).join("EFI")
Path::new(&*MOUNT_PATH).join("EFI")
}

fn esp_device(&self) -> PathBuf {
Path::new("/dev/disk/by-partlabel/").join(ESP_PART_LABEL)
}

fn open_esp_optional(&self) -> Result<Option<openat::Dir>> {
self.ensure_mounted_esp()?;
let sysroot = openat::Dir::open("/")?;
let esp = sysroot.sub_dir_optional(&self.esp_path())?;
Ok(esp)
}
fn open_esp(&self) -> Result<openat::Dir> {
self.ensure_mounted_esp()?;
let sysroot = openat::Dir::open("/")?;
let esp = sysroot.sub_dir(&self.esp_path())?;
Ok(esp)
}

fn ensure_mounted_esp(&self) -> Result<()> {
let mount_point = &Path::new("/").join(&*MOUNT_PATH);
let output = std::process::Command::new("mountpoint")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't bad, but now that we know our process is the only one doing the mount (in our mount namespace), I think it'd be better to have a something like Arc<Mutex<Option<PathBuf>>> or...actually there's a lazy_init crate for that even.

Basically do the tempdir creation + mount as a single locked unit instead.

.arg(mount_point)
.output()?;
if !output.status.success() {
let esp_device = &self.esp_device();
if !esp_device.exists() {
log::error!("Single ESP device not found; ESP on multiple independent filesystems currently unsupported");
anyhow::bail!("Could not find {:?}", esp_device);
}
let status = std::process::Command::new("mount")
.arg(&self.esp_device())
.arg(mount_point)
.status()?;
if !status.success() {
anyhow::bail!("Failed to mount {:?}", esp_device);
}
};
Ok(())
}
}

impl Component for EFI {
Expand Down Expand Up @@ -94,7 +132,6 @@ impl Component for EFI {
let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?;
// For adoption, we should only touch files that we know about.
let diff = updatef.relative_diff_to(&esp)?;
ensure_writable_efi()?;
log::trace!("applying adoption diff: {}", &diff);
filetree::apply_diff(&updated, &esp, &diff, None).context("applying filesystem changes")?;
Ok(InstalledContent {
Expand All @@ -112,7 +149,8 @@ impl Component for EFI {
};
let srcdir_name = component_updatedirname(self);
let ft = crate::filetree::FileTree::new_from_dir(&src_root.sub_dir(&srcdir_name)?)?;
let destdir = Path::new(dest_root).join(MOUNT_PATH);
self.ensure_mounted_esp()?;
let destdir = Path::new(dest_root).join(&*MOUNT_PATH);
{
let destd = openat::Dir::open(&destdir)
.with_context(|| format!("opening dest dir {}", destdir.display()))?;
Expand Down Expand Up @@ -151,10 +189,9 @@ impl Component for EFI {
.context("opening update dir")?;
let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?;
let diff = currentf.diff(&updatef)?;
let destdir = openat::Dir::open(&Path::new("/").join(MOUNT_PATH).join("EFI"))
.context("opening EFI dir")?;
self.ensure_mounted_esp()?;
let destdir = self.open_esp().context("opening EFI dir")?;
validate_esp(&destdir)?;
ensure_writable_efi()?;
log::trace!("applying diff: {}", &diff);
filetree::apply_diff(&updated, &destdir, &diff, None)
.context("applying filesystem changes")?;
Expand Down Expand Up @@ -256,7 +293,8 @@ impl Component for EFI {
.filetree
.as_ref()
.ok_or_else(|| anyhow::anyhow!("No filetree for installed EFI found!"))?;
let efidir = openat::Dir::open(&Path::new("/").join(MOUNT_PATH).join("EFI"))?;
self.ensure_mounted_esp()?;
let efidir = self.open_esp()?;
let diff = currentf.relative_diff_to(&efidir)?;
let mut errs = Vec::new();
for f in diff.changes.iter() {
Expand All @@ -282,7 +320,3 @@ fn validate_esp(dir: &openat::Dir) -> Result<()> {
};
Ok(())
}

fn ensure_writable_efi() -> Result<()> {
util::ensure_writable_mount(&Path::new("/").join(MOUNT_PATH))
}
1 change: 0 additions & 1 deletion systemd/bootupd.service
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ ProtectHome=yes
ReadOnlyPaths=/usr
PrivateTmp=yes
PrivateNetwork=yes
ProtectClock=yes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably something going wrong in the seccomp policy systemd makes. It's...about time...we looked at that.

ProtectHostname=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
Expand Down
7 changes: 5 additions & 2 deletions tests/e2e-adopt/e2e-adopt-in-vm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,13 @@ if test "$semode" != Enforcing; then
fatal "SELinux mode is ${semode}"
fi

source_grub_cfg=$(find /boot/efi/EFI -name grub.cfg)
# Mount the EFI partition.
tmpefimount=$(mount_tmp_efi)

source_grub_cfg=$(find ${tmpefimount}/EFI -name grub.cfg)
test -f "${source_grub_cfg}"

source_grub=$(find /boot/efi/EFI -name grubx64.efi)
source_grub=$(find ${tmpefimount}/EFI -name grubx64.efi)
test -f ${source_grub}
source_grub_sha256=$(sha256sum ${source_grub} | cut -f 1 -d ' ')

Expand Down
7 changes: 5 additions & 2 deletions tests/e2e-update/e2e-update-in-vm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,15 @@ bootupctl status --print-if-available > out.txt
assert_file_has_content_literal 'out.txt' 'Updates available: EFI'
ok update avail

assert_not_has_file /boot/efi/EFI/fedora/test-bootupd.efi
# Mount the EFI partition.
tmpefimount=$(mount_tmp_efi)

assert_not_has_file ${tmpefimount}/EFI/fedora/test-bootupd.efi

bootupctl update | tee out.txt
assert_file_has_content out.txt "Updated EFI: ${TARGET_GRUB_PKG}.*,test-bootupd-payload-1.0"

assert_file_has_content /boot/efi/EFI/fedora/test-bootupd.efi test-payload
assert_file_has_content ${tmpefimount}/EFI/fedora/test-bootupd.efi test-payload

bootupctl status --print-if-available > out.txt
if test -s out.txt; then
Expand Down
8 changes: 8 additions & 0 deletions tests/kola/data/libtest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,11 @@ assert_not_file_has_content_literal () {
done
}

# Mount the EFI partition at a temporary location.
efipart=/dev/disk/by-partlabel/EFI-SYSTEM
mount_tmp_efi () {
tmpmount=$(mktemp -d)
mkdir -p ${tmpmount}
mount ${efipart} ${tmpmount}
echo ${tmpmount}
}
18 changes: 8 additions & 10 deletions tests/kola/test-bootupd
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ function cleanup () {
fi
}

# Mount the EFI partition.
tmpefimount=$(mount_tmp_efi)
bootmount=/boot
bootefimount=${bootmount}/efi
bootefidir=${bootefimount}/EFI
tmpefidir=${tmpefimount}/EFI
bootupdir=/usr/lib/bootupd/updates
efiupdir=${bootupdir}/EFI
ostbaseefi=/usr/lib/ostree-boot/efi/EFI
Expand Down Expand Up @@ -103,24 +104,21 @@ bootupctl validate | tee out.txt
ok validate after update

# FIXME see above
# assert_file_has_content ${bootefidir}/${efisubdir}/somenew.efi 'somenewfile'
if test -f ${bootefidir}/${efisubdir}/shim.efi; then
# assert_file_has_content ${tmpefidir}/${efisubdir}/somenew.efi 'somenewfile'
if test -f ${tmpefidir}/${efisubdir}/shim.efi; then
fatal "failed to remove file"
fi
if ! grep -q 'bootupd-test-changes' ${bootefidir}/${efisubdir}/grubx64.efi; then
if ! grep -q 'bootupd-test-changes' ${tmpefidir}/${efisubdir}/grubx64.efi; then
fatal "failed to update modified file"
fi
cmp ${bootefidir}/${efisubdir}/shimx64.efi ${efiupdir}/${efisubdir}/shimx64.efi
cmp ${tmpefidir}/${efisubdir}/shimx64.efi ${efiupdir}/${efisubdir}/shimx64.efi
ok filesystem changes

bootupctl update | tee out.txt
assert_file_has_content_literal out.txt 'No update available for any component'
assert_not_file_has_content_literal out.txt 'Updated EFI'

# Ensure /boot and /boot/efi can be written to
mount -o remount,rw ${bootmount}
mount -o remount,rw ${bootefimount}
echo "some additions" >> ${bootefidir}/${efisubdir}/shimx64.efi
echo "some additions" >> ${tmpefidir}/${efisubdir}/shimx64.efi
if bootupctl validate 2>err.txt; then
fatal "unexpectedly passed validation"
fi
Expand Down