Skip to content

Commit f4c5052

Browse files
committed
Adds LoaderV4Instruction::Copy.
1 parent fa62025 commit f4c5052

File tree

4 files changed

+276
-11
lines changed

4 files changed

+276
-11
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

programs/loader-v4/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ solana-bincode = { workspace = true }
1616
solana-bpf-loader-program = { workspace = true, features = ["svm-internal"] }
1717
solana-compute-budget = { workspace = true }
1818
solana-instruction = { workspace = true }
19+
solana-loader-v3-interface = { workspace = true }
1920
solana-loader-v4-interface = { workspace = true }
2021
solana-log-collector = { workspace = true }
2122
solana-measure = { workspace = true }

programs/loader-v4/src/lib.rs

Lines changed: 205 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use {
44
solana_bincode::limited_deserialize,
55
solana_bpf_loader_program::{deploy_program, execute},
66
solana_instruction::error::InstructionError,
7+
solana_loader_v3_interface::state::UpgradeableLoaderState,
78
solana_loader_v4_interface::{
89
instruction::LoaderV4Instruction,
910
state::{LoaderV4State, LoaderV4Status},
@@ -17,7 +18,7 @@ use {
1718
},
1819
solana_pubkey::Pubkey,
1920
solana_sbpf::{declare_builtin_function, memory_region::MemoryMapping},
20-
solana_sdk_ids::loader_v4,
21+
solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, loader_v4},
2122
solana_transaction_context::{BorrowedAccount, InstructionContext},
2223
solana_type_overrides::sync::{atomic::Ordering, Arc},
2324
std::{cell::RefCell, rc::Rc},
@@ -106,13 +107,10 @@ fn process_instruction_write(
106107
ic_logger_msg!(log_collector, "Program is not retracted");
107108
return Err(InstructionError::InvalidArgument);
108109
}
109-
let end_offset = (offset as usize).saturating_add(bytes.len());
110+
let destination_offset = (offset as usize).saturating_add(LoaderV4State::program_data_offset());
110111
program
111112
.get_data_mut()?
112-
.get_mut(
113-
LoaderV4State::program_data_offset().saturating_add(offset as usize)
114-
..LoaderV4State::program_data_offset().saturating_add(end_offset),
115-
)
113+
.get_mut(destination_offset..destination_offset.saturating_add(bytes.len()))
116114
.ok_or_else(|| {
117115
ic_logger_msg!(log_collector, "Write out of bounds");
118116
InstructionError::AccountDataTooSmall
@@ -121,6 +119,65 @@ fn process_instruction_write(
121119
Ok(())
122120
}
123121

122+
fn process_instruction_copy(
123+
invoke_context: &mut InvokeContext,
124+
destination_offset: u32,
125+
source_offset: u32,
126+
length: u32,
127+
) -> Result<(), InstructionError> {
128+
let log_collector = invoke_context.get_log_collector();
129+
let transaction_context = &invoke_context.transaction_context;
130+
let instruction_context = transaction_context.get_current_instruction_context()?;
131+
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
132+
let authority_address = instruction_context
133+
.get_index_of_instruction_account_in_transaction(1)
134+
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
135+
let source_program =
136+
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
137+
let state = check_program_account(
138+
&log_collector,
139+
instruction_context,
140+
&program,
141+
authority_address,
142+
)?;
143+
if !matches!(state.status, LoaderV4Status::Retracted) {
144+
ic_logger_msg!(log_collector, "Program is not retracted");
145+
return Err(InstructionError::InvalidArgument);
146+
}
147+
let source_owner = &source_program.get_owner();
148+
let source_offset =
149+
(source_offset as usize).saturating_add(if loader_v4::check_id(source_owner) {
150+
LoaderV4State::program_data_offset()
151+
} else if bpf_loader_upgradeable::check_id(source_owner) {
152+
UpgradeableLoaderState::size_of_programdata_metadata()
153+
} else if bpf_loader_deprecated::check_id(source_owner)
154+
|| bpf_loader::check_id(source_owner)
155+
{
156+
0
157+
} else {
158+
ic_logger_msg!(log_collector, "Source is not a program");
159+
return Err(InstructionError::InvalidArgument);
160+
});
161+
let data = source_program
162+
.get_data()
163+
.get(source_offset..source_offset.saturating_add(length as usize))
164+
.ok_or_else(|| {
165+
ic_logger_msg!(log_collector, "Read out of bounds");
166+
InstructionError::AccountDataTooSmall
167+
})?;
168+
let destination_offset =
169+
(destination_offset as usize).saturating_add(LoaderV4State::program_data_offset());
170+
program
171+
.get_data_mut()?
172+
.get_mut(destination_offset..destination_offset.saturating_add(length as usize))
173+
.ok_or_else(|| {
174+
ic_logger_msg!(log_collector, "Write out of bounds");
175+
InstructionError::AccountDataTooSmall
176+
})?
177+
.copy_from_slice(data);
178+
Ok(())
179+
}
180+
124181
fn process_instruction_truncate(
125182
invoke_context: &mut InvokeContext,
126183
new_size: u32,
@@ -434,6 +491,13 @@ fn process_instruction_inner(
434491
LoaderV4Instruction::Write { offset, bytes } => {
435492
process_instruction_write(invoke_context, offset, bytes)
436493
}
494+
LoaderV4Instruction::Copy {
495+
destination_offset,
496+
source_offset,
497+
length,
498+
} => {
499+
process_instruction_copy(invoke_context, destination_offset, source_offset, length)
500+
}
437501
LoaderV4Instruction::Truncate { new_size } => {
438502
process_instruction_truncate(invoke_context, new_size)
439503
}
@@ -756,6 +820,141 @@ mod tests {
756820
});
757821
}
758822

823+
#[test]
824+
fn test_loader_instruction_copy() {
825+
let authority_address = Pubkey::new_unique();
826+
let transaction_accounts = vec![
827+
(
828+
Pubkey::new_unique(),
829+
load_program_account_from_elf(
830+
authority_address,
831+
LoaderV4Status::Retracted,
832+
"sbpfv3_return_err",
833+
),
834+
),
835+
(
836+
authority_address,
837+
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
838+
),
839+
(
840+
Pubkey::new_unique(),
841+
load_program_account_from_elf(
842+
authority_address,
843+
LoaderV4Status::Deployed,
844+
"sbpfv3_return_err",
845+
),
846+
),
847+
(
848+
clock::id(),
849+
create_account_shared_data_for_test(&clock::Clock::default()),
850+
),
851+
(
852+
rent::id(),
853+
create_account_shared_data_for_test(&rent::Rent::default()),
854+
),
855+
];
856+
857+
// Overwrite existing data
858+
process_instruction(
859+
vec![],
860+
&bincode::serialize(&LoaderV4Instruction::Copy {
861+
destination_offset: 1,
862+
source_offset: 2,
863+
length: 3,
864+
})
865+
.unwrap(),
866+
transaction_accounts.clone(),
867+
&[(0, false, true), (1, true, false), (2, false, false)],
868+
Ok(()),
869+
);
870+
871+
// Empty copy
872+
process_instruction(
873+
vec![],
874+
&bincode::serialize(&LoaderV4Instruction::Copy {
875+
destination_offset: 1,
876+
source_offset: 2,
877+
length: 0,
878+
})
879+
.unwrap(),
880+
transaction_accounts.clone(),
881+
&[(0, false, true), (1, true, false), (2, false, false)],
882+
Ok(()),
883+
);
884+
885+
// Error: Program is not retracted
886+
process_instruction(
887+
vec![],
888+
&bincode::serialize(&LoaderV4Instruction::Copy {
889+
destination_offset: 1,
890+
source_offset: 2,
891+
length: 3,
892+
})
893+
.unwrap(),
894+
transaction_accounts.clone(),
895+
&[(2, false, true), (1, true, false), (0, false, false)],
896+
Err(InstructionError::InvalidArgument),
897+
);
898+
899+
// Error: Destination and source collide
900+
process_instruction(
901+
vec![],
902+
&bincode::serialize(&LoaderV4Instruction::Copy {
903+
destination_offset: 1,
904+
source_offset: 2,
905+
length: 3,
906+
})
907+
.unwrap(),
908+
transaction_accounts.clone(),
909+
&[(2, false, true), (1, true, false), (2, false, false)],
910+
Err(InstructionError::AccountBorrowFailed),
911+
);
912+
913+
// Error: Read out of bounds
914+
process_instruction(
915+
vec![],
916+
&bincode::serialize(&LoaderV4Instruction::Copy {
917+
destination_offset: 1,
918+
source_offset: transaction_accounts[2]
919+
.1
920+
.data()
921+
.len()
922+
.saturating_sub(LoaderV4State::program_data_offset())
923+
.saturating_sub(3) as u32,
924+
length: 4,
925+
})
926+
.unwrap(),
927+
transaction_accounts.clone(),
928+
&[(0, false, true), (1, true, false), (2, false, false)],
929+
Err(InstructionError::AccountDataTooSmall),
930+
);
931+
932+
// Error: Write out of bounds
933+
process_instruction(
934+
vec![],
935+
&bincode::serialize(&LoaderV4Instruction::Copy {
936+
destination_offset: transaction_accounts[0]
937+
.1
938+
.data()
939+
.len()
940+
.saturating_sub(LoaderV4State::program_data_offset())
941+
.saturating_sub(3) as u32,
942+
source_offset: 2,
943+
length: 4,
944+
})
945+
.unwrap(),
946+
transaction_accounts.clone(),
947+
&[(0, false, true), (1, true, false), (2, false, false)],
948+
Err(InstructionError::AccountDataTooSmall),
949+
);
950+
951+
test_loader_instruction_general_errors(LoaderV4Instruction::Copy {
952+
destination_offset: 1,
953+
source_offset: 2,
954+
length: 3,
955+
});
956+
}
957+
759958
#[test]
760959
fn test_loader_instruction_truncate() {
761960
let authority_address = Pubkey::new_unique();

sdk/loader-v4-interface/src/instruction.rs

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ pub enum LoaderV4Instruction {
2626
bytes: Vec<u8>,
2727
},
2828

29+
/// Copy ELF data into an undeployed program account.
30+
///
31+
/// # Account references
32+
/// 0. `[writable]` The program account to write to.
33+
/// 1. `[signer]` The authority of the program.
34+
/// 2. `[]` The program account to copy from.
35+
Copy {
36+
/// Offset at which to write.
37+
destination_offset: u32,
38+
/// Offset at which to read.
39+
source_offset: u32,
40+
/// Amount of bytes to copy.
41+
length: u32,
42+
},
43+
2944
/// Changes the size of an undeployed program account.
3045
///
3146
/// A program account is automatically initialized when its size is first increased.
@@ -90,26 +105,30 @@ pub fn is_write_instruction(instruction_data: &[u8]) -> bool {
90105
!instruction_data.is_empty() && 0 == instruction_data[0]
91106
}
92107

93-
pub fn is_truncate_instruction(instruction_data: &[u8]) -> bool {
108+
pub fn is_copy_instruction(instruction_data: &[u8]) -> bool {
94109
!instruction_data.is_empty() && 1 == instruction_data[0]
95110
}
96111

97-
pub fn is_deploy_instruction(instruction_data: &[u8]) -> bool {
112+
pub fn is_truncate_instruction(instruction_data: &[u8]) -> bool {
98113
!instruction_data.is_empty() && 2 == instruction_data[0]
99114
}
100115

101-
pub fn is_retract_instruction(instruction_data: &[u8]) -> bool {
116+
pub fn is_deploy_instruction(instruction_data: &[u8]) -> bool {
102117
!instruction_data.is_empty() && 3 == instruction_data[0]
103118
}
104119

105-
pub fn is_transfer_authority_instruction(instruction_data: &[u8]) -> bool {
120+
pub fn is_retract_instruction(instruction_data: &[u8]) -> bool {
106121
!instruction_data.is_empty() && 4 == instruction_data[0]
107122
}
108123

109-
pub fn is_finalize_instruction(instruction_data: &[u8]) -> bool {
124+
pub fn is_transfer_authority_instruction(instruction_data: &[u8]) -> bool {
110125
!instruction_data.is_empty() && 5 == instruction_data[0]
111126
}
112127

128+
pub fn is_finalize_instruction(instruction_data: &[u8]) -> bool {
129+
!instruction_data.is_empty() && 6 == instruction_data[0]
130+
}
131+
113132
/// Returns the instructions required to initialize a program/buffer account.
114133
#[cfg(feature = "bincode")]
115134
pub fn create_buffer(
@@ -190,6 +209,31 @@ pub fn write(
190209
)
191210
}
192211

212+
/// Returns the instructions required to copy a chunk of program data.
213+
#[cfg(feature = "bincode")]
214+
pub fn copy(
215+
program_address: &Pubkey,
216+
authority: &Pubkey,
217+
source_address: &Pubkey,
218+
destination_offset: u32,
219+
source_offset: u32,
220+
length: u32,
221+
) -> Instruction {
222+
Instruction::new_with_bincode(
223+
id(),
224+
&LoaderV4Instruction::Copy {
225+
destination_offset,
226+
source_offset,
227+
length,
228+
},
229+
vec![
230+
AccountMeta::new(*program_address, false),
231+
AccountMeta::new_readonly(*authority, true),
232+
AccountMeta::new_readonly(*source_address, false),
233+
],
234+
)
235+
}
236+
193237
/// Returns the instructions required to deploy a program.
194238
#[cfg(feature = "bincode")]
195239
pub fn deploy(program_address: &Pubkey, authority: &Pubkey) -> Instruction {
@@ -319,6 +363,26 @@ mod tests {
319363
assert!(instruction.accounts[1].is_signer);
320364
}
321365

366+
#[test]
367+
fn test_copy_instruction() {
368+
let program = Pubkey::new_unique();
369+
let authority = Pubkey::new_unique();
370+
let source = Pubkey::new_unique();
371+
let instruction = copy(&program, &authority, &source, 1, 2, 3);
372+
assert!(is_copy_instruction(&instruction.data));
373+
assert_eq!(instruction.program_id, id());
374+
assert_eq!(instruction.accounts.len(), 3);
375+
assert_eq!(instruction.accounts[0].pubkey, program);
376+
assert!(instruction.accounts[0].is_writable);
377+
assert!(!instruction.accounts[0].is_signer);
378+
assert_eq!(instruction.accounts[1].pubkey, authority);
379+
assert!(!instruction.accounts[1].is_writable);
380+
assert!(instruction.accounts[1].is_signer);
381+
assert_eq!(instruction.accounts[2].pubkey, source);
382+
assert!(!instruction.accounts[2].is_writable);
383+
assert!(!instruction.accounts[2].is_signer);
384+
}
385+
322386
#[test]
323387
fn test_truncate_instruction() {
324388
let program = Pubkey::new_unique();

0 commit comments

Comments
 (0)