Skip to content

Commit 6a13438

Browse files
committed
fix: approve middleware were permissionless instructions that allowed an attacker to block a user from executing a transaction if timed correctly. This PR fixes this
1 parent 32ccbdf commit 6a13438

10 files changed

Lines changed: 85 additions & 13 deletions

File tree

packages/client/core/src/lib/CryptidTransaction.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ export class CryptidTransaction {
278278
this.cryptidAccount.didAccountBump,
279279
TransactionState.toBorsh(state),
280280
allowUnauthorized,
281+
this.cryptidAccount.middlewares.map((m) => m.programId),
281282
this.instructions,
282283
this.accountMetas.length
283284
)

packages/client/idl/src/cryptid.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ export type Cryptid = {
214214
"name": "allowUnauthorized",
215215
"type": "bool"
216216
},
217+
{
218+
"name": "whitelistedMiddlewarePrograms",
219+
"type": {
220+
"vec": "publicKey"
221+
}
222+
},
217223
{
218224
"name": "instructions",
219225
"type": {
@@ -609,6 +615,17 @@ export type Cryptid = {
609615
"option": "publicKey"
610616
}
611617
},
618+
{
619+
"name": "whitelistedMiddlewarePrograms",
620+
"docs": [
621+
"This vector contains a list of middleware program ids that are allowed to",
622+
"approve the execution. Important, is not used for passing transactions execution",
623+
"checks. (approved_middleware: Option<Pubkey>) is used for that."
624+
],
625+
"type": {
626+
"vec": "publicKey"
627+
}
628+
},
612629
{
613630
"name": "authorized",
614631
"type": "bool"
@@ -1066,6 +1083,12 @@ export const IDL: Cryptid = {
10661083
"name": "allowUnauthorized",
10671084
"type": "bool"
10681085
},
1086+
{
1087+
"name": "whitelistedMiddlewarePrograms",
1088+
"type": {
1089+
"vec": "publicKey"
1090+
}
1091+
},
10691092
{
10701093
"name": "instructions",
10711094
"type": {
@@ -1461,6 +1484,17 @@ export const IDL: Cryptid = {
14611484
"option": "publicKey"
14621485
}
14631486
},
1487+
{
1488+
"name": "whitelistedMiddlewarePrograms",
1489+
"docs": [
1490+
"This vector contains a list of middleware program ids that are allowed to",
1491+
"approve the execution. Important, is not used for passing transactions execution",
1492+
"checks. (approved_middleware: Option<Pubkey>) is used for that."
1493+
],
1494+
"type": {
1495+
"vec": "publicKey"
1496+
}
1497+
},
14641498
{
14651499
"name": "authorized",
14661500
"type": "bool"

packages/tests/src/middleware/checkPass.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ describe("Middleware: checkPass", () => {
143143

144144
const propose = async (
145145
transactionAccount: Keypair,
146-
instruction: InstructionData = transferInstructionData
146+
instruction: InstructionData = transferInstructionData,
147+
whitelistedMiddleware: PublicKey[] = [checkPassMiddlewareProgram.programId]
147148
) =>
148149
program.methods
149150
.proposeTransaction(
@@ -153,6 +154,7 @@ describe("Middleware: checkPass", () => {
153154
cryptid.details.didAccountBump,
154155
TransactionState.toBorsh(TransactionState.Ready),
155156
false,
157+
whitelistedMiddleware,
156158
[instruction],
157159
2
158160
)
@@ -446,6 +448,22 @@ describe("Middleware: checkPass", () => {
446448
expect(previousBalance - currentBalance).to.equal(LAMPORTS_PER_SOL); // Now the tx has been executed
447449
});
448450

451+
it("fails a transfer if the middleware program has not been whitelisted in the propose", async () => {
452+
const transactionAccount = Keypair.generate();
453+
454+
// issue a gateway token to the authority
455+
const gatewayToken = await createGatewayToken(authority.publicKey);
456+
457+
// propose the Cryptid transaction without whitelisting the middleware
458+
await propose(transactionAccount, transferInstructionData, []);
459+
460+
// pass through the middleware
461+
const shouldFail = checkPass(transactionAccount, gatewayToken.publicKey);
462+
return expect(shouldFail).to.be.rejectedWith(
463+
"Error Code: InvalidMiddlewareAccount"
464+
);
465+
});
466+
449467
it("allows a transfer if the DID account has a valid gateway token", async () => {
450468
// the difference between this one and the previous one is that it shows that
451469
// the gateway token can be associated with the DID account itself rather than the authority wallet

packages/tests/src/proposeExecute.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => {
5656
cryptid.details.didAccountBump,
5757
TransactionState.toBorsh(TransactionState.Ready),
5858
false,
59+
[], // no whitelisted middleware programs
5960
[instruction],
6061
2
6162
)
@@ -178,6 +179,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => {
178179
cryptid.details.didAccountBump,
179180
TransactionState.toBorsh(TransactionState.Ready),
180181
false,
182+
[], // no whitelisted middleware programs
181183
[transferInstructionData],
182184
2
183185
)
@@ -337,6 +339,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => {
337339
cryptid.details.didAccountBump,
338340
TransactionState.toBorsh(TransactionState.Ready),
339341
false,
342+
[], // no whitelisted middleware programs
340343
[transferInstructionData],
341344
2
342345
)

programs/cryptid/src/instructions/approve_execution.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@ pub struct ApproveExecution<'info> {
1818
pub fn approve_execution<'info>(
1919
ctx: Context<'_, '_, '_, 'info, ApproveExecution<'info>>,
2020
) -> Result<()> {
21-
// Make sure owner is NOT the System Program
22-
// TODO(ticket): Consider implementing an approval registry on middleware
2321
require!(
24-
!anchor_lang::solana_program::system_program::check_id(
25-
ctx.accounts.middleware_account.owner
26-
),
22+
ctx.accounts
23+
.transaction_account
24+
.whitelisted_middleware_programs
25+
.contains(ctx.accounts.middleware_account.owner),
2726
CryptidError::InvalidMiddlewareAccount
2827
);
2928

programs/cryptid/src/instructions/extend_transaction.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ pub struct ExtendTransaction<'info> {
5454
transaction_account.accounts.len() + num_accounts as usize,
5555
InstructionSize::from_iter_to_iter(
5656
instructions.iter().chain(transaction_account.instructions.iter())
57-
)
57+
),
58+
transaction_account.whitelisted_middleware_programs.len()
5859
),
5960
realloc::payer = authority,
6061
realloc::zero = false,

programs/cryptid/src/instructions/propose_transaction.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ did_account_bump: u8,
2222
state: TransactionState,
2323
/// True if the transaction account is being proposed by a non-authority on the DID
2424
allow_unauthorized: bool,
25+
/// A list of middleware programs that are allowed to approve this transaction account.
26+
whitelisted_middleware_programs: Vec<Pubkey>,
2527
/// The instructions to execute
2628
instructions: Vec<AbbreviatedInstructionData>,
2729
num_accounts: u8,
@@ -50,7 +52,8 @@ pub struct ProposeTransaction<'info> {
5052
num_accounts.into(),
5153
InstructionSize::from_iter_to_iter(
5254
instructions.iter()
53-
)
55+
),
56+
whitelisted_middleware_programs.len()
5457
))
5558
]
5659
transaction_account: Account<'info, TransactionAccount>,
@@ -89,6 +92,7 @@ pub fn propose_transaction<'info>(
8992
did_account_bump: u8,
9093
state: TransactionState,
9194
allow_unauthorized: bool,
95+
whitelisted_middleware_programs: Vec<Pubkey>,
9296
instructions: Vec<AbbreviatedInstructionData>,
9397
) -> Result<()> {
9498
let all_accounts = ctx.all_accounts();
@@ -132,6 +136,9 @@ pub fn propose_transaction<'info>(
132136
None
133137
};
134138
ctx.accounts.transaction_account.authorized = !allow_unauthorized;
139+
ctx.accounts
140+
.transaction_account
141+
.whitelisted_middleware_programs = whitelisted_middleware_programs;
135142

136143
// if the transaction is being created by an unauthorized signer,
137144
// then the cryptid account must have superuser middleware registered

programs/cryptid/src/instructions/superuser_approve_execution.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ pub struct SuperuserApproveExecution<'info> {
2525
pub fn superuser_approve_execution<'info>(
2626
ctx: Context<'_, '_, '_, 'info, SuperuserApproveExecution<'info>>,
2727
) -> Result<()> {
28-
// Make sure owner is NOT the System Program
29-
// TODO(ticket): Consider implementing an approval registry on middleware
3028
require!(
31-
!anchor_lang::solana_program::system_program::check_id(
32-
ctx.accounts.middleware_account.owner
33-
),
29+
ctx.accounts
30+
.transaction_account
31+
.whitelisted_middleware_programs
32+
.contains(ctx.accounts.middleware_account.owner),
3433
CryptidError::InvalidMiddlewareAccount
3534
);
3635

programs/cryptid/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub mod cryptid {
6868
did_account_bump: u8,
6969
state: TransactionState,
7070
allow_unauthorized: bool,
71+
whitelisted_middleware_programs: Vec<Pubkey>,
7172
instructions: Vec<AbbreviatedInstructionData>,
7273
_num_accounts: u8,
7374
) -> Result<()> {
@@ -79,6 +80,7 @@ pub mod cryptid {
7980
did_account_bump,
8081
state,
8182
allow_unauthorized,
83+
whitelisted_middleware_programs,
8284
instructions,
8385
)
8486
}

programs/cryptid/src/state/transaction_account.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,18 @@ pub struct TransactionAccount {
2929
/// If the transaction account is proposed by an unauthorized cryptid client, then
3030
/// it is set to to that signer, and only a `superUser` middleware can approve it.
3131
pub unauthorized_signer: Option<Pubkey>,
32+
/// This vector contains a list of middleware program ids that are allowed to
33+
/// approve the execution. Important, is not used for passing transactions execution
34+
/// checks. (approved_middleware: Option<Pubkey>) is used for that.
35+
pub whitelisted_middleware_programs: Vec<Pubkey>,
3236
pub authorized: bool,
3337
}
3438
impl TransactionAccount {
3539
/// Calculates the on-chain size of a [`TransactionAccount`]
3640
pub fn calculate_size(
3741
num_accounts: usize,
3842
instruction_sizes: impl Iterator<Item = InstructionSize>,
43+
num_whitelisted_middleware_programs: usize,
3944
) -> usize {
4045
DISCRIMINATOR_SIZE
4146
+ 32 // cryptid_account
@@ -45,6 +50,7 @@ impl TransactionAccount {
4550
+ 1 + 32 // approved_middleware
4651
+ 1 // state
4752
+ 1 + 32 // unauthorized signer
53+
+ 4 + 32 * num_whitelisted_middleware_programs // whitelisted_middleware_programs
4854
+ 1 // authorized
4955
}
5056

@@ -87,6 +93,7 @@ mod test {
8793
accounts: 1,
8894
data_len: 1,
8995
}),
96+
1,
9097
);
9198
println!("Size: {size}");
9299

@@ -102,6 +109,7 @@ mod test {
102109
approved_middleware: None,
103110
state: TransactionState::Ready,
104111
unauthorized_signer: None,
112+
whitelisted_middleware_programs: vec![Default::default()],
105113
authorized: true,
106114
};
107115
let ser_size = BorshSerialize::try_to_vec(&account).unwrap().len();

0 commit comments

Comments
 (0)