Skip to content

Commit ea4eea3

Browse files
committed
fix validation if there is a system libtcl/libtk
1 parent 8cdf3c1 commit ea4eea3

File tree

1 file changed

+72
-27
lines changed

1 file changed

+72
-27
lines changed

src/validation.rs

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -265,24 +265,32 @@ static ELF_ALLOWED_LIBRARIES_BY_TRIPLE: Lazy<HashMap<&'static str, Vec<&'static
265265
.collect()
266266
});
267267

268-
static ELF_ALLOWED_LIBRARIES_BY_MODULE: Lazy<HashMap<&'static str, Vec<&'static str>>> =
269-
Lazy::new(|| {
270-
[
271-
(
272-
// libcrypt is provided by the system, but only on older distros.
273-
"_crypt",
274-
vec!["libcrypt.so.1"],
275-
),
276-
(
277-
// libtcl and libtk are shipped in our distribution.
278-
"_tkinter",
279-
vec!["libtcl8.6.so", "libtk8.6.so"],
280-
),
281-
]
282-
.iter()
283-
.cloned()
284-
.collect()
285-
});
268+
#[derive(Copy, Clone, PartialEq)]
269+
enum DepSource {
270+
SystemRequired,
271+
SystemOptional,
272+
Vendored,
273+
}
274+
use DepSource::*;
275+
276+
static ELF_ALLOWED_LIBRARIES_BY_MODULE: Lazy<
277+
HashMap<&'static str, Vec<(&'static str, DepSource)>>,
278+
> = Lazy::new(|| {
279+
[
280+
(
281+
// libcrypt is provided by the system, but only on older distros.
282+
"_crypt",
283+
vec![("libcrypt.so.1", SystemOptional)],
284+
),
285+
(
286+
"_tkinter",
287+
vec![("libtcl8.6.so", Vendored), ("libtk8.6.so", Vendored)],
288+
),
289+
]
290+
.iter()
291+
.cloned()
292+
.collect()
293+
});
286294

287295
static DARWIN_ALLOWED_DYLIBS: Lazy<Vec<MachOAllowedDylib>> = Lazy::new(|| {
288296
[
@@ -1022,7 +1030,7 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
10221030
if let Some(filename) = path.file_name() {
10231031
if let Some((module, _)) = filename.to_string_lossy().split_once(".cpython-") {
10241032
if let Some(extra) = ELF_ALLOWED_LIBRARIES_BY_MODULE.get(module) {
1025-
allowed_libraries.extend(extra.iter().map(|x| x.to_string()));
1033+
allowed_libraries.extend(extra.iter().map(|x| x.0.to_string()));
10261034
}
10271035
}
10281036
}
@@ -2194,11 +2202,6 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
21942202
// https://github.com/pyinstaller/pyinstaller/issues/9204#issuecomment-3171050891
21952203
if cfg!(target_os = "linux") {
21962204
for (name, variants) in python_json.build_info.extensions.iter() {
2197-
if name == "_crypt" {
2198-
// Our test environment may lack libcrypt (and _crypt is
2199-
// split out specifically because of this problem).
2200-
continue;
2201-
}
22022205
for ext in variants {
22032206
let Some(shared_lib) = &ext.shared_lib else {
22042207
continue;
@@ -2208,10 +2211,52 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
22082211
.unchecked()
22092212
.stdout_capture()
22102213
.run()
2211-
.context(format!("Failed to run `ldd {}`", shared_lib))?;
2214+
.context(format!("Failed to run `ldd {shared_lib}`"))?;
22122215
let stdout = String::from_utf8_lossy(&output.stdout);
2213-
if !output.status.success() || stdout.contains("not found") {
2214-
errors.push(format!("Error from `ldd {}`:\n{}", shared_lib, stdout));
2216+
// Format of ldd output, for both glibc and musl:
2217+
// - Everything starts with a tab.
2218+
// - Most things are "libxyz.so.1 => /usr/lib/libxyz.so.1 (0xabcde000)".
2219+
// - The ELF interpreter is displayed as just "/lib/ld.so (0xabcde000)".
2220+
// - glibc, but not musl, shows the vDSO as "linux-vdso.so.1 (0xfffff000)".
2221+
// - If a library is listed in DT_NEEDED with an absolute path, or (currently only
2222+
// supported on glibc) with an $ORIGIN-relative path, it displays as just
2223+
// "/path/to/libxyz.so (0xabcde000)".
2224+
// - On glibc, if a library cannot be found ldd returns zero and shows "=> not
2225+
// found" as the resolution (even if it wouldn't use the => form if found).
2226+
// - On musl, if a library cannot be found, ldd returns nonzero and shows "Error
2227+
// loading shared library ...:" on stderr.
2228+
if !output.status.success() {
2229+
// TODO: If we ever have any optional dependencies besides libcrypt (which is
2230+
// glibc-only), we will need to capture musl ldd's stderr and parse it.
2231+
errors.push(format!(
2232+
"`ldd {shared_lib}` exited with {}:\n{stdout}",
2233+
output.status
2234+
));
2235+
} else {
2236+
let mut ldd_errors = vec![];
2237+
let deps = ELF_ALLOWED_LIBRARIES_BY_MODULE.get(&name[..]);
2238+
let temp_dir_lossy = temp_dir.path().to_string_lossy().into_owned();
2239+
for line in stdout.lines() {
2240+
let Some((needed, resolution)) = line.trim().split_once(" => ") else {
2241+
continue;
2242+
};
2243+
let dep_source = deps
2244+
.and_then(|deps| deps.iter().find(|dep| dep.0 == needed).map(|dep| dep.1))
2245+
.unwrap_or(SystemRequired);
2246+
if resolution.starts_with("not found") && dep_source != SystemOptional {
2247+
ldd_errors.push(format!("{needed} was expected to be found"));
2248+
} else if !resolution.contains(&temp_dir_lossy) && dep_source == Vendored {
2249+
ldd_errors.push(format!(
2250+
"{needed} should not come from the OS (missing rpath/$ORIGIN?)"
2251+
));
2252+
}
2253+
}
2254+
if !ldd_errors.is_empty() {
2255+
errors.push(format!(
2256+
"In `ldd {shared_lib}`:\n - {}\n{stdout}",
2257+
ldd_errors.join("\n - ")
2258+
));
2259+
}
22152260
}
22162261
}
22172262
}

0 commit comments

Comments
 (0)