Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic for reading KASLR offset #951

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/elf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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
21 changes: 19 additions & 2 deletions src/elf/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -887,9 +887,9 @@ where
_backend: B::ObjTy,
}

#[cfg(test)]
impl ElfParser<File> {
#[cfg(test)]
pub(crate) fn open_file_io<P>(file: File, path: P) -> Self
fn open_file_io<P>(file: File, path: P) -> Self
where
P: Into<PathBuf>,
{
Expand All @@ -903,6 +903,23 @@ impl ElfParser<File> {
};
parser
}

/// Create an `ElfParser` from an open file.
pub(crate) fn open_non_mmap<P>(path: P) -> Result<Self>
where
P: Into<PathBuf>,
{
let path = path.into();
let file =
File::open(&path).with_context(|| format!("failed to open `{}`", path.display()))?;
let slf = Self::open_file_io(file, path);
Ok(slf)
}

/// Retrieve a reference to the backend in use.
pub(crate) fn backend(&self) -> &File {
&self._backend
}
}

impl ElfParser<Mmap> {
Expand Down
40 changes: 39 additions & 1 deletion src/elf/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ impl ElfN_Ehdr<'_> {
}


pub(crate) const PT_LOAD: u32 = 1;
pub(crate) const PT_LOAD: u32 = 1; /* Loadable program segment */
pub(crate) const PT_NOTE: u32 = 4; /* Auxiliary information */


#[derive(Copy, Clone, Debug, Default)]
Expand Down Expand Up @@ -275,6 +276,32 @@ impl Has32BitTy for Elf64_Phdr {
pub(crate) type ElfN_Phdr<'elf> = ElfN<'elf, Elf64_Phdr>;
pub(crate) type ElfN_Phdrs<'elf> = ElfNSlice<'elf, Elf64_Phdr>;

impl ElfN_Phdr<'_> {
#[inline]
pub fn type_(&self) -> Elf64_Word {
match self {
ElfN::B32(phdr) => phdr.p_type,
ElfN::B64(phdr) => phdr.p_type,
}
}

#[inline]
pub fn offset(&self) -> Elf64_Off {
match self {
ElfN::B32(phdr) => phdr.p_offset.into(),
ElfN::B64(phdr) => phdr.p_offset,
}
}

#[inline]
pub fn file_size(&self) -> Elf64_Xword {
match self {
ElfN::B32(phdr) => phdr.p_filesz.into(),
ElfN::B64(phdr) => phdr.p_filesz,
}
}
}


pub(crate) const PF_X: Elf64_Word = 1;

