Skip to content

Commit

Permalink
Include KASLR offset in vmlinux based symbolization logic
Browse files Browse the repository at this point in the history
Our vmlinux based symbolization can only work properly of the kernel
location is not randomized, because then the in-memory addresses should
match up with those in the file.
To make sure that symbolization will work properly when KASLR (kernel
address space layout randomization) is enabled, query the corresponding
randomization offset and incorporate it in symbolization requests. Also
add a new attribute, kaslr_offset, to the Kernel symbolization source
that allows for passing in the offset if necessary.

Signed-off-by: Daniel Müller <deso@posteo.net>
  • Loading branch information
d-e-s-o committed Feb 3, 2025
1 parent 9152b8b commit dd26d8f
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 21 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
uses: ./.github/workflows/build-linux.yml
secrets: inherit
with:
# Please keep in sync with vmlinux release version specification
# below.
rev: v6.11
config: 'data/config'
build:
Expand Down Expand Up @@ -221,8 +223,10 @@ jobs:
--header "X-GitHub-Api-Version: 2022-11-28" \
--output artifact.zip \
"${ARTIFACT_URL}"
# This unzip will produce the kernel bzImage.
# This unzip will produce the kernel bzImage and vmlinux.
unzip artifact.zip
# Put vmlinux file into a location amenable to auto-discovery.
sudo mv vmlinux /boot/vmlinux-6.11.0
cat <<EOF > main.sh
#!/bin/sh
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Unreleased
file, if present
- Changed `symbolize::Kernel::{kallsyms,kernel_image}` to support
disabling of the source
- Adjusted `vmlinux` based kernel address symbolization logic to take
into account system KASLR state
- Added `kaslr_offset` member to `symbolize::Kernel`


0.2.0-rc.2
Expand Down
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
24 changes: 16 additions & 8 deletions src/kernel/kaslr.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
use std::error::Error as StdError;
use std::fs::File;
use std::io;
use std::io::Read as _;
use std::path::Path;
use std::mem::size_of;
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 All @@ -32,7 +27,7 @@ const VMCOREINFO_NAME: &[u8] = b"VMCOREINFO\0";
/// "Parse" the VMCOREINFO descriptor.
///
/// This underspecified blob roughly has the following format:
/// ```
/// ```text
/// OSRELEASE=6.2.15-100.fc36.x86_64
/// BUILD-ID=d3d01c80278f8927486b7f01d0ab6be77784dceb
/// PAGESIZE=4096
Expand Down Expand Up @@ -112,6 +107,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
78 changes: 77 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,83 @@ 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,
..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 dd26d8f

Please sign in to comment.