Skip to content

Commit 58c1026

Browse files
committed
rust: validate all linked ELF libraries have annotations
We added the proper annotations a few commits ago. Here we teach the validation code to check for it. Only ELF for the moment because macOS and Windows generally aren't as big of a problem when it comes to this.
1 parent 2d1ca3c commit 58c1026

File tree

1 file changed

+81
-8
lines changed

1 file changed

+81
-8
lines changed

src/validation.rs

+81-8
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,29 @@ fn allowed_dylibs_for_triple(triple: &str) -> Vec<MachOAllowedDylib> {
401401
}
402402

403403
fn validate_elf(
404+
json: &PythonJsonMain,
404405
target_triple: &str,
405406
python_major_minor: &str,
406407
path: &Path,
407408
elf: &goblin::elf::Elf,
408409
bytes: &[u8],
409410
) -> Result<Vec<String>> {
411+
let mut system_links = BTreeSet::new();
412+
for link in &json.build_info.core.links {
413+
if link.system.unwrap_or_default() {
414+
system_links.insert(link.name.as_str());
415+
}
416+
}
417+
for extension in json.build_info.extensions.values() {
418+
for variant in extension {
419+
for link in &variant.links {
420+
if link.system.unwrap_or_default() {
421+
system_links.insert(link.name.as_str());
422+
}
423+
}
424+
}
425+
}
426+
410427
let mut errors = vec![];
411428

412429
let wanted_cpu_type = match target_triple {
@@ -453,6 +470,48 @@ fn validate_elf(
453470
if !allowed_libraries.contains(&lib.to_string()) {
454471
errors.push(format!("{} loads illegal library {}", path.display(), lib));
455472
}
473+
474+
// Most linked libraries should have an annotation in the JSON metadata.
475+
let requires_annotation = !lib.contains("libpython")
476+
&& !lib.starts_with("ld-linux")
477+
&& !lib.starts_with("ld64.so")
478+
&& !lib.starts_with("ld.so")
479+
&& !lib.starts_with("libc.so")
480+
&& !lib.starts_with("libgcc_s.so");
481+
482+
if requires_annotation {
483+
if lib.starts_with("lib") {
484+
if let Some(index) = lib.rfind(".so") {
485+
let lib_name = &lib[3..index];
486+
487+
// There should be a system links entry for this library in the JSON
488+
// metadata.
489+
//
490+
// Nominally we would look at where this ELF came from and make sure
491+
// the annotation is present in its section (e.g. core or extension).
492+
// But this is more work.
493+
if !system_links.contains(lib_name) {
494+
errors.push(format!(
495+
"{} library load of {} does not have system link build annotation",
496+
path.display(),
497+
lib
498+
));
499+
}
500+
} else {
501+
errors.push(format!(
502+
"{} library load of {} does not have .so extension",
503+
path.display(),
504+
lib
505+
));
506+
}
507+
} else {
508+
errors.push(format!(
509+
"{} library load of {} does not begin with lib",
510+
path.display(),
511+
lib
512+
));
513+
}
514+
}
456515
}
457516

458517
let wanted_glibc_max_version = GLIBC_MAX_VERSION_BY_TRIPLE
@@ -581,6 +640,7 @@ fn validate_pe(path: &Path, pe: &goblin::pe::PE) -> Result<Vec<String>> {
581640

582641
/// Attempt to parse data as an object file and validate it.
583642
fn validate_possible_object_file(
643+
json: &PythonJsonMain,
584644
python_major_minor: &str,
585645
triple: &str,
586646
path: &Path,
@@ -593,6 +653,7 @@ fn validate_possible_object_file(
593653
match object {
594654
goblin::Object::Elf(elf) => {
595655
errors.extend(validate_elf(
656+
json,
596657
triple,
597658
python_major_minor,
598659
path.as_ref(),
@@ -727,17 +788,24 @@ fn validate_distribution(dist_path: &Path) -> Result<Vec<String>> {
727788
let mut entries = tf.entries()?;
728789

729790
let mut wanted_python_paths = BTreeSet::new();
791+
let mut json = None;
730792

731793
let mut entry = entries.next().unwrap()?;
732794
if entry.path()?.display().to_string() == "python/PYTHON.json" {
733795
seen_paths.insert(entry.path()?.to_path_buf());
734796

735797
let mut data = Vec::new();
736798
entry.read_to_end(&mut data)?;
737-
let json = parse_python_json(&data).context("parsing PYTHON.json")?;
738-
errors.extend(validate_json(&json, triple, is_debug)?);
739-
740-
wanted_python_paths.extend(json.python_paths.values().map(|x| format!("python/{}", x)));
799+
json = Some(parse_python_json(&data).context("parsing PYTHON.json")?);
800+
errors.extend(validate_json(json.as_ref().unwrap(), triple, is_debug)?);
801+
802+
wanted_python_paths.extend(
803+
json.as_ref()
804+
.unwrap()
805+
.python_paths
806+
.values()
807+
.map(|x| format!("python/{}", x)),
808+
);
741809
} else {
742810
errors.push(format!(
743811
"1st archive entry should be for python/PYTHON.json; got {}",
@@ -770,8 +838,13 @@ fn validate_distribution(dist_path: &Path) -> Result<Vec<String>> {
770838
let mut data = Vec::new();
771839
entry.read_to_end(&mut data)?;
772840

773-
let (local_errors, local_seen_dylibs) =
774-
validate_possible_object_file(python_major_minor, &triple, &path, &data)?;
841+
let (local_errors, local_seen_dylibs) = validate_possible_object_file(
842+
json.as_ref().unwrap(),
843+
python_major_minor,
844+
&triple,
845+
&path,
846+
&data,
847+
)?;
775848
errors.extend(local_errors);
776849
seen_dylibs.extend(local_seen_dylibs);
777850

@@ -790,6 +863,7 @@ fn validate_distribution(dist_path: &Path) -> Result<Vec<String>> {
790863
));
791864

792865
let (local_errors, local_seen_dylibs) = validate_possible_object_file(
866+
json.as_ref().unwrap(),
793867
python_major_minor,
794868
&triple,
795869
&member_path,
@@ -801,8 +875,7 @@ fn validate_distribution(dist_path: &Path) -> Result<Vec<String>> {
801875
}
802876

803877
if path == PathBuf::from("python/PYTHON.json") {
804-
let json = parse_python_json(&data).context("parsing PYTHON.json")?;
805-
errors.extend(validate_json(&json, triple, is_debug)?);
878+
errors.push("python/PYTHON.json seen twice".to_string());
806879
}
807880
}
808881

0 commit comments

Comments
 (0)