Expand Down Expand Up @@ -708,6 +735,17 @@ mod tests {
let _val = shdr.addr();
let _val = shdr.link();

let phdr32 = Elf32_Phdr::default();
let phdr64 = Elf64_Phdr::default();
for phdr in [
ElfN_Phdr::B32(Cow::Borrowed(&phdr32)),
ElfN_Phdr::B64(Cow::Borrowed(&phdr64)),
] {
let _val = phdr.type_();
let _val = phdr.offset();
let _val = phdr.file_size();
}

let sym32 = Elf32_Sym::default();
let sym = ElfN_Sym::B32(Cow::Borrowed(&sym32));
let _val = sym.value();
Expand Down
147 changes: 147 additions & 0 deletions src/normalize/kernel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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::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::IntoError as _;
use crate::Result;

use super::normalizer::Output;


/// The absolute path to the `kcore` `proc` node.
const PROC_KCORE: &str = "/proc/kcore";
/// The name of the `VMCOREINFO` ELF note.
///
/// See https://www.kernel.org/doc/html/latest/admin-guide/kdump/vmcoreinfo.html
const VMCOREINFO_NAME: &[u8] = b"VMCOREINFO\0";


/// "Parse" the VMCOREINFO descriptor.
///
/// This underspecified blob roughly has the following format:
/// ```
/// OSRELEASE=6.2.15-100.fc36.x86_64
/// BUILD-ID=d3d01c80278f8927486b7f01d0ab6be77784dceb
/// PAGESIZE=4096
/// SYMBOL(init_uts_ns)=ffffffffb72b8160
/// OFFSET(uts_namespace.name)=0
/// [...]
/// ```
fn parse_vmcoreinfo_desc(desc: &[u8]) -> impl Iterator<Item = (&[u8], &[u8])> {
desc.split(|&b| b == b'\n')
.filter_map(|line| split_bytes(line, |b| b == b'='))
}

/// Find and read the `KERNELOFFSET` note in a "kcore" file represented by
/// `parser` (i.e., already opened as an ELF).
fn find_kaslr_offset(parser: &ElfParser<File>) -> Result<Option<u64>> {
let phdrs = parser.program_headers()?;
for phdr in phdrs.iter(0) {
if phdr.type_() != elf::types::PT_NOTE {
continue
}

let file = parser.backend();
let mut offset = phdr.offset();

// Iterate through all available notes. See `elf(5)` for
// details.
while offset + (size_of::<ElfN_Nhdr>() as u64) <= phdr.file_size() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: blazesym parses Elf notes at least in two places now, right? Why not add a proper iterator support and clean up this code (and the one that does build ID, right?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I thought about it, but couldn't find a good abstraction and the implementations have to exhibit certain differences for technical reasons. It's just a few lines of code, so I think it's fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No big deal and can be done as a follow up. But I do find all those offset checks, file size, etc quite mundane and error prone, so I still feel like iterator would be an improvement. It can return Nhdr + name/descr slices, which would save a bunch of error check and otherwise distracting plumbing. But up to you.

let nhdr = file
.read_pod_obj::<ElfN_Nhdr>(offset)
.context("failed to read kcore note header")?;
offset += size_of::<ElfN_Nhdr>() as u64;

let name = if nhdr.n_namesz > 0 {
let name = file.read_pod_slice::<u8>(offset, nhdr.n_namesz as _)?;
offset += u64::from(align_up_u32(nhdr.n_namesz, 4));
Some(name)
} else {
None
};

// We are looking for the note named `VMCOREINFO`.
if name.as_deref() == Some(VMCOREINFO_NAME) {
if nhdr.n_descsz > 0 {
let desc = file.read_pod_slice::<u8>(offset, nhdr.n_descsz as _)?;
let offset = parse_vmcoreinfo_desc(&desc)
.find(|(key, _value)| key == b"KERNELOFFSET")
// The value is in hexadecimal format. Go figure.
.map(|(_key, value)| {
from_radix_16(value).ok_or_invalid_data(|| {
format!("failed to parse KERNELOFFSET value `{value:x?}`")
})
})
.transpose();
return offset
}

// There shouldn't be multiple notes with that name,
// but I suppose it can't hurt to keep checking...?
}

offset += u64::from(align_up_u32(nhdr.n_descsz, 4));
}
}
Ok(None)
}


#[cfg(test)]
mod tests {
use super::*;

use test_log::test;

use crate::ErrorKind;


/// Check that we can parse a dummy VMCOREINFO descriptor.
#[test]
fn vmcoreinfo_desc_parsing() {
let desc = b"OSRELEASE=6.2.15-100.fc36.x86_64
BUILD-ID=d3d01c80278f8927486b7f01d0ab6be77784dceb
SYMBOL(init_uts_ns)=ffffffffb72b8160
OFFSET(uts_namespace.name)=0
PAGESIZE=4096
";

let page_size = parse_vmcoreinfo_desc(desc)
.find(|(key, _value)| key == b"PAGESIZE")
.map(|(_key, value)| value)
.unwrap();
assert_eq!(page_size, b"4096");
}

/// Check that we can determine the system's KASLR state.
#[test]
fn kaslr_offset_reading() {
// Always attempt reading the KASLR to exercise the VMCOREINFO
// parsing path.
// Note that we cannot use the regular mmap based ELF parser
// backend for this file, as it cannot be mmap'ed. We have to
// fall back to using regular I/O instead.
let parser = match ElfParser::open_non_mmap(PROC_KCORE) {
Ok(parser) => parser,
Err(err) if err.kind() == ErrorKind::NotFound => return,
Err(err) => panic!("{err}"),
};
// We care about the parsing logic, but can't make any claims
// about the expected offset at this point.
let _offset = find_kaslr_offset(&parser).unwrap();
}
}
4 changes: 4 additions & 0 deletions src/normalize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@

pub(crate) mod buildid;
pub(crate) mod ioctl;
// Still work in progress.
#[allow(unused)]
#[cfg(test)]
mod kernel;
mod meta;
mod normalizer;
mod user;
Expand Down
Loading