@@ -60,16 +60,61 @@ fn get_lookup_table_status(
6060 }
6161}
6262
63- // [Core BPF]: Locally-implemented
64- // `solana_sdk::program_utils::limited_deserialize`.
65- fn limited_deserialize < T > ( input : & [ u8 ] ) -> Result < T , ProgramError >
66- where
67- T : serde:: de:: DeserializeOwned ,
68- {
69- solana_program:: program_utils:: limited_deserialize (
70- input, 1232 , // [Core BPF]: See `solana_sdk::packet::PACKET_DATA_SIZE`
71- )
72- . map_err ( |_| ProgramError :: InvalidInstructionData )
63+ // Maximum input buffer length that can be deserialized.
64+ // See `solana_sdk::packet::PACKET_DATA_SIZE`.
65+ const MAX_INPUT_LEN : usize = 1232 ;
66+ // Maximum vector length for new keys to be appended to a lookup table,
67+ // provided to the `ExtendLookupTable` instruction.
68+ // See comments below for `safe_deserialize_instruction`.
69+ //
70+ // Take the maximum input length and subtract 4 bytes for the discriminator,
71+ // 8 bytes for the vector length, then divide that by the size of a `Pubkey`.
72+ const MAX_NEW_KEYS_VECTOR_LEN : usize = ( MAX_INPUT_LEN - 4 - 8 ) / 32 ;
73+
74+ // Stub of `AddressLookupTableInstruction` for partial deserialization.
75+ // Keep in sync with the program's instructions in `instructions`.
76+ #[ allow( clippy:: enum_variant_names) ]
77+ #[ cfg_attr( test, derive( strum_macros:: EnumIter ) ) ]
78+ #[ derive( serde:: Serialize , serde:: Deserialize , PartialEq ) ]
79+ enum InstructionStub {
80+ CreateLookupTable ,
81+ FreezeLookupTable ,
82+ ExtendLookupTable { vector_len : u64 } ,
83+ DeactivateLookupTable ,
84+ CloseLookupTable ,
85+ }
86+
87+ // [Core BPF]: The original Address Lookup Table builtin leverages the
88+ // `solana_sdk::program_utils::limited_deserialize` method to cap the length of
89+ // the input buffer at `MAX_INPUT_LEN` (1232). As a result, any input buffer
90+ // larger than `MAX_INPUT_LEN` will abort deserialization and return
91+ // `InstructionError::InvalidInstructionData`.
92+ //
93+ // Howevever, since `ExtendLookupTable` contains a vector of `Pubkey`, the
94+ // `limited_deserialize` method will still read the vector's length and attempt
95+ // to allocate a vector of the designated size. For extremely large length
96+ // values, this can cause the initial allocation of a large vector to exhuast
97+ // the BPF program's heap before deserialization can proceed.
98+ //
99+ // To mitigate this memory issue, the BPF version of the program has been
100+ // designed to "peek" the length value for `ExtendLookupTable`, and ensure it
101+ // cannot allocate a vector that would otherwise violate the input buffer
102+ // length restriction.
103+ fn safe_deserialize_instruction (
104+ input : & [ u8 ] ,
105+ ) -> Result < AddressLookupTableInstruction , ProgramError > {
106+ match bincode:: deserialize :: < InstructionStub > ( input)
107+ . map_err ( |_| ProgramError :: InvalidInstructionData ) ?
108+ {
109+ InstructionStub :: ExtendLookupTable { vector_len }
110+ if vector_len as usize > MAX_NEW_KEYS_VECTOR_LEN =>
111+ {
112+ return Err ( ProgramError :: InvalidInstructionData ) ;
113+ }
114+ _ => { }
115+ }
116+ solana_program:: program_utils:: limited_deserialize ( input, MAX_INPUT_LEN as u64 )
117+ . map_err ( |_| ProgramError :: InvalidInstructionData )
73118}
74119
75120// [Core BPF]: Feature "FKAcEvNgSY79RpqsPNUV5gDyumopH4cEHqUxyfm8b8Ap"
@@ -461,7 +506,7 @@ fn process_close_lookup_table(program_id: &Pubkey, accounts: &[AccountInfo]) ->
461506/// Processes a
462507/// `solana_programs_address_lookup_table::instruction::AddressLookupTableInstruction`
463508pub fn process ( program_id : & Pubkey , accounts : & [ AccountInfo ] , input : & [ u8 ] ) -> ProgramResult {
464- let instruction = limited_deserialize ( input) ?;
509+ let instruction = safe_deserialize_instruction ( input) ?;
465510 match instruction {
466511 AddressLookupTableInstruction :: CreateLookupTable {
467512 recent_slot,
@@ -488,3 +533,58 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> P
488533 }
489534 }
490535}
536+
537+ #[ cfg( test) ]
538+ mod tests {
539+ use super :: * ;
540+
541+ fn assert_instruction_serialization (
542+ stub : & InstructionStub ,
543+ instruction : & AddressLookupTableInstruction ,
544+ len : usize ,
545+ ) {
546+ assert_eq ! (
547+ bincode:: serialize( & stub) . unwrap( ) ,
548+ bincode:: serialize( & instruction) . unwrap( ) [ 0 ..len] ,
549+ )
550+ }
551+
552+ #[ test]
553+ fn test_instruction_stubs ( ) {
554+ assert_eq ! (
555+ <InstructionStub as strum:: IntoEnumIterator >:: iter( ) . count( ) ,
556+ <AddressLookupTableInstruction as strum:: IntoEnumIterator >:: iter( ) . count( ) ,
557+ ) ;
558+
559+ assert_instruction_serialization (
560+ & InstructionStub :: CreateLookupTable ,
561+ & AddressLookupTableInstruction :: CreateLookupTable {
562+ recent_slot : 0 ,
563+ bump_seed : 0 ,
564+ } ,
565+ 4 ,
566+ ) ;
567+ assert_instruction_serialization (
568+ & InstructionStub :: FreezeLookupTable ,
569+ & AddressLookupTableInstruction :: FreezeLookupTable ,
570+ 4 ,
571+ ) ;
572+ assert_instruction_serialization (
573+ & InstructionStub :: ExtendLookupTable { vector_len : 4 } ,
574+ & AddressLookupTableInstruction :: ExtendLookupTable {
575+ new_addresses : vec ! [ Pubkey :: new_unique( ) ; 4 ] ,
576+ } ,
577+ 12 , // Check the vector length as well.
578+ ) ;
579+ assert_instruction_serialization (
580+ & InstructionStub :: DeactivateLookupTable ,
581+ & AddressLookupTableInstruction :: DeactivateLookupTable ,
582+ 4 ,
583+ ) ;
584+ assert_instruction_serialization (
585+ & InstructionStub :: CloseLookupTable ,
586+ & AddressLookupTableInstruction :: CloseLookupTable ,
587+ 4 ,
588+ ) ;
589+ }
590+ }
0 commit comments