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) -> Result<
193    (
194        AlignedMemory<HOST_ALIGN>,
195        Vec<MemoryRegion>,
196        Vec<SerializedAccountMetadata>,
197    ),
198    InstructionError,
199> {
200    let num_ix_accounts = instruction_context.get_number_of_instruction_accounts();
201    if num_ix_accounts > MAX_INSTRUCTION_ACCOUNTS as IndexOfAccount {
202        return Err(InstructionError::MaxAccountsExceeded);
203    }
204
205    let (program_id, is_loader_deprecated) = {
206        let program_account =
207            instruction_context.try_borrow_last_program_account(transaction_context)?;
208        (
209            *program_account.get_key(),
210            *program_account.get_owner() == bpf_loader_deprecated::id(),
211        )
212    };
213
214    let accounts = (0..instruction_context.get_number_of_instruction_accounts())
215        .map(|instruction_account_index| {
216            if let Some(index) = instruction_context
217                .is_instruction_account_duplicate(instruction_account_index)
218                .unwrap()
219            {
220                SerializeAccount::Duplicate(index)
221            } else {
222                let account = instruction_context
223                    .try_borrow_instruction_account(transaction_context, instruction_account_index)
224                    .unwrap();
225                SerializeAccount::Account(instruction_account_index, account)
226            }
227        })
228        // fun fact: jemalloc is good at caching tiny allocations like this one,
229        // so collecting here is actually faster than passing the iterator
230        // around, since the iterator does the work to produce its items each
231        // time it's iterated on.
232        .collect::<Vec<_>>();
233
234    if is_loader_deprecated {
235        serialize_parameters_unaligned(
236            accounts,
237            instruction_context.get_instruction_data(),
238            &program_id,
239            copy_account_data,
240        )
241    } else {
242        serialize_parameters_aligned(
243            accounts,
244            instruction_context.get_instruction_data(),
245            &program_id,
246            copy_account_data,
247        )
248    }
249}
250
251pub(crate) fn deserialize_parameters(
252    transaction_context: &TransactionContext,
253    instruction_context: &InstructionContext,
254    copy_account_data: bool,
255    buffer: &[u8],
256    accounts_metadata: &[SerializedAccountMetadata],
257) -> Result<(), InstructionError> {
258    let is_loader_deprecated = *instruction_context
259        .try_borrow_last_program_account(transaction_context)?
260        .get_owner()
261        == bpf_loader_deprecated::id();
262    let account_lengths = accounts_metadata.iter().map(|a| a.original_data_len);
263    if is_loader_deprecated {
264        deserialize_parameters_unaligned(
265            transaction_context,
266            instruction_context,
267            copy_account_data,
268            buffer,
269            account_lengths,
270        )
271    } else {
272        deserialize_parameters_aligned(
273            transaction_context,
274            instruction_context,
275            copy_account_data,
276            buffer,
277            account_lengths,
278        )
279    }
280}
281
282fn serialize_parameters_unaligned(
283    accounts: Vec<SerializeAccount>,
284    instruction_data: &[u8],
285    program_id: &Pubkey,
286    copy_account_data: bool,
287) -> Result<
288    (
289        AlignedMemory<HOST_ALIGN>,
290        Vec<MemoryRegion>,
291        Vec<SerializedAccountMetadata>,
292    ),
293    InstructionError,
294> {
295    // Calculate size in order to alloc once
296    let mut size = size_of::<u64>();
297    for account in &accounts {
298        size += 1; // dup
299        match account {
300            SerializeAccount::Duplicate(_) => {}
301            SerializeAccount::Account(_, account) => {
302                size += size_of::<u8>() // is_signer
303                + size_of::<u8>() // is_writable
304                + size_of::<Pubkey>() // key
305                + size_of::<u64>()  // lamports
306                + size_of::<u64>()  // data len
307                + size_of::<Pubkey>() // owner
308                + size_of::<u8>() // executable
309                + size_of::<u64>(); // rent_epoch
310                if copy_account_data {
311                    size += account.get_data().len();
312                }
313            }
314        }
315    }
316    size += size_of::<u64>() // instruction data len
317         + instruction_data.len() // instruction data
318         + size_of::<Pubkey>(); // program id
319
320    let mut s = Serializer::new(size, MM_INPUT_START, false, copy_account_data);
321
322    let mut accounts_metadata: Vec<SerializedAccountMetadata> = Vec::with_capacity(accounts.len());
323    s.write::<u64>((accounts.len() as u64).to_le());
324    for account in accounts {
325        match account {
326            SerializeAccount::Duplicate(position) => {
327                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
328                s.write(position as u8);
329            }
330            SerializeAccount::Account(_, mut account) => {
331                s.write::<u8>(NON_DUP_MARKER);
332                s.write::<u8>(account.is_signer() as u8);
333                s.write::<u8>(account.is_writable() as u8);
334                let vm_key_addr = s.write_all(account.get_key().as_ref());
335                let vm_lamports_addr = s.write::<u64>(account.get_lamports().to_le());
336                s.write::<u64>((account.get_data().len() as u64).to_le());
337                let vm_data_addr = s.write_account(&mut account)?;
338                let vm_owner_addr = s.write_all(account.get_owner().as_ref());
339                #[allow(deprecated)]
340                s.write::<u8>(account.is_executable() as u8);
341                s.write::<u64>((account.get_rent_epoch()).to_le());
342                accounts_metadata.push(SerializedAccountMetadata {
343                    original_data_len: account.get_data().len(),
344                    vm_key_addr,
345                    vm_lamports_addr,
346                    vm_owner_addr,
347                    vm_data_addr,
348                });
349            }
350        };
351    }
352    s.write::<u64>((instruction_data.len() as u64).to_le());
353    s.write_all(instruction_data);
354    s.write_all(program_id.as_ref());
355
356    let (mem, regions) = s.finish();
357    Ok((mem, regions, accounts_metadata))
358}
359
360fn deserialize_parameters_unaligned<I: IntoIterator<Item = usize>>(
361    transaction_context: &TransactionContext,
362    instruction_context: &InstructionContext,
363    copy_account_data: bool,
364    buffer: &[u8],
365    account_lengths: I,
366) -> Result<(), InstructionError> {
367    let mut start = size_of::<u64>(); // number of accounts
368    for (instruction_account_index, pre_len) in (0..instruction_context
369        .get_number_of_instruction_accounts())
370        .zip(account_lengths.into_iter())
371    {
372        let duplicate =
373            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
374        start += 1; // is_dup
375        if duplicate.is_none() {
376            let mut borrowed_account = instruction_context
377                .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
378            start += size_of::<u8>(); // is_signer
379            start += size_of::<u8>(); // is_writable
380            start += size_of::<Pubkey>(); // key
381            let lamports = buffer
382                .get(start..start.saturating_add(8))
383                .map(<[u8; 8]>::try_from)
384                .and_then(Result::ok)
385                .map(u64::from_le_bytes)
386                .ok_or(InstructionError::InvalidArgument)?;
387            if borrowed_account.get_lamports() != lamports {
388                borrowed_account.set_lamports(lamports)?;
389            }
390            start += size_of::<u64>() // lamports
391                + size_of::<u64>(); // data length
392            if copy_account_data {
393                let data = buffer
394                    .get(start..start + pre_len)
395                    .ok_or(InstructionError::InvalidArgument)?;
396                // The redundant check helps to avoid the expensive data comparison if we can
397                match borrowed_account
398                    .can_data_be_resized(data.len())
399                    .and_then(|_| borrowed_account.can_data_be_changed())
400                {
401                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
402                    Err(err) if borrowed_account.get_data() != data => return Err(err),
403                    _ => {}
404                }
405                start += pre_len; // data
406            }
407            start += size_of::<Pubkey>() // owner
408                + size_of::<u8>() // executable
409                + size_of::<u64>(); // rent_epoch
410        }
411    }
412    Ok(())
413}
414
415fn serialize_parameters_aligned(
416    accounts: Vec<SerializeAccount>,
417    instruction_data: &[u8],
418    program_id: &Pubkey,
419    copy_account_data: bool,
420) -> Result<
421    (
422        AlignedMemory<HOST_ALIGN>,
423        Vec<MemoryRegion>,
424        Vec<SerializedAccountMetadata>,
425    ),
426    InstructionError,
427> {
428    let mut accounts_metadata = Vec::with_capacity(accounts.len());
429    // Calculate size in order to alloc once
430    let mut size = size_of::<u64>();
431    for account in &accounts {
432        size += 1; // dup
433        match account {
434            SerializeAccount::Duplicate(_) => size += 7, // padding to 64-bit aligned
435            SerializeAccount::Account(_, account) => {
436                let data_len = account.get_data().len();
437                size += size_of::<u8>() // is_signer
438                + size_of::<u8>() // is_writable
439                + size_of::<u8>() // executable
440                + size_of::<u32>() // original_data_len
441                + size_of::<Pubkey>()  // key
442                + size_of::<Pubkey>() // owner
443                + size_of::<u64>()  // lamports
444                + size_of::<u64>()  // data len
445                + MAX_PERMITTED_DATA_INCREASE
446                + size_of::<u64>(); // rent epoch
447                if copy_account_data {
448                    size += data_len + (data_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
449                } else {
450                    size += BPF_ALIGN_OF_U128;
451                }
452            }
453        }
454    }
455    size += size_of::<u64>() // data len
456    + instruction_data.len()
457    + size_of::<Pubkey>(); // program id;
458
459    let mut s = Serializer::new(size, MM_INPUT_START, true, copy_account_data);
460
461    // Serialize into the buffer
462    s.write::<u64>((accounts.len() as u64).to_le());
463    for account in accounts {
464        match account {
465            SerializeAccount::Account(_, mut borrowed_account) => {
466                s.write::<u8>(NON_DUP_MARKER);
467                s.write::<u8>(borrowed_account.is_signer() as u8);
468                s.write::<u8>(borrowed_account.is_writable() as u8);
469                #[allow(deprecated)]
470                s.write::<u8>(borrowed_account.is_executable() as u8);
471                s.write_all(&[0u8, 0, 0, 0]);
472                let vm_key_addr = s.write_all(borrowed_account.get_key().as_ref());
473                let vm_owner_addr = s.write_all(borrowed_account.get_owner().as_ref());
474                let vm_lamports_addr = s.write::<u64>(borrowed_account.get_lamports().to_le());
475                s.write::<u64>((borrowed_account.get_data().len() as u64).to_le());
476                let vm_data_addr = s.write_account(&mut borrowed_account)?;
477                s.write::<u64>((borrowed_account.get_rent_epoch()).to_le());
478                accounts_metadata.push(SerializedAccountMetadata {
479                    original_data_len: borrowed_account.get_data().len(),
480                    vm_key_addr,
481                    vm_owner_addr,
482                    vm_lamports_addr,
483                    vm_data_addr,
484                });
485            }
486            SerializeAccount::Duplicate(position) => {
487                accounts_metadata.push(accounts_metadata.get(position as usize).unwrap().clone());
488                s.write::<u8>(position as u8);
489                s.write_all(&[0u8, 0, 0, 0, 0, 0, 0]);
490            }
491        };
492    }
493    s.write::<u64>((instruction_data.len() as u64).to_le());
494    s.write_all(instruction_data);
495    s.write_all(program_id.as_ref());
496
497    let (mem, regions) = s.finish();
498    Ok((mem, regions, accounts_metadata))
499}
500
501fn deserialize_parameters_aligned<I: IntoIterator<Item = usize>>(
502    transaction_context: &TransactionContext,
503    instruction_context: &InstructionContext,
504    copy_account_data: bool,
505    buffer: &[u8],
506    account_lengths: I,
507) -> Result<(), InstructionError> {
508    let mut start = size_of::<u64>(); // number of accounts
509    for (instruction_account_index, pre_len) in (0..instruction_context
510        .get_number_of_instruction_accounts())
511        .zip(account_lengths.into_iter())
512    {
513        let duplicate =
514            instruction_context.is_instruction_account_duplicate(instruction_account_index)?;
515        start += size_of::<u8>(); // position
516        if duplicate.is_some() {
517            start += 7; // padding to 64-bit aligned
518        } else {
519            let mut borrowed_account = instruction_context
520                .try_borrow_instruction_account(transaction_context, instruction_account_index)?;
521            start += size_of::<u8>() // is_signer
522                + size_of::<u8>() // is_writable
523                + size_of::<u8>() // executable
524                + size_of::<u32>() // original_data_len
525                + size_of::<Pubkey>(); // key
526            let owner = buffer
527                .get(start..start + size_of::<Pubkey>())
528                .ok_or(InstructionError::InvalidArgument)?;
529            start += size_of::<Pubkey>(); // owner
530            let lamports = buffer
531                .get(start..start.saturating_add(8))
532                .map(<[u8; 8]>::try_from)
533                .and_then(Result::ok)
534                .map(u64::from_le_bytes)
535                .ok_or(InstructionError::InvalidArgument)?;
536            if borrowed_account.get_lamports() != lamports {
537                borrowed_account.set_lamports(lamports)?;
538            }
539            start += size_of::<u64>(); // lamports
540            let post_len = buffer
541                .get(start..start.saturating_add(8))
542                .map(<[u8; 8]>::try_from)
543                .and_then(Result::ok)
544                .map(u64::from_le_bytes)
545                .ok_or(InstructionError::InvalidArgument)? as usize;
546            start += size_of::<u64>(); // data length
547            if post_len.saturating_sub(pre_len) > MAX_PERMITTED_DATA_INCREASE
548                || post_len > MAX_PERMITTED_DATA_LENGTH as usize
549            {
550                return Err(InstructionError::InvalidRealloc);
551            }
552            // The redundant check helps to avoid the expensive data comparison if we can
553            let alignment_offset = (pre_len as *const u8).align_offset(BPF_ALIGN_OF_U128);
554            if copy_account_data {
555                let data = buffer
556                    .get(start..start + post_len)
557                    .ok_or(InstructionError::InvalidArgument)?;
558                match borrowed_account
559                    .can_data_be_resized(post_len)
560                    .and_then(|_| borrowed_account.can_data_be_changed())
561                {
562                    Ok(()) => borrowed_account.set_data_from_slice(data)?,
563                    Err(err) if borrowed_account.get_data() != data => return Err(err),
564                    _ => {}
565                }
566                start += pre_len; // data
567            } else {
568                // See Serializer::write_account() as to why we have this
569                // padding before the realloc region here.
570                start += BPF_ALIGN_OF_U128.saturating_sub(alignment_offset);
571                let data = buffer
572                    .get(start..start + MAX_PERMITTED_DATA_INCREASE)
573                    .ok_or(InstructionError::InvalidArgument)?;
574                match borrowed_account
575                    .can_data_be_resized(post_len)
576                    .and_then(|_| borrowed_account.can_data_be_changed())
577                {
578                    Ok(()) => {
579                        borrowed_account.set_data_length(post_len)?;
580                        let allocated_bytes = post_len.saturating_sub(pre_len);
581                        if allocated_bytes > 0 {
582                            borrowed_account
583                                .get_data_mut()?
584                                .get_mut(pre_len..pre_len.saturating_add(allocated_bytes))
585                                .ok_or(InstructionError::InvalidArgument)?
586                                .copy_from_slice(
587                                    data.get(0..allocated_bytes)
588                                        .ok_or(InstructionError::InvalidArgument)?,
589                                );
590                        }
591                    }
592                    Err(err) if borrowed_account.get_data().len() != post_len => return Err(err),
593                    _ => {}
594                }
595            }
596            start += MAX_PERMITTED_DATA_INCREASE;
597            start += alignment_offset;
598            start += size_of::<u64>(); // rent_epoch
599            if borrowed_account.get_owner().to_bytes() != owner {
600                // Change the owner at the end so that we are allowed to change the lamports and data before
601                borrowed_account.set_owner(owner)?;
602            }
603        }
604    }
605    Ok(())
606}
607
608pub(crate) fn account_data_region_memory_state(account: &BorrowedAccount<'_>) -> MemoryState {
609    if account.can_data_be_changed().is_ok() {
610        if account.is_shared() {
611            MemoryState::Cow(account.get_index_in_transaction() as u64)
612        } else {
613            MemoryState::Writable
614        }
615    } else {
616        MemoryState::Readable
617    }
618}
619
620#[cfg(test)]
621#[allow(clippy::indexing_slicing)]
622mod tests {
623    use {
624        super::*,
625        solana_account::{Account, AccountSharedData, WritableAccount},
626        solana_account_info::AccountInfo,
627        solana_program_entrypoint::deserialize,
628        solana_program_runtime::with_mock_invoke_context,
629        solana_sdk_ids::bpf_loader,
630        solana_transaction_context::InstructionAccount,
631        std::{
632            cell::RefCell,
633            mem::transmute,
634            rc::Rc,
635            slice::{self, from_raw_parts, from_raw_parts_mut},
636        },
637    };
638
639    fn deduplicated_instruction_accounts(
640        transaction_indexes: &[IndexOfAccount],
641        is_writable: fn(usize) -> bool,
642    ) -> Vec<InstructionAccount> {
643        transaction_indexes
644            .iter()
645            .enumerate()
646            .map(|(index_in_instruction, index_in_transaction)| {
647                let index_in_callee = transaction_indexes
648                    .get(0..index_in_instruction)
649                    .unwrap()
650                    .iter()
651                    .position(|account_index| account_index == index_in_transaction)
652                    .unwrap_or(index_in_instruction);
653                InstructionAccount {
654                    index_in_transaction: *index_in_transaction,
655                    index_in_caller: *index_in_transaction,
656                    index_in_callee: index_in_callee as IndexOfAccount,
657                    is_signer: false,
658                    is_writable: is_writable(index_in_instruction),
659                }
660            })
661            .collect()
662    }
663
664    #[test]
665    fn test_serialize_parameters_with_many_accounts() {
666        struct TestCase {
667            num_ix_accounts: usize,
668            append_dup_account: bool,
669            expected_err: Option<InstructionError>,
670            name: &'static str,
671        }
672
673        for copy_account_data in [true] {
674            for TestCase {
675                num_ix_accounts,
676                append_dup_account,
677                expected_err,
678                name,
679            } in [
680                TestCase {
681                    name: "serialize max accounts with cap",
682                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
683                    append_dup_account: false,
684                    expected_err: None,
685                },
686                TestCase {
687                    name: "serialize too many accounts with cap",
688                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS) + 1,
689                    append_dup_account: false,
690                    expected_err: Some(InstructionError::MaxAccountsExceeded),
691                },
692                TestCase {
693                    name: "serialize too many accounts and append dup with cap",
694                    num_ix_accounts: usize::from(MAX_INSTRUCTION_ACCOUNTS),
695                    append_dup_account: true,
696                    expected_err: Some(InstructionError::MaxAccountsExceeded),
697                },
698            ] {
699                let program_id = solana_pubkey::new_rand();
700                let mut transaction_accounts = vec![(
701                    program_id,
702                    AccountSharedData::from(Account {
703                        lamports: 0,
704                        data: vec![],
705                        owner: bpf_loader::id(),
706                        executable: true,
707                        rent_epoch: 0,
708                    }),
709                )];
710                for _ in 0..num_ix_accounts {
711                    transaction_accounts.push((
712                        Pubkey::new_unique(),
713                        AccountSharedData::from(Account {
714                            lamports: 0,
715                            data: vec![],
716                            owner: program_id,
717                            executable: false,
718                            rent_epoch: 0,
719                        }),
720                    ));
721                }
722
723                let transaction_accounts_indexes: Vec<IndexOfAccount> =
724                    (1..(num_ix_accounts + 1) as u16).collect();
725                let mut instruction_accounts =
726                    deduplicated_instruction_accounts(&transaction_accounts_indexes, |_| false);
727                if append_dup_account {
728                    instruction_accounts.push(instruction_accounts.last().cloned().unwrap());
729                }
730                let program_indices = [0];
731                let instruction_data = vec![];
732
733                with_mock_invoke_context!(
734                    invoke_context,
735                    transaction_context,
736                    transaction_accounts
737                );
738                invoke_context
739                    .transaction_context
740                    .get_next_instruction_context()
741                    .unwrap()
742                    .configure(&program_indices, &instruction_accounts, &instruction_data);
743                invoke_context.push().unwrap();
744                let instruction_context = invoke_context
745                    .transaction_context
746                    .get_current_instruction_context()
747                    .unwrap();
748
749                let serialization_result = serialize_parameters(
750                    invoke_context.transaction_context,
751                    instruction_context,
752                    copy_account_data,
753                );
754                assert_eq!(
755                    serialization_result.as_ref().err(),
756                    expected_err.as_ref(),
757                    "{name} test case failed",
758                );
759                if expected_err.is_some() {
760                    continue;
761                }
762
763                let (mut serialized, regions, _account_lengths) = serialization_result.unwrap();
764                let mut serialized_regions = concat_regions(&regions);
765                let (de_program_id, de_accounts, de_instruction_data) = unsafe {
766                    deserialize(
767                        if copy_account_data {
768                            serialized.as_slice_mut()
769                        } else {
770                            serialized_regions.as_slice_mut()
771                        }
772                        .first_mut()
773                        .unwrap() as *mut u8,
774                    )
775                };
776                assert_eq!(de_program_id, &program_id);
777                assert_eq!(de_instruction_data, &instruction_data);
778                for account_info in de_accounts {
779                    let index_in_transaction = invoke_context
780                        .transaction_context
781                        .find_index_of_account(account_info.key)
782                        .unwrap();
783                    let account = invoke_context
784                        .transaction_context
785                        .get_account_at_index(index_in_transaction)
786                        .unwrap()
787                        .borrow();
788                    assert_eq!(account.lamports(), account_info.lamports());
789                    assert_eq!(account.data(), &account_info.data.borrow()[..]);
790                    assert_eq!(account.owner(), account_info.owner);
791                    assert_eq!(account.executable(), account_info.executable);
792                    assert_eq!(account.rent_epoch(), account_info.rent_epoch);
793                }
794            }
795        }
796    }
797
798    #[test]
799    fn test_serialize_parameters() {
800        for copy_account_data in [false, true] {
801            let program_id = solana_pubkey::new_rand();
802            let transaction_accounts = vec![
803                (
804                    program_id,
805                    AccountSharedData::from(Account {
806                        lamports: 0,
807                        data: vec![],
808                        owner: bpf_loader::id(),
809                        executable: true,
810                        rent_epoch: 0,
811                    }),
812                ),
813                (
814                    solana_pubkey::new_rand(),
815                    AccountSharedData::from(Account {
816                        lamports: 1,
817                        data: vec![1u8, 2, 3, 4, 5],
818                        owner: bpf_loader::id(),
819                        executable: false,
820                        rent_epoch: 100,
821                    }),
822                ),
823                (
824                    solana_pubkey::new_rand(),
825                    AccountSharedData::from(Account {
826                        lamports: 2,
827                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
828                        owner: bpf_loader::id(),
829                        executable: true,
830                        rent_epoch: 200,
831                    }),
832                ),
833                (
834                    solana_pubkey::new_rand(),
835                    AccountSharedData::from(Account {
836                        lamports: 3,
837                        data: vec![],
838                        owner: bpf_loader::id(),
839                        executable: false,
840                        rent_epoch: 3100,
841                    }),
842                ),
843                (
844                    solana_pubkey::new_rand(),
845                    AccountSharedData::from(Account {
846                        lamports: 4,
847                        data: vec![1u8, 2, 3, 4, 5],
848                        owner: bpf_loader::id(),
849                        executable: false,
850                        rent_epoch: 100,
851                    }),
852                ),
853                (
854                    solana_pubkey::new_rand(),
855                    AccountSharedData::from(Account {
856                        lamports: 5,
857                        data: vec![11u8, 12, 13, 14, 15, 16, 17, 18, 19],
858                        owner: bpf_loader::id(),
859                        executable: true,
860                        rent_epoch: 200,
861                    }),
862                ),
863                (
864                    solana_pubkey::new_rand(),
865                    AccountSharedData::from(Account {
866                        lamports: 6,
867                        data: vec![],
868                        owner: bpf_loader::id(),
869                        executable: false,
870                        rent_epoch: 3100,
871                    }),
872                ),
873            ];
874            let instruction_accounts =
875                deduplicated_instruction_accounts(&[1, 1, 2, 3, 4, 4, 5, 6], |index| index >= 4);
876            let instruction_data = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
877            let program_indices = [0];
878            let mut original_accounts = transaction_accounts.clone();
879            with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
880            invoke_context
881                .transaction_context
882                .get_next_instruction_context()
883                .unwrap()
884                .configure(&program_indices, &instruction_accounts, &instruction_data);
885            invoke_context.push().unwrap();
886            let instruction_context = invoke_context
887                .transaction_context
888                .get_current_instruction_context()
889                .unwrap();
890
891            // check serialize_parameters_aligned
892            let (mut serialized, regions, accounts_metadata) = serialize_parameters(
893                invoke_context.transaction_context,
894                instruction_context,
895                copy_account_data,
896            )
897            .unwrap();
898
899            let mut serialized_regions = concat_regions(&regions);
900            if copy_account_data {
901                assert_eq!(serialized.as_slice(), serialized_regions.as_slice());
902            }
903            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
904                deserialize(
905                    if copy_account_data {
906                        serialized.as_slice_mut()
907                    } else {
908                        serialized_regions.as_slice_mut()
909                    }
910                    .first_mut()
911                    .unwrap() as *mut u8,
912                )
913            };
914
915            assert_eq!(&program_id, de_program_id);
916            assert_eq!(instruction_data, de_instruction_data);
917            assert_eq!(
918                (de_instruction_data.first().unwrap() as *const u8).align_offset(BPF_ALIGN_OF_U128),
919                0
920            );
921            for account_info in de_accounts {
922                let index_in_transaction = invoke_context
923                    .transaction_context
924                    .find_index_of_account(account_info.key)
925                    .unwrap();
926                let account = invoke_context
927                    .transaction_context
928                    .get_account_at_index(index_in_transaction)
929                    .unwrap()
930                    .borrow();
931                assert_eq!(account.lamports(), account_info.lamports());
932                assert_eq!(account.data(), &account_info.data.borrow()[..]);
933                assert_eq!(account.owner(), account_info.owner);
934                assert_eq!(account.executable(), account_info.executable);
935                assert_eq!(account.rent_epoch(), account_info.rent_epoch);
936
937                assert_eq!(
938                    (*account_info.lamports.borrow() as *const u64).align_offset(BPF_ALIGN_OF_U128),
939                    0
940                );
941                assert_eq!(
942                    account_info
943                        .data
944                        .borrow()
945                        .as_ptr()
946                        .align_offset(BPF_ALIGN_OF_U128),
947                    0
948                );
949            }
950
951            deserialize_parameters(
952                invoke_context.transaction_context,
953                instruction_context,
954                copy_account_data,
955                serialized.as_slice(),
956                &accounts_metadata,
957            )
958            .unwrap();
959            for (index_in_transaction, (_key, original_account)) in
960                original_accounts.iter().enumerate()
961            {
962                let account = invoke_context
963                    .transaction_context
964                    .get_account_at_index(index_in_transaction as IndexOfAccount)
965                    .unwrap()
966                    .borrow();
967                assert_eq!(&*account, original_account);
968            }
969
970            // check serialize_parameters_unaligned
971            original_accounts
972                .first_mut()
973                .unwrap()
974                .1
975                .set_owner(bpf_loader_deprecated::id());
976            invoke_context
977                .transaction_context
978                .get_account_at_index(0)
979                .unwrap()
980                .borrow_mut()
981                .set_owner(bpf_loader_deprecated::id());
982
983            let (mut serialized, regions, account_lengths) = serialize_parameters(
984                invoke_context.transaction_context,
985                instruction_context,
986                copy_account_data,
987            )
988            .unwrap();
989            let mut serialized_regions = concat_regions(&regions);
990
991            let (de_program_id, de_accounts, de_instruction_data) = unsafe {
992                deserialize_unaligned(
993                    if copy_account_data {
994                        serialized.as_slice_mut()
995                    } else {
996                        serialized_regions.as_slice_mut()
997                    }
998                    .first_mut()
999                    .unwrap() as *mut u8,
1000                )
1001            };
1002            assert_eq!(&program_id, de_program_id);
1003            assert_eq!(instruction_data, de_instruction_data);
1004            for account_info in de_accounts {
1005                let index_in_transaction = invoke_context
1006                    .transaction_context
1007                    .find_index_of_account(account_info.key)
1008                    .unwrap();
1009                let account = invoke_context
1010                    .transaction_context
1011                    .get_account_at_index(index_in_transaction)
1012                    .unwrap()
1013                    .borrow();
1014                assert_eq!(account.lamports(), account_info.lamports());
1015                assert_eq!(account.data(), &account_info.data.borrow()[..]);
1016                assert_eq!(account.owner(), account_info.owner);
1017                assert_eq!(account.executable(), account_info.executable);
1018                assert_eq!(account.rent_epoch(), account_info.rent_epoch);
1019            }
1020
1021            deserialize_parameters(
1022                invoke_context.transaction_context,
1023                instruction_context,
1024                copy_account_data,
1025                serialized.as_slice(),
1026                &account_lengths,
1027            )
1028            .unwrap();
1029            for (index_in_transaction, (_key, original_account)) in
1030                original_accounts.iter().enumerate()
1031            {
1032                let account = invoke_context
1033                    .transaction_context
1034                    .get_account_at_index(index_in_transaction as IndexOfAccount)
1035                    .unwrap()
1036                    .borrow();
1037                assert_eq!(&*account, original_account);
1038            }
1039        }
1040    }
1041
1042    // the old bpf_loader in-program deserializer bpf_loader::id()
1043    #[deny(unsafe_op_in_unsafe_fn)]
1044    unsafe fn deserialize_unaligned<'a>(
1045        input: *mut u8,
1046    ) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
1047        // this boring boilerplate struct is needed until inline const...
1048        struct Ptr<T>(std::marker::PhantomData<T>);
1049        impl<T> Ptr<T> {
1050            const COULD_BE_UNALIGNED: bool = std::mem::align_of::<T>() > 1;
1051
1052            #[inline(always)]
1053            fn read_possibly_unaligned(input: *mut u8, offset: usize) -> T {
1054                unsafe {
1055                    let src = input.add(offset) as *const T;
1056                    if Self::COULD_BE_UNALIGNED {
1057                        src.read_unaligned()
1058                    } else {
1059                        src.read()
1060                    }
1061                }
1062            }
1063
1064            // rustc inserts debug_assert! for misaligned pointer dereferences when
1065            // deserializing, starting from [1]. so, use std::mem::transmute as the last resort
1066            // while preventing clippy from complaining to suggest not to use it.
1067            // [1]: https://github.com/rust-lang/rust/commit/22a7a19f9333bc1fcba97ce444a3515cb5fb33e6
1068            // as for the ub nature of the misaligned pointer dereference, this is
1069            // acceptable in this code, given that this is cfg(test) and it's cared only with
1070            // x86-64 and the target only incurs some performance penalty, not like segfaults
1071            // in other targets.
1072            #[inline(always)]
1073            fn ref_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a T {
1074                #[allow(clippy::transmute_ptr_to_ref)]
1075                unsafe {
1076                    transmute(input.add(offset) as *const T)
1077                }
1078            }
1079
1080            // See ref_possibly_unaligned's comment
1081            #[inline(always)]
1082            fn mut_possibly_unaligned<'a>(input: *mut u8, offset: usize) -> &'a mut T {
1083                #[allow(clippy::transmute_ptr_to_ref)]
1084                unsafe {
1085                    transmute(input.add(offset) as *mut T)
1086                }
1087            }
1088        }
1089
1090        let mut offset: usize = 0;
1091
1092        // number of accounts present
1093
1094        let num_accounts = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1095        offset += size_of::<u64>();
1096
1097        // account Infos
1098
1099        let mut accounts = Vec::with_capacity(num_accounts);
1100        for _ in 0..num_accounts {
1101            let dup_info = Ptr::<u8>::read_possibly_unaligned(input, offset);
1102            offset += size_of::<u8>();
1103            if dup_info == NON_DUP_MARKER {
1104                let is_signer = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1105                offset += size_of::<u8>();
1106
1107                let is_writable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1108                offset += size_of::<u8>();
1109
1110                let key = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1111                offset += size_of::<Pubkey>();
1112
1113                let lamports = Rc::new(RefCell::new(Ptr::mut_possibly_unaligned(input, offset)));
1114                offset += size_of::<u64>();
1115
1116                let data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1117                offset += size_of::<u64>();
1118
1119                let data = Rc::new(RefCell::new(unsafe {
1120                    from_raw_parts_mut(input.add(offset), data_len)
1121                }));
1122                offset += data_len;
1123
1124                let owner: &Pubkey = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1125                offset += size_of::<Pubkey>();
1126
1127                let executable = Ptr::<u8>::read_possibly_unaligned(input, offset) != 0;
1128                offset += size_of::<u8>();
1129
1130                let rent_epoch = Ptr::<u64>::read_possibly_unaligned(input, offset);
1131                offset += size_of::<u64>();
1132
1133                accounts.push(AccountInfo {
1134                    key,
1135                    is_signer,
1136                    is_writable,
1137                    lamports,
1138                    data,
1139                    owner,
1140                    executable,
1141                    rent_epoch,
1142                });
1143            } else {
1144                // duplicate account, clone the original
1145                accounts.push(accounts.get(dup_info as usize).unwrap().clone());
1146            }
1147        }
1148
1149        // instruction data
1150
1151        let instruction_data_len = Ptr::<u64>::read_possibly_unaligned(input, offset) as usize;
1152        offset += size_of::<u64>();
1153
1154        let instruction_data = unsafe { from_raw_parts(input.add(offset), instruction_data_len) };
1155        offset += instruction_data_len;
1156
1157        // program Id
1158
1159        let program_id = Ptr::<Pubkey>::ref_possibly_unaligned(input, offset);
1160
1161        (program_id, accounts, instruction_data)
1162    }
1163
1164    fn concat_regions(regions: &[MemoryRegion]) -> AlignedMemory<HOST_ALIGN> {
1165        let len = regions.iter().fold(0, |len, region| len + region.len) as usize;
1166        let mut mem = AlignedMemory::zero_filled(len);
1167        for region in regions {
1168            let host_slice = unsafe {
1169                slice::from_raw_parts(region.host_addr.get() as *const u8, region.len as usize)
1170            };
1171            mem.as_slice_mut()[(region.vm_addr - MM_INPUT_START) as usize..][..region.len as usize]
1172                .copy_from_slice(host_slice)
1173        }
1174        mem
1175    }
1176}