Skip to content

Commit 9ca26f3

Browse files
committed
program: safe deserialize config keys from state
1 parent 51a4f39 commit 9ca26f3

File tree

2 files changed

+71
-15
lines changed

2 files changed

+71
-15
lines changed

program/src/processor.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,33 @@ fn safe_deserialize_config_keys(input: &[u8]) -> Result<ConfigKeys, ProgramError
4949
}
5050
}
5151

52+
// [Core BPF]: Similar to `safe_deserialize_config_keys`, this helper serves to
53+
// avoid over-allocations of memory, especially when the trailing data is
54+
// invalid.
55+
//
56+
// Consider a case where an account is malformed, and the `ShortU16` vector
57+
// length actually stores a value larger than the buffer itself. The original
58+
// Config program builtin can handle the initial memory allocation, eventually
59+
// returning `ProgramError::InvalidAccountData` when bincode throws an EOF
60+
// error. However, the BPF version will panic on OOM before it can successfully
61+
// return `ProgramError::InvalidAccountData`.
62+
//
63+
// This helper's purpose is solely to catch malformed `ConfigKeys` data before
64+
// a memory allocation panic can occur, to ensure maximum backwards
65+
// compatibility with the original builtin.
66+
fn safe_deserialize_config_keys_from_state(input: &[u8]) -> Result<ConfigKeys, ProgramError> {
67+
let (vector_len, offset) = solana_program::short_vec::decode_shortu16_len(input)
68+
.map_err(|_| ProgramError::InvalidAccountData)?;
69+
if input[offset..].len() / (32 + 1) < vector_len {
70+
Err(ProgramError::InvalidAccountData)
71+
} else {
72+
bincode::deserialize(input).map_err(|err| {
73+
msg!("Unable to deserialize config account: {}", err);
74+
ProgramError::InvalidAccountData
75+
})
76+
}
77+
}
78+
5279
/// Config program processor.
5380
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
5481
let key_list = safe_deserialize_config_keys(input)?;
@@ -60,11 +87,7 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> P
6087
return Err(ProgramError::InvalidAccountOwner);
6188
}
6289

63-
let current_data: ConfigKeys = bincode::deserialize(&config_account.try_borrow_data()?)
64-
.map_err(|err| {
65-
msg!("Unable to deserialize config account: {}", err);
66-
ProgramError::InvalidAccountData
67-
})?;
90+
let current_data = safe_deserialize_config_keys_from_state(&config_account.try_borrow_data()?)?;
6891

6992
let current_signer_keys: Vec<Pubkey> = current_data
7093
.keys

