Skip to content

Commit cd39c18

Browse files
committed
multisig dup signer attack should-fail test
1 parent 60b6805 commit cd39c18

3 files changed

Lines changed: 58 additions & 1 deletion

File tree

p-ata/benches/common_builders.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ pub enum FailureMode {
9292
RecoverWalletNotSigner,
9393
/// Recover: multisig insufficient signers
9494
RecoverMultisigInsufficientSigners,
95+
/// Recover: multisig duplicate signers (vulnerability test)
96+
RecoverMultisigDuplicateSigners,
9597
/// Recover: wrong nested ATA address
9698
RecoverWrongNestedAta(Pubkey),
9799
/// Recover: wrong destination address
@@ -939,6 +941,10 @@ impl CommonTestCaseBuilder {
939941
}
940942
}
941943
}
944+
FailureMode::RecoverMultisigDuplicateSigners => {
945+
// This will be handled by the custom builder in failure_scenarios.rs
946+
// The custom builder will duplicate a signer account to exploit the vulnerability
947+
}
942948

943949
// Address replacement (both instruction and accounts)
944950
FailureMode::WrongSystemProgram(wrong_id) => {

p-ata/benches/failure_scenarios.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 ================================

p-ata/src/processor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ fn parse_create_accounts(accounts: &[AccountInfo]) -> Result<CreateAccounts, Pro
172172
_ => return Err(ProgramError::NotEnoughAccountKeys),
173173
};
174174

175-
// SAFETY: account info already checked
175+
// SAFETY: account len already checked
176176
unsafe {
177177
Ok(CreateAccounts {
178178
payer: accounts.get_unchecked(0),

0 commit comments

Comments
 (0)