use crate::elf::ELF_MAGIC;
pub const BPF_LOADER_V2: &str = "BPFLoader2111111111111111111111111111111111";
pub const BPF_LOADER_UPGRADEABLE: &str = "BPFLoaderUpgradeab1e11111111111111111111111";
pub const LOADER_V4: &str = "LoaderV411111111111111111111111111111111111";
const UPGRADEABLE_STATE_PROGRAM: u32 = 2;
const UPGRADEABLE_STATE_PROGRAM_DATA: u32 = 3;
const PROGRAMDATA_HEADER_WITH_AUTH: usize = 45;
const PROGRAMDATA_HEADER_NO_AUTH: usize = 13;
const LOADER_V4_HEADER: usize = 48;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoaderKind {
BpfLoader2,
Upgradeable,
LoaderV4,
Unknown,
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("account data too short for {loader}: need ≥{need} bytes, got {got}")]
TooShort {
loader: &'static str,
need: usize,
got: usize,
},
#[error("ELF magic missing at expected offset {offset} for {loader}")]
NotElf { loader: &'static str, offset: usize },
#[error("expected {loader} Borsh enum tag {expected} at offset 0, got {got}")]
BadEnumTag {
loader: &'static str,
expected: u32,
got: u32,
},
#[error(
"ProgramData has neither {with_auth}-byte nor {no_auth}-byte header that exposes ELF magic"
)]
AmbiguousProgramData { with_auth: usize, no_auth: usize },
}
#[must_use]
pub fn classify_loader(owner: &str) -> LoaderKind {
match owner {
BPF_LOADER_V2 => LoaderKind::BpfLoader2,
BPF_LOADER_UPGRADEABLE => LoaderKind::Upgradeable,
LOADER_V4 => LoaderKind::LoaderV4,
_ => LoaderKind::Unknown,
}
}
pub fn programdata_pubkey(program_data: &[u8]) -> Result<[u8; 32], Error> {
const NEEDED: usize = 36;
if program_data.len() < NEEDED {
return Err(Error::TooShort {
loader: "BPFLoaderUpgradeable Program",
need: NEEDED,
got: program_data.len(),
});
}
let tag = u32::from_le_bytes([
program_data[0],
program_data[1],
program_data[2],
program_data[3],
]);
if tag != UPGRADEABLE_STATE_PROGRAM {
return Err(Error::BadEnumTag {
loader: "BPFLoaderUpgradeable Program",
expected: UPGRADEABLE_STATE_PROGRAM,
got: tag,
});
}
let mut pubkey = [0u8; 32];
pubkey.copy_from_slice(&program_data[4..36]);
Ok(pubkey)
}
pub fn strip_bpf_loader_v2(data: &[u8]) -> Result<&[u8], Error> {
verify_elf(data, "BPFLoader2", 0)?;
Ok(data)
}
pub fn strip_bpf_loader_upgradeable(programdata: &[u8]) -> Result<&[u8], Error> {
if programdata.len() < PROGRAMDATA_HEADER_NO_AUTH {
return Err(Error::TooShort {
loader: "BPFLoaderUpgradeable ProgramData",
need: PROGRAMDATA_HEADER_NO_AUTH,
got: programdata.len(),
});
}
let tag = u32::from_le_bytes([
programdata[0],
programdata[1],
programdata[2],
programdata[3],
]);
if tag != UPGRADEABLE_STATE_PROGRAM_DATA {
return Err(Error::BadEnumTag {
loader: "BPFLoaderUpgradeable ProgramData",
expected: UPGRADEABLE_STATE_PROGRAM_DATA,
got: tag,
});
}
let auth_present = programdata.get(12).copied().unwrap_or(0) != 0;
let primary = if auth_present {
PROGRAMDATA_HEADER_WITH_AUTH
} else {
PROGRAMDATA_HEADER_NO_AUTH
};
if has_elf_at(programdata, primary) {
return Ok(&programdata[primary..]);
}
let alt = if auth_present {
PROGRAMDATA_HEADER_NO_AUTH
} else {
PROGRAMDATA_HEADER_WITH_AUTH
};
if has_elf_at(programdata, alt) {
return Ok(&programdata[alt..]);
}
Err(Error::AmbiguousProgramData {
with_auth: PROGRAMDATA_HEADER_WITH_AUTH,
no_auth: PROGRAMDATA_HEADER_NO_AUTH,
})
}
pub fn strip_loader_v4(data: &[u8]) -> Result<&[u8], Error> {
if data.len() < LOADER_V4_HEADER + ELF_MAGIC.len() {
return Err(Error::TooShort {
loader: "LoaderV4",
need: LOADER_V4_HEADER + ELF_MAGIC.len(),
got: data.len(),
});
}
verify_elf(&data[LOADER_V4_HEADER..], "LoaderV4", LOADER_V4_HEADER)?;
Ok(&data[LOADER_V4_HEADER..])
}
fn has_elf_at(data: &[u8], offset: usize) -> bool {
data.len() >= offset + ELF_MAGIC.len() && data[offset..offset + ELF_MAGIC.len()] == ELF_MAGIC
}
fn verify_elf(bytes: &[u8], loader: &'static str, offset: usize) -> Result<(), Error> {
if bytes.len() < ELF_MAGIC.len() || bytes[..ELF_MAGIC.len()] != ELF_MAGIC {
return Err(Error::NotElf { loader, offset });
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn elf_bytes(extra: usize) -> Vec<u8> {
let mut v = Vec::with_capacity(ELF_MAGIC.len() + extra);
v.extend_from_slice(&ELF_MAGIC);
v.resize(ELF_MAGIC.len() + extra, 0);
v
}
#[test]
fn classify_recognises_the_three_loaders() {
assert_eq!(classify_loader(BPF_LOADER_V2), LoaderKind::BpfLoader2);
assert_eq!(
classify_loader(BPF_LOADER_UPGRADEABLE),
LoaderKind::Upgradeable
);
assert_eq!(classify_loader(LOADER_V4), LoaderKind::LoaderV4);
assert_eq!(
classify_loader("11111111111111111111111111111111"),
LoaderKind::Unknown
);
}
#[test]
fn strip_bpf_loader_v2_is_identity_with_elf_check() {
let bytes = elf_bytes(64);
assert_eq!(strip_bpf_loader_v2(&bytes).unwrap(), &bytes[..]);
}
#[test]
fn strip_bpf_loader_v2_rejects_non_elf() {
let mut bytes = elf_bytes(64);
bytes[0] = 0; assert!(matches!(
strip_bpf_loader_v2(&bytes),
Err(Error::NotElf { offset: 0, .. })
));
}
#[test]
fn programdata_pubkey_extracts_the_32_byte_address() {
let mut buf = [0u8; 36];
buf[0..4].copy_from_slice(&UPGRADEABLE_STATE_PROGRAM.to_le_bytes());
for (i, b) in (0u8..32u8).enumerate() {
buf[4 + i] = b;
}
let pk = programdata_pubkey(&buf).unwrap();
let expected: [u8; 32] = std::array::from_fn(|i| u8::try_from(i).unwrap());
assert_eq!(pk, expected);
}
#[test]
fn programdata_pubkey_rejects_short_data() {
assert!(matches!(
programdata_pubkey(&[0u8; 20]),
Err(Error::TooShort { .. })
));
}
#[test]
fn programdata_pubkey_rejects_wrong_tag() {
let buf = [0u8; 36]; assert!(matches!(
programdata_pubkey(&buf),
Err(Error::BadEnumTag { .. })
));
}
#[test]
fn strip_upgradeable_with_authority_header() {
let mut buf = Vec::new();
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);
buf.extend_from_slice(&payload);
let stripped = strip_bpf_loader_upgradeable(&buf).unwrap();
assert_eq!(stripped, &payload[..]);
}
#[test]
fn strip_upgradeable_without_authority_header() {
let mut buf = Vec::new();
buf.extend_from_slice(&UPGRADEABLE_STATE_PROGRAM_DATA.to_le_bytes());
buf.extend_from_slice(&[0u8; 8]);
buf.push(0); let payload = elf_bytes(32);
buf.extend_from_slice(&payload);
let stripped = strip_bpf_loader_upgradeable(&buf).unwrap();
assert_eq!(stripped, &payload[..]);
}
#[test]
fn strip_upgradeable_fallback_when_option_tag_lies() {
let mut buf = Vec::new();
buf.extend_from_slice(&UPGRADEABLE_STATE_PROGRAM_DATA.to_le_bytes());
buf.extend_from_slice(&[0u8; 8]);
buf.push(1); let payload = elf_bytes(32);
buf.extend_from_slice(&payload); let stripped = strip_bpf_loader_upgradeable(&buf).unwrap();
assert_eq!(stripped, &payload[..]);
}
#[test]
fn strip_loader_v4_removes_48_byte_header() {
let mut buf = vec![0u8; LOADER_V4_HEADER];
let payload = elf_bytes(32);
buf.extend_from_slice(&payload);
let stripped = strip_loader_v4(&buf).unwrap();
assert_eq!(stripped, &payload[..]);
}
#[test]
fn strip_loader_v4_rejects_short_account() {
assert!(matches!(
strip_loader_v4(&[0u8; 20]),
Err(Error::TooShort { .. })
));
}
}