Skip to content

Commit

Permalink
Read vDSO from auxiliary vector (#3)
Browse files Browse the repository at this point in the history
* Read vDSO from auxiliary vector
* Read the auxv in during the startup phase

Having `putenv()` called overwrites the auxiliary vector, giving garbage
values for the vDSO base address and the page size.

Reading these values from the auxiliary constructor during the
startup phase, before any actual program code executes prevents the
issue.

The auxiliary vector is only read once and stored as a static variable.
  • Loading branch information
DavidVentura authored Jan 4, 2024
1 parent 16798e3 commit 77594f1
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 105 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ path = "src/lib.rs"
name = "tpom"
path = "src/bin.rs"
[dependencies]
ctor = "0.2.6"
goblin = "0.6.0"
libc = "0.2.137"
libc = "0.2.151"

[dev-dependencies]
serial_test = "0.9.0"
Expand Down
60 changes: 60 additions & 0 deletions src/auxv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::error::Error;
use ctor::ctor;

#[derive(Debug, Copy, Clone)]
pub struct AuxVecValues {
pub(crate) vdso_base: usize,
pub(crate) page_size: usize,
}

extern "C" {
static environ: *const *const u8;
}

unsafe fn get_auxv_ptr() -> *const usize {
// the auxiliary vector is right behind the environment variables, which
// is an array of strings, delimited by a nullpointer.
let mut env_entry_ptr = environ;

while !(*env_entry_ptr).is_null() {
env_entry_ptr = env_entry_ptr.offset(1);
}

env_entry_ptr = env_entry_ptr.offset(1);

return std::mem::transmute::<*const *const u8, *const usize>(env_entry_ptr);
}

pub(crate) fn read_aux_vec() -> Result<AuxVecValues, Box<dyn Error>> {
Ok(*AUX)
}
//#[cfg_attr(any(target_os = "linux"), link_section = ".init_array")]
#[ctor]
static AUX: AuxVecValues = {
// The auxiliary vector is an array of key:value tuples, represented as [usize, usize]
// The end is delimited by having the key == AT_NULL
let mut out = unsafe { get_auxv_ptr() };
let mut ptr = 0;
let mut pagesize = 0;
unsafe {
while *out != libc::AT_NULL as usize {
let key = *out;
let val = *out.offset(1);
if key == libc::AT_SYSINFO_EHDR as usize {
ptr = val;
}
if key == libc::AT_PAGESZ as usize {
pagesize = val;
}
out = out.offset(2);
}
}
if ptr == 0 {
panic!("Could not find vDSO base");
}
if pagesize == 0 {
panic!("Could not find page size");
}
AuxVecValues {vdso_base: ptr, page_size: pagesize}
};

4 changes: 2 additions & 2 deletions src/bin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{error::Error, time::SystemTime};

use tpom::{vdso, Kind, TVDSOFun, Time, TimeSpec, TimeVal};
use tpom::{vdso, Kind, Time, TimeSpec, TimeVal, TVDSOFun};

extern crate tpom;

Expand All @@ -25,7 +25,7 @@ fn my_time() -> Time {
pub fn main() -> Result<(), Box<dyn Error>> {
println!("Now: {:?}", SystemTime::now());
println!("Executing");
let v = vdso::vDSO::open()?;
let v = vdso::vDSO::read()?;
let og = v.entry(Kind::GetTime).ok_or("Could not find clock")?;
let backup = og.overwrite(myclock);
println!("Done, Now: {:?}, restoring", SystemTime::now());
Expand Down
10 changes: 2 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
//! }
//! }
//!
//! let v = vdso::vDSO::open().unwrap();
//! let v = vdso::vDSO::read().unwrap();
//! let og = v.entry(Kind::GetTime).ok_or("Could not find clock").unwrap();
//! let backup = og.overwrite(myclock);
//!
Expand All @@ -43,17 +43,11 @@
mod opcodes;
pub(crate) mod trampolines;
pub mod vdso;
pub mod auxv;

use crate::trampolines::*;
use crate::vdso::vDSO;

#[derive(Debug, Clone, Copy, PartialEq)]
struct Range {
start: usize,
end: usize,
writable: bool,
}

pub type Time = libc::time_t; // as libc::time_t

/// Return type for `ClockGetTime` and `ClockGetRes`; maps to
Expand Down
25 changes: 0 additions & 25 deletions src/test_files/proc/self/maps

This file was deleted.

104 changes: 36 additions & 68 deletions src/vdso.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
use crate::Range;
use crate::*;
use goblin::elf::*;
use goblin::strtab::Strtab;
use core::slice;
use std::error::Error;
use std::fs::{self, File};
use std::os::unix::prelude::FileExt;
use std::fs;

#[derive(Debug, PartialEq)]
pub(crate) struct DynSym {
pub(crate) name: String,
pub(crate) address: usize,
pub(crate) size: usize,
}

#[allow(non_camel_case_types)]
#[derive(PartialEq, Clone, Debug)]
#[derive(Debug)]
pub struct vDSO {
range: Range,
avv: auxv::AuxVecValues,
data: Vec<u8>,
}

#[cfg(target_pointer_width="32")]
const ELF_HDR_SIZE: usize = 52;

#[cfg(target_pointer_width="64")]
const ELF_HDR_SIZE: usize = 64;

impl vDSO {
pub(crate) fn read(range: &Range) -> Vec<u8> {
let mut buf = vec![0; range.end - range.start];
let f = File::open("/proc/self/mem").unwrap();
f.read_at(&mut buf, range.start as u64).unwrap();
drop(f);
buf
pub fn read() -> Result<vDSO, Box<dyn Error>> {
let auxvec = auxv::read_aux_vec()?;

// As the size of the vDSO is unknown, read first only the header which has constant size
let header_bytes: &[u8] = unsafe { slice::from_raw_parts(&*(auxvec.vdso_base as *const u8), ELF_HDR_SIZE) };
let bare_header = Elf::parse_header(&header_bytes).unwrap();
// Having parsed the header, we can now calculate the len of the vDSO
let vdso_len = usize::from(bare_header.e_shnum * bare_header.e_shentsize) + (bare_header.e_shoff as usize);
// And with the len, we can read the right amount
let vdso_bytes = unsafe { slice::from_raw_parts(&*(auxvec.vdso_base as *const u8), vdso_len) };

Ok(vDSO {data: vdso_bytes.into(), avv: auxvec })
}

pub(crate) fn change_mode(&self, write: bool) {
Expand All @@ -34,49 +46,19 @@ impl vDSO {
} else {
libc::PROT_EXEC | libc::PROT_READ
};
// As we need to mprotect() the vDSO and that can only be done in full pages, we need
// to bump the vDSO length to the next page
let vdso_size_page_aligned = (self.data.len() + self.avv.page_size-1) & !(self.avv.page_size-1);
unsafe {

libc::mprotect(
self.range.start as *mut libc::c_void,
self.range.end - self.range.start,
self.avv.vdso_base as *mut libc::c_void,
vdso_size_page_aligned,
mode,
);
}
}
fn parse_mem_map(path: Option<&str>) -> Result<Range, Box<dyn Error>> {
// could use getauxval(AT_SYSINFO_EHDR)
// but calculating the length is complicated, and i'm not really sure how to
// pass a pointer to memory as a &[u8], without specifying length

let data = fs::read_to_string(path.unwrap_or("/proc/self/maps"))?;

for line in data.lines() {
if !line.contains("[vdso]") {
continue;
}
let (range, _) = line.split_once(' ').unwrap();
let (start, end) = range.split_once('-').unwrap();
let parts: Vec<&str> = line.split_whitespace().collect();
let perms = parts[1];
return Ok(Range {
start: usize::from_str_radix(start, 16).unwrap(),
end: usize::from_str_radix(end, 16).unwrap(),
writable: perms.contains('w'),
});
}
Err("No vDSO mapped in memory range. Cannot continue".into())
}

pub fn open() -> Result<Self, Box<dyn Error>> {
vDSO::open_at(None)
}

pub fn open_at(path: Option<&str>) -> Result<Self, Box<dyn Error>> {
let r = vDSO::parse_mem_map(path)?;
Ok(vDSO {
range: r,
data: vDSO::read(&r),
})
}
pub(crate) fn dynsyms(&self) -> Vec<DynSym> {
let r = Elf::parse(&self.data).expect("bad elf");

Expand Down Expand Up @@ -123,7 +105,7 @@ impl vDSO {
/// Overwrites the process' vDSO memory at offset `symbol_address` with `opcodes`.
/// It is the caller's responsibility to provide the correct amount of data.
pub(crate) fn overwrite(&self, symbol_address: usize, opcodes: &[u8]) {
let dst_addr = self.range.start + symbol_address;
let dst_addr = self.avv.vdso_base + symbol_address;
self.change_mode(true);
for (i, b) in opcodes.iter().enumerate() {
unsafe {
Expand Down Expand Up @@ -187,27 +169,14 @@ fn get_str_til_nul(s: &Strtab, at: usize) -> String {
mod tests {
use super::*;

#[test]
fn test_parse_proc_self_maps() {
let parsed = vDSO::parse_mem_map(Some("src/test_files/proc/self/maps"));
let expected = Range {
start: 0x7fff37953000,
end: 0x7fff37955000,
writable: false,
};
assert_eq!(parsed.is_ok(), true);
assert_eq!(parsed.unwrap(), expected);
}

#[test]
fn test_dynsyms() {
let test_vdso =
fs::read("src/test_files/test_vdso_elf_1").expect("Unable to read test file");
let a = vDSO {
range: Range {
start: 0,
end: 0,
writable: true,
avv: auxv::AuxVecValues {
vdso_base: 0,
page_size: 0x1000,
},
data: test_vdso,
};
Expand Down Expand Up @@ -276,10 +245,9 @@ mod tests {
let test_vdso =
fs::read("src/test_files/test_vdso_elf_2").expect("Unable to read test file");
let a = vDSO {
range: Range {
start: 0,
end: 0,
writable: true,
avv: auxv::AuxVecValues {
vdso_base: 0,
page_size: 0x1000,
},
data: test_vdso,
};
Expand Down
22 changes: 21 additions & 1 deletion tests/pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,27 @@ mod tests {
#[test]
#[serial]
fn it_freezes_system_clock() {
let v = vdso::vDSO::open().unwrap();
let v = vdso::vDSO::read().unwrap();
let og = v
.entry(Kind::GetTime)
.ok_or("Could not find clock")
.unwrap();
let backup = og.overwrite(myclock);

let time_a = SystemTime::now();
std::thread::sleep(std::time::Duration::from_millis(1)); // clock in github actions is coarse
let time_b = SystemTime::now();
backup.restore();
assert_eq!(time_a, time_b);
}

#[test]
#[serial]
fn it_works_after_putenv() {
unsafe {
libc::putenv(b"SOMETHING=VALUE\0".as_ptr() as *mut _);
}
let v = vdso::vDSO::read().unwrap();
let og = v
.entry(Kind::GetTime)
.ok_or("Could not find clock")
Expand Down

0 comments on commit 77594f1

Please sign in to comment.