Check safety of as_raw with a simplified borrow checker (#37)

* wiggle-runtime: add as_raw method for [T]

* add trivial borrow checker back in

* integrate runtime borrow checker with as_raw methods

* handle pointer arith overflow correctly in as_raw, create PtrOverflow error

* runtime: add validation back to GuestType

* generate: impl validate for enums, flags, handles, ints

* oops! make validate its own method on trait GuestTypeTransparent

* fix transparent impls for enum, flag, handle, int

* some structs are transparent. fix tests.

* tests: define byte_slice_strat and friends

* wiggle-tests: i believe my allocator is working now

* some type juggling around memset for ease of use

* make GuestTypeTransparent an unsafe trait

* delete redundant validation of pointer align

* fix doc

* wiggle_test: aha, you cant use sets to track memory areas

* add multi-string test

which exercises the runtime borrow checker against
HostMemory::byte_slice_strat

* oops left debug panic in

* remove redundant (& incorrect, since unchecked) length calc

* redesign validate again, and actually hook to as_raw

* makr all validate impls as inline

this should hopefully allow as_raw's check loop to be unrolled to a
no-op in most cases!

* code review fixes
This commit is contained in:
Pat Hickey
2020-03-06 16:04:56 -08:00
committed by GitHub
parent 7669dee902
commit c78416912c
18 changed files with 655 additions and 50 deletions

View File

@@ -2,16 +2,34 @@ use proc_macro2::TokenStream;
use quote::quote;
pub trait LifetimeExt {
fn is_transparent(&self) -> bool;
fn needs_lifetime(&self) -> bool;
}
impl LifetimeExt for witx::TypeRef {
fn is_transparent(&self) -> bool {
self.type_().is_transparent()
}
fn needs_lifetime(&self) -> bool {
self.type_().needs_lifetime()
}
}
impl LifetimeExt for witx::Type {
fn is_transparent(&self) -> bool {
match self {
witx::Type::Builtin(b) => b.is_transparent(),
witx::Type::Struct(s) => s.is_transparent(),
witx::Type::Enum { .. }
| witx::Type::Flags { .. }
| witx::Type::Int { .. }
| witx::Type::Handle { .. } => true,
witx::Type::Union { .. }
| witx::Type::Pointer { .. }
| witx::Type::ConstPointer { .. }
| witx::Type::Array { .. } => false,
}
}
fn needs_lifetime(&self) -> bool {
match self {
witx::Type::Builtin(b) => b.needs_lifetime(),
@@ -29,6 +47,9 @@ impl LifetimeExt for witx::Type {
}
impl LifetimeExt for witx::BuiltinType {
fn is_transparent(&self) -> bool {
!self.needs_lifetime()
}
fn needs_lifetime(&self) -> bool {
match self {
witx::BuiltinType::String => true,
@@ -38,12 +59,18 @@ impl LifetimeExt for witx::BuiltinType {
}
impl LifetimeExt for witx::StructDatatype {
fn is_transparent(&self) -> bool {
self.members.iter().all(|m| m.tref.is_transparent())
}
fn needs_lifetime(&self) -> bool {
self.members.iter().any(|m| m.tref.needs_lifetime())
}
}
impl LifetimeExt for witx::UnionDatatype {
fn is_transparent(&self) -> bool {
false
}
fn needs_lifetime(&self) -> bool {
self.variants
.iter()

View File

@@ -87,8 +87,9 @@ pub(super) fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype
fn read(location: &wiggle_runtime::GuestPtr<#ident>) -> Result<#ident, wiggle_runtime::GuestError> {
use std::convert::TryFrom;
let val = #repr::read(&location.cast())?;
#ident::try_from(val)
let reprval = #repr::read(&location.cast())?;
let value = #ident::try_from(reprval)?;
Ok(value)
}
fn write(location: &wiggle_runtime::GuestPtr<'_, #ident>, val: Self)
@@ -97,5 +98,16 @@ pub(super) fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype
#repr::write(&location.cast(), #repr::from(val))
}
}
unsafe impl <'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
#[inline]
fn validate(location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
use std::convert::TryFrom;
// Validate value in memory using #ident::try_from(reprval)
let reprval = unsafe { (location as *mut #repr).read() };
let _val = #ident::try_from(reprval)?;
Ok(())
}
}
}
}

View File

@@ -134,10 +134,11 @@ pub(super) fn define_flags(names: &Names, name: &witx::Id, f: &witx::FlagsDataty
#repr::guest_align()
}
fn read(location: &wiggle_runtime::GuestPtr<'a, #ident>) -> Result<#ident, wiggle_runtime::GuestError> {
fn read(location: &wiggle_runtime::GuestPtr<#ident>) -> Result<#ident, wiggle_runtime::GuestError> {
use std::convert::TryFrom;
let bits = #repr::read(&location.cast())?;
#ident::try_from(bits)
let reprval = #repr::read(&location.cast())?;
let value = #ident::try_from(reprval)?;
Ok(value)
}
fn write(location: &wiggle_runtime::GuestPtr<'_, #ident>, val: Self) -> Result<(), wiggle_runtime::GuestError> {
@@ -145,5 +146,16 @@ pub(super) fn define_flags(names: &Names, name: &witx::Id, f: &witx::FlagsDataty
#repr::write(&location.cast(), val)
}
}
unsafe impl <'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
#[inline]
fn validate(location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
use std::convert::TryFrom;
// Validate value in memory using #ident::try_from(reprval)
let reprval = unsafe { (location as *mut #repr).read() };
let _val = #ident::try_from(reprval)?;
Ok(())
}
}
}
}

View File

@@ -13,6 +13,7 @@ pub(super) fn define_handle(
let size = h.mem_size_align().size as u32;
let align = h.mem_size_align().align as usize;
quote! {
#[repr(transparent)]
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
pub struct #ident(u32);
@@ -62,5 +63,15 @@ pub(super) fn define_handle(
u32::write(&location.cast(), val.0)
}
}
unsafe impl<'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
#[inline]
fn validate(_location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
// All bit patterns accepted
Ok(())
}
}
}
}

View File

@@ -73,11 +73,21 @@ pub(super) fn define_int(names: &Names, name: &witx::Id, i: &witx::IntDatatype)
fn read(location: &wiggle_runtime::GuestPtr<'a, #ident>) -> Result<#ident, wiggle_runtime::GuestError> {
Ok(#ident(#repr::read(&location.cast())?))
}
fn write(location: &wiggle_runtime::GuestPtr<'_, #ident>, val: Self) -> Result<(), wiggle_runtime::GuestError> {
#repr::write(&location.cast(), val.0)
}
}
unsafe impl<'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
#[inline]
fn validate(_location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
// All bit patterns accepted
Ok(())
}
}
}
}

View File

@@ -77,6 +77,32 @@ pub(super) fn define_struct(
(quote!(), quote!(, Copy, PartialEq))
};
let transparent = if s.is_transparent() {
let member_validate = s.member_layout().into_iter().map(|ml| {
let offset = ml.offset;
let typename = names.type_ref(&ml.member.tref, anon_lifetime());
quote! {
// SAFETY: caller has validated bounds and alignment of `location`.
// member_layout gives correctly-aligned pointers inside that area.
#typename::validate(
unsafe { (location as *mut u8).add(#offset) as *mut _ }
)?;
}
});
quote! {
unsafe impl<'a> wiggle_runtime::GuestTypeTransparent<'a> for #ident {
#[inline]
fn validate(location: *mut #ident) -> Result<(), wiggle_runtime::GuestError> {
#(#member_validate)*
Ok(())
}
}
}
} else {
quote!()
};
quote! {
#[derive(Clone, Debug #extra_derive)]
pub struct #ident #struct_lifetime {
@@ -102,5 +128,7 @@ pub(super) fn define_struct(
Ok(())
}
}
#transparent
}
}