فهرست منبع

Direct Mapping Supercharged (#5871)

* Patches SBPF dependency in Cargo.toml

* Splits off aligment padding from realloc region.

* Only serialize MAX_PERMITTED_DATA_INCREASE when copy_account_data.

* Separate data writeback, resize and address bump.

* Throws InstructionError::AccountDataTooSmall for load accesses and InstructionError::InvalidRealloc for write accesses beyond the current account length.

* Removes the realloc padding region, leaving its address space unmapped.

* Resizes / reallocs account on demand in TransactionContext::access_violation_handler().

* Stops zeroing of spare capacity in update_caller_account().

* Stops moving the contents of the realloc region around.

* Renames update_caller_account_perms() => update_caller_account_region().

* Splits TranslatedAccount update_caller_account_region and update_caller_account_info.

* Reformulates realloc constraint in terms of address_space_reserved_for_account.

* Removes noncontiguous versions of memops.

* Adds touch_type_mut() and touch_slice_mut() to translate_mut!().

* Removes parameter writable from Serializer::push_region().

* Inlines account_data_region().

* Cleanup unused code and dependencies.

* Runs update_callee_account() and update_caller_account() tests with direct_mapping as well.

* Removes test_update_caller_account_data_capacity_direct_mapping().

* Removes test_update_caller_account_data_direct_mapping().

* Removes test_update_callee_account_data_direct_mapping().

* Removes test_cpi_change_account_data_memory_allocation().

* Adjusts tests.

* Adds test_deny_access_beyond_current_length().

* Adds test_access_violation_handler().

* Moves the `post_len > address_space_reserved_for_account` check outside of `if prev_len != post_len`.

* Swaps the execution order of `update_caller_account()` and `update_caller_account_region()`.
Alexander Meißner 4 ماه پیش
والد
کامیت
d35248b55c

+ 3 - 3
Cargo.lock

@@ -7185,7 +7185,6 @@ dependencies = [
  "num-traits",
  "num-traits",
  "qualifier_attr",
  "qualifier_attr",
  "rand 0.8.5",
  "rand 0.8.5",
- "scopeguard",
  "solana-account",
  "solana-account",
  "solana-account-info",
  "solana-account-info",
  "solana-big-mod-exp",
  "solana-big-mod-exp",
@@ -10270,9 +10269,9 @@ checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf"
 
 
 [[package]]
 [[package]]
 name = "solana-sbpf"
 name = "solana-sbpf"
-version = "0.11.2"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "642335ab08889cd963790faeaa7824e320a5ada4b6eb93ebfc842fa03ddef425"
+checksum = "3c7a3d3cff34df928b804917bf111d3ede779af406703580cd7ed8fb239f5acf"
 dependencies = [
 dependencies = [
  "byteorder",
  "byteorder",
  "combine 3.8.1",
  "combine 3.8.1",
@@ -11305,6 +11304,7 @@ dependencies = [
  "solana-instructions-sysvar",
  "solana-instructions-sysvar",
  "solana-pubkey",
  "solana-pubkey",
  "solana-rent",
  "solana-rent",
+ "solana-sbpf",
  "solana-sdk-ids",
  "solana-sdk-ids",
  "solana-signature",
  "solana-signature",
  "solana-system-interface",
  "solana-system-interface",

+ 1 - 1
Cargo.toml

@@ -499,7 +499,7 @@ solana-rpc-client-types = { path = "rpc-client-types", version = "=3.0.0" }
 solana-runtime = { path = "runtime", version = "=3.0.0" }
 solana-runtime = { path = "runtime", version = "=3.0.0" }
 solana-runtime-transaction = { path = "runtime-transaction", version = "=3.0.0" }
 solana-runtime-transaction = { path = "runtime-transaction", version = "=3.0.0" }
 solana-sanitize = "2.2.1"
 solana-sanitize = "2.2.1"
-solana-sbpf = "=0.11.2"
+solana-sbpf = "=0.12.0"
 solana-sdk-ids = "2.2.1"
 solana-sdk-ids = "2.2.1"
 solana-secp256k1-program = "2.2.3"
 solana-secp256k1-program = "2.2.3"
 solana-secp256k1-recover = "2.2.1"
 solana-secp256k1-recover = "2.2.1"

+ 272 - 65
program-runtime/src/serialization.rs

@@ -22,6 +22,23 @@ use {
 /// SBF VM.
 /// SBF VM.
 const MAX_INSTRUCTION_ACCOUNTS: u8 = NON_DUP_MARKER;
 const MAX_INSTRUCTION_ACCOUNTS: u8 = NON_DUP_MARKER;
 
 
+/// Creates the account data direct mapping in serialization and CPI return
+pub fn create_memory_region_of_account(
+    account: &mut BorrowedAccount<'_>,
+    vaddr: u64,
+) -> Result<MemoryRegion, InstructionError> {
+    let can_data_be_changed = account.can_data_be_changed().is_ok();
+    let mut memory_region = if can_data_be_changed && !account.is_shared() {
+        MemoryRegion::new_writable(account.get_data_mut()?, vaddr)
+    } else {
+        MemoryRegion::new_readonly(account.get_data(), vaddr)
+    };
+    if can_data_be_changed {
+        memory_region.access_violation_handler_payload = Some(account.get_index_in_transaction());
+    }
+    Ok(memory_region)
+}
+
 #[allow(dead_code)]
 #[allow(dead_code)]
 enum SerializeAccount<'a> {
 enum SerializeAccount<'a> {
     Account(IndexOfAccount, BorrowedAccount<'a>),
     Account(IndexOfAccount, BorrowedAccount<'a>),
@@ -97,20 +114,19 @@ impl Serializer {
             self.write_all(account.get_data());
             self.write_all(account.get_data());
             vm_data_addr
             vm_data_addr
         } else {
         } else {
-            self.push_region(true);
+            self.push_region();
             let vaddr = self.vaddr;
             let vaddr = self.vaddr;
-            if !account.get_data().is_empty() {
-                let writable = account.can_data_be_changed().is_ok();
-                let shared = account.is_shared();
-                let mut new_region = if writable && !shared {
-                    MemoryRegion::new_writable(account.get_data_mut()?, self.vaddr)
-                } else {
-                    MemoryRegion::new_readonly(account.get_data(), self.vaddr)
-                };
-                if writable && shared {
-                    new_region.cow_callback_payload = account.get_index_in_transaction() as u32;
-                }
-                self.vaddr += new_region.len;
+            let address_space_reserved_for_account = if self.aligned {
+                account
+                    .get_data()
+                    .len()
+                    .saturating_add(MAX_PERMITTED_DATA_INCREASE)
+            } else {
+                account.get_data().len()
+            };
+            if address_space_reserved_for_account > 0 {
+                let new_region = create_memory_region_of_account(account, self.vaddr)?;
+                self.vaddr += address_space_reserved_for_account as u64;
                 self.regions.push(new_region);
                 self.regions.push(new_region);
             }
             }
             vaddr
             vaddr
@@ -128,37 +144,27 @@ impl Serializer {
                 // padding and shift the start of the next region, so that once
                 // padding and shift the start of the next region, so that once
                 // vm_addr is aligned, the corresponding host_addr is aligned
                 // vm_addr is aligned, the corresponding host_addr is aligned
                 // too.
                 // too.
-                self.fill_write(MAX_PERMITTED_DATA_INCREASE + BPF_ALIGN_OF_U128, 0)
+                self.fill_write(BPF_ALIGN_OF_U128, 0)
                     .map_err(|_| InstructionError::InvalidArgument)?;
                     .map_err(|_| InstructionError::InvalidArgument)?;
                 self.region_start += BPF_ALIGN_OF_U128.saturating_sub(align_offset);
                 self.region_start += BPF_ALIGN_OF_U128.saturating_sub(align_offset);
-                // put the realloc padding in its own region
-                self.push_region(account.can_data_be_changed().is_ok());
             }
             }
         }
         }
 
 
         Ok(vm_data_addr)
         Ok(vm_data_addr)
     }
     }
 
 
-    fn push_region(&mut self, writable: bool) {
+    fn push_region(&mut self) {
         let range = self.region_start..self.buffer.len();
         let range = self.region_start..self.buffer.len();
-        let region = if writable {
-            MemoryRegion::new_writable(
-                self.buffer.as_slice_mut().get_mut(range.clone()).unwrap(),
-                self.vaddr,
-            )
-        } else {
-            MemoryRegion::new_readonly(
-                self.buffer.as_slice().get(range.clone()).unwrap(),
-                self.vaddr,
-            )
-        };
-        self.regions.push(region);
+        self.regions.push(MemoryRegion::new_writable(
+            self.buffer.as_slice_mut().get_mut(range.clone()).unwrap(),
+            self.vaddr,
+        ));
         self.region_start = range.end;
         self.region_start = range.end;
         self.vaddr += range.len() as u64;
         self.vaddr += range.len() as u64;
     }
     }
 
 
     fn finish(mut self) -> (AlignedMemory<HOST_ALIGN>, Vec<MemoryRegion>) {
     fn finish(mut self) -> (AlignedMemory<HOST_ALIGN>, Vec<MemoryRegion>) {
-        self.push_region(true);
+        self.push_region();
         debug_assert_eq!(self.region_start, self.buffer.len());
         debug_assert_eq!(self.region_start, self.buffer.len());
         (self.buffer, self.regions)
         (self.buffer, self.regions)
     }
     }
@@ -441,10 +447,11 @@ fn serialize_parameters_aligned(
                 + size_of::<Pubkey>() // owner
                 + size_of::<Pubkey>() // owner
                 + size_of::<u64>()  // lamports
                 + size_of::<u64>()  // lamports
                 + size_of::<u64>()  // data len
                 + size_of::<u64>()  // data len
-                + MAX_PERMITTED_DATA_INCREASE
                 + size_of::<u64>(); // rent epoch
                 + size_of::<u64>(); // rent epoch
                 if copy_account_data {
                 if copy_account_data {
-                    size += data_len + (data_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
+                    size += data_len
+                        + MAX_PERMITTED_DATA_INCREASE
+                        + (data_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
                 } else {
                 } else {
                     size += BPF_ALIGN_OF_U128;
                     size += BPF_ALIGN_OF_U128;
                 }
                 }
@@ -553,46 +560,28 @@ fn deserialize_parameters_aligned<I: IntoIterator<Item = usize>>(
             {
             {
                 return Err(InstructionError::InvalidRealloc);
                 return Err(InstructionError::InvalidRealloc);
             }
             }
-            // The redundant check helps to avoid the expensive data comparison if we can
-            let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
             if copy_account_data {
             if copy_account_data {
                 let data = buffer
                 let data = buffer
                     .get(start..start + post_len)
                     .get(start..start + post_len)
                     .ok_or(InstructionError::InvalidArgument)?;
                     .ok_or(InstructionError::InvalidArgument)?;
+                // The redundant check helps to avoid the expensive data comparison if we can
                 match borrowed_account.can_data_be_resized(post_len) {
                 match borrowed_account.can_data_be_resized(post_len) {
                     Ok(()) => borrowed_account.set_data_from_slice(data)?,
                     Ok(()) => borrowed_account.set_data_from_slice(data)?,
                     Err(err) if borrowed_account.get_data() != data => return Err(err),
                     Err(err) if borrowed_account.get_data() != data => return Err(err),
                     _ => {}
                     _ => {}
                 }
                 }
-                start += pre_len; // data
+            } else if borrowed_account.get_data().len() != post_len {
+                borrowed_account.set_data_length(post_len)?;
+            }
+            start += if copy_account_data {
+                let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
+                pre_len // data
+                    .saturating_add(MAX_PERMITTED_DATA_INCREASE) // realloc padding
+                    .saturating_add(alignment_offset)
             } else {
             } else {
                 // See Serializer::write_account() as to why we have this
                 // See Serializer::write_account() as to why we have this
-                // padding before the realloc region here.
-                start += BPF_ALIGN_OF_U128.saturating_sub(alignment_offset);
-                let data = buffer
-                    .get(start..start + MAX_PERMITTED_DATA_INCREASE)
-                    .ok_or(InstructionError::InvalidArgument)?;
-                match borrowed_account.can_data_be_resized(post_len) {
-                    Ok(()) => {
-                        borrowed_account.set_data_length(post_len)?;
-                        let allocated_bytes = post_len.saturating_sub(pre_len);
-                        if allocated_bytes > 0 {
-                            borrowed_account
-                                .get_data_mut()?
-                                .get_mut(pre_len..pre_len.saturating_add(allocated_bytes))
-                                .ok_or(InstructionError::InvalidArgument)?
-                                .copy_from_slice(
-                                    data.get(0..allocated_bytes)
-                                        .ok_or(InstructionError::InvalidArgument)?,
-                                );
-                        }
-                    }
-                    Err(err) if borrowed_account.get_data().len() != post_len => return Err(err),
-                    _ => {}
-                }
-            }
-            start += MAX_PERMITTED_DATA_INCREASE;
-            start += alignment_offset;
+                BPF_ALIGN_OF_U128
+            };
             start += size_of::<u64>(); // rent_epoch
             start += size_of::<u64>(); // rent_epoch
             if borrowed_account.get_owner().to_bytes() != owner {
             if borrowed_account.get_owner().to_bytes() != owner {
                 // Change the owner at the end so that we are allowed to change the lamports and data before
                 // Change the owner at the end so that we are allowed to change the lamports and data before
@@ -609,10 +598,13 @@ mod tests {
     use {
     use {
         super::*,
         super::*,
         crate::with_mock_invoke_context,
         crate::with_mock_invoke_context,
-        solana_account::{Account, AccountSharedData, WritableAccount},
+        solana_account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
         solana_account_info::AccountInfo,
         solana_account_info::AccountInfo,
         solana_program_entrypoint::deserialize,
         solana_program_entrypoint::deserialize,
+        solana_rent::Rent,
+        solana_sbpf::{memory_region::MemoryMapping, program::SBPFVersion, vm::Config},
         solana_sdk_ids::bpf_loader,
         solana_sdk_ids::bpf_loader,
+        solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
         solana_transaction_context::InstructionAccount,
         solana_transaction_context::InstructionAccount,
         std::{
         std::{
             cell::RefCell,
             cell::RefCell,
@@ -1324,15 +1316,230 @@ mod tests {
     }
     }
 
 
     fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
     fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
-        let len = regions.iter().fold(0, |len, region| len + region.len) as usize;
-        let mut mem = AlignedMemory::zero_filled(len);
+        let last_region = regions.last().unwrap();
+        let mut mem = AlignedMemory::zero_filled(
+            (last_region.vm_addr - MM_INPUT_START + last_region.len) as usize,
+        );
         for region in regions {
         for region in regions {
             let host_slice = unsafe {
             let host_slice = unsafe {
-                slice::from_raw_parts(region.host_addr.get() as *const u8, region.len as usize)
+                slice::from_raw_parts(region.host_addr as *const u8, region.len as usize)
             };
             };
             mem.as_slice_mut()[(region.vm_addr - MM_INPUT_START) as usize..][..region.len as usize]
             mem.as_slice_mut()[(region.vm_addr - MM_INPUT_START) as usize..][..region.len as usize]
                 .copy_from_slice(host_slice)
                 .copy_from_slice(host_slice)
         }
         }
         mem
         mem
     }
     }
+
+    #[test]
+    fn test_access_violation_handler() {
+        let program_id = Pubkey::new_unique();
+        let shared_account = AccountSharedData::new(0, 4, &program_id);
+        let mut transaction_context = TransactionContext::new(
+            vec![
+                (
+                    Pubkey::new_unique(),
+                    AccountSharedData::new(0, 4, &program_id),
+                ), // readonly
+                (Pubkey::new_unique(), shared_account.clone()), // writable shared
+                (
+                    Pubkey::new_unique(),
+                    AccountSharedData::new(0, 0, &program_id),
+                ), // another writable account
+                (
+                    Pubkey::new_unique(),
+                    AccountSharedData::new(
+                        0,
+                        MAX_PERMITTED_DATA_LENGTH as usize - 0x100,
+                        &program_id,
+                    ),
+                ), // almost max sized writable account
+                (
+                    Pubkey::new_unique(),
+                    AccountSharedData::new(0, 0, &program_id),
+                ), // writable dummy to burn accounts_resize_delta
+                (
+                    Pubkey::new_unique(),
+                    AccountSharedData::new(0, 0x3000, &program_id),
+                ), // writable dummy to burn accounts_resize_delta
+                (program_id, AccountSharedData::default()),     // program
+            ],
+            Rent::default(),
+            /* max_instruction_stack_depth */ 1,
+            /* max_instruction_trace_length */ 1,
+        );
+        let program_indices = [6];
+        let transaction_accounts_indexes = [0, 1, 2, 3, 4, 5];
+        let instruction_accounts =
+            deduplicated_instruction_accounts(&transaction_accounts_indexes, |index| index > 0);
+        let instruction_data = [];
+        transaction_context
+            .get_next_instruction_context()
+            .unwrap()
+            .configure(&program_indices, &instruction_accounts, &instruction_data);
+        transaction_context.push().unwrap();
+        let instruction_context = transaction_context
+            .get_current_instruction_context()
+            .unwrap();
+        let account_start_offsets = [
+            MM_INPUT_START,
+            MM_INPUT_START + 4 + MAX_PERMITTED_DATA_INCREASE as u64,
+            MM_INPUT_START + (4 + MAX_PERMITTED_DATA_INCREASE as u64) * 2,
+            MM_INPUT_START + (4 + MAX_PERMITTED_DATA_INCREASE as u64) * 3,
+        ];
+        let regions = account_start_offsets
+            .iter()
+            .enumerate()
+            .map(|(index_in_instruction, account_start_offset)| {
+                create_memory_region_of_account(
+                    &mut instruction_context
+                        .try_borrow_instruction_account(
+                            &transaction_context,
+                            index_in_instruction as IndexOfAccount,
+                        )
+                        .unwrap(),
+                    *account_start_offset,
+                )
+                .unwrap()
+            })
+            .collect::<Vec<_>>();
+        let config = Config {
+            aligned_memory_mapping: false,
+            ..Config::default()
+        };
+        let mut memory_mapping = MemoryMapping::new_with_access_violation_handler(
+            regions,
+            &config,
+            SBPFVersion::V3,
+            transaction_context.access_violation_handler(),
+        )
+        .unwrap();
+
+        // Reading readonly account is allowed
+        memory_mapping
+            .load::<u32>(account_start_offsets[0])
+            .unwrap();
+
+        // Reading writable account is allowed
+        memory_mapping
+            .load::<u32>(account_start_offsets[1])
+            .unwrap();
+
+        // Reading beyond readonly accounts current size is denied
+        memory_mapping
+            .load::<u32>(account_start_offsets[0] + 4)
+            .unwrap_err();
+
+        // Writing to readonly account is denied
+        memory_mapping
+            .store::<u32>(0, account_start_offsets[0])
+            .unwrap_err();
+
+        // Writing to shared writable account makes it unique (CoW logic)
+        assert!(transaction_context
+            .accounts()
+            .try_borrow(1)
+            .unwrap()
+            .is_shared());
+        memory_mapping
+            .store::<u32>(0, account_start_offsets[1])
+            .unwrap();
+        assert!(!transaction_context
+            .accounts()
+            .try_borrow(1)
+            .unwrap()
+            .is_shared());
+        assert_eq!(
+            transaction_context
+                .accounts()
+                .try_borrow(1)
+                .unwrap()
+                .data()
+                .len(),
+            4,
+        );
+
+        // Reading beyond writable accounts current size grows is denied
+        memory_mapping
+            .load::<u32>(account_start_offsets[1] + 4)
+            .unwrap_err();
+
+        // Writing beyond writable accounts current size grows it
+        // to original length plus MAX_PERMITTED_DATA_INCREASE
+        memory_mapping
+            .store::<u32>(0, account_start_offsets[1] + 4)
+            .unwrap();
+        assert_eq!(
+            transaction_context
+                .accounts()
+                .try_borrow(1)
+                .unwrap()
+                .data()
+                .len(),
+            4 + MAX_PERMITTED_DATA_INCREASE,
+        );
+        assert!(
+            transaction_context
+                .accounts()
+                .try_borrow(1)
+                .unwrap()
+                .data()
+                .len()
+                < 0x3000
+        );
+
+        // Writing beyond almost max sized writable accounts current size only grows it
+        // to MAX_PERMITTED_DATA_LENGTH
+        memory_mapping
+            .store::<u32>(0, account_start_offsets[3] + MAX_PERMITTED_DATA_LENGTH - 4)
+            .unwrap();
+        assert_eq!(
+            transaction_context
+                .accounts()
+                .try_borrow(3)
+                .unwrap()
+                .data()
+                .len(),
+            MAX_PERMITTED_DATA_LENGTH as usize,
+        );
+
+        // Accessing the rest of the address space reserved for
+        // the almost max sized writable account is denied
+        memory_mapping
+            .load::<u32>(account_start_offsets[3] + MAX_PERMITTED_DATA_LENGTH)
+            .unwrap_err();
+        memory_mapping
+            .store::<u32>(0, account_start_offsets[3] + MAX_PERMITTED_DATA_LENGTH)
+            .unwrap_err();
+
+        // Burn through most of the accounts_resize_delta budget
+        let remaining_allowed_growth: usize = 0x700;
+        for index_in_instruction in 4..6 {
+            let mut borrowed_account = instruction_context
+                .try_borrow_instruction_account(&transaction_context, index_in_instruction)
+                .unwrap();
+            borrowed_account
+                .set_data(vec![0u8; MAX_PERMITTED_DATA_LENGTH as usize])
+                .unwrap();
+        }
+        assert_eq!(
+            transaction_context.accounts_resize_delta().unwrap(),
+            MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
+                - remaining_allowed_growth as i64,
+        );
+
+        // Writing beyond empty writable accounts current size
+        // only grows it to fill up MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
+        memory_mapping
+            .store::<u32>(0, account_start_offsets[2] + 0x500)
+            .unwrap();
+        assert_eq!(
+            transaction_context
+                .accounts()
+                .try_borrow(2)
+                .unwrap()
+                .data()
+                .len(),
+            remaining_allowed_growth,
+        );
+    }
 }
 }

+ 0 - 1
programs/bpf_loader/Cargo.toml

@@ -31,7 +31,6 @@ bincode = { workspace = true }
 libsecp256k1 = { workspace = true }
 libsecp256k1 = { workspace = true }
 num-traits = { workspace = true }
 num-traits = { workspace = true }
 qualifier_attr = { workspace = true }
 qualifier_attr = { workspace = true }
-scopeguard = { workspace = true }
 solana-account = { workspace = true }
 solana-account = { workspace = true }
 solana-account-info = { workspace = true }
 solana-account-info = { workspace = true }
 solana-big-mod-exp = { workspace = true }
 solana-big-mod-exp = { workspace = true }

+ 51 - 28
programs/bpf_loader/src/lib.rs

@@ -349,11 +349,11 @@ fn create_memory_mapping<'a, 'b, C: ContextObject>(
     .chain(additional_regions)
     .chain(additional_regions)
     .collect();
     .collect();
 
 
-    Ok(MemoryMapping::new_with_cow(
+    Ok(MemoryMapping::new_with_access_violation_handler(
         regions,
         regions,
         config,
         config,
         sbpf_version,
         sbpf_version,
-        transaction_context.account_data_write_access_handler(),
+        transaction_context.access_violation_handler(),
     )?)
     )?)
 }
 }
 
 
@@ -1692,44 +1692,67 @@ fn execute<'a, 'b: 'a>(
                 }
                 }
 
 
                 if direct_mapping {
                 if direct_mapping {
-                    if let EbpfError::AccessViolation(
-                        AccessType::Store,
-                        address,
-                        _size,
-                        _section_name,
-                    ) = error
+                    if let EbpfError::SyscallError(err) = error {
+                        error = err
+                            .downcast::<EbpfError>()
+                            .map(|err| *err)
+                            .unwrap_or_else(EbpfError::SyscallError);
+                    }
+                    if let EbpfError::AccessViolation(access_type, vm_addr, len, _section_name) =
+                        error
                     {
                     {
                         // If direct_mapping is enabled and a program tries to write to a readonly
                         // If direct_mapping is enabled and a program tries to write to a readonly
                         // region we'll get a memory access violation. Map it to a more specific
                         // region we'll get a memory access violation. Map it to a more specific
                         // error so it's easier for developers to see what happened.
                         // error so it's easier for developers to see what happened.
-                        if let Some((instruction_account_index, _)) = account_region_addrs
-                            .iter()
-                            .enumerate()
-                            .find(|(_, vm_region)| vm_region.contains(&address))
+                        if let Some((instruction_account_index, vm_addr_range)) =
+                            account_region_addrs
+                                .iter()
+                                .enumerate()
+                                .find(|(_, vm_addr_range)| vm_addr_range.contains(&vm_addr))
                         {
                         {
                             let transaction_context = &invoke_context.transaction_context;
                             let transaction_context = &invoke_context.transaction_context;
                             let instruction_context =
                             let instruction_context =
                                 transaction_context.get_current_instruction_context()?;
                                 transaction_context.get_current_instruction_context()?;
-
                             let account = instruction_context.try_borrow_instruction_account(
                             let account = instruction_context.try_borrow_instruction_account(
                                 transaction_context,
                                 transaction_context,
                                 instruction_account_index as IndexOfAccount,
                                 instruction_account_index as IndexOfAccount,
                             )?;
                             )?;
-
-                            error = EbpfError::SyscallError(Box::new(
-                                #[allow(deprecated)]
-                                if !invoke_context
-                                    .get_feature_set()
-                                    .remove_accounts_executable_flag_checks
-                                    && account.is_executable()
-                                {
-                                    InstructionError::ExecutableDataModified
-                                } else if account.is_writable() {
-                                    InstructionError::ExternalAccountDataModified
-                                } else {
-                                    InstructionError::ReadonlyDataModified
-                                },
-                            ));
+                            if vm_addr.saturating_add(len) <= vm_addr_range.end {
+                                // The access was within the range of the accounts address space,
+                                // but it might not be within the range of the actual data.
+                                let is_access_outside_of_data = vm_addr
+                                    .saturating_add(len)
+                                    .saturating_sub(vm_addr_range.start)
+                                    as usize
+                                    > account.get_data().len();
+                                error = EbpfError::SyscallError(Box::new(
+                                    #[allow(deprecated)]
+                                    match access_type {
+                                        AccessType::Store => {
+                                            if let Err(err) = account.can_data_be_changed() {
+                                                err
+                                            } else {
+                                                // The store was allowed but failed,
+                                                // thus it must have been an attempt to grow the account.
+                                                debug_assert!(is_access_outside_of_data);
+                                                InstructionError::InvalidRealloc
+                                            }
+                                        }
+                                        AccessType::Load => {
+                                            // Loads should only fail when they are outside of the account data.
+                                            debug_assert!(is_access_outside_of_data);
+                                            if account.can_data_be_changed().is_err() {
+                                                // Load beyond readonly account data happened because the program
+                                                // expected more data than there actually is.
+                                                InstructionError::AccountDataTooSmall
+                                            } else {
+                                                // Load beyond writable account data also attempted to grow.
+                                                InstructionError::InvalidRealloc
+                                            }
+                                        }
+                                    },
+                                ));
+                            }
                         }
                         }
                     }
                     }
                 }
                 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 115 - 666
programs/bpf_loader/src/syscalls/cpi.rs


+ 47 - 1071
programs/bpf_loader/src/syscalls/mem_ops.rs

@@ -1,10 +1,4 @@
-use {
-    super::*,
-    crate::translate_mut,
-    solana_program_runtime::invoke_context::SerializedAccountMetadata,
-    solana_sbpf::{error::EbpfError, memory_region::MemoryRegion},
-    std::slice,
-};
+use {super::*, crate::translate_mut};
 
 
 fn mem_op_consume(invoke_context: &mut InvokeContext, n: u64) -> Result<(), Error> {
 fn mem_op_consume(invoke_context: &mut InvokeContext, n: u64) -> Result<(), Error> {
     let compute_cost = invoke_context.get_execution_cost();
     let compute_cost = invoke_context.get_execution_cost();
@@ -84,47 +78,33 @@ declare_builtin_function!(
     ) -> Result<u64, Error> {
     ) -> Result<u64, Error> {
         mem_op_consume(invoke_context, n)?;
         mem_op_consume(invoke_context, n)?;
 
 
-        if invoke_context
-            .get_feature_set()
-            .bpf_account_data_direct_mapping
-        {
-            translate_mut!(
-                memory_mapping,
-                invoke_context.get_check_aligned(),
-                let cmp_result_ref_mut: &mut i32 = map(cmp_result_addr)?;
-            );
-            let syscall_context = invoke_context.get_syscall_context()?;
-
-            *cmp_result_ref_mut = memcmp_non_contiguous(s1_addr, s2_addr, n, &syscall_context.accounts_metadata, memory_mapping, invoke_context.get_check_aligned())?;
-        } else {
-            let s1 = translate_slice::<u8>(
-                memory_mapping,
-                s1_addr,
-                n,
-                invoke_context.get_check_aligned(),
-            )?;
-            let s2 = translate_slice::<u8>(
-                memory_mapping,
-                s2_addr,
-                n,
-                invoke_context.get_check_aligned(),
-            )?;
+        let s1 = translate_slice::<u8>(
+            memory_mapping,
+            s1_addr,
+            n,
+            invoke_context.get_check_aligned(),
+        )?;
+        let s2 = translate_slice::<u8>(
+            memory_mapping,
+            s2_addr,
+            n,
+            invoke_context.get_check_aligned(),
+        )?;
 
 
-            debug_assert_eq!(s1.len(), n as usize);
-            debug_assert_eq!(s2.len(), n as usize);
-            // Safety:
-            // memcmp is marked unsafe since it assumes that the inputs are at least
-            // `n` bytes long. `s1` and `s2` are guaranteed to be exactly `n` bytes
-            // long because `translate_slice` would have failed otherwise.
-            let result = unsafe { memcmp(s1, s2, n as usize) };
+        debug_assert_eq!(s1.len(), n as usize);
+        debug_assert_eq!(s2.len(), n as usize);
+        // Safety:
+        // memcmp is marked unsafe since it assumes that the inputs are at least
+        // `n` bytes long. `s1` and `s2` are guaranteed to be exactly `n` bytes
+        // long because `translate_slice` would have failed otherwise.
+        let result = unsafe { memcmp(s1, s2, n as usize) };
 
 
-            translate_mut!(
-                memory_mapping,
-                invoke_context.get_check_aligned(),
-                let cmp_result_ref_mut: &mut i32 = map(cmp_result_addr)?;
-            );
-            *cmp_result_ref_mut = result;
-        }
+        translate_mut!(
+            memory_mapping,
+            invoke_context.get_check_aligned(),
+            let cmp_result_ref_mut: &mut i32 = map(cmp_result_addr)?;
+        );
+        *cmp_result_ref_mut = result;
 
 
         Ok(0)
         Ok(0)
     }
     }
@@ -144,90 +124,39 @@ declare_builtin_function!(
     ) -> Result<u64, Error> {
     ) -> Result<u64, Error> {
         mem_op_consume(invoke_context, n)?;
         mem_op_consume(invoke_context, n)?;
 
 
-        if invoke_context
-            .get_feature_set()
-            .bpf_account_data_direct_mapping
-        {
-            let syscall_context = invoke_context.get_syscall_context()?;
-
-            memset_non_contiguous(dst_addr, c as u8, n, &syscall_context.accounts_metadata, memory_mapping, invoke_context.get_check_aligned())
-        } else {
-            translate_mut!(
-                memory_mapping,
-                invoke_context.get_check_aligned(),
-                let s: &mut [u8] = map(dst_addr, n)?;
-            );
-            s.fill(c as u8);
-            Ok(0)
-        }
-    }
-);
-
-fn memmove(
-    invoke_context: &mut InvokeContext,
-    dst_addr: u64,
-    src_addr: u64,
-    n: u64,
-    memory_mapping: &MemoryMapping,
-) -> Result<u64, Error> {
-    if invoke_context
-        .get_feature_set()
-        .bpf_account_data_direct_mapping
-    {
-        let syscall_context = invoke_context.get_syscall_context()?;
-
-        memmove_non_contiguous(
-            dst_addr,
-            src_addr,
-            n,
-            &syscall_context.accounts_metadata,
-            memory_mapping,
-            invoke_context.get_check_aligned(),
-        )
-    } else {
         translate_mut!(
         translate_mut!(
             memory_mapping,
             memory_mapping,
             invoke_context.get_check_aligned(),
             invoke_context.get_check_aligned(),
-            let dst_ref_mut: &mut [u8] = map(dst_addr, n)?;
+            let s: &mut [u8] = map(dst_addr, n)?;
         );
         );
-        let dst_ptr = dst_ref_mut.as_mut_ptr();
-        let src_ptr = translate_slice::<u8>(
-            memory_mapping,
-            src_addr,
-            n,
-            invoke_context.get_check_aligned(),
-        )?
-        .as_ptr();
-
-        unsafe { std::ptr::copy(src_ptr, dst_ptr, n as usize) };
+        s.fill(c as u8);
         Ok(0)
         Ok(0)
     }
     }
-}
+);
 
 
-fn memmove_non_contiguous(
+fn memmove(
+    invoke_context: &mut InvokeContext,
     dst_addr: u64,
     dst_addr: u64,
     src_addr: u64,
     src_addr: u64,
     n: u64,
     n: u64,
-    accounts: &[SerializedAccountMetadata],
-    memory_mapping: &MemoryMapping,
-    resize_area: bool,
+    memory_mapping: &mut MemoryMapping,
 ) -> Result<u64, Error> {
 ) -> Result<u64, Error> {
-    let reverse = dst_addr.wrapping_sub(src_addr) < n;
-    iter_memory_pair_chunks(
-        AccessType::Load,
+    translate_mut!(
+        memory_mapping,
+        invoke_context.get_check_aligned(),
+        let dst_ref_mut: &mut [u8] = map(dst_addr, n)?;
+    );
+    let dst_ptr = dst_ref_mut.as_mut_ptr();
+    let src_ptr = translate_slice::<u8>(
+        memory_mapping,
         src_addr,
         src_addr,
-        AccessType::Store,
-        dst_addr,
         n,
         n,
-        accounts,
-        memory_mapping,
-        reverse,
-        resize_area,
-        |src_host_addr, dst_host_addr, chunk_len| {
-            unsafe { std::ptr::copy(src_host_addr, dst_host_addr as *mut u8, chunk_len) };
-            Ok(0)
-        },
-    )
+        invoke_context.get_check_aligned(),
+    )?
+    .as_ptr();
+
+    unsafe { std::ptr::copy(src_ptr, dst_ptr, n as usize) };
+    Ok(0)
 }
 }
 
 
 // Marked unsafe since it assumes that the slices are at least `n` bytes long.
 // Marked unsafe since it assumes that the slices are at least `n` bytes long.
@@ -243,964 +172,11 @@ unsafe fn memcmp(s1: &[u8], s2: &[u8], n: usize) -> i32 {
     0
     0
 }
 }
 
 
-fn memcmp_non_contiguous(
-    src_addr: u64,
-    dst_addr: u64,
-    n: u64,
-    accounts: &[SerializedAccountMetadata],
-    memory_mapping: &MemoryMapping,
-    resize_area: bool,
-) -> Result<i32, Error> {
-    let memcmp_chunk = |s1_addr, s2_addr, chunk_len| {
-        let res = unsafe {
-            let s1 = slice::from_raw_parts(s1_addr, chunk_len);
-            let s2 = slice::from_raw_parts(s2_addr, chunk_len);
-            // Safety:
-            // memcmp is marked unsafe since it assumes that s1 and s2 are exactly chunk_len
-            // long. The whole point of iter_memory_pair_chunks is to find same length chunks
-            // across two memory regions.
-            memcmp(s1, s2, chunk_len)
-        };
-        if res != 0 {
-            return Err(MemcmpError::Diff(res).into());
-        }
-        Ok(0)
-    };
-    match iter_memory_pair_chunks(
-        AccessType::Load,
-        src_addr,
-        AccessType::Load,
-        dst_addr,
-        n,
-        accounts,
-        memory_mapping,
-        false,
-        resize_area,
-        memcmp_chunk,
-    ) {
-        Ok(res) => Ok(res),
-        Err(error) => match error.downcast_ref() {
-            Some(MemcmpError::Diff(diff)) => Ok(*diff),
-            _ => Err(error),
-        },
-    }
-}
-
-#[derive(Debug)]
-enum MemcmpError {
-    Diff(i32),
-}
-
-impl std::fmt::Display for MemcmpError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            MemcmpError::Diff(diff) => write!(f, "memcmp diff: {diff}"),
-        }
-    }
-}
-
-impl std::error::Error for MemcmpError {
-    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
-        match self {
-            MemcmpError::Diff(_) => None,
-        }
-    }
-}
-
-fn memset_non_contiguous(
-    dst_addr: u64,
-    c: u8,
-    n: u64,
-    accounts: &[SerializedAccountMetadata],
-    memory_mapping: &MemoryMapping,
-    check_aligned: bool,
-) -> Result<u64, Error> {
-    let dst_chunk_iter = MemoryChunkIterator::new(
-        memory_mapping,
-        accounts,
-        AccessType::Store,
-        dst_addr,
-        n,
-        check_aligned,
-    )?;
-    for item in dst_chunk_iter {
-        let (dst_region, dst_vm_addr, dst_len) = item?;
-        let dst_host_addr = dst_region
-            .vm_to_host(dst_vm_addr, dst_len as u64)
-            .ok_or_else(|| {
-                EbpfError::AccessViolation(AccessType::Store, dst_vm_addr, dst_len as u64, "")
-            })?;
-        unsafe { slice::from_raw_parts_mut(dst_host_addr as *mut u8, dst_len).fill(c) }
-    }
-
-    Ok(0)
-}
-
-#[allow(clippy::too_many_arguments)]
-fn iter_memory_pair_chunks<T, F>(
-    src_access: AccessType,
-    src_addr: u64,
-    dst_access: AccessType,
-    dst_addr: u64,
-    n_bytes: u64,
-    accounts: &[SerializedAccountMetadata],
-    memory_mapping: &MemoryMapping,
-    reverse: bool,
-    resize_area: bool,
-    mut fun: F,
-) -> Result<T, Error>
-where
-    T: Default,
-    F: FnMut(*const u8, *const u8, usize) -> Result<T, Error>,
-{
-    let mut src_chunk_iter = MemoryChunkIterator::new(
-        memory_mapping,
-        accounts,
-        src_access,
-        src_addr,
-        n_bytes,
-        resize_area,
-    )?;
-    let mut dst_chunk_iter = MemoryChunkIterator::new(
-        memory_mapping,
-        accounts,
-        dst_access,
-        dst_addr,
-        n_bytes,
-        resize_area,
-    )?;
-
-    let mut src_chunk = None;
-    let mut dst_chunk = None;
-
-    macro_rules! memory_chunk {
-        ($chunk_iter:ident, $chunk:ident) => {
-            if let Some($chunk) = &mut $chunk {
-                // Keep processing the current chunk
-                $chunk
-            } else {
-                // This is either the first call or we've processed all the bytes in the current
-                // chunk. Move to the next one.
-                let chunk = match if reverse {
-                    $chunk_iter.next_back()
-                } else {
-                    $chunk_iter.next()
-                } {
-                    Some(item) => item?,
-                    None => break,
-                };
-                $chunk.insert(chunk)
-            }
-        };
-    }
-
-    loop {
-        let (src_region, src_chunk_addr, src_remaining) = memory_chunk!(src_chunk_iter, src_chunk);
-        let (dst_region, dst_chunk_addr, dst_remaining) = memory_chunk!(dst_chunk_iter, dst_chunk);
-
-        // We always process same-length pairs
-        let chunk_len = *src_remaining.min(dst_remaining);
-
-        let (src_host_addr, dst_host_addr) = {
-            let (src_addr, dst_addr) = if reverse {
-                // When scanning backwards not only we want to scan regions from the end,
-                // we want to process the memory within regions backwards as well.
-                (
-                    src_chunk_addr
-                        .saturating_add(*src_remaining as u64)
-                        .saturating_sub(chunk_len as u64),
-                    dst_chunk_addr
-                        .saturating_add(*dst_remaining as u64)
-                        .saturating_sub(chunk_len as u64),
-                )
-            } else {
-                (*src_chunk_addr, *dst_chunk_addr)
-            };
-
-            (
-                src_region
-                    .vm_to_host(src_addr, chunk_len as u64)
-                    .ok_or_else(|| {
-                        EbpfError::AccessViolation(AccessType::Load, src_addr, chunk_len as u64, "")
-                    })?,
-                dst_region
-                    .vm_to_host(dst_addr, chunk_len as u64)
-                    .ok_or_else(|| {
-                        EbpfError::AccessViolation(
-                            AccessType::Store,
-                            dst_addr,
-                            chunk_len as u64,
-                            "",
-                        )
-                    })?,
-            )
-        };
-
-        fun(
-            src_host_addr as *const u8,
-            dst_host_addr as *const u8,
-            chunk_len,
-        )?;
-
-        // Update how many bytes we have left to scan in each chunk
-        *src_remaining = src_remaining.saturating_sub(chunk_len);
-        *dst_remaining = dst_remaining.saturating_sub(chunk_len);
-
-        if !reverse {
-            // We've scanned `chunk_len` bytes so we move the vm address forward. In reverse
-            // mode we don't do this since we make progress by decreasing src_len and
-            // dst_len.
-            *src_chunk_addr = src_chunk_addr.saturating_add(chunk_len as u64);
-            *dst_chunk_addr = dst_chunk_addr.saturating_add(chunk_len as u64);
-        }
-
-        if *src_remaining == 0 {
-            src_chunk = None;
-        }
-
-        if *dst_remaining == 0 {
-            dst_chunk = None;
-        }
-    }
-
-    Ok(T::default())
-}
-
-struct MemoryChunkIterator<'a> {
-    memory_mapping: &'a MemoryMapping<'a>,
-    accounts: &'a [SerializedAccountMetadata],
-    access_type: AccessType,
-    initial_vm_addr: u64,
-    vm_addr_start: u64,
-    // exclusive end index (start + len, so one past the last valid address)
-    vm_addr_end: u64,
-    len: u64,
-    account_index: Option<usize>,
-    is_account: Option<bool>,
-    resize_area: bool,
-}
-
-impl<'a> MemoryChunkIterator<'a> {
-    fn new(
-        memory_mapping: &'a MemoryMapping,
-        accounts: &'a [SerializedAccountMetadata],
-        access_type: AccessType,
-        vm_addr: u64,
-        len: u64,
-        resize_area: bool,
-    ) -> Result<MemoryChunkIterator<'a>, EbpfError> {
-        let vm_addr_end = vm_addr.checked_add(len).ok_or(EbpfError::AccessViolation(
-            access_type,
-            vm_addr,
-            len,
-            "unknown",
-        ))?;
-
-        Ok(MemoryChunkIterator {
-            memory_mapping,
-            accounts,
-            access_type,
-            initial_vm_addr: vm_addr,
-            len,
-            vm_addr_start: vm_addr,
-            vm_addr_end,
-            account_index: None,
-            is_account: None,
-            resize_area,
-        })
-    }
-
-    fn region(&mut self, vm_addr: u64) -> Result<&'a MemoryRegion, Error> {
-        match self.memory_mapping.region(self.access_type, vm_addr) {
-            Ok((_region_index, region)) => Ok(region),
-            Err(error) => match error {
-                EbpfError::AccessViolation(access_type, _vm_addr, _len, name) => Err(Box::new(
-                    EbpfError::AccessViolation(access_type, self.initial_vm_addr, self.len, name),
-                )),
-                EbpfError::StackAccessViolation(access_type, _vm_addr, _len, frame) => {
-                    Err(Box::new(EbpfError::StackAccessViolation(
-                        access_type,
-                        self.initial_vm_addr,
-                        self.len,
-                        frame,
-                    )))
-                }
-                _ => Err(error.into()),
-            },
-        }
-    }
-}
-
-impl<'a> Iterator for MemoryChunkIterator<'a> {
-    type Item = Result<(&'a MemoryRegion, u64, usize), Error>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.vm_addr_start == self.vm_addr_end {
-            return None;
-        }
-
-        let region = match self.region(self.vm_addr_start) {
-            Ok(region) => region,
-            Err(e) => {
-                self.vm_addr_start = self.vm_addr_end;
-                return Some(Err(e));
-            }
-        };
-
-        let region_is_account;
-
-        let mut account_index = self.account_index.unwrap_or_default();
-        self.account_index = Some(account_index);
-
-        loop {
-            if let Some(account) = self.accounts.get(account_index) {
-                let account_addr = account.vm_data_addr;
-                let resize_addr = account_addr.saturating_add(account.original_data_len as u64);
-
-                if resize_addr < region.vm_addr {
-                    // region is after this account, move on next one
-                    account_index = account_index.saturating_add(1);
-                    self.account_index = Some(account_index);
-                } else {
-                    region_is_account = (account.original_data_len != 0 && region.vm_addr == account_addr)
-                        // unaligned programs do not have a resize area
-                        || (self.resize_area && region.vm_addr == resize_addr);
-                    break;
-                }
-            } else {
-                // address is after all the accounts
-                region_is_account = false;
-                break;
-            }
-        }
-
-        if let Some(is_account) = self.is_account {
-            if is_account != region_is_account {
-                return Some(Err(SyscallError::InvalidLength.into()));
-            }
-        } else {
-            self.is_account = Some(region_is_account);
-        }
-
-        let vm_addr = self.vm_addr_start;
-
-        let chunk_len = if region.vm_addr_end <= self.vm_addr_end {
-            // consume the whole region
-            let len = region.vm_addr_end.saturating_sub(self.vm_addr_start);
-            self.vm_addr_start = region.vm_addr_end;
-            len
-        } else {
-            // consume part of the region
-            let len = self.vm_addr_end.saturating_sub(self.vm_addr_start);
-            self.vm_addr_start = self.vm_addr_end;
-            len
-        };
-
-        Some(Ok((region, vm_addr, chunk_len as usize)))
-    }
-}
-
-impl DoubleEndedIterator for MemoryChunkIterator<'_> {
-    fn next_back(&mut self) -> Option<Self::Item> {
-        if self.vm_addr_start == self.vm_addr_end {
-            return None;
-        }
-
-        let region = match self.region(self.vm_addr_end.saturating_sub(1)) {
-            Ok(region) => region,
-            Err(e) => {
-                self.vm_addr_start = self.vm_addr_end;
-                return Some(Err(e));
-            }
-        };
-
-        let region_is_account;
-
-        let mut account_index = self
-            .account_index
-            .unwrap_or_else(|| self.accounts.len().saturating_sub(1));
-        self.account_index = Some(account_index);
-
-        loop {
-            let Some(account) = self.accounts.get(account_index) else {
-                // address is after all the accounts
-                region_is_account = false;
-                break;
-            };
-
-            let account_addr = account.vm_data_addr;
-            let resize_addr = account_addr.saturating_add(account.original_data_len as u64);
-
-            if account_index > 0 && account_addr > region.vm_addr {
-                account_index = account_index.saturating_sub(1);
-
-                self.account_index = Some(account_index);
-            } else {
-                region_is_account = (account.original_data_len != 0 && region.vm_addr == account_addr)
-                    // unaligned programs do not have a resize area
-                    || (self.resize_area && region.vm_addr == resize_addr);
-                break;
-            }
-        }
-
-        if let Some(is_account) = self.is_account {
-            if is_account != region_is_account {
-                return Some(Err(SyscallError::InvalidLength.into()));
-            }
-        } else {
-            self.is_account = Some(region_is_account);
-        }
-
-        let chunk_len = if region.vm_addr >= self.vm_addr_start {
-            // consume the whole region
-            let len = self.vm_addr_end.saturating_sub(region.vm_addr);
-            self.vm_addr_end = region.vm_addr;
-            len
-        } else {
-            // consume part of the region
-            let len = self.vm_addr_end.saturating_sub(self.vm_addr_start);
-            self.vm_addr_end = self.vm_addr_start;
-            len
-        };
-
-        Some(Ok((region, self.vm_addr_end, chunk_len as usize)))
-    }
-}
-
 #[cfg(test)]
 #[cfg(test)]
 #[allow(clippy::indexing_slicing)]
 #[allow(clippy::indexing_slicing)]
 #[allow(clippy::arithmetic_side_effects)]
 #[allow(clippy::arithmetic_side_effects)]
 mod tests {
 mod tests {
-    use {
-        super::*,
-        assert_matches::assert_matches,
-        solana_sbpf::{ebpf::MM_RODATA_START, program::SBPFVersion},
-        test_case::test_case,
-    };
-
-    fn to_chunk_vec<'a>(
-        iter: impl Iterator<Item = Result<(&'a MemoryRegion, u64, usize), Error>>,
-    ) -> Vec<(u64, usize)> {
-        iter.flat_map(|res| res.map(|(_, vm_addr, len)| (vm_addr, len)))
-            .collect::<Vec<_>>()
-    }
-
-    #[test]
-    #[should_panic(expected = "AccessViolation")]
-    fn test_memory_chunk_iterator_no_regions() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let memory_mapping = MemoryMapping::new(vec![], &config, SBPFVersion::V3).unwrap();
-
-        let mut src_chunk_iter =
-            MemoryChunkIterator::new(&memory_mapping, &[], AccessType::Load, 0, 1, true).unwrap();
-        src_chunk_iter.next().unwrap().unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "AccessViolation")]
-    fn test_memory_chunk_iterator_new_out_of_bounds_upper() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let memory_mapping = MemoryMapping::new(vec![], &config, SBPFVersion::V3).unwrap();
-
-        let mut src_chunk_iter =
-            MemoryChunkIterator::new(&memory_mapping, &[], AccessType::Load, u64::MAX, 1, true)
-                .unwrap();
-        src_chunk_iter.next().unwrap().unwrap();
-    }
-
-    #[test]
-    fn test_memory_chunk_iterator_out_of_bounds() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = vec![0xFF; 42];
-        let memory_mapping = MemoryMapping::new(
-            vec![MemoryRegion::new_readonly(&mem1, MM_RODATA_START)],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        // check oob at the lower bound on the first next()
-        let mut src_chunk_iter = MemoryChunkIterator::new(
-            &memory_mapping,
-            &[],
-            AccessType::Load,
-            MM_RODATA_START - 1,
-            42,
-            true,
-        )
-        .unwrap();
-        assert_matches!(
-            src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(),
-            EbpfError::AccessViolation(AccessType::Load, addr, 42, "unknown") if *addr == MM_RODATA_START - 1
-        );
-
-        // check oob at the upper bound. Since the memory mapping isn't empty,
-        // this always happens on the second next().
-        let mut src_chunk_iter = MemoryChunkIterator::new(
-            &memory_mapping,
-            &[],
-            AccessType::Load,
-            MM_RODATA_START,
-            43,
-            true,
-        )
-        .unwrap();
-        assert!(src_chunk_iter.next().unwrap().is_ok());
-        assert_matches!(
-            src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(),
-            EbpfError::AccessViolation(AccessType::Load, addr, 43, "program") if *addr == MM_RODATA_START
-        );
-
-        // check oob at the upper bound on the first next_back()
-        let mut src_chunk_iter = MemoryChunkIterator::new(
-            &memory_mapping,
-            &[],
-            AccessType::Load,
-            MM_RODATA_START,
-            43,
-            true,
-        )
-        .unwrap()
-        .rev();
-        assert_matches!(
-            src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(),
-            EbpfError::AccessViolation(AccessType::Load, addr, 43, "program") if *addr == MM_RODATA_START
-        );
-
-        // check oob at the upper bound on the 2nd next_back()
-        let mut src_chunk_iter = MemoryChunkIterator::new(
-            &memory_mapping,
-            &[],
-            AccessType::Load,
-            MM_RODATA_START - 1,
-            43,
-            true,
-        )
-        .unwrap()
-        .rev();
-        assert!(src_chunk_iter.next().unwrap().is_ok());
-        assert_matches!(
-            src_chunk_iter.next().unwrap().unwrap_err().downcast_ref().unwrap(),
-            EbpfError::AccessViolation(AccessType::Load, addr, 43, "unknown") if *addr == MM_RODATA_START - 1
-        );
-    }
-
-    #[test]
-    fn test_memory_chunk_iterator_one() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = vec![0xFF; 42];
-        let memory_mapping = MemoryMapping::new(
-            vec![MemoryRegion::new_readonly(&mem1, MM_RODATA_START)],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        // check lower bound
-        let mut src_chunk_iter = MemoryChunkIterator::new(
-            &memory_mapping,
-            &[],
-            AccessType::Load,
-            MM_RODATA_START - 1,
-            1,
-            true,
-        )
-        .unwrap();
-        assert!(src_chunk_iter.next().unwrap().is_err());
-
-        // check upper bound
-        let mut src_chunk_iter = MemoryChunkIterator::new(
-            &memory_mapping,
-            &[],
-            AccessType::Load,
-            MM_RODATA_START + 42,
-            1,
-            true,
-        )
-        .unwrap();
-        assert!(src_chunk_iter.next().unwrap().is_err());
-
-        for (vm_addr, len) in [
-            (MM_RODATA_START, 0),
-            (MM_RODATA_START + 42, 0),
-            (MM_RODATA_START, 1),
-            (MM_RODATA_START, 42),
-            (MM_RODATA_START + 41, 1),
-        ] {
-            for rev in [true, false] {
-                let iter = MemoryChunkIterator::new(
-                    &memory_mapping,
-                    &[],
-                    AccessType::Load,
-                    vm_addr,
-                    len,
-                    true,
-                )
-                .unwrap();
-                let res = if rev {
-                    to_chunk_vec(iter.rev())
-                } else {
-                    to_chunk_vec(iter)
-                };
-                if len == 0 {
-                    assert_eq!(res, &[]);
-                } else {
-                    assert_eq!(res, &[(vm_addr, len as usize)]);
-                }
-            }
-        }
-    }
-
-    #[test]
-    fn test_memory_chunk_iterator_two() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = vec![0x11; 8];
-        let mem2 = vec![0x22; 4];
-        let memory_mapping = MemoryMapping::new(
-            vec![
-                MemoryRegion::new_readonly(&mem1, MM_RODATA_START),
-                MemoryRegion::new_readonly(&mem2, MM_RODATA_START + 8),
-            ],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        for (vm_addr, len, mut expected) in [
-            (MM_RODATA_START, 8, vec![(MM_RODATA_START, 8)]),
-            (
-                MM_RODATA_START + 7,
-                2,
-                vec![(MM_RODATA_START + 7, 1), (MM_RODATA_START + 8, 1)],
-            ),
-            (MM_RODATA_START + 8, 4, vec![(MM_RODATA_START + 8, 4)]),
-        ] {
-            for rev in [false, true] {
-                let iter = MemoryChunkIterator::new(
-                    &memory_mapping,
-                    &[],
-                    AccessType::Load,
-                    vm_addr,
-                    len,
-                    true,
-                )
-                .unwrap();
-                let res = if rev {
-                    expected.reverse();
-                    to_chunk_vec(iter.rev())
-                } else {
-                    to_chunk_vec(iter)
-                };
-
-                assert_eq!(res, expected);
-            }
-        }
-    }
-
-    #[test]
-    fn test_iter_memory_pair_chunks_short() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = vec![0x11; 8];
-        let mem2 = vec![0x22; 4];
-        let memory_mapping = MemoryMapping::new(
-            vec![
-                MemoryRegion::new_readonly(&mem1, MM_RODATA_START),
-                MemoryRegion::new_readonly(&mem2, MM_RODATA_START + 8),
-            ],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        // dst is shorter than src
-        assert_matches!(
-            iter_memory_pair_chunks(
-                AccessType::Load,
-                MM_RODATA_START,
-                AccessType::Load,
-                MM_RODATA_START + 8,
-                8,
-                &[],
-                &memory_mapping,
-                false,
-                true,
-                |_src, _dst, _len| Ok::<_, Error>(0),
-            ).unwrap_err().downcast_ref().unwrap(),
-            EbpfError::AccessViolation(AccessType::Load, addr, 8, "program") if *addr == MM_RODATA_START + 8
-        );
-
-        // src is shorter than dst
-        assert_matches!(
-            iter_memory_pair_chunks(
-                AccessType::Load,
-                MM_RODATA_START + 10,
-                AccessType::Load,
-                MM_RODATA_START + 2,
-                3,
-                &[],
-                &memory_mapping,
-                false,
-                true,
-                |_src, _dst, _len| Ok::<_, Error>(0),
-            ).unwrap_err().downcast_ref().unwrap(),
-            EbpfError::AccessViolation(AccessType::Load, addr, 3, "program") if *addr == MM_RODATA_START + 10
-        );
-    }
-
-    #[test]
-    #[should_panic(expected = "AccessViolation(Store, 4294967296, 4")]
-    fn test_memmove_non_contiguous_readonly() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = vec![0x11; 8];
-        let mem2 = vec![0x22; 4];
-        let memory_mapping = MemoryMapping::new(
-            vec![
-                MemoryRegion::new_readonly(&mem1, MM_RODATA_START),
-                MemoryRegion::new_readonly(&mem2, MM_RODATA_START + 8),
-            ],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        memmove_non_contiguous(
-            MM_RODATA_START,
-            MM_RODATA_START + 8,
-            4,
-            &[],
-            &memory_mapping,
-            true,
-        )
-        .unwrap();
-    }
-
-    #[test_case(&[], (0, 0, 0); "no regions")]
-    #[test_case(&[10], (1, 10, 0); "single region 0 len")]
-    #[test_case(&[10], (0, 5, 5); "single region no overlap")]
-    #[test_case(&[10], (0, 0, 10) ; "single region complete overlap")]
-    #[test_case(&[10], (2, 0, 5); "single region partial overlap start")]
-    #[test_case(&[10], (0, 1, 6); "single region partial overlap middle")]
-    #[test_case(&[10], (2, 5, 5); "single region partial overlap end")]
-    #[test_case(&[3, 5], (0, 5, 2) ; "two regions no overlap, single source region")]
-    #[test_case(&[4, 7], (0, 5, 5) ; "two regions no overlap, multiple source regions")]
-    #[test_case(&[3, 8], (0, 0, 11) ; "two regions complete overlap")]
-    #[test_case(&[2, 9], (3, 0, 5) ; "two regions partial overlap start")]
-    #[test_case(&[3, 9], (1, 2, 5) ; "two regions partial overlap middle")]
-    #[test_case(&[7, 3], (2, 6, 4) ; "two regions partial overlap end")]
-    #[test_case(&[2, 6, 3, 4], (0, 10, 2) ; "many regions no overlap, single source region")]
-    #[test_case(&[2, 1, 2, 5, 6], (2, 10, 4) ; "many regions no overlap, multiple source regions")]
-    #[test_case(&[8, 1, 3, 6], (0, 0, 18) ; "many regions complete overlap")]
-    #[test_case(&[7, 3, 1, 4, 5], (5, 0, 8) ; "many regions overlap start")]
-    #[test_case(&[1, 5, 2, 9, 3], (5, 4, 8) ; "many regions overlap middle")]
-    #[test_case(&[3, 9, 1, 1, 2, 1], (2, 9, 8) ; "many regions overlap end")]
-    fn test_memmove_non_contiguous(
-        regions: &[usize],
-        (src_offset, dst_offset, len): (usize, usize, usize),
-    ) {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let (mem, memory_mapping) = build_memory_mapping(regions, &config);
-
-        // flatten the memory so we can memmove it with ptr::copy
-        let mut expected_memory = flatten_memory(&mem);
-        unsafe {
-            std::ptr::copy(
-                expected_memory.as_ptr().add(src_offset),
-                expected_memory.as_mut_ptr().add(dst_offset),
-                len,
-            )
-        };
-
-        // do our memmove
-        memmove_non_contiguous(
-            MM_RODATA_START + dst_offset as u64,
-            MM_RODATA_START + src_offset as u64,
-            len as u64,
-            &[],
-            &memory_mapping,
-            true,
-        )
-        .unwrap();
-
-        // flatten memory post our memmove
-        let memory = flatten_memory(&mem);
-
-        // compare libc's memmove with ours
-        assert_eq!(expected_memory, memory);
-    }
-
-    #[test]
-    #[should_panic(expected = "AccessViolation(Store, 4294967296, 9")]
-    fn test_memset_non_contiguous_readonly() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mut mem1 = vec![0x11; 8];
-        let mem2 = vec![0x22; 4];
-        let memory_mapping = MemoryMapping::new(
-            vec![
-                MemoryRegion::new_writable(&mut mem1, MM_RODATA_START),
-                MemoryRegion::new_readonly(&mem2, MM_RODATA_START + 8),
-            ],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        assert_eq!(
-            memset_non_contiguous(MM_RODATA_START, 0x33, 9, &[], &memory_mapping, true).unwrap(),
-            0
-        );
-    }
-
-    #[test]
-    fn test_memset_non_contiguous() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = vec![0x11; 1];
-        let mut mem2 = vec![0x22; 2];
-        let mut mem3 = vec![0x33; 3];
-        let mut mem4 = vec![0x44; 4];
-        let memory_mapping = MemoryMapping::new(
-            vec![
-                MemoryRegion::new_readonly(&mem1, MM_RODATA_START),
-                MemoryRegion::new_writable(&mut mem2, MM_RODATA_START + 1),
-                MemoryRegion::new_writable(&mut mem3, MM_RODATA_START + 3),
-                MemoryRegion::new_writable(&mut mem4, MM_RODATA_START + 6),
-            ],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        assert_eq!(
-            memset_non_contiguous(MM_RODATA_START + 1, 0x55, 7, &[], &memory_mapping, true)
-                .unwrap(),
-            0
-        );
-        assert_eq!(&mem1, &[0x11]);
-        assert_eq!(&mem2, &[0x55, 0x55]);
-        assert_eq!(&mem3, &[0x55, 0x55, 0x55]);
-        assert_eq!(&mem4, &[0x55, 0x55, 0x44, 0x44]);
-    }
-
-    #[test]
-    fn test_memcmp_non_contiguous() {
-        let config = Config {
-            aligned_memory_mapping: false,
-            ..Config::default()
-        };
-        let mem1 = b"foo".to_vec();
-        let mem2 = b"barbad".to_vec();
-        let mem3 = b"foobarbad".to_vec();
-        let memory_mapping = MemoryMapping::new(
-            vec![
-                MemoryRegion::new_readonly(&mem1, MM_RODATA_START),
-                MemoryRegion::new_readonly(&mem2, MM_RODATA_START + 3),
-                MemoryRegion::new_readonly(&mem3, MM_RODATA_START + 9),
-            ],
-            &config,
-            SBPFVersion::V3,
-        )
-        .unwrap();
-
-        // non contiguous src
-        assert_eq!(
-            memcmp_non_contiguous(
-                MM_RODATA_START,
-                MM_RODATA_START + 9,
-                9,
-                &[],
-                &memory_mapping,
-                true
-            )
-            .unwrap(),
-            0
-        );
-
-        // non contiguous dst
-        assert_eq!(
-            memcmp_non_contiguous(
-                MM_RODATA_START + 10,
-                MM_RODATA_START + 1,
-                8,
-                &[],
-                &memory_mapping,
-                true
-            )
-            .unwrap(),
-            0
-        );
-
-        // diff
-        assert_eq!(
-            memcmp_non_contiguous(
-                MM_RODATA_START + 1,
-                MM_RODATA_START + 11,
-                5,
-                &[],
-                &memory_mapping,
-                true
-            )
-            .unwrap(),
-            unsafe { memcmp(b"oobar", b"obarb", 5) }
-        );
-    }
-
-    fn build_memory_mapping<'a>(
-        regions: &[usize],
-        config: &'a Config,
-    ) -> (Vec<Vec<u8>>, MemoryMapping<'a>) {
-        let mut regs = vec![];
-        let mut mem = Vec::new();
-        let mut offset = 0;
-        for (i, region_len) in regions.iter().enumerate() {
-            mem.push(
-                (0..*region_len)
-                    .map(|x| (i * 10 + x) as u8)
-                    .collect::<Vec<_>>(),
-            );
-            regs.push(MemoryRegion::new_writable(
-                &mut mem[i],
-                MM_RODATA_START + offset as u64,
-            ));
-            offset += *region_len;
-        }
-
-        let memory_mapping = MemoryMapping::new(regs, config, SBPFVersion::V3).unwrap();
-
-        (mem, memory_mapping)
-    }
-
-    fn flatten_memory(mem: &[Vec<u8>]) -> Vec<u8> {
-        mem.iter().flatten().copied().collect()
-    }
+    use super::*;
 
 
     #[test]
     #[test]
     fn test_is_nonoverlapping() {
     fn test_is_nonoverlapping() {

+ 53 - 7
programs/bpf_loader/src/syscalls/mod.rs

@@ -581,10 +581,10 @@ fn address_is_aligned<T>(address: u64) -> bool {
 // Do not use this directly
 // Do not use this directly
 #[macro_export]
 #[macro_export]
 macro_rules! translate_inner {
 macro_rules! translate_inner {
-    ($memory_mapping:expr, $access_type:expr, $vm_addr:expr, $len:expr $(,)?) => {
+    ($memory_mapping:expr, $map:ident, $access_type:expr, $vm_addr:expr, $len:expr $(,)?) => {
         Result::<u64, Error>::from(
         Result::<u64, Error>::from(
             $memory_mapping
             $memory_mapping
-                .map($access_type, $vm_addr, $len)
+                .$map($access_type, $vm_addr, $len)
                 .map_err(|err| err.into()),
                 .map_err(|err| err.into()),
         )
         )
     };
     };
@@ -595,6 +595,7 @@ macro_rules! translate_type_inner {
     ($memory_mapping:expr, $access_type:expr, $vm_addr:expr, $T:ty, $check_aligned:expr $(,)?) => {{
     ($memory_mapping:expr, $access_type:expr, $vm_addr:expr, $T:ty, $check_aligned:expr $(,)?) => {{
         let host_addr = translate_inner!(
         let host_addr = translate_inner!(
             $memory_mapping,
             $memory_mapping,
+            map,
             $access_type,
             $access_type,
             $vm_addr,
             $vm_addr,
             size_of::<$T>() as u64
             size_of::<$T>() as u64
@@ -619,7 +620,7 @@ macro_rules! translate_slice_inner {
         if isize::try_from(total_size).is_err() {
         if isize::try_from(total_size).is_err() {
             return Err(SyscallError::InvalidLength.into());
             return Err(SyscallError::InvalidLength.into());
         }
         }
-        let host_addr = translate_inner!($memory_mapping, $access_type, $vm_addr, total_size)?;
+        let host_addr = translate_inner!($memory_mapping, map, $access_type, $vm_addr, total_size)?;
         if $check_aligned && !address_is_aligned::<$T>(host_addr) {
         if $check_aligned && !address_is_aligned::<$T>(host_addr) {
             return Err(SyscallError::UnalignedPointer.into());
             return Err(SyscallError::UnalignedPointer.into());
         }
         }
@@ -693,12 +694,52 @@ fn translate_slice_mut<'a, T>(
     )
     )
 }
 }
 
 
-// Safety: This will invalidate previously translated references.
-// No other translated references shall be live when calling this.
+fn touch_type_mut<T>(memory_mapping: &mut MemoryMapping, vm_addr: u64) -> Result<(), Error> {
+    translate_inner!(
+        memory_mapping,
+        map_with_access_violation_handler,
+        AccessType::Store,
+        vm_addr,
+        size_of::<T>() as u64,
+    )
+    .map(|_| ())
+}
+fn touch_slice_mut<T>(
+    memory_mapping: &mut MemoryMapping,
+    vm_addr: u64,
+    element_count: u64,
+) -> Result<(), Error> {
+    if element_count == 0 {
+        return Ok(());
+    }
+    translate_inner!(
+        memory_mapping,
+        map_with_access_violation_handler,
+        AccessType::Store,
+        vm_addr,
+        element_count.saturating_mul(size_of::<T>() as u64),
+    )
+    .map(|_| ())
+}
+
+// No other translated references can be live when calling this.
 // Meaning it should generally be at the beginning or end of a syscall and
 // Meaning it should generally be at the beginning or end of a syscall and
 // it should only be called once with all translations passed in one call.
 // it should only be called once with all translations passed in one call.
 #[macro_export]
 #[macro_export]
 macro_rules! translate_mut {
 macro_rules! translate_mut {
+    (internal, $memory_mapping:expr, &mut [$T:ty], $vm_addr_and_element_count:expr) => {
+        touch_slice_mut::<$T>(
+            $memory_mapping,
+            $vm_addr_and_element_count.0,
+            $vm_addr_and_element_count.1,
+        )?
+    };
+    (internal, $memory_mapping:expr, &mut $T:ty, $vm_addr:expr) => {
+        touch_type_mut::<$T>(
+            $memory_mapping,
+            $vm_addr,
+        )?
+    };
     (internal, $memory_mapping:expr, $check_aligned:expr, &mut [$T:ty], $vm_addr_and_element_count:expr) => {{
     (internal, $memory_mapping:expr, $check_aligned:expr, &mut [$T:ty], $vm_addr_and_element_count:expr) => {{
         let slice = translate_slice_mut::<$T>(
         let slice = translate_slice_mut::<$T>(
             $memory_mapping,
             $memory_mapping,
@@ -722,6 +763,7 @@ macro_rules! translate_mut {
         // This ensures that all the parameters are collected first so that if they depend on previous translations
         // This ensures that all the parameters are collected first so that if they depend on previous translations
         $(let $binding = ($vm_addr $(, $element_count)?);)+
         $(let $binding = ($vm_addr $(, $element_count)?);)+
         // they are not invalidated by the following translations here:
         // they are not invalidated by the following translations here:
+        $(translate_mut!(internal, $memory_mapping, &mut $T, $binding);)+
         $(let $binding = translate_mut!(internal, $memory_mapping, $check_aligned, &mut $T, $binding);)+
         $(let $binding = translate_mut!(internal, $memory_mapping, $check_aligned, &mut $T, $binding);)+
         let host_ranges = [
         let host_ranges = [
             $(($binding.1, $binding.2),)+
             $(($binding.1, $binding.2),)+
@@ -2236,11 +2278,15 @@ mod tests {
         for (ok, start, length, value) in cases {
         for (ok, start, length, value) in cases {
             if ok {
             if ok {
                 assert_eq!(
                 assert_eq!(
-                    translate_inner!(&memory_mapping, AccessType::Load, start, length).unwrap(),
+                    translate_inner!(&memory_mapping, map, AccessType::Load, start, length)
+                        .unwrap(),
                     value
                     value
                 )
                 )
             } else {
             } else {
-                assert!(translate_inner!(&memory_mapping, AccessType::Load, start, length).is_err())
+                assert!(
+                    translate_inner!(&memory_mapping, map, AccessType::Load, start, length)
+                        .is_err()
+                )
             }
             }
         }
         }
     }
     }

+ 3 - 3
programs/sbf/Cargo.lock

@@ -5692,7 +5692,6 @@ dependencies = [
  "libsecp256k1 0.6.0",
  "libsecp256k1 0.6.0",
  "num-traits",
  "num-traits",
  "qualifier_attr",
  "qualifier_attr",
- "scopeguard",
  "solana-account",
  "solana-account",
  "solana-account-info",
  "solana-account-info",
  "solana-big-mod-exp",
  "solana-big-mod-exp",
@@ -8691,9 +8690,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "solana-sbpf"
 name = "solana-sbpf"
-version = "0.11.2"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "642335ab08889cd963790faeaa7824e320a5ada4b6eb93ebfc842fa03ddef425"
+checksum = "3c7a3d3cff34df928b804917bf111d3ede779af406703580cd7ed8fb239f5acf"
 dependencies = [
 dependencies = [
  "byteorder 1.5.0",
  "byteorder 1.5.0",
  "combine 3.8.1",
  "combine 3.8.1",
@@ -9482,6 +9481,7 @@ dependencies = [
  "solana-instructions-sysvar",
  "solana-instructions-sysvar",
  "solana-pubkey",
  "solana-pubkey",
  "solana-rent",
  "solana-rent",
+ "solana-sbpf",
  "solana-sdk-ids",
  "solana-sdk-ids",
 ]
 ]
 
 

+ 1 - 1
programs/sbf/Cargo.toml

@@ -151,7 +151,7 @@ solana-sbf-rust-mem-dep = { path = "rust/mem_dep", version = "=3.0.0" }
 solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=3.0.0" }
 solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=3.0.0" }
 solana-sbf-rust-realloc-dep = { path = "rust/realloc_dep", version = "=3.0.0" }
 solana-sbf-rust-realloc-dep = { path = "rust/realloc_dep", version = "=3.0.0" }
 solana-sbf-rust-realloc-invoke-dep = { path = "rust/realloc_invoke_dep", version = "=3.0.0" }
 solana-sbf-rust-realloc-invoke-dep = { path = "rust/realloc_invoke_dep", version = "=3.0.0" }
-solana-sbpf = "=0.11.2"
+solana-sbpf = "=0.12.0"
 solana-sdk-ids = "=2.2.1"
 solana-sdk-ids = "=2.2.1"
 solana-secp256k1-recover = "=2.2.1"
 solana-secp256k1-recover = "=2.2.1"
 solana-sha256-hasher = { version = "=2.2.1", features = ["sha2"] }
 solana-sha256-hasher = { version = "=2.2.1", features = ["sha2"] }

+ 5 - 10
programs/sbf/benches/bpf_loader.rs

@@ -332,24 +332,19 @@ fn clone_regions(regions: &[MemoryRegion]) -> Vec<MemoryRegion> {
         regions
         regions
             .iter()
             .iter()
             .map(|region| {
             .map(|region| {
-                let mut new_region = if region.writable.get() {
+                let mut new_region = if region.writable {
                     MemoryRegion::new_writable(
                     MemoryRegion::new_writable(
-                        slice::from_raw_parts_mut(
-                            region.host_addr.get() as *mut _,
-                            region.len as usize,
-                        ),
+                        slice::from_raw_parts_mut(region.host_addr as *mut _, region.len as usize),
                         region.vm_addr,
                         region.vm_addr,
                     )
                     )
                 } else {
                 } else {
                     MemoryRegion::new_readonly(
                     MemoryRegion::new_readonly(
-                        slice::from_raw_parts(
-                            region.host_addr.get() as *const _,
-                            region.len as usize,
-                        ),
+                        slice::from_raw_parts(region.host_addr as *const _, region.len as usize),
                         region.vm_addr,
                         region.vm_addr,
                     )
                     )
                 };
                 };
-                new_region.cow_callback_payload = region.cow_callback_payload;
+                new_region.access_violation_handler_payload =
+                    region.access_violation_handler_payload;
                 new_region
                 new_region
             })
             })
             .collect()
             .collect()

+ 1 - 1
programs/sbf/c/src/float/float.c

@@ -15,7 +15,7 @@ extern uint64_t entrypoint(const uint8_t *input) {
     return ERROR_INVALID_ARGUMENT;
     return ERROR_INVALID_ARGUMENT;
   }
   }
   /* test float conversion to int compiles and works */
   /* test float conversion to int compiles and works */
-  uint32_t data = *(uint32_t *)(params.ka[0].data);
+  uint32_t data = 0;
   uint32_t new_data = data + 1;
   uint32_t new_data = data + 1;
   data += 1.5;
   data += 1.5;
   sol_assert(data == new_data);
   sol_assert(data == new_data);

+ 24 - 18
programs/sbf/rust/deprecated_loader/src/lib.rs

@@ -144,7 +144,8 @@ fn process_instruction(
             let realloc_program_id = accounts[REALLOC_PROGRAM_INDEX].key;
             let realloc_program_id = accounts[REALLOC_PROGRAM_INDEX].key;
             let realloc_program_owner = accounts[REALLOC_PROGRAM_INDEX].owner;
             let realloc_program_owner = accounts[REALLOC_PROGRAM_INDEX].owner;
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
-            let new_len = usize::from_le_bytes(instruction_data[1..9].try_into().unwrap());
+            let direct_mapping = instruction_data[1];
+            let new_len = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap());
             let prev_len = account.data_len();
             let prev_len = account.data_len();
             let expected = account.data.borrow()[..new_len].to_vec();
             let expected = account.data.borrow()[..new_len].to_vec();
             let mut instruction_data = vec![REALLOC, 0];
             let mut instruction_data = vec![REALLOC, 0];
@@ -165,7 +166,9 @@ fn process_instruction(
 
 
             // deserialize_parameters_unaligned predates realloc support, and
             // deserialize_parameters_unaligned predates realloc support, and
             // hardcodes the account data length to the original length.
             // hardcodes the account data length to the original length.
-            if !solana_sdk_ids::bpf_loader_deprecated::check_id(realloc_program_owner) {
+            if !solana_program::bpf_loader_deprecated::check_id(realloc_program_owner)
+                && direct_mapping == 0
+            {
                 assert_eq!(&*account.data.borrow(), &expected);
                 assert_eq!(&*account.data.borrow(), &expected);
                 assert_eq!(
                 assert_eq!(
                     unsafe {
                     unsafe {
@@ -187,7 +190,7 @@ fn process_instruction(
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
 
 
             let prev_data = {
             let prev_data = {
-                let data = &instruction_data[9..];
+                let data = &instruction_data[10..];
                 let prev_len = account.data_len();
                 let prev_len = account.data_len();
                 account.resize(prev_len + data.len())?;
                 account.resize(prev_len + data.len())?;
                 account.data.borrow_mut()[prev_len..].copy_from_slice(data);
                 account.data.borrow_mut()[prev_len..].copy_from_slice(data);
@@ -205,8 +208,9 @@ fn process_instruction(
             };
             };
 
 
             let mut expected = account.data.borrow().to_vec();
             let mut expected = account.data.borrow().to_vec();
-            let new_len = usize::from_le_bytes(instruction_data[1..9].try_into().unwrap());
-            expected.extend_from_slice(&instruction_data[9..]);
+            let direct_mapping = instruction_data[1];
+            let new_len = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap());
+            expected.extend_from_slice(&instruction_data[10..]);
             let mut instruction_data =
             let mut instruction_data =
                 vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED];
                 vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED];
             instruction_data.extend_from_slice(&new_len.to_le_bytes());
             instruction_data.extend_from_slice(&new_len.to_le_bytes());
@@ -224,19 +228,21 @@ fn process_instruction(
             .unwrap();
             .unwrap();
 
 
             assert_eq!(*account.data.borrow(), &prev_data[..new_len]);
             assert_eq!(*account.data.borrow(), &prev_data[..new_len]);
-            assert_eq!(
-                unsafe {
-                    std::slice::from_raw_parts(
-                        account.data.borrow().as_ptr().add(new_len),
-                        prev_data.len() - new_len,
-                    )
-                },
-                &vec![0; prev_data.len() - new_len]
-            );
-            assert_eq!(
-                unsafe { *account.data.borrow().as_ptr().add(prev_data.len()) },
-                SENTINEL
-            );
+            if direct_mapping == 0 {
+                assert_eq!(
+                    unsafe {
+                        std::slice::from_raw_parts(
+                            account.data.borrow().as_ptr().add(new_len),
+                            prev_data.len() - new_len,
+                        )
+                    },
+                    &vec![0; prev_data.len() - new_len]
+                );
+                assert_eq!(
+                    unsafe { *account.data.borrow().as_ptr().add(prev_data.len()) },
+                    SENTINEL
+                );
+            }
         }
         }
         Some(&TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED) => {
         Some(&TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED) => {
             msg!("DEPRECATED LOADER TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED");
             msg!("DEPRECATED LOADER TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED");

+ 31 - 136
programs/sbf/rust/invoke/src/lib.rs

@@ -1024,7 +1024,8 @@ fn process_instruction<'a>(
             let realloc_program_id = accounts[REALLOC_PROGRAM_INDEX].key;
             let realloc_program_id = accounts[REALLOC_PROGRAM_INDEX].key;
             let realloc_program_owner = accounts[REALLOC_PROGRAM_INDEX].owner;
             let realloc_program_owner = accounts[REALLOC_PROGRAM_INDEX].owner;
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
-            let new_len = usize::from_le_bytes(instruction_data[1..9].try_into().unwrap());
+            let direct_mapping = instruction_data[1];
+            let new_len = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap());
             let prev_len = account.data_len();
             let prev_len = account.data_len();
             let expected = account.data.borrow()[..new_len].to_vec();
             let expected = account.data.borrow()[..new_len].to_vec();
             let mut instruction_data = vec![REALLOC, 0];
             let mut instruction_data = vec![REALLOC, 0];
@@ -1045,7 +1046,7 @@ fn process_instruction<'a>(
 
 
             // deserialize_parameters_unaligned predates realloc support, and
             // deserialize_parameters_unaligned predates realloc support, and
             // hardcodes the account data length to the original length.
             // hardcodes the account data length to the original length.
-            if !bpf_loader_deprecated::check_id(realloc_program_owner) {
+            if !bpf_loader_deprecated::check_id(realloc_program_owner) && direct_mapping == 0 {
                 assert_eq!(&*account.data.borrow(), &expected);
                 assert_eq!(&*account.data.borrow(), &expected);
                 assert_eq!(
                 assert_eq!(
                     unsafe {
                     unsafe {
@@ -1066,7 +1067,7 @@ fn process_instruction<'a>(
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
             let invoke_program_id = accounts[INVOKE_PROGRAM_INDEX].key;
 
 
             let prev_data = {
             let prev_data = {
-                let data = &instruction_data[9..];
+                let data = &instruction_data[10..];
                 let prev_len = account.data_len();
                 let prev_len = account.data_len();
                 account.resize(prev_len + data.len())?;
                 account.resize(prev_len + data.len())?;
                 account.data.borrow_mut()[prev_len..].copy_from_slice(data);
                 account.data.borrow_mut()[prev_len..].copy_from_slice(data);
@@ -1084,8 +1085,9 @@ fn process_instruction<'a>(
             };
             };
 
 
             let mut expected = account.data.borrow().to_vec();
             let mut expected = account.data.borrow().to_vec();
-            let new_len = usize::from_le_bytes(instruction_data[1..9].try_into().unwrap());
-            expected.extend_from_slice(&instruction_data[9..]);
+            let direct_mapping = instruction_data[1];
+            let new_len = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap());
+            expected.extend_from_slice(&instruction_data[10..]);
             let mut instruction_data =
             let mut instruction_data =
                 vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED];
                 vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED];
             instruction_data.extend_from_slice(&new_len.to_le_bytes());
             instruction_data.extend_from_slice(&new_len.to_le_bytes());
@@ -1103,19 +1105,21 @@ fn process_instruction<'a>(
             .unwrap();
             .unwrap();
 
 
             assert_eq!(*account.data.borrow(), &prev_data[..new_len]);
             assert_eq!(*account.data.borrow(), &prev_data[..new_len]);
-            assert_eq!(
-                unsafe {
-                    slice::from_raw_parts(
-                        account.data.borrow().as_ptr().add(new_len),
-                        prev_data.len() - new_len,
-                    )
-                },
-                &vec![0; prev_data.len() - new_len]
-            );
-            assert_eq!(
-                unsafe { *account.data.borrow().as_ptr().add(prev_data.len()) },
-                SENTINEL
-            );
+            if direct_mapping == 0 {
+                assert_eq!(
+                    unsafe {
+                        slice::from_raw_parts(
+                            account.data.borrow().as_ptr().add(new_len),
+                            prev_data.len() - new_len,
+                        )
+                    },
+                    &vec![0; prev_data.len() - new_len]
+                );
+                assert_eq!(
+                    unsafe { *account.data.borrow().as_ptr().add(prev_data.len()) },
+                    SENTINEL
+                );
+            }
         }
         }
         TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED => {
         TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED => {
             msg!("TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED");
             msg!("TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS_NESTED");
@@ -1227,124 +1231,15 @@ fn process_instruction<'a>(
             )
             )
             .unwrap();
             .unwrap();
         }
         }
-        TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION => {
-            msg!("TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION");
-            const CALLEE_PROGRAM_INDEX: usize = 2;
-            let account = &accounts[ARGUMENT_INDEX];
-            let callee_program_id = accounts[CALLEE_PROGRAM_INDEX].key;
-            let original_data_len = account.data_len();
-
-            // Initial data is all [0xFF; 20]
-            assert_eq!(&*account.data.borrow(), &[0xFF; 20]);
-
-            // Realloc to [0xFE; 10]
-            invoke(
-                &create_instruction(
-                    *callee_program_id,
-                    &[
-                        (account.key, true, false),
-                        (callee_program_id, false, false),
-                    ],
-                    vec![0xFE; 10],
-                ),
-                accounts,
-            )
-            .unwrap();
-
-            // Check that [10..20] is zeroed
-            let new_len = account.data_len();
-            assert_eq!(&*account.data.borrow(), &[0xFE; 10]);
-            assert_eq!(
-                unsafe {
-                    slice::from_raw_parts(
-                        account.data.borrow().as_ptr().add(new_len),
-                        original_data_len - new_len,
-                    )
-                },
-                &vec![0; original_data_len - new_len]
-            );
-
-            // Realloc to [0xFD; 5]
-            invoke(
-                &create_instruction(
-                    *callee_program_id,
-                    &[
-                        (accounts[ARGUMENT_INDEX].key, true, false),
-                        (callee_program_id, false, false),
-                    ],
-                    vec![0xFD; 5],
-                ),
-                accounts,
-            )
-            .unwrap();
-
-            // Check that [5..20] is zeroed
-            let new_len = account.data_len();
-            assert_eq!(&*account.data.borrow(), &[0xFD; 5]);
-            assert_eq!(
-                unsafe {
-                    slice::from_raw_parts(
-                        account.data.borrow().as_ptr().add(new_len),
-                        original_data_len - new_len,
-                    )
-                },
-                &vec![0; original_data_len - new_len]
-            );
-
-            // Realloc to [0xFC; 2]
-            invoke(
-                &create_instruction(
-                    *callee_program_id,
-                    &[
-                        (accounts[ARGUMENT_INDEX].key, true, false),
-                        (callee_program_id, false, false),
-                    ],
-                    vec![0xFC; 2],
-                ),
-                accounts,
-            )
-            .unwrap();
-
-            // Check that [2..20] is zeroed
-            let new_len = account.data_len();
-            assert_eq!(&*account.data.borrow(), &[0xFC; 2]);
-            assert_eq!(
-                unsafe {
-                    slice::from_raw_parts(
-                        account.data.borrow().as_ptr().add(new_len),
-                        original_data_len - new_len,
-                    )
-                },
-                &vec![0; original_data_len - new_len]
-            );
-
-            // Realloc to [0xFC; 2]. Here we keep the same length, but realloc the underlying
-            // vector. CPI must zero even if the length is unchanged.
-            invoke(
-                &create_instruction(
-                    *callee_program_id,
-                    &[
-                        (accounts[ARGUMENT_INDEX].key, true, false),
-                        (callee_program_id, false, false),
-                    ],
-                    vec![0xFC; 2],
-                ),
-                accounts,
-            )
-            .unwrap();
-
-            // Check that [2..20] is zeroed
-            let new_len = account.data_len();
-            assert_eq!(&*account.data.borrow(), &[0xFC; 2]);
-            assert_eq!(
-                unsafe {
-                    slice::from_raw_parts(
-                        account.data.borrow().as_ptr().add(new_len),
-                        original_data_len - new_len,
-                    )
-                },
-                &vec![0; original_data_len - new_len]
-            );
+        TEST_READ_ACCOUNT => {
+            msg!("TEST_READ_ACCOUNT");
+            let account_index = instruction_data[1] as usize;
+            let account = &accounts[account_index];
+            let byte_index = usize::from_le_bytes(instruction_data[2..10].try_into().unwrap());
+            let data = unsafe {
+                *(account.data.borrow().get_unchecked(byte_index) as *const u8).cast::<u64>()
+            };
+            assert_eq!(data, 0);
         }
         }
         TEST_WRITE_ACCOUNT => {
         TEST_WRITE_ACCOUNT => {
             msg!("TEST_WRITE_ACCOUNT");
             msg!("TEST_WRITE_ACCOUNT");

+ 1 - 1
programs/sbf/rust/invoke_dep/src/lib.rs

@@ -38,7 +38,7 @@ pub const TEST_CPI_INVALID_KEY_POINTER: u8 = 35;
 pub const TEST_CPI_INVALID_OWNER_POINTER: u8 = 36;
 pub const TEST_CPI_INVALID_OWNER_POINTER: u8 = 36;
 pub const TEST_CPI_INVALID_LAMPORTS_POINTER: u8 = 37;
 pub const TEST_CPI_INVALID_LAMPORTS_POINTER: u8 = 37;
 pub const TEST_CPI_INVALID_DATA_POINTER: u8 = 38;
 pub const TEST_CPI_INVALID_DATA_POINTER: u8 = 38;
-pub const TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION: u8 = 39;
+pub const TEST_READ_ACCOUNT: u8 = 39;
 pub const TEST_WRITE_ACCOUNT: u8 = 40;
 pub const TEST_WRITE_ACCOUNT: u8 = 40;
 pub const TEST_CALLEE_ACCOUNT_UPDATES: u8 = 41;
 pub const TEST_CALLEE_ACCOUNT_UPDATES: u8 = 41;
 pub const TEST_STACK_HEAP_ZEROED: u8 = 42;
 pub const TEST_STACK_HEAP_ZEROED: u8 = 42;

+ 94 - 96
programs/sbf/tests/programs.rs

@@ -3679,7 +3679,7 @@ fn test_cpi_account_ownership_writability() {
                         result.unwrap_err().unwrap(),
                         result.unwrap_err().unwrap(),
                         TransactionError::InstructionError(
                         TransactionError::InstructionError(
                             0,
                             0,
-                            InstructionError::ExternalAccountDataModified
+                            InstructionError::ExternalAccountDataModified,
                         )
                         )
                     );
                     );
                 } else {
                 } else {
@@ -3939,8 +3939,10 @@ fn test_cpi_account_data_updates() {
         let mut account = AccountSharedData::new(42, 0, &account_metas[2].pubkey);
         let mut account = AccountSharedData::new(42, 0, &account_metas[2].pubkey);
         account.set_data(b"foobar".to_vec());
         account.set_data(b"foobar".to_vec());
         bank.store_account(&account_keypair.pubkey(), &account);
         bank.store_account(&account_keypair.pubkey(), &account);
-        let mut instruction_data =
-            vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN];
+        let mut instruction_data = vec![
+            TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN,
+            direct_mapping as u8,
+        ];
         instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
         instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
         let instruction = Instruction::new_with_bytes(
         let instruction = Instruction::new_with_bytes(
             account_metas[3].pubkey,
             account_metas[3].pubkey,
@@ -3979,7 +3981,10 @@ fn test_cpi_account_data_updates() {
         let mut account = AccountSharedData::new(42, 0, &account_metas[3].pubkey);
         let mut account = AccountSharedData::new(42, 0, &account_metas[3].pubkey);
         account.set_data(b"foo".to_vec());
         account.set_data(b"foo".to_vec());
         bank.store_account(&account_keypair.pubkey(), &account);
         bank.store_account(&account_keypair.pubkey(), &account);
-        let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS];
+        let mut instruction_data = vec![
+            TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS,
+            direct_mapping as u8,
+        ];
         // realloc to "foobazbad" then shrink to "foobazb"
         // realloc to "foobazbad" then shrink to "foobazb"
         instruction_data.extend_from_slice(7usize.to_le_bytes().as_ref());
         instruction_data.extend_from_slice(7usize.to_le_bytes().as_ref());
         instruction_data.extend_from_slice(b"bazbad");
         instruction_data.extend_from_slice(b"bazbad");
@@ -4013,7 +4018,10 @@ fn test_cpi_account_data_updates() {
         let mut account = AccountSharedData::new(42, 0, &account_metas[3].pubkey);
         let mut account = AccountSharedData::new(42, 0, &account_metas[3].pubkey);
         account.set_data(b"foo".to_vec());
         account.set_data(b"foo".to_vec());
         bank.store_account(&account_keypair.pubkey(), &account);
         bank.store_account(&account_keypair.pubkey(), &account);
-        let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS];
+        let mut instruction_data = vec![
+            TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS,
+            direct_mapping as u8,
+        ];
         // realloc to "foobazbad" then shrink to "f"
         // realloc to "foobazbad" then shrink to "f"
         instruction_data.extend_from_slice(1usize.to_le_bytes().as_ref());
         instruction_data.extend_from_slice(1usize.to_le_bytes().as_ref());
         instruction_data.extend_from_slice(b"bazbad");
         instruction_data.extend_from_slice(b"bazbad");
@@ -4043,89 +4051,6 @@ fn test_cpi_account_data_updates() {
     }
     }
 }
 }
 
 
-#[test]
-#[cfg(feature = "sbf_rust")]
-fn test_cpi_change_account_data_memory_allocation() {
-    use solana_program_runtime::{declare_process_instruction, loaded_programs::ProgramCacheEntry};
-
-    solana_logger::setup();
-
-    let GenesisConfigInfo {
-        genesis_config,
-        mint_keypair,
-        ..
-    } = create_genesis_config(100_123_456_789);
-
-    let bank = Bank::new_for_tests(&genesis_config);
-
-    declare_process_instruction!(MockBuiltin, 42, |invoke_context| {
-        let transaction_context = &invoke_context.transaction_context;
-        let instruction_context = transaction_context.get_current_instruction_context()?;
-        let instruction_data = instruction_context.get_instruction_data();
-        let mut borrowed_account =
-            instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
-
-        // Test changing the account data both in place and by changing the
-        // underlying vector. CPI will have to detect the vector change and
-        // update the corresponding memory region. In all cases CPI will have
-        // to zero the spare bytes correctly.
-        match instruction_data[0] {
-            0xFE => borrowed_account.set_data(instruction_data.to_vec()),
-            0xFD => borrowed_account.set_data_from_slice(instruction_data),
-            0xFC => {
-                // Exercise the update_caller_account capacity check where account len != capacity.
-                let mut data = instruction_data.to_vec();
-                data.reserve_exact(1);
-                borrowed_account.set_data(data)
-            }
-            _ => panic!(),
-        }
-        .unwrap();
-
-        Ok(())
-    });
-
-    let builtin_program_id = Pubkey::new_unique();
-    bank.get_transaction_processor().add_builtin(
-        &bank,
-        builtin_program_id,
-        "test_cpi_change_account_data_memory_allocation_builtin",
-        ProgramCacheEntry::new_builtin(0, 42, MockBuiltin::vm),
-    );
-
-    let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
-    let mut bank_client = BankClient::new_shared(bank);
-    let authority_keypair = Keypair::new();
-
-    let (bank, invoke_program_id) = load_program_of_loader_v4(
-        &mut bank_client,
-        &bank_forks,
-        &mint_keypair,
-        &authority_keypair,
-        "solana_sbf_rust_invoke",
-    );
-
-    let account_keypair = Keypair::new();
-    let mint_pubkey = mint_keypair.pubkey();
-    let account_metas = vec![
-        AccountMeta::new(mint_pubkey, true),
-        AccountMeta::new(account_keypair.pubkey(), false),
-        AccountMeta::new_readonly(builtin_program_id, false),
-        AccountMeta::new_readonly(invoke_program_id, false),
-    ];
-
-    let mut account = AccountSharedData::new(42, 20, &builtin_program_id);
-    account.set_data(vec![0xFF; 20]);
-    bank.store_account(&account_keypair.pubkey(), &account);
-    let mut instruction_data = vec![TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION];
-    instruction_data.extend_from_slice(builtin_program_id.as_ref());
-    let instruction =
-        Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas.clone());
-
-    let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
-    assert!(result.is_ok(), "{result:?}");
-}
-
 #[test]
 #[test]
 #[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
 #[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
 fn test_cpi_invalid_account_info_pointers() {
 fn test_cpi_invalid_account_info_pointers() {
@@ -4317,6 +4242,74 @@ fn test_program_sbf_deplete_cost_meter_with_divide_by_zero() {
     assert_eq!(result.executed_units, u64::from(compute_unit_limit));
     assert_eq!(result.executed_units, u64::from(compute_unit_limit));
 }
 }
 
 
+#[test]
+#[cfg(feature = "sbf_rust")]
+fn test_deny_access_beyond_current_length() {
+    solana_logger::setup();
+
+    let GenesisConfigInfo {
+        genesis_config,
+        mint_keypair,
+        ..
+    } = create_genesis_config(100_123_456_789);
+
+    for direct_mapping in [false, true] {
+        let mut bank = Bank::new_for_tests(&genesis_config);
+        let feature_set = Arc::make_mut(&mut bank.feature_set);
+        // by default test banks have all features enabled, so we only need to
+        // disable when needed
+        if !direct_mapping {
+            feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
+        }
+        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
+        let mut bank_client = BankClient::new_shared(bank);
+        let authority_keypair = Keypair::new();
+
+        let (bank, invoke_program_id) = load_program_of_loader_v4(
+            &mut bank_client,
+            &bank_forks,
+            &mint_keypair,
+            &authority_keypair,
+            "solana_sbf_rust_invoke",
+        );
+        let account = AccountSharedData::new(42, 0, &invoke_program_id);
+        let readonly_account_keypair = Keypair::new();
+        let writable_account_keypair = Keypair::new();
+        bank.store_account(&readonly_account_keypair.pubkey(), &account);
+        bank.store_account(&writable_account_keypair.pubkey(), &account);
+
+        let mint_pubkey = mint_keypair.pubkey();
+        let account_metas = vec![
+            AccountMeta::new(mint_pubkey, true),
+            AccountMeta::new_readonly(readonly_account_keypair.pubkey(), false),
+            AccountMeta::new(writable_account_keypair.pubkey(), false),
+            AccountMeta::new_readonly(invoke_program_id, false),
+        ];
+
+        for (instruction_account_index, expected_error) in [
+            (1, InstructionError::AccountDataTooSmall),
+            (2, InstructionError::InvalidRealloc),
+        ] {
+            let mut instruction_data = vec![TEST_READ_ACCOUNT, instruction_account_index];
+            instruction_data.extend_from_slice(3usize.to_le_bytes().as_ref());
+            let instruction = Instruction::new_with_bytes(
+                invoke_program_id,
+                &instruction_data,
+                account_metas.clone(),
+            );
+            let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
+            if direct_mapping {
+                assert_eq!(
+                    result.unwrap_err().unwrap(),
+                    TransactionError::InstructionError(0, expected_error)
+                );
+            } else {
+                result.unwrap();
+            }
+        }
+    }
+}
+
 #[test]
 #[test]
 #[cfg(feature = "sbf_rust")]
 #[cfg(feature = "sbf_rust")]
 fn test_deny_executable_write() {
 fn test_deny_executable_write() {
@@ -5217,12 +5210,12 @@ fn test_mem_syscalls_overlap_account_begin_or_end() {
                 let message = Message::new(&[instruction], Some(&mint_pubkey));
                 let message = Message::new(&[instruction], Some(&mint_pubkey));
                 let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
                 let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
                 let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx);
                 let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx);
+                let last_line = logs.last().unwrap();
 
 
                 if direct_mapping {
                 if direct_mapping {
-                    assert!(logs.last().unwrap().ends_with(" failed: InvalidLength"));
-                } else if result.is_err() {
-                    // without direct mapping, we should never get the InvalidLength error
-                    assert!(!logs.last().unwrap().ends_with(" failed: InvalidLength"));
+                    assert!(last_line.contains(" failed: Access violation"), "{logs:?}");
+                } else {
+                    assert!(result.is_ok(), "{logs:?}");
                 }
                 }
             }
             }
 
 
@@ -5237,14 +5230,19 @@ fn test_mem_syscalls_overlap_account_begin_or_end() {
                 let message = Message::new(&[instruction], Some(&mint_pubkey));
                 let message = Message::new(&[instruction], Some(&mint_pubkey));
                 let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
                 let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
                 let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx);
                 let (result, _, logs, _) = process_transaction_and_record_inner(&bank, tx);
+                let last_line = logs.last().unwrap();
 
 
-                if direct_mapping && !deprecated {
-                    // we have a resize area
+                if direct_mapping && (!deprecated || instr < 8) {
                     assert!(
                     assert!(
-                        logs.last().unwrap().ends_with(" failed: InvalidLength"),
-                        "{logs:?}"
+                        last_line.contains(" failed: account data too small")
+                            || last_line.contains(" failed: Failed to reallocate account data")
+                            || last_line.contains(" failed: Access violation"),
+                        "{logs:?}",
                     );
                     );
                 } else {
                 } else {
+                    // direct_mapping && deprecated && instr >= 8 succeeds with zero-length accounts
+                    // because there is no MemoryRegion for the account,
+                    // so there can be no error when leaving that non-existent region.
                     assert!(result.is_ok(), "{logs:?}");
                     assert!(result.is_ok(), "{logs:?}");
                 }
                 }
             }
             }

+ 3 - 3
svm/examples/Cargo.lock

@@ -5525,7 +5525,6 @@ dependencies = [
  "libsecp256k1",
  "libsecp256k1",
  "num-traits",
  "num-traits",
  "qualifier_attr",
  "qualifier_attr",
- "scopeguard",
  "solana-account",
  "solana-account",
  "solana-account-info",
  "solana-account-info",
  "solana-big-mod-exp",
  "solana-big-mod-exp",
@@ -7742,9 +7741,9 @@ checksum = "61f1bc1357b8188d9c4a3af3fc55276e56987265eb7ad073ae6f8180ee54cecf"
 
 
 [[package]]
 [[package]]
 name = "solana-sbpf"
 name = "solana-sbpf"
-version = "0.11.2"
+version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "642335ab08889cd963790faeaa7824e320a5ada4b6eb93ebfc842fa03ddef425"
+checksum = "3c7a3d3cff34df928b804917bf111d3ede779af406703580cd7ed8fb239f5acf"
 dependencies = [
 dependencies = [
  "byteorder",
  "byteorder",
  "combine 3.8.1",
  "combine 3.8.1",
@@ -8568,6 +8567,7 @@ dependencies = [
  "solana-instructions-sysvar",
  "solana-instructions-sysvar",
  "solana-pubkey",
  "solana-pubkey",
  "solana-rent",
  "solana-rent",
+ "solana-sbpf",
  "solana-sdk-ids",
  "solana-sdk-ids",
 ]
 ]
 
 

+ 2 - 2
svm/tests/mock_bank.rs

@@ -353,7 +353,7 @@ pub fn create_custom_loader<'a>() -> BuiltinProgram<InvokeContext<'a>> {
         max_call_depth: compute_budget.max_call_depth,
         max_call_depth: compute_budget.max_call_depth,
         stack_frame_size: compute_budget.stack_frame_size,
         stack_frame_size: compute_budget.stack_frame_size,
         enable_address_translation: true,
         enable_address_translation: true,
-        enable_stack_frame_gaps: true,
+        enable_stack_frame_gaps: false,
         instruction_meter_checkpoint_distance: 10000,
         instruction_meter_checkpoint_distance: 10000,
         enable_instruction_meter: true,
         enable_instruction_meter: true,
         enable_instruction_tracing: true,
         enable_instruction_tracing: true,
@@ -363,7 +363,7 @@ pub fn create_custom_loader<'a>() -> BuiltinProgram<InvokeContext<'a>> {
         sanitize_user_provided_values: true,
         sanitize_user_provided_values: true,
         enabled_sbpf_versions: SBPFVersion::V0..=SBPFVersion::V3,
         enabled_sbpf_versions: SBPFVersion::V0..=SBPFVersion::V3,
         optimize_rodata: false,
         optimize_rodata: false,
-        aligned_memory_mapping: true,
+        aligned_memory_mapping: false,
     };
     };
 
 
     // These functions are system calls the compile contract calls during execution, so they
     // These functions are system calls the compile contract calls during execution, so they

+ 1 - 0
transaction-context/Cargo.toml

@@ -26,6 +26,7 @@ solana-account = { workspace = true }
 solana-instruction = { workspace = true, features = ["std"] }
 solana-instruction = { workspace = true, features = ["std"] }
 solana-instructions-sysvar = { workspace = true }
 solana-instructions-sysvar = { workspace = true }
 solana-pubkey = { workspace = true }
 solana-pubkey = { workspace = true }
+solana-sbpf = { workspace = true }
 
 
 [target.'cfg(not(target_os = "solana"))'.dependencies]
 [target.'cfg(not(target_os = "solana"))'.dependencies]
 bincode = { workspace = true, optional = true }
 bincode = { workspace = true, optional = true }

+ 77 - 53
transaction-context/src/lib.rs

@@ -3,12 +3,13 @@
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 #![cfg_attr(docsrs, feature(doc_auto_cfg))]
 
 
 #[cfg(not(target_os = "solana"))]
 #[cfg(not(target_os = "solana"))]
-use {solana_account::WritableAccount, solana_rent::Rent, std::mem::MaybeUninit};
+use {solana_account::WritableAccount, solana_rent::Rent};
 use {
 use {
     solana_account::{AccountSharedData, ReadableAccount},
     solana_account::{AccountSharedData, ReadableAccount},
     solana_instruction::error::InstructionError,
     solana_instruction::error::InstructionError,
     solana_instructions_sysvar as instructions,
     solana_instructions_sysvar as instructions,
     solana_pubkey::Pubkey,
     solana_pubkey::Pubkey,
+    solana_sbpf::memory_region::{AccessType, AccessViolationHandler, MemoryRegion},
     std::{
     std::{
         cell::{Ref, RefCell, RefMut},
         cell::{Ref, RefCell, RefMut},
         collections::HashSet,
         collections::HashSet,
@@ -30,6 +31,9 @@ static_assertions::const_assert_eq!(
 #[cfg(not(target_os = "solana"))]
 #[cfg(not(target_os = "solana"))]
 const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
 const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
     MAX_PERMITTED_DATA_LENGTH as i64 * 2;
     MAX_PERMITTED_DATA_LENGTH as i64 * 2;
+// Note: With direct mapping programs can grow accounts faster than they intend to,
+// because the AccessViolationHandler might grow an account up to
+// MAX_PERMITTED_DATA_LENGTH at once.
 #[cfg(test)]
 #[cfg(test)]
 static_assertions::const_assert_eq!(
 static_assertions::const_assert_eq!(
     MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
     MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
@@ -484,29 +488,79 @@ impl TransactionContext {
     }
     }
 
 
     /// Returns a new account data write access handler
     /// Returns a new account data write access handler
-    pub fn account_data_write_access_handler(&self) -> Box<dyn Fn(u32) -> Result<u64, ()>> {
+    pub fn access_violation_handler(&self) -> AccessViolationHandler {
         let accounts = Rc::clone(&self.accounts);
         let accounts = Rc::clone(&self.accounts);
-        Box::new(move |index_in_transaction| {
-            // The two calls below can't really fail. If they fail because of a bug,
-            // whatever is writing will trigger an EbpfError::AccessViolation like
-            // if the region was readonly, and the transaction will fail gracefully.
-            let mut account = accounts
-                .accounts
-                .get(index_in_transaction as usize)
-                .ok_or(())?
-                .try_borrow_mut()
-                .map_err(|_| ())?;
-            accounts
-                .touch(index_in_transaction as IndexOfAccount)
-                .map_err(|_| ())?;
-
-            if account.is_shared() {
-                // See BorrowedAccount::make_data_mut() as to why we reserve extra
-                // MAX_PERMITTED_DATA_INCREASE bytes here.
-                account.reserve(MAX_PERMITTED_DATA_INCREASE);
-            }
-            Ok(account.data_as_mut_slice().as_mut_ptr() as u64)
-        })
+        Box::new(
+            move |region: &mut MemoryRegion,
+                  address_space_reserved_for_account: u64,
+                  access_type: AccessType,
+                  vm_addr: u64,
+                  len: u64| {
+                if access_type == AccessType::Load {
+                    return;
+                }
+                let Some(index_in_transaction) = region.access_violation_handler_payload else {
+                    // This region is not a writable account.
+                    return;
+                };
+                let requested_length =
+                    vm_addr.saturating_add(len).saturating_sub(region.vm_addr) as usize;
+                if requested_length > address_space_reserved_for_account as usize {
+                    // Requested access goes further than the account region.
+                    return;
+                }
+
+                // The four calls below can't really fail. If they fail because of a bug,
+                // whatever is writing will trigger an EbpfError::AccessViolation like
+                // if the region was readonly, and the transaction will fail gracefully.
+                let Some(account) = accounts.accounts.get(index_in_transaction as usize) else {
+                    debug_assert!(false);
+                    return;
+                };
+                let Ok(mut account) = account.try_borrow_mut() else {
+                    debug_assert!(false);
+                    return;
+                };
+                if accounts.touch(index_in_transaction).is_err() {
+                    debug_assert!(false);
+                    return;
+                }
+                let Ok(remaining_allowed_growth) =
+                    accounts.resize_delta.try_borrow().map(|resize_delta| {
+                        MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
+                            .saturating_sub(*resize_delta)
+                            .max(0) as usize
+                    })
+                else {
+                    debug_assert!(false);
+                    return;
+                };
+
+                if requested_length > region.len as usize {
+                    // Realloc immediately here to fit the requested access,
+                    // then later in CPI or deserialization realloc again to the
+                    // account length the program stored in AccountInfo.
+                    let old_len = account.data().len();
+                    let new_len = (address_space_reserved_for_account as usize)
+                        .min(MAX_PERMITTED_DATA_LENGTH as usize)
+                        .min(old_len.saturating_add(remaining_allowed_growth));
+                    // The last two min operations ensure the following:
+                    debug_assert!(accounts.can_data_be_resized(old_len, new_len).is_ok());
+                    if accounts
+                        .update_accounts_resize_delta(old_len, new_len)
+                        .is_err()
+                    {
+                        return;
+                    }
+                    account.resize(new_len, 0);
+                    region.len = new_len as u64;
+                }
+
+                // Potentially unshare / make the account shared data unique (CoW logic).
+                region.host_addr = account.data_as_mut_slice().as_mut_ptr() as u64;
+                region.writable = true;
+            },
+        )
     }
     }
 }
 }
 
 
@@ -905,16 +959,6 @@ impl BorrowedAccount<'_> {
         Ok(self.account.data_as_mut_slice())
         Ok(self.account.data_as_mut_slice())
     }
     }
 
 
-    /// Returns the spare capacity of the vector backing the account data.
-    ///
-    /// This method should only ever be used during CPI, where after a shrinking
-    /// realloc we want to zero the spare capacity.
-    #[cfg(not(target_os = "solana"))]
-    pub fn spare_data_capacity_mut(&mut self) -> Result<&mut [MaybeUninit<u8>], InstructionError> {
-        debug_assert!(!self.account.is_shared());
-        Ok(self.account.spare_data_capacity_mut())
-    }
-
     /// Overwrites the account data and size (transaction wide).
     /// Overwrites the account data and size (transaction wide).
     ///
     ///
     /// You should always prefer set_data_from_slice(). Calling this method is
     /// You should always prefer set_data_from_slice(). Calling this method is
@@ -987,26 +1031,6 @@ impl BorrowedAccount<'_> {
         Ok(())
         Ok(())
     }
     }
 
 
-    /// Reserves capacity for at least additional more elements to be inserted
-    /// in the given account. Does nothing if capacity is already sufficient.
-    #[cfg(not(target_os = "solana"))]
-    pub fn reserve(&mut self, additional: usize) -> Result<(), InstructionError> {
-        // Note that we don't need to call can_data_be_changed() here nor
-        // touch() the account. reserve() only changes the capacity of the
-        // memory that holds the account but it doesn't actually change content
-        // nor length of the account.
-        self.make_data_mut();
-        self.account.reserve(additional);
-
-        Ok(())
-    }
-
-    /// Returns the number of bytes the account can hold without reallocating.
-    #[cfg(not(target_os = "solana"))]
-    pub fn capacity(&self) -> usize {
-        self.account.capacity()
-    }
-
     /// Returns whether the underlying AccountSharedData is shared.
     /// Returns whether the underlying AccountSharedData is shared.
     ///
     ///
     /// The data is shared if the account has been loaded from the accounts database and has never
     /// The data is shared if the account has been loaded from the accounts database and has never

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است