v0.2.14-pre.005
This commit is contained in:
156
crates/ksp-program-api/tests/security_hardening.rs
Normal file
156
crates/ksp-program-api/tests/security_hardening.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
// file: crates/ksp-program-api/tests/security_hardening.rs
|
||||
// version: 1
|
||||
|
||||
//! Adversarial and bound-safety canaries for the Program API foundation.
|
||||
|
||||
const HOSTILE_MARKER: &str = "PROGRAM-SECRET-CANARY";
|
||||
const MALFORMED_OPCODE: u8 = 0xFF_u8;
|
||||
const PROGRAM_ID_BYTES: [u8; 32] = [0xD1_u8; 32];
|
||||
|
||||
struct BoundsObserved {
|
||||
account_count: usize,
|
||||
data_len: usize,
|
||||
}
|
||||
|
||||
struct BoundedDecoder {
|
||||
program_ids: [ksp_program_api::Pubkey; 1],
|
||||
}
|
||||
|
||||
impl BoundedDecoder {
|
||||
fn new() -> Self {
|
||||
return Self { program_ids: [ksp_program_api::Pubkey::new_from_array(PROGRAM_ID_BYTES)] };
|
||||
}
|
||||
}
|
||||
|
||||
impl ksp_program_api::ProgramInstructionDecoder for BoundedDecoder {
|
||||
type Decoded = BoundsObserved;
|
||||
|
||||
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||
return &self.program_ids;
|
||||
}
|
||||
|
||||
fn recognize(&self, instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||
if instruction.program_id() == &self.program_ids[0] {
|
||||
return ksp_program_api::ProgramInstructionRecognition::ExactMatch;
|
||||
}
|
||||
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||
}
|
||||
|
||||
fn decode(
|
||||
&self,
|
||||
instruction: &ksp_program_api::ProgramInstruction,
|
||||
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||
if instruction.data().first() == std::option::Option::Some(&MALFORMED_OPCODE) {
|
||||
return std::result::Result::Err(ksp_program_api::Error::new(
|
||||
ksp_program_api::ErrorCode::new("program_test", "malformed_instruction"),
|
||||
"malformed external Program instruction",
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundsObserved {
|
||||
account_count: instruction.accounts().len(),
|
||||
data_len: instruction.data().len(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
struct BoundlessDecoded {
|
||||
marker: std::rc::Rc<std::cell::Cell<u8>>,
|
||||
}
|
||||
|
||||
struct BoundlessOutputDecoder;
|
||||
|
||||
impl ksp_program_api::ProgramInstructionDecoder for BoundlessOutputDecoder {
|
||||
type Decoded = BoundlessDecoded;
|
||||
|
||||
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||
return &[];
|
||||
}
|
||||
|
||||
fn recognize(&self, _instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||
}
|
||||
|
||||
fn decode(
|
||||
&self,
|
||||
_instruction: &ksp_program_api::ProgramInstruction,
|
||||
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundlessDecoded {
|
||||
marker: std::rc::Rc::new(std::cell::Cell::new(0x5A_u8)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_max_interface_instruction_crosses_decoder_boundary_without_new_contract() {
|
||||
let decoder = BoundedDecoder::new();
|
||||
let account = ksp_program_api::ProgramAccountMeta::readonly(ksp_program_api::Pubkey::new_from_array([0xD2_u8; 32]), false);
|
||||
let accounts = std::vec![account; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS];
|
||||
let data = std::vec![0x5A_u8; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN];
|
||||
let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], accounts, data);
|
||||
assert!(instruction.is_ok());
|
||||
let instruction = match instruction {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
|
||||
assert!(outcome.is_ok());
|
||||
let outcome = match outcome {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let observed = match outcome {
|
||||
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||
_ => return,
|
||||
};
|
||||
assert_eq!(observed.account_count, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS);
|
||||
assert_eq!(observed.data_len, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN);
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_malformed_payload_error_path_does_not_gain_automatic_payload_echo() {
|
||||
let decoder = BoundedDecoder::new();
|
||||
let mut payload = std::vec![MALFORMED_OPCODE];
|
||||
payload.extend_from_slice(HOSTILE_MARKER.as_bytes());
|
||||
let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], std::vec![], payload);
|
||||
assert!(instruction.is_ok());
|
||||
let instruction = match instruction {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
|
||||
assert!(outcome.is_err());
|
||||
let error = match outcome {
|
||||
std::result::Result::Err(value) => value,
|
||||
std::result::Result::Ok(_) => return,
|
||||
};
|
||||
assert_eq!(error.code().domain(), "program_test");
|
||||
assert_eq!(error.code().code(), "malformed_instruction");
|
||||
assert!(!std::format!("{error}").contains(HOSTILE_MARKER));
|
||||
assert!(!std::format!("{error:?}").contains(HOSTILE_MARKER));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_005_associated_decoded_type_keeps_no_implicit_debug_clone_send_or_sync_bound() {
|
||||
let decoder = BoundlessOutputDecoder;
|
||||
let instruction = ksp_program_api::ProgramInstruction::try_new(ksp_program_api::Pubkey::new_from_array([0xD3_u8; 32]), std::vec![], std::vec![]);
|
||||
assert!(instruction.is_ok());
|
||||
let instruction = match instruction {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction);
|
||||
assert!(outcome.is_ok());
|
||||
let outcome = match outcome {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(std::format!("{outcome:?}"), "Decoded");
|
||||
let decoded = match outcome {
|
||||
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||
_ => return,
|
||||
};
|
||||
assert_eq!(decoded.marker.get(), 0x5A_u8);
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user