Skip to content

Commit ea0cb58

Browse files
committed
Correct behavior when reading cross page boundaries
1 parent 6fa7c25 commit ea0cb58

File tree

2 files changed

+122
-78
lines changed

2 files changed

+122
-78
lines changed

src/target/linux.rs

Lines changed: 116 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use nix::unistd::{getpid, Pid};
22
use procfs::process::Process;
33
use std::{
4+
cmp::min,
45
fs::File,
56
io::{BufRead, BufReader},
67
marker::PhantomData,
@@ -77,6 +78,50 @@ impl ReadOp {
7778
iov_len: self.len,
7879
}
7980
}
81+
82+
/// Splits ReadOp so that each resulting ReadOp resides in only one memory page.
83+
fn split_on_page_boundary(&self) -> Vec<ReadOp> {
84+
let page_size: usize;
85+
86+
unsafe {
87+
page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize;
88+
}
89+
90+
let mut out = Vec::new();
91+
92+
let mut left = self.len;
93+
94+
let to_next_readop = std::cmp::min(left, page_size - ((page_size - 1) & self.remote_base));
95+
out.push(ReadOp {
96+
remote_base: self.remote_base,
97+
len: to_next_readop,
98+
local_ptr: self.local_ptr,
99+
});
100+
left -= to_next_readop;
101+
102+
loop {
103+
if left == 0 {
104+
break;
105+
}
106+
if left < page_size {
107+
out.push(ReadOp {
108+
remote_base: self.remote_base + (self.len - left),
109+
len: left,
110+
local_ptr: (self.local_ptr as usize + (self.len - left)) as *mut libc::c_void,
111+
});
112+
break;
113+
} else {
114+
out.push(ReadOp {
115+
remote_base: self.remote_base + (self.len - left),
116+
len: page_size,
117+
local_ptr: (self.local_ptr as usize + (self.len - left)) as *mut libc::c_void,
118+
});
119+
left -= page_size;
120+
}
121+
}
122+
123+
out
124+
}
80125
}
81126

82127
/// Allows to read memory from different locations in debuggee's memory as a single operation.
@@ -109,11 +154,14 @@ impl<'a> ReadMemory<'a> {
109154
/// In case of doubt, wrap the type in [`mem::MaybeUninit`].
110155
// todo: further document mem safety - e.g., what happens in the case of partial read
111156
pub unsafe fn read<T>(mut self, val: &'a mut T, remote_base: usize) -> Self {
112-
self.read_ops.push(ReadOp {
113-
remote_base,
114-
len: mem::size_of::<T>(),
115-
local_ptr: val as *mut T as *mut libc::c_void,
116-
});
157+
self.read_ops.append(
158+
&mut ReadOp {
159+
remote_base,
160+
len: mem::size_of::<T>(),
161+
local_ptr: val as *mut T as *mut libc::c_void,
162+
}
163+
.split_on_page_boundary(),
164+
);
117165

118166
self
119167
}
@@ -154,65 +202,58 @@ impl<'a> ReadMemory<'a> {
154202
)
155203
};
156204

