Skip to content

Commit

Permalink
find_kalsr_offset
Browse files Browse the repository at this point in the history
  • Loading branch information
d-e-s-o committed Feb 3, 2025
1 parent c03c914 commit 5f5295b
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ anyhow = "1.0.71"
blazesym-dev = {path = "dev", features = ["generate-unit-test-files"]}
# TODO: Use 0.5.2 once released.
criterion = {git = "https://github.com/bheisler/criterion.rs.git", rev = "b913e232edd98780961ecfbae836ec77ede49259", default-features = false, features = ["rayon", "cargo_bench_support"]}
rand = {version = "0.9", default-features = false, features = ["std", "thread_rng"]}
scopeguard = "1.2"
stats_alloc = {version = "0.1.1", features = ["nightly"]}
tempfile = "3.4"
Expand Down
1 change: 1 addition & 0 deletions capi/src/symbolize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ impl From<blaze_symbolize_src_kernel> for Kernel {
Self {
kallsyms: to_maybe_path(kallsyms),
vmlinux: to_maybe_path(vmlinux),
kaslr_offset: None,
debug_syms,
_non_exhaustive: (),
}
Expand Down
1 change: 0 additions & 1 deletion src/elf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ pub(crate) mod types;
// of concerns that is not a workable location.
pub(crate) static DEFAULT_DEBUG_DIRS: &[&str] = &["/usr/lib/debug", "/lib/debug/"];

#[cfg(test)]
pub(crate) use parser::BackendImpl;
pub(crate) use parser::ElfParser;
pub(crate) use resolver::ElfResolverData;
Expand Down
1 change: 0 additions & 1 deletion src/elf/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,6 @@ where
_backend: B::ObjTy,
}

