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

Lines changed: 0 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 15 additions & 15 deletions
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

Lines changed: 27 additions & 21 deletions
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

Lines changed: 2 additions & 0 deletions
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
};
Lines changed: 14 additions & 2 deletions
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
}
Lines changed: 14 additions & 2 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 8 additions & 2 deletions
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

Lines changed: 15 additions & 12 deletions
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
}

0 commit comments

Comments
 (0)