Skip to content

Commit fa3478f

Browse files
committed
Make read_memory fail on unitialized memory; add is_memory_accessible
1 parent 467e7bb commit fa3478f

File tree

5 files changed

+161
-22
lines changed

5 files changed

+161
-22
lines changed

crates/polkavm/src/api.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,56 @@ impl RawInstance {
10741074
.into_result("failed to reset the instance's memory"))
10751075
}
10761076

1077+
/// Returns whether a given chunk of memory is accessible through [`read_memory_into`](Self::read_memory_into)/[`write_memory`](Self::write_memory).
1078+
///
1079+
/// If `size` is zero then this will always return `true`.
1080+
pub fn is_memory_accessible(&self, address: u32, size: u32, is_writable: bool) -> bool {
1081+
if size == 0 {
1082+
return true;
1083+
}
1084+
1085+
if address < 0x10000 {
1086+
return false;
1087+
}
1088+
1089+
if u64::from(address) + cast(size).to_u64() > 0x100000000 {
1090+
return false;
1091+
}
1092+
1093+
#[inline]
1094+
fn is_within(range: core::ops::Range<u32>, address: u32, size: u32) -> bool {
1095+
let address_end = u64::from(address) + cast(size).to_u64();
1096+
address >= range.start && address_end <= u64::from(range.end)
1097+
}
1098+
1099+
if !self.module.is_dynamic_paging() {
1100+
let map = self.module.memory_map();
1101+
if is_within(map.stack_range(), address, size) {
1102+
return true;
1103+
}
1104+
1105+
let heap_size = self.heap_size();
1106+
let heap_top = map.heap_base() + heap_size;
1107+
let heap_top = self.module.round_to_page_size_up(heap_top);
1108+
if is_within(map.rw_data_address()..heap_top, address, size) {
1109+
return true;
1110+
}
1111+
1112+
let aux_size = access_backend!(self.backend, |backend| backend.accessible_aux_size());
1113+
if is_within(map.aux_data_address()..map.aux_data_address() + aux_size, address, size) {
1114+
return true;
1115+
}
1116+
1117+
if !is_writable && is_within(map.ro_data_range(), address, size) {
1118+
return true;
1119+
}
1120+
1121+
false
1122+
} else {
1123+
access_backend!(self.backend, |backend| backend.is_memory_accessible(address, size, is_writable))
1124+
}
1125+
}
1126+
10771127
/// Reads the VM's memory.
10781128
pub fn read_memory_into<'slice, B>(&self, address: u32, buffer: &'slice mut B) -> Result<&'slice mut [u8], MemoryAccessError>
10791129
where
@@ -1124,6 +1174,21 @@ impl RawInstance {
11241174
panic!("read_memory: crosscheck mismatch, range = 0x{address:x}..0x{address_end:x}, interpreter = {expected_success}, backend = {success}");
11251175
}
11261176
}
1177+
1178+
#[cfg(debug_assertions)]
1179+
{
1180+
let is_accessible = self.is_memory_accessible(address, cast(length).assert_always_fits_in_u32(), false);
1181+
if is_accessible != result.is_ok() {
1182+
panic!(
1183+
"'read_memory_into' doesn't match with 'is_memory_accessible' for 0x{:x}-0x{:x} (read_memory_into = {}, is_memory_accessible = {})",
1184+
address,
1185+
cast(address).to_usize() + length,
1186+
result.is_ok(),
1187+
is_accessible,
1188+
);
1189+
}
1190+
}
1191+
11271192
result
11281193
}
11291194

@@ -1161,6 +1226,20 @@ impl RawInstance {
11611226
}
11621227
}
11631228

1229+
#[cfg(debug_assertions)]
1230+
{
1231+
let is_accessible = self.is_memory_accessible(address, cast(data.len()).assert_always_fits_in_u32(), true);
1232+
if is_accessible != result.is_ok() {
1233+
panic!(
1234+
"'write_memory' doesn't match with 'is_memory_accessible' for 0x{:x}-0x{:x} (write_memory = {}, is_memory_accessible = {})",
1235+
address,
1236+
cast(address).to_usize() + data.len(),
1237+
result.is_ok(),
1238+
is_accessible,
1239+
);
1240+
}
1241+
}
1242+
11641243
result
11651244
}
11661245

