157 lines
6.1 KiB
Rust
157 lines
6.1 KiB
Rust
// 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;
|
|
}
|