program/tests/functional.rs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fn test_process_create_ok() {
8383
&[(config, config_account)],
8484
&[
8585
Check::success(),
86-
Check::compute_units(580),
86+
Check::compute_units(612),
8787
Check::account(&config)
8888
.data(
8989
&bincode::serialize(&(ConfigKeys { keys: vec![] }, MyConfig::default()))
@@ -111,7 +111,7 @@ fn test_process_store_ok() {
111111
&[(config, config_account)],
112112
&[
113113
Check::success(),
114-
Check::compute_units(580),
114+
Check::compute_units(612),
115115
Check::account(&config)
116116
.data(&bincode::serialize(&(ConfigKeys { keys }, my_config)).unwrap())
117117
.build(),
@@ -186,7 +186,7 @@ fn test_process_store_with_additional_signers() {
186186
],
187187
&[
188188
Check::success(),
189-
Check::compute_units(3_234),
189+
Check::compute_units(3_267),
190190
Check::account(&config)
191191
.data(&bincode::serialize(&(ConfigKeys { keys }, my_config)).unwrap())
192192
.build(),
@@ -334,7 +334,7 @@ fn test_config_updates() {
334334
(signer0, AccountSharedData::default()),
335335
(signer1, AccountSharedData::default()),
336336
],
337-
&[Check::success(), Check::compute_units(3_234)],
337+
&[Check::success(), Check::compute_units(3_267)],
338338
);
339339

340340
// Use this for next invoke.
@@ -352,7 +352,7 @@ fn test_config_updates() {
352352
],
353353
&[
354354
Check::success(),
355-
Check::compute_units(3_235),
355+
Check::compute_units(3_268),
356356
Check::account(&config)
357357
.data(&bincode::serialize(&(ConfigKeys { keys }, new_config)).unwrap())
358358
.build(),
@@ -465,7 +465,7 @@ fn test_config_update_contains_duplicates_fails() {
465465
(signer0, AccountSharedData::default()),
466466
(signer1, AccountSharedData::default()),
467467
],
468-
&[Check::success(), Check::compute_units(3_234)],
468+
&[Check::success(), Check::compute_units(3_267)],
469469
);
470470

471471
// Attempt update with duplicate signer inputs.
@@ -509,7 +509,7 @@ fn test_config_updates_requiring_config() {
509509
],
510510
&[
511511
Check::success(),
512-
Check::compute_units(3_330),
512+
Check::compute_units(3_363),
513513
Check::account(&config)
514514
.data(&bincode::serialize(&(ConfigKeys { keys: keys.clone() }, my_config)).unwrap())
515515
.build(),
@@ -530,7 +530,7 @@ fn test_config_updates_requiring_config() {
530530
],
531531
&[
532532
Check::success(),
533-
Check::compute_units(3_330),
533+
Check::compute_units(3_363),
534534
Check::account(&config)
535535
.data(&bincode::serialize(&(ConfigKeys { keys }, new_config)).unwrap())
536536
.build(),
@@ -624,7 +624,7 @@ fn test_maximum_keys_input() {
624624
let result = mollusk.process_and_validate_instruction(
625625
&instruction,
626626
&[(config, config_account)],
627-
&[Check::success(), Check::compute_units(25_243)],
627+
&[Check::success(), Check::compute_units(25_275)],
628628
);
629629

630630
// Use this for next invoke.
@@ -637,7 +637,7 @@ fn test_maximum_keys_input() {
637637
let result = mollusk.process_and_validate_instruction(
638638
&instruction,
639639
&[(config, updated_config_account)],
640-
&[Check::success(), Check::compute_units(25_243)],
640+
&[Check::success(), Check::compute_units(25_275)],
641641
);
642642

643643
// Use this for next invoke.
@@ -715,3 +715,36 @@ fn test_safe_deserialize() {
715715
&[Check::err(ProgramError::InvalidInstructionData)],
716716
);
717717
}
718+
719+
#[test]
720+
fn test_safe_deserialize_from_state() {
721+
let mollusk = setup();
722+
723+
let config = Pubkey::new_unique();
724+
725+
let keys = vec![(config, false), (config, true)];
726+
let my_config = MyConfig::new(42);
727+
728+
// Input doesn't matter for this test.
729+
let instruction = config_instruction::store(&config, false, keys.clone(), &my_config);
730+
731+
// Store the keys in the config account, but give it the wrong length.
732+
let config_account = {
733+
let space = bincode::serialized_size(&ConfigKeys { keys: keys.clone() }).unwrap() as usize;
734+
let lamports = mollusk.sysvars.rent.minimum_balance(space);
735+
736+
let mut data = vec![0; space];
737+
bincode::serialize_into(&mut data, &ConfigKeys { keys }).unwrap();
738+
data[0] = 255; // length of 255.
739+
740+
let mut account = AccountSharedData::new(lamports, space, &solana_config_program::id());
741+
account.set_data_from_slice(&data);
742+
account
743+
};
744+
745+
mollusk.process_and_validate_instruction(
746+
&instruction,
747+
&[(config, config_account)],
748+
&[Check::err(ProgramError::InvalidAccountData)],
749+
);
750+
}

0 commit comments

Comments
 (0)