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 implementation of PTRACE_{GET,SET}REGSET #2044

Merged
merged 3 commits into from
Apr 14, 2024
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 changelog/2044.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add `getregset()/setregset()` for Linux/glibc/x86/x86_64/aarch64/riscv64 and
`getregs()/setregs()` for Linux/glibc/aarch64/riscv64
135 changes: 132 additions & 3 deletions src/sys/ptrace/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ pub type AddressType = *mut ::libc::c_void;
target_arch = "x86_64",
any(target_env = "gnu", target_env = "musl")
),
all(target_arch = "x86", target_env = "gnu")
)
all(target_arch = "x86", target_env = "gnu"),
all(target_arch = "aarch64", target_env = "gnu"),
all(target_arch = "riscv64", target_env = "gnu"),
),
))]
use libc::user_regs_struct;

Expand Down Expand Up @@ -170,6 +172,29 @@ libc_enum! {
}
}

libc_enum! {
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
#[repr(i32)]
/// Defining a specific register set, as used in [`getregset`] and [`setregset`].
#[non_exhaustive]
pub enum RegisterSet {
NT_PRSTATUS,
NT_PRFPREG,
NT_PRPSINFO,
NT_TASKSTRUCT,
NT_AUXV,
}
}

libc_bitflags! {
/// Ptrace options used in conjunction with the PTRACE_SETOPTIONS request.
/// See `man ptrace` for more details.
Expand Down Expand Up @@ -217,6 +242,12 @@ fn ptrace_peek(
}

/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)`
///
/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]),
/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
/// on aarch64 and riscv64.
///
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(all(
target_os = "linux",
any(
Expand All @@ -231,7 +262,58 @@ pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
ptrace_get_data::<user_regs_struct>(Request::PTRACE_GETREGS, pid)
}

/// Get user registers, as with `ptrace(PTRACE_GETREGS, ...)`
///
/// Note that since `PTRACE_GETREGS` are not available on all platforms (as in [ptrace(2)]),
/// `ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
/// on aarch64 and riscv64.
///
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(target_arch = "aarch64", target_arch = "riscv64")
))]
pub fn getregs(pid: Pid) -> Result<user_regs_struct> {
hack3ric marked this conversation as resolved.
Show resolved Hide resolved
hack3ric marked this conversation as resolved.
Show resolved Hide resolved
getregset(pid, RegisterSet::NT_PRSTATUS)
}

/// Get a particular set of user registers, as with `ptrace(PTRACE_GETREGSET, ...)`
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
pub fn getregset(pid: Pid, set: RegisterSet) -> Result<user_regs_struct> {
let request = Request::PTRACE_GETREGSET;
let mut data = mem::MaybeUninit::<user_regs_struct>::uninit();
let mut iov = libc::iovec {
iov_base: data.as_mut_ptr().cast(),
iov_len: mem::size_of::<user_regs_struct>(),
};
unsafe {
ptrace_other(
request,
pid,
set as i32 as AddressType,
(&mut iov as *mut libc::iovec).cast(),
)?;
};
Ok(unsafe { data.assume_init() })
}

/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
///
/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]),
/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
/// on aarch64 and riscv64.
///
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(all(
target_os = "linux",
any(
Expand All @@ -248,12 +330,59 @@ pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
Request::PTRACE_SETREGS as RequestType,
libc::pid_t::from(pid),
ptr::null_mut::<c_void>(),
&regs as *const _ as *const c_void,
&regs as *const user_regs_struct as *const c_void,
)
};
Errno::result(res).map(drop)
}

/// Set user registers, as with `ptrace(PTRACE_SETREGS, ...)`
///
/// Note that since `PTRACE_SETREGS` are not available on all platforms (as in [ptrace(2)]),
/// `ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ...)` is used instead to achieve the same effect
/// on aarch64 and riscv64.
///
/// [ptrace(2)]: https://www.man7.org/linux/man-pages/man2/ptrace.2.html
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(target_arch = "aarch64", target_arch = "riscv64")
))]
pub fn setregs(pid: Pid, regs: user_regs_struct) -> Result<()> {
setregset(pid, RegisterSet::NT_PRSTATUS, regs)
}

/// Set a particular set of user registers, as with `ptrace(PTRACE_SETREGSET, ...)`
#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
pub fn setregset(
pid: Pid,
set: RegisterSet,
mut regs: user_regs_struct,
) -> Result<()> {
let mut iov = libc::iovec {
iov_base: (&mut regs as *mut user_regs_struct).cast(),
iov_len: mem::size_of::<user_regs_struct>(),
};
unsafe {
ptrace_other(
Request::PTRACE_SETREGSET,
pid,
set as i32 as AddressType,
(&mut iov as *mut libc::iovec).cast(),
)?;
}
Ok(())
}

