GENERAL BREAK: get rid of signed pointers
This commit is contained in:
parent
18a7c524e6
commit
e101c9fd9f
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -93,7 +93,6 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"int-enum",
|
"int-enum",
|
||||||
"proptest",
|
"proptest",
|
||||||
"siphasher",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -328,12 +327,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "siphasher"
|
|
||||||
version = "0.3.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
|
|
@ -11,10 +11,6 @@ license = "LGPL-2.1+"
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.siphasher]
|
|
||||||
version = "0.3"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[dev-dependencies.proptest]
|
[dev-dependencies.proptest]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub enum Instr {
|
||||||
|
|
||||||
// basic stack operations
|
// basic stack operations
|
||||||
/// pushes the given value to the stack
|
/// pushes the given value to the stack
|
||||||
Push(u128),
|
Push(u64),
|
||||||
|
|
||||||
/// pops the top $0+1 values from the stack
|
/// pops the top $0+1 values from the stack
|
||||||
Pop(u16),
|
Pop(u16),
|
||||||
|
@ -105,7 +105,7 @@ impl<'a> crate::Parse<'a> for Instr {
|
||||||
OpType::CallLocal => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLocal(val))),
|
OpType::CallLocal => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLocal(val))),
|
||||||
OpType::CallLDefer => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLDefer(val))),
|
OpType::CallLDefer => u64::parse(inp).map(|(inp, val)| (inp, Instr::CallLDefer(val))),
|
||||||
OpType::JumpCond => u64::parse(inp).map(|(inp, val)| (inp, Instr::JumpCond(val))),
|
OpType::JumpCond => u64::parse(inp).map(|(inp, val)| (inp, Instr::JumpCond(val))),
|
||||||
OpType::Push => u128::parse(inp).map(|(inp, val)| (inp, Instr::Push(val))),
|
OpType::Push => u64::parse(inp).map(|(inp, val)| (inp, Instr::Push(val))),
|
||||||
OpType::Pop => u16::parse(inp).map(|(inp, val)| (inp, Instr::Pop(val))),
|
OpType::Pop => u16::parse(inp).map(|(inp, val)| (inp, Instr::Pop(val))),
|
||||||
OpType::Dup => u16::parse(inp).map(|(inp, val)| (inp, Instr::Dup(val))),
|
OpType::Dup => u16::parse(inp).map(|(inp, val)| (inp, Instr::Dup(val))),
|
||||||
OpType::Swap => u16::parse(inp).map(|(inp, val)| (inp, Instr::Swap(val))),
|
OpType::Swap => u16::parse(inp).map(|(inp, val)| (inp, Instr::Swap(val))),
|
||||||
|
|
|
@ -8,5 +8,3 @@ mod instr;
|
||||||
pub use instr::{Instr, ParseError as InstrParseError};
|
pub use instr::{Instr, ParseError as InstrParseError};
|
||||||
mod parse;
|
mod parse;
|
||||||
pub use parse::Parse;
|
pub use parse::Parse;
|
||||||
mod pointer;
|
|
||||||
pub use pointer::Pointer;
|
|
||||||
|
|
|
@ -1,150 +0,0 @@
|
||||||
use siphasher::sip::SipHasher24 as SipHasher;
|
|
||||||
|
|
||||||
/// A signed pointer, the `payload` should never by dereferenced
|
|
||||||
/// without verifying the pointer first
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[repr(C, align(16))]
|
|
||||||
pub struct Pointer(pub [u8; 16]);
|
|
||||||
|
|
||||||
impl From<u128> for Pointer {
|
|
||||||
#[inline]
|
|
||||||
fn from(x: u128) -> Self {
|
|
||||||
Self(x.to_be_bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(u64, u64)> for Pointer {
|
|
||||||
#[inline]
|
|
||||||
fn from((a, b): (u64, u64)) -> Self {
|
|
||||||
let mut this = Self([0; 16]);
|
|
||||||
{
|
|
||||||
let (a_, b_) = this.0.split_at_mut(8);
|
|
||||||
a_.copy_from_slice(&a.to_be_bytes());
|
|
||||||
b_.copy_from_slice(&b.to_be_bytes());
|
|
||||||
}
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Pointer> for u128 {
|
|
||||||
#[inline]
|
|
||||||
fn from(Pointer(x): Pointer) -> u128 {
|
|
||||||
u128::from_be_bytes(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Pointer> for (u64, u64) {
|
|
||||||
#[inline]
|
|
||||||
fn from(Pointer(x): Pointer) -> (u64, u64) {
|
|
||||||
let (a, b) = x.split_at(8);
|
|
||||||
(
|
|
||||||
u64::from_be_bytes(a.try_into().unwrap()),
|
|
||||||
u64::from_be_bytes(b.try_into().unwrap()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pointer {
|
|
||||||
fn calculate_hmac(payload: u64, key: &u128) -> u64 {
|
|
||||||
use core::hash::Hasher;
|
|
||||||
let mut h = SipHasher::new_with_key(&key.to_be_bytes());
|
|
||||||
h.write_u64(payload & ((1 << 48) - 1));
|
|
||||||
h.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SECURITY NOTE: the upper two bytes of `payload` (`origin`) aren't taken
|
|
||||||
/// into account when calculating the HMAC because they're node-specific
|
|
||||||
pub fn new_with_key(payload: u64, key: &u128) -> Pointer {
|
|
||||||
let hmac = Self::calculate_hmac(payload, key);
|
|
||||||
Self::from((hmac, payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn verify(&self, key: &u128) -> Option<u64> {
|
|
||||||
let (hmac, payload) = (*self).into();
|
|
||||||
if hmac == Self::calculate_hmac(payload, key) {
|
|
||||||
Some(payload)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_origin(&mut self, origin: u16) {
|
|
||||||
self.0[8..10].copy_from_slice(&origin.to_be_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn origin(&self) -> u16 {
|
|
||||||
u16::from_be_bytes(self.0[8..10].try_into().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::Parse<'_> for Pointer {
|
|
||||||
type Err = ();
|
|
||||||
fn parse(inp: &[u8]) -> Result<(&[u8], Self), ()> {
|
|
||||||
if inp.len() < 16 {
|
|
||||||
Err(())
|
|
||||||
} else {
|
|
||||||
let (this, next) = inp.split_at(16);
|
|
||||||
Ok((next, Self(this.try_into().unwrap())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(any(test, feature = "std"))]
|
|
||||||
impl Pointer {
|
|
||||||
#[inline]
|
|
||||||
pub fn write_to<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
|
|
||||||
writer.write_all(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
proptest::proptest! {
|
|
||||||
#[test]
|
|
||||||
fn u128_roundtrip(x in 0..u128::MAX) {
|
|
||||||
let ptr = Pointer::from(x);
|
|
||||||
assert_eq!(u128::from(ptr), x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pointer_repr() {
|
|
||||||
use core::mem;
|
|
||||||
assert_eq!(mem::size_of::<Pointer>(), 16);
|
|
||||||
assert_eq!(mem::align_of::<Pointer>(), 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pointer_usage() {
|
|
||||||
let k: u128 = 0x000000000000dead000000000000beef;
|
|
||||||
let p = Pointer::new_with_key(0xfefe, &k);
|
|
||||||
// verify that this is the same value on all systems
|
|
||||||
assert_eq!(p.0[0..8], [42, 115, 131, 215, 127, 235, 147, 241]);
|
|
||||||
assert_eq!(p.verify(&k), Some(0xfefe));
|
|
||||||
assert_eq!(p.origin(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pointer_usage2() {
|
|
||||||
let k: u128 = 0x000000000000dead000000000000beef;
|
|
||||||
let p = Pointer::new_with_key(0x0508deadbeeffefe, &k);
|
|
||||||
// verify that this is the same value on all systems
|
|
||||||
assert_eq!(p.0[0..8], [191, 23, 107, 0, 61, 74, 249, 219]);
|
|
||||||
assert_eq!(p.verify(&k), Some(0x0508deadbeeffefe));
|
|
||||||
assert_eq!(p.origin(), 0x0508);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn pointer_usage3() {
|
|
||||||
let k: u128 = 0x000000000000dead000000000000beef;
|
|
||||||
let p = Pointer::new_with_key(0xf7d8deadbeeffefe, &k);
|
|
||||||
// verify that this is the same value on all systems
|
|
||||||
assert_eq!(p.0[0..8], [191, 23, 107, 0, 61, 74, 249, 219]);
|
|
||||||
assert_eq!(p.verify(&k), Some(0xf7d8deadbeeffefe));
|
|
||||||
assert_eq!(p.origin(), 0xf7d8);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ extern crate alloc;
|
||||||
|
|
||||||
use alloc::{sync::Arc, vec::Vec};
|
use alloc::{sync::Arc, vec::Vec};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use fogtix_bytecode::{consts, Instr, Parse, Pointer};
|
use fogtix_bytecode::{consts, Instr, Parse};
|
||||||
|
|
||||||
pub type Module = Arc<dyn ModuleKind>;
|
pub type Module = Arc<dyn ModuleKind>;
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ impl InstrPtr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StackEntValue = u128;
|
pub type StackEntValue = u64;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -75,7 +75,7 @@ pub enum Error {
|
||||||
OutOfFuel,
|
OutOfFuel,
|
||||||
NotEnoughStacked,
|
NotEnoughStacked,
|
||||||
DivisionByZero,
|
DivisionByZero,
|
||||||
RemoteCall(Pointer),
|
RemoteCall(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
|
@ -97,7 +97,7 @@ impl fmt::Display for Error {
|
||||||
Self::OutOfFuel => write!(f, "out of fuel"),
|
Self::OutOfFuel => write!(f, "out of fuel"),
|
||||||
Self::NotEnoughStacked => write!(f, "not enough operands on stack"),
|
Self::NotEnoughStacked => write!(f, "not enough operands on stack"),
|
||||||
Self::DivisionByZero => write!(f, "tried to divide by zero"),
|
Self::DivisionByZero => write!(f, "tried to divide by zero"),
|
||||||
Self::RemoteCall(ptr) => write!(f, "tried to call remote @ {:x?}", ptr.0),
|
Self::RemoteCall(ptr) => write!(f, "tried to call remote @ {:x?}", ptr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,7 @@ impl Process {
|
||||||
if !self.instrp.is_call2jump() {
|
if !self.instrp.is_call2jump() {
|
||||||
self.callstack.push(self.instrp.clone());
|
self.callstack.push(self.instrp.clone());
|
||||||
}
|
}
|
||||||
return Err(Error::RemoteCall(Pointer::from(self.stpop()?)));
|
return Err(Error::RemoteCall(self.stpop()?));
|
||||||
}
|
}
|
||||||
Instr::CallLocal(x) => {
|
Instr::CallLocal(x) => {
|
||||||
if !self.instrp.is_call2jump() {
|
if !self.instrp.is_call2jump() {
|
||||||
|
@ -282,8 +282,8 @@ impl Process {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Err(Error::DivisionByZero),
|
None => return Err(Error::DivisionByZero),
|
||||||
},
|
},
|
||||||
B::Srem => match (a as i128).checked_rem(b as i128) {
|
B::Srem => match (a as i64).checked_rem(b as i64) {
|
||||||
Some(x) => x as u128,
|
Some(x) => x as u64,
|
||||||
None => return Err(Error::DivisionByZero),
|
None => return Err(Error::DivisionByZero),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -299,7 +299,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stack_item_size() {
|
fn stack_item_size() {
|
||||||
assert_eq!(core::mem::size_of::<StackEntValue>(), 16);
|
assert_eq!(core::mem::size_of::<StackEntValue>(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
proptest::proptest! {
|
proptest::proptest! {
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
# basic ideas
|
# basic ideas
|
||||||
|
|
||||||
* use tagged, signed pointers for memory addresses
|
|
||||||
* use a separate call stack to prevent ROP
|
* use a separate call stack to prevent ROP
|
||||||
* implement message passing
|
* implement message passing
|
||||||
|
* integers as only internal data type
|
||||||
# values
|
|
||||||
|
|
||||||
* atoms (u128): atomic values, also used as pointer keys to sign pointers
|
|
||||||
* bytes ([]u8): arbitrary byte strings
|
|
||||||
* int (u64): the default integer type
|
|
||||||
* pointer (u128): signed pointer (can only be accessed with matching key)
|
|
||||||
|
|
||||||
# entry points and libraries
|
# entry points and libraries
|
||||||
|
|
||||||
|
@ -17,3 +10,18 @@ each module starts with the entry point, this avoids the need for a separate "en
|
||||||
the entry point of the main module is what's initially called by the VM to start execution.
|
the entry point of the main module is what's initially called by the VM to start execution.
|
||||||
the entry point of library modules are called to build the library descriptor table,
|
the entry point of library modules are called to build the library descriptor table,
|
||||||
which should leave an additional pointer on the stack which can be passed to `call-r`.
|
which should leave an additional pointer on the stack which can be passed to `call-r`.
|
||||||
|
|
||||||
|
# historic
|
||||||
|
|
||||||
|
## signed pointers
|
||||||
|
|
||||||
|
Initially, this project tried to implement an idea of signed pointers, so that a
|
||||||
|
party can verify that it authored a pointer (via HMAC, e.g. SipHash); but some
|
||||||
|
problems remain, which make this impractical
|
||||||
|
|
||||||
|
* either it is insecure because the pointer is too small to store a resistant-enough hash
|
||||||
|
* thus, the pointer is so large that it gets unwieldly (e.g. `u128`)
|
||||||
|
* how to find out which node or process owns a pointer if you aren't that node
|
||||||
|
(the hash only indicates if you own it or not, not who owns it)
|
||||||
|
* how to find out if the pointer is still valid (you need some hashmap or so to store that),
|
||||||
|
but then you have basically reinvented file descriptors.
|
||||||
|
|
Loading…
Reference in a new issue