blob: 2ae9cde7fc61ccb51f808987dabdbfae22bf798a [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fuchsia_runtime::process_self;
use helper::{bootfs_entries, init_test_logging, koid};
use libc::{dl_iterate_phdr, dl_phdr_info, size_t};
use std::collections::BTreeSet;
use std::ffi::{c_int, c_void};
use zx::{Koid, MapDetails};
unsafe extern "C" fn collect_module_addr(
hdr: *mut dl_phdr_info,
_hdr_size: size_t,
ck: *mut c_void,
) -> c_int {
// SAFETY: `hdr` and `ck` are provided by `dl_iterate_phdr`, where `ck`
// points to the `BTreeSet<usize>` passed by `check_bootfs_entries`, and
// `hdr->dlpi_phdr` has at least one program header (`dlpi_phnum > 0`).
unsafe {
let modules = &mut *(ck as *mut BTreeSet<usize>);
let info = &*hdr;
assert!(info.dlpi_phnum > 0);
let first_phdr = &*info.dlpi_phdr;
// Pick the first program header to locate the VMO backing the mapping.
modules.insert((info.dlpi_addr + first_phdr.p_vaddr) as usize);
}
0
}
fn check_bootfs_entries() {
let entries = bootfs_entries();
// Base address of RO section of each module.
let mut modules = BTreeSet::<usize>::new();
// SAFETY: `collect_module_addr` casts `data` back to `&mut BTreeSet<usize>`
// for the duration of the synchronous `dl_iterate_phdr` call.
unsafe {
dl_iterate_phdr(Some(collect_module_addr), (&raw mut modules).cast::<c_void>());
}
// One of the entries is the vDSO, which is not a blob handed over from bootfs.
assert_eq!(modules.len(), entries.len() + 1);
// Determine that all the blobs in `entries` exist in this process by checking
// the base addresses and matching them up.
let mappings = process_self().info_maps_vec().expect("failed to query process maps");
let mut module_koids = BTreeSet::<Koid>::new();
for mapping in &mappings {
let MapDetails::Mapping(details) = mapping.details() else {
continue;
};
let Some(&module_addr) = modules.range(mapping.base..).next() else {
continue;
};
// `module_addr` may or not be contained in our mapping.
if module_addr < mapping.base + mapping.size {
module_koids.insert(details.vmo_koid);
}
}
// Now we have a unique mapping from a module to a mapping. Let's check that the mappings KOIDs
// and content match. We don't need to match the entire VMO contents, since some sections may be
// execute only, but the selected mapping will give us the offset in the vmo and the mapping
// size should give us the length we can use to compare. The VMO KOID will allow us to match
// those handed over by userboot protocol.
let entry_koids: BTreeSet<Koid> =
entries.iter().map(|entry| koid(entry.contents.as_handle_ref())).collect();
let not_found_in_entries: Vec<Koid> = module_koids.difference(&entry_koids).copied().collect();
// There should only be one missing KOID, that is the vDSO VMO.
assert_eq!(not_found_in_entries.len(), 1);
let not_found_in_modules: Vec<Koid> = entry_koids.difference(&module_koids).copied().collect();
// All VMOs in `entries` are present in the mappings, though not all VMOs in
// `mappings` are present in `entries` due to the vDSO.
assert!(not_found_in_modules.is_empty());
}
fn main() {
init_test_logging();
check_bootfs_entries();
}