Prevent privilege escalation (#10232)

automerge
This commit is contained in:
Jack May
2020-05-26 01:02:31 -07:00
committed by GitHub
parent 5d96fcec63
commit 03abd3ddd7
13 changed files with 458 additions and 392 deletions

View File

@ -80,7 +80,7 @@ fn bench_program_alu(bencher: &mut Bencher) {
let mut invoke_context = MockInvokeContext::default();
let elf = load_elf().unwrap();
let (mut vm, _) = solana_bpf_loader_program::create_vm(&elf, &mut invoke_context).unwrap();
let (mut vm, _) = solana_bpf_loader_program::create_vm(&elf, &[], &mut invoke_context).unwrap();
println!("Interpreted:");
assert_eq!(
@ -145,7 +145,6 @@ impl InvokeContext for MockInvokeContext {
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_signers: &[Pubkey],
_accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
Ok(())

View File

@ -4,6 +4,10 @@
#include "../invoked/instruction.h"
#include <solana_sdk.h>
static const uint8_t TEST_SUCCESS = 1;
static const uint8_t TEST_PRIVILEGE_ESCALATION_SIGNER = 2;
static const uint8_t TEST_PRIVILEGE_ESCALATION_WRITABLE = 3;
static const int MINT_INDEX = 0;
static const int ARGUMENT_INDEX = 1;
static const int INVOKED_PROGRAM_INDEX = 2;
@ -26,127 +30,168 @@ extern uint64_t entrypoint(const uint8_t *input) {
return ERROR_INVALID_ARGUMENT;
}
sol_log("Call system program");
{
sol_assert(*accounts[FROM_INDEX].lamports = 43);
sol_assert(*accounts[ARGUMENT_INDEX].lamports = 41);
SolAccountMeta arguments[] = {{accounts[FROM_INDEX].key, false, true},
{accounts[ARGUMENT_INDEX].key, false, false}};
uint8_t data[] = {2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0};
const SolInstruction instruction = {accounts[SYSTEM_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_assert(*accounts[FROM_INDEX].lamports = 42);
sol_assert(*accounts[ARGUMENT_INDEX].lamports = 42);
}
sol_log("Test data translation");
{
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
accounts[ARGUMENT_INDEX].data[i] = i;
switch (params.data[0]) {
case TEST_SUCCESS: {
sol_log("Call system program");
{
sol_assert(*accounts[FROM_INDEX].lamports = 43);
sol_assert(*accounts[ARGUMENT_INDEX].lamports = 41);
SolAccountMeta arguments[] = {
{accounts[FROM_INDEX].key, false, true},
{accounts[ARGUMENT_INDEX].key, false, false}};
uint8_t data[] = {2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0};
const SolInstruction instruction = {accounts[SYSTEM_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_assert(*accounts[FROM_INDEX].lamports = 42);
sol_assert(*accounts[ARGUMENT_INDEX].lamports = 42);
}
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_PROGRAM_INDEX].key, false, false},
{accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false}};
uint8_t data[] = {TEST_VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_log("Test data translation");
{
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
accounts[ARGUMENT_INDEX].data[i] = i;
}
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
SolAccountMeta arguments[] = {
{accounts[ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[INVOKED_PROGRAM_INDEX].key, false, false},
{accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false}};
uint8_t data[] = {TEST_VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test return error");
{
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = {TEST_RETURN_ERROR};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(42 ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test derived signers");
{
sol_assert(!accounts[DERIVED_KEY1_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY3_INDEX].is_signer);
SolAccountMeta arguments[] = {
{accounts[INVOKED_PROGRAM_INDEX].key, false, false},
{accounts[DERIVED_KEY1_INDEX].key, true, true},
{accounts[DERIVED_KEY2_INDEX].key, true, false},
{accounts[DERIVED_KEY3_INDEX].key, false, false}};
uint8_t data[] = {TEST_DERIVED_SIGNERS};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
char seed1[] = "You pass butter";
char seed2[] = "Lil'";
char seed3[] = "Bits";
const SolSignerSeed seeds1[] = {{seed1, sol_strlen(seed1)}};
const SolSignerSeed seeds2[] = {{seed2, sol_strlen(seed2)},
{seed3, sol_strlen(seed3)}};
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts,
SOL_ARRAY_SIZE(accounts),
signers_seeds,
SOL_ARRAY_SIZE(signers_seeds)));
}
sol_log("Test readonly with writable account");
{
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, false}};
uint8_t data[] = {TEST_VERIFY_WRITER};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test invoke");
{
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
*accounts[ARGUMENT_INDEX].lamports -= 5;
*accounts[INVOKED_ARGUMENT_INDEX].lamports += 5;
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = {TEST_NESTED_INVOKE};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_log("First invoke");
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_log("2nd invoke from first program");
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42 - 5 + 1 + 1);
sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10 + 5 - 1 - 1);
}
sol_log("Verify data values are retained and updated");
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[ARGUMENT_INDEX].data[i] == i);
}
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data[i] == i);
}
break;
}
sol_log("Test return error");
{
SolAccountMeta arguments[] = {{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = {TEST_RETURN_ERROR};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(42 ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test derived signers");
{
sol_assert(!accounts[DERIVED_KEY1_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer);
sol_assert(!accounts[DERIVED_KEY3_INDEX].is_signer);
case TEST_PRIVILEGE_ESCALATION_SIGNER: {
sol_log("Test privilege escalation signer");
SolAccountMeta arguments[] = {
{accounts[INVOKED_PROGRAM_INDEX].key, false, false},
{accounts[DERIVED_KEY1_INDEX].key, true, true},
{accounts[DERIVED_KEY2_INDEX].key, true, false},
{accounts[DERIVED_KEY3_INDEX].key, false, false}};
uint8_t data[] = {TEST_DERIVED_SIGNERS};
uint8_t data[] = {TEST_VERIFY_PRIVILEGE_ESCALATION};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
char seed1[] = "You pass butter";
char seed2[] = "Lil'";
char seed3[] = "Bits";
const SolSignerSeed seeds1[] = {{seed1, sol_strlen(seed1)}};
const SolSignerSeed seeds2[] = {{seed2, sol_strlen(seed2)},
{seed3, sol_strlen(seed3)}};
const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)},
{seeds2, SOL_ARRAY_SIZE(seeds2)}};
sol_assert(SUCCESS == sol_invoke_signed(
&instruction, accounts, SOL_ARRAY_SIZE(accounts),
signers_seeds, SOL_ARRAY_SIZE(signers_seeds)));
}
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_log("Test readonly with writable account");
{
instruction.accounts[0].is_signer = true;
sol_assert(SUCCESS !=
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
break;
}
case TEST_PRIVILEGE_ESCALATION_WRITABLE: {
sol_log("Test privilege escalation writable");
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, false}};
uint8_t data[] = {TEST_VERIFY_WRITER};
{accounts[DERIVED_KEY3_INDEX].key, false, false}};
uint8_t data[] = {TEST_VERIFY_PRIVILEGE_ESCALATION};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
}
sol_log("Test invoke");
{
sol_assert(accounts[ARGUMENT_INDEX].is_signer);
*accounts[ARGUMENT_INDEX].lamports -= 5;
*accounts[INVOKED_ARGUMENT_INDEX].lamports += 5;
SolAccountMeta arguments[] = {
{accounts[INVOKED_ARGUMENT_INDEX].key, true, true},
{accounts[ARGUMENT_INDEX].key, true, true}};
uint8_t data[] = {TEST_NESTED_INVOKE};
const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key,
arguments, SOL_ARRAY_SIZE(arguments),
data, SOL_ARRAY_SIZE(data)};
sol_log("Fist invoke");
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_log("2nd invoke from first program");
sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
sol_assert(*accounts[ARGUMENT_INDEX].lamports == 42 - 5 + 1 + 1);
sol_assert(*accounts[INVOKED_ARGUMENT_INDEX].lamports == 10 + 5 - 1 - 1);
instruction.accounts[0].is_writable = true;
sol_assert(SUCCESS !=
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
break;
}
sol_log("Verify data values are retained and updated");
for (int i = 0; i < accounts[ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[ARGUMENT_INDEX].data[i] == i);
}
for (int i = 0; i < accounts[INVOKED_ARGUMENT_INDEX].data_len; i++) {
sol_assert(accounts[INVOKED_ARGUMENT_INDEX].data[i] == i);
default:
sol_panic();
}
return SUCCESS;

View File

@ -2,9 +2,12 @@
* @brief Instruction definitions for the invoked program
*/
const int TEST_VERIFY_TRANSLATIONS = 0;
const int TEST_RETURN_ERROR = 1;
const int TEST_DERIVED_SIGNERS = 2;
const int TEST_VERIFY_NESTED_SIGNERS = 3;
const int TEST_VERIFY_WRITER = 4;
const int TEST_NESTED_INVOKE = 5;
#include <solana_sdk.h>
const uint8_t TEST_VERIFY_TRANSLATIONS = 0;
const uint8_t TEST_RETURN_ERROR = 1;
const uint8_t TEST_DERIVED_SIGNERS = 2;
const uint8_t TEST_VERIFY_NESTED_SIGNERS = 3;
const uint8_t TEST_VERIFY_WRITER = 4;
const uint8_t TEST_VERIFY_PRIVILEGE_ESCALATION = 5;
const uint8_t TEST_NESTED_INVOKE = 6;

View File

@ -136,6 +136,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_assert(accounts[ARGUMENT_INDEX].is_writable);
break;
}
case TEST_VERIFY_PRIVILEGE_ESCALATION: {
sol_log("Success");
}
case TEST_NESTED_INVOKE: {
sol_log("invoke");

View File

@ -16,6 +16,10 @@ use solana_sdk::{
system_instruction,
};
const TEST_SUCCESS: u8 = 1;
const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2;
const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3;
// const MINT_INDEX: usize = 0;
const ARGUMENT_INDEX: usize = 1;
const INVOKED_PROGRAM_INDEX: usize = 2;
@ -32,128 +36,166 @@ entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
instruction_data: &[u8],
) -> ProgramResult {
info!("invoke Rust program");
info!("Call system program");
{
assert_eq!(accounts[FROM_INDEX].lamports(), 43);
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 41);
let instruction =
system_instruction::transfer(accounts[FROM_INDEX].key, accounts[ARGUMENT_INDEX].key, 1);
invoke(&instruction, accounts)?;
assert_eq!(accounts[FROM_INDEX].lamports(), 42);
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42);
}
match instruction_data[0] {
TEST_SUCCESS => {
info!("Call system program");
{
assert_eq!(accounts[FROM_INDEX].lamports(), 43);
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 41);
let instruction = system_instruction::transfer(
accounts[FROM_INDEX].key,
accounts[ARGUMENT_INDEX].key,
1,
);
invoke(&instruction, accounts)?;
assert_eq!(accounts[FROM_INDEX].lamports(), 42);
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42);
}
info!("Test data translation");
{
{
let mut data = accounts[ARGUMENT_INDEX].try_borrow_mut_data()?;
for i in 0..100 {
data[i as usize] = i;
info!("Test data translation");
{
{
let mut data = accounts[ARGUMENT_INDEX].try_borrow_mut_data()?;
for i in 0..100 {
data[i as usize] = i;
}
}
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_PROGRAM_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![TEST_VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5],
);
invoke(&instruction, accounts)?;
}
info!("Test return error");
{
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, true, true)],
vec![TEST_RETURN_ERROR],
);
assert_eq!(
invoke(&instruction, accounts),
Err(ProgramError::Custom(42))
);
}
info!("Test derived signers");
{
assert!(!accounts[DERIVED_KEY1_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY3_INDEX].is_signer);
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[INVOKED_PROGRAM_INDEX].key, false, false),
(accounts[DERIVED_KEY1_INDEX].key, true, true),
(accounts[DERIVED_KEY2_INDEX].key, true, false),
(accounts[DERIVED_KEY3_INDEX].key, false, false),
],
vec![TEST_DERIVED_SIGNERS],
);
invoke_signed(
&invoked_instruction,
accounts,
&[&["You pass butter"], &["Lil'", "Bits"]],
)?;
}
info!("Test readonly with writable account");
{
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, false, true)],
vec![TEST_VERIFY_WRITER],
);
invoke(&invoked_instruction, accounts)?;
}
info!("Test nested invoke");
{
assert!(accounts[ARGUMENT_INDEX].is_signer);
**accounts[ARGUMENT_INDEX].lamports.borrow_mut() -= 5;
**accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() += 5;
info!("First invoke");
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![TEST_NESTED_INVOKE],
);
invoke(&instruction, accounts)?;
info!("2nd invoke from first program");
invoke(&instruction, accounts)?;
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1);
assert_eq!(
accounts[INVOKED_ARGUMENT_INDEX].lamports(),
10 + 5 - 1 - 1 - 1 - 1
);
}
info!("Verify data values are retained and updated");
{
let data = accounts[ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..100 {
assert_eq!(data[i as usize], i);
}
let data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..10 {
assert_eq!(data[i as usize], i);
}
}
}
TEST_PRIVILEGE_ESCALATION_SIGNER => {
info!("Test privilege escalation signer");
let mut invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[DERIVED_KEY3_INDEX].key, false, false)],
vec![TEST_VERIFY_PRIVILEGE_ESCALATION],
);
invoke(&invoked_instruction, accounts)?;
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_PROGRAM_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![TEST_VERIFY_TRANSLATIONS, 1, 2, 3, 4, 5],
);
invoke(&instruction, accounts)?;
}
info!("Test return error");
{
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, true, true)],
vec![TEST_RETURN_ERROR],
);
assert_eq!(
invoke(&instruction, accounts),
Err(ProgramError::Custom(42))
);
}
info!("Test derived signers");
{
assert!(!accounts[DERIVED_KEY1_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY2_INDEX].is_signer);
assert!(!accounts[DERIVED_KEY3_INDEX].is_signer);
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[INVOKED_PROGRAM_INDEX].key, false, false),
(accounts[DERIVED_KEY1_INDEX].key, true, true),
(accounts[DERIVED_KEY2_INDEX].key, true, false),
(accounts[DERIVED_KEY3_INDEX].key, false, false),
],
vec![TEST_DERIVED_SIGNERS],
);
invoke_signed(
&invoked_instruction,
accounts,
&[&["You pass butter"], &["Lil'", "Bits"]],
)?;
}
info!("Test readonly with writable account");
{
let invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[ARGUMENT_INDEX].key, false, true)],
vec![TEST_VERIFY_WRITER],
);
invoke(&invoked_instruction, accounts)?;
}
info!("Test nested invoke");
{
assert!(accounts[ARGUMENT_INDEX].is_signer);
**accounts[ARGUMENT_INDEX].lamports.borrow_mut() -= 5;
**accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() += 5;
info!("Fist invoke");
let instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[
(accounts[ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_ARGUMENT_INDEX].key, true, true),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
(accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false),
],
vec![TEST_NESTED_INVOKE],
);
invoke(&instruction, accounts)?;
info!("2nd invoke from first program");
invoke(&instruction, accounts)?;
assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1);
assert_eq!(
accounts[INVOKED_ARGUMENT_INDEX].lamports(),
10 + 5 - 1 - 1 - 1 - 1
);
}
info!("Verify data values are retained and updated");
{
let data = accounts[ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..100 {
assert_eq!(data[i as usize], i);
invoked_instruction.accounts[0].is_signer = true;
assert_eq!(
invoke(&invoked_instruction, accounts),
Err(ProgramError::Custom(0x0b9f_0002))
);
}
let data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_data()?;
for i in 0..10 {
assert_eq!(data[i as usize], i);
TEST_PRIVILEGE_ESCALATION_WRITABLE => {
info!("Test privilege escalation writable");
let mut invoked_instruction = create_instruction(
*accounts[INVOKED_PROGRAM_INDEX].key,
&[(accounts[DERIVED_KEY3_INDEX].key, false, false)],
vec![TEST_VERIFY_PRIVILEGE_ESCALATION],
);
invoke(&invoked_instruction, accounts)?;
invoked_instruction.accounts[0].is_writable = true;
assert_eq!(
invoke(&invoked_instruction, accounts),
Err(ProgramError::Custom(0x0b9f_0002))
);
}
_ => panic!(),
}
Ok(())

View File

@ -10,7 +10,8 @@ pub const TEST_RETURN_ERROR: u8 = 1;
pub const TEST_DERIVED_SIGNERS: u8 = 2;
pub const TEST_VERIFY_NESTED_SIGNERS: u8 = 3;
pub const TEST_VERIFY_WRITER: u8 = 4;
pub const TEST_NESTED_INVOKE: u8 = 5;
pub const TEST_VERIFY_PRIVILEGE_ESCALATION: u8 = 5;
pub const TEST_NESTED_INVOKE: u8 = 6;
pub fn create_instruction(
program_id: Pubkey,

View File

@ -151,6 +151,9 @@ fn process_instruction(
assert!(!accounts[ARGUMENT_INDEX].is_writable);
}
TEST_VERIFY_PRIVILEGE_ESCALATION => {
info!("Success");
}
TEST_NESTED_INVOKE => {
info!("nested invoke");

View File

@ -309,6 +309,10 @@ mod bpf {
fn test_program_bpf_invoke() {
solana_logger::setup();
const TEST_SUCCESS: u8 = 1;
const TEST_PRIVILEGE_ESCALATION_SIGNER: u8 = 2;
const TEST_PRIVILEGE_ESCALATION_WRITABLE: u8 = 3;
let mut programs = Vec::new();
#[cfg(feature = "bpf_c")]
{
@ -369,9 +373,11 @@ mod bpf {
AccountMeta::new(from_keypair.pubkey(), true),
];
let instruction = Instruction::new(invoke_program_id, &1u8, account_metas);
let message = Message::new(&[instruction]);
// success cases
let instruction =
Instruction::new(invoke_program_id, &TEST_SUCCESS, account_metas.clone());
let message = Message::new(&[instruction]);
assert!(bank_client
.send_message(
&[
@ -383,6 +389,52 @@ mod bpf {
message,
)
.is_ok());
// failure cases
let instruction = Instruction::new(
invoke_program_id,
&TEST_PRIVILEGE_ESCALATION_SIGNER,
account_metas.clone(),
);
let message = Message::new(&[instruction]);
assert_eq!(
bank_client
.send_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(194969602))
);
let instruction = Instruction::new(
invoke_program_id,
&TEST_PRIVILEGE_ESCALATION_WRITABLE,
account_metas.clone(),
);
let message = Message::new(&[instruction]);
assert_eq!(
bank_client
.send_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(194969602))
);
}
}
}

View File

@ -57,6 +57,7 @@ impl UserDefinedError for BPFError {}
pub fn create_vm<'a>(
prog: &'a [u8],
parameter_accounts: &'a [KeyedAccount<'a>],
invoke_context: &'a mut dyn InvokeContext,
) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError<BPFError>> {
let mut vm = EbpfVm::new(None)?;
@ -64,7 +65,7 @@ pub fn create_vm<'a>(
vm.set_max_instruction_count(100_000)?;
vm.set_elf(&prog)?;
let heap_region = syscalls::register_syscalls(&mut vm, invoke_context)?;
let heap_region = syscalls::register_syscalls(&mut vm, parameter_accounts, invoke_context)?;
Ok((vm, heap_region))
}
@ -182,13 +183,14 @@ pub fn process_instruction(
)?;
{
let program_account = program.try_account_ref_mut()?;
let (mut vm, heap_region) = match create_vm(&program_account.data, invoke_context) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
return Err(BPFLoaderError::VirtualMachineCreationFailed.into());
}
};
let (mut vm, heap_region) =
match create_vm(&program_account.data, &parameter_accounts, invoke_context) {
Ok(info) => info,
Err(e) => {
warn!("Failed to create BPF VM: {}", e);
return Err(BPFLoaderError::VirtualMachineCreationFailed.into());
}
};
info!("Call BPF program {}", program.unsigned_key());
match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) {
@ -274,7 +276,6 @@ mod tests {
&mut self,
_message: &Message,
_instruction: &CompiledInstruction,
_signers: &[Pubkey],
_accounts: &[Rc<RefCell<Account>>],
) -> Result<(), InstructionError> {
Ok(())

View File

@ -9,6 +9,7 @@ use solana_rbpf::{
use solana_runtime::{builtin_programs::get_builtin_programs, message_processor::MessageProcessor};
use solana_sdk::{
account::Account,
account::KeyedAccount,
account_info::AccountInfo,
bpf_loader,
entrypoint::SUCCESS,
@ -48,6 +49,8 @@ pub enum SyscallError {
ProgramNotSupported,
#[error("{0}")]
InstructionError(InstructionError),
#[error("Cross-program invocation with unauthorized signer or writable account")]
PrivilegeEscalation,
}
impl From<SyscallError> for EbpfError<BPFError> {
fn from(error: SyscallError) -> Self {
@ -69,6 +72,7 @@ const DEFAULT_HEAP_SIZE: usize = 32 * 1024;
pub fn register_syscalls<'a>(
vm: &mut EbpfVm<'a, BPFError>,
callers_keyed_accounts: &'a [KeyedAccount<'a>],
invoke_context: &'a mut dyn InvokeContext,
) -> Result<MemoryRegion, EbpfError<BPFError>> {
// Syscall function common across languages
@ -83,12 +87,14 @@ pub fn register_syscalls<'a>(
vm.register_syscall_with_context_ex(
"sol_invoke_signed_c",
Box::new(SyscallProcessSolInstructionC {
callers_keyed_accounts,
invoke_context: invoke_context.clone(),
}),
)?;
vm.register_syscall_with_context_ex(
"sol_invoke_signed_rust",
Box::new(SyscallProcessInstructionRust {
callers_keyed_accounts,
invoke_context: invoke_context.clone(),
}),
)?;
@ -301,6 +307,7 @@ pub type TranslatedAccounts<'a> = (Vec<Rc<RefCell<Account>>>, Vec<(&'a mut u64,
/// Implemented by language specific data structure translators
trait SyscallProcessInstruction<'a> {
fn get_context_mut(&self) -> Result<RefMut<&'a mut dyn InvokeContext>, EbpfError<BPFError>>;
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>];
fn translate_instruction(
&self,
addr: u64,
@ -325,6 +332,7 @@ trait SyscallProcessInstruction<'a> {
/// Cross-program invocation called from Rust
pub struct SyscallProcessInstructionRust<'a> {
callers_keyed_accounts: &'a [KeyedAccount<'a>],
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> SyscallProcessInstruction<'a> for SyscallProcessInstructionRust<'a> {
@ -333,6 +341,9 @@ impl<'a> SyscallProcessInstruction<'a> for SyscallProcessInstructionRust<'a> {
.try_borrow_mut()
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
}
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>] {
self.callers_keyed_accounts
}
fn translate_instruction(
&self,
addr: u64,
@ -519,6 +530,7 @@ struct SolSignerSeedsC {
/// Cross-program invocation called from C
pub struct SyscallProcessSolInstructionC<'a> {
callers_keyed_accounts: &'a [KeyedAccount<'a>],
invoke_context: Rc<RefCell<&'a mut dyn InvokeContext>>,
}
impl<'a> SyscallProcessInstruction<'a> for SyscallProcessSolInstructionC<'a> {
@ -527,7 +539,9 @@ impl<'a> SyscallProcessInstruction<'a> for SyscallProcessSolInstructionC<'a> {
.try_borrow_mut()
.map_err(|_| SyscallError::InvokeContextBorrowFailed.into())
}
fn get_callers_keyed_accounts(&self) -> &'a [KeyedAccount<'a>] {
self.callers_keyed_accounts
}
fn translate_instruction(
&self,
addr: u64,
@ -673,6 +687,44 @@ impl<'a> SyscallObject<BPFError> for SyscallProcessSolInstructionC<'a> {
}
}
fn verify_instruction<'a>(
syscall: &dyn SyscallProcessInstruction<'a>,
instruction: &Instruction,
signers: &[Pubkey],
) -> Result<(), EbpfError<BPFError>> {
let callers_keyed_accounts = syscall.get_callers_keyed_accounts();
// Check for privilege escalation
for account in instruction.accounts.iter() {
let keyed_account = callers_keyed_accounts
.iter()
.find_map(|keyed_account| {
if &account.pubkey == keyed_account.unsigned_key() {
Some(keyed_account)
} else {
None
}
})
.ok_or(SyscallError::InstructionError(
InstructionError::MissingAccount,
))?;
// Readonly account cannot become writable
if account.is_writable && !keyed_account.is_writable() {
return Err(SyscallError::PrivilegeEscalation.into());
}
if account.is_signer && // If message indicates account is signed
!( // one of the following needs to be true:
keyed_account.signer_key().is_some() // Signed in the parent instruction
|| signers.contains(&account.pubkey) // Signed by the program
) {
return Err(SyscallError::PrivilegeEscalation.into());
}
}
Ok(())
}
/// Call process instruction, common to both Rust and C
fn call<'a>(
syscall: &mut dyn SyscallProcessInstruction<'a>,
@ -689,12 +741,19 @@ fn call<'a>(
// Translate data passed from the VM
let instruction = syscall.translate_instruction(instruction_addr, ro_regions)?;
let message = Message::new_with_payer(&[instruction], None);
let callee_program_id_index = message.instructions[0].program_id_index as usize;
let callee_program_id = message.account_keys[callee_program_id_index];
let caller_program_id = invoke_context
.get_caller()
.map_err(SyscallError::InstructionError)?;
let signers = syscall.translate_signers(
caller_program_id,
signers_seeds_addr,
signers_seeds_len as usize,
ro_regions,
)?;
verify_instruction(syscall, &instruction, &signers)?;
let message = Message::new_with_payer(&[instruction], None);
let callee_program_id_index = message.instructions[0].program_id_index as usize;
let callee_program_id = message.account_keys[callee_program_id_index];
let (accounts, refs) = syscall.translate_accounts(
&message,
account_infos_addr,
@ -702,12 +761,6 @@ fn call<'a>(
ro_regions,
rw_regions,
)?;
let signers = syscall.translate_signers(
caller_program_id,
signers_seeds_addr,
signers_seeds_len as usize,
ro_regions,
)?;
// Process instruction
@ -725,7 +778,6 @@ fn call<'a>(
&message,
&executable_accounts,
&accounts,
&signers,
*(&mut *invoke_context),
) {
Ok(()) => (),