crates/polkavm/src/interpreter.rs

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ impl BasicMemory {
131131
}
132132
}
133133

134+
fn accessible_aux_size(&self) -> u32 {
135+
cast(self.accessible_aux_size).assert_always_fits_in_u32()
136+
}
137+
134138
fn set_accessible_aux_size(&mut self, size: u32) {
135139
self.accessible_aux_size = cast(size).to_usize();
136140
}
@@ -253,38 +257,43 @@ macro_rules! emit_branch {
253257
};
254258
}
255259

256-
fn each_page(module: &Module, address: u32, length: u32, callback: impl FnMut(u32, usize, usize, usize)) {
260+
fn each_page<E>(
261+
module: &Module,
262+
address: u32,
263+
length: u32,
264+
callback: impl FnMut(u32, usize, usize, usize) -> Result<(), E>,
265+
) -> Result<(), E> {
257266
let page_size = module.memory_map().page_size();
258267
let page_address_lo = module.round_to_page_size_down(address);
259268
let page_address_hi = module.round_to_page_size_down(address + (length - 1));
260269
each_page_impl(page_size, page_address_lo, page_address_hi, address, length, callback)
261270
}
262271

263-
fn each_page_impl(
272+
fn each_page_impl<E>(
264273
page_size: u32,
265274
page_address_lo: u32,
266275
page_address_hi: u32,
267276
address: u32,
268277
length: u32,
269-
mut callback: impl FnMut(u32, usize, usize, usize),
270-
) {
278+
mut callback: impl FnMut(u32, usize, usize, usize) -> Result<(), E>,
279+
) -> Result<(), E> {
271280
let page_size = cast(page_size).to_usize();
272281
let length = cast(length).to_usize();
273282

274283
let initial_page_offset = cast(address).to_usize() - cast(page_address_lo).to_usize();
275284
let initial_chunk_length = core::cmp::min(length, page_size - initial_page_offset);
276-
callback(page_address_lo, initial_page_offset, 0, initial_chunk_length);
285+
callback(page_address_lo, initial_page_offset, 0, initial_chunk_length)?;
277286

278287
if page_address_lo == page_address_hi {
279-
return;
288+
return Ok(());
280289
}
281290

282291
let mut page_address_lo = cast(page_address_lo).to_u64();
283292
let page_address_hi = cast(page_address_hi).to_u64();
284293
page_address_lo += cast(page_size).to_u64();
285294
let mut buffer_offset = initial_chunk_length;
286295
while page_address_lo < page_address_hi {
287-
callback(cast(page_address_lo).assert_always_fits_in_u32(), 0, buffer_offset, page_size);
296+
callback(cast(page_address_lo).assert_always_fits_in_u32(), 0, buffer_offset, page_size)?;
288297
buffer_offset += page_size;
289298
page_address_lo += cast(page_size).to_u64();
290299
}
@@ -304,16 +313,18 @@ fn test_each_page() {
304313
let page_address_lo = address / page_size * page_size;
305314
let page_address_hi = (address + (length - 1)) / page_size * page_size;
306315
let mut output = Vec::new();
307-
each_page_impl(
316+
each_page_impl::<()>(
308317
page_size,
309318
page_address_lo,
310319
page_address_hi,
311320
address,
312321
length,
313322
|page_address, page_offset, buffer_offset, length| {
314323
output.push((page_address, page_offset, buffer_offset, length));
324+
Ok(())
315325
},
316-
);
326+
)
327+
.unwrap();
317328
output
318329
}
319330

@@ -448,6 +459,11 @@ impl InterpretedInstance {
448459
self.next_program_counter_changed = true;
449460
}
450461

462+
pub fn accessible_aux_size(&self) -> u32 {
463+
assert!(!self.module.is_dynamic_paging());
464+
self.basic_memory.accessible_aux_size()
465+
}
466+
451467
pub fn set_accessible_aux_size(&mut self, size: u32) {
452468
assert!(!self.module.is_dynamic_paging());
453469
self.basic_memory.set_accessible_aux_size(size);
@@ -458,6 +474,21 @@ impl InterpretedInstance {
458474
None
459475
}
460476

477+
pub fn is_memory_accessible(&self, address: u32, size: u32, _is_writable: bool) -> bool {
478+
assert!(self.module.is_dynamic_paging());
479+
480+
// TODO: This is very slow.
481+
let result = each_page(&self.module, address, size, |page_address, _, _, _| {
482+
if !self.dynamic_memory.pages.contains_key(&page_address) {
483+
Err(())
484+
} else {
485+
Ok(())
486+
}
487+
});
488+
489+
result.is_ok()
490+
}
491+
461492
pub fn read_memory_into<'slice>(
462493
&self,
463494
address: u32,
@@ -491,12 +522,16 @@ impl InterpretedInstance {
491522
if let Some(page) = page {
492523
let src = page.as_ptr().add(page_offset);
493524
core::ptr::copy_nonoverlapping(src, dst, length);
525+
Ok(())
494526
} else {
495-
core::ptr::write_bytes(dst, 0, length);
527+
Err(MemoryAccessError::OutOfRangeAccess {
528+
address: page_address + cast(page_offset).assert_always_fits_in_u32(),
529+
length: cast(length).to_u64(),
530+
})
496531
}
497532
}
498533
},
499-
);
534+
)?;
500535

501536
// SAFETY: The buffer was initialized.
502537
Ok(unsafe { slice_assume_init_mut(buffer) })
@@ -519,15 +554,17 @@ impl InterpretedInstance {
519554
} else {
520555
let dynamic_memory = &mut self.dynamic_memory;
521556
let page_size = self.module.memory_map().page_size();
522-
each_page(
557+
each_page::<()>(
523558
&self.module,
524559
address,
525560
cast(data.len()).assert_always_fits_in_u32(),
526561
move |page_address, page_offset, buffer_offset, length| {
527562
let page = dynamic_memory.pages.entry(page_address).or_insert_with(|| empty_page(page_size));
528563
page[page_offset..page_offset + length].copy_from_slice(&data[buffer_offset..buffer_offset + length]);
564+
Ok(())
529565
},
530-
);
566+
)
567+
.unwrap();
531568
}
532569

533570
Ok(())
@@ -546,20 +583,23 @@ impl InterpretedInstance {
546583
} else {
547584
let dynamic_memory = &mut self.dynamic_memory;
548585
let page_size = self.module.memory_map().page_size();
549-
each_page(
586+
each_page::<()>(
550587
&self.module,
551588
address,
552589
length,
553590
move |page_address, page_offset, _, length| match dynamic_memory.pages.entry(page_address) {
554591
Entry::Occupied(mut entry) => {
555592
let page = entry.get_mut();
556593
page[page_offset..page_offset + length].fill(0);
594+
Ok(())
557595
}
558596
Entry::Vacant(entry) => {
559597
entry.insert(empty_page(page_size));
598+
Ok(())
560599
}
561600
},
562-
);
601+
)
602+
.unwrap();
563603
}
564604

565605
Ok(())
@@ -573,9 +613,11 @@ impl InterpretedInstance {
573613
todo!()
574614
} else {
575615
let dynamic_memory = &mut self.dynamic_memory;
576-
each_page(&self.module, address, length, move |page_address, _, _, _| {
616+
each_page::<()>(&self.module, address, length, move |page_address, _, _, _| {
577617
dynamic_memory.pages.remove(&page_address);
578-
});
618+
Ok(())
619+
})
620+
.unwrap();
579621
}
580622
}
581623