#[cfg(test)]
impl ElfParser<File> {
fn open_file_io<P>(file: File, path: P) -> Self
where
Expand Down
21 changes: 14 additions & 7 deletions src/kernel/kaslr.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
use std::error::Error as StdError;
use std::fs::File;
use std::io;
use std::io::Read as _;
use std::path::Path;
use std::str;
use std::str::FromStr;

use crate::elf;
use crate::elf::types::ElfN_Nhdr;
use crate::elf::BackendImpl;
use crate::elf::ElfParser;
use crate::log;
use crate::util::align_up_u32;
use crate::util::from_radix_16;
use crate::util::split_bytes;
use crate::Addr;
use crate::Error;
use crate::ErrorExt as _;
use crate::ErrorKind;
use crate::IntoError as _;
Expand Down Expand Up @@ -112,6 +106,19 @@ fn find_kcore_kaslr_offset() -> Result<Option<u64>> {
Ok(offset)
}

pub(crate) fn find_kalsr_offset() -> Result<Option<u64>> {
if let offset @ Some(o) = find_kcore_kaslr_offset()? {
log::debug!("determined KASLR offset to be {o:#x} based on {PROC_KCORE} contents");
return Ok(offset)
}

// TODO: Try other methods of determining KASLR offset, including
// comparisons between `/proc/kallsyms` values to
// `System.map-*` contents or parsing `dmesg` (no, really...)

Ok(None)
}


#[cfg(test)]
mod tests {
Expand Down
5 changes: 1 addition & 4 deletions src/kernel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#[cfg(feature = "bpf")]
mod bpf;
mod kaslr;
mod ksym;
mod resolver;
// Still work in progress.
#[allow(unused)]
#[cfg(test)]
mod kaslr;

// TODO: KsymResolver should ideally be an implementation detail.
pub(crate) use ksym::KsymResolver;
Expand Down
29 changes: 26 additions & 3 deletions src/kernel/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,69 @@ use crate::symbolize::ResolvedSym;
use crate::symbolize::Symbolize;
use crate::Addr;
use crate::Error;
use crate::ErrorExt as _;
use crate::IntoError as _;
use crate::Result;

use super::kaslr::find_kalsr_offset;
use super::ksym::KsymResolver;


pub(crate) struct KernelResolver {
ksym_resolver: Option<Rc<KsymResolver>>,
elf_resolver: Option<Rc<ElfResolver>>,
kaslr_offset: u64,
}

impl KernelResolver {
pub(crate) fn new(
ksym_resolver: Option<Rc<KsymResolver>>,
elf_resolver: Option<Rc<ElfResolver>>,
kaslr_offset: Option<u64>,
) -> Result<KernelResolver> {
if ksym_resolver.is_none() && elf_resolver.is_none() {
return Err(Error::with_not_found(
"failed to create kernel resolver: neither kallsyms nor vmlinux symbol source are present",
))
}

let kaslr_offset = if let Some(kaslr_offset) = kaslr_offset {
kaslr_offset
} else {
find_kalsr_offset()
.context("failed to query system KASLR offset")?
.unwrap_or_default()
};

Ok(KernelResolver {
ksym_resolver,
elf_resolver,
kaslr_offset,
})
}
}

impl Symbolize for KernelResolver {
fn find_sym(&self, addr: Addr, opts: &FindSymOpts) -> Result<Result<ResolvedSym<'_>, Reason>> {
let elf_addr = || {
addr.checked_sub(self.kaslr_offset).ok_or_invalid_input(|| {
format!(
"address {addr:#x} is less then KASLR offset ({:#x})",
self.kaslr_offset
)
})
};

match (self.elf_resolver.as_ref(), self.ksym_resolver.as_ref()) {
(Some(elf_resolver), None) => elf_resolver.find_sym(addr, opts),
(Some(elf_resolver), None) => elf_resolver.find_sym(elf_addr()?, opts),
(None, Some(ksym_resolver)) => ksym_resolver.find_sym(addr, opts),
(Some(elf_resolver), Some(ksym_resolver)) => {
// We give preference to vmlinux, because it is likely
// to report more information. If it could not find an
// address, though, we fall back to kallsyms. This is
// helpful for example for kernel modules, which
// naturally are not captured by vmlinux.
let result = elf_resolver.find_sym(addr, opts)?;
let result = elf_resolver.find_sym(elf_addr()?, opts)?;
if result.is_ok() {
Ok(result)
} else {
Expand Down Expand Up @@ -95,7 +118,7 @@ mod tests {
#[test]
fn debug_repr() {
let ksym = Rc::new(KsymResolver::load_file_name(Path::new(KALLSYMS)).unwrap());
let kernel = KernelResolver::new(Some(ksym), None).unwrap();
let kernel = KernelResolver::new(Some(ksym), None, Some(0)).unwrap();
assert_ne!(format!("{kernel:?}"), "");
}
}
8 changes: 8 additions & 0 deletions src/symbolize/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ pub struct Kernel {
/// `vmlinux` will generally be given preference and `kallsyms` acts
/// as a fallback.
pub vmlinux: MaybeDefault<PathBuf>,
/// The KASLR offset to use.
///
/// Given a value of `None`, the library will attempt to deduce the
/// offset itself. Note that this value only has relevance when a
/// kernel image is used for symbolization, because `kallsyms` based
/// data already include randomization adjusted addresses.
pub kaslr_offset: Option<u64>,
/// Whether or not to consult debug symbols from `vmlinux` to
/// satisfy the request (if present).
///
Expand All @@ -212,6 +219,7 @@ impl Default for Kernel {
Self {
kallsyms: MaybeDefault::Default,
vmlinux: MaybeDefault::Default,
kaslr_offset: None,
debug_syms: true,
_non_exhaustive: (),
}
Expand Down
8 changes: 6 additions & 2 deletions src/symbolize/symbolizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,7 @@ impl Symbolizer {
let Kernel {
kallsyms,
vmlinux,
kaslr_offset,
debug_syms,
_non_exhaustive: (),
} = src;
Expand Down Expand Up @@ -992,7 +993,10 @@ impl Symbolizer {
.elf_cache
.elf_resolver(&vmlinux, self.maybe_debug_dirs(*debug_syms));
match result {
Ok(resolver) => Some(resolver),
Ok(resolver) => {
log::debug!("found suitable vmlinux file `{}`", vmlinux.display());
Some(resolver)
}
Err(err) => {
log::warn!(
"failed to load vmlinux `{}`: {err}; ignoring...",
Expand All @@ -1008,7 +1012,7 @@ impl Symbolizer {
MaybeDefault::None => None,
};

KernelResolver::new(ksym_resolver.cloned(), elf_resolver.cloned())
KernelResolver::new(ksym_resolver.cloned(), elf_resolver.cloned(), *kaslr_offset)
}

#[cfg(not(linux))]
Expand Down
80 changes: 79 additions & 1 deletion tests/suite/symbolize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::env;
use std::ffi::OsStr;
use std::fs::copy;
use std::fs::read as read_file;
use std::fs::File;
use std::io;
use std::io::Read as _;
use std::io::Write as _;
Expand Down Expand Up @@ -47,6 +48,7 @@ use blazesym::__private::find_the_answer_fn_in_zip;
#[cfg(linux)]
use blazesym_dev::with_bpf_symbolization_target_addrs;

use rand::Rng as _;
use scopeguard::defer;

use tempfile::tempdir;
Expand Down Expand Up @@ -1024,6 +1026,7 @@ fn symbolize_kernel_no_valid_source() {
let kernel = Kernel {
kallsyms: MaybeDefault::None,
vmlinux: MaybeDefault::None,
kaslr_offset: Some(0),
..Default::default()
};
let src = Source::Kernel(kernel);
Expand Down Expand Up @@ -1051,6 +1054,7 @@ fn symbolize_kernel_kallsyms() {
.join("kallsyms"),
),
vmlinux,
kaslr_offset: Some(0),
..Default::default()
};
let src = Source::Kernel(kernel);
Expand Down Expand Up @@ -1107,6 +1111,7 @@ fn symbolize_kernel_vmlinux() {
.join("data")
.join("test-stable-addrs.bin"),
),
kaslr_offset: Some(0),
debug_syms: true,
..Default::default()
};
Expand All @@ -1130,12 +1135,85 @@ fn symbolize_kernel_vmlinux() {
test(src.clone(), false);
}

/// Test symbolization of a kernel address using vmlinux and the system
/// KASLR state.
#[test]
#[ignore = "test requires discoverable vmlinux file present"]
fn symbolize_kernel_system_vmlinux() {
fn find_kernel_syms() -> Vec<(Addr, String)> {
let mut file = File::open("/proc/kallsyms").unwrap();
let mut content = String::new();
let _cnt = file.read_to_string(&mut content).unwrap();
let pairs = content
.lines()
.filter_map(|line| {
let [addr, ty, name] = line
.split_ascii_whitespace()
.collect::<Vec<_>>()
.get(0..3)?
.try_into()
.unwrap();
if !["T", "t"].contains(&ty) {
return None
}
let addr = Addr::from_str_radix(addr, 16).unwrap();
Some((addr, name))
})
.collect::<Vec<_>>();

let mut rng = rand::rng();
let pairs = (0..20)
.map(|_| {
let idx = rng.random_range(0..pairs.len());
let addr = pairs[idx].0;
let name = pairs[idx].1;
(addr, name.to_string())
})
.collect::<Vec<_>>();

pairs
}

let syms = find_kernel_syms();
let kernel = Kernel {
kallsyms: MaybeDefault::None,
vmlinux: MaybeDefault::Default,
kaslr_offset: None,
..Default::default()
};
let src = Source::Kernel(kernel);
let symbolizer = Symbolizer::new();
let symbolized = symbolizer
.symbolize(
&src,
Input::AbsAddr(
syms.iter()
.map(|(addr, _name)| *addr)
.collect::<Vec<_>>()
.as_slice(),
),
)
.unwrap();
assert_eq!(symbolized.len(), syms.len());
for (i, sym) in symbolized.iter().enumerate() {
let sym = sym.as_sym().unwrap();
assert_eq!(sym.name, syms[i].1, "{sym:?} | {:?}", syms[i]);
}
}

/// Test symbolization of a kernel address inside a BPF program.
#[cfg(linux)]
#[test]
fn symbolize_kernel_bpf_program() {
with_bpf_symbolization_target_addrs(|handle_getpid, subprogram| {
let src = Source::Kernel(Kernel::default());
let kernel = Kernel {
vmlinux: MaybeDefault::None,
// KASLR offset shouldn't have any effect for BPF program
// symbolization.
kaslr_offset: Some(u64::MAX),
..Default::default()
};
let src = Source::Kernel(kernel);
let symbolizer = Symbolizer::new();
let result = symbolizer
.symbolize(&src, Input::AbsAddr(&[handle_getpid, subprogram]))
Expand Down

0 comments on commit 5f5295b

Please sign in to comment.