reverie-ptrace/src/vdso.rs (190 lines of code) (raw):
/*
* Copyright (c) 2018-2019, Trustees of Indiana University
* ("University Works" via Baojun Wang)
* Copyright (c) 2018-2019, Ryan Newton
* ("Traditional Works of Scholarship")
* Copyright (c) 2020-, Facebook, Inc. and its affiliates.
*
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
//! provide APIs to disable VDSOs at runtime.
use goblin::elf::Elf;
use lazy_static::lazy_static;
use nix::{sys::mman::ProtFlags, unistd};
use reverie::{
syscalls::{AddrMut, MemoryAccess, Mprotect},
Error, Guest, Tool,
};
use std::collections::HashMap;
use tracing::debug;
/*
* byte code for the new psudo vdso functions
* which do the actual syscalls.
* NB: the byte code must be 8 bytes
* aligned
*/
#[allow(non_upper_case_globals)]
const __vdso_time: &[u8] = &[
0xb8, 0xc9, 0x0, 0x0, 0x0, // mov %SYS_time, %eax
0x0f, 0x05, // syscall
0xc3, // retq
0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, // nopl 0x0(%rax, %rax, 1)
0x00,
];
#[allow(non_upper_case_globals)]
const __vdso_clock_gettime: &[u8] = &[
0xb8, 0xe4, 0x00, 0x00, 0x00, // mov SYS_clock_gettime, %eax
0x0f, 0x05, // syscall
0xc3, // retq
0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, // nopl 0x0(%rax, %rax, 1)
0x00,
];
#[allow(non_upper_case_globals)]
const __vdso_getcpu: &[u8] = &[
0x48, 0x85, 0xff, // test %rdi, %rdi
0x74, 0x06, // je ..
0xc7, 0x07, 0x00, 0x00, 0x00, 0x00, // movl $0x0, (%rdi)
0x48, 0x85, 0xf6, // test %rsi, %rsi
0x74, 0x06, // je ..
0xc7, 0x06, 0x00, 0x00, 0x00, 0x00, // movl $0x0, (%rsi)
0x31, 0xc0, // xor %eax, %eax
0xc3, // retq
0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00,
]; // nopl 0x0(%rax)
#[allow(non_upper_case_globals)]
const __vdso_gettimeofday: &[u8] = &[
0xb8, 0x60, 0x00, 0x00, 0x00, // mov SYS_gettimeofday, %eax
0x0f, 0x05, // syscall
0xc3, // retq
0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, // nopl 0x0(%rax, %rax, 1)
0x00,
];
const VDSO_SYMBOLS: &[&str] = &[
"__vdso_time",
"__vdso_clock_gettime",
"__vdso_getcpu",
"__vdso_gettimeofday",
];
lazy_static! {
static ref VDSO_PATCH_INFO: HashMap<String, (u64, usize, &'static [u8])> = {
let info = vdso_get_symbols_info();
let mut res: HashMap<String, (u64, usize, &'static [u8])> = HashMap::new();
let funcs = &[
__vdso_time,
__vdso_clock_gettime,
__vdso_getcpu,
__vdso_gettimeofday,
];
VDSO_SYMBOLS.iter().zip(funcs).for_each(|(k, v)| {
let name = String::from(*k);
if let Some(&(base, size)) = info.get(&name) {
assert!(v.len() <= size);
res.insert(String::from(*k), (base, size, v));
}
});
res
};
}
// get vdso symbols offset/size from current process
// assuming vdso binary is the same for all processes
// so that we don't have to decode vdso for each process
fn vdso_get_symbols_info() -> HashMap<String, (u64, usize)> {
let mut res: HashMap<String, (u64, usize)> = HashMap::new();
procfs::process::Process::new(unistd::getpid().as_raw())
.and_then(|p| p.maps())
.unwrap_or_else(|_| Vec::new())
.iter()
.find(|e| e.pathname == procfs::process::MMapPath::Vdso)
.and_then(|vdso| {
let slice = unsafe {
std::slice::from_raw_parts(
vdso.address.0 as *mut u8,
(vdso.address.1 - vdso.address.0) as usize,
)
};
Elf::parse(slice)
.map(|elf| {
let strtab = elf.dynstrtab;
elf.dynsyms.iter().for_each(|sym| {
let sym_name = &strtab[sym.st_name];
if VDSO_SYMBOLS.contains(&sym_name) {
debug_assert!(sym.is_function());
res.insert(
String::from(sym_name),
(sym.st_value, sym.st_size as usize),
);
}
});
})
.ok()
});
res
}
/// patch VDSOs when enabled
///
/// `guest` must be in one of ptrace's stopped states.
pub async fn vdso_patch<G, T>(guest: &mut G) -> Result<(), Error>
where
G: Guest<T>,
T: Tool,
{
if let Some(vdso) = procfs::process::Process::new(guest.pid().as_raw())
.and_then(|p| p.maps())
.unwrap_or_else(|_| Vec::new())
.iter()
.find(|e| e.pathname == procfs::process::MMapPath::Vdso)
{
let mut memory = guest.memory();
// Allow write access to the vdso memory page.
guest
.inject_with_retry(
Mprotect::new()
.with_addr(AddrMut::from_raw(vdso.address.0 as usize))
.with_len((vdso.address.1 - vdso.address.0) as usize)
.with_protection(
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC,
),
)
.await?;
for (name, (offset, size, bytes)) in VDSO_PATCH_INFO.iter() {
let start = vdso.address.0 + offset;
assert!(bytes.len() <= *size);
let rptr = AddrMut::from_raw(start as usize).unwrap();
memory.write_exact(rptr, bytes)?;
assert!(*size >= bytes.len());
if *size > bytes.len() {
let fill: Vec<u8> = std::iter::repeat(0x90u8).take(size - bytes.len()).collect();
memory.write_exact(unsafe { rptr.add(bytes.len()) }, &fill)?;
}
debug!("{} patched {}@{:x}", guest.pid(), name, start);
}
guest
.inject_with_retry(
Mprotect::new()
.with_addr(AddrMut::from_raw(vdso.address.0 as usize))
.with_len((vdso.address.1 - vdso.address.0) as usize)
.with_protection(ProtFlags::PROT_READ | ProtFlags::PROT_EXEC),
)
.await?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_find_vdso() {
assert!(
procfs::process::Process::new(unistd::getpid().as_raw())
.and_then(|p| p.maps())
.unwrap_or_else(|_| Vec::new())
.iter()
.any(|e| e.pathname == procfs::process::MMapPath::Vdso)
);
}
#[test]
fn vdso_can_find_symbols_info() {
assert!(!vdso_get_symbols_info().is_empty());
}
#[test]
fn vdso_patch_info_is_valid() {
let info = &VDSO_PATCH_INFO;
info.iter().for_each(|i| println!("info: {:x?}", i));
assert!(!info.is_empty());
}
}