1use crate::elf::ELF_MAGIC;
42
43pub const BPF_LOADER_V2: &str = "BPFLoader2111111111111111111111111111111111";
46
47pub const BPF_LOADER_UPGRADEABLE: &str = "BPFLoaderUpgradeab1e11111111111111111111111";
50
51pub const LOADER_V4: &str = "LoaderV411111111111111111111111111111111111";
54
55const UPGRADEABLE_STATE_PROGRAM: u32 = 2;
57const UPGRADEABLE_STATE_PROGRAM_DATA: u32 = 3;
59
60const PROGRAMDATA_HEADER_WITH_AUTH: usize = 45;
65
66const PROGRAMDATA_HEADER_NO_AUTH: usize = 13;
69
70const LOADER_V4_HEADER: usize = 48;
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum LoaderKind {
80 BpfLoader2,
82 Upgradeable,
84 LoaderV4,
86 Unknown,
89}
90
91#[derive(Debug, thiserror::Error)]
92pub enum Error {
93 #[error("account data too short for {loader}: need ≥{need} bytes, got {got}")]
94 TooShort {
95 loader: &'static str,
96 need: usize,
97 got: usize,
98 },
99 #[error("ELF magic missing at expected offset {offset} for {loader}")]
100 NotElf { loader: &'static str, offset: usize },
101 #[error("expected {loader} Borsh enum tag {expected} at offset 0, got {got}")]
102 BadEnumTag {
103 loader: &'static str,
104 expected: u32,
105 got: u32,
106 },
107 #[error(
108 "ProgramData has neither {with_auth}-byte nor {no_auth}-byte header that exposes ELF magic"
109 )]
110 AmbiguousProgramData { with_auth: usize, no_auth: usize },
111}
112
113#[must_use]
116pub fn classify_loader(owner: &str) -> LoaderKind {
117 match owner {
118 BPF_LOADER_V2 => LoaderKind::BpfLoader2,
119 BPF_LOADER_UPGRADEABLE => LoaderKind::Upgradeable,
120 LOADER_V4 => LoaderKind::LoaderV4,
121 _ => LoaderKind::Unknown,
122 }
123}
124
125pub fn programdata_pubkey(program_data: &[u8]) -> Result<[u8; 32], Error> {
134 const NEEDED: usize = 36;
135 if program_data.len() < NEEDED {
136 return Err(Error::TooShort {
137 loader: "BPFLoaderUpgradeable Program",
138 need: NEEDED,
139 got: program_data.len(),
140 });
141 }
142 let tag = u32::from_le_bytes([
143 program_data[0],
144 program_data[1],
145 program_data[2],
146 program_data[3],
147 ]);
148 if tag != UPGRADEABLE_STATE_PROGRAM {
149 return Err(Error::BadEnumTag {
150 loader: "BPFLoaderUpgradeable Program",
151 expected: UPGRADEABLE_STATE_PROGRAM,
152 got: tag,
153 });
154 }
155 let mut pubkey = [0u8; 32];
156 pubkey.copy_from_slice(&program_data[4..36]);
157 Ok(pubkey)
158}
159
160pub fn strip_bpf_loader_v2(data: &[u8]) -> Result<&[u8], Error> {
165 verify_elf(data, "BPFLoader2", 0)?;
166 Ok(data)
167}
168
169pub fn strip_bpf_loader_upgradeable(programdata: &[u8]) -> Result<&[u8], Error> {
177 if programdata.len() < PROGRAMDATA_HEADER_NO_AUTH {
178 return Err(Error::TooShort {
179 loader: "BPFLoaderUpgradeable ProgramData",
180 need: PROGRAMDATA_HEADER_NO_AUTH,
181 got: programdata.len(),
182 });
183 }
184 let tag = u32::from_le_bytes([
185 programdata[0],
186 programdata[1],
187 programdata[2],
188 programdata[3],
189 ]);
190 if tag != UPGRADEABLE_STATE_PROGRAM_DATA {
191 return Err(Error::BadEnumTag {
192 loader: "BPFLoaderUpgradeable ProgramData",
193 expected: UPGRADEABLE_STATE_PROGRAM_DATA,
194 got: tag,
195 });
196 }
197 let auth_present = programdata.get(12).copied().unwrap_or(0) != 0;
199 let primary = if auth_present {
200 PROGRAMDATA_HEADER_WITH_AUTH
201 } else {
202 PROGRAMDATA_HEADER_NO_AUTH
203 };
204 if has_elf_at(programdata, primary) {
205 return Ok(&programdata[primary..]);
206 }
207 let alt = if auth_present {
208 PROGRAMDATA_HEADER_NO_AUTH
209 } else {
210 PROGRAMDATA_HEADER_WITH_AUTH
211 };
212 if has_elf_at(programdata, alt) {
213 return Ok(&programdata[alt..]);
214 }
215 Err(Error::AmbiguousProgramData {
216 with_auth: PROGRAMDATA_HEADER_WITH_AUTH,
217 no_auth: PROGRAMDATA_HEADER_NO_AUTH,
218 })
219}
220
221pub fn strip_loader_v4(data: &[u8]) -> Result<&[u8], Error> {
224 if data.len() < LOADER_V4_HEADER + ELF_MAGIC.len() {
225 return Err(Error::TooShort {
226 loader: "LoaderV4",
227 need: LOADER_V4_HEADER + ELF_MAGIC.len(),
228 got: data.len(),
229 });
230 }
231 verify_elf(&data[LOADER_V4_HEADER..], "LoaderV4", LOADER_V4_HEADER)?;
232 Ok(&data[LOADER_V4_HEADER..])
233}
234
235fn has_elf_at(data: &[u8], offset: usize) -> bool {
236 data.len() >= offset + ELF_MAGIC.len() && data[offset..offset + ELF_MAGIC.len()] == ELF_MAGIC
237}
238
239fn verify_elf(bytes: &[u8], loader: &'static str, offset: usize) -> Result<(), Error> {
240 if bytes.len() < ELF_MAGIC.len() || bytes[..ELF_MAGIC.len()] != ELF_MAGIC {
241 return Err(Error::NotElf { loader, offset });
242 }
243 Ok(())
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 fn elf_bytes(extra: usize) -> Vec<u8> {
251 let mut v = Vec::with_capacity(ELF_MAGIC.len() + extra);
252 v.extend_from_slice(&ELF_MAGIC);
253 v.resize(ELF_MAGIC.len() + extra, 0);
254 v
255 }
256
257 #[test]
258 fn classify_recognises_the_three_loaders() {
259 assert_eq!(classify_loader(BPF_LOADER_V2), LoaderKind::BpfLoader2);
260 assert_eq!(
261 classify_loader(BPF_LOADER_UPGRADEABLE),
262 LoaderKind::Upgradeable
263 );
264 assert_eq!(classify_loader(LOADER_V4), LoaderKind::LoaderV4);
265 assert_eq!(
266 classify_loader("11111111111111111111111111111111"),
267 LoaderKind::Unknown
268 );
269 }
270
271 #[test]
272 fn strip_bpf_loader_v2_is_identity_with_elf_check() {
273 let bytes = elf_bytes(64);
274 assert_eq!(strip_bpf_loader_v2(&bytes).unwrap(), &bytes[..]);
275 }
276
277 #[test]
278 fn strip_bpf_loader_v2_rejects_non_elf() {
279 let mut bytes = elf_bytes(64);
280 bytes[0] = 0; assert!(matches!(
282 strip_bpf_loader_v2(&bytes),
283 Err(Error::NotElf { offset: 0, .. })
284 ));
285 }
286
287 #[test]
288 fn programdata_pubkey_extracts_the_32_byte_address() {
289 let mut buf = [0u8; 36];
290 buf[0..4].copy_from_slice(&UPGRADEABLE_STATE_PROGRAM.to_le_bytes());
291 for (i, b) in (0u8..32u8).enumerate() {
292 buf[4 + i] = b;
293 }
294 let pk = programdata_pubkey(&buf).unwrap();
295 let expected: [u8; 32] = std::array::from_fn(|i| u8::try_from(i).unwrap());
296 assert_eq!(pk, expected);
297 }
298
299 #[test]
300 fn programdata_pubkey_rejects_short_data() {
301 assert!(matches!(
302 programdata_pubkey(&[0u8; 20]),
303 Err(Error::TooShort { .. })
304 ));
305 }
306
307 #[test]
308 fn programdata_pubkey_rejects_wrong_tag() {
309 let buf = [0u8; 36]; assert!(matches!(
311 programdata_pubkey(&buf),
312 Err(Error::BadEnumTag { .. })
313 ));
314 }
315
316 #[test]
317 fn strip_upgradeable_with_authority_header() {
318 let mut buf = Vec::new();
319 buf.extend_from_slice(&UPGRADEABLE_STATE_PROGRAM_DATA.to_le_bytes()); buf.extend_from_slice(&[0u8; 8]); buf.push(1); buf.extend_from_slice(&[0u8; 32]); let payload = elf_bytes(32);
324 buf.extend_from_slice(&payload);
325 let stripped = strip_bpf_loader_upgradeable(&buf).unwrap();
326 assert_eq!(stripped, &payload[..]);
327 }
328
329 #[test]
330 fn strip_upgradeable_without_authority_header() {
331 let mut buf = Vec::new();
332 buf.extend_from_slice(&UPGRADEABLE_STATE_PROGRAM_DATA.to_le_bytes());
333 buf.extend_from_slice(&[0u8; 8]);
334 buf.push(0); let payload = elf_bytes(32);
336 buf.extend_from_slice(&payload);
337 let stripped = strip_bpf_loader_upgradeable(&buf).unwrap();
338 assert_eq!(stripped, &payload[..]);
339 }
340
341 #[test]
342 fn strip_upgradeable_fallback_when_option_tag_lies() {
343 let mut buf = Vec::new();
348 buf.extend_from_slice(&UPGRADEABLE_STATE_PROGRAM_DATA.to_le_bytes());
349 buf.extend_from_slice(&[0u8; 8]);
350 buf.push(1); let payload = elf_bytes(32);
352 buf.extend_from_slice(&payload); let stripped = strip_bpf_loader_upgradeable(&buf).unwrap();
354 assert_eq!(stripped, &payload[..]);
355 }
356
357 #[test]
358 fn strip_loader_v4_removes_48_byte_header() {
359 let mut buf = vec![0u8; LOADER_V4_HEADER];
360 let payload = elf_bytes(32);
361 buf.extend_from_slice(&payload);
362 let stripped = strip_loader_v4(&buf).unwrap();
363 assert_eq!(stripped, &payload[..]);
364 }
365
366 #[test]
367 fn strip_loader_v4_rejects_short_account() {
368 assert!(matches!(
369 strip_loader_v4(&[0u8; 20]),
370 Err(Error::TooShort { .. })
371 ));
372 }
373}