diff --git a/sdk/program/src/account_info.rs b/sdk/program/src/account_info.rs index 3652e2251efebd..1ba9ae3b264297 100644 --- a/sdk/program/src/account_info.rs +++ b/sdk/program/src/account_info.rs @@ -15,23 +15,24 @@ use { /// Account information #[derive(Clone)] +#[repr(C)] pub struct AccountInfo<'a> { /// Public key of the account pub key: &'a Pubkey, - /// Was the transaction signed by this account's public key? - pub is_signer: bool, - /// Is the account writable? - pub is_writable: bool, /// The lamports in the account. Modifiable by programs. pub lamports: Rc>, /// The data held in this account. Modifiable by programs. pub data: Rc>, /// Program that owns this account pub owner: &'a Pubkey, - /// This account's data contains a loaded program (and is now read-only) - pub executable: bool, /// The epoch at which this account will next owe rent pub rent_epoch: Epoch, + /// Was the transaction signed by this account's public key? + pub is_signer: bool, + /// Is the account writable? + pub is_writable: bool, + /// This account's data contains a loaded program (and is now read-only) + pub executable: bool, } impl<'a> fmt::Debug for AccountInfo<'a> { diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index e224f2abd4248e..2d3e2ce1a0a324 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -602,6 +602,7 @@ pub mod serialize_utils; pub mod short_vec; pub mod slot_hashes; pub mod slot_history; +pub mod stable_layout; pub mod stake; pub mod stake_history; pub mod syscalls; diff --git a/sdk/program/src/program.rs b/sdk/program/src/program.rs index d9de4188342025..424a388b5220a3 100644 --- a/sdk/program/src/program.rs +++ b/sdk/program/src/program.rs @@ -10,6 +10,7 @@ use crate::{ account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey, + stable_layout::stable_instruction::StableInstruction, }; /// Invoke a cross-program instruction. @@ -290,9 +291,10 @@ pub fn invoke_signed_unchecked( ) -> ProgramResult { #[cfg(target_os = "solana")] { + let instruction = StableInstruction::from(instruction.clone()); let result = unsafe { crate::syscalls::sol_invoke_signed_rust( - instruction as *const _ as *const u8, + &instruction as *const _ as *const u8, account_infos as *const _ as *const u8, account_infos.len() as u64, signers_seeds as *const _ as *const u8, @@ -430,17 +432,18 @@ pub fn check_type_assumptions() { accounts: vec![account_meta1.clone(), account_meta2.clone()], data: data.clone(), }; + let instruction = StableInstruction::from(instruction); let instruction_addr = &instruction as *const _ as u64; // program id - assert_eq!(offset_of!(Instruction, program_id), 48); + assert_eq!(offset_of!(StableInstruction, program_id), 48); let pubkey_ptr = (instruction_addr + 48) as *const Pubkey; unsafe { assert_eq!(*pubkey_ptr, pubkey1); } // accounts - assert_eq!(offset_of!(Instruction, accounts), 0); + assert_eq!(offset_of!(StableInstruction, accounts), 0); let accounts_ptr = (instruction_addr) as *const *const AccountMeta; let accounts_cap = (instruction_addr + 8) as *const usize; let accounts_len = (instruction_addr + 16) as *const usize; @@ -453,7 +456,7 @@ pub fn check_type_assumptions() { } // data - assert_eq!(offset_of!(Instruction, data), 24); + assert_eq!(offset_of!(StableInstruction, data), 24); let data_ptr = (instruction_addr + 24) as *const *const [u8; 5]; let data_cap = (instruction_addr + 24 + 8) as *const usize; let data_len = (instruction_addr + 24 + 16) as *const usize; diff --git a/sdk/program/src/stable_layout.rs b/sdk/program/src/stable_layout.rs new file mode 100644 index 00000000000000..37d3d8bf20ccb5 --- /dev/null +++ b/sdk/program/src/stable_layout.rs @@ -0,0 +1,10 @@ +#![doc(hidden)] +//! Types with stable memory layouts +//! +//! Internal use only; here be dragons! + +pub mod stable_instruction; +pub mod stable_rc; +pub mod stable_ref_cell; +pub mod stable_slice; +pub mod stable_vec; diff --git a/sdk/program/src/stable_layout/stable_instruction.rs b/sdk/program/src/stable_layout/stable_instruction.rs new file mode 100644 index 00000000000000..e6c06270b0b4b0 --- /dev/null +++ b/sdk/program/src/stable_layout/stable_instruction.rs @@ -0,0 +1,97 @@ +//! `Instruction`, with a stable memory layout + +use { + crate::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + stable_layout::stable_vec::StableVec, + }, + std::fmt::Debug, +}; + +/// `Instruction`, with a stable memory layout +/// +/// This is used within the runtime to ensure memory mapping and memory accesses are valid. We +/// rely on known addresses and offsets within the runtime, and since `Instruction`'s layout is +/// allowed to change, we must provide a way to lock down the memory layout. `StableInstruction` +/// reimplements the bare minimum of `Instruction`'s API sufficient only for the runtime's needs. +/// +/// # Examples +/// +/// Creating a `StableInstruction` from an `Instruction` +/// +/// ``` +/// # use solana_program::{instruction::Instruction, pubkey::Pubkey, stable_layout::stable_instruction::StableInstruction}; +/// # let program_id = Pubkey::default(); +/// # let accounts = Vec::default(); +/// # let data = Vec::default(); +/// let instruction = Instruction { program_id, accounts, data }; +/// let instruction = StableInstruction::from(instruction); +/// ``` +#[derive(Debug, PartialEq)] +#[repr(C)] +pub struct StableInstruction { + pub accounts: StableVec, + pub data: StableVec, + pub program_id: Pubkey, +} + +impl From for StableInstruction { + fn from(other: Instruction) -> Self { + Self { + accounts: other.accounts.into(), + data: other.data.into(), + program_id: other.program_id, + } + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + memoffset::offset_of, + std::mem::{align_of, size_of}, + }; + + #[allow(clippy::integer_arithmetic)] + #[test] + fn test_memory_layout() { + assert_eq!(offset_of!(StableInstruction, accounts), 0); + assert_eq!(offset_of!(StableInstruction, data), 24); + assert_eq!(offset_of!(StableInstruction, program_id), 48); + assert_eq!(align_of::(), 8); + assert_eq!(size_of::(), 24 + 24 + 32); + + let program_id = Pubkey::new_unique(); + let account_meta1 = AccountMeta { + pubkey: Pubkey::new_unique(), + is_signer: true, + is_writable: false, + }; + let account_meta2 = AccountMeta { + pubkey: Pubkey::new_unique(), + is_signer: false, + is_writable: true, + }; + let accounts = vec![account_meta1, account_meta2]; + let data = vec![1, 2, 3, 4, 5]; + let instruction = Instruction { + program_id, + accounts: accounts.clone(), + data: data.clone(), + }; + let instruction = StableInstruction::from(instruction); + + let instruction_addr = &instruction as *const _ as u64; + + let accounts_ptr = instruction_addr as *const StableVec; + assert_eq!(unsafe { &*accounts_ptr }, &accounts); + + let data_ptr = (instruction_addr + 24) as *const StableVec; + assert_eq!(unsafe { &*data_ptr }, &data); + + let pubkey_ptr = (instruction_addr + 48) as *const Pubkey; + assert_eq!(unsafe { *pubkey_ptr }, program_id); + } +} diff --git a/sdk/program/src/stable_layout/stable_rc.rs b/sdk/program/src/stable_layout/stable_rc.rs new file mode 100644 index 00000000000000..5865eda3e8c700 --- /dev/null +++ b/sdk/program/src/stable_layout/stable_rc.rs @@ -0,0 +1,29 @@ +//! Ensure Rc has a stable memory layout + +#[cfg(test)] +mod tests { + use std::{ + mem::{align_of, size_of}, + rc::Rc, + }; + + #[test] + fn test_memory_layout() { + assert_eq!(align_of::>(), 8); + assert_eq!(size_of::>(), 8); + + let value = 42; + let rc = Rc::new(value); + let _rc2 = Rc::clone(&rc); // used to increment strong count + + let addr_rc = &rc as *const _ as usize; + let addr_ptr = addr_rc; + let addr_rcbox = unsafe { *(addr_ptr as *const *const i32) } as usize; + let addr_strong = addr_rcbox; + let addr_weak = addr_rcbox + 8; + let addr_value = addr_rcbox + 16; + assert_eq!(unsafe { *(addr_strong as *const usize) }, 2); + assert_eq!(unsafe { *(addr_weak as *const usize) }, 1); + assert_eq!(unsafe { *(addr_value as *const i32) }, 42); + } +} diff --git a/sdk/program/src/stable_layout/stable_ref_cell.rs b/sdk/program/src/stable_layout/stable_ref_cell.rs new file mode 100644 index 00000000000000..c5741acac0bf9d --- /dev/null +++ b/sdk/program/src/stable_layout/stable_ref_cell.rs @@ -0,0 +1,25 @@ +//! Ensure RefCell has a stable layout + +#[cfg(test)] +mod tests { + use std::{ + cell::RefCell, + mem::{align_of, size_of}, + }; + + #[test] + fn test_memory_layout() { + assert_eq!(align_of::>(), 8); + assert_eq!(size_of::>(), 8 + 4 + /* padding */4); + + let value = 42; + let refcell = RefCell::new(value); + let _borrow = refcell.borrow(); // used to increment borrow count + + let addr_refcell = &refcell as *const _ as usize; + let addr_borrow = addr_refcell; + let addr_value = addr_refcell + 8; + assert_eq!(unsafe { *(addr_borrow as *const isize) }, 1); + assert_eq!(unsafe { *(addr_value as *const i32) }, 42); + } +} diff --git a/sdk/program/src/stable_layout/stable_slice.rs b/sdk/program/src/stable_layout/stable_slice.rs new file mode 100644 index 00000000000000..55b9dca1515f46 --- /dev/null +++ b/sdk/program/src/stable_layout/stable_slice.rs @@ -0,0 +1,27 @@ +//! Ensure slice has a stable memory layout + +#[cfg(test)] +mod tests { + use std::mem::{align_of, size_of}; + + #[test] + fn test_memory_layout() { + assert_eq!(align_of::<&[i32]>(), 8); + assert_eq!(size_of::<&[i32]>(), /*ptr*/ 8 + /*len*/8); + + let array = [11, 22, 33, 44, 55]; + let slice = array.as_slice(); + + let addr_slice = &slice as *const _ as usize; + let addr_ptr = addr_slice; + let addr_len = addr_slice + 8; + assert_eq!(unsafe { *(addr_len as *const usize) }, 5); + + let ptr_data = addr_ptr as *const *const i32; + assert_eq!(unsafe { *((*ptr_data).offset(0)) }, 11); + assert_eq!(unsafe { *((*ptr_data).offset(1)) }, 22); + assert_eq!(unsafe { *((*ptr_data).offset(2)) }, 33); + assert_eq!(unsafe { *((*ptr_data).offset(3)) }, 44); + assert_eq!(unsafe { *((*ptr_data).offset(4)) }, 55); + } +} diff --git a/sdk/program/src/stable_layout/stable_vec.rs b/sdk/program/src/stable_layout/stable_vec.rs new file mode 100644 index 00000000000000..26fcefdc47d7ac --- /dev/null +++ b/sdk/program/src/stable_layout/stable_vec.rs @@ -0,0 +1,182 @@ +//! `Vec`, with a stable memory layout + +use std::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; + +/// `Vec`, with a stable memory layout +/// +/// This container is used within the runtime to ensure memory mapping and memory accesses are +/// valid. We rely on known addresses and offsets within the runtime, and since `Vec`'s layout +/// is allowed to change, we must provide a way to lock down the memory layout. `StableVec` +/// reimplements the bare minimum of `Vec`'s API sufficient only for the runtime's needs. +/// +/// To ensure memory allocation and deallocation is handled correctly, it is only possible to +/// create a new `StableVec` from an existing `Vec`. This way we ensure all Rust invariants are +/// upheld. +/// +/// # Examples +/// +/// Creating a `StableVec` from a `Vec` +/// +/// ``` +/// # use solana_program::stable_layout::stable_vec::StableVec; +/// let vec = vec!["meow", "woof", "moo"]; +/// let vec = StableVec::from(vec); +/// ``` +#[repr(C)] +pub struct StableVec { + pub ptr: NonNull, + pub cap: usize, + pub len: usize, + _marker: PhantomData, +} + +impl StableVec { + #[inline] + pub fn as_ptr(&self) -> *const T { + // We shadow the slice method of the same name to avoid going through + // `deref`, which creates an intermediate reference. + self.ptr.as_ptr() + } + + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut T { + // We shadow the slice method of the same name to avoid going through + // `deref_mut`, which creates an intermediate reference. + self.ptr.as_ptr() + } +} + +impl AsRef<[T]> for StableVec { + fn as_ref(&self) -> &[T] { + self + } +} + +impl AsMut<[T]> for StableVec { + fn as_mut(&mut self) -> &mut [T] { + self + } +} + +impl std::ops::Deref for StableVec { + type Target = [T]; + + #[inline] + fn deref(&self) -> &[T] { + unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len) } + } +} + +impl std::ops::DerefMut for StableVec { + #[inline] + fn deref_mut(&mut self) -> &mut [T] { + unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len) } + } +} + +impl std::fmt::Debug for StableVec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&**self, f) + } +} + +macro_rules! impl_partial_eq { + ([$($vars:tt)*] $lhs:ty, $rhs:ty) => { + impl PartialEq<$rhs> for $lhs + where + T: PartialEq, + { + #[inline] + fn eq(&self, other: &$rhs) -> bool { self[..] == other[..] } + } + } +} +impl_partial_eq! { [] StableVec, StableVec } +impl_partial_eq! { [] StableVec, Vec } +impl_partial_eq! { [] Vec, StableVec } +impl_partial_eq! { [] StableVec, &[U] } +impl_partial_eq! { [] StableVec, &mut [U] } +impl_partial_eq! { [] &[T], StableVec } +impl_partial_eq! { [] &mut [T], StableVec } +impl_partial_eq! { [] StableVec, [U] } +impl_partial_eq! { [] [T], StableVec } +impl_partial_eq! { [const N: usize] StableVec, [U; N] } +impl_partial_eq! { [const N: usize] StableVec, &[U; N] } + +impl From> for StableVec { + fn from(other: Vec) -> Self { + // NOTE: This impl is basically copied from `Vec::into_raw_parts()`. Once that fn is + // stabilized, use it here. + // + // We are going to pilfer `other`'s guts, and we don't want it to be dropped when it goes + // out of scope. + let mut other = ManuallyDrop::new(other); + Self { + // SAFETY: We have a valid Vec, so its ptr is non-null. + ptr: unsafe { NonNull::new_unchecked(other.as_mut_ptr()) }, + cap: other.capacity(), + len: other.len(), + _marker: PhantomData, + } + } +} + +impl From> for Vec { + fn from(other: StableVec) -> Self { + // We are going to pilfer `other`'s guts, and we don't want it to be dropped when it goes + // out of scope. + let other = ManuallyDrop::new(other); + // SAFETY: We have a valid StableVec, which we can only get from a Vec. Therefore it is + // safe to convert back to Vec. + unsafe { Vec::from_raw_parts(other.ptr.as_ptr(), other.len, other.cap) } + } +} + +impl Drop for StableVec { + fn drop(&mut self) { + // We only allow creating a StableVec through creating a Vec. To ensure we are dropped + // correctly, convert ourselves back to a Vec and let Vec's drop handling take over. + // + // SAFETY: We have a valid StableVec, which we can only get from a Vec. Therefore it is + // safe to convert back to Vec. + let _vec = unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.cap) }; + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + memoffset::offset_of, + std::mem::{align_of, size_of}, + }; + + #[allow(clippy::integer_arithmetic)] + #[test] + fn test_memory_layout() { + assert_eq!(offset_of!(StableVec, ptr), 0); + assert_eq!(offset_of!(StableVec, cap), 8); + assert_eq!(offset_of!(StableVec, len), 16); + assert_eq!(align_of::>(), 8); + assert_eq!(size_of::>(), 8 + 8 + 8); + + // create a vec with different values for cap and len + let vec = { + let mut vec = Vec::with_capacity(3); + vec.push(11); + vec.push(22); + vec + }; + let vec = StableVec::from(vec); + + let addr_vec = &vec as *const _ as usize; + let addr_ptr = addr_vec; + let addr_cap = addr_vec + 8; + let addr_len = addr_vec + 16; + assert_eq!(unsafe { *(addr_cap as *const usize) }, 3); + assert_eq!(unsafe { *(addr_len as *const usize) }, 2); + + let ptr_data = addr_ptr as *const &[i32; 2]; + assert_eq!(unsafe { *ptr_data }, &[11, 22]); + } +}