use crate::error::{Error, ErrorKind};
pub const REQUIRED_ELF_MACHINE: u16 = 0xB7;
pub fn elf_machine(bytes: &[u8]) -> Option<u16> {
let header = bytes.get(..20)?;
if &header[..4] != b"\x7fELF" {
return None;
}
let field = [header[18], header[19]];
Some(if header[5] == 1 {
u16::from_le_bytes(field)
} else {
u16::from_be_bytes(field)
})
}
pub fn not_aarch64(bytes: &[u8]) -> Option<String> {
match elf_machine(bytes) {
Some(REQUIRED_ELF_MACHINE) => None,
Some(machine) => Some(format!(
"ELF machine 0x{machine:x}, not aarch64 (0x{REQUIRED_ELF_MACHINE:x})"
)),
None => Some("not an ELF binary at all".to_string()),
}
}
pub fn require_aarch64(bytes: &[u8]) -> Result<(), Error> {
match not_aarch64(bytes) {
None => Ok(()),
Some(why) => Err(Error::new(
ErrorKind::Precondition,
format!(
"the daemon binary is {why}. MicroVMs are ARM64-only, and a wrong-architecture \
daemon fails as a run-hook timeout 45 minutes into a build.\n - build one: \
cargo build --release -p agentd --target aarch64-unknown-linux-musl"
),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn elf_header(machine: u16) -> Vec<u8> {
let mut header = vec![0u8; 20];
header[..4].copy_from_slice(b"\x7fELF");
header[5] = 1;
header[18..20].copy_from_slice(&machine.to_le_bytes());
header
}
#[test]
fn the_elf_header_is_read_in_its_own_byte_order() {
assert_eq!(elf_machine(&elf_header(0xB7)), Some(0xB7));
let mut big = elf_header(0);
big[5] = 2;
big[18..20].copy_from_slice(&0xB7u16.to_be_bytes());
assert_eq!(elf_machine(&big), Some(0xB7));
assert_eq!(elf_machine(b"#!/bin/sh"), None);
assert_eq!(elf_machine(&elf_header(0xB7)[..19]), None);
assert_eq!(not_aarch64(&elf_header(0xB7)), None);
}
#[test]
fn a_non_aarch64_binary_is_refused_with_its_machine() {
assert!(not_aarch64(&elf_header(0x3E)).is_some_and(|why| why.contains("0x3e")));
assert!(not_aarch64(b"#!/bin/sh").is_some_and(|why| why.contains("not an ELF")));
}
#[test]
fn an_images_daemon_must_be_an_aarch64_elf() {
assert!(require_aarch64(&elf_header(0xB7)).is_ok());
for (bytes, why) in [
(elf_header(0x3E), "ELF machine 0x3e, not aarch64 (0xb7)"),
(b"#!/bin/sh".to_vec(), "not an ELF binary at all"),
(Vec::new(), "not an ELF binary at all"),
] {
let error = require_aarch64(&bytes).expect_err(why);
assert_eq!(error.kind(), ErrorKind::Precondition, "{error}");
let message = error.to_string();
assert!(message.contains(why), "{message}");
assert!(message.contains("ARM64-only"), "{message}");
assert!(
message.contains("--target aarch64-unknown-linux-musl"),
"{message}"
);
}
}
}