Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 64010f1

Browse files
authored
v1.16: Reverify programs that are extended using ExtendProgram (backport of #31886) (#31920)
1 parent 05e6267 commit 64010f1

File tree

3 files changed

+136
-7
lines changed

3 files changed

+136
-7
lines changed

programs/bpf-loader-tests/noop.so

1.55 KB
Binary file not shown.

programs/bpf-loader-tests/tests/extend_program_ix.rs

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use {
1010
signature::{Keypair, Signer},
1111
system_instruction::{self, SystemError, MAX_PERMITTED_DATA_LENGTH},
1212
system_program,
13-
transaction::Transaction,
13+
transaction::{Transaction, TransactionError},
1414
},
1515
};
1616

@@ -19,10 +19,11 @@ mod common;
1919
#[tokio::test]
2020
async fn test_extend_program() {
2121
let mut context = setup_test_context().await;
22+
let program_file = find_file("noop.so").expect("Failed to find the file");
23+
let data = read_file(program_file);
2224

2325
let program_address = Pubkey::new_unique();
2426
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
25-
let program_data_len = 100;
2627
add_upgradeable_loader_account(
2728
&mut context,
2829
&program_address,
@@ -33,6 +34,8 @@ async fn test_extend_program() {
3334
|_| {},
3435
)
3536
.await;
37+
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
38+
let program_data_len = data.len() + programdata_data_offset;
3639
add_upgradeable_loader_account(
3740
&mut context,
3841
&programdata_address,
@@ -41,9 +44,68 @@ async fn test_extend_program() {
4144
upgrade_authority_address: Some(Pubkey::new_unique()),
4245
},
4346
program_data_len,
47+
|account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data),
48+
)
49+
.await;
50+
51+
let client = &mut context.banks_client;
52+
let payer = &context.payer;
53+
let recent_blockhash = context.last_blockhash;
54+
const ADDITIONAL_BYTES: u32 = 42;
55+
let transaction = Transaction::new_signed_with_payer(
56+
&[extend_program(
57+
&program_address,
58+
Some(&payer.pubkey()),
59+
ADDITIONAL_BYTES,
60+
)],
61+
Some(&payer.pubkey()),
62+
&[payer],
63+
recent_blockhash,
64+
);
65+
66+
assert_matches!(client.process_transaction(transaction).await, Ok(()));
67+
let updated_program_data_account = client
68+
.get_account(programdata_address)
69+
.await
70+
.unwrap()
71+
.unwrap();
72+
assert_eq!(
73+
updated_program_data_account.data().len(),
74+
program_data_len + ADDITIONAL_BYTES as usize
75+
);
76+
}
77+
78+
#[tokio::test]
79+
async fn test_failed_extend_twice_in_same_slot() {
80+
let mut context = setup_test_context().await;
81+
let program_file = find_file("noop.so").expect("Failed to find the file");
82+
let data = read_file(program_file);
83+
84+
let program_address = Pubkey::new_unique();
85+
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
86+
add_upgradeable_loader_account(
87+
&mut context,
88+
&program_address,
89+
&UpgradeableLoaderState::Program {
90+
programdata_address,
91+
},
92+
UpgradeableLoaderState::size_of_program(),
4493
|_| {},
4594
)
4695
.await;
96+
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
97+
let program_data_len = data.len() + programdata_data_offset;
98+
add_upgradeable_loader_account(
99+
&mut context,
100+
&programdata_address,
101+
&UpgradeableLoaderState::ProgramData {
102+
slot: 0,
103+
upgrade_authority_address: Some(Pubkey::new_unique()),
104+
},
105+
program_data_len,
106+
|account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data),
107+
)
108+
.await;
47109

48110
let client = &mut context.banks_client;
49111
let payer = &context.payer;
@@ -70,6 +132,31 @@ async fn test_extend_program() {
70132
updated_program_data_account.data().len(),
71133
program_data_len + ADDITIONAL_BYTES as usize
72134
);
135+
136+
let recent_blockhash = client
137+
.get_new_latest_blockhash(&recent_blockhash)
138+
.await
139+
.unwrap();
140+
// Extending the program in the same slot should fail
141+
let transaction = Transaction::new_signed_with_payer(
142+
&[extend_program(
143+
&program_address,
144+
Some(&payer.pubkey()),
145+
ADDITIONAL_BYTES,
146+
)],
147+
Some(&payer.pubkey()),
148+
&[payer],
149+
recent_blockhash,
150+
);
151+
152+
assert_matches!(
153+
client
154+
.process_transaction(transaction)
155+
.await
156+
.unwrap_err()
157+
.unwrap(),
158+
TransactionError::InstructionError(0, InstructionError::InvalidArgument)
159+
);
73160
}
74161

