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 authored and danielocfb committed Feb 7, 2025
1 parent bbfcb09 commit a747b30
Show file tree
Hide file tree
Showing 12 changed files with 170 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 @@ -152,6 +152,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
23 changes: 15 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,18 @@ fn find_kcore_kaslr_offset() -> Result<Option<u64>> {
Ok(offset)
}

pub(crate) fn find_kalsr_offset() -> Result<Option<u64>> {
// TODO: Try other methods of determining KASLR offset, including
// comparisons between `/proc/kallsyms` values to
// `System.map-*` contents or parsing `dmesg` (no, really...)

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)
}
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 than 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
Loading

0 comments on commit a747b30

Please sign in to comment.