From afbd768571910f1e98d865746f94ec600294dabc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 09:24:54 +0000 Subject: [PATCH] ipk-verify: discover libraries via bundled libs' own RUNPATH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit list_libs only scanned the executable's rpath dirs and the top-level lib/ dir, and LibraryInfo::parse never read DT_RUNPATH/DT_RPATH. So a dependency bundled in a subdirectory that a library locates through its own $ORIGIN-relative runpath was reported as a missing library with all its symbols undefined — even though the loader resolves it fine. This is the libpulse case in apps-repo PR #190: libpulse.so.0 (in lib/) has RUNPATH $ORIGIN/pulseaudio and its pa_* symbols are provided by the bundled lib/pulseaudio/libpulsecommon-15.0.so, which the flat scan never collected. Capture DT_RUNPATH/DT_RPATH in LibraryInfo, and turn list_libs into a breadth-first walk that follows each bundled library's own runpath ($ORIGIN-relative) to discover further bundled directories. Adds a fixture-based parse test plus the regression coverage. Bumps bin-lib 0.1.1 -> 0.1.2, ipk-lib 0.1.3 -> 0.1.4, ipk-verify 0.1.5 -> 0.1.6. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011jY7WzoWeU9TWRBhWJ2Wbq --- Cargo.lock | 6 ++-- common/bin/Cargo.toml | 2 +- common/bin/src/fixtures/lib_runpath.so | Bin 0 -> 13568 bytes common/bin/src/lib.rs | 2 ++ common/bin/src/library.rs | 33 +++++++++++++++++++ common/ipk/Cargo.toml | 2 +- common/ipk/src/component.rs | 38 +++++++++++++++++----- packages/ipk-verify/Cargo.toml | 2 +- packages/ipk-verify/tests/global_scope.rs | 1 + 9 files changed, 72 insertions(+), 14 deletions(-) create mode 100755 common/bin/src/fixtures/lib_runpath.so diff --git a/Cargo.lock b/Cargo.lock index bd20e269..f55ebe85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,7 +112,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bin-lib" -version = "0.1.1" +version = "0.1.2" dependencies = [ "elf", "semver", @@ -785,7 +785,7 @@ dependencies = [ [[package]] name = "ipk-lib" -version = "0.1.3" +version = "0.1.4" dependencies = [ "bin-lib", "common-path", @@ -798,7 +798,7 @@ dependencies = [ [[package]] name = "ipk-verify" -version = "0.1.5" +version = "0.1.6" dependencies = [ "bin-lib", "clap", diff --git a/common/bin/Cargo.toml b/common/bin/Cargo.toml index da92974f..c8bd987d 100644 --- a/common/bin/Cargo.toml +++ b/common/bin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bin-lib" -version = "0.1.1" +version = "0.1.2" edition = "2021" [dependencies] diff --git a/common/bin/src/fixtures/lib_runpath.so b/common/bin/src/fixtures/lib_runpath.so new file mode 100755 index 0000000000000000000000000000000000000000..f7bfd4e43487f83258d702f331f7c7f2b7c10107 GIT binary patch literal 13568 zcmeHO%}Z2K6hEVunqSk$La^d3EGqCieuWfNGLDXvW+N^l;xXfl&44o_<9nb5CM|+! zAR@0_Q;#xOx5I2WAXz2}_YJ@@_Q-Uq&S=4tHC zP*o@d+^XRQ?2G_zkz=WgBhc0l4f?YgnoMlXj_bGHlWNbz2w`uGIgVkM#zAcH!hn(e z$TRsY3JBmhnHhKUj%#0}(+h~)+>0B;V`N^Hw*L*Uj^@EN;@=8RPysDt1z-&F)`=Iy zcH%K&4r~*fPWmwU#);=I#4^+Q3p+zYM$UIn^H7(AwlD=IDBnT92mHRVVP2dA_ssCv zBxrfo&_>(K_;HOmM*sHWn|E&Gs}IQwE3e;=r{8|6UR~BMVo#WiIL%vvCdg{W-gO-)4Ae) z8N@CMhytR3C?E=m0-}H@APR^AqQKE8@U7+ikIDFp%~kxb=i$erNg}g~0-}H@APR^A zqJStM3Wx%tfG8jehytR(KUE;qR(&1co0|8mTjg?uR>SXmLyr>>nh%HC8f)v8bn+y z7tYbxBHaF2HkG|wRKIO%+_f)Gh00tr-AdyV#Oon!AZ#T(Y8Z}2uUVHSQ*Oa=t*#!` zqaqzwT{G-n?Cw>OUe$G(9DiC1K0l~|ujNWHmF#%kA*ctg!)M-U)2Rip*y71@_?mv4PQ%DA=|x0Jmdtdnhq58nfer2?!KtgVI&nPBNv@DS4RXUZLn@)T4r{ zW=qA{OvzaQ<(pD&ZYJH4OM?l>Cg-xC(hCJWnTJjZl#`ivKxMM_bSat7*x9tU@yJ9a z^SLQdQ^kBfQ*icPnSNhnR+Q>~0VA0Bw9O zxvrnlukR;Pke?(z=Ld@d3Hdo1S#@O!-{ZGDrHZ154uAOct z{(0ixS8Pxq--npb_pfK#kNH_=3ik~izQOBYEVkXqYxg|fXM`qPCS#*UTm$Fl@2_6l fciT{!V34{D!Vrsr; literal 0 HcmV?d00001 diff --git a/common/bin/src/lib.rs b/common/bin/src/lib.rs index 60afee98..b09a4ec8 100644 --- a/common/bin/src/lib.rs +++ b/common/bin/src/lib.rs @@ -22,6 +22,8 @@ pub struct LibraryInfo { pub names: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub undefined: Vec, + #[serde(skip_serializing, default)] + pub rpath: Vec, #[serde(skip_serializing, default = "LibraryPriority::default")] pub priority: LibraryPriority, } diff --git a/common/bin/src/library.rs b/common/bin/src/library.rs index 9b297284..6f1736ce 100644 --- a/common/bin/src/library.rs +++ b/common/bin/src/library.rs @@ -45,6 +45,7 @@ impl LibraryInfo { N: AsRef, { let mut needed = Vec::::new(); + let mut rpath = Vec::::new(); let mut elf = ElfStream::::open_stream(source)?; let mut name = String::from(name.as_ref()); @@ -65,6 +66,15 @@ impl LibraryInfo { abi::DT_SONAME => { name = String::from(dynstr_table.get(entry.d_val() as usize).unwrap()); } + abi::DT_RPATH | abi::DT_RUNPATH => { + rpath.extend( + dynstr_table + .get(entry.d_val() as usize) + .unwrap() + .split(":") + .map(|s| String::from(s)), + ); + } _ => {} } } @@ -140,8 +150,31 @@ impl LibraryInfo { needed, symbols, undefined, + rpath, names: Default::default(), priority: Default::default(), }) } } + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use crate::LibraryInfo; + + #[test] + fn test_parse_runpath() { + // Fixture is a shared object built with -Wl,-rpath,'$ORIGIN/pulseaudio' + // -Wl,--enable-new-dtags, i.e. a DT_RUNPATH entry. + let mut content = Cursor::new(include_bytes!("fixtures/lib_runpath.so")); + let info = LibraryInfo::parse(&mut content, true, "lib_runpath.so") + .expect("should not have any error"); + assert_eq!(info.name, "libfixture.so.1", "name should come from DT_SONAME"); + assert!( + info.rpath.iter().any(|p| p == "$ORIGIN/pulseaudio"), + "rpath should capture DT_RUNPATH, got {:?}", + info.rpath + ); + } +} diff --git a/common/ipk/Cargo.toml b/common/ipk/Cargo.toml index f5eb5dc1..cbf8235a 100644 --- a/common/ipk/Cargo.toml +++ b/common/ipk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ipk-lib" -version = "0.1.3" +version = "0.1.4" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/ipk/src/component.rs b/common/ipk/src/component.rs index 415efcc8..c4e0deba 100644 --- a/common/ipk/src/component.rs +++ b/common/ipk/src/component.rs @@ -1,6 +1,6 @@ use common_path::common_path; use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet, VecDeque}; use std::fs; use std::fs::File; use std::io::{Error, ErrorKind}; @@ -160,16 +160,30 @@ impl Component { rpath: &Vec, links: &Symlinks, ) -> Result, Error> { - let mut libs = HashMap::new(); - let lib_dir = dir.join("lib").canonicalize(); - let mut lib_dirs: Vec<(&Path, bool)> = rpath.iter().map(|p| (p.as_path(), true)).collect(); - if let Ok(lib_dir) = lib_dir.as_ref() { + let mut libs: HashMap = HashMap::new(); + let mut visited_dirs: HashSet = HashSet::new(); + let mut queue: VecDeque<(PathBuf, bool)> = VecDeque::new(); + + for p in rpath { + queue.push_back((p.clone(), true)); + } + if let Ok(lib_dir) = dir.join("lib").canonicalize() { if !rpath.contains(&lib_dir) { - lib_dirs.push((lib_dir.as_path(), false)); + queue.push_back((lib_dir, false)); } } - for (lib_dir, is_rpath) in lib_dirs { - let Ok(entries) = fs::read_dir(lib_dir) else { + + // Discover libraries by walking the executable's rpath directories and, + // transitively, each bundled library's own DT_RUNPATH/DT_RPATH + // ($ORIGIN-relative). This mirrors the dynamic loader: e.g. a bundled + // libpulse.so.0 with RUNPATH $ORIGIN/pulseaudio pulls in + // lib/pulseaudio/libpulsecommon-15.0.so, which a flat scan of lib/ + // would miss. + while let Some((lib_dir, is_rpath)) = queue.pop_front() { + if !visited_dirs.insert(lib_dir.clone()) { + continue; + } + let Ok(entries) = fs::read_dir(&lib_dir) else { continue; }; for entry in entries { @@ -190,9 +204,17 @@ impl Component { } else { LibraryPriority::Package }; + // A bundled library's own runpath can point at further bundled + // directories; queue them for discovery too. + for sub_dir in Self::rpath(&lib.rpath, &path) { + if !visited_dirs.contains(&sub_dir) { + queue.push_back((sub_dir, true)); + } + } libs.insert(path, lib); } } + for (path, lib) in &mut libs { lib.names .push(String::from(path.file_name().unwrap().to_string_lossy())); diff --git a/packages/ipk-verify/Cargo.toml b/packages/ipk-verify/Cargo.toml index 0b1bd6ae..6a315d79 100644 --- a/packages/ipk-verify/Cargo.toml +++ b/packages/ipk-verify/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ipk-verify" -version = "0.1.5" +version = "0.1.6" edition = "2021" description = "Command line tool for checking symbols in an exectuable and libraries in an IPK file" authors = ["Mariotaku Lee "] diff --git a/packages/ipk-verify/tests/global_scope.rs b/packages/ipk-verify/tests/global_scope.rs index b837add1..c0bb0538 100644 --- a/packages/ipk-verify/tests/global_scope.rs +++ b/packages/ipk-verify/tests/global_scope.rs @@ -22,6 +22,7 @@ fn bundled_lib(name: &str, needed: &[&str], symbols: &[&str], undefined: &[&str] symbols, names: vec![name.to_string()], undefined: undefined.iter().map(|s| s.to_string()).collect(), + rpath: vec![], priority: LibraryPriority::Rpath, } }