From dee6396dcfff7cee1113164a14505e9b98a71311 Mon Sep 17 00:00:00 2001 From: Simon Chopin Date: Wed, 19 Aug 2026 16:26:25 +0200 Subject: [PATCH] linux: modules: clamp when they overlap other modules This isn't a problem yet (barring user-provided data, of course) since the procmaps approach cannot, by obstruction, generate overlapping modules, but it will become a problem when we'll be using the DT_DEBUG data. Indeed, some objects are disjointed in memory, and because of that you can have other modules inserted into the gap. In particular, on Linux arm64, the vDSO routinely lands in the middle of ld.so segments. It's usually benign since the latter segments are only data, not code, and we record an error if not. --- src/linux/module_list.rs | 46 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/linux/module_list.rs b/src/linux/module_list.rs index bbf7cdb0..d2dbcd4d 100644 --- a/src/linux/module_list.rs +++ b/src/linux/module_list.rs @@ -29,6 +29,17 @@ pub enum ModuleResolveError { name.to_string_lossy() )] DuplicateUserModule { name: OsString, base_address: usize }, + #[error( + "module `{}` at {base_address:#x} was clamped at {clamped_to:#x} while \ + its last executable segment ends at {code_end:#x}", + name.to_string_lossy() + )] + ModuleImageClamped { + name: OsString, + base_address: usize, + code_end: usize, + clamped_to: usize, + }, } /// Where a module's information came from. @@ -162,13 +173,46 @@ pub(crate) fn resolve( filter_out_user_overlap(&mut process_candidates, &user_candidates); ensure_entrypoint_is_first(&mut process_candidates, entry_point); - process_candidates + let mut candidates: Vec = process_candidates .into_iter() .chain(user_candidates) + .collect(); + clamp_overlapping_modules(&mut candidates, &mut soft_errors); + + candidates + .into_iter() .map(ModuleCandidate::into_module) .collect() } +/// Shortens any module whose image overlaps another entry of the module list. +fn clamp_overlapping_modules( + modules: &mut [ModuleCandidate], + mut soft_errors: impl WriteErrorList, +) { + let mut claims: Vec = modules.iter().map(|module| module.base_address).collect(); + claims.sort_unstable(); + + for module in modules { + let Some(&claim) = claims.iter().find(|&&claim| claim > module.base_address) else { + continue; + }; + if claim < module.end_address { + // Clamping is typically harmless *unless* the stuff that's cut + // out is executable code. + if claim < module.code_end { + soft_errors.push(ModuleResolveError::ModuleImageClamped { + name: module.name.clone().unwrap_or_default(), + base_address: module.base_address, + code_end: module.code_end, + clamped_to: claim, + }); + } + module.end_address = claim; + } + } +} + /// Where the object behind `mapping` was loaded. /// /// `/proc//maps` reports where a mapping *starts*, which for an Android