1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
/// Allocate bytes that won't be dropped by the allocator.
/// Return the pointer to the leaked allocation so the host can write to it.
#[no_mangle]
#[inline(always)]
pub extern "C" fn __hc__allocate_1(len: usize) -> usize {
write_bytes(Vec::with_capacity(len))
}
/// Free an allocation.
/// Needed because we leak memory every time we call `__hc__allocate_1` and
/// `write_bytes`.
#[no_mangle]
#[inline(always)]
pub extern "C" fn __hc__deallocate_1(guest_ptr: usize, len: usize) {
let _ = consume_bytes(guest_ptr, len);
}
/// Attempt to consume bytes from a known `guest_ptr` and `len`.
///
/// Consume in this context means take ownership of previously forgotten data.
///
/// This needs to work for bytes written into the guest from the host and for bytes written with
/// the `write_bytes` function within the guest.
#[inline(always)]
pub fn consume_bytes(guest_ptr: usize, len: usize) -> Vec<u8> {
// This must be a Vec and not only a slice, because slices will fail to
// deallocate memory properly when dropped.
// Assumes length and capacity are the same, which is true if `__hc__allocate_1` is
// used to allocate memory for the vector.
unsafe { std::vec::Vec::from_raw_parts(guest_ptr as *mut u8, len, len) }
}
/// Given an owned vector of bytes, leaks it and returns a pointer the host can
/// use to read the bytes. This does NOT handle the length of the bytes, so the
/// guest will need to track the length separately from leaking the vector.
///
/// This facilitates the guest handing a `GuestPtr` back to the host as the return value of guest
/// functions so that the host can read the output of guest logic from a pointer.
///
/// The host MUST ensure either `__hc__deallocate_1` is called or the entire wasm memory is dropped.
/// If the host fails to tell the guest where and how many bytes to deallocate, then this leak
/// becomes permanent to the guest.
#[inline(always)]
pub fn write_bytes(v: Vec<u8>) -> usize {
// This *const u8 cast to u32 is safe and the only way to get a raw pointer as a u32 afaik.
// > e has type *T and U is a numeric type, while T: Sized; ptr-addr-cast
// https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/casting-between-types.html#pointer-casts
v.leak().as_ptr() as usize
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn round_trip_5_5() {
let bytes_round = consume_bytes(write_bytes(vec![1, 2, 3, 4, 5]), 5);
dbg!(&bytes_round);
}
fn _round_trip_allocation(bytes: Vec<u8>) {
let bytes_round = consume_bytes(write_bytes(bytes.clone()), bytes.len());
assert_eq!(bytes, bytes_round);
}
// https://github.com/trailofbits/test-fuzz/issues/171
#[cfg(not(target_os = "windows"))]
#[test_fuzz::test_fuzz]
fn round_trip_allocation(bytes: Vec<u8>) {
_round_trip_allocation(bytes);
}
#[test]
fn some_round_trip_allocation() {
_round_trip_allocation(vec![1, 2, 3]);
}
fn _alloc_dealloc(len: usize) {
__hc__deallocate_1(__hc__allocate_1(len), len);
}
// https://github.com/trailofbits/test-fuzz/issues/171
#[cfg(not(target_os = "windows"))]
#[test_fuzz::test_fuzz]
fn alloc_dealloc(len: usize) {
_alloc_dealloc(len);
}
#[test]
fn some_alloc_dealloc() {
_alloc_dealloc(1_000_000_000_usize);
}
}