198 lines
5.4 KiB
Rust
198 lines
5.4 KiB
Rust
use std::path::Path;
|
|
use std::process::{Command,Stdio};
|
|
|
|
use libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, LoopDevice};
|
|
use libcitadel::verity::Verity;
|
|
|
|
pub fn setup_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut p = choose_boot_partiton(true, CommandLine::revert_rootfs())?;
|
|
if CommandLine::noverity() {
|
|
setup_partition_unverified(&p)
|
|
} else {
|
|
setup_partition_verified(&mut p)
|
|
}
|
|
}
|
|
|
|
pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
|
if CommandLine::noverity() {
|
|
setup_resource_unverified(&rootfs)
|
|
} else {
|
|
setup_resource_verified(&rootfs)
|
|
}
|
|
}
|
|
|
|
fn setup_resource_unverified(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
|
if img.is_compressed() {
|
|
img.decompress(false)?;
|
|
}
|
|
let loopdev = LoopDevice::create(img.path(), Some(4096), true)?;
|
|
info!("Loop device created: {}", loopdev);
|
|
setup_linear_mapping(loopdev.device())
|
|
}
|
|
|
|
fn setup_resource_verified(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
|
let _ = img.setup_verity_device()?;
|
|
Ok(())
|
|
}
|
|
|
|
fn setup_partition_unverified(p: &Partition) -> Result<(), Box<dyn std::error::Error>> {
|
|
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
|
setup_linear_mapping(p.path())
|
|
}
|
|
|
|
fn setup_partition_verified(p: &mut Partition) -> Result<(), Box<dyn std::error::Error>> {
|
|
info!("Creating /dev/mapper/rootfs dm-verity device");
|
|
if !CommandLine::nosignatures() {
|
|
if !p.has_public_key() {
|
|
return Result::Err(
|
|
format!(
|
|
"no public key available for channel {}",
|
|
p.metainfo().channel()
|
|
)
|
|
.into(),
|
|
);
|
|
}
|
|
if !p.is_signature_valid() {
|
|
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
|
return Result::Err("signature verification failed on partition".into());
|
|
}
|
|
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
|
}
|
|
Verity::setup_partition(p)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn setup_linear_mapping(blockdev: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
|
let dev = BlockDev::open_ro(blockdev)?;
|
|
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
|
|
|
info!("/usr/sbin/dmsetup create rootfs --table '{}'", table);
|
|
|
|
let ok = Command::new("/usr/sbin/dmsetup")
|
|
.args(&["create", "rootfs", "--table", &table])
|
|
.stderr(Stdio::inherit())
|
|
.status()
|
|
.expect("unable to execute /usr/sbin/dmsetup")
|
|
.success();
|
|
|
|
if !ok {
|
|
return Result::Err(
|
|
"failed to set up linear identity mapping with /usr/sbin/dmsetup".into(),
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn is_revertible_partition(best: &Option<Partition>, partition: &Partition) -> bool {
|
|
if !is_bootable(partition) {
|
|
return false;
|
|
}
|
|
match best {
|
|
Some(p) => p.path() != partition.path(),
|
|
None => true,
|
|
}
|
|
}
|
|
|
|
fn choose_revert_partition(best: Option<Partition>) -> Option<Partition> {
|
|
for p in Partition::rootfs_partitions().unwrap_or(Vec::new()) {
|
|
if is_revertible_partition(&best, &p) {
|
|
return Some(p);
|
|
}
|
|
}
|
|
best
|
|
}
|
|
|
|
fn choose_boot_partiton(scan: bool, revert_rootfs: bool,
|
|
) -> Result<Partition, Box<dyn std::error::Error>> {
|
|
let mut partitions = Partition::rootfs_partitions()?;
|
|
|
|
if scan {
|
|
for p in &mut partitions {
|
|
p.boot_scan()?;
|
|
}
|
|
}
|
|
|
|
let mut best = None;
|
|
for p in partitions {
|
|
best = compare_boot_partitions(best, p);
|
|
}
|
|
|
|
if revert_rootfs {
|
|
best = choose_revert_partition(best);
|
|
}
|
|
|
|
best.ok_or_else(|| format_err!("No partition found to boot from").into())
|
|
}
|
|
|
|
fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partition> {
|
|
if !is_bootable(&b) {
|
|
return a;
|
|
}
|
|
|
|
// b is bootable, so if a is None, then just return b
|
|
let a = match a {
|
|
Some(partition) => partition,
|
|
None => return Some(b),
|
|
};
|
|
|
|
// First partition with FLAG_PREFER_BOOT trumps everything
|
|
if a.is_preferred() {
|
|
return Some(a);
|
|
}
|
|
|
|
if b.is_preferred() {
|
|
return Some(b);
|
|
}
|
|
|
|
// Compare versions and channels
|
|
let meta_a = a.metainfo();
|
|
let meta_b = b.metainfo();
|
|
|
|
let ver_a = meta_a.version();
|
|
let ver_b = meta_b.version();
|
|
|
|
// Compare versions only if channels match
|
|
if a.metainfo().channel() == b.metainfo().channel() {
|
|
if ver_a > ver_b {
|
|
return Some(a);
|
|
} else if ver_b > ver_a {
|
|
return Some(b);
|
|
}
|
|
}
|
|
|
|
// choose NEW over GOOD if versions are the same or
|
|
// if versions cannot be compared because channels differ
|
|
if b.is_new() && a.is_good() {
|
|
return Some(b);
|
|
}
|
|
|
|
Some(a)
|
|
}
|
|
|
|
fn is_bootable(p: &Partition) -> bool {
|
|
if !p.is_initialized() {
|
|
return false;
|
|
}
|
|
|
|
// signatures enabled so not bootable without pubkey
|
|
if signatures_enabled() && !p.has_public_key() {
|
|
return false;
|
|
}
|
|
|
|
if p.is_new() || p.is_good() {
|
|
return true;
|
|
}
|
|
|
|
// If signatures are disabled then don't disqualify an
|
|
// image which failed a prior signature verification
|
|
if !signatures_enabled() && p.is_sig_failed() {
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn signatures_enabled() -> bool {
|
|
!(CommandLine::nosignatures() || CommandLine::noverity())
|
|
}
|