1use {
2 crate::timings::ExecuteDetailsTimings,
3 solana_sdk::{
4 account::{AccountSharedData, ReadableAccount, WritableAccount},
5 instruction::InstructionError,
6 pubkey::Pubkey,
7 rent::Rent,
8 system_instruction::MAX_PERMITTED_DATA_LENGTH,
9 },
10 std::fmt::Debug,
11};
12
13#[derive(Clone, Debug, Default)]
16pub struct PreAccount {
17 key: Pubkey,
18 account: AccountSharedData,
19 changed: bool,
20}
21impl PreAccount {
22 pub fn new(key: &Pubkey, account: AccountSharedData) -> Self {
23 Self {
24 key: *key,
25 account,
26 changed: false,
27 }
28 }
29
30 pub fn verify(
31 &self,
32 program_id: &Pubkey,
33 is_writable: bool,
34 rent: &Rent,
35 post: &AccountSharedData,
36 timings: &mut ExecuteDetailsTimings,
37 outermost_call: bool,
38 ) -> Result<(), InstructionError> {
39 let pre = &self.account;
40
41 let owner_changed = pre.owner() != post.owner();
46 if owner_changed
47 && (!is_writable || pre.executable()
49 || program_id != pre.owner()
50 || !Self::is_zeroed(post.data()))
51 {
52 return Err(InstructionError::ModifiedProgramId);
53 }
54
55 if program_id != pre.owner() && pre.lamports() > post.lamports()
58 {
59 return Err(InstructionError::ExternalAccountLamportSpend);
60 }
61
62 let lamports_changed = pre.lamports() != post.lamports();
64 if lamports_changed {
65 if !is_writable {
66 return Err(InstructionError::ReadonlyLamportChange);
67 }
68 if pre.executable() {
69 return Err(InstructionError::ExecutableLamportChange);
70 }
71 }
72
73 if post.data().len() > MAX_PERMITTED_DATA_LENGTH as usize {
75 return Err(InstructionError::InvalidRealloc);
76 }
77
78 let data_len_changed = pre.data().len() != post.data().len();
80 if data_len_changed && program_id != pre.owner() {
81 return Err(InstructionError::AccountDataSizeChanged);
82 }
83
84 if !(program_id == pre.owner()
88 && is_writable && !pre.executable())
90 && pre.data() != post.data()
91 {
92 if pre.executable() {
93 return Err(InstructionError::ExecutableDataModified);
94 } else if is_writable {
95 return Err(InstructionError::ExternalAccountDataModified);
96 } else {
97 return Err(InstructionError::ReadonlyDataModified);
98 }
99 }
100
101 let executable_changed = pre.executable() != post.executable();
103 if executable_changed {
104 if !rent.is_exempt(post.lamports(), post.data().len()) {
105 return Err(InstructionError::ExecutableAccountNotRentExempt);
106 }
107 if !is_writable || pre.executable()
109 || program_id != post.owner()
110 {
111 return Err(InstructionError::ExecutableModified);
112 }
113 }
114
115 let rent_epoch_changed = pre.rent_epoch() != post.rent_epoch();
117 if rent_epoch_changed {
118 return Err(InstructionError::RentEpochModified);
119 }
120
121 if outermost_call {
122 timings.total_account_count = timings.total_account_count.saturating_add(1);
123 timings.total_data_size = timings.total_data_size.saturating_add(post.data().len());
124 if owner_changed
125 || lamports_changed
126 || data_len_changed
127 || executable_changed
128 || rent_epoch_changed
129 || self.changed
130 {
131 timings.changed_account_count = timings.changed_account_count.saturating_add(1);
132 timings.data_size_changed =
133 timings.data_size_changed.saturating_add(post.data().len());
134 }
135 }
136
137 Ok(())
138 }
139
140 pub fn update(&mut self, account: AccountSharedData) {
141 let rent_epoch = self.account.rent_epoch();
142 self.account = account;
143 self.account.set_rent_epoch(rent_epoch);
144
145 self.changed = true;
146 }
147
148 pub fn key(&self) -> &Pubkey {
149 &self.key
150 }
151
152 pub fn data(&self) -> &[u8] {
153 self.account.data()
154 }
155
156 pub fn lamports(&self) -> u64 {
157 self.account.lamports()
158 }
159
160 pub fn executable(&self) -> bool {
161 self.account.executable()
162 }
163
164 pub fn is_zeroed(buf: &[u8]) -> bool {
165 const ZEROS_LEN: usize = 1024;
166 static ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
167 let mut chunks = buf.chunks_exact(ZEROS_LEN);
168
169 #[allow(clippy::indexing_slicing)]
170 {
171 chunks.all(|chunk| chunk == &ZEROS[..])
172 && chunks.remainder() == &ZEROS[..chunks.remainder().len()]
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use {
180 super::*,
181 solana_sdk::{account::Account, instruction::InstructionError, system_program},
182 };
183
184 #[test]
185 fn test_is_zeroed() {
186 const ZEROS_LEN: usize = 1024;
187 let mut buf = [0; ZEROS_LEN];
188 assert!(PreAccount::is_zeroed(&buf));
189 buf[0] = 1;
190 assert!(!PreAccount::is_zeroed(&buf));
191
192 let mut buf = [0; ZEROS_LEN - 1];
193 assert!(PreAccount::is_zeroed(&buf));
194 buf[0] = 1;
195 assert!(!PreAccount::is_zeroed(&buf));
196
197 let mut buf = [0; ZEROS_LEN + 1];
198 assert!(PreAccount::is_zeroed(&buf));
199 buf[0] = 1;
200 assert!(!PreAccount::is_zeroed(&buf));
201
202 let buf = vec![];
203 assert!(PreAccount::is_zeroed(&buf));
204 }
205
206 struct Change {
207 program_id: Pubkey,
208 is_writable: bool,
209 rent: Rent,
210 pre: PreAccount,
211 post: AccountSharedData,
212 }
213 impl Change {
214 pub fn new(owner: &Pubkey, program_id: &Pubkey) -> Self {
215 Self {
216 program_id: *program_id,
217 rent: Rent::default(),
218 is_writable: true,
219 pre: PreAccount::new(
220 &solana_sdk::pubkey::new_rand(),
221 AccountSharedData::from(Account {
222 owner: *owner,
223 lamports: std::u64::MAX,
224 ..Account::default()
225 }),
226 ),
227 post: AccountSharedData::from(Account {
228 owner: *owner,
229 lamports: std::u64::MAX,
230 ..Account::default()
231 }),
232 }
233 }
234 pub fn read_only(mut self) -> Self {
235 self.is_writable = false;
236 self
237 }
238 pub fn executable(mut self, pre: bool, post: bool) -> Self {
239 self.pre.account.set_executable(pre);
240 self.post.set_executable(post);
241 self
242 }
243 pub fn lamports(mut self, pre: u64, post: u64) -> Self {
244 self.pre.account.set_lamports(pre);
245 self.post.set_lamports(post);
246 self
247 }
248 pub fn owner(mut self, post: &Pubkey) -> Self {
249 self.post.set_owner(*post);
250 self
251 }
252 pub fn data(mut self, pre: Vec<u8>, post: Vec<u8>) -> Self {
253 self.pre.account.set_data(pre);
254 self.post.set_data(post);
255 self
256 }
257 pub fn rent_epoch(mut self, pre: u64, post: u64) -> Self {
258 self.pre.account.set_rent_epoch(pre);
259 self.post.set_rent_epoch(post);
260 self
261 }
262 pub fn verify(&self) -> Result<(), InstructionError> {
263 self.pre.verify(
264 &self.program_id,
265 self.is_writable,
266 &self.rent,
267 &self.post,
268 &mut ExecuteDetailsTimings::default(),
269 false,
270 )
271 }
272 }
273
274 #[test]
275 fn test_verify_account_changes_owner() {
276 let system_program_id = system_program::id();
277 let alice_program_id = solana_sdk::pubkey::new_rand();
278 let mallory_program_id = solana_sdk::pubkey::new_rand();
279
280 assert_eq!(
281 Change::new(&system_program_id, &system_program_id)
282 .owner(&alice_program_id)
283 .verify(),
284 Ok(()),
285 "system program should be able to change the account owner"
286 );
287 assert_eq!(
288 Change::new(&system_program_id, &system_program_id)
289 .owner(&alice_program_id)
290 .read_only()
291 .verify(),
292 Err(InstructionError::ModifiedProgramId),
293 "system program should not be able to change the account owner of a read-only account"
294 );
295 assert_eq!(
296 Change::new(&mallory_program_id, &system_program_id)
297 .owner(&alice_program_id)
298 .verify(),
299 Err(InstructionError::ModifiedProgramId),
300 "system program should not be able to change the account owner of a non-system account"
301 );
302 assert_eq!(
303 Change::new(&mallory_program_id, &mallory_program_id)
304 .owner(&alice_program_id)
305 .verify(),
306 Ok(()),
307 "mallory should be able to change the account owner, if she leaves clear data"
308 );
309 assert_eq!(
310 Change::new(&mallory_program_id, &mallory_program_id)
311 .owner(&alice_program_id)
312 .data(vec![42], vec![0])
313 .verify(),
314 Ok(()),
315 "mallory should be able to change the account owner, if she leaves clear data"
316 );
317 assert_eq!(
318 Change::new(&mallory_program_id, &mallory_program_id)
319 .owner(&alice_program_id)
320 .executable(true, true)
321 .data(vec![42], vec![0])
322 .verify(),
323 Err(InstructionError::ModifiedProgramId),
324 "mallory should not be able to change the account owner, if the account executable"
325 );
326 assert_eq!(
327 Change::new(&mallory_program_id, &mallory_program_id)
328 .owner(&alice_program_id)
329 .data(vec![42], vec![42])
330 .verify(),
331 Err(InstructionError::ModifiedProgramId),
332 "mallory should not be able to inject data into the alice program"
333 );
334 }
335
336 #[test]
337 fn test_verify_account_changes_executable() {
338 let owner = solana_sdk::pubkey::new_rand();
339 let mallory_program_id = solana_sdk::pubkey::new_rand();
340 let system_program_id = system_program::id();
341
342 assert_eq!(
343 Change::new(&owner, &system_program_id)
344 .executable(false, true)
345 .verify(),
346 Err(InstructionError::ExecutableModified),
347 "system program can't change executable if system doesn't own the account"
348 );
349 assert_eq!(
350 Change::new(&owner, &system_program_id)
351 .executable(true, true)
352 .data(vec![1], vec![2])
353 .verify(),
354 Err(InstructionError::ExecutableDataModified),
355 "system program can't change executable data if system doesn't own the account"
356 );
357 assert_eq!(
358 Change::new(&owner, &owner).executable(false, true).verify(),
359 Ok(()),
360 "owner should be able to change executable"
361 );
362 assert_eq!(
363 Change::new(&owner, &owner)
364 .executable(false, true)
365 .read_only()
366 .verify(),
367 Err(InstructionError::ExecutableModified),
368 "owner can't modify executable of read-only accounts"
369 );
370 assert_eq!(
371 Change::new(&owner, &owner).executable(true, false).verify(),
372 Err(InstructionError::ExecutableModified),
373 "owner program can't reverse executable"
374 );
375 assert_eq!(
376 Change::new(&owner, &mallory_program_id)
377 .executable(false, true)
378 .verify(),
379 Err(InstructionError::ExecutableModified),
380 "malicious Mallory should not be able to change the account executable"
381 );
382 assert_eq!(
383 Change::new(&owner, &owner)
384 .executable(false, true)
385 .data(vec![1], vec![2])
386 .verify(),
387 Ok(()),
388 "account data can change in the same instruction that sets the bit"
389 );
390 assert_eq!(
391 Change::new(&owner, &owner)
392 .executable(true, true)
393 .data(vec![1], vec![2])
394 .verify(),
395 Err(InstructionError::ExecutableDataModified),
396 "owner should not be able to change an account's data once its marked executable"
397 );
398 assert_eq!(
399 Change::new(&owner, &owner)
400 .executable(true, true)
401 .lamports(1, 2)
402 .verify(),
403 Err(InstructionError::ExecutableLamportChange),
404 "owner should not be able to add lamports once marked executable"
405 );
406 assert_eq!(
407 Change::new(&owner, &owner)
408 .executable(true, true)
409 .lamports(1, 2)
410 .verify(),
411 Err(InstructionError::ExecutableLamportChange),
412 "owner should not be able to add lamports once marked executable"
413 );
414 assert_eq!(
415 Change::new(&owner, &owner)
416 .executable(true, true)
417 .lamports(2, 1)
418 .verify(),
419 Err(InstructionError::ExecutableLamportChange),
420 "owner should not be able to subtract lamports once marked executable"
421 );
422 let data = vec![1; 100];
423 let min_lamports = Rent::default().minimum_balance(data.len());
424 assert_eq!(
425 Change::new(&owner, &owner)
426 .executable(false, true)
427 .lamports(0, min_lamports)
428 .data(data.clone(), data.clone())
429 .verify(),
430 Ok(()),
431 );
432 assert_eq!(
433 Change::new(&owner, &owner)
434 .executable(false, true)
435 .lamports(0, min_lamports - 1)
436 .data(data.clone(), data)
437 .verify(),
438 Err(InstructionError::ExecutableAccountNotRentExempt),
439 "owner should not be able to change an account's data once its marked executable"
440 );
441 }
442
443 #[test]
444 fn test_verify_account_changes_data_len() {
445 let alice_program_id = solana_sdk::pubkey::new_rand();
446
447 assert_eq!(
448 Change::new(&system_program::id(), &system_program::id())
449 .data(vec![0], vec![0, 0])
450 .verify(),
451 Ok(()),
452 "system program should be able to change the data len"
453 );
454 assert_eq!(
455 Change::new(&alice_program_id, &system_program::id())
456 .data(vec![0], vec![0,0])
457 .verify(),
458 Err(InstructionError::AccountDataSizeChanged),
459 "system program should not be able to change the data length of accounts it does not own"
460 );
461 }
462
463 #[test]
464 fn test_verify_account_changes_data() {
465 let alice_program_id = solana_sdk::pubkey::new_rand();
466 let mallory_program_id = solana_sdk::pubkey::new_rand();
467
468 assert_eq!(
469 Change::new(&alice_program_id, &alice_program_id)
470 .data(vec![0], vec![42])
471 .verify(),
472 Ok(()),
473 "alice program should be able to change the data"
474 );
475 assert_eq!(
476 Change::new(&mallory_program_id, &alice_program_id)
477 .data(vec![0], vec![42])
478 .verify(),
479 Err(InstructionError::ExternalAccountDataModified),
480 "non-owner mallory should not be able to change the account data"
481 );
482 assert_eq!(
483 Change::new(&alice_program_id, &alice_program_id)
484 .data(vec![0], vec![42])
485 .read_only()
486 .verify(),
487 Err(InstructionError::ReadonlyDataModified),
488 "alice isn't allowed to touch a CO account"
489 );
490 }
491
492 #[test]
493 fn test_verify_account_changes_rent_epoch() {
494 let alice_program_id = solana_sdk::pubkey::new_rand();
495
496 assert_eq!(
497 Change::new(&alice_program_id, &system_program::id()).verify(),
498 Ok(()),
499 "nothing changed!"
500 );
501 assert_eq!(
502 Change::new(&alice_program_id, &system_program::id())
503 .rent_epoch(0, 1)
504 .verify(),
505 Err(InstructionError::RentEpochModified),
506 "no one touches rent_epoch"
507 );
508 }
509
510 #[test]
511 fn test_verify_account_changes_deduct_lamports_and_reassign_account() {
512 let alice_program_id = solana_sdk::pubkey::new_rand();
513 let bob_program_id = solana_sdk::pubkey::new_rand();
514
515 assert_eq!(
517 Change::new(&alice_program_id, &alice_program_id)
518 .owner(&bob_program_id)
519 .lamports(42, 1)
520 .data(vec![42], vec![0])
521 .verify(),
522 Ok(()),
523 "alice should be able to deduct lamports and give the account to bob if the data is zeroed",
524 );
525 }
526
527 #[test]
528 fn test_verify_account_changes_lamports() {
529 let alice_program_id = solana_sdk::pubkey::new_rand();
530
531 assert_eq!(
532 Change::new(&alice_program_id, &system_program::id())
533 .lamports(42, 0)
534 .read_only()
535 .verify(),
536 Err(InstructionError::ExternalAccountLamportSpend),
537 "debit should fail, even if system program"
538 );
539 assert_eq!(
540 Change::new(&alice_program_id, &alice_program_id)
541 .lamports(42, 0)
542 .read_only()
543 .verify(),
544 Err(InstructionError::ReadonlyLamportChange),
545 "debit should fail, even if owning program"
546 );
547 assert_eq!(
548 Change::new(&alice_program_id, &system_program::id())
549 .lamports(42, 0)
550 .owner(&system_program::id())
551 .verify(),
552 Err(InstructionError::ModifiedProgramId),
553 "system program can't debit the account unless it was the pre.owner"
554 );
555 assert_eq!(
556 Change::new(&system_program::id(), &system_program::id())
557 .lamports(42, 0)
558 .owner(&alice_program_id)
559 .verify(),
560 Ok(()),
561 "system can spend (and change owner)"
562 );
563 }
564
565 #[test]
566 fn test_verify_account_changes_data_size_changed() {
567 let alice_program_id = solana_sdk::pubkey::new_rand();
568
569 assert_eq!(
570 Change::new(&alice_program_id, &system_program::id())
571 .data(vec![0], vec![0, 0])
572 .verify(),
573 Err(InstructionError::AccountDataSizeChanged),
574 "system program should not be able to change another program's account data size"
575 );
576 assert_eq!(
577 Change::new(&alice_program_id, &solana_sdk::pubkey::new_rand())
578 .data(vec![0], vec![0, 0])
579 .verify(),
580 Err(InstructionError::AccountDataSizeChanged),
581 "one program should not be able to change another program's account data size"
582 );
583 assert_eq!(
584 Change::new(&alice_program_id, &alice_program_id)
585 .data(vec![0], vec![0, 0])
586 .verify(),
587 Ok(()),
588 "programs can change their own data size"
589 );
590 assert_eq!(
591 Change::new(&system_program::id(), &system_program::id())
592 .data(vec![0], vec![0, 0])
593 .verify(),
594 Ok(()),
595 "system program should be able to change account data size"
596 );
597 }
598
599 #[test]
600 fn test_verify_account_changes_owner_executable() {
601 let alice_program_id = solana_sdk::pubkey::new_rand();
602 let bob_program_id = solana_sdk::pubkey::new_rand();
603
604 assert_eq!(
605 Change::new(&alice_program_id, &alice_program_id)
606 .owner(&bob_program_id)
607 .executable(false, true)
608 .verify(),
609 Err(InstructionError::ExecutableModified),
610 "program should not be able to change owner and executable at the same time"
611 );
612 }
613}