@@ -168,6 +168,14 @@ static FAILURE_TESTS: &[FailureTestConfig] = &[
168168 failure_mode : FailureMode :: RecoverMultisigInsufficientSigners ,
169169 builder_type : TestBuilderType :: Simple ,
170170 } ,
171+ FailureTestConfig {
172+ name : "fail_recover_multisig_duplicate_signers" ,
173+ category : TestCategory :: RecoveryOperations ,
174+ base_test : BaseTestType :: RecoverMultisig ,
175+ variant : TestVariant :: BASE ,
176+ failure_mode : FailureMode :: RecoverMultisigDuplicateSigners ,
177+ builder_type : TestBuilderType :: Custom ,
178+ } ,
171179 FailureTestConfig {
172180 name : "fail_recover_wrong_nested_ata_address" ,
173181 category : TestCategory :: RecoveryOperations ,
@@ -409,6 +417,12 @@ impl FailureTestBuilder {
409417 "fail_create_extended_mint_v1" => {
410418 Self :: build_fail_create_extended_mint_v1 ( ata_impl, token_program_id)
411419 }
420+ "fail_recover_multisig_duplicate_signers" => {
421+ Self :: build_fail_recover_multisig_duplicate_signers (
422+ ata_impl,
423+ token_program_id,
424+ )
425+ }
412426 _ => panic ! ( "Unknown custom test: {}" , config. name) ,
413427 }
414428 }
@@ -735,6 +749,43 @@ impl FailureTestBuilder {
735749 }
736750 ( ix, accounts)
737751 }
752+
753+ /// Custom builder for multisig duplicate signers vulnerability test
754+ /// This test exploits the vulnerability where the same signer can be counted multiple times
755+ fn build_fail_recover_multisig_duplicate_signers (
756+ ata_impl : & AtaImplementation ,
757+ token_program_id : & Pubkey ,
758+ ) -> ( Instruction , Vec < ( Pubkey , Account ) > ) {
759+ // Start with a standard RecoverMultisig test case
760+ let ( mut ix, mut accounts) = CommonTestCaseBuilder :: build_test_case (
761+ BaseTestType :: RecoverMultisig ,
762+ TestVariant :: BASE ,
763+ ata_impl,
764+ token_program_id,
765+ ) ;
766+
767+ log_test_info (
768+ "fail_recover_multisig_duplicate_signers" ,
769+ ata_impl,
770+ & [ ( "wallet" , & ix. accounts [ 5 ] . pubkey ) ] ,
771+ ) ;
772+
773+ // The standard RecoverMultisig test creates a 2-of-3 multisig
774+ // We'll exploit the vulnerability by providing the same signer twice
775+ // This should allow us to bypass the 2-of-3 requirement with only 1 actual signer
776+
777+ // Find the first signer account (should be at index 7 in the instruction accounts)
778+ if ix. accounts . len ( ) > 7 {
779+ let first_signer = ix. accounts [ 7 ] . pubkey ;
780+ ix. accounts
781+ . push ( AccountMeta :: new_readonly ( first_signer, true ) ) ;
782+ if let Some ( existing_meta) = ix. accounts . get_mut ( 7 ) {
783+ existing_meta. is_signer = true ;
784+ }
785+ }
786+
787+ ( ix, accounts)
788+ }
738789}
739790
740791// ================================ FAILURE TEST COMPARISON RUNNER ================================
0 commit comments