Skip to main content

solana_program_runtime/
serialization.rs

1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4    crate::memory_context::SerializedAccountMetadata,
5    solana_instruction::error::InstructionError,
6    solana_program_entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, NON_DUP_MARKER},
7    solana_pubkey::Pubkey,
8    solana_sbpf::{
9        aligned_memory::{AlignedMemory, Pod},
10        ebpf::{HOST_ALIGN, MM_INPUT_START},
11        memory_region::MemoryRegion,
12    },
13    solana_sdk_ids::bpf_loader_deprecated,
14    solana_system_interface::MAX_PERMITTED_DATA_LENGTH,
15    solana_transaction_context::{
16        IndexOfAccount, MAX_ACCOUNTS_PER_INSTRUCTION, instruction::InstructionContext,
17        instruction_accounts::BorrowedInstructionAccount,
18    },
19    std::mem::{self, size_of},
20};
21
22/// Modifies the memory mapping in serialization and CPI return for virtual_address_space_adjustments
23pub fn modify_memory_region_of_account(
24    account: &mut BorrowedInstructionAccount<'_, '_>,
25    region: &mut MemoryRegion,
26) {
27    if account.can_data_be_changed().is_ok() {
28        unsafe {
29            // SAFETY:
30            // Contract from `HostBuffer::mutable`: This host buffer must have been initially
31            // constructed with a mutable pointer.
32            // Evidence: this region must be pointing to a serialized data buffer which is known to
33            // be mutable.
34            region.redirect(region.host_buffer().mutable());
35        }
36        region.access_violation_handler_payload = Some(account.get_index_in_transaction());
37    } else {
38        region.make_immutable();
39        region.access_violation_handler_payload = None;
40    }
41}
42
43/// Creates the memory mapping in serialization and CPI return for account_data_direct_mapping
44pub fn create_memory_region_of_account(
45    account: &mut BorrowedInstructionAccount<'_, '_>,
46    vaddr: u64,
47) -> Result<MemoryRegion, InstructionError> {
48    let can_data_be_changed = account.can_data_be_changed().is_ok();
49    let mut memory_region = if can_data_be_changed && !account.is_shared() {
50        MemoryRegion::new(&raw mut account.get_data_mut()?[..], vaddr)
51    } else {
52        MemoryRegion::new(&raw const account.get_data()[..], vaddr)
53    };
54    if can_data_be_changed {
55        memory_region.access_violation_handler_payload = Some(account.get_index_in_transaction());
56    }
57    Ok(memory_region)
58}
59
60#[expect(dead_code)]
61enum SerializeAccount<'a, 'ix_data> {
62    Account(IndexOfAccount, BorrowedInstructionAccount<'a, 'ix_data>),
63    Duplicate(IndexOfAccount),
64}
65
66struct Serializer {
67    buffer: AlignedMemory<HOST_ALIGN>,
68    regions: Vec<MemoryRegion>,
69    vaddr: u64,
70    region_start: usize,
71    is_loader_v1: bool,
72    virtual_address_space_adjustments: bool,
73    account_data_direct_mapping: bool,
74}
75
76impl Serializer {
77    fn new(
78        size: usize,
79        start_addr: u64,
80        is_loader_v1: bool,
81        virtual_address_space_adjustments: bool,
82        account_data_direct_mapping: bool,
83    ) -> Serializer {
84        Serializer {
85            buffer: AlignedMemory::with_capacity(size),
86            regions: Vec::new(),
87            region_start: 0,
88            vaddr: start_addr,
89            is_loader_v1,
90            virtual_address_space_adjustments,
91            account_data_direct_mapping,
92        }
93    }
94
95    fn fill_write(&mut self, num: usize, value: u8) -> std::io::Result<()> {
96        self.buffer.fill_write(num, value)
97    }
98
99    fn write<T: Pod>(&mut self, value: T) -> u64 {
100        self.debug_assert_alignment::<T>();
101        let vaddr = self
102            .vaddr
103            .saturating_add(self.buffer.len() as u64)
104            .saturating_sub(self.region_start as u64);
105        // Safety:
106        // in serialize_parameters_(aligned|unaligned) first we compute the
107        // required size then we write into the newly allocated buffer. There's
108        // no need to check bounds at every write.
109        //
110        // AlignedMemory::write_unchecked _does_ debug_assert!() that the capacity
111        // is enough, so in the unlikely case we introduce a bug in the size
112        // computation, tests will abort.
113        unsafe {
114            self.buffer.write_unchecked(value);
115        }
116
117        vaddr
118    }
119
120    fn write_all(&mut self, value: &[u8]) -> u64 {
121        let vaddr = self
122            .vaddr
123            .saturating_add(self.buffer.len() as u64)
124            .saturating_sub(self.region_start as u64);
125        // Safety:
126        // see write() - the buffer is guaranteed to be large enough
127        unsafe {
128            self.buffer.write_all_unchecked(value);
129        }
130
131        vaddr
132    }
133
134    fn write_account(
135        &mut self,
136        account: &mut BorrowedInstructionAccount<'_, '_>,
137    ) -> Result<u64, InstructionError> {
138        if !self.virtual_address_space_adjustments {
139            let vm_data_addr = self.vaddr.saturating_add(self.buffer.len() as u64);
140            self.write_all(account.get_data());
141            if !self.is_loader_v1 {
142                let align_offset =
143                    (account.get_data().len() as *const u8).align_offset(BPF_ALIGN_OF_U128);
144                self.fill_write(MAX_PERMITTED_DATA_INCREASE + align_offset, 0)
145                    .map_err(|_| InstructionError::InvalidArgument)?;
146            }
147            Ok(vm_data_addr)
148        } else {
149            self.push_region();
150            let vm_data_addr = self.vaddr;
151            if !self.account_data_direct_mapping {
152                self.write_all(account.get_data());
153                if !self.is_loader_v1 {
154                    self.fill_write(MAX_PERMITTED_DATA_INCREASE, 0)
155                        .map_err(|_| InstructionError::InvalidArgument)?;
156                }
157            }
158            let address_space_reserved_for_account = if !self.is_loader_v1 {
159                account
160                    .get_data()
161                    .len()
162                    .saturating_add(MAX_PERMITTED_DATA_INCREASE)
163            } else {
164                account.get_data().len()
165            };
166            if address_space_reserved_for_account > 0 {
167                if !self.account_data_direct_mapping {
168                    self.push_region();
169                    let region = self.regions.last_mut().unwrap();
170                    modify_memory_region_of_account(account, region);
171                } else {
172                    let new_region = create_memory_region_of_account(account, self.vaddr)?;
173                    self.vaddr += address_space_reserved_for_account as u64;
174                    self.regions.push(new_region);
175                }
176            }
177            if !self.is_loader_v1 {
178                let align_offset =
179                    (account.get_data().len() as *const u8).align_offset(BPF_ALIGN_OF_U128);
180                if !self.account_data_direct_mapping {
181                    self.fill_write(align_offset, 0)
182                        .map_err(|_| InstructionError::InvalidArgument)?;
183                } else {
184                    // The deserialization code is going to align the vm_addr to
185                    // BPF_ALIGN_OF_U128. Always add one BPF_ALIGN_OF_U128 worth of
186                    // padding and shift the start of the next region, so that once
187                    // vm_addr is aligned, the corresponding host_addr is aligned
188                    // too.
189                    self.fill_write(BPF_ALIGN_OF_U128, 0)
190                        .map_err(|_| InstructionError::InvalidArgument)?;
191                    self.region_start += BPF_ALIGN_OF_U128.saturating_sub(align_offset);
192                }
193            }
194            Ok(vm_data_addr)
195        }
196    }
197
198    fn push_region(&mut self) {
199        let range = self.region_start..self.buffer.len();
200        let region_slice = self.buffer.as_slice_mut().get_mut(range.clone()).unwrap();
201        self.regions
202            .push(MemoryRegion::new(&raw mut region_slice[..], self.vaddr));
203        self.region_start = range.end;
204        self.vaddr += range.len() as u64;
205    }
206
207    fn finish(mut self) -> (AlignedMemory<HOST_ALIGN>, Vec<MemoryRegion>) {
208        self.push_region();
209        debug_assert_eq!(self.region_start, self.buffer.len());
210        (self.buffer, self.regions)
211    }
212
213    fn debug_assert_alignment<T>(&self) {
214        debug_assert!(
215            self.is_loader_v1
216                || self
217                    .buffer
218                    .as_slice()
219                    .as_ptr_range()
220                    .end
221                    .align_offset(mem::align_of::<T>())
222                    == 0
223        );
224    }
225}
226
227pub fn serialize_parameters(
228    instruction_context: &InstructionContext,
229    virtual_address_space_adjustments: bool,
230    account_data_direct_mapping: bool,
231    direct_account_pointers_in_program_input: bool,
232) -> Result<
233    (
234        AlignedMemory<HOST_ALIGN>,
235        Vec<MemoryRegion>,
236        Vec<SerializedAccountMetadata>,
237        usize,
238    ),
239    InstructionError,
240> {
241    let num_ix_accounts = instruction_context.get_number_of_instruction_accounts();
242    if num_ix_accounts > MAX_ACCOUNTS_PER_INSTRUCTION as IndexOfAccount {
243        return Err(InstructionError::MaxAccountsExceeded);
244    }
245
246    let program_id = *instruction_context.get_program_key()?;
247    let is_loader_deprecated =
248        instruction_context.get_program_owner()? == bpf_loader_deprecated::id();
249
250    let accounts = (0..instruction_context.get_number_of_instruction_accounts())
251        .map(|instruction_account_index| {
252            if let Some(index) = instruction_context
253                .is_instruction_account_duplicate(instruction_account_index)
254                .unwrap()
255            {
256                SerializeAccount::Duplicate(index)
257            } else {
258                let account = instruction_context
259                    .try_borrow_instruction_account(instruction_account_index)
260                    .unwrap();
261                SerializeAccount::Account(instruction_account_index, account)
262            }
263        })
264        // fun fact: jemalloc is good at caching tiny allocations like this one,
265        // so collecting here is actually faster than passing the iterator
266        // around, since the iterator does the work to produce its items each
267        // time it's iterated on.
268        .collect::<Vec<_>>();
269
270    if is_loader_deprecated {
271        // Used by loader-v1 (bpf_loader_deprecated)
272        serialize_parameters_for_abiv0(
273            accounts,
274            instruction_context.get_instruction_data(),
275            &program_id,
276            virtual_address_space_adjustments,
277            account_data_direct_mapping,
278        )
279    } else {
280        // Used by loader-v2 (bpf_loader) and loader-v3 (bpf_loader_upgradeable)
281        serialize_parameters_for_abiv1(
282            accounts,
283            instruction_context.get_instruction_data(),
284            &program_id,
285            virtual_address_space_adjustments,
286            account_data_direct_mapping,
287            // SIMD-0449: only available on ABIv1
288            direct_account_pointers_in_program_input,
289        )
290    }
291}
292
293pub fn deserialize_parameters(
294    instruction_context: &InstructionContext,
295    virtual_address_space_adjustments: bool,
296    account_data_direct_mapping: bool,
297    buffer: &[u8],
298    accounts_metadata: &[SerializedAccountMetadata],
299) -> Result<(), InstructionError> {
300    let is_loader_deprecated =
301        instruction_context.get_program_owner()? == bpf_loader_deprecated::id();
302    let account_lengths = accounts_metadata.iter().map(|a| a.original_data_len);
303    if is_loader_deprecated {
304        // Used by loader-v1 (bpf_loader_deprecated)
305        deserialize_parameters_for_abiv0(
306            instruction_context,
307            virtual_address_space_adjustments,
308            account_data_direct_mapping,
309            buffer,
310            account_lengths,
311        )
312    } else {
313        // Used by loader-v2 (bpf_loader) and loader-v3 (bpf_loader_upgradeable)
314        deserialize_parameters_for_abiv1(
315            instruction_context,
316            virtual_address_space_adjustments,
317            account_data_direct_mapping,
318            buffer,
319            account_lengths,
320        )
321    }
322}
323
324fn serialize_parameters_for_abiv0(
325    accounts: Vec<SerializeAccount>,
326    instruction_data: &[u8],
327    program_id: &Pubkey,
328    virtual_address_space_adjustments: bool,
329    account_data_direct_mapping: bool,
330) -> Result<
331    (
332        AlignedMemory<HOST_ALIGN>,
333        Vec<MemoryRegion>,
334        Vec<SerializedAccountMetadata>,
335        usize,
336    ),
337    InstructionError,
338> {
339    // Calculate size in order to alloc once
340    let mut size = size_of::<u64>();
341    for account in &accounts {
342        size += 1; // dup
343        match account {
344            SerializeAccount::Duplicate(_) => {}
345            SerializeAccount::Account(_, account) => {
346                size += size_of::<u8>() // is_signer
347                + size_of::<u8>() // is_writable
348                + size_of::<Pubkey>() // key
349                + size_of::<u64>()  // lamports
350                + size_of::<u64>()  // data len
351                + size_of::<Pubkey>() // owner
352                + size_of::<u8>() // executable
353                + size_of::<u64>(); // rent_epoch
354                if !(virtual_address_space_adjustments && account_data_direct_mapping) {
355                    size += account.get_data().len();
356                }
357            }
358        }
359    }
360    size += size_of::<u64>() // instruction data len
361         + instruction_data.len() // instruction data
362         + size_of::<Pubkey>(); // program id
363
364    let mut s = Serializer::new(
365        size,
366        MM_INPUT_START,
367        true,
368        virtual_address_space_adjustments,
369        account_data_direct_mapping,
370    );
371
372    let mut accounts_metadata: Vec<SerializedAccountMetadata> = Vec::with_capacity(accounts.len());
373    s.write::<u64>((accounts.len() as u64).to_le());
374    for account in accounts {
375        match account {
376            SerializeAccount::Duplicate(position) => {
377                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
378                s.write(position as u8);
379            }
380            SerializeAccount::Account(_, mut account) => {
381                let vm_addr = s.write::<u8>(NON_DUP_MARKER);
382                s.write::<u8>(account.is_signer() as u8);
383                s.write::<u8>(account.is_writable() as u8);
384                let vm_key_addr = s.write_all(account.get_key().as_ref());
385                let vm_lamports_addr = s.write::<u64>(account.get_lamports().to_le());
386                s.write::<u64>((account.get_data().len() as u64).to_le());
387                let vm_data_addr = s.write_account(&mut account)?;
388                let vm_owner_addr = s.write_all(account.get_owner().as_ref());
389                #[expect(deprecated)]
390                s.write::<u8>(account.is_executable() as u8);
391                let rent_epoch = u64::MAX;
392                s.write::<u64>(rent_epoch.to_le());
393                accounts_metadata.push(SerializedAccountMetadata {
394                    vm_addr,
395                    original_data_len: account.get_data().len(),
396                    vm_key_addr,
397                    vm_lamports_addr,
398                    vm_owner_addr,
399                    vm_data_addr,
400                });
401            }
402        };
403    }
404    s.write::<u64>((instruction_data.len() as u64).to_le());
405    let instruction_data_offset = s.write_all(instruction_data);
406    s.write_all(program_id.as_ref());
407
408    let (mem, regions) = s.finish();
409    Ok((
410        mem,
411        regions,
412        accounts_metadata,
413        instruction_data_offset as usize,
414    ))
415}
416
417fn deserialize_parameters_for_abiv0<I: IntoIterator<Item = usize>>(
418    instruction_context: &InstructionContext,
419    virtual_address_space_adjustments: bool,
420    account_data_direct_mapping: bool,
421    buffer: &[u8],
422    account_lengths: I,
423) -> Result<(), InstructionError> {
424    let mut start = size_of::<u64>(); // number of accounts
425    for (instruction_account_index, pre_len) in
426        (0..instruction_context.get_number_of_instruction_accounts()).zip(account_lengths)
427    {
428        let duplicate =
429            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
430        start += 1; // is_dup
431        if duplicate.is_none() {
432            let mut borrowed_account =
433                instruction_context.try_borrow_instruction_account(instruction_account_index)?;
434            start += size_of::<u8>(); // is_signer
435            start += size_of::<u8>(); // is_writable
436            start += size_of::<Pubkey>(); // key
437            let lamports = buffer
438                .get(start..start.saturating_add(8))
439                .map(<[u8; 8]>::try_from)
440                .and_then(Result::ok)
441                .map(u64::from_le_bytes)
442                .ok_or(InstructionError::InvalidArgument)?;
443            if borrowed_account.get_lamports() != lamports {
444                borrowed_account.set_lamports(lamports)?;
445            }
446            start += size_of::<u64>() // lamports
447                + size_of::<u64>(); // data length
448            if !virtual_address_space_adjustments {
449                let data = buffer
450                    .get(start..start + pre_len)
451                    .ok_or(InstructionError::InvalidArgument)?;
452                // The redundant check helps to avoid the expensive data comparison if we can
453                match borrowed_account.can_data_be_resized(pre_len) {
454                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
455                    Err(err) if borrowed_account.get_data() != data => return Err(err),
456                    _ => {}
457                }
458            } else if !account_data_direct_mapping && borrowed_account.can_data_be_changed().is_ok()
459            {
460                let data = buffer
461                    .get(start..start + pre_len)
462                    .ok_or(InstructionError::InvalidArgument)?;
463                borrowed_account.set_data_from_slice(data)?;
464            } else if borrowed_account.get_data().len() != pre_len {
465                borrowed_account.set_data_length(pre_len)?;
466            }
467            if !(virtual_address_space_adjustments && account_data_direct_mapping) {
468                start += pre_len; // data
469            }
470            start += size_of::<Pubkey>() // owner
471                + size_of::<u8>() // executable
472                + size_of::<u64>(); // rent_epoch
473        }
474    }
475    Ok(())
476}
477
478fn serialize_parameters_for_abiv1(
479    accounts: Vec<SerializeAccount>,
480    instruction_data: &[u8],
481    program_id: &Pubkey,
482    virtual_address_space_adjustments: bool,
483    account_data_direct_mapping: bool,
484    direct_account_pointers_program_input: bool,
485) -> Result<
486    (
487        AlignedMemory<HOST_ALIGN>,
488        Vec<MemoryRegion>,
489        Vec<SerializedAccountMetadata>,
490        usize,
491    ),
492    InstructionError,
493> {
494    let mut accounts_metadata = Vec::with_capacity(accounts.len());
495    // Calculate size in order to alloc once
496    let mut size = size_of::<u64>();
497    for account in &accounts {
498        size += 1; // dup
499        match account {
500            SerializeAccount::Duplicate(_) => size += 7, // padding to 64-bit aligned
501            SerializeAccount::Account(_, account) => {
502                let data_len = account.get_data().len();
503                size += size_of::<u8>() // is_signer
504                + size_of::<u8>() // is_writable
505                + size_of::<u8>() // executable
506                + size_of::<u32>() // original_data_len
507                + size_of::<Pubkey>()  // key
508                + size_of::<Pubkey>() // owner
509                + size_of::<u64>()  // lamports
510                + size_of::<u64>()  // data len
511                + size_of::<u64>(); // rent epoch
512                if !(virtual_address_space_adjustments && account_data_direct_mapping) {
513                    size += data_len
514                        + MAX_PERMITTED_DATA_INCREASE
515                        + (data_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
516                } else {
517                    size += BPF_ALIGN_OF_U128;
518                }
519            }
520        }
521    }
522    size += size_of::<u64>() // data len
523    + instruction_data.len()
524    + size_of::<Pubkey>(); // program id;
525
526    // reserve space for account pointer array if SIMD-0449 is enabled
527    let account_pointers_offset = if direct_account_pointers_program_input {
528        let offset = (size as *const u8).align_offset(BPF_ALIGN_OF_U128);
529        size += offset + accounts.len() * size_of::<u64>();
530        Some(offset)
531    } else {
532        None
533    };
534
535    let mut s = Serializer::new(
536        size,
537        MM_INPUT_START,
538        false,
539        virtual_address_space_adjustments,
540        account_data_direct_mapping,
541    );
542
543    // Serialize into the buffer
544    s.write::<u64>((accounts.len() as u64).to_le());
545    for account in accounts {
546        match account {
547            SerializeAccount::Account(_, mut borrowed_account) => {
548                let vm_addr = s.write::<u8>(NON_DUP_MARKER);
549                s.write::<u8>(borrowed_account.is_signer() as u8);
550                s.write::<u8>(borrowed_account.is_writable() as u8);
551                #[expect(deprecated)]
552                s.write::<u8>(borrowed_account.is_executable() as u8);
553                s.write_all(&[0u8, 0, 0, 0]);
554                let vm_key_addr = s.write_all(borrowed_account.get_key().as_ref());
555                let vm_owner_addr = s.write_all(borrowed_account.get_owner().as_ref());
556                let vm_lamports_addr = s.write::<u64>(borrowed_account.get_lamports().to_le());
557                s.write::<u64>((borrowed_account.get_data().len() as u64).to_le());
558                let vm_data_addr = s.write_account(&mut borrowed_account)?;
559                let rent_epoch = u64::MAX;
560                s.write::<u64>(rent_epoch.to_le());
561                accounts_metadata.push(SerializedAccountMetadata {
562                    vm_addr,
563                    original_data_len: borrowed_account.get_data().len(),
564                    vm_key_addr,
565                    vm_owner_addr,
566                    vm_lamports_addr,
567                    vm_data_addr,
568                });
569            }
570            SerializeAccount::Duplicate(position) => {
571                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
572                s.write::<u8>(position as u8);
573                s.write_all(&[0u8, 0, 0, 0, 0, 0, 0]);
574            }
575        };
576    }
577    s.write::<u64>((instruction_data.len() as u64).to_le());
578    let instruction_data_offset = s.write_all(instruction_data);
579    s.write_all(program_id.as_ref());
580
581    if let Some(offset) = account_pointers_offset {
582        // Add padding before the account pointer array to reach 8-byte alignment
583        // (BPF_ALIGN_OF_U128).
584        s.fill_write(offset, 0)
585            .map_err(|_| InstructionError::InvalidArgument)?;
586        for entry in accounts_metadata.iter() {
587            s.write::<u64>(entry.vm_addr.to_le());
588        }
589    }
590
591    let (mem, regions) = s.finish();
592    Ok((
593        mem,
594        regions,
595        accounts_metadata,
596        instruction_data_offset as usize,
597    ))
598}
599
600fn deserialize_parameters_for_abiv1<I: IntoIterator<Item = usize>>(
601    instruction_context: &InstructionContext,
602    virtual_address_space_adjustments: bool,
603    account_data_direct_mapping: bool,
604    buffer: &[u8],
605    account_lengths: I,
606) -> Result<(), InstructionError> {
607    let mut start = size_of::<u64>(); // number of accounts
608    for (instruction_account_index, pre_len) in
609        (0..instruction_context.get_number_of_instruction_accounts()).zip(account_lengths)
610    {
611        let duplicate =
612            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
613        start += size_of::<u8>(); // position
614        if duplicate.is_some() {
615            start += 7; // padding to 64-bit aligned
616        } else {
617            let mut borrowed_account =
618                instruction_context.try_borrow_instruction_account(instruction_account_index)?;
619            start += size_of::<u8>() // is_signer
620                + size_of::<u8>() // is_writable
621                + size_of::<u8>() // executable
622                + size_of::<u32>() // original_data_len
623                + size_of::<Pubkey>(); // key
624            let owner = buffer
625                .get(start..start + size_of::<Pubkey>())
626                .ok_or(InstructionError::InvalidArgument)?;
627            start += size_of::<Pubkey>(); // owner
628            let lamports = buffer
629                .get(start..start.saturating_add(8))
630                .map(<[u8; 8]>::try_from)
631                .and_then(Result::ok)
632                .map(u64::from_le_bytes)
633                .ok_or(InstructionError::InvalidArgument)?;
634            if borrowed_account.get_lamports() != lamports {
635                borrowed_account.set_lamports(lamports)?;
636            }
637            start += size_of::<u64>(); // lamports
638            let post_len = buffer
639                .get(start..start.saturating_add(8))
640                .map(<[u8; 8]>::try_from)
641                .and_then(Result::ok)
642                .map(u64::from_le_bytes)
643                .ok_or(InstructionError::InvalidArgument)? as usize;
644            start += size_of::<u64>(); // data length
645            if post_len.saturating_sub(pre_len) > MAX_PERMITTED_DATA_INCREASE
646                || post_len > MAX_PERMITTED_DATA_LENGTH as usize
647            {
648                return Err(InstructionError::InvalidRealloc);
649            }
650            if !virtual_address_space_adjustments {
651                let data = buffer
652                    .get(start..start + post_len)
653                    .ok_or(InstructionError::InvalidArgument)?;
654                // The redundant check helps to avoid the expensive data comparison if we can
655                match borrowed_account.can_data_be_resized(post_len) {
656                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
657                    Err(err) if borrowed_account.get_data() != data => return Err(err),
658                    _ => {}
659                }
660            } else if !account_data_direct_mapping && borrowed_account.can_data_be_changed().is_ok()
661            {
662                let data = buffer
663                    .get(start..start + post_len)
664                    .ok_or(InstructionError::InvalidArgument)?;
665                borrowed_account.set_data_from_slice(data)?;
666            } else if borrowed_account.get_data().len() != post_len {
667                borrowed_account.set_data_length(post_len)?;
668            }
669            start += if !(virtual_address_space_adjustments && account_data_direct_mapping) {
670                let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
671                pre_len // data
672                    .saturating_add(MAX_PERMITTED_DATA_INCREASE) // realloc padding
673                    .saturating_add(alignment_offset)
674            } else {
675                // See Serializer::write_account() as to why we have this
676                BPF_ALIGN_OF_U128
677            };
678            start += size_of::<u64>(); // rent_epoch
679            if borrowed_account.get_owner().to_bytes() != owner {
680                // Change the owner at the end so that we are allowed to change the lamports and data before
681                borrowed_account.set_owner(owner)?;
682            }
683        }
684    }
685    Ok(())
686}
687
688#[cfg(test)]
689#[allow(clippy::indexing_slicing)]
690mod tests {
691    use {
692        super::*,
693        crate::with_mock_invoke_context,
694        solana_account::{Account, AccountSharedData, ReadableAccount},
695        solana_account_info::AccountInfo,
696        solana_program_entrypoint::deserialize,
697        solana_rent::Rent,
698        solana_sbpf::{memory_region::MemoryMapping, program::SBPFVersion, vm::Config},
699        solana_sdk_ids::bpf_loader,
700        solana_system_interface::MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION,
701        solana_transaction_context::{
702            MAX_ACCOUNTS_PER_TRANSACTION, instruction_accounts::InstructionAccount,
703            transaction::TransactionContext,
704        },
705        std::{
706            borrow::Cow,
707            cell::RefCell,
708            mem::transmute,
709            rc::Rc,
710            slice::{from_raw_parts, from_raw_parts_mut},
711        },
712        test_case::test_case,
713    };
714
715    fn deduplicated_instruction_accounts(
716        transaction_indexes: &[IndexOfAccount],
717        is_writable: fn(usize) -> bool,
718    ) -> Vec<InstructionAccount> {
719        transaction_indexes
720            .iter()
721            .enumerate()
722            .map(|(index_in_instruction, index_in_transaction)| {
723                InstructionAccount::new(
724                    *index_in_transaction,
725                    false,
726                    is_writable(index_in_instruction),
727                )
728            })
729            .collect()
730    }
731
732    #[test_case(false; "direct_account_pointers_in_program_input disabled")]
733    #[test_case(true; "direct_account_pointers_in_program_input enabled")]
734    fn test_serialize_parameters_with_many_accounts(
735        direct_account_pointers_in_program_input: bool,
736    ) {
737        struct TestCase {
738            num_ix_accounts: usize,
739            append_dup_account: bool,
740            expected_err: Option<InstructionError>,
741            name: &'static str,
742        }
743
744        for virtual_address_space_adjustments in [false, true] {
745            for TestCase {
746                num_ix_accounts,
747                append_dup_account,
748                expected_err,
749                name,
750            } in [
751                TestCase {
752                    name: "serialize max accounts with cap",
753                    num_ix_accounts: MAX_ACCOUNTS_PER_INSTRUCTION,
754                    append_dup_account: false,
755                    expected_err: None,
756                },
757                TestCase {
758                    name: "serialize too many accounts with cap",
759                    num_ix_accounts: MAX_ACCOUNTS_PER_INSTRUCTION + 1,
760                    append_dup_account: false,
761                    expected_err: Some(InstructionError::MaxAccountsExceeded),
762                },
763                TestCase {
764                    name: "serialize too many accounts and append dup with cap",
765                    num_ix_accounts: MAX_ACCOUNTS_PER_INSTRUCTION,
766                    append_dup_account: true,
767                    expected_err: Some(InstructionError::MaxAccountsExceeded),
768                },
769            ] {
770                let program_id = solana_pubkey::new_rand();
771                let mut transaction_accounts = vec![(
772                    program_id,
773                    AccountSharedData::from(Account {
774                        lamports: 0,
775                        data: vec![],
776                        owner: bpf_loader::id(),
777                        executable: true,
778                        rent_epoch: 0,
779                    }),
780                )];
781                for _ in 0..num_ix_accounts {
782                    transaction_accounts.push((
783                        Pubkey::new_unique(),
784                        AccountSharedData::from(Account {
785                            lamports: 0,
786                            data: vec![],
787                            owner: program_id,
788                            executable: false,
789                            rent_epoch: 0,
790                        }),
791                    ));
792                }
793
794                let transaction_accounts_indexes: Vec<IndexOfAccount> =
795                    (0..num_ix_accounts as u16).collect();
796                let mut instruction_accounts =
797                    deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false);
798                if append_dup_account {
799                    instruction_accounts.push(instruction_accounts.last().cloned().unwrap());
800                }
801                let instruction_data = vec![];
802
803                with_mock_invoke_context!(
804                    invoke_context,
805                    transaction_context,
806                    transaction_accounts
807                );
808                if instruction_accounts.len() > MAX_ACCOUNTS_PER_INSTRUCTION {
809                    // Special case implementation of configure_next_instruction_for_tests()
810                    // which avoids the overflow when constructing the dedup_map
811                    // by simply not filling it.
812                    let dedup_map = vec![u16::MAX; MAX_ACCOUNTS_PER_TRANSACTION];
813                    invoke_context
814                        .transaction_context
815                        .configure_instruction_at_index(
816                            0,
817                            0,
818                            instruction_accounts,
819                            dedup_map,
820                            Cow::Owned(instruction_data.clone()),
821                            Some(0),
822                        )
823                        .unwrap();
824                } else {
825                    invoke_context
826                        .transaction_context
827                        .configure_top_level_instruction_for_tests(
828                            0,
829                            instruction_accounts,
830                            instruction_data.clone(),
831                        )
832                        .unwrap();
833                }
834                invoke_context.push().unwrap();
835                let instruction_context = invoke_context
836                    .transaction_context
837                    .get_current_instruction_context()
838                    .unwrap();
839
840                let serialization_result = serialize_parameters(
841                    &instruction_context,
842                    virtual_address_space_adjustments,
843                    false, // account_data_direct_mapping
844                    direct_account_pointers_in_program_input,
845                );
846                assert_eq!(
847                    serialization_result.as_ref().err(),
848                    expected_err.as_ref(),
849                    "{name} test case failed",
850                );
851                if expected_err.is_some() {
852                    continue;
853                }
854
855                let (mut serialized, regions, _account_lengths, _instruction_data_offset) =
856                    serialization_result.unwrap();
857                let mut serialized_regions = unsafe {
858                    // SAFETY: test code, serialize_parameters should be constructing valid regions.
859                    concat_regions(&regions)
860                };
861                let (de_program_id, de_accounts, de_instruction_data) = unsafe {
862                    deserialize(
863                        if !virtual_address_space_adjustments {
864                            serialized.as_slice_mut()
865                        } else {
866                            serialized_regions.as_slice_mut()
867                        }
868                        .first_mut()
869                        .unwrap() as *mut u8,
870                    )
871                };
872                assert_eq!(de_program_id, &program_id);
873                assert_eq!(de_instruction_data, &instruction_data);
874                for account_info in de_accounts {
875                    let index_in_transaction = invoke_context
876                        .transaction_context
877                        .find_index_of_account(account_info.key)
878                        .unwrap();
879                    let account = invoke_context
880                        .transaction_context
881                        .accounts()
882                        .try_borrow(index_in_transaction)
883                        .unwrap();
884                    assert_eq!(account.lamports(), account_info.lamports());
885                    assert_eq!(account.data(), &account_info.data.borrow()[..]);
886                    assert_eq!(account.owner(), account_info.owner);
887                    assert_eq!(account.executable(), account_info.executable);
888                    #[allow(deprecated)]
889                    {
890                        // Using the sdk entrypoint, the rent-epoch is skipped
891                        assert_eq!(0, account_info._unused);
892                    }
893                }
894            }
895        }
896    }
897
898    #[test_case(false; "direct_account_pointers_in_program_input disabled")]
899    #[test_case(true; "direct_account_pointers_in_program_input enabled")]
900    fn test_serialize_parameters(direct_account_pointers_in_program_input: bool) {
901        for virtual_address_space_adjustments in [false, true] {
902            let program_id = solana_pubkey::new_rand();
903            let transaction_accounts = vec![
904                (
905                    program_id,
906                    AccountSharedData::from(Account {
907                        lamports: 0,
908                        data: vec![],
909                        owner: bpf_loader::id(),
910                        executable: true,
911                        rent_epoch: 0,
912                    }),
913                ),
914                (
915                    solana_pubkey::new_rand(),
916                    AccountSharedData::from(Account {
917                        lamports: 1,
918                        data: vec![1u8, 2, 3, 4, 5],
919                        owner: bpf_loader::id(),
920                        executable: false,
921                        rent_epoch: 100,
922                    }),
923                ),
924                (
925                    solana_pubkey::new_rand(),
926                    AccountSharedData::from(Account {
927                        lamports: 2,
928                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
929                        owner: bpf_loader::id(),
930                        executable: true,
931                        rent_epoch: 200,
932                    }),
933                ),
934                (
935                    solana_pubkey::new_rand(),
936                    AccountSharedData::from(Account {
937                        lamports: 3,
938                        data: vec![],
939                        owner: bpf_loader::id(),
940                        executable: false,
941                        rent_epoch: 3100,
942                    }),
943                ),
944                (
945                    solana_pubkey::new_rand(),
946                    AccountSharedData::from(Account {
947                        lamports: 4,
948                        data: vec![1u8, 2, 3, 4, 5],
949                        owner: bpf_loader::id(),
950                        executable: false,
951                        rent_epoch: 100,
952                    }),
953                ),
954                (
955                    solana_pubkey::new_rand(),
956                    AccountSharedData::from(Account {
957                        lamports: 5,
958                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
959                        owner: bpf_loader::id(),
960                        executable: true,
961                        rent_epoch: 200,
962                    }),
963                ),
964                (
965                    solana_pubkey::new_rand(),
966                    AccountSharedData::from(Account {
967                        lamports: 6,
968                        data: vec![],
969                        owner: bpf_loader::id(),
970                        executable: false,
971                        rent_epoch: 3100,
972                    }),
973                ),
974                (
975                    program_id,
976                    AccountSharedData::from(Account {
977                        lamports: 0,
978                        data: vec![],
979                        owner: bpf_loader_deprecated::id(),
980                        executable: true,
981                        rent_epoch: 0,
982                    }),
983                ),
984            ];
985            let instruction_accounts =
986                deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
987            let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
988            let original_accounts = transaction_accounts.clone();
989            with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
990            invoke_context
991                .transaction_context
992                .configure_top_level_instruction_for_tests(
993                    0,
994                    instruction_accounts.clone(),
995                    instruction_data.clone(),
996                )
997                .unwrap();
998            invoke_context.push().unwrap();
999            let instruction_context = invoke_context
1000                .transaction_context
1001                .get_current_instruction_context()
1002                .unwrap();
1003
1004            // check serialize_parameters_for_abiv1
1005            let (mut serialized, regions, accounts_metadata, _instruction_data_offset) =
1006                serialize_parameters(
1007                    &instruction_context,
1008                    virtual_address_space_adjustments,
1009                    false, // account_data_direct_mapping
1010                    direct_account_pointers_in_program_input,
1011                )
1012                .unwrap();
1013
1014            let mut serialized_regions = unsafe {
1015                // SAFETY: test code, serialize_parameters should be constructing valid regions.
1016                concat_regions(&regions)
1017            };
1018            if !virtual_address_space_adjustments {
1019                assert_eq!(serialized.as_slice(), serialized_regions.as_slice());
1020            }
1021            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
1022                deserialize(
1023                    if !virtual_address_space_adjustments {
1024                        serialized.as_slice_mut()
1025                    } else {
1026                        serialized_regions.as_slice_mut()
1027                    }
1028                    .first_mut()
1029                    .unwrap() as *mut u8,
1030                )
1031            };
1032
1033            assert_eq!(&program_id, de_program_id);
1034            assert_eq!(instruction_data, de_instruction_data);
1035            assert_eq!(
1036                (de_instruction_data.first().unwrap() as *const u8).align_offset(BPF_ALIGN_OF_U128),
1037                0
1038            );
1039            for account_info in de_accounts {
1040                let index_in_transaction = invoke_context
1041                    .transaction_context
1042                    .find_index_of_account(account_info.key)
1043                    .unwrap();
1044                let account = invoke_context
1045                    .transaction_context
1046                    .accounts()
1047                    .try_borrow(index_in_transaction)
1048                    .unwrap();
1049                assert_eq!(account.lamports(), account_info.lamports());
1050                assert_eq!(account.data(), &account_info.data.borrow()[..]);
1051                assert_eq!(account.owner(), account_info.owner);
1052                assert_eq!(account.executable(), account_info.executable);
1053                #[allow(deprecated)]
1054                {
1055                    // Using the sdk entrypoint, the rent-epoch is skipped
1056                    assert_eq!(0, account_info._unused);
1057                }
1058
1059                assert_eq!(
1060                    (*account_info.lamports.borrow() as *const u64).align_offset(BPF_ALIGN_OF_U128),
1061                    0
1062                );
1063                assert_eq!(
1064                    account_info
1065                        .data
1066                        .borrow()
1067                        .as_ptr()
1068                        .align_offset(BPF_ALIGN_OF_U128),
1069                    0
1070                );
1071            }
1072
1073            deserialize_parameters(
1074                &instruction_context,
1075                virtual_address_space_adjustments,
1076                false, // account_data_direct_mapping
1077                serialized.as_slice(),
1078                &accounts_metadata,
1079            )
1080            .unwrap();
1081            for (index_in_transaction, (_key, original_account)) in
1082                original_accounts.iter().enumerate()
1083            {
1084                let account = invoke_context
1085                    .transaction_context
1086                    .accounts()
1087                    .try_borrow(index_in_transaction as IndexOfAccount)
1088                    .unwrap();
1089                assert_eq!(&*account, original_account);
1090            }
1091
1092            invoke_context.pop().unwrap();
1093            // check serialize_parameters_for_abiv0
1094            invoke_context
1095                .transaction_context
1096                .configure_top_level_instruction_for_tests(
1097                    7,
1098                    instruction_accounts,
1099                    instruction_data.clone(),
1100                )
1101                .unwrap();
1102            invoke_context.push().unwrap();
1103            let instruction_context = invoke_context
1104                .transaction_context
1105                .get_current_instruction_context()
1106                .unwrap();
1107
1108            let (mut serialized, regions, account_lengths, _instruction_data_offset) =
1109                serialize_parameters(
1110                    &instruction_context,
1111                    virtual_address_space_adjustments,
1112                    false, // account_data_direct_mapping
1113                    direct_account_pointers_in_program_input,
1114                )
1115                .unwrap();
1116            let mut serialized_regions = unsafe {
1117                // SAFETY: test code, serialize_parameters should be constructing valid regions.
1118                concat_regions(&regions)
1119            };
1120
1121            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
1122                deserialize_for_abiv0(
1123                    if !virtual_address_space_adjustments {
1124                        serialized.as_slice_mut()
1125                    } else {
1126                        serialized_regions.as_slice_mut()
1127                    }
1128                    .first_mut()
1129                    .unwrap() as *mut u8,
1130                )
1131            };
1132            assert_eq!(&program_id, de_program_id);
1133            assert_eq!(instruction_data, de_instruction_data);
1134            for account_info in de_accounts {
1135                let index_in_transaction = invoke_context
1136                    .transaction_context
1137                    .find_index_of_account(account_info.key)
1138                    .unwrap();
1139                let account = invoke_context
1140                    .transaction_context
1141                    .accounts()
1142                    .try_borrow(index_in_transaction)
1143                    .unwrap();
1144                assert_eq!(account.lamports(), account_info.lamports());
1145                assert_eq!(account.data(), &account_info.data.borrow()[..]);
1146                assert_eq!(account.owner(), account_info.owner);
1147                assert_eq!(account.executable(), account_info.executable);
1148                #[allow(deprecated)]
1149                {
1150                    assert_eq!(u64::MAX, account_info._unused);
1151                }
1152            }
1153
1154            deserialize_parameters(
1155                &instruction_context,
1156                virtual_address_space_adjustments,
1157                false, // account_data_direct_mapping
1158                serialized.as_slice(),
1159                &account_lengths,
1160            )
1161            .unwrap();
1162            for (index_in_transaction, (_key, original_account)) in
1163                original_accounts.iter().enumerate()
1164            {
1165                let account = invoke_context
1166                    .transaction_context
1167                    .accounts()
1168                    .try_borrow(index_in_transaction as IndexOfAccount)
1169                    .unwrap();
1170                assert_eq!(&*account, original_account);
1171            }
1172        }
1173    }
1174
1175    #[test_case(false; "direct_account_pointers_in_program_input disabled")]
1176    #[test_case(true; "direct_account_pointers_in_program_input enabled")]
1177    fn test_serialize_parameters_mask_out_rent_epoch_in_vm_serialization(
1178        direct_account_pointers_in_program_input: bool,
1179    ) {
1180        let transaction_accounts = vec![
1181            (
1182                solana_pubkey::new_rand(),
1183                AccountSharedData::from(Account {
1184                    lamports: 0,
1185                    data: vec![],
1186                    owner: bpf_loader::id(),
1187                    executable: true,
1188                    rent_epoch: 0,
1189                }),
1190            ),
1191            (
1192                solana_pubkey::new_rand(),
1193                AccountSharedData::from(Account {
1194                    lamports: 1,
1195                    data: vec![1u8, 2, 3, 4, 5],
1196                    owner: bpf_loader::id(),
1197                    executable: false,
1198                    rent_epoch: 100,
1199                }),
1200            ),
1201            (
1202                solana_pubkey::new_rand(),
1203                AccountSharedData::from(Account {
1204                    lamports: 2,
1205                    data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1206                    owner: bpf_loader::id(),
1207                    executable: true,
1208                    rent_epoch: 200,
1209                }),
1210            ),
1211            (
1212                solana_pubkey::new_rand(),
1213                AccountSharedData::from(Account {
1214                    lamports: 3,
1215                    data: vec![],
1216                    owner: bpf_loader::id(),
1217                    executable: false,
1218                    rent_epoch: 300,
1219                }),
1220            ),
1221            (
1222                solana_pubkey::new_rand(),
1223                AccountSharedData::from(Account {
1224                    lamports: 4,
1225                    data: vec![1u8, 2, 3, 4, 5],
1226                    owner: bpf_loader::id(),
1227                    executable: false,
1228                    rent_epoch: 100,
1229                }),
1230            ),
1231            (
1232                solana_pubkey::new_rand(),
1233                AccountSharedData::from(Account {
1234                    lamports: 5,
1235                    data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1236                    owner: bpf_loader::id(),
1237                    executable: true,
1238                    rent_epoch: 200,
1239                }),
1240            ),
1241            (
1242                solana_pubkey::new_rand(),
1243                AccountSharedData::from(Account {
1244                    lamports: 6,
1245                    data: vec![],
1246                    owner: bpf_loader::id(),
1247                    executable: false,
1248                    rent_epoch: 3100,
1249                }),
1250            ),
1251            (
1252                solana_pubkey::new_rand(),
1253                AccountSharedData::from(Account {
1254                    lamports: 0,
1255                    data: vec![],
1256                    owner: bpf_loader_deprecated::id(),
1257                    executable: true,
1258                    rent_epoch: 0,
1259                }),
1260            ),
1261        ];
1262        let instruction_accounts =
1263            deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
1264        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1265        invoke_context
1266            .transaction_context
1267            .configure_top_level_instruction_for_tests(0, instruction_accounts.clone(), vec![])
1268            .unwrap();
1269        invoke_context.push().unwrap();
1270        let instruction_context = invoke_context
1271            .transaction_context
1272            .get_current_instruction_context()
1273            .unwrap();
1274
1275        // check serialize_parameters_for_abiv1
1276        let (_serialized, regions, _accounts_metadata, _instruction_data_offset) =
1277            serialize_parameters(
1278                &instruction_context,
1279                true,
1280                false, // account_data_direct_mapping
1281                direct_account_pointers_in_program_input,
1282            )
1283            .unwrap();
1284
1285        let mut serialized_regions = unsafe {
1286            // SAFETY: test code, serialize_parameters should be constructing valid regions.
1287            concat_regions(&regions)
1288        };
1289        let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1290            deserialize(serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8)
1291        };
1292
1293        for account_info in de_accounts {
1294            // Using program-entrypoint, the rent-epoch will always be 0
1295            #[allow(deprecated)]
1296            {
1297                assert_eq!(0, account_info._unused);
1298            }
1299        }
1300
1301        // check serialize_parameters_for_abiv0
1302        invoke_context
1303            .transaction_context
1304            .configure_top_level_instruction_for_tests(7, instruction_accounts, vec![])
1305            .unwrap();
1306        invoke_context.push().unwrap();
1307        let instruction_context = invoke_context
1308            .transaction_context
1309            .get_current_instruction_context()
1310            .unwrap();
1311
1312        let (_serialized, regions, _account_lengths, _instruction_data_offset) =
1313            serialize_parameters(
1314                &instruction_context,
1315                true,
1316                false, // account_data_direct_mapping
1317                direct_account_pointers_in_program_input,
1318            )
1319            .unwrap();
1320        let mut serialized_regions = unsafe {
1321            // SAFETY: test code, serialize_parameters should be constructing valid regions.
1322            concat_regions(&regions)
1323        };
1324
1325        let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1326            deserialize_for_abiv0(serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8)
1327        };
1328        for account_info in de_accounts {
1329            #[allow(deprecated)]
1330            {
1331                assert_eq!(account_info._unused, u64::MAX);
1332            }
1333        }
1334    }
1335
1336    // the old bpf_loader in-program deserializer bpf_loader::id()
1337    #[deny(unsafe_op_in_unsafe_fn)]
1338    unsafe fn deserialize_for_abiv0<'a>(
1339        input: *mut u8,
1340    ) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
1341        // this boring boilerplate struct is needed until inline const...
1342        struct Ptr<T>(std::marker::PhantomData<T>);
1343        impl<T> Ptr<T> {
1344            const COULD_BE_UNALIGNED: bool = std::mem::align_of::<T>() > 1;
1345
1346            #[inline(always)]
1347            fn read_possibly_unaligned(input: *mut u8, offset: usize) -> T {
1348                unsafe {
1349                    let src = input.add(offset) as *const T;
1350                    if Self::COULD_BE_UNALIGNED {
1351                        src.read_unaligned()
1352                    } else {
1353                        src.read()
1354                    }
1355                }
1356            }
1357
1358            // rustc inserts debug_assert! for misaligned pointer dereferences when
1359            // deserializing, starting from [1]. so, use std::mem::transmute as the last resort
1360            // while preventing clippy from complaining to suggest not to use it.
1361            // [1]: https://github.com/rust-lang/rust/commit/22a7a19f9333bc1fcba97ce444a3515cb5fb33e6
1362            // as for the ub nature of the misaligned pointer dereference, this is
1363            // acceptable in this code, given that this is cfg(test) and it's cared only with
1364            // x86-64 and the target only incurs some performance penalty, not like segfaults
1365            // in other targets.
1366            #[inline(always)]
1367            fn ref_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a T {
1368                #[allow(clippy::transmute_ptr_to_ref)]
1369                unsafe {
1370                    transmute(input.add(offset) as *const T)
1371                }
1372            }
1373
1374            // See ref_possibly_unaligned's comment
1375            #[inline(always)]
1376            fn mut_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a mut T {
1377                #[allow(clippy::transmute_ptr_to_ref)]
1378                unsafe {
1379                    transmute(input.add(offset) as *mut T)
1380                }
1381            }
1382        }
1383
1384        let mut offset: usize = 0;
1385
1386        // number of accounts present
1387
1388        let num_accounts = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1389        offset += size_of::<u64>();
1390
1391        // account Infos
1392
1393        let mut accounts = Vec::with_capacity(num_accounts);
1394        for _ in 0..num_accounts {
1395            let dup_info = Ptr::<u8>::read_possibly_unaligned(input, offset);
1396            offset += size_of::<u8>();
1397            if dup_info == NON_DUP_MARKER {
1398                let is_signer = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1399                offset += size_of::<u8>();
1400
1401                let is_writable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1402                offset += size_of::<u8>();
1403
1404                let key = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1405                offset += size_of::<Pubkey>();
1406
1407                let lamports = Rc::new(RefCell::new(Ptr::mut_possibly_unaligned(input, offset)));
1408                offset += size_of::<u64>();
1409
1410                let data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1411                offset += size_of::<u64>();
1412
1413                let data = Rc::new(RefCell::new(unsafe {
1414                    from_raw_parts_mut(input.add(offset), data_len)
1415                }));
1416                offset += data_len;
1417
1418                let owner: &Pubkey = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1419                offset += size_of::<Pubkey>();
1420
1421                let executable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1422                offset += size_of::<u8>();
1423
1424                let unused = Ptr::<u64>::read_possibly_unaligned(input, offset);
1425                offset += size_of::<u64>();
1426
1427                #[allow(deprecated)]
1428                accounts.push(AccountInfo {
1429                    key,
1430                    is_signer,
1431                    is_writable,
1432                    lamports,
1433                    data,
1434                    owner,
1435                    executable,
1436                    _unused: unused,
1437                });
1438            } else {
1439                // duplicate account, clone the original
1440                accounts.push(accounts.get(dup_info as usize).unwrap().clone());
1441            }
1442        }
1443
1444        // instruction data
1445
1446        let instruction_data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1447        offset += size_of::<u64>();
1448
1449        let instruction_data = unsafe { from_raw_parts(input.add(offset), instruction_data_len) };
1450        offset += instruction_data_len;
1451
1452        // program Id
1453
1454        let program_id = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1455
1456        (program_id, accounts, instruction_data)
1457    }
1458
1459    /// # Safety
1460    ///
1461    /// All memory regions must be pointing to valid to dereference host buffers.
1462    unsafe fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
1463        let last_region = regions.last().unwrap();
1464        let last_region_vm_addr = last_region.vm_addr_range().start;
1465        let mut mem = AlignedMemory::zero_filled(
1466            (last_region_vm_addr - MM_INPUT_START + last_region.len() as u64) as usize,
1467        );
1468        for region in regions {
1469            let vm_start = region.vm_addr_range().start;
1470            let buffer = region.host_buffer().ptr();
1471            mem.as_slice_mut()[(vm_start - MM_INPUT_START) as usize..][..buffer.len()]
1472                .copy_from_slice(unsafe {
1473                    // SAFETY:
1474                    // Contract from `<*const [u8]>::as_ref_unchecked`: ensure that the pointer is
1475                    // convertible to reference.
1476                    // Evidence: The contract delegated to the callers.
1477                    buffer.as_ref_unchecked()
1478                })
1479        }
1480        mem
1481    }
1482
1483    #[test]
1484    fn test_access_violation_handler() {
1485        let program_id = Pubkey::new_unique();
1486        let shared_account = AccountSharedData::new(0, 4, &program_id);
1487        let mut transaction_context = TransactionContext::new(
1488            vec![
1489                (
1490                    Pubkey::new_unique(),
1491                    AccountSharedData::new(0, 4, &program_id),
1492                ), // readonly
1493                (Pubkey::new_unique(), shared_account.clone()), // writable shared
1494                (
1495                    Pubkey::new_unique(),
1496                    AccountSharedData::new(0, 0, &program_id),
1497                ), // another writable account
1498                (
1499                    Pubkey::new_unique(),
1500                    AccountSharedData::new(
1501                        0,
1502                        MAX_PERMITTED_DATA_LENGTH as usize - 0x100,
1503                        &program_id,
1504                    ),
1505                ), // almost max sized writable account
1506                (
1507                    Pubkey::new_unique(),
1508                    AccountSharedData::new(0, 0, &program_id),
1509                ), // writable dummy to burn accounts_resize_delta
1510                (
1511                    Pubkey::new_unique(),
1512                    AccountSharedData::new(0, 0x3000, &program_id),
1513                ), // writable dummy to burn accounts_resize_delta
1514                (program_id, AccountSharedData::default()),     // program
1515            ],
1516            Rent::default(),
1517            /* max_instruction_stack_depth */ 1,
1518            /* max_instruction_trace_length */ 1,
1519            /* number_of_top_level_instructions */ 1,
1520        );
1521        let transaction_accounts_indexes = [0, 1, 2, 3, 4, 5];
1522        let instruction_accounts =
1523            deduplicated_instruction_accounts(&transaction_accounts_indexes, |index| index > 0);
1524        transaction_context
1525            .configure_top_level_instruction_for_tests(6, instruction_accounts, vec![])
1526            .unwrap();
1527        transaction_context.push().unwrap();
1528        let instruction_context = transaction_context
1529            .get_current_instruction_context()
1530            .unwrap();
1531        let account_start_offsets = [
1532            MM_INPUT_START,
1533            MM_INPUT_START + 4 + MAX_PERMITTED_DATA_INCREASE as u64,
1534            MM_INPUT_START + (4 + MAX_PERMITTED_DATA_INCREASE as u64) * 2,
1535            MM_INPUT_START + (4 + MAX_PERMITTED_DATA_INCREASE as u64) * 3,
1536        ];
1537        let regions = account_start_offsets
1538            .iter()
1539            .enumerate()
1540            .map(|(index_in_instruction, account_start_offset)| {
1541                create_memory_region_of_account(
1542                    &mut instruction_context
1543                        .try_borrow_instruction_account(index_in_instruction as IndexOfAccount)
1544                        .unwrap(),
1545                    *account_start_offset,
1546                )
1547                .unwrap()
1548            })
1549            .collect::<Vec<_>>();
1550        let config = Config {
1551            aligned_memory_mapping: false,
1552            ..Config::default()
1553        };
1554        let mut memory_mapping = unsafe {
1555            MemoryMapping::new_with_access_violation_handler(
1556                regions,
1557                &config,
1558                SBPFVersion::V3,
1559                transaction_context.access_violation_handler(true, true),
1560            )
1561            .unwrap()
1562        };
1563
1564        // Reading readonly account is allowed
1565        memory_mapping
1566            .load::<u32>(account_start_offsets[0])
1567            .unwrap();
1568
1569        // Reading writable account is allowed
1570        memory_mapping
1571            .load::<u32>(account_start_offsets[1])
1572            .unwrap();
1573
1574        // Reading beyond readonly accounts current size is denied
1575        memory_mapping
1576            .load::<u32>(account_start_offsets[0] + 4)
1577            .unwrap_err();
1578
1579        // Writing to readonly account is denied
1580        memory_mapping
1581            .store::<u32>(0, account_start_offsets[0])
1582            .unwrap_err();
1583
1584        // Writing to shared writable account makes it unique (CoW logic.)
1585        // It has been previously been made non-unique at the beginning of
1586        // the test through a clone.
1587        let _shared_account_ref = shared_account;
1588        assert!(
1589            transaction_context
1590                .accounts()
1591                .try_borrow_mut(1)
1592                .unwrap()
1593                .is_shared()
1594        );
1595        memory_mapping
1596            .store::<u32>(0, account_start_offsets[1])
1597            .unwrap();
1598        assert!(
1599            !transaction_context
1600                .accounts()
1601                .try_borrow_mut(1)
1602                .unwrap()
1603                .is_shared()
1604        );
1605        assert_eq!(
1606            transaction_context
1607                .accounts()
1608                .try_borrow(1)
1609                .unwrap()
1610                .data()
1611                .len(),
1612            4,
1613        );
1614
1615        // Reading beyond writable accounts current size grows is denied
1616        memory_mapping
1617            .load::<u32>(account_start_offsets[1] + 4)
1618            .unwrap_err();
1619
1620        // Writing beyond writable accounts current size grows it
1621        // to original length plus MAX_PERMITTED_DATA_INCREASE
1622        memory_mapping
1623            .store::<u32>(0, account_start_offsets[1] + 4)
1624            .unwrap();
1625        assert_eq!(
1626            transaction_context
1627                .accounts()
1628                .try_borrow(1)
1629                .unwrap()
1630                .data()
1631                .len(),
1632            4 + MAX_PERMITTED_DATA_INCREASE,
1633        );
1634        assert!(
1635            transaction_context
1636                .accounts()
1637                .try_borrow(1)
1638                .unwrap()
1639                .data()
1640                .len()
1641                < 0x3000
1642        );
1643
1644        // Writing beyond almost max sized writable accounts current size only grows it
1645        // to MAX_PERMITTED_DATA_LENGTH
1646        memory_mapping
1647            .store::<u32>(0, account_start_offsets[3] + MAX_PERMITTED_DATA_LENGTH - 4)
1648            .unwrap();
1649        assert_eq!(
1650            transaction_context
1651                .accounts()
1652                .try_borrow(3)
1653                .unwrap()
1654                .data()
1655                .len(),
1656            MAX_PERMITTED_DATA_LENGTH as usize,
1657        );
1658
1659        // Accessing the rest of the address space reserved for
1660        // the almost max sized writable account is denied
1661        memory_mapping
1662            .load::<u32>(account_start_offsets[3] + MAX_PERMITTED_DATA_LENGTH)
1663            .unwrap_err();
1664        memory_mapping
1665            .store::<u32>(0, account_start_offsets[3] + MAX_PERMITTED_DATA_LENGTH)
1666            .unwrap_err();
1667
1668        // Burn through most of the accounts_resize_delta budget
1669        let remaining_allowed_growth: usize = 0x700;
1670        for index_in_instruction in 4..6 {
1671            let mut borrowed_account = instruction_context
1672                .try_borrow_instruction_account(index_in_instruction)
1673                .unwrap();
1674            borrowed_account
1675                .set_data_from_slice(&vec![0u8; MAX_PERMITTED_DATA_LENGTH as usize])
1676                .unwrap();
1677        }
1678        assert_eq!(
1679            transaction_context.accounts().resize_delta(),
1680            MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
1681                - remaining_allowed_growth as i64,
1682        );
1683
1684        // Writing beyond empty writable accounts current size
1685        // only grows it to fill up MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION
1686        memory_mapping
1687            .store::<u32>(0, account_start_offsets[2] + 0x500)
1688            .unwrap();
1689        assert_eq!(
1690            transaction_context
1691                .accounts()
1692                .try_borrow(2)
1693                .unwrap()
1694                .data()
1695                .len(),
1696            remaining_allowed_growth,
1697        );
1698    }
1699}