157-
if bytes_read == -1 {
158-
// Check for errors related to permissions.
159-
// todo: restructure the code to be more readable
160-
match nix::Error::last() {
161-
nix::Error::Sys(nix::errno::Errno::EFAULT) => {
162-
let maps = Process::new(self.pid.as_raw())?.maps()?;
163-
164-
let protected_maps = maps
165-
.iter()
166-
.filter(|map| map.perms.chars().nth(0) != Some('r'))
167-
.collect::<Vec<_>>();
168-
169-
// Splits readOps to those that read from read protected memory and those that do not.
170-
let (protected, readable): (_, Vec<_>) =
171-
self.read_ops.into_iter().partition(|read_op| {
172-
protected_maps.iter().any(|map| {
173-
map.address.0 as usize <= read_op.remote_base
174-
&& read_op.remote_base < map.address.1 as usize
175-
})
176-
});
177-
178-
// Read data that can be read using libc::process_vm_readv
179-
let new_remote_iov = readable
180-
.iter()
181-
.map(ReadOp::as_remote_iovec)
182-
.collect::<Vec<_>>();
183-
184-
let new_local_iov = readable
185-
.iter()
186-
.map(ReadOp::as_local_iovec)
187-
.collect::<Vec<_>>();
188-
189-
let new_bytes_read = unsafe {
190-
// todo: document unsafety
191-
libc::process_vm_readv(
192-
self.pid.into(),
193-
new_local_iov.as_ptr(),
194-
new_local_iov.len() as libc::c_ulong,
195-
new_remote_iov.as_ptr(),
196-
new_remote_iov.len() as libc::c_ulong,
197-
0,
198-
)
199-
};
200-
201-
if new_bytes_read == -1 {
202-
return Err(Box::new(nix::Error::last()));
203-
}
205+
if bytes_read < read_len as isize {
206+
let maps = Process::new(self.pid.as_raw())?.maps()?;
207+
208+
let protected_maps = maps
209+
.iter()
210+
.filter(|map| map.perms.chars().nth(0) != Some('r'))
211+
.collect::<Vec<_>>();
212+
213+
// Splits readOps to those that read from read protected memory and those that do not.
214+
let (protected, readable): (_, Vec<_>) =
215+
self.read_ops.into_iter().partition(|read_op| {
216+
protected_maps.iter().any(|map| {
217+
map.address.0 as usize <= read_op.remote_base
218+
&& read_op.remote_base < map.address.1 as usize
219+
})
220+
});
221+
222+
// Read data that can be read using libc::process_vm_readv
223+
let new_remote_iov = readable
224+
.iter()
225+
.map(ReadOp::as_remote_iovec)
226+
.collect::<Vec<_>>();
227+
228+
let new_local_iov = readable
229+
.iter()
230+
.map(ReadOp::as_local_iovec)
231+
.collect::<Vec<_>>();
232+
233+
let new_bytes_read = unsafe {
234+
// todo: document unsafety
235+
libc::process_vm_readv(
236+
self.pid.into(),
237+
new_local_iov.as_ptr(),
238+
new_local_iov.len() as libc::c_ulong,
239+
new_remote_iov.as_ptr(),
240+
new_remote_iov.len() as libc::c_ulong,
241+
0,
242+
)
243+
};
244+
245+
if new_bytes_read == -1 {
246+
return Err(Box::new(nix::Error::last()));
247+
}
204248

205-
// Read rest of the data using PTRACE_PEEKDATA
206-
for read_op in protected {
207-
unix::read(
208-
self.pid,
209-
read_op.remote_base,
210-
read_op.len,
211-
read_op.local_ptr,
212-
)?;
213-
}
214-
}
215-
error => return Err(Box::new(error)),
249+
// Read rest of the data using PTRACE_PEEKDATA
250+
for read_op in protected {
251+
unix::read(
252+
self.pid,
253+
read_op.remote_base,
254+
read_op.len,
255+
read_op.local_ptr,
256+
)?;
216257
}
217258
}
218259

@@ -330,10 +371,14 @@ mod tests {
330371

331372
#[test]
332373
fn read_cross_page_memory() {
333-
let mut read_var_op = [0u32; 2];
374+
let mut read_var_op = [0u32; PAGE_SIZE + 2];
375+
376+
let mut var = [123; PAGE_SIZE + 2];
377+
var[0] = 321;
378+
var[PAGE_SIZE + 1] = 234;
334379

335380
unsafe {
336-
let layout = Layout::from_size_align(PAGE_SIZE * 2, PAGE_SIZE).unwrap();
381+
let layout = Layout::from_size_align(PAGE_SIZE * 3, PAGE_SIZE).unwrap();
337382
let ptr = alloc_zeroed(layout);
338383

339384
let array_ptr = (ptr as usize + PAGE_SIZE - std::mem::size_of::<u32>()) as *mut u8;
@@ -342,12 +387,8 @@ mod tests {
342387

343388
match fork() {
344389
Ok(ForkResult::Child) => {
345-
346-
*(array_ptr as *mut [u32; 2]) = [123, 456];
347-
mprotect(
348-
second_page_ptr,
349-
PAGE_SIZE,
350-
ProtFlags::PROT_WRITE)
390+
*(array_ptr as *mut [u32; PAGE_SIZE + 2]) = var;
391+
mprotect(second_page_ptr, PAGE_SIZE, ProtFlags::PROT_WRITE)
351392
.expect("Failed to mprotect");
352393

353394
// Parent reads memory
@@ -368,8 +409,10 @@ mod tests {
368409
.read(&mut read_var_op, array_ptr as *const _ as usize)
369410
.apply()
370411
.expect("Failed to apply memop");
371-
372-
assert_eq!([123, 456], read_var_op);
412+
413+
for i in 0..PAGE_SIZE + 2 {
414+
assert_eq!(var[i], read_var_op[i]);
415+
}
373416

374417
dealloc(ptr, layout);
375418

src/target/unix.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,17 @@ pub(crate) fn read(
6868
unsafe {
6969
*((local_ptr as usize + offset) as *mut i64) = data;
7070
}
71-
}
72-
else {
71+
} else {
7372
unsafe {
74-
let previous_bytes: &mut [u8] = std::slice::from_raw_parts_mut((local_ptr as usize + offset) as *mut u8, len-offset);
73+
let previous_bytes: &mut [u8] = std::slice::from_raw_parts_mut(
74+
(local_ptr as usize + offset) as *mut u8,
75+
len - offset,
76+
);
7577
let data_bytes = data.to_ne_bytes();
7678

77-
for i in 0..(len-offset) {
79+
for i in 0..(len - offset) {
7880
previous_bytes[i] = data_bytes[i];
7981
}
80-
8182
}
8283
}
8384

0 commit comments

Comments
 (0)