From 6f959a9e9607defbabdbd2c5c77096f205989eef Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Thu, 27 Jun 2024 04:21:11 -0400 Subject: [PATCH] nixos/make-initrd-ng: dlopen ELF notes --- nixos/lib/systemd-types.nix | 26 ++++ nixos/modules/system/boot/systemd/initrd.nix | 2 - .../kernel/make-initrd-ng/src/main.rs | 133 +++++++++++++++--- 3 files changed, 137 insertions(+), 24 deletions(-) diff --git a/nixos/lib/systemd-types.nix b/nixos/lib/systemd-types.nix index 7b5e3b69dda7..65ddb2458627 100644 --- a/nixos/lib/systemd-types.nix +++ b/nixos/lib/systemd-types.nix @@ -46,12 +46,14 @@ let inherit (lib.types) attrsOf coercedTo + enum lines listOf nullOr oneOf package path + singleLineStr submodule ; @@ -71,6 +73,30 @@ let type = path; description = "Path of the source file."; }; + + dlopen = { + usePriority = mkOption { + type = enum [ "required" "recommended" "suggested" ]; + default = "recommended"; + description = '' + Priority of dlopen ELF notes to include. "required" is + minimal, "recommended" includes "required", and + "suggested" includes "recommended". + + See: https://systemd.io/ELF_DLOPEN_METADATA/ + ''; + }; + + features = mkOption { + type = listOf singleLineStr; + default = [ ]; + description = '' + Features to enable via dlopen ELF notes. These will be in + addition to anything included via 'usePriority', + regardless of their priority. + ''; + }; + }; }; }; diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 9f813eab2e2d..f2ffb9209719 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -488,8 +488,6 @@ in { # fido2 support "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" "${pkgs.libfido2}/lib/libfido2.so.1" - ] ++ optionals cfg.package.withKmod [ - "${pkgs.kmod.lib}/lib/libkmod.so.2" ] ++ jobScripts ++ map (c: builtins.removeAttrs c ["text"]) (builtins.attrValues cfg.contents); diff --git a/pkgs/build-support/kernel/make-initrd-ng/src/main.rs b/pkgs/build-support/kernel/make-initrd-ng/src/main.rs index cb4200214684..29f10552bdef 100644 --- a/pkgs/build-support/kernel/make-initrd-ng/src/main.rs +++ b/pkgs/build-support/kernel/make-initrd-ng/src/main.rs @@ -1,4 +1,4 @@ -use std::collections::{HashSet, VecDeque}; +use std::collections::{BTreeSet, HashSet, VecDeque}; use std::env; use std::ffi::{OsStr, OsString}; use std::fs; @@ -12,10 +12,41 @@ use eyre::Context; use goblin::{elf::Elf, Object}; use serde::Deserialize; +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Deserialize, Hash)] +#[serde(rename_all = "lowercase")] +enum DLOpenPriority { + Required, + Recommended, + Suggested, +} + +#[derive(PartialEq, Eq, Debug, Deserialize, Clone, Hash)] +#[serde(rename_all = "camelCase")] +struct DLOpenConfig { + use_priority: DLOpenPriority, + features: BTreeSet, +} + #[derive(Deserialize, Debug)] +struct DLOpenNote { + soname: Vec, + feature: Option, + // description is in the spec, but we don't need it here. + // description: Option, + priority: Option, +} + +#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)] struct StoreInput { source: String, target: Option, + dlopen: Option, +} + +#[derive(PartialEq, Eq, Hash, Clone)] +struct StorePath { + path: Box, + dlopen: Option, } struct NonRepeatingQueue { @@ -48,13 +79,47 @@ impl NonRepeatingQueue { } } -fn add_dependencies + AsRef>( +fn add_dependencies + AsRef + std::fmt::Debug>( source: P, elf: Elf, - queue: &mut NonRepeatingQueue>, -) { + contents: &[u8], + dlopen: &Option, + queue: &mut NonRepeatingQueue, +) -> eyre::Result<()> { if let Some(interp) = elf.interpreter { - queue.push_back(Box::from(Path::new(interp))); + queue.push_back(StorePath { + path: Box::from(Path::new(interp)), + dlopen: dlopen.clone(), + }); + } + + let mut dlopen_libraries = vec![]; + if let Some(dlopen) = dlopen { + for n in elf + .iter_note_sections(&contents, Some(".note.dlopen")) + .into_iter() + .flatten() + { + let note = n.wrap_err_with(|| format!("bad note in {:?}", source))?; + // Payload is padded and zero terminated + let payload = ¬e.desc[..note + .desc + .iter() + .position(|x| *x == 0) + .unwrap_or(note.desc.len())]; + let parsed = serde_json::from_slice::>(payload)?; + for mut parsed_note in parsed { + if dlopen.use_priority + >= parsed_note.priority.unwrap_or(DLOpenPriority::Recommended) + || parsed_note + .feature + .map(|f| dlopen.features.contains(&f)) + .unwrap_or(false) + { + dlopen_libraries.append(&mut parsed_note.soname); + } + } + } } let rpaths = if elf.runpaths.len() > 0 { @@ -71,13 +136,21 @@ fn add_dependencies + AsRef>( .map(|p| Box::::from(Path::new(p))) .collect::>(); - for line in elf.libraries { + for line in elf + .libraries + .into_iter() + .map(|s| s.to_string()) + .chain(dlopen_libraries) + { let mut found = false; for path in &rpaths_as_path { - let lib = path.join(line); + let lib = path.join(&line); if lib.exists() { // No need to recurse. The queue will bring it back round. - queue.push_back(Box::from(lib.as_path())); + queue.push_back(StorePath { + path: Box::from(lib.as_path()), + dlopen: dlopen.clone(), + }); found = true; break; } @@ -92,6 +165,8 @@ fn add_dependencies + AsRef>( ); } } + + Ok(()) } fn copy_file< @@ -100,7 +175,8 @@ fn copy_file< >( source: P, target: S, - queue: &mut NonRepeatingQueue>, + dlopen: &Option, + queue: &mut NonRepeatingQueue, ) -> eyre::Result<()> { fs::copy(&source, &target) .wrap_err_with(|| format!("failed to copy {:?} to {:?}", source, target))?; @@ -109,7 +185,7 @@ fn copy_file< fs::read(&source).wrap_err_with(|| format!("failed to read from {:?}", source))?; if let Ok(Object::Elf(e)) = Object::parse(&contents) { - add_dependencies(source, e, queue); + add_dependencies(source, e, &contents, &dlopen, queue)?; // Make file writable to strip it let mut permissions = fs::metadata(&target) @@ -138,14 +214,18 @@ fn copy_file< fn queue_dir + std::fmt::Debug>( source: P, - queue: &mut NonRepeatingQueue>, + dlopen: &Option, + queue: &mut NonRepeatingQueue, ) -> eyre::Result<()> { for entry in fs::read_dir(&source).wrap_err_with(|| format!("failed to read dir {:?}", source))? { let entry = entry?; // No need to recurse. The queue will bring us back round here on its own. - queue.push_back(Box::from(entry.path().as_path())); + queue.push_back(StorePath { + path: Box::from(entry.path().as_path()), + dlopen: dlopen.clone(), + }); } Ok(()) @@ -153,12 +233,12 @@ fn queue_dir + std::fmt::Debug>( fn handle_path( root: &Path, - p: &Path, - queue: &mut NonRepeatingQueue>, + p: StorePath, + queue: &mut NonRepeatingQueue, ) -> eyre::Result<()> { let mut source = PathBuf::new(); let mut target = Path::new(root).to_path_buf(); - let mut iter = p.components().peekable(); + let mut iter = p.path.components().peekable(); while let Some(comp) = iter.next() { match comp { Component::Prefix(_) => panic!("This tool is not meant for Windows"), @@ -182,7 +262,7 @@ fn handle_path( .wrap_err_with(|| format!("failed to get symlink metadata for {:?}", source))? .file_type(); if typ.is_file() && !target.exists() { - copy_file(&source, &target, queue)?; + copy_file(&source, &target, &p.dlopen, queue)?; if let Some(filename) = source.file_name() { source.set_file_name(OsString::from_iter([ @@ -193,7 +273,10 @@ fn handle_path( let wrapped_path = source.as_path(); if wrapped_path.exists() { - queue.push_back(Box::from(wrapped_path)); + queue.push_back(StorePath { + path: Box::from(wrapped_path), + dlopen: p.dlopen.clone(), + }); } } } else if typ.is_symlink() { @@ -213,7 +296,10 @@ fn handle_path( } let link_target_path = source.as_path(); if link_target_path.exists() { - queue.push_back(Box::from(link_target_path)); + queue.push_back(StorePath { + path: Box::from(link_target_path), + dlopen: p.dlopen.clone(), + }); } break; } else if typ.is_dir() { @@ -224,7 +310,7 @@ fn handle_path( // Only recursively copy if the directory is the target object if iter.peek().is_none() { - queue_dir(&source, queue) + queue_dir(&source, &p.dlopen, queue) .wrap_err_with(|| format!("failed to queue dir {:?}", source))?; } } @@ -244,11 +330,14 @@ fn main() -> eyre::Result<()> { let output = &args[2]; let out_path = Path::new(output); - let mut queue = NonRepeatingQueue::>::new(); + let mut queue = NonRepeatingQueue::::new(); for sp in input { let obj_path = Path::new(&sp.source); - queue.push_back(Box::from(obj_path)); + queue.push_back(StorePath { + path: Box::from(obj_path), + dlopen: sp.dlopen, + }); if let Some(target) = sp.target { println!("{} -> {}", &target, &sp.source); // We don't care about preserving symlink structure here @@ -264,7 +353,7 @@ fn main() -> eyre::Result<()> { } } while let Some(obj) = queue.pop_front() { - handle_path(out_path, &*obj, &mut queue)?; + handle_path(out_path, obj, &mut queue)?; } Ok(())