Allow programs to realloc their accounts within limits (#19475)

This commit is contained in:
Jack May 2021-09-28 01:13:03 -07:00 committed by GitHub
parent 578efdd59f
commit 4e27543415
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1536 additions and 78 deletions

View File

@ -26,6 +26,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
&post,
&mut ExecuteDetailsTimings::default(),
false,
true,
),
Ok(())
);
@ -39,6 +40,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
&post,
&mut ExecuteDetailsTimings::default(),
false,
true,
)
.unwrap();
});
@ -63,6 +65,7 @@ fn bench_verify_account_changes_data(bencher: &mut Bencher) {
&post,
&mut ExecuteDetailsTimings::default(),
false,
true,
)
.unwrap();
});

View File

@ -4,13 +4,14 @@ use solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{demote_program_write_locks, fix_write_privs},
feature_set::{demote_program_write_locks, do_support_realloc, fix_write_privs},
ic_msg,
instruction::{Instruction, InstructionError},
message::Message,
process_instruction::{Executor, InvokeContext, ProcessInstructionWithContext},
pubkey::Pubkey,
rent::Rent,
system_instruction::MAX_PERMITTED_DATA_LENGTH,
system_program,
};
use std::{
@ -115,6 +116,7 @@ impl PreAccount {
post: &AccountSharedData,
timings: &mut ExecuteDetailsTimings,
outermost_call: bool,
do_support_realloc: bool,
) -> Result<(), InstructionError> {
let pre = self.account.borrow();
@ -150,15 +152,30 @@ impl PreAccount {
}
}
// Only the system program can change the size of the data
// and only if the system program owns the account
let data_len_changed = pre.data().len() != post.data().len();
if data_len_changed
&& (!system_program::check_id(program_id) // line coverage used to get branch coverage
|| !system_program::check_id(pre.owner()))
{
return Err(InstructionError::AccountDataSizeChanged);
}
let data_len_changed = if do_support_realloc {
// Account data size cannot exceed a maxumum length
if post.data().len() > MAX_PERMITTED_DATA_LENGTH as usize {
return Err(InstructionError::InvalidRealloc);
}
// The owner of the account can change the size of the data
let data_len_changed = pre.data().len() != post.data().len();
if data_len_changed && program_id != pre.owner() {
return Err(InstructionError::AccountDataSizeChanged);
}
data_len_changed
} else {
// Only the system program can change the size of the data
// and only if the system program owns the account
let data_len_changed = pre.data().len() != post.data().len();
if data_len_changed
&& (!system_program::check_id(program_id) // line coverage used to get branch coverage
|| !system_program::check_id(pre.owner()))
{
return Err(InstructionError::AccountDataSizeChanged);
}
data_len_changed
};
// Only the owner may change account data
// and if the account is writable
@ -502,6 +519,7 @@ impl InstructionProcessor {
keyed_account_indices_obsolete: &[usize],
signers: &[Pubkey],
) -> Result<(), InstructionError> {
let do_support_realloc = invoke_context.is_feature_active(&do_support_realloc::id());
let invoke_context = RefCell::new(invoke_context);
let mut invoke_context = invoke_context.borrow_mut();
@ -541,7 +559,8 @@ impl InstructionProcessor {
// Verify the called program has not misbehaved
for (account, prev_size) in accounts.iter() {
if *prev_size != account.borrow().data().len() && *prev_size != 0 {
if !do_support_realloc && *prev_size != account.borrow().data().len() && *prev_size != 0
{
// Only support for `CreateAccount` at this time.
// Need a way to limit total realloc size across multiple CPI calls
ic_msg!(
@ -617,7 +636,7 @@ impl InstructionProcessor {
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{account::Account, instruction::InstructionError};
use solana_sdk::{account::Account, instruction::InstructionError, system_program};
#[test]
fn test_is_zeroed() {
@ -706,6 +725,7 @@ mod tests {
&self.post,
&mut ExecuteDetailsTimings::default(),
false,
true,
)
}
}
@ -1013,11 +1033,18 @@ mod tests {
"system program should not be able to change another program's account data size"
);
assert_eq!(
Change::new(&alice_program_id, &alice_program_id)
Change::new(&alice_program_id, &solana_sdk::pubkey::new_rand())
.data(vec![0], vec![0, 0])
.verify(),
Err(InstructionError::AccountDataSizeChanged),
"non-system programs cannot change their data size"
"one program should not be able to change another program's account data size"
);
assert_eq!(
Change::new(&alice_program_id, &alice_program_id)
.data(vec![0], vec![0, 0])
.verify(),
Ok(()),
"programs can change their own data size"
);
assert_eq!(
Change::new(&system_program::id(), &system_program::id())
@ -1039,7 +1066,7 @@ mod tests {
.executable(false, true)
.verify(),
Err(InstructionError::ExecutableModified),
"Program should not be able to change owner and executable at the same time"
"program should not be able to change owner and executable at the same time"
);
}

View File

@ -2499,6 +2499,8 @@ dependencies = [
"net2",
"solana-account-decoder",
"solana-bpf-loader-program",
"solana-bpf-rust-realloc",
"solana-bpf-rust-realloc-invoke",
"solana-cli-output",
"solana-logger 1.8.0",
"solana-measure",
@ -2725,6 +2727,21 @@ dependencies = [
"solana-program 1.8.0",
]
[[package]]
name = "solana-bpf-rust-realloc"
version = "1.8.0"
dependencies = [
"solana-program 1.8.0",
]
[[package]]
name = "solana-bpf-rust-realloc-invoke"
version = "1.8.0"
dependencies = [
"solana-bpf-rust-realloc",
"solana-program 1.8.0",
]
[[package]]
name = "solana-bpf-rust-ro-account_modify"
version = "1.8.0"

View File

@ -26,7 +26,9 @@ itertools = "0.10.1"
log = "0.4.11"
miow = "0.3.6"
net2 = "0.2.37"
solana-bpf-loader-program = { path = "../bpf_loader", version = "=1.8.0" }
solana-bpf-loader-program = { path = "../bpf_loader", version = "=1.8.0"}
solana-bpf-rust-realloc = { path = "rust/realloc", version = "=1.8.0", features = ["custom-heap"]}
solana-bpf-rust-realloc-invoke = { path = "rust/realloc_invoke", version = "=1.8.0", features = ["custom-heap"]}
solana-cli-output = { path = "../../cli-output", version = "=1.8.0" }
solana-logger = { path = "../../logger", version = "=1.8.0" }
solana-measure = { path = "../../measure", version = "=1.8.0" }
@ -36,7 +38,6 @@ solana-sdk = { path = "../../sdk", version = "=1.8.0" }
solana-transaction-status = { path = "../../transaction-status", version = "=1.8.0" }
solana-account-decoder = { path = "../../account-decoder", version = "=1.8.0" }
[[bench]]
name = "bpf_loader"
@ -71,6 +72,8 @@ members = [
"rust/param_passing",
"rust/param_passing_dep",
"rust/rand",
"rust/realloc",
"rust/realloc_invoke",
"rust/ro_modify",
"rust/ro_account_modify",
"rust/sanity",

View File

@ -113,6 +113,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
executable.as_ref(),
&mut inner_iter,
&mut invoke_context,
&[],
)
.unwrap();
@ -220,7 +221,7 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
// Serialize account data
let keyed_accounts = invoke_context.get_keyed_accounts().unwrap();
let (mut serialized, _account_lengths) = serialize_parameters(
let (mut serialized, account_lengths) = serialize_parameters(
&bpf_loader::id(),
&solana_sdk::pubkey::new_rand(),
keyed_accounts,
@ -243,6 +244,7 @@ fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
executable.as_ref(),
serialized.as_slice_mut(),
&mut invoke_context,
&account_lengths,
)
.unwrap();

View File

@ -84,6 +84,8 @@ fn main() {
"panic",
"param_passing",
"rand",
"realloc",
"realloc_invoke",
"ro_modify",
"ro_account_modify",
"sanity",

View File

@ -0,0 +1,22 @@
[package]
name = "solana-bpf-rust-realloc"
version = "1.8.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-realloc"
edition = "2018"
[features]
custom-heap = []
[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=1.8.0" }
[lib]
crate-type = ["lib", "cdylib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,71 @@
//! @brief Example Rust-based BPF realloc test program
use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
pub const REALLOC: u8 = 1;
pub const REALLOC_EXTEND: u8 = 2;
pub const REALLOC_EXTEND_AND_FILL: u8 = 3;
pub const REALLOC_AND_ASSIGN: u8 = 4;
pub const REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM: u8 = 5;
pub const ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC: u8 = 6;
pub const DEALLOC_AND_ASSIGN_TO_CALLER: u8 = 7;
pub const CHECK: u8 = 8;
pub const ZERO_INIT: u8 = 9;
pub fn realloc(program_id: &Pubkey, address: &Pubkey, size: usize, bump: &mut u8) -> Instruction {
let mut instruction_data = vec![REALLOC, *bump];
instruction_data.extend_from_slice(&size.to_le_bytes());
*bump += 1;
Instruction::new_with_bytes(
*program_id,
&instruction_data,
vec![AccountMeta::new(*address, false)],
)
}
pub fn realloc_extend(
program_id: &Pubkey,
address: &Pubkey,
size: usize,
bump: &mut u8,
) -> Instruction {
let mut instruction_data = vec![REALLOC_EXTEND, *bump];
instruction_data.extend_from_slice(&size.to_le_bytes());
*bump += 1;
Instruction::new_with_bytes(
*program_id,
&instruction_data,
vec![AccountMeta::new(*address, false)],
)
}
pub fn realloc_extend_and_fill(
program_id: &Pubkey,
address: &Pubkey,
size: usize,
fill: u8,
bump: &mut u64,
) -> Instruction {
let mut instruction_data = vec![
REALLOC_EXTEND_AND_FILL,
fill,
*bump as u8,
(*bump / 255) as u8,
];
instruction_data.extend_from_slice(&size.to_le_bytes());
*bump += 1;
Instruction::new_with_bytes(
*program_id,
&instruction_data,
vec![AccountMeta::new(*address, false)],
)
}

View File

@ -0,0 +1,134 @@
//! @brief Example Rust-based BPF realloc test program
pub mod instructions;
extern crate solana_program;
use crate::instructions::*;
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
entrypoint::MAX_PERMITTED_DATA_INCREASE, msg, program::invoke, pubkey::Pubkey,
system_instruction, system_program,
};
use std::convert::TryInto;
entrypoint!(process_instruction);
#[allow(clippy::unnecessary_wraps)]
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let account = &accounts[0];
match instruction_data[0] {
REALLOC => {
let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::<usize>());
let new_len = usize::from_le_bytes(bytes.try_into().unwrap());
msg!("realloc to {}", new_len);
account.realloc(new_len, false)?;
assert_eq!(new_len, account.data_len());
}
REALLOC_EXTEND => {
let pre_len = account.data_len();
let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::<usize>());
let new_len = pre_len + usize::from_le_bytes(bytes.try_into().unwrap());
msg!("realloc extend by {}", new_len);
account.realloc(new_len, false)?;
assert_eq!(new_len, account.data_len());
}
REALLOC_EXTEND_AND_FILL => {
let pre_len = account.data_len();
let fill = instruction_data[2];
let (bytes, _) = instruction_data[4..].split_at(std::mem::size_of::<usize>());
let new_len = pre_len + usize::from_le_bytes(bytes.try_into().unwrap());
msg!("realloc extend by {}", new_len);
account.realloc(new_len, false)?;
assert_eq!(new_len, account.data_len());
account.try_borrow_mut_data()?[pre_len..].fill(fill);
}
REALLOC_AND_ASSIGN => {
msg!("realloc and assign");
account.realloc(MAX_PERMITTED_DATA_INCREASE, false)?;
assert_eq!(MAX_PERMITTED_DATA_INCREASE, account.data_len());
account.assign(&system_program::id());
assert_eq!(*account.owner, system_program::id());
}
REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM => {
msg!("realloc and assign to self via system program");
let pre_len = account.data_len();
account.realloc(pre_len + MAX_PERMITTED_DATA_INCREASE, false)?;
assert_eq!(pre_len + MAX_PERMITTED_DATA_INCREASE, account.data_len());
invoke(
&system_instruction::assign(account.key, program_id),
accounts,
)?;
assert_eq!(account.owner, program_id);
}
ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC => {
msg!("assign to self via system program and realloc");
let pre_len = account.data_len();
invoke(
&system_instruction::assign(account.key, program_id),
accounts,
)?;
assert_eq!(account.owner, program_id);
account.realloc(pre_len + MAX_PERMITTED_DATA_INCREASE, false)?;
assert_eq!(account.data_len(), pre_len + MAX_PERMITTED_DATA_INCREASE);
}
DEALLOC_AND_ASSIGN_TO_CALLER => {
msg!("dealloc and assign to caller");
account.realloc(0, false)?;
assert_eq!(account.data_len(), 0);
account.assign(accounts[1].key);
assert_eq!(account.owner, accounts[1].key);
}
CHECK => {
msg!("check");
assert_eq!(100, account.data_len());
let data = account.try_borrow_mut_data()?;
for x in data[0..5].iter() {
assert_eq!(0, *x);
}
for x in data[5..].iter() {
assert_eq!(2, *x);
}
}
ZERO_INIT => {
account.realloc(10, false)?;
{
let mut data = account.try_borrow_mut_data()?;
for i in 0..10 {
assert_eq!(0, data[i]);
}
data.fill(1);
for i in 0..10 {
assert_eq!(1, data[i]);
}
}
account.realloc(5, false)?;
account.realloc(10, false)?;
{
let data = account.try_borrow_data()?;
for i in 0..10 {
assert_eq!(1, data[i]);
}
}
account.realloc(5, false)?;
account.realloc(10, true)?;
{
let data = account.try_borrow_data()?;
for i in 0..5 {
assert_eq!(1, data[i]);
}
for i in 5..10 {
assert_eq!(0, data[i]);
}
}
}
_ => panic!(),
}
Ok(())
}

View File

@ -0,0 +1,23 @@
[package]
name = "solana-bpf-rust-realloc-invoke"
version = "1.8.0"
description = "Solana BPF test program written in Rust"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
documentation = "https://docs.rs/solana-bpf-rust-realloc-invoke"
edition = "2018"
[features]
custom-heap = []
[dependencies]
solana-program = { path = "../../../../sdk/program", version = "=1.8.0" }
solana-bpf-rust-realloc = { path = "../realloc", version = "=1.8.0", features = ["custom-heap"]}
[lib]
crate-type = ["lib", "cdylib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,18 @@
//! @brief Example Rust-based BPF realloc test program
pub const INVOKE_REALLOC_ZERO_RO: u8 = 0;
pub const INVOKE_REALLOC_ZERO: u8 = 1;
pub const INVOKE_REALLOC_MAX_PLUS_ONE: u8 = 2;
pub const INVOKE_REALLOC_EXTEND_MAX: u8 = 3;
pub const INVOKE_REALLOC_AND_ASSIGN: u8 = 4;
pub const INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM: u8 = 5;
pub const INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC: u8 = 6;
pub const INVOKE_REALLOC_INVOKE_CHECK: u8 = 7;
pub const INVOKE_OVERFLOW: u8 = 8;
pub const INVOKE_REALLOC_TO: u8 = 9;
pub const INVOKE_REALLOC_RECURSIVE: u8 = 10;
pub const INVOKE_CREATE_ACCOUNT_REALLOC_CHECK: u8 = 11;
pub const INVOKE_DEALLOC_AND_ASSIGN: u8 = 12;
pub const INVOKE_REALLOC_MAX_TWICE: u8 = 13;
pub const INVOKE_REALLOC_MAX_INVOKE_MAX: u8 = 14;
pub const INVOKE_INVOKE_MAX_TWICE: u8 = 15;

View File

@ -0,0 +1,301 @@
//! @brief Example Rust-based BPF realloc test program
pub mod instructions;
extern crate solana_program;
use crate::instructions::*;
use solana_bpf_rust_realloc::instructions::*;
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
entrypoint::MAX_PERMITTED_DATA_INCREASE,
instruction::{AccountMeta, Instruction},
msg,
program::invoke,
pubkey::Pubkey,
system_instruction, system_program,
};
use std::convert::TryInto;
entrypoint!(process_instruction);
#[allow(clippy::unnecessary_wraps)]
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let account = &accounts[0];
let invoke_program_id = accounts[1].key;
let pre_len = account.data_len();
let mut bump = 0;
match instruction_data[0] {
INVOKE_REALLOC_ZERO_RO => {
msg!("invoke realloc to zero of ro account");
// Realloc RO account
let mut instruction = realloc(invoke_program_id, account.key, 0, &mut bump);
instruction.accounts[0].is_writable = false;
invoke(&instruction, accounts)?;
}
INVOKE_REALLOC_ZERO => {
msg!("invoke realloc to zero");
invoke(
&realloc(invoke_program_id, account.key, 0, &mut bump),
accounts,
)?;
assert_eq!(0, account.data_len());
}
INVOKE_REALLOC_MAX_PLUS_ONE => {
msg!("invoke realloc max + 1");
invoke(
&realloc(
invoke_program_id,
account.key,
MAX_PERMITTED_DATA_INCREASE + 1,
&mut bump,
),
accounts,
)?;
}
INVOKE_REALLOC_EXTEND_MAX => {
msg!("invoke realloc max");
invoke(
&realloc_extend(
invoke_program_id,
account.key,
MAX_PERMITTED_DATA_INCREASE,
&mut bump,
),
accounts,
)?;
assert_eq!(pre_len + MAX_PERMITTED_DATA_INCREASE, account.data_len());
}
INVOKE_REALLOC_MAX_TWICE => {
msg!("invoke realloc max twice");
invoke(
&realloc(
invoke_program_id,
account.key,
MAX_PERMITTED_DATA_INCREASE,
&mut bump,
),
accounts,
)?;
let new_len = pre_len + MAX_PERMITTED_DATA_INCREASE;
assert_eq!(new_len, account.data_len());
account.realloc(new_len + MAX_PERMITTED_DATA_INCREASE, false)?;
assert_eq!(new_len + MAX_PERMITTED_DATA_INCREASE, account.data_len());
}
INVOKE_REALLOC_AND_ASSIGN => {
msg!("invoke realloc and assign");
invoke(
&Instruction::new_with_bytes(
*invoke_program_id,
&[REALLOC_AND_ASSIGN],
vec![AccountMeta::new(*account.key, false)],
),
accounts,
)?;
assert_eq!(pre_len + MAX_PERMITTED_DATA_INCREASE, account.data_len());
assert_eq!(*account.owner, system_program::id());
}
INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM => {
msg!("invoke realloc and assign to self via system program");
invoke(
&Instruction::new_with_bytes(
*accounts[1].key,
&[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(*account.key, true),
AccountMeta::new_readonly(*accounts[2].key, false),
],
),
accounts,
)?;
}
INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC => {
msg!("invoke assign to self and realloc via system program");
invoke(
&Instruction::new_with_bytes(
*accounts[1].key,
&[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(*account.key, true),
AccountMeta::new_readonly(*accounts[2].key, false),
],
),
accounts,
)?;
}
INVOKE_REALLOC_INVOKE_CHECK => {
msg!("realloc invoke check size");
account.realloc(100, false)?;
assert_eq!(100, account.data_len());
account.try_borrow_mut_data()?[pre_len..].fill(2);
invoke(
&Instruction::new_with_bytes(
*accounts[1].key,
&[CHECK],
vec![AccountMeta::new(*account.key, false)],
),
accounts,
)?;
}
INVOKE_REALLOC_TO => {
let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::<usize>());
let new_len = usize::from_le_bytes(bytes.try_into().unwrap());
msg!("realloc to {}", new_len);
account.realloc(new_len, false)?;
assert_eq!(new_len, account.data_len());
if pre_len < new_len {
account.try_borrow_mut_data()?[pre_len..].fill(instruction_data[1]);
}
}
INVOKE_REALLOC_RECURSIVE => {
msg!("realloc invoke recursive");
let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::<usize>());
let new_len = usize::from_le_bytes(bytes.try_into().unwrap());
account.realloc(new_len, false)?;
assert_eq!(new_len, account.data_len());
account.try_borrow_mut_data()?[pre_len..].fill(instruction_data[1]);
let final_len: usize = 200;
let mut new_instruction_data = vec![];
new_instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO, 2]);
new_instruction_data.extend_from_slice(&final_len.to_le_bytes());
invoke(
&Instruction::new_with_bytes(
*program_id,
&new_instruction_data,
vec![
AccountMeta::new(*account.key, false),
AccountMeta::new_readonly(*accounts[1].key, false),
],
),
accounts,
)?;
assert_eq!(final_len, account.data_len());
let data = account.try_borrow_mut_data()?;
for i in 0..new_len {
assert_eq!(data[i], instruction_data[1]);
}
for i in new_len..final_len {
assert_eq!(data[i], new_instruction_data[1]);
}
}
INVOKE_CREATE_ACCOUNT_REALLOC_CHECK => {
msg!("Create new account, realloc, and check");
let pre_len: usize = 100;
invoke(
&system_instruction::create_account(
accounts[0].key,
accounts[1].key,
1,
pre_len as u64,
program_id,
),
accounts,
)?;
assert_eq!(pre_len, accounts[1].data_len());
accounts[1].realloc(pre_len + 1, false)?;
assert_eq!(pre_len + 1, accounts[1].data_len());
assert_eq!(accounts[1].owner, program_id);
let final_len: usize = 200;
let mut new_instruction_data = vec![];
new_instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO, 2]);
new_instruction_data.extend_from_slice(&final_len.to_le_bytes());
invoke(
&Instruction::new_with_bytes(
*program_id,
&new_instruction_data,
vec![
AccountMeta::new(*accounts[1].key, false),
AccountMeta::new_readonly(*accounts[3].key, false),
],
),
accounts,
)?;
assert_eq!(final_len, accounts[1].data_len());
}
INVOKE_DEALLOC_AND_ASSIGN => {
msg!("realloc zerod");
let (bytes, _) = instruction_data[2..].split_at(std::mem::size_of::<usize>());
let pre_len = usize::from_le_bytes(bytes.try_into().unwrap());
let new_len = pre_len * 2;
assert_eq!(pre_len, 100);
{
let data = account.try_borrow_mut_data()?;
for i in 0..pre_len {
assert_eq!(data[i], instruction_data[1]);
}
}
invoke(
&Instruction::new_with_bytes(
*accounts[2].key,
&[DEALLOC_AND_ASSIGN_TO_CALLER],
vec![
AccountMeta::new(*account.key, false),
AccountMeta::new_readonly(*accounts[1].key, false),
],
),
accounts,
)?;
assert_eq!(account.owner, program_id);
assert_eq!(account.data_len(), 0);
account.realloc(new_len, false)?;
assert_eq!(account.data_len(), new_len);
{
let data = account.try_borrow_mut_data()?;
for i in 0..new_len {
assert_eq!(data[i], 0);
}
}
}
INVOKE_REALLOC_MAX_INVOKE_MAX => {
msg!("invoke realloc max invoke max");
assert_eq!(0, account.data_len());
account.realloc(MAX_PERMITTED_DATA_INCREASE, false)?;
assert_eq!(MAX_PERMITTED_DATA_INCREASE, account.data_len());
account.assign(invoke_program_id);
assert_eq!(account.owner, invoke_program_id);
invoke(
&realloc_extend(
invoke_program_id,
account.key,
MAX_PERMITTED_DATA_INCREASE,
&mut bump,
),
accounts,
)?;
}
INVOKE_INVOKE_MAX_TWICE => {
msg!("invoke invoke max twice");
assert_eq!(0, account.data_len());
account.assign(accounts[2].key);
assert_eq!(account.owner, accounts[2].key);
invoke(
&realloc(
accounts[2].key,
account.key,
MAX_PERMITTED_DATA_INCREASE,
&mut bump,
),
accounts,
)?;
invoke(
&realloc_extend(
accounts[2].key,
account.key,
MAX_PERMITTED_DATA_INCREASE,
&mut bump,
),
accounts,
)?;
panic!("last invoke should fail");
}
_ => panic!(),
}
Ok(())
}

View File

@ -14,6 +14,8 @@ use solana_bpf_loader_program::{
syscalls::register_syscalls,
BpfError, ThisInstructionMeter,
};
use solana_bpf_rust_realloc::instructions::*;
use solana_bpf_rust_realloc_invoke::instructions::*;
use solana_cli_output::display::println_transaction;
use solana_rbpf::{
static_analysis::Analysis,
@ -43,7 +45,8 @@ use solana_sdk::{
process_instruction::{InvokeContext, MockInvokeContext},
pubkey::Pubkey,
signature::{keypair_from_seed, Keypair, Signer},
system_instruction, system_program, sysvar,
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
system_program, sysvar,
sysvar::{clock, rent},
transaction::{Transaction, TransactionError},
};
@ -234,6 +237,7 @@ fn run_program(
executable.as_ref(),
parameter_bytes.as_slice_mut(),
&mut invoke_context,
&account_lengths,
)
.unwrap();
let result = if i == 0 {
@ -283,6 +287,7 @@ fn run_program(
parameter_accounts,
parameter_bytes.as_slice(),
&account_lengths,
true,
)
.unwrap();
}
@ -2536,3 +2541,716 @@ fn test_program_bpf_ro_account_modify() {
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
}
#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_realloc() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair];
let mut bank = Bank::new_for_tests(&genesis_config);
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, id, entrypoint);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_realloc",
);
let mut bump = 0;
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let account = AccountSharedData::new(42, 5, &program_id);
bank.store_account(&pubkey, &account);
// Realloc RO account
let mut instruction = realloc(&program_id, &pubkey, 0, &mut bump);
instruction.accounts[0].is_writable = false;
assert_eq!(
bank_client
.send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
// Realloc account to overflow
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, usize::MAX, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max + 1
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE + 1,
&mut bump
)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
let mut bump = i as u64;
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend_and_fill(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
1,
&mut bump,
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc_extend(
&program_id,
&pubkey,
MAX_PERMITTED_DATA_INCREASE,
&mut bump
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc and assign
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[REALLOC_AND_ASSIGN],
vec![AccountMeta::new(pubkey, false)],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&solana_sdk::system_program::id(), account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0 with wrong owner
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// realloc and assign to self via cpi
assert_eq!(
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// Assign to self and realloc via cpi
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&program_id, account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// zero-init
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
program_id,
&[ZERO_INIT],
vec![AccountMeta::new(pubkey, true)],
)],
Some(&mint_pubkey),
),
)
.unwrap();
}
#[cfg(feature = "bpf_rust")]
#[test]
fn test_program_bpf_realloc_invoke() {
solana_logger::setup();
let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config(50);
let mint_pubkey = mint_keypair.pubkey();
let signer = &[&mint_keypair];
let mut bank = Bank::new_for_tests(&genesis_config);
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, id, entrypoint);
let bank = Arc::new(bank);
let bank_client = BankClient::new_shared(&bank);
let realloc_program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_realloc",
);
let realloc_invoke_program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_realloc_invoke",
);
let mut bump = 0;
let keypair = Keypair::new();
let pubkey = keypair.pubkey().clone();
let account = AccountSharedData::new(42, 5, &realloc_program_id);
bank.store_account(&pubkey, &account);
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
// Realloc RO account
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_ZERO_RO],
vec![
AccountMeta::new_readonly(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
);
// Realloc account to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to max + 1
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_PLUS_ONE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max twice
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_TWICE],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to max length in max increase increments
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
}
for i in 0..data.len() {
assert_eq!(data[i], 1);
}
// and one more time should fail
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc and assign
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_AND_ASSIGN],
vec![
AccountMeta::new(pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&solana_sdk::system_program::id(), account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0 with wrong owner
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// realloc and assign to self via system program
assert_eq!(
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
);
// Assign to self and realloc via system program
bank_client
.send_and_confirm_message(
&[&mint_keypair, &keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new_readonly(realloc_program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let account = bank.get_account(&pubkey).unwrap();
assert_eq!(&realloc_program_id, account.owner());
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
// Realloc to 0
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
assert_eq!(0, data.len());
// Realloc to 100 and check via CPI
let invoke_account = AccountSharedData::new(42, 5, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_INVOKE_CHECK],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(100, data.len());
for i in 0..5 {
assert_eq!(data[i], 0);
}
for i in 5..data.len() {
assert_eq!(data[i], 2);
}
// Realloc rescursively and fill data
let invoke_keypair = Keypair::new();
let invoke_pubkey = invoke_keypair.pubkey().clone();
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(200, data.len());
for i in 0..100 {
assert_eq!(data[i], 1);
}
for i in 100..200 {
assert_eq!(data[i], 2);
}
// Create account, realloc, check
let new_keypair = Keypair::new();
let new_pubkey = new_keypair.pubkey().clone();
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_CREATE_ACCOUNT_REALLOC_CHECK, 1]);
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
bank_client
.send_and_confirm_message(
&[&mint_keypair, &new_keypair],
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(mint_pubkey, true),
AccountMeta::new(new_pubkey, true),
AccountMeta::new(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client.get_account_data(&new_pubkey).unwrap().unwrap();
assert_eq!(200, data.len());
let account = bank.get_account(&new_pubkey).unwrap();
assert_eq!(&realloc_invoke_program_id, account.owner());
// Invoke, dealloc, and assign
let pre_len = 100;
let new_len = pre_len * 2;
let mut invoke_account = AccountSharedData::new(42, pre_len, &realloc_program_id);
invoke_account.set_data_from_slice(&vec![1; pre_len]);
bank.store_account(&invoke_pubkey, &invoke_account);
let mut instruction_data = vec![];
instruction_data.extend_from_slice(&[INVOKE_DEALLOC_AND_ASSIGN, 1]);
instruction_data.extend_from_slice(&pre_len.to_le_bytes());
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&instruction_data,
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
),
)
.unwrap();
let data = bank_client
.get_account_data(&invoke_pubkey)
.unwrap()
.unwrap();
assert_eq!(new_len, data.len());
for i in 0..new_len {
assert_eq!(data[i], 0);
}
// Realloc to max invoke max
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_REALLOC_MAX_INVOKE_MAX],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
// Realloc invoke max twice
let invoke_account = AccountSharedData::new(42, 0, &realloc_program_id);
bank.store_account(&invoke_pubkey, &invoke_account);
assert_eq!(
bank_client
.send_and_confirm_message(
signer,
Message::new(
&[Instruction::new_with_bytes(
realloc_invoke_program_id,
&[INVOKE_INVOKE_MAX_TWICE],
vec![
AccountMeta::new(invoke_pubkey, false),
AccountMeta::new_readonly(realloc_invoke_program_id, false),
AccountMeta::new_readonly(realloc_program_id, false),
],
)],
Some(&mint_pubkey),
)
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
);
}

View File

@ -31,8 +31,8 @@ use solana_sdk::{
clock::Clock,
entrypoint::{HEAP_LENGTH, SUCCESS},
feature_set::{
add_missing_program_error_mappings, close_upgradeable_program_accounts, fix_write_privs,
reduce_required_deploy_balance, stop_verify_mul64_imm_nonzero,
add_missing_program_error_mappings, close_upgradeable_program_accounts, do_support_realloc,
fix_write_privs, reduce_required_deploy_balance, stop_verify_mul64_imm_nonzero,
},
ic_logger_msg, ic_msg,
instruction::{AccountMeta, InstructionError},
@ -150,12 +150,19 @@ pub fn create_vm<'a>(
program: &'a dyn Executable<BpfError, ThisInstructionMeter>,
parameter_bytes: &mut [u8],
invoke_context: &'a mut dyn InvokeContext,
orig_data_lens: &'a [usize],
) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> {
let compute_budget = invoke_context.get_compute_budget();
let mut heap =
AlignedMemory::new_with_size(compute_budget.heap_size.unwrap_or(HEAP_LENGTH), HOST_ALIGN);
let mut vm = EbpfVm::new(program, heap.as_slice_mut(), parameter_bytes)?;
syscalls::bind_syscall_context_objects(loader_id, &mut vm, invoke_context, heap)?;
syscalls::bind_syscall_context_objects(
loader_id,
&mut vm,
invoke_context,
heap,
orig_data_lens,
)?;
Ok(vm)
}
@ -903,6 +910,7 @@ impl Executor for BpfExecutor {
self.executable.as_ref(),
parameter_bytes.as_slice_mut(),
invoke_context,
&account_lengths,
) {
Ok(info) => info,
Err(e) => {
@ -979,6 +987,7 @@ impl Executor for BpfExecutor {
keyed_accounts,
parameter_bytes.as_slice(),
&account_lengths,
invoke_context.is_feature_active(&do_support_realloc::id()),
)?;
deserialize_time.stop();
invoke_context.update_timing(

View File

@ -7,6 +7,7 @@ use solana_sdk::{
instruction::InstructionError,
keyed_account::KeyedAccount,
pubkey::Pubkey,
system_instruction::MAX_PERMITTED_DATA_LENGTH,
};
use std::{
io::prelude::*,
@ -48,11 +49,12 @@ pub fn deserialize_parameters(
keyed_accounts: &[KeyedAccount],
buffer: &[u8],
account_lengths: &[usize],
do_support_realloc: bool,
) -> Result<(), InstructionError> {
if *loader_id == bpf_loader_deprecated::id() {
deserialize_parameters_unaligned(keyed_accounts, buffer, account_lengths)
} else {
deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths)
deserialize_parameters_aligned(keyed_accounts, buffer, account_lengths, do_support_realloc)
}
}
@ -261,6 +263,7 @@ pub fn deserialize_parameters_aligned(
keyed_accounts: &[KeyedAccount],
buffer: &[u8],
account_lengths: &[usize],
do_support_realloc: bool,
) -> Result<(), InstructionError> {
let mut start = size_of::<u64>(); // number of accounts
for (i, (keyed_account, pre_len)) in keyed_accounts
@ -285,13 +288,22 @@ pub fn deserialize_parameters_aligned(
start += size_of::<u64>(); // lamports
let post_len = LittleEndian::read_u64(&buffer[start..]) as usize;
start += size_of::<u64>(); // data length
let mut data_end = start + *pre_len;
if post_len != *pre_len
&& (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE
{
data_end = start + post_len;
}
let data_end = if do_support_realloc {
if post_len.saturating_sub(*pre_len) > MAX_PERMITTED_DATA_INCREASE
|| post_len > MAX_PERMITTED_DATA_LENGTH as usize
{
return Err(InstructionError::InvalidRealloc);
}
start + post_len
} else {
let mut data_end = start + *pre_len;
if post_len != *pre_len
&& (post_len.saturating_sub(*pre_len)) <= MAX_PERMITTED_DATA_INCREASE
{
data_end = start + post_len;
}
data_end
};
account.set_data_from_slice(&buffer[start..data_end]);
start += *pre_len + MAX_PERMITTED_DATA_INCREASE; // data
start += (start as *const u8).align_offset(align_of::<u128>());
@ -467,6 +479,7 @@ mod tests {
&de_keyed_accounts,
serialized.as_slice(),
&account_lengths,
true,
)
.unwrap();
for ((account, de_keyed_account), key) in
@ -520,6 +533,7 @@ mod tests {
&de_keyed_accounts,
serialized.as_slice(),
&account_lengths,
true,
)
.unwrap();
for ((account, de_keyed_account), key) in

View File

@ -21,8 +21,9 @@ use solana_sdk::{
feature_set::{
allow_native_ids, blake3_syscall_enabled, check_seed_length,
close_upgradeable_program_accounts, demote_program_write_locks, disable_fees_sysvar,
libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix, return_data_syscall_enabled,
secp256k1_recover_syscall_enabled, sol_log_data_syscall_enabled,
do_support_realloc, libsecp256k1_0_5_upgrade_enabled, mem_overlap_fix,
return_data_syscall_enabled, secp256k1_recover_syscall_enabled,
sol_log_data_syscall_enabled,
},
hash::{Hasher, HASH_BYTES},
ic_msg,
@ -209,6 +210,7 @@ pub fn bind_syscall_context_objects<'a>(
vm: &mut EbpfVm<'a, BpfError, crate::ThisInstructionMeter>,
invoke_context: &'a mut dyn InvokeContext,
heap: AlignedMemory,
orig_data_lens: &'a [usize],
) -> Result<(), EbpfError<BpfError>> {
let compute_budget = invoke_context.get_compute_budget();
@ -430,6 +432,7 @@ pub fn bind_syscall_context_objects<'a>(
vm.bind_syscall_context_object(
Box::new(SyscallInvokeSignedC {
invoke_context: invoke_context.clone(),
orig_data_lens,
loader_id,
}),
None,
@ -437,6 +440,7 @@ pub fn bind_syscall_context_objects<'a>(
vm.bind_syscall_context_object(
Box::new(SyscallInvokeSignedRust {
invoke_context: invoke_context.clone(),
orig_data_lens,
loader_id,
}),
None,
@ -1519,9 +1523,10 @@ impl<'a> SyscallObject<BpfError> for SyscallBlake3<'a> {
// Cross-program invocation syscalls
struct AccountReferences<'a> {
struct CallerAccount<'a> {
lamports: &'a mut u64,
owner: &'a mut Pubkey,
original_data_len: usize,
data: &'a mut [u8],
vm_data_addr: u64,
ref_to_len_in_vm: &'a mut u64,
@ -1531,10 +1536,7 @@ struct AccountReferences<'a> {
}
type TranslatedAccounts<'a> = (
Vec<usize>,
Vec<(
Rc<RefCell<AccountSharedData>>,
Option<AccountReferences<'a>>,
)>,
Vec<(Rc<RefCell<AccountSharedData>>, Option<CallerAccount<'a>>)>,
);
/// Implemented by language specific data structure translators
@ -1567,6 +1569,7 @@ trait SyscallInvokeSigned<'a> {
/// Cross-program invocation called from Rust
pub struct SyscallInvokeSignedRust<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
orig_data_lens: &'a [usize],
loader_id: &'a Pubkey,
}
impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
@ -1694,9 +1697,10 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
)
};
Ok(AccountReferences {
Ok(CallerAccount {
lamports,
owner,
original_data_len: 0, // set later
data,
vm_data_addr,
ref_to_len_in_vm,
@ -1711,6 +1715,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedRust<'a> {
&account_info_keys,
account_infos,
invoke_context,
self.orig_data_lens,
translate,
)
}
@ -1839,6 +1844,7 @@ struct SolSignerSeedsC {
/// Cross-program invocation called from C
pub struct SyscallInvokeSignedC<'a> {
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
orig_data_lens: &'a [usize],
loader_id: &'a Pubkey,
}
impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
@ -1964,9 +1970,10 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
self.loader_id,
)?;
Ok(AccountReferences {
Ok(CallerAccount {
lamports,
owner,
original_data_len: 0, // set later
data,
vm_data_addr,
ref_to_len_in_vm,
@ -1981,6 +1988,7 @@ impl<'a> SyscallInvokeSigned<'a> for SyscallInvokeSignedC<'a> {
&account_info_keys,
account_infos,
invoke_context,
self.orig_data_lens,
translate,
)
}
@ -2065,10 +2073,11 @@ fn get_translated_accounts<'a, T, F>(
account_info_keys: &[&Pubkey],
account_infos: &[T],
invoke_context: &mut dyn InvokeContext,
orig_data_lens: &[usize],
do_translate: F,
) -> Result<TranslatedAccounts<'a>, EbpfError<BpfError>>
where
F: Fn(&T, &mut dyn InvokeContext) -> Result<AccountReferences<'a>, EbpfError<BpfError>>,
F: Fn(&T, &mut dyn InvokeContext) -> Result<CallerAccount<'a>, EbpfError<BpfError>>,
{
let demote_program_write_locks =
invoke_context.is_feature_active(&demote_program_write_locks::id());
@ -2083,25 +2092,27 @@ where
account_indices.push(account_index);
accounts.push((account, None));
continue;
} else if let Some(account_ref_index) =
} else if let Some(caller_account_index) =
account_info_keys.iter().position(|key| *key == account_key)
{
let account_ref = do_translate(&account_infos[account_ref_index], invoke_context)?;
let mut caller_account =
do_translate(&account_infos[caller_account_index], invoke_context)?;
{
let mut account = account.borrow_mut();
account.copy_into_owner_from_slice(account_ref.owner.as_ref());
account.set_data_from_slice(account_ref.data);
account.set_lamports(*account_ref.lamports);
account.set_executable(account_ref.executable);
account.set_rent_epoch(account_ref.rent_epoch);
account.copy_into_owner_from_slice(caller_account.owner.as_ref());
caller_account.original_data_len = orig_data_lens[caller_account_index];
account.set_data_from_slice(caller_account.data);
account.set_lamports(*caller_account.lamports);
account.set_executable(caller_account.executable);
account.set_rent_epoch(caller_account.rent_epoch);
}
let account_ref = if message.is_writable(i, demote_program_write_locks) {
Some(account_ref)
let caller_account = if message.is_writable(i, demote_program_write_locks) {
Some(caller_account)
} else {
None
};
account_indices.push(account_index);
accounts.push((account, account_ref));
accounts.push((account, caller_account));
continue;
}
}
@ -2176,6 +2187,7 @@ fn call<'a>(
invoke_context
.get_compute_meter()
.consume(invoke_context.get_compute_budget().invoke_units)?;
let do_support_realloc = invoke_context.is_feature_active(&do_support_realloc::id());
// Translate and verify caller's data
let instruction =
@ -2219,13 +2231,14 @@ fn call<'a>(
.map_err(SyscallError::InstructionError)?;
// Copy results back to caller
for (account, account_ref) in accounts.iter_mut() {
let account = account.borrow();
if let Some(account_ref) = account_ref {
*account_ref.lamports = account.lamports();
*account_ref.owner = *account.owner();
if account_ref.data.len() != account.data().len() {
if !account_ref.data.is_empty() {
for (callee_account, caller_account) in accounts.iter_mut() {
if let Some(caller_account) = caller_account {
let callee_account = callee_account.borrow();
*caller_account.lamports = callee_account.lamports();
*caller_account.owner = *callee_account.owner();
let new_len = callee_account.data().len();
if caller_account.data.len() != new_len {
if !do_support_realloc && !caller_account.data.is_empty() {
// Only support for `CreateAccount` at this time.
// Need a way to limit total realloc size across multiple CPI calls
ic_msg!(
@ -2236,28 +2249,36 @@ fn call<'a>(
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
);
}
if account.data().len() > account_ref.data.len() + MAX_PERMITTED_DATA_INCREASE {
let data_overflow = if do_support_realloc {
new_len > caller_account.original_data_len + MAX_PERMITTED_DATA_INCREASE
} else {
new_len > caller_account.data.len() + MAX_PERMITTED_DATA_INCREASE
};
if data_overflow {
ic_msg!(
invoke_context,
"SystemProgram::CreateAccount data size limited to {} in inner instructions",
"Account data size realloc limited to {} in inner instructions",
MAX_PERMITTED_DATA_INCREASE
);
return Err(
SyscallError::InstructionError(InstructionError::InvalidRealloc).into(),
);
}
account_ref.data = translate_slice_mut::<u8>(
if new_len < caller_account.data.len() {
caller_account.data[new_len..].fill(0);
}
caller_account.data = translate_slice_mut::<u8>(
memory_mapping,
account_ref.vm_data_addr,
account.data().len() as u64,
caller_account.vm_data_addr,
new_len as u64,
&bpf_loader_deprecated::id(), // Don't care since it is byte aligned
)?;
*account_ref.ref_to_len_in_vm = account.data().len() as u64;
*account_ref.serialized_len_ptr = account.data().len() as u64;
*caller_account.ref_to_len_in_vm = new_len as u64;
*caller_account.serialized_len_ptr = new_len as u64;
}
account_ref
caller_account
.data
.copy_from_slice(&account.data()[0..account_ref.data.len()]);
.copy_from_slice(&callee_account.data()[0..new_len]);
}
}

View File

@ -153,10 +153,10 @@ native machine code before execting it in the virtual machine.",
let mut account_refcells = Vec::new();
let default_account = RefCell::new(AccountSharedData::default());
let key = solana_sdk::pubkey::new_rand();
let mut mem = match matches.value_of("input").unwrap().parse::<usize>() {
let (mut mem, account_lengths) = match matches.value_of("input").unwrap().parse::<usize>() {
Ok(allocate) => {
accounts.push(KeyedAccount::new(&key, false, &default_account));
vec![0u8; allocate]
(vec![0u8; allocate], vec![allocate])
}
Err(_) => {
let input = load_accounts(Path::new(matches.value_of("input").unwrap())).unwrap();
@ -170,9 +170,9 @@ native machine code before execting it in the virtual machine.",
}
let lid = bpf_loader::id();
let pid = Pubkey::new(&[0u8; 32]);
let (mut bytes, _account_lenghts) =
let (mut bytes, account_lengths) =
serialize_parameters(&lid, &pid, &accounts, &input.insndata).unwrap();
Vec::from(bytes.as_slice_mut())
(Vec::from(bytes.as_slice_mut()), account_lengths)
}
};
let mut invoke_context = MockInvokeContext::new(accounts);
@ -228,7 +228,14 @@ native machine code before execting it in the virtual machine.",
}
let id = bpf_loader::id();
let mut vm = create_vm(&id, executable.as_ref(), &mut mem, &mut invoke_context).unwrap();
let mut vm = create_vm(
&id,
executable.as_ref(),
&mut mem,
&mut invoke_context,
&account_lengths,
)
.unwrap();
let start_time = Instant::now();
let result = if matches.value_of("use").unwrap() == "interpreter" {
vm.execute_program_interpreted(&mut instruction_meter)

View File

@ -10,7 +10,8 @@ use solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
compute_budget::ComputeBudget,
feature_set::{
demote_program_write_locks, neon_evm_compute_budget, tx_wide_compute_cap, FeatureSet,
demote_program_write_locks, do_support_realloc, neon_evm_compute_budget,
tx_wide_compute_cap, FeatureSet,
},
fee_calculator::FeeCalculator,
hash::Hash,
@ -206,6 +207,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
) -> Result<(), InstructionError> {
let program_id = instruction.program_id(&message.account_keys);
let demote_program_write_locks = self.is_feature_active(&demote_program_write_locks::id());
let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id());
// Verify all executable accounts have zero outstanding refs
for account_index in program_indices.iter() {
@ -234,6 +236,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
&account,
&mut self.timings,
true,
do_support_realloc,
)
.map_err(|err| {
ic_logger_msg!(
@ -262,6 +265,7 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
account_indices: &[usize],
write_privileges: &[bool],
) -> Result<(), InstructionError> {
let do_support_realloc = self.feature_set.is_active(&do_support_realloc::id());
let stack_frame = self
.invoke_stack
.last()
@ -293,7 +297,15 @@ impl<'a> InvokeContext for ThisInvokeContext<'a> {
}
let account = account.borrow();
pre_account
.verify(program_id, is_writable, rent, &account, timings, false)
.verify(
program_id,
is_writable,
rent,
&account,
timings,
false,
do_support_realloc,
)
.map_err(|err| {
ic_logger_msg!(logger, "failed to verify account {}: {}", key, err);
err

View File

@ -1,4 +1,6 @@
use crate::{clock::Epoch, program_error::ProgramError, pubkey::Pubkey};
use crate::{
clock::Epoch, program_error::ProgramError, program_memory::sol_memset, pubkey::Pubkey,
};
use std::{
cell::{Ref, RefCell, RefMut},
cmp, fmt,
@ -114,6 +116,53 @@ impl<'a> AccountInfo<'a> {
.map_err(|_| ProgramError::AccountBorrowFailed)
}
/// Realloc the account's data and optionally zero-initialize the new
/// memory.
///
/// Note: Account data can be increased within a single call by up to
/// `solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE` bytes.
///
/// Note: Memory used to grow is already zero-initialized upon program
/// entrypoint and re-zeroing it wastes compute units. If within the same
/// call a program reallocs from larger to smaller and back to larger again
/// the new space could contain stale data. Pass `true` for `zero_init` in
/// this case, otherwise compute units will be wasted re-zero-initializing.
pub fn realloc(&self, new_len: usize, zero_init: bool) -> Result<(), ProgramError> {
let orig_len = self.data_len();
// realloc
unsafe {
// First set new length in the serialized data
let ptr = self.try_borrow_mut_data()?.as_mut_ptr().offset(-8) as *mut u64;
*ptr = new_len as u64;
// Then set the new length in the local slice
let ptr = &mut *(((self.data.as_ptr() as *const u64).offset(1) as u64) as *mut u64);
*ptr = new_len as u64;
}
// zero-init if requested
if zero_init && new_len > orig_len {
sol_memset(
&mut self.try_borrow_mut_data()?[orig_len..],
0,
new_len.saturating_sub(orig_len),
);
}
Ok(())
}
pub fn assign(&self, new_owner: &Pubkey) {
// Set the non-mut owner field
unsafe {
std::ptr::write_volatile(
self.owner as *const Pubkey as *mut [u8; 32],
new_owner.to_bytes(),
);
}
}
pub fn new(
key: &'a Pubkey,
is_signer: bool,

View File

@ -66,8 +66,8 @@ pub enum InstructionError {
#[error("sum of account balances before and after instruction do not match")]
UnbalancedInstruction,
/// Program modified an account's program id
#[error("instruction modified the program id of an account")]
/// Program illegally modified an account's program id
#[error("instruction illegally modified the program id of an account")]
ModifiedProgramId,
/// Program spent the lamports of an account that doesn't belong to it
@ -103,8 +103,8 @@ pub enum InstructionError {
#[error("insufficient account keys for instruction")]
NotEnoughAccountKeys,
/// A non-system program changed the size of the account data
#[error("non-system instruction changed account size")]
/// Program other than the account's owner changed the size of the account data
#[error("program other than the account's owner changed the size of the account data")]
AccountDataSizeChanged,
/// The instruction expected an executable account

View File

@ -219,6 +219,10 @@ pub mod stakes_remove_delegation_if_inactive {
solana_sdk::declare_id!("HFpdDDNQjvcXnXKec697HDDsyk6tFoWS2o8fkxuhQZpL");
}
pub mod do_support_realloc {
solana_sdk::declare_id!("75m6ysz33AfLA5DDEzWM1obBrnPQRSsdVQ2nRmc8Vuu1");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -269,6 +273,7 @@ lazy_static! {
(reduce_required_deploy_balance::id(), "reduce required payer balance for program deploys"),
(sol_log_data_syscall_enabled::id(), "enable sol_log_data syscall"),
(stakes_remove_delegation_if_inactive::id(), "remove delegations from stakes cache when inactive"),
(do_support_realloc::id(), "support account data reallocation"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()