Skip to content

Commit ba98569

Browse files
febojoncinque
authored andcommitted
Add safety comments (#8)
* Remove spl-token test cases * Update CU values * Add safety comments * Use git dependencies
1 parent 93adbec commit ba98569

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+205
-118
lines changed

p-token/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ test-sbf = []
2020
[dependencies]
2121
pinocchio = { workspace = true }
2222
pinocchio-log = { workspace = true }
23-
pinocchio-pubkey = { workspace = true }
2423
token-interface = { version = "^0", path = "../interface" }
2524

2625
[dev-dependencies]

p-token/src/processor/amount_to_ui_amount.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ pub fn process_amount_to_ui_amount(
2323

2424
let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
2525
check_account_owner(mint_info)?;
26-
// SAFETY: there is a single borrow to the `Mint` account.
26+
// SAFETY: single immutable borrow to `mint_info` account data and
27+
// `load` validates that the mint is initialized.
2728
let mint = unsafe {
2829
load::<Mint>(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)?
2930
};

p-token/src/processor/burn_checked.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ use super::shared;
44

55
#[inline(always)]
66
pub fn process_burn_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
7-
let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
8-
let amount = u64::from_le_bytes(
9-
amount
10-
.try_into()
11-
.map_err(|_error| ProgramError::InvalidInstructionData)?,
12-
);
7+
// expected u64 (8) + u8 (1)
8+
let (amount, decimals) = if instruction_data.len() == 9 {
9+
let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
10+
(
11+
u64::from_le_bytes(
12+
amount
13+
.try_into()
14+
.map_err(|_error| ProgramError::InvalidInstructionData)?,
15+
),
16+
decimals.first(),
17+
)
18+
} else {
19+
return Err(ProgramError::InvalidInstructionData);
20+
};
1321

14-
shared::burn::process_burn(
15-
accounts,
16-
amount,
17-
Some(
18-
*decimals
19-
.first()
20-
.ok_or(ProgramError::InvalidInstructionData)?,
21-
),
22-
)
22+
shared::burn::process_burn(accounts, amount, decimals.copied())
2323
}

p-token/src/processor/close_account.rs

+27-21
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ use pinocchio::{
33
};
44
use token_interface::{
55
error::TokenError,
6-
state::{account::Account, load_mut},
6+
state::{account::Account, load},
77
};
88

99
use super::validate_owner;
1010

11-
/// Incinerator address.
12-
const INCINERATOR_ID: Pubkey =
13-
pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111");
11+
/// Incinerator (`1nc1nerator11111111111111111111111111111111`) address.
12+
const INCINERATOR_ID: Pubkey = [
13+
0, 51, 144, 114, 141, 52, 17, 96, 121, 189, 201, 17, 191, 255, 0, 219, 212, 77, 46, 205, 204,
14+
247, 156, 166, 225, 0, 56, 225, 0, 0, 0, 0,
15+
];
1416

1517
#[inline(always)]
1618
pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult {
@@ -24,26 +26,30 @@ pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult {
2426
// raw pointer.
2527
if source_account_info == destination_account_info {
2628
return Err(ProgramError::InvalidAccountData);
27-
}
28-
29-
let source_account =
30-
unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
31-
32-
if !source_account.is_native() && source_account.amount() != 0 {
33-
return Err(TokenError::NonNativeHasBalance.into());
34-
}
35-
36-
let authority = source_account
37-
.close_authority()
38-
.unwrap_or(&source_account.owner);
39-
40-
if !source_account.is_owned_by_system_program_or_incinerator() {
41-
validate_owner(authority, authority_info, remaining)?;
42-
} else if destination_account_info.key() != &INCINERATOR_ID {
43-
return Err(ProgramError::InvalidAccountData);
29+
} else {
30+
// SAFETY: scoped immutable borrow to `source_account_info` account data and
31+
// `load` validates that the account is initialized.
32+
let source_account =
33+
unsafe { load::<Account>(source_account_info.borrow_data_unchecked())? };
34+
35+
if !source_account.is_native() && source_account.amount() != 0 {
36+
return Err(TokenError::NonNativeHasBalance.into());
37+
}
38+
39+
let authority = source_account
40+
.close_authority()
41+
.unwrap_or(&source_account.owner);
42+
43+
if !source_account.is_owned_by_system_program_or_incinerator() {
44+
validate_owner(authority, authority_info, remaining)?;
45+
} else if destination_account_info.key() != &INCINERATOR_ID {
46+
return Err(ProgramError::InvalidAccountData);
47+
}
4448
}
4549

4650
let destination_starting_lamports = destination_account_info.lamports();
51+
// SAFETY: single mutable borrow to `destination_account_info` lamports and
52+
// there are no "active" borrows of `source_account_info` account data.
4753
unsafe {
4854
// Moves the lamports to the destination account.
4955
*destination_account_info.borrow_mut_lamports_unchecked() = destination_starting_lamports

p-token/src/processor/get_account_data_size.rs

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub fn process_get_account_data_size(accounts: &[AccountInfo]) -> ProgramResult
1717
// Make sure the mint is valid.
1818
check_account_owner(mint_info)?;
1919

20+
// SAFETY: single immutable borrow to `mint_info` account data and
21+
// `load` validates that the mint is initialized.
2022
let _ = unsafe {
2123
load::<Mint>(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)?
2224
};
+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
program_error::ProgramError,
4+
pubkey::{Pubkey, PUBKEY_BYTES},
5+
ProgramResult,
6+
};
27

38
use super::shared;
49

@@ -7,6 +12,13 @@ pub fn process_initialize_account2(
712
accounts: &[AccountInfo],
813
instruction_data: &[u8],
914
) -> ProgramResult {
10-
let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) };
15+
// SAFETY: validate `instruction_data` length.
16+
let owner = unsafe {
17+
if instruction_data.len() != PUBKEY_BYTES {
18+
return Err(ProgramError::InvalidInstructionData);
19+
} else {
20+
&*(instruction_data.as_ptr() as *const Pubkey)
21+
}
22+
};
1123
shared::initialize_account::process_initialize_account(accounts, Some(owner), true)
1224
}
+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
program_error::ProgramError,
4+
pubkey::{Pubkey, PUBKEY_BYTES},
5+
ProgramResult,
6+
};
27

