Skip to content

Commit

Permalink
fix: approve middleware were permissionless instructions that allowed…
Browse files Browse the repository at this point in the history
… an attacker to block a user from executing a transaction if timed correctly. This PR fixes this
  • Loading branch information
rado0x54 committed Feb 23, 2023
1 parent bd091ec commit c2a64fc
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/client/core/src/lib/CryptidTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export class CryptidTransaction {
this.cryptidAccount.didAccountBump,
TransactionState.toBorsh(state),
allowUnauthorized,
this.cryptidAccount.middlewares.map((m) => m.programId),
this.instructions,
this.accountMetas.length
)
Expand Down
34 changes: 34 additions & 0 deletions packages/client/idl/src/cryptid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ export type Cryptid = {
"name": "allowUnauthorized",
"type": "bool"
},
{
"name": "whitelistedMiddlewarePrograms",
"type": {
"vec": "publicKey"
}
},
{
"name": "instructions",
"type": {
Expand Down Expand Up @@ -609,6 +615,17 @@ export type Cryptid = {
"option": "publicKey"
}
},
{
"name": "whitelistedMiddlewarePrograms",
"docs": [
"This vector contains a list of middleware program ids that are allowed to",
"approve the execution. Important, is not used for passing transactions execution",
"checks. (approved_middleware: Option<Pubkey>) is used for that."
],
"type": {
"vec": "publicKey"
}
},
{
"name": "authorized",
"type": "bool"
Expand Down Expand Up @@ -1066,6 +1083,12 @@ export const IDL: Cryptid = {
"name": "allowUnauthorized",
"type": "bool"
},
{
"name": "whitelistedMiddlewarePrograms",
"type": {
"vec": "publicKey"
}
},
{
"name": "instructions",
"type": {
Expand Down Expand Up @@ -1461,6 +1484,17 @@ export const IDL: Cryptid = {
"option": "publicKey"
}
},
{
"name": "whitelistedMiddlewarePrograms",
"docs": [
"This vector contains a list of middleware program ids that are allowed to",
"approve the execution. Important, is not used for passing transactions execution",
"checks. (approved_middleware: Option<Pubkey>) is used for that."
],
"type": {
"vec": "publicKey"
}
},
{
"name": "authorized",
"type": "bool"
Expand Down
20 changes: 19 additions & 1 deletion packages/tests/src/middleware/checkPass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ describe("Middleware: checkPass", () => {

const propose = async (
transactionAccount: Keypair,
instruction: InstructionData = transferInstructionData
instruction: InstructionData = transferInstructionData,
whitelistedMiddleware: PublicKey[] = [checkPassMiddlewareProgram.programId]
) =>
program.methods
.proposeTransaction(
Expand All @@ -153,6 +154,7 @@ describe("Middleware: checkPass", () => {
cryptid.details.didAccountBump,
TransactionState.toBorsh(TransactionState.Ready),
false,
whitelistedMiddleware,
[instruction],
2
)
Expand Down Expand Up @@ -446,6 +448,22 @@ describe("Middleware: checkPass", () => {
expect(previousBalance - currentBalance).to.equal(LAMPORTS_PER_SOL); // Now the tx has been executed
});

it("fails a transfer if the middleware program has not been whitelisted in the propose", async () => {
const transactionAccount = Keypair.generate();

// issue a gateway token to the authority
const gatewayToken = await createGatewayToken(authority.publicKey);

// propose the Cryptid transaction without whitelisting the middleware
await propose(transactionAccount, transferInstructionData, []);

// pass through the middleware
const shouldFail = checkPass(transactionAccount, gatewayToken.publicKey);
return expect(shouldFail).to.be.rejectedWith(
"Error Code: InvalidMiddlewareAccount"
);
});

it("allows a transfer if the DID account has a valid gateway token", async () => {
// the difference between this one and the previous one is that it shows that
// the gateway token can be associated with the DID account itself rather than the authority wallet
Expand Down
3 changes: 3 additions & 0 deletions packages/tests/src/proposeExecute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => {
cryptid.details.didAccountBump,
TransactionState.toBorsh(TransactionState.Ready),
false,
[], // no whitelisted middleware programs
[instruction],
2
)
Expand Down Expand Up @@ -178,6 +179,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => {
cryptid.details.didAccountBump,
TransactionState.toBorsh(TransactionState.Ready),
false,
[], // no whitelisted middleware programs
[transferInstructionData],
2
)
Expand Down Expand Up @@ -337,6 +339,7 @@ didTestCases.forEach(({ didType, getDidAccount }) => {
cryptid.details.didAccountBump,
TransactionState.toBorsh(TransactionState.Ready),
false,
[], // no whitelisted middleware programs
[transferInstructionData],
2
)
Expand Down
9 changes: 4 additions & 5 deletions programs/cryptid/src/instructions/approve_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ pub struct ApproveExecution<'info> {
pub fn approve_execution<'info>(
ctx: Context<'_, '_, '_, 'info, ApproveExecution<'info>>,
) -> Result<()> {
// Make sure owner is NOT the System Program
// TODO(ticket): Consider implementing an approval registry on middleware
require!(
!anchor_lang::solana_program::system_program::check_id(
ctx.accounts.middleware_account.owner
),
ctx.accounts
.transaction_account
.whitelisted_middleware_programs
.contains(ctx.accounts.middleware_account.owner),
CryptidError::InvalidMiddlewareAccount
);

Expand Down
3 changes: 2 additions & 1 deletion programs/cryptid/src/instructions/extend_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ pub struct ExtendTransaction<'info> {
transaction_account.accounts.len() + num_accounts as usize,
InstructionSize::from_iter_to_iter(
instructions.iter().chain(transaction_account.instructions.iter())
)
),
transaction_account.whitelisted_middleware_programs.len()
),
realloc::payer = authority,
realloc::zero = false,
Expand Down
9 changes: 8 additions & 1 deletion programs/cryptid/src/instructions/propose_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ did_account_bump: u8,
state: TransactionState,
/// True if the transaction account is being proposed by a non-authority on the DID
allow_unauthorized: bool,
/// A list of middleware programs that are allowed to approve this transaction account.
whitelisted_middleware_programs: Vec<Pubkey>,
/// The instructions to execute
instructions: Vec<AbbreviatedInstructionData>,
num_accounts: u8,
Expand Down Expand Up @@ -50,7 +52,8 @@ pub struct ProposeTransaction<'info> {
num_accounts.into(),
InstructionSize::from_iter_to_iter(
instructions.iter()
)
),
whitelisted_middleware_programs.len()
))
]
transaction_account: Account<'info, TransactionAccount>,
Expand Down Expand Up @@ -89,6 +92,7 @@ pub fn propose_transaction<'info>(
did_account_bump: u8,
state: TransactionState,
allow_unauthorized: bool,
whitelisted_middleware_programs: Vec<Pubkey>,
instructions: Vec<AbbreviatedInstructionData>,
) -> Result<()> {
let all_accounts = ctx.all_accounts();
Expand Down Expand Up @@ -132,6 +136,9 @@ pub fn propose_transaction<'info>(
None
};
ctx.accounts.transaction_account.authorized = !allow_unauthorized;
ctx.accounts
.transaction_account
.whitelisted_middleware_programs = whitelisted_middleware_programs;

// if the transaction is being created by an unauthorized signer,
// then the cryptid account must have superuser middleware registered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ pub struct SuperuserApproveExecution<'info> {
pub fn superuser_approve_execution<'info>(
ctx: Context<'_, '_, '_, 'info, SuperuserApproveExecution<'info>>,
) -> Result<()> {
// Make sure owner is NOT the System Program
// TODO(ticket): Consider implementing an approval registry on middleware
require!(
!anchor_lang::solana_program::system_program::check_id(
ctx.accounts.middleware_account.owner
),
ctx.accounts
.transaction_account
.whitelisted_middleware_programs
.contains(ctx.accounts.middleware_account.owner),
CryptidError::InvalidMiddlewareAccount
);

Expand Down
2 changes: 2 additions & 0 deletions programs/cryptid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub mod cryptid {
did_account_bump: u8,
state: TransactionState,
allow_unauthorized: bool,
whitelisted_middleware_programs: Vec<Pubkey>,
instructions: Vec<AbbreviatedInstructionData>,
_num_accounts: u8,
) -> Result<()> {
Expand All @@ -79,6 +80,7 @@ pub mod cryptid {
did_account_bump,
state,
allow_unauthorized,
whitelisted_middleware_programs,
instructions,
)
}
Expand Down
8 changes: 8 additions & 0 deletions programs/cryptid/src/state/transaction_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ pub struct TransactionAccount {
/// If the transaction account is proposed by an unauthorized cryptid client, then
/// it is set to to that signer, and only a `superUser` middleware can approve it.
pub unauthorized_signer: Option<Pubkey>,
/// This vector contains a list of middleware program ids that are allowed to
/// approve the execution. Important, is not used for passing transactions execution
/// checks. (approved_middleware: Option<Pubkey>) is used for that.
pub whitelisted_middleware_programs: Vec<Pubkey>,
pub authorized: bool,
}
impl TransactionAccount {
/// Calculates the on-chain size of a [`TransactionAccount`]
pub fn calculate_size(
num_accounts: usize,
instruction_sizes: impl Iterator<Item = InstructionSize>,
num_whitelisted_middleware_programs: usize,
) -> usize {
DISCRIMINATOR_SIZE
+ 32 // cryptid_account
Expand All @@ -45,6 +50,7 @@ impl TransactionAccount {
+ 1 + 32 // approved_middleware
+ 1 // state
+ 1 + 32 // unauthorized signer
+ 4 + 32 * num_whitelisted_middleware_programs // whitelisted_middleware_programs
+ 1 // authorized
}

Expand Down Expand Up @@ -87,6 +93,7 @@ mod test {
accounts: 1,
data_len: 1,
}),
1,
);
println!("Size: {size}");

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

0 comments on commit c2a64fc

Please sign in to comment.