75162
#[tokio::test]
@@ -293,6 +380,9 @@ async fn test_extend_program_without_payer() {
293380
let mut context = setup_test_context().await;
294381
let rent = context.banks_client.get_rent().await.unwrap();
295382

383+
let program_file = find_file("noop.so").expect("Failed to find the file");
384+
let data = read_file(program_file);
385+
296386
let program_address = Pubkey::new_unique();
297387
let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id());
298388
add_upgradeable_loader_account(
@@ -305,7 +395,8 @@ async fn test_extend_program_without_payer() {
305395
|_| {},
306396
)
307397
.await;
308-
let program_data_len = 100;
398+
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
399+
let program_data_len = data.len() + programdata_data_offset;
309400
add_upgradeable_loader_account(
310401
&mut context,
311402
&programdata_address,
@@ -314,7 +405,7 @@ async fn test_extend_program_without_payer() {
314405
upgrade_authority_address: Some(Pubkey::new_unique()),
315406
},
316407
program_data_len,
317-
|_| {},
408+
|account| account.data_as_mut_slice()[programdata_data_offset..].copy_from_slice(&data),
318409
)
319410
.await;
320411

programs/bpf_loader/src/lib.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@ fn process_loader_upgradeable_instruction(
13251325
ic_logger_msg!(log_collector, "Program account not owned by loader");
13261326
return Err(InstructionError::InvalidAccountOwner);
13271327
}
1328+
let program_key = *program_account.get_key();
13281329
match program_account.get_state()? {
13291330
UpgradeableLoaderState::Program {
13301331
programdata_address,
@@ -1356,22 +1357,33 @@ fn process_loader_upgradeable_instruction(
13561357
return Err(InstructionError::InvalidRealloc);
13571358
}
13581359

1359-
if let UpgradeableLoaderState::ProgramData {
1360-
slot: _,
1360+
let clock_slot = invoke_context
1361+
.get_sysvar_cache()
1362+
.get_clock()
1363+
.map(|clock| clock.slot)?;
1364+
1365+
let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData {
1366+
slot,
13611367
upgrade_authority_address,
13621368
} = programdata_account.get_state()?
13631369
{
1370+
if clock_slot == slot {
1371+
ic_logger_msg!(log_collector, "Program was extended in this block already");
1372+
return Err(InstructionError::InvalidArgument);
1373+
}
1374+
13641375
if upgrade_authority_address.is_none() {
13651376
ic_logger_msg!(
13661377
log_collector,
13671378
"Cannot extend ProgramData accounts that are not upgradeable"
13681379
);
13691380
return Err(InstructionError::Immutable);
13701381
}
1382+
upgrade_authority_address
13711383
} else {
13721384
ic_logger_msg!(log_collector, "ProgramData state is invalid");
13731385
return Err(InstructionError::InvalidAccountData);
1374-
}
1386+
};
13751387

13761388
let required_payment = {
13771389
let balance = programdata_account.get_lamports();
@@ -1383,6 +1395,8 @@ fn process_loader_upgradeable_instruction(
13831395
// Borrowed accounts need to be dropped before native_invoke
13841396
drop(programdata_account);
13851397

1398+
// Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context
1399+
let program_id = *program_id;
13861400
if required_payment > 0 {
13871401
let payer_key = *transaction_context.get_key_of_account_at_index(
13881402
instruction_context.get_index_of_instruction_account_in_transaction(
@@ -1403,6 +1417,30 @@ fn process_loader_upgradeable_instruction(
14031417
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
14041418
programdata_account.set_data_length(new_len)?;
14051419

1420+
let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
1421+
1422+
deploy_program!(
1423+
invoke_context,
1424+
program_key,
1425+
&program_id,
1426+
UpgradeableLoaderState::size_of_program().saturating_add(new_len),
1427+
clock_slot,
1428+
{
1429+
drop(programdata_account);
1430+
},
1431+
programdata_account
1432+
.get_data()
1433+
.get(programdata_data_offset..)
1434+
.ok_or(InstructionError::AccountDataTooSmall)?,
1435+
);
1436+
1437+
let mut programdata_account = instruction_context
1438+
.try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?;
1439+
programdata_account.set_state(&UpgradeableLoaderState::ProgramData {
1440+
slot: clock_slot,
1441+
upgrade_authority_address,
1442+
})?;
1443+
14061444
ic_logger_msg!(
14071445
log_collector,
14081446
"Extended ProgramData account by {} bytes",

0 commit comments

Comments
 (0)