pprof/
addr_validate.rs

1use std::{
2    mem::size_of,
3    sync::atomic::{AtomicI32, Ordering},
4};
5
6use nix::{
7    errno::Errno,
8    unistd::{close, read, write},
9};
10
11struct Pipes {
12    read_fd: AtomicI32,
13    write_fd: AtomicI32,
14}
15
16static MEM_VALIDATE_PIPE: Pipes = Pipes {
17    read_fd: AtomicI32::new(-1),
18    write_fd: AtomicI32::new(-1),
19};
20
21#[inline]
22#[cfg(any(target_os = "android", target_os = "linux"))]
23fn create_pipe() -> nix::Result<(i32, i32)> {
24    use nix::fcntl::OFlag;
25    use nix::unistd::pipe2;
26
27    pipe2(OFlag::O_CLOEXEC | OFlag::O_NONBLOCK)
28}
29
30#[inline]
31#[cfg(any(target_os = "macos", target_os = "freebsd"))]
32fn create_pipe() -> nix::Result<(i32, i32)> {
33    use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
34    use nix::unistd::pipe;
35    use std::os::unix::io::RawFd;
36
37    fn set_flags(fd: RawFd) -> nix::Result<()> {
38        let mut flags = FdFlag::from_bits(fcntl(fd, FcntlArg::F_GETFD)?).unwrap();
39        flags |= FdFlag::FD_CLOEXEC;
40        fcntl(fd, FcntlArg::F_SETFD(flags))?;
41        let mut flags = OFlag::from_bits(fcntl(fd, FcntlArg::F_GETFL)?).unwrap();
42        flags |= OFlag::O_NONBLOCK;
43        fcntl(fd, FcntlArg::F_SETFL(flags))?;
44        Ok(())
45    }
46
47    let (read_fd, write_fd) = pipe()?;
48    set_flags(read_fd)?;
49    set_flags(write_fd)?;
50    Ok((read_fd, write_fd))
51}
52
53fn open_pipe() -> nix::Result<()> {
54    // ignore the result
55    let _ = close(MEM_VALIDATE_PIPE.read_fd.load(Ordering::SeqCst));
56    let _ = close(MEM_VALIDATE_PIPE.write_fd.load(Ordering::SeqCst));
57
58    let (read_fd, write_fd) = create_pipe()?;
59
60    MEM_VALIDATE_PIPE.read_fd.store(read_fd, Ordering::SeqCst);
61    MEM_VALIDATE_PIPE.write_fd.store(write_fd, Ordering::SeqCst);
62
63    Ok(())
64}
65
66// validate whether the address `addr` is readable through `write()` to a pipe
67//
68// if the second argument of `write(ptr, buf)` is not a valid address, the
69// `write()` will return an error the error number should be `EFAULT` in most
70// cases, but we regard all errors (except EINTR) as a failure of validation
71pub fn validate(addr: *const libc::c_void) -> bool {
72    // it's a short circuit for null pointer, as it'll give an error in
73    // `std::slice::from_raw_parts` if the pointer is null.
74    if addr.is_null() {
75        return false;
76    }
77
78    const CHECK_LENGTH: usize = 2 * size_of::<*const libc::c_void>() / size_of::<u8>();
79
80    // read data in the pipe
81    let read_fd = MEM_VALIDATE_PIPE.read_fd.load(Ordering::SeqCst);
82    let valid_read = loop {
83        let mut buf = [0u8; CHECK_LENGTH];
84
85        match read(read_fd, &mut buf) {
86            Ok(bytes) => break bytes > 0,
87            Err(_err @ Errno::EINTR) => continue,
88            Err(_err @ Errno::EAGAIN) => break true,
89            Err(_) => break false,
90        }
91    };
92
93    if !valid_read && open_pipe().is_err() {
94        return false;
95    }
96
97    let write_fd = MEM_VALIDATE_PIPE.write_fd.load(Ordering::SeqCst);
98    loop {
99        let buf = unsafe { std::slice::from_raw_parts(addr as *const u8, CHECK_LENGTH) };
100
101        match write(write_fd, buf) {
102            Ok(bytes) => break bytes > 0,
103            Err(_err @ Errno::EINTR) => continue,
104            Err(_) => break false,
105        }
106    }
107}
108
109#[cfg(test)]
110mod test {
111    use super::*;
112
113    #[test]
114    fn validate_stack() {
115        let i = 0;
116
117        assert!(validate(&i as *const _ as *const libc::c_void));
118    }
119
120    #[test]
121    fn validate_heap() {
122        let vec = vec![0; 1000];
123
124        for i in vec.iter() {
125            assert!(validate(i as *const _ as *const libc::c_void));
126        }
127    }
128
129    #[test]
130    fn failed_validate() {
131        assert!(!validate(std::ptr::null::<libc::c_void>()));
132        assert!(!validate(-1_i32 as usize as *const libc::c_void))
133    }
134}