/// Function for ptrace requests that return values from the data field.
/// Some ptrace get requests populate structs or larger elements than `c_long`
/// and therefore use the data field to return values. This function handles these
Expand Down
97 changes: 93 additions & 4 deletions test/sys/test_ptrace.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(all(
target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86"),
target_env = "gnu"
target_env = "gnu",
any(target_arch = "x86_64", target_arch = "x86")
))]
use memoffset::offset_of;
use nix::errno::Errno;
Expand Down Expand Up @@ -179,8 +179,13 @@ fn test_ptrace_interrupt() {
// ptrace::{setoptions, getregs} are only available in these platforms
#[cfg(all(
target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86"),
target_env = "gnu"
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
#[test]
fn test_ptrace_syscall() {
Expand Down Expand Up @@ -226,12 +231,21 @@ fn test_ptrace_syscall() {
let get_syscall_id =
|| ptrace::getregs(child).unwrap().orig_eax as libc::c_long;

#[cfg(target_arch = "aarch64")]
let get_syscall_id =
|| ptrace::getregs(child).unwrap().regs[8] as libc::c_long;

#[cfg(target_arch = "riscv64")]
let get_syscall_id =
|| ptrace::getregs(child).unwrap().a7 as libc::c_long;

// this duplicates `get_syscall_id` for the purpose of testing `ptrace::read_user`.
#[cfg(target_arch = "x86_64")]
let rax_offset = offset_of!(libc::user_regs_struct, orig_rax);
#[cfg(target_arch = "x86")]
let rax_offset = offset_of!(libc::user_regs_struct, orig_eax);

#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
let get_syscall_from_user_area = || {
// Find the offset of `user.regs.rax` (or `user.regs.eax` for x86)
let rax_offset = offset_of!(libc::user, regs) + rax_offset;
Expand All @@ -246,6 +260,7 @@ fn test_ptrace_syscall() {
Ok(WaitStatus::PtraceSyscall(child))
);
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);

// kill exit
Expand All @@ -255,6 +270,7 @@ fn test_ptrace_syscall() {
Ok(WaitStatus::PtraceSyscall(child))
);
assert_eq!(get_syscall_id(), ::libc::SYS_kill);
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
assert_eq!(get_syscall_from_user_area(), ::libc::SYS_kill);

// receive signal
Expand All @@ -273,3 +289,76 @@ fn test_ptrace_syscall() {
}
}
}

#[cfg(all(
target_os = "linux",
target_env = "gnu",
any(
target_arch = "x86_64",
target_arch = "x86",
target_arch = "aarch64",
target_arch = "riscv64",
)
))]
#[test]
fn test_ptrace_regsets() {
use nix::sys::ptrace::{self, getregset, setregset, RegisterSet};
use nix::sys::signal::*;
use nix::sys::wait::{waitpid, WaitStatus};
use nix::unistd::fork;
use nix::unistd::ForkResult::*;

require_capability!("test_ptrace_regsets", CAP_SYS_PTRACE);

let _m = crate::FORK_MTX.lock();

match unsafe { fork() }.expect("Error: Fork Failed") {
Child => {
ptrace::traceme().unwrap();
// As recommended by ptrace(2), raise SIGTRAP to pause the child
// until the parent is ready to continue
loop {
raise(Signal::SIGTRAP).unwrap();
}
}

Parent { child } => {
assert_eq!(
waitpid(child, None),
Ok(WaitStatus::Stopped(child, Signal::SIGTRAP))
);
let mut regstruct =
getregset(child, RegisterSet::NT_PRSTATUS).unwrap();

#[cfg(target_arch = "x86_64")]
let reg = &mut regstruct.r15;
#[cfg(target_arch = "x86")]
let reg = &mut regstruct.edx;
#[cfg(target_arch = "aarch64")]
let reg = &mut regstruct.regs[16];
#[cfg(target_arch = "riscv64")]
let reg = &mut regstruct.regs[16];

*reg = 0xdeadbeefu32 as _;
let _ = setregset(child, RegisterSet::NT_PRSTATUS, regstruct);
regstruct = getregset(child, RegisterSet::NT_PRSTATUS).unwrap();

#[cfg(target_arch = "x86_64")]
let reg = regstruct.r15;
#[cfg(target_arch = "x86")]
let reg = regstruct.edx;
#[cfg(target_arch = "aarch64")]
let reg = regstruct.regs[16];
#[cfg(target_arch = "riscv64")]
let reg = regstruct.regs[16];
assert_eq!(reg, 0xdeadbeefu32 as _);

ptrace::cont(child, Some(Signal::SIGKILL)).unwrap();
match waitpid(child, None) {
Ok(WaitStatus::Signaled(pid, Signal::SIGKILL, _))
if pid == child => {}
_ => panic!("The process should have been killed"),
}
}
}
}