pprof2/
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    const CHECK_LENGTH: usize = 2 * size_of::<*const libc::c_void>() / size_of::<u8>();
73
74    // read data in the pipe
75    let read_fd = MEM_VALIDATE_PIPE.read_fd.load(Ordering::SeqCst);
76    let valid_read = loop {
77        let mut buf = [0u8; CHECK_LENGTH];
78
79        match read(read_fd, &mut buf) {
80            Ok(bytes) => break bytes > 0,
81            Err(_err @ Errno::EINTR) => continue,
82            Err(_err @ Errno::EAGAIN) => break true,
83            Err(_) => break false,
84        }
85    };
86
87    if !valid_read && open_pipe().is_err() {
88        return false;
89    }
90
91    let write_fd = MEM_VALIDATE_PIPE.write_fd.load(Ordering::SeqCst);
92    loop {
93        let ptr = addr as *const u8;
94
95        if !ptr.is_aligned() || ptr.is_null() {
96            break false;
97        }
98
99        let buf = unsafe { std::slice::from_raw_parts(ptr, 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}