crates/polkavm/src/sandbox.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ pub(crate) trait Sandbox: Sized {
122122
fn next_program_counter(&self) -> Option<ProgramCounter>;
123123
fn next_native_program_counter(&self) -> Option<usize>;
124124
fn set_next_program_counter(&mut self, pc: ProgramCounter);
125+
fn accessible_aux_size(&self) -> u32;
125126
fn set_accessible_aux_size(&mut self, size: u32) -> Result<(), Self::Error>;
127+
fn is_memory_accessible(&self, address: u32, size: u32, is_writable: bool) -> bool;
126128
fn reset_memory(&mut self) -> Result<(), Self::Error>;
127129
fn read_memory_into<'slice>(&self, address: u32, slice: &'slice mut [MaybeUninit<u8>]) -> Result<&'slice mut [u8], MemoryAccessError>;
128130
fn write_memory(&mut self, address: u32, data: &[u8]) -> Result<(), MemoryAccessError>;

crates/polkavm/src/sandbox/linux.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,11 @@ impl super::Sandbox for Sandbox {
19101910
}
19111911
}
19121912

1913+
fn accessible_aux_size(&self) -> u32 {
1914+
assert!(!self.dynamic_paging_enabled);
1915+
self.aux_data_length
1916+
}
1917+
19131918
fn set_accessible_aux_size(&mut self, size: u32) -> Result<(), Error> {
19141919
assert!(!self.dynamic_paging_enabled);
19151920

@@ -1924,6 +1929,15 @@ impl super::Sandbox for Sandbox {
19241929
self.wake_oneshot_and_expect_idle()
19251930
}
19261931

1932+
fn is_memory_accessible(&self, address: u32, size: u32, _is_writable: bool) -> bool {
1933+
assert!(self.dynamic_paging_enabled);
1934+
1935+
let module = self.module.as_ref().unwrap();
1936+
let page_start = module.address_to_page(module.round_to_page_size_down(address));
1937+
let page_end = module.address_to_page(module.round_to_page_size_down(address + size));
1938+
self.page_set.contains((page_start, page_end))
1939+
}
1940+
19271941
fn reset_memory(&mut self) -> Result<(), Error> {
19281942
if self.module.is_none() {
19291943
return Err(Error::from_str("no module loaded into the sandbox"));
@@ -1957,9 +1971,7 @@ impl super::Sandbox for Sandbox {
19571971
let page_start = module.address_to_page(module.round_to_page_size_down(address));
19581972
let page_end = module.address_to_page(module.round_to_page_size_down(address + slice.len() as u32));
19591973
if !self.page_set.contains((page_start, page_end)) {
1960-
unsafe {
1961-
core::ptr::write_bytes(slice.as_mut_ptr().cast::<u8>(), 0, slice.len());
1962-
}
1974+
return Err(MemoryAccessError::Error("incomplete read".into()));
19631975
} else {
19641976
let memory: &[core::mem::MaybeUninit<u8>] =
19651977
unsafe { core::slice::from_raw_parts(self.memory_mmap.as_ptr().cast(), self.memory_mmap.len()) };

crates/polkavm/src/tests.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ fn dynamic_paging_reading_does_not_resolve_segfaults(mut engine_config: Config)
927927
instance.set_next_program_counter(offsets[0]);
928928
let segfault = expect_segfault(instance.run().unwrap());
929929
assert_eq!(segfault.page_address, 0x10000);
930-
assert_eq!(instance.read_u32(0x10000).unwrap(), 0x00000000);
930+
assert!(instance.read_u32(0x10000).is_err());
931931

932932
let segfault = expect_segfault(instance.run().unwrap());
933933
assert_eq!(segfault.page_address, 0x10000);
@@ -1175,7 +1175,11 @@ fn dynamic_paging_worker_recycle_turn_dynamic_paging_on_and_off(mut engine_confi
11751175
module_static.instantiate().unwrap()
11761176
};
11771177

1178-
assert_eq!(instance.read_u32(0x20000).unwrap(), 0);
1178+
if !is_dynamic {
1179+
assert_eq!(instance.read_u32(0x20000).unwrap(), 0);
1180+
} else {
1181+
assert!(instance.read_u32(0x20000).is_err());
1182+
}
11791183

11801184
instance.set_reg(Reg::RA, crate::RETURN_TO_HOST);
11811185
instance.set_next_program_counter(ProgramCounter(0));

0 commit comments

Comments
 (0)