38
use super::shared;
49

@@ -7,6 +12,13 @@ pub fn process_initialize_account3(
712
accounts: &[AccountInfo],
813
instruction_data: &[u8],
914
) -> ProgramResult {
10-
let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) };
15+
// SAFETY: validate `instruction_data` length.
16+
let owner = unsafe {
17+
if instruction_data.len() != PUBKEY_BYTES {
18+
return Err(ProgramError::InvalidInstructionData);
19+
} else {
20+
&*(instruction_data.as_ptr() as *const Pubkey)
21+
}
22+
};
1123
shared::initialize_account::process_initialize_account(accounts, Some(owner), false)
1224
}

p-token/src/processor/initialize_immutable_owner.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use token_interface::{
88
pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult {
99
let token_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
1010

11+
// SAFETY: single immutable borrow to `token_account_info` account data.
1112
let account = unsafe { load_unchecked::<Account>(token_account_info.borrow_data_unchecked())? };
1213

1314
if account.is_initialized() {

p-token/src/processor/initialize_mint.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub fn process_initialize_mint(
3535
(mint_info, None)
3636
};
3737

38+
// SAFETY: single mutable borrow to `mint_info` account data.
3839
let mint = unsafe { load_mut_unchecked::<Mint>(mint_info.borrow_mut_data_unchecked())? };
3940

4041
if mint.is_initialized() {
@@ -44,7 +45,9 @@ pub fn process_initialize_mint(
4445
// Check rent-exempt status of the mint account.
4546

4647
let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info {
47-
let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) };
48+
// SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are
49+
// checked by `from_account_info_unchecked`.
50+
let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? };
4851
rent.is_exempt(mint_info.lamports(), size_of::<Mint>())
4952
} else {
5053
Rent::get()?.is_exempt(mint_info.lamports(), size_of::<Mint>())
@@ -81,7 +84,7 @@ impl InitializeMint<'_> {
8184
// - decimals (1 byte)
8285
// - mint_authority (32 bytes)
8386
// - option + freeze_authority (1 byte + 32 bytes)
84-
if bytes.len() < 34 {
87+
if bytes.len() < 34 || (bytes[33] == 1 && bytes.len() < 66) {
8588
return Err(ProgramError::InvalidInstructionData);
8689
}
8790

@@ -93,16 +96,19 @@ impl InitializeMint<'_> {
9396

9497
#[inline]
9598
pub fn decimals(&self) -> u8 {
99+
// SAFETY: the `bytes` length was validated in `try_from_bytes`.
96100
unsafe { *self.raw }
97101
}
98102

99103
#[inline]
100104
pub fn mint_authority(&self) -> &Pubkey {
105+
// SAFETY: the `bytes` length was validated in `try_from_bytes`.
101106
unsafe { &*(self.raw.add(1) as *const Pubkey) }
102107
}
103108

104109
#[inline]
105110
pub fn freeze_authority(&self) -> Option<&Pubkey> {
111+
// SAFETY: the `bytes` length was validated in `try_from_bytes`.
106112
unsafe {
107113
if *self.raw.add(33) == 0 {
108114
Option::None

p-token/src/processor/mint_to_checked.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ use super::shared;
44

55
#[inline(always)]
66
pub fn process_mint_to_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
7-
let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
7+
// expected u64 (8) + u8 (1)
8+
let (amount, decimals) = if instruction_data.len() == 9 {
9+
let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
10+
(
11+
u64::from_le_bytes(
12+
amount
13+
.try_into()
14+
.map_err(|_error| ProgramError::InvalidInstructionData)?,
15+
),
16+
decimals.first(),
17+
)
18+
} else {
19+
return Err(ProgramError::InvalidInstructionData);
20+
};
821

9-
let amount = u64::from_le_bytes(
10-
amount
11-
.try_into()
12-
.map_err(|_error| ProgramError::InvalidInstructionData)?,
13-
);
14-
15-
shared::mint_to::process_mint_to(
16-
accounts,
17-
amount,
18-
Some(*decimals.first().ok_or(ProgramError::InvalidAccountData)?),
19-
)
22+
shared::mint_to::process_mint_to(accounts, amount, decimals.copied())
2023
}

p-token/src/processor/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ fn check_account_owner(account_info: &AccountInfo) -> ProgramResult {
9292
}
9393
}
9494

95-
/// Validates owner(s) are present
95+
/// Validates owner(s) are present.
96+
///
97+
/// Note that `owner_account_info` will be immutable borrowed when it represents
98+
/// a multisig account.
9699
#[inline(always)]
97100
fn validate_owner(
98101
expected_owner: &Pubkey,
@@ -106,6 +109,8 @@ fn validate_owner(
106109
if owner_account_info.data_len() == Multisig::LEN
107110
&& owner_account_info.owner() == &TOKEN_PROGRAM_ID
108111
{
112+
// SAFETY: the caller guarantees that there are no mutable borrows of `owner_account_info`
113+
// account data and the `load` validates that the account is initialized.
109114
let multisig = unsafe { load::<Multisig>(owner_account_info.borrow_data_unchecked())? };
110115

111116
let mut num_signers = 0;

p-token/src/processor/revoke.rs

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub fn process_revoke(accounts: &[AccountInfo], _instruction_data: &[u8]) -> Pro
1212
return Err(ProgramError::NotEnoughAccountKeys);
1313
};
1414

15+
// SAFETY: single mutable borrow to `source_account_info` account data and
16+
// `load_mut` validates that the account is initialized.
1517
let source_account =
1618
unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
1719

p-token/src/processor/set_authority.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8])
2727
};
2828

2929
if account_info.data_len() == Account::LEN {
30+
// SAFETY: single mutable borrow to `account_info` account data and
31+
// `load_mut` validates that the account is initialized.
3032
let account = unsafe { load_mut::<Account>(account_info.borrow_mut_data_unchecked())? };
3133

3234
if account.is_frozen() {
@@ -65,6 +67,8 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8])
6567
}
6668
}
6769
} else if account_info.data_len() == Mint::LEN {
70+
// SAFETY: single mutable borrow to `account_info` account data and
71+
// `load_mut` validates that the mint is initialized.
6872
let mint = unsafe { load_mut::<Mint>(account_info.borrow_mut_data_unchecked())? };
6973

7074
match authority_type {
@@ -119,7 +123,7 @@ impl SetAuthority<'_> {
119123
// The minimum expected size of the instruction data.
120124
// - authority_type (1 byte)
121125
// - option + new_authority (1 byte + 32 bytes)
122-
if bytes.len() < 2 {
126+
if bytes.len() < 2 || (bytes[1] == 1 && bytes.len() < 34) {
123127
return Err(ProgramError::InvalidInstructionData);
124128
}
125129

@@ -131,11 +135,13 @@ impl SetAuthority<'_> {
131135

132136
#[inline(always)]
133137
pub fn authority_type(&self) -> Result<AuthorityType, ProgramError> {
138+
// SAFETY: `bytes` length is validated in `try_from_bytes`.
134139
unsafe { AuthorityType::from(*self.raw) }
135140
}
136141

137142
#[inline(always)]
138143
pub fn new_authority(&self) -> Option<&Pubkey> {
144+
// SAFETY: `bytes` length is validated in `try_from_bytes`.
139145
unsafe {
140146
if *self.raw.add(1) == 0 {
141147
Option::None

p-token/src/processor/shared/approve.rs

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub fn process_approve(
4545

4646
// Validates source account.
4747

48+
// SAFETY: single mutable borrow to `source_account_info` account data and
49+
// `load_mut` validates that the account is initialized.
4850
let source_account =
4951
unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
5052

@@ -57,6 +59,8 @@ pub fn process_approve(
5759
return Err(TokenError::MintMismatch.into());
5860
}
5961

62+
// SAFETY: single immutable borrow of `mint_info` account data and
63+
// `load` validates that the mint is initialized.
6064
let mint = unsafe { load::<Mint>(mint_info.borrow_data_unchecked())? };
6165

6266
if expected_decimals != mint.decimals {

p-token/src/processor/shared/burn.rs

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub fn process_burn(
1616
return Err(ProgramError::NotEnoughAccountKeys);
1717
};
1818

19+
// SAFETY: single mutable borrow to `source_account_info` account data and
20+
// `load_mut` validates that the account is initialized.
1921
let source_account =
2022
unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
2123

@@ -33,6 +35,8 @@ pub fn process_burn(
3335
.checked_sub(amount)
3436
.ok_or(TokenError::InsufficientFunds)?;
3537

38+
// SAFETY: single mutable borrow to `mint_info` account data and
39+
// `load_mut` validates that the mint is initialized.
3640
let mint = unsafe { load_mut::<Mint>(mint_info.borrow_mut_data_unchecked())? };
3741

3842
if mint_info.key() != &source_account.mint {

0 commit comments

Comments
 (0)