// 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> { 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>, } 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> { 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; }