solana_bpf_loader_program/
serialization.rs

1#![allow(clippy::arithmetic_side_effects)]
2
3use {
4    solana_instruction::error::InstructionError,
5    solana_program_entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, NON_DUP_MARKER},
6    solana_program_runtime::invoke_context::SerializedAccountMetadata,
7    solana_pubkey::Pubkey,
8    solana_sbpf::{
9        aligned_memory::{AlignedMemory, Pod},
10        ebpf::{HOST_ALIGN, MM_INPUT_START},
11        memory_region::{MemoryRegion, MemoryState},
12    },
13    solana_sdk_ids::bpf_loader_deprecated,
14    solana_system_interface::MAX_PERMITTED_DATA_LENGTH,
15    solana_transaction_context::{
16        BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
17    },
18    std::mem::{self, size_of},
19};
20
21/// Maximum number of instruction accounts that can be serialized into the
22/// SBF VM.
23const MAX_INSTRUCTION_ACCOUNTS: u8 = NON_DUP_MARKER;
24
25#[allow(dead_code)]
26enum SerializeAccount<'a> {
27    Account(IndexOfAccount, BorrowedAccount<'a>),
28    Duplicate(IndexOfAccount),
29}
30
31struct Serializer {
32    buffer: AlignedMemory<HOST_ALIGN>,
33    regions: Vec<MemoryRegion>,
34    vaddr: u64,
35    region_start: usize,
36    aligned: bool,
37    copy_account_data: bool,
38}
39
40impl Serializer {
41    fn new(size: usize, start_addr: u64, aligned: bool, copy_account_data: bool) -> Serializer {
42        Serializer {
43            buffer: AlignedMemory::with_capacity(size),
44            regions: Vec::new(),
45            region_start: 0,
46            vaddr: start_addr,
47            aligned,
48            copy_account_data,
49        }
50    }
51
52    fn fill_write(&mut self, num: usize, value: u8) -> std::io::Result<()> {
53        self.buffer.fill_write(num, value)
54    }
55
56    fn write<T: Pod>(&mut self, value: T) -> u64 {
57        self.debug_assert_alignment::<T>();
58        let vaddr = self
59            .vaddr
60            .saturating_add(self.buffer.len() as u64)
61            .saturating_sub(self.region_start as u64);
62        // Safety:
63        // in serialize_parameters_(aligned|unaligned) first we compute the
64        // required size then we write into the newly allocated buffer. There's
65        // no need to check bounds at every write.
66        //
67        // AlignedMemory::write_unchecked _does_ debug_assert!() that the capacity
68        // is enough, so in the unlikely case we introduce a bug in the size
69        // computation, tests will abort.
70        unsafe {
71            self.buffer.write_unchecked(value);
72        }
73
74        vaddr
75    }
76
77    fn write_all(&mut self, value: &[u8]) -> u64 {
78        let vaddr = self
79            .vaddr
80            .saturating_add(self.buffer.len() as u64)
81            .saturating_sub(self.region_start as u64);
82        // Safety:
83        // see write() - the buffer is guaranteed to be large enough
84        unsafe {
85            self.buffer.write_all_unchecked(value);
86        }
87
88        vaddr
89    }
90
91    fn write_account(
92        &mut self,
93        account: &mut BorrowedAccount<'_>,
94    ) -> Result<u64, InstructionError> {
95        let vm_data_addr = if self.copy_account_data {
96            let vm_data_addr = self.vaddr.saturating_add(self.buffer.len() as u64);
97            self.write_all(account.get_data());
98            vm_data_addr
99        } else {
100            self.push_region(true);
101            let vaddr = self.vaddr;
102            self.push_account_data_region(account)?;
103            vaddr
104        };
105
106        if self.aligned {
107            let align_offset =
108                (account.get_data().len() as *const u8).align_offset(BPF_ALIGN_OF_U128);
109            if self.copy_account_data {
110                self.fill_write(MAX_PERMITTED_DATA_INCREASE + align_offset, 0)
111                    .map_err(|_| InstructionError::InvalidArgument)?;
112            } else {
113                // The deserialization code is going to align the vm_addr to
114                // BPF_ALIGN_OF_U128. Always add one BPF_ALIGN_OF_U128 worth of
115                // padding and shift the start of the next region, so that once
116                // vm_addr is aligned, the corresponding host_addr is aligned
117                // too.
118                self.fill_write(MAX_PERMITTED_DATA_INCREASE + BPF_ALIGN_OF_U128, 0)
119                    .map_err(|_| InstructionError::InvalidArgument)?;
120                self.region_start += BPF_ALIGN_OF_U128.saturating_sub(align_offset);
121                // put the realloc padding in its own region
122                self.push_region(account.can_data_be_changed().is_ok());
123            }
124        }
125
126        Ok(vm_data_addr)
127    }
128
129    fn push_account_data_region(
130        &mut self,
131        account: &mut BorrowedAccount<'_>,
132    ) -> Result<(), InstructionError> {
133        if !account.get_data().is_empty() {
134            let region = match account_data_region_memory_state(account) {
135                MemoryState::Readable => MemoryRegion::new_readonly(account.get_data(), self.vaddr),
136                MemoryState::Writable => {
137                    MemoryRegion::new_writable(account.get_data_mut()?, self.vaddr)
138                }
139                MemoryState::Cow(index_in_transaction) => {
140                    MemoryRegion::new_cow(account.get_data(), self.vaddr, index_in_transaction)
141                }
142            };
143            self.vaddr += region.len;
144            self.regions.push(region);
145        }
146
147        Ok(())
148    }
149
150    fn push_region(&mut self, writable: bool) {
151        let range = self.region_start..self.buffer.len();
152        let region = if writable {
153            MemoryRegion::new_writable(
154                self.buffer.as_slice_mut().get_mut(range.clone()).unwrap(),
155                self.vaddr,
156            )
157        } else {
158            MemoryRegion::new_readonly(
159                self.buffer.as_slice().get(range.clone()).unwrap(),
160                self.vaddr,
161            )
162        };
163        self.regions.push(region);
164        self.region_start = range.end;
165        self.vaddr += range.len() as u64;
166    }
167
168    fn finish(mut self) -> (AlignedMemory<HOST_ALIGN>, Vec<MemoryRegion>) {
169        self.push_region(true);
170        debug_assert_eq!(self.region_start, self.buffer.len());
171        (self.buffer, self.regions)
172    }
173
174    fn debug_assert_alignment<T>(&self) {
175        debug_assert!(
176            !self.aligned
177                || self
178                    .buffer
179                    .as_slice()
180                    .as_ptr_range()
181                    .end
182                    .align_offset(mem::align_of::<T>())
183                    == 0
184        );
185    }
186}
187
188pub fn serialize_parameters(
189    transaction_context: &TransactionContext,
190    instruction_context: &InstructionContext,
191    copy_account_data: bool,
192    mask_out_rent_epoch_in_vm_serialization: bool,
193) -> Result<
194    (
195        AlignedMemory<HOST_ALIGN>,
196        Vec<MemoryRegion>,
197        Vec<SerializedAccountMetadata>,
198    ),
199    InstructionError,
200> {
201    let num_ix_accounts = instruction_context.get_number_of_instruction_accounts();
202    if num_ix_accounts > MAX_INSTRUCTION_ACCOUNTS as IndexOfAccount {
203        return Err(InstructionError::MaxAccountsExceeded);
204    }
205
206    let (program_id, is_loader_deprecated) = {
207        let program_account =
208            instruction_context.try_borrow_last_program_account(transaction_context)?;
209        (
210            *program_account.get_key(),
211            *program_account.get_owner() == bpf_loader_deprecated::id(),
212        )
213    };
214
215    let accounts = (0..instruction_context.get_number_of_instruction_accounts())
216        .map(|instruction_account_index| {
217            if let Some(index) = instruction_context
218                .is_instruction_account_duplicate(instruction_account_index)
219                .unwrap()
220            {
221                SerializeAccount::Duplicate(index)
222            } else {
223                let account = instruction_context
224                    .try_borrow_instruction_account(transaction_context, instruction_account_index)
225                    .unwrap();
226                SerializeAccount::Account(instruction_account_index, account)
227            }
228        })
229        // fun fact: jemalloc is good at caching tiny allocations like this one,
230        // so collecting here is actually faster than passing the iterator
231        // around, since the iterator does the work to produce its items each
232        // time it's iterated on.
233        .collect::<Vec<_>>();
234
235    if is_loader_deprecated {
236        serialize_parameters_unaligned(
237            accounts,
238            instruction_context.get_instruction_data(),
239            &program_id,
240            copy_account_data,
241            mask_out_rent_epoch_in_vm_serialization,
242        )
243    } else {
244        serialize_parameters_aligned(
245            accounts,
246            instruction_context.get_instruction_data(),
247            &program_id,
248            copy_account_data,
249            mask_out_rent_epoch_in_vm_serialization,
250        )
251    }
252}
253
254pub(crate) fn deserialize_parameters(
255    transaction_context: &TransactionContext,
256    instruction_context: &InstructionContext,
257    copy_account_data: bool,
258    buffer: &[u8],
259    accounts_metadata: &[SerializedAccountMetadata],
260) -> Result<(), InstructionError> {
261    let is_loader_deprecated = *instruction_context
262        .try_borrow_last_program_account(transaction_context)?
263        .get_owner()
264        == bpf_loader_deprecated::id();
265    let account_lengths = accounts_metadata.iter().map(|a| a.original_data_len);
266    if is_loader_deprecated {
267        deserialize_parameters_unaligned(
268            transaction_context,
269            instruction_context,
270            copy_account_data,
271            buffer,
272            account_lengths,
273        )
274    } else {
275        deserialize_parameters_aligned(
276            transaction_context,
277            instruction_context,
278            copy_account_data,
279            buffer,
280            account_lengths,
281        )
282    }
283}
284
285fn serialize_parameters_unaligned(
286    accounts: Vec<SerializeAccount>,
287    instruction_data: &[u8],
288    program_id: &Pubkey,
289    copy_account_data: bool,
290    mask_out_rent_epoch_in_vm_serialization: bool,
291) -> Result<
292    (
293        AlignedMemory<HOST_ALIGN>,
294        Vec<MemoryRegion>,
295        Vec<SerializedAccountMetadata>,
296    ),
297    InstructionError,
298> {
299    // Calculate size in order to alloc once
300    let mut size = size_of::<u64>();
301    for account in &accounts {
302        size += 1; // dup
303        match account {
304            SerializeAccount::Duplicate(_) => {}
305            SerializeAccount::Account(_, account) => {
306                size += size_of::<u8>() // is_signer
307                + size_of::<u8>() // is_writable
308                + size_of::<Pubkey>() // key
309                + size_of::<u64>()  // lamports
310                + size_of::<u64>()  // data len
311                + size_of::<Pubkey>() // owner
312                + size_of::<u8>() // executable
313                + size_of::<u64>(); // rent_epoch
314                if copy_account_data {
315                    size += account.get_data().len();
316                }
317            }
318        }
319    }
320    size += size_of::<u64>() // instruction data len
321         + instruction_data.len() // instruction data
322         + size_of::<Pubkey>(); // program id
323
324    let mut s = Serializer::new(size, MM_INPUT_START, false, copy_account_data);
325
326    let mut accounts_metadata: Vec<SerializedAccountMetadata> = Vec::with_capacity(accounts.len());
327    s.write::<u64>((accounts.len() as u64).to_le());
328    for account in accounts {
329        match account {
330            SerializeAccount::Duplicate(position) => {
331                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
332                s.write(position as u8);
333            }
334            SerializeAccount::Account(_, mut account) => {
335                s.write::<u8>(NON_DUP_MARKER);
336                s.write::<u8>(account.is_signer() as u8);
337                s.write::<u8>(account.is_writable() as u8);
338                let vm_key_addr = s.write_all(account.get_key().as_ref());
339                let vm_lamports_addr = s.write::<u64>(account.get_lamports().to_le());
340                s.write::<u64>((account.get_data().len() as u64).to_le());
341                let vm_data_addr = s.write_account(&mut account)?;
342                let vm_owner_addr = s.write_all(account.get_owner().as_ref());
343                #[allow(deprecated)]
344                s.write::<u8>(account.is_executable() as u8);
345                let rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
346                    u64::MAX
347                } else {
348                    account.get_rent_epoch()
349                };
350                s.write::<u64>(rent_epoch.to_le());
351                accounts_metadata.push(SerializedAccountMetadata {
352                    original_data_len: account.get_data().len(),
353                    vm_key_addr,
354                    vm_lamports_addr,
355                    vm_owner_addr,
356                    vm_data_addr,
357                });
358            }
359        };
360    }
361    s.write::<u64>((instruction_data.len() as u64).to_le());
362    s.write_all(instruction_data);
363    s.write_all(program_id.as_ref());
364
365    let (mem, regions) = s.finish();
366    Ok((mem, regions, accounts_metadata))
367}
368
369fn deserialize_parameters_unaligned<I: IntoIterator<Item = usize>>(
370    transaction_context: &TransactionContext,
371    instruction_context: &InstructionContext,
372    copy_account_data: bool,
373    buffer: &[u8],
374    account_lengths: I,
375) -> Result<(), InstructionError> {
376    let mut start = size_of::<u64>(); // number of accounts
377    for (instruction_account_index, pre_len) in (0..instruction_context
378        .get_number_of_instruction_accounts())
379        .zip(account_lengths.into_iter())
380    {
381        let duplicate =
382            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
383        start += 1; // is_dup
384        if duplicate.is_none() {
385            let mut borrowed_account = instruction_context
386                .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
387            start += size_of::<u8>(); // is_signer
388            start += size_of::<u8>(); // is_writable
389            start += size_of::<Pubkey>(); // key
390            let lamports = buffer
391                .get(start..start.saturating_add(8))
392                .map(<[u8; 8]>::try_from)
393                .and_then(Result::ok)
394                .map(u64::from_le_bytes)
395                .ok_or(InstructionError::InvalidArgument)?;
396            if borrowed_account.get_lamports() != lamports {
397                borrowed_account.set_lamports(lamports)?;
398            }
399            start += size_of::<u64>() // lamports
400                + size_of::<u64>(); // data length
401            if copy_account_data {
402                let data = buffer
403                    .get(start..start + pre_len)
404                    .ok_or(InstructionError::InvalidArgument)?;
405                // The redundant check helps to avoid the expensive data comparison if we can
406                match borrowed_account
407                    .can_data_be_resized(data.len())
408                    .and_then(|_| borrowed_account.can_data_be_changed())
409                {
410                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
411                    Err(err) if borrowed_account.get_data() != data => return Err(err),
412                    _ => {}
413                }
414                start += pre_len; // data
415            }
416            start += size_of::<Pubkey>() // owner
417                + size_of::<u8>() // executable
418                + size_of::<u64>(); // rent_epoch
419        }
420    }
421    Ok(())
422}
423
424fn serialize_parameters_aligned(
425    accounts: Vec<SerializeAccount>,
426    instruction_data: &[u8],
427    program_id: &Pubkey,
428    copy_account_data: bool,
429    mask_out_rent_epoch_in_vm_serialization: bool,
430) -> Result<
431    (
432        AlignedMemory<HOST_ALIGN>,
433        Vec<MemoryRegion>,
434        Vec<SerializedAccountMetadata>,
435    ),
436    InstructionError,
437> {
438    let mut accounts_metadata = Vec::with_capacity(accounts.len());
439    // Calculate size in order to alloc once
440    let mut size = size_of::<u64>();
441    for account in &accounts {
442        size += 1; // dup
443        match account {
444            SerializeAccount::Duplicate(_) => size += 7, // padding to 64-bit aligned
445            SerializeAccount::Account(_, account) => {
446                let data_len = account.get_data().len();
447                size += size_of::<u8>() // is_signer
448                + size_of::<u8>() // is_writable
449                + size_of::<u8>() // executable
450                + size_of::<u32>() // original_data_len
451                + size_of::<Pubkey>()  // key
452                + size_of::<Pubkey>() // owner
453                + size_of::<u64>()  // lamports
454                + size_of::<u64>()  // data len
455                + MAX_PERMITTED_DATA_INCREASE
456                + size_of::<u64>(); // rent epoch
457                if copy_account_data {
458                    size += data_len + (data_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
459                } else {
460                    size += BPF_ALIGN_OF_U128;
461                }
462            }
463        }
464    }
465    size += size_of::<u64>() // data len
466    + instruction_data.len()
467    + size_of::<Pubkey>(); // program id;
468
469    let mut s = Serializer::new(size, MM_INPUT_START, true, copy_account_data);
470
471    // Serialize into the buffer
472    s.write::<u64>((accounts.len() as u64).to_le());
473    for account in accounts {
474        match account {
475            SerializeAccount::Account(_, mut borrowed_account) => {
476                s.write::<u8>(NON_DUP_MARKER);
477                s.write::<u8>(borrowed_account.is_signer() as u8);
478                s.write::<u8>(borrowed_account.is_writable() as u8);
479                #[allow(deprecated)]
480                s.write::<u8>(borrowed_account.is_executable() as u8);
481                s.write_all(&[0u8, 0, 0, 0]);
482                let vm_key_addr = s.write_all(borrowed_account.get_key().as_ref());
483                let vm_owner_addr = s.write_all(borrowed_account.get_owner().as_ref());
484                let vm_lamports_addr = s.write::<u64>(borrowed_account.get_lamports().to_le());
485                s.write::<u64>((borrowed_account.get_data().len() as u64).to_le());
486                let vm_data_addr = s.write_account(&mut borrowed_account)?;
487                let rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
488                    u64::MAX
489                } else {
490                    borrowed_account.get_rent_epoch()
491                };
492                s.write::<u64>(rent_epoch.to_le());
493                accounts_metadata.push(SerializedAccountMetadata {
494                    original_data_len: borrowed_account.get_data().len(),
495                    vm_key_addr,
496                    vm_owner_addr,
497                    vm_lamports_addr,
498                    vm_data_addr,
499                });
500            }
501            SerializeAccount::Duplicate(position) => {
502                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
503                s.write::<u8>(position as u8);
504                s.write_all(&[0u8, 0, 0, 0, 0, 0, 0]);
505            }
506        };
507    }
508    s.write::<u64>((instruction_data.len() as u64).to_le());
509    s.write_all(instruction_data);
510    s.write_all(program_id.as_ref());
511
512    let (mem, regions) = s.finish();
513    Ok((mem, regions, accounts_metadata))
514}
515
516fn deserialize_parameters_aligned<I: IntoIterator<Item = usize>>(
517    transaction_context: &TransactionContext,
518    instruction_context: &InstructionContext,
519    copy_account_data: bool,
520    buffer: &[u8],
521    account_lengths: I,
522) -> Result<(), InstructionError> {
523    let mut start = size_of::<u64>(); // number of accounts
524    for (instruction_account_index, pre_len) in (0..instruction_context
525        .get_number_of_instruction_accounts())
526        .zip(account_lengths.into_iter())
527    {
528        let duplicate =
529            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
530        start += size_of::<u8>(); // position
531        if duplicate.is_some() {
532            start += 7; // padding to 64-bit aligned
533        } else {
534            let mut borrowed_account = instruction_context
535                .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
536            start += size_of::<u8>() // is_signer
537                + size_of::<u8>() // is_writable
538                + size_of::<u8>() // executable
539                + size_of::<u32>() // original_data_len
540                + size_of::<Pubkey>(); // key
541            let owner = buffer
542                .get(start..start + size_of::<Pubkey>())
543                .ok_or(InstructionError::InvalidArgument)?;
544            start += size_of::<Pubkey>(); // owner
545            let lamports = buffer
546                .get(start..start.saturating_add(8))
547                .map(<[u8; 8]>::try_from)
548                .and_then(Result::ok)
549                .map(u64::from_le_bytes)
550                .ok_or(InstructionError::InvalidArgument)?;
551            if borrowed_account.get_lamports() != lamports {
552                borrowed_account.set_lamports(lamports)?;
553            }
554            start += size_of::<u64>(); // lamports
555            let post_len = buffer
556                .get(start..start.saturating_add(8))
557                .map(<[u8; 8]>::try_from)
558                .and_then(Result::ok)
559                .map(u64::from_le_bytes)
560                .ok_or(InstructionError::InvalidArgument)? as usize;
561            start += size_of::<u64>(); // data length
562            if post_len.saturating_sub(pre_len) > MAX_PERMITTED_DATA_INCREASE
563                || post_len > MAX_PERMITTED_DATA_LENGTH as usize
564            {
565                return Err(InstructionError::InvalidRealloc);
566            }
567            // The redundant check helps to avoid the expensive data comparison if we can
568            let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
569            if copy_account_data {
570                let data = buffer
571                    .get(start..start + post_len)
572                    .ok_or(InstructionError::InvalidArgument)?;
573                match borrowed_account
574                    .can_data_be_resized(post_len)
575                    .and_then(|_| borrowed_account.can_data_be_changed())
576                {
577                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
578                    Err(err) if borrowed_account.get_data() != data => return Err(err),
579                    _ => {}
580                }
581                start += pre_len; // data
582            } else {
583                // See Serializer::write_account() as to why we have this
584                // padding before the realloc region here.
585                start += BPF_ALIGN_OF_U128.saturating_sub(alignment_offset);
586                let data = buffer
587                    .get(start..start + MAX_PERMITTED_DATA_INCREASE)
588                    .ok_or(InstructionError::InvalidArgument)?;
589                match borrowed_account
590                    .can_data_be_resized(post_len)
591                    .and_then(|_| borrowed_account.can_data_be_changed())
592                {
593                    Ok(()) => {
594                        borrowed_account.set_data_length(post_len)?;
595                        let allocated_bytes = post_len.saturating_sub(pre_len);
596                        if allocated_bytes > 0 {
597                            borrowed_account
598                                .get_data_mut()?
599                                .get_mut(pre_len..pre_len.saturating_add(allocated_bytes))
600                                .ok_or(InstructionError::InvalidArgument)?
601                                .copy_from_slice(
602                                    data.get(0..allocated_bytes)
603                                        .ok_or(InstructionError::InvalidArgument)?,
604                                );
605                        }
606                    }
607                    Err(err) if borrowed_account.get_data().len() != post_len => return Err(err),
608                    _ => {}
609                }
610            }
611            start += MAX_PERMITTED_DATA_INCREASE;
612            start += alignment_offset;
613            start += size_of::<u64>(); // rent_epoch
614            if borrowed_account.get_owner().to_bytes() != owner {
615                // Change the owner at the end so that we are allowed to change the lamports and data before
616                borrowed_account.set_owner(owner)?;
617            }
618        }
619    }
620    Ok(())
621}
622
623pub(crate) fn account_data_region_memory_state(account: &BorrowedAccount<'_>) -> MemoryState {
624    if account.can_data_be_changed().is_ok() {
625        if account.is_shared() {
626            MemoryState::Cow(account.get_index_in_transaction() as u64)
627        } else {
628            MemoryState::Writable
629        }
630    } else {
631        MemoryState::Readable
632    }
633}
634
635#[cfg(test)]
636#[allow(clippy::indexing_slicing)]
637mod tests {
638    use {
639        super::*,
640        solana_account::{Account, AccountSharedData, WritableAccount},
641        solana_account_info::AccountInfo,
642        solana_program_entrypoint::deserialize,
643        solana_program_runtime::with_mock_invoke_context,
644        solana_sdk_ids::bpf_loader,
645        solana_transaction_context::InstructionAccount,
646        std::{
647            cell::RefCell,
648            mem::transmute,
649            rc::Rc,
650            slice::{self, from_raw_parts, from_raw_parts_mut},
651        },
652    };
653
654    fn deduplicated_instruction_accounts(
655        transaction_indexes: &[IndexOfAccount],
656        is_writable: fn(usize) -> bool,
657    ) -> Vec<InstructionAccount> {
658        transaction_indexes
659            .iter()
660            .enumerate()
661            .map(|(index_in_instruction, index_in_transaction)| {
662                let index_in_callee = transaction_indexes
663                    .get(0..index_in_instruction)
664                    .unwrap()
665                    .iter()
666                    .position(|account_index| account_index == index_in_transaction)
667                    .unwrap_or(index_in_instruction);
668                InstructionAccount {
669                    index_in_transaction: *index_in_transaction,
670                    index_in_caller: *index_in_transaction,
671                    index_in_callee: index_in_callee as IndexOfAccount,
672                    is_signer: false,
673                    is_writable: is_writable(index_in_instruction),
674                }
675            })
676            .collect()
677    }
678
679    #[test]
680    fn test_serialize_parameters_with_many_accounts() {
681        struct TestCase {
682            num_ix_accounts: usize,
683            append_dup_account: bool,
684            expected_err: Option<InstructionError>,
685            name: &'static str,
686        }
687
688        for copy_account_data in [true] {
689            for TestCase {
690                num_ix_accounts,
691                append_dup_account,
692                expected_err,
693                name,
694            } in [
695                TestCase {
696                    name: "serialize max accounts with cap",
697                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
698                    append_dup_account: false,
699                    expected_err: None,
700                },
701                TestCase {
702                    name: "serialize too many accounts with cap",
703                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS) + 1,
704                    append_dup_account: false,
705                    expected_err: Some(InstructionError::MaxAccountsExceeded),
706                },
707                TestCase {
708                    name: "serialize too many accounts and append dup with cap",
709                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
710                    append_dup_account: true,
711                    expected_err: Some(InstructionError::MaxAccountsExceeded),
712                },
713            ] {
714                let program_id = solana_pubkey::new_rand();
715                let mut transaction_accounts = vec![(
716                    program_id,
717                    AccountSharedData::from(Account {
718                        lamports: 0,
719                        data: vec![],
720                        owner: bpf_loader::id(),
721                        executable: true,
722                        rent_epoch: 0,
723                    }),
724                )];
725                for _ in 0..num_ix_accounts {
726                    transaction_accounts.push((
727                        Pubkey::new_unique(),
728                        AccountSharedData::from(Account {
729                            lamports: 0,
730                            data: vec![],
731                            owner: program_id,
732                            executable: false,
733                            rent_epoch: 0,
734                        }),
735                    ));
736                }
737
738                let transaction_accounts_indexes: Vec<IndexOfAccount> =
739                    (1..(num_ix_accounts + 1) as u16).collect();
740                let mut instruction_accounts =
741                    deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false);
742                if append_dup_account {
743                    instruction_accounts.push(instruction_accounts.last().cloned().unwrap());
744                }
745                let program_indices = [0];
746                let instruction_data = vec![];
747
748                with_mock_invoke_context!(
749                    invoke_context,
750                    transaction_context,
751                    transaction_accounts
752                );
753                invoke_context
754                    .transaction_context
755                    .get_next_instruction_context()
756                    .unwrap()
757                    .configure(&program_indices, &instruction_accounts, &instruction_data);
758                invoke_context.push().unwrap();
759                let instruction_context = invoke_context
760                    .transaction_context
761                    .get_current_instruction_context()
762                    .unwrap();
763
764                let serialization_result = serialize_parameters(
765                    invoke_context.transaction_context,
766                    instruction_context,
767                    copy_account_data,
768                    true, // mask_out_rent_epoch_in_vm_serialization
769                );
770                assert_eq!(
771                    serialization_result.as_ref().err(),
772                    expected_err.as_ref(),
773                    "{name} test case failed",
774                );
775                if expected_err.is_some() {
776                    continue;
777                }
778
779                let (mut serialized, regions, _account_lengths) = serialization_result.unwrap();
780                let mut serialized_regions = concat_regions(&regions);
781                let (de_program_id, de_accounts, de_instruction_data) = unsafe {
782                    deserialize(
783                        if copy_account_data {
784                            serialized.as_slice_mut()
785                        } else {
786                            serialized_regions.as_slice_mut()
787                        }
788                        .first_mut()
789                        .unwrap() as *mut u8,
790                    )
791                };
792                assert_eq!(de_program_id, &program_id);
793                assert_eq!(de_instruction_data, &instruction_data);
794                for account_info in de_accounts {
795                    let index_in_transaction = invoke_context
796                        .transaction_context
797                        .find_index_of_account(account_info.key)
798                        .unwrap();
799                    let account = invoke_context
800                        .transaction_context
801                        .accounts()
802                        .try_borrow(index_in_transaction)
803                        .unwrap();
804                    assert_eq!(account.lamports(), account_info.lamports());
805                    assert_eq!(account.data(), &account_info.data.borrow()[..]);
806                    assert_eq!(account.owner(), account_info.owner);
807                    assert_eq!(account.executable(), account_info.executable);
808                    assert_eq!(u64::MAX, account_info.rent_epoch);
809                }
810            }
811        }
812    }
813
814    #[test]
815    fn test_serialize_parameters() {
816        for copy_account_data in [false, true] {
817            let program_id = solana_pubkey::new_rand();
818            let transaction_accounts = vec![
819                (
820                    program_id,
821                    AccountSharedData::from(Account {
822                        lamports: 0,
823                        data: vec![],
824                        owner: bpf_loader::id(),
825                        executable: true,
826                        rent_epoch: 0,
827                    }),
828                ),
829                (
830                    solana_pubkey::new_rand(),
831                    AccountSharedData::from(Account {
832                        lamports: 1,
833                        data: vec![1u8, 2, 3, 4, 5],
834                        owner: bpf_loader::id(),
835                        executable: false,
836                        rent_epoch: 100,
837                    }),
838                ),
839                (
840                    solana_pubkey::new_rand(),
841                    AccountSharedData::from(Account {
842                        lamports: 2,
843                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
844                        owner: bpf_loader::id(),
845                        executable: true,
846                        rent_epoch: 200,
847                    }),
848                ),
849                (
850                    solana_pubkey::new_rand(),
851                    AccountSharedData::from(Account {
852                        lamports: 3,
853                        data: vec![],
854                        owner: bpf_loader::id(),
855                        executable: false,
856                        rent_epoch: 3100,
857                    }),
858                ),
859                (
860                    solana_pubkey::new_rand(),
861                    AccountSharedData::from(Account {
862                        lamports: 4,
863                        data: vec![1u8, 2, 3, 4, 5],
864                        owner: bpf_loader::id(),
865                        executable: false,
866                        rent_epoch: 100,
867                    }),
868                ),
869                (
870                    solana_pubkey::new_rand(),
871                    AccountSharedData::from(Account {
872                        lamports: 5,
873                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
874                        owner: bpf_loader::id(),
875                        executable: true,
876                        rent_epoch: 200,
877                    }),
878                ),
879                (
880                    solana_pubkey::new_rand(),
881                    AccountSharedData::from(Account {
882                        lamports: 6,
883                        data: vec![],
884                        owner: bpf_loader::id(),
885                        executable: false,
886                        rent_epoch: 3100,
887                    }),
888                ),
889            ];
890            let instruction_accounts =
891                deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
892            let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
893            let program_indices = [0];
894            let mut original_accounts = transaction_accounts.clone();
895            with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
896            invoke_context
897                .transaction_context
898                .get_next_instruction_context()
899                .unwrap()
900                .configure(&program_indices, &instruction_accounts, &instruction_data);
901            invoke_context.push().unwrap();
902            let instruction_context = invoke_context
903                .transaction_context
904                .get_current_instruction_context()
905                .unwrap();
906
907            // check serialize_parameters_aligned
908            let (mut serialized, regions, accounts_metadata) = serialize_parameters(
909                invoke_context.transaction_context,
910                instruction_context,
911                copy_account_data,
912                true, // mask_out_rent_epoch_in_vm_serialization
913            )
914            .unwrap();
915
916            let mut serialized_regions = concat_regions(&regions);
917            if copy_account_data {
918                assert_eq!(serialized.as_slice(), serialized_regions.as_slice());
919            }
920            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
921                deserialize(
922                    if copy_account_data {
923                        serialized.as_slice_mut()
924                    } else {
925                        serialized_regions.as_slice_mut()
926                    }
927                    .first_mut()
928                    .unwrap() as *mut u8,
929                )
930            };
931
932            assert_eq!(&program_id, de_program_id);
933            assert_eq!(instruction_data, de_instruction_data);
934            assert_eq!(
935                (de_instruction_data.first().unwrap() as *const u8).align_offset(BPF_ALIGN_OF_U128),
936                0
937            );
938            for account_info in de_accounts {
939                let index_in_transaction = invoke_context
940                    .transaction_context
941                    .find_index_of_account(account_info.key)
942                    .unwrap();
943                let account = invoke_context
944                    .transaction_context
945                    .accounts()
946                    .try_borrow(index_in_transaction)
947                    .unwrap();
948                assert_eq!(account.lamports(), account_info.lamports());
949                assert_eq!(account.data(), &account_info.data.borrow()[..]);
950                assert_eq!(account.owner(), account_info.owner);
951                assert_eq!(account.executable(), account_info.executable);
952                assert_eq!(u64::MAX, account_info.rent_epoch);
953
954                assert_eq!(
955                    (*account_info.lamports.borrow() as *const u64).align_offset(BPF_ALIGN_OF_U128),
956                    0
957                );
958                assert_eq!(
959                    account_info
960                        .data
961                        .borrow()
962                        .as_ptr()
963                        .align_offset(BPF_ALIGN_OF_U128),
964                    0
965                );
966            }
967
968            deserialize_parameters(
969                invoke_context.transaction_context,
970                instruction_context,
971                copy_account_data,
972                serialized.as_slice(),
973                &accounts_metadata,
974            )
975            .unwrap();
976            for (index_in_transaction, (_key, original_account)) in
977                original_accounts.iter().enumerate()
978            {
979                let account = invoke_context
980                    .transaction_context
981                    .accounts()
982                    .try_borrow(index_in_transaction as IndexOfAccount)
983                    .unwrap();
984                assert_eq!(&*account, original_account);
985            }
986
987            // check serialize_parameters_unaligned
988            original_accounts
989                .first_mut()
990                .unwrap()
991                .1
992                .set_owner(bpf_loader_deprecated::id());
993            invoke_context
994                .transaction_context
995                .get_account_at_index(0)
996                .unwrap()
997                .try_borrow_mut()
998                .unwrap()
999                .set_owner(bpf_loader_deprecated::id());
1000
1001            let (mut serialized, regions, account_lengths) = serialize_parameters(
1002                invoke_context.transaction_context,
1003                instruction_context,
1004                copy_account_data,
1005                true, // mask_out_rent_epoch_in_vm_serialization
1006            )
1007            .unwrap();
1008            let mut serialized_regions = concat_regions(&regions);
1009
1010            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
1011                deserialize_unaligned(
1012                    if copy_account_data {
1013                        serialized.as_slice_mut()
1014                    } else {
1015                        serialized_regions.as_slice_mut()
1016                    }
1017                    .first_mut()
1018                    .unwrap() as *mut u8,
1019                )
1020            };
1021            assert_eq!(&program_id, de_program_id);
1022            assert_eq!(instruction_data, de_instruction_data);
1023            for account_info in de_accounts {
1024                let index_in_transaction = invoke_context
1025                    .transaction_context
1026                    .find_index_of_account(account_info.key)
1027                    .unwrap();
1028                let account = invoke_context
1029                    .transaction_context
1030                    .accounts()
1031                    .try_borrow(index_in_transaction)
1032                    .unwrap();
1033                assert_eq!(account.lamports(), account_info.lamports());
1034                assert_eq!(account.data(), &account_info.data.borrow()[..]);
1035                assert_eq!(account.owner(), account_info.owner);
1036                assert_eq!(account.executable(), account_info.executable);
1037                assert_eq!(u64::MAX, account_info.rent_epoch);
1038            }
1039
1040            deserialize_parameters(
1041                invoke_context.transaction_context,
1042                instruction_context,
1043                copy_account_data,
1044                serialized.as_slice(),
1045                &account_lengths,
1046            )
1047            .unwrap();
1048            for (index_in_transaction, (_key, original_account)) in
1049                original_accounts.iter().enumerate()
1050            {
1051                let account = invoke_context
1052                    .transaction_context
1053                    .accounts()
1054                    .try_borrow(index_in_transaction as IndexOfAccount)
1055                    .unwrap();
1056                assert_eq!(&*account, original_account);
1057            }
1058        }
1059    }
1060
1061    #[test]
1062    fn test_serialize_parameters_mask_out_rent_epoch_in_vm_serialization() {
1063        for mask_out_rent_epoch_in_vm_serialization in [false, true] {
1064            let transaction_accounts = vec![
1065                (
1066                    solana_pubkey::new_rand(),
1067                    AccountSharedData::from(Account {
1068                        lamports: 0,
1069                        data: vec![],
1070                        owner: bpf_loader::id(),
1071                        executable: true,
1072                        rent_epoch: 0,
1073                    }),
1074                ),
1075                (
1076                    solana_pubkey::new_rand(),
1077                    AccountSharedData::from(Account {
1078                        lamports: 1,
1079                        data: vec![1u8, 2, 3, 4, 5],
1080                        owner: bpf_loader::id(),
1081                        executable: false,
1082                        rent_epoch: 100,
1083                    }),
1084                ),
1085                (
1086                    solana_pubkey::new_rand(),
1087                    AccountSharedData::from(Account {
1088                        lamports: 2,
1089                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1090                        owner: bpf_loader::id(),
1091                        executable: true,
1092                        rent_epoch: 200,
1093                    }),
1094                ),
1095                (
1096                    solana_pubkey::new_rand(),
1097                    AccountSharedData::from(Account {
1098                        lamports: 3,
1099                        data: vec![],
1100                        owner: bpf_loader::id(),
1101                        executable: false,
1102                        rent_epoch: 300,
1103                    }),
1104                ),
1105                (
1106                    solana_pubkey::new_rand(),
1107                    AccountSharedData::from(Account {
1108                        lamports: 4,
1109                        data: vec![1u8, 2, 3, 4, 5],
1110                        owner: bpf_loader::id(),
1111                        executable: false,
1112                        rent_epoch: 100,
1113                    }),
1114                ),
1115                (
1116                    solana_pubkey::new_rand(),
1117                    AccountSharedData::from(Account {
1118                        lamports: 5,
1119                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
1120                        owner: bpf_loader::id(),
1121                        executable: true,
1122                        rent_epoch: 200,
1123                    }),
1124                ),
1125                (
1126                    solana_pubkey::new_rand(),
1127                    AccountSharedData::from(Account {
1128                        lamports: 6,
1129                        data: vec![],
1130                        owner: bpf_loader::id(),
1131                        executable: false,
1132                        rent_epoch: 3100,
1133                    }),
1134                ),
1135            ];
1136            let instruction_accounts =
1137                deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
1138            let instruction_data = vec![];
1139            let program_indices = [0];
1140            let mut original_accounts = transaction_accounts.clone();
1141            with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1142            invoke_context
1143                .transaction_context
1144                .get_next_instruction_context()
1145                .unwrap()
1146                .configure(&program_indices, &instruction_accounts, &instruction_data);
1147            invoke_context.push().unwrap();
1148            let instruction_context = invoke_context
1149                .transaction_context
1150                .get_current_instruction_context()
1151                .unwrap();
1152
1153            // check serialize_parameters_aligned
1154            let (_serialized, regions, _accounts_metadata) = serialize_parameters(
1155                invoke_context.transaction_context,
1156                instruction_context,
1157                true,
1158                mask_out_rent_epoch_in_vm_serialization,
1159            )
1160            .unwrap();
1161
1162            let mut serialized_regions = concat_regions(&regions);
1163            let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1164                deserialize(serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8)
1165            };
1166
1167            for account_info in de_accounts {
1168                let index_in_transaction = invoke_context
1169                    .transaction_context
1170                    .find_index_of_account(account_info.key)
1171                    .unwrap();
1172                let account = invoke_context
1173                    .transaction_context
1174                    .accounts()
1175                    .try_borrow(index_in_transaction)
1176                    .unwrap();
1177                let expected_rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
1178                    u64::MAX
1179                } else {
1180                    account.rent_epoch()
1181                };
1182                assert_eq!(expected_rent_epoch, account_info.rent_epoch);
1183            }
1184
1185            // check serialize_parameters_unaligned
1186            original_accounts
1187                .first_mut()
1188                .unwrap()
1189                .1
1190                .set_owner(bpf_loader_deprecated::id());
1191            invoke_context
1192                .transaction_context
1193                .get_account_at_index(0)
1194                .unwrap()
1195                .try_borrow_mut()
1196                .unwrap()
1197                .set_owner(bpf_loader_deprecated::id());
1198
1199            let (_serialized, regions, _account_lengths) = serialize_parameters(
1200                invoke_context.transaction_context,
1201                instruction_context,
1202                true,
1203                mask_out_rent_epoch_in_vm_serialization,
1204            )
1205            .unwrap();
1206            let mut serialized_regions = concat_regions(&regions);
1207
1208            let (_de_program_id, de_accounts, _de_instruction_data) = unsafe {
1209                deserialize_unaligned(
1210                    serialized_regions.as_slice_mut().first_mut().unwrap() as *mut u8
1211                )
1212            };
1213            for account_info in de_accounts {
1214                let index_in_transaction = invoke_context
1215                    .transaction_context
1216                    .find_index_of_account(account_info.key)
1217                    .unwrap();
1218                let account = invoke_context
1219                    .transaction_context
1220                    .accounts()
1221                    .try_borrow(index_in_transaction)
1222                    .unwrap();
1223                let expected_rent_epoch = if mask_out_rent_epoch_in_vm_serialization {
1224                    u64::MAX
1225                } else {
1226                    account.rent_epoch()
1227                };
1228                assert_eq!(expected_rent_epoch, account_info.rent_epoch);
1229            }
1230        }
1231    }
1232
1233    // the old bpf_loader in-program deserializer bpf_loader::id()
1234    #[deny(unsafe_op_in_unsafe_fn)]
1235    unsafe fn deserialize_unaligned<'a>(
1236        input: *mut u8,
1237    ) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
1238        // this boring boilerplate struct is needed until inline const...
1239        struct Ptr<T>(std::marker::PhantomData<T>);
1240        impl<T> Ptr<T> {
1241            const COULD_BE_UNALIGNED: bool = std::mem::align_of::<T>() > 1;
1242
1243            #[inline(always)]
1244            fn read_possibly_unaligned(input: *mut u8, offset: usize) -> T {
1245                unsafe {
1246                    let src = input.add(offset) as *const T;
1247                    if Self::COULD_BE_UNALIGNED {
1248                        src.read_unaligned()
1249                    } else {
1250                        src.read()
1251                    }
1252                }
1253            }
1254
1255            // rustc inserts debug_assert! for misaligned pointer dereferences when
1256            // deserializing, starting from [1]. so, use std::mem::transmute as the last resort
1257            // while preventing clippy from complaining to suggest not to use it.
1258            // [1]: https://github.com/rust-lang/rust/commit/22a7a19f9333bc1fcba97ce444a3515cb5fb33e6
1259            // as for the ub nature of the misaligned pointer dereference, this is
1260            // acceptable in this code, given that this is cfg(test) and it's cared only with
1261            // x86-64 and the target only incurs some performance penalty, not like segfaults
1262            // in other targets.
1263            #[inline(always)]
1264            fn ref_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a T {
1265                #[allow(clippy::transmute_ptr_to_ref)]
1266                unsafe {
1267                    transmute(input.add(offset) as *const T)
1268                }
1269            }
1270
1271            // See ref_possibly_unaligned's comment
1272            #[inline(always)]
1273            fn mut_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a mut T {
1274                #[allow(clippy::transmute_ptr_to_ref)]
1275                unsafe {
1276                    transmute(input.add(offset) as *mut T)
1277                }
1278            }
1279        }
1280
1281        let mut offset: usize = 0;
1282
1283        // number of accounts present
1284
1285        let num_accounts = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1286        offset += size_of::<u64>();
1287
1288        // account Infos
1289
1290        let mut accounts = Vec::with_capacity(num_accounts);
1291        for _ in 0..num_accounts {
1292            let dup_info = Ptr::<u8>::read_possibly_unaligned(input, offset);
1293            offset += size_of::<u8>();
1294            if dup_info == NON_DUP_MARKER {
1295                let is_signer = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1296                offset += size_of::<u8>();
1297
1298                let is_writable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1299                offset += size_of::<u8>();
1300
1301                let key = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1302                offset += size_of::<Pubkey>();
1303
1304                let lamports = Rc::new(RefCell::new(Ptr::mut_possibly_unaligned(input, offset)));
1305                offset += size_of::<u64>();
1306
1307                let data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1308                offset += size_of::<u64>();
1309
1310                let data = Rc::new(RefCell::new(unsafe {
1311                    from_raw_parts_mut(input.add(offset), data_len)
1312                }));
1313                offset += data_len;
1314
1315                let owner: &Pubkey = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1316                offset += size_of::<Pubkey>();
1317
1318                let executable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1319                offset += size_of::<u8>();
1320
1321                let rent_epoch = Ptr::<u64>::read_possibly_unaligned(input, offset);
1322                offset += size_of::<u64>();
1323
1324                accounts.push(AccountInfo {
1325                    key,
1326                    is_signer,
1327                    is_writable,
1328                    lamports,
1329                    data,
1330                    owner,
1331                    executable,
1332                    rent_epoch,
1333                });
1334            } else {
1335                // duplicate account, clone the original
1336                accounts.push(accounts.get(dup_info as usize).unwrap().clone());
1337            }
1338        }
1339
1340        // instruction data
1341
1342        let instruction_data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1343        offset += size_of::<u64>();
1344
1345        let instruction_data = unsafe { from_raw_parts(input.add(offset), instruction_data_len) };
1346        offset += instruction_data_len;
1347
1348        // program Id
1349
1350        let program_id = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1351
1352        (program_id, accounts, instruction_data)
1353    }
1354
1355    fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
1356        let len = regions.iter().fold(0, |len, region| len + region.len) as usize;
1357        let mut mem = AlignedMemory::zero_filled(len);
1358        for region in regions {
1359            let host_slice = unsafe {
1360                slice::from_raw_parts(region.host_addr.get() as *const u8, region.len as usize)
1361            };
1362            mem.as_slice_mut()[(region.vm_addr - MM_INPUT_START) as usize..][..region.len as usize]
1363                .copy_from_slice(host_slice)
1364        }
1365        mem
1366    }
1367}