use std::fs;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
pub fn has_self_extract_trailer(path: &Path) -> bool {
const TRAILER: &[u8] = b"\n---- Bun! ----\n";
let Ok(mut file) = fs::File::open(path) else {
return false;
};
let Ok(meta) = file.metadata() else {
return false;
};
if meta.len() < 24 {
return false;
}
let mut buf = [0u8; 24];
if file.seek(SeekFrom::End(-24)).is_err() {
return false;
}
if file.read_exact(&mut buf).is_err() {
return false;
}
if &buf[8..24] == TRAILER {
return true;
}
if &buf[0..16] == TRAILER {
return true;
}
false
}
fn read_pt_interp(binary: &Path) -> io::Result<String> {
let mut data = vec![0u8; 8192];
let mut file = fs::File::open(binary)?;
let n = file.read(&mut data)?;
data.truncate(n);
if data.len() < 64 || data[0..4] != *b"\x7fELF" {
return Err(io::Error::new(io::ErrorKind::InvalidData, "not an ELF"));
}
if data[4] != 2 {
return Err(io::Error::new(io::ErrorKind::Unsupported, "not 64-bit ELF"));
}
let e_phoff = u64::from_le_bytes(data[32..40].try_into().unwrap()) as usize;
let e_phentsize = u16::from_le_bytes(data[54..56].try_into().unwrap()) as usize;
let e_phnum = u16::from_le_bytes(data[56..58].try_into().unwrap()) as usize;
for i in 0..e_phnum {
let off = e_phoff + i * e_phentsize;
if off + e_phentsize > data.len() {
break;
}
let p_type = u32::from_le_bytes(data[off..off + 4].try_into().unwrap());
if p_type != 3 {
continue;
}
let p_offset = u64::from_le_bytes(data[off + 8..off + 16].try_into().unwrap()) as usize;
let p_filesz = u64::from_le_bytes(data[off + 32..off + 40].try_into().unwrap()) as usize;
if p_offset + p_filesz > data.len() {
file.seek(SeekFrom::Start(p_offset as u64))?;
let mut buf = vec![0u8; p_filesz];
file.read_exact(&mut buf)?;
let s = std::str::from_utf8(strip_nul(&buf))
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "PT_INTERP not UTF-8"))?;
return Ok(s.to_string());
}
let s = std::str::from_utf8(strip_nul(&data[p_offset..p_offset + p_filesz]))
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "PT_INTERP not UTF-8"))?;
return Ok(s.to_string());
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"no PT_INTERP entry",
))
}
fn strip_nul(buf: &[u8]) -> &[u8] {
match buf.iter().position(|&b| b == 0) {
Some(p) => &buf[..p],
None => buf,
}
}
pub fn bind_mount_interp(binary: &Path, bundled_linker: &Path) -> io::Result<PathBuf> {
let interp = read_pt_interp(binary)?;
let interp_path = PathBuf::from(&interp);
if !interp_path.is_absolute() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("PT_INTERP is not absolute: {interp}"),
));
}
let parent = interp_path.parent().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("PT_INTERP has no parent: {interp}"),
)
})?;
if !parent.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"PT_INTERP parent dir doesn't exist on host: {}",
parent.display()
),
));
}
rustix::mount::mount_bind(bundled_linker, &interp_path).map_err(|e| {
io::Error::other(format!(
"bind-mount {} -> {} failed: {e}",
bundled_linker.display(),
interp_path.display()
))
})?;
Ok(interp_path)
}
pub fn symlink_interp(binary: &Path, bundled_linker: &Path) -> io::Result<PathBuf> {
let canonical_linker = bundled_linker.canonicalize()?;
let hash = simple_hash(canonical_linker.to_string_lossy().as_bytes());
let link_path = PathBuf::from(format!("/tmp/onelf-ld-{hash:08x}.so"));
let needs_create = match fs::read_link(&link_path) {
Ok(existing) => existing != canonical_linker,
Err(_) => true,
};
if needs_create {
let tmp = PathBuf::from(format!("/tmp/onelf-ld-{hash:08x}.tmp"));
let _ = fs::remove_file(&tmp);
std::os::unix::fs::symlink(&canonical_linker, &tmp)?;
fs::rename(&tmp, &link_path)?;
}
let link_str = link_path.to_string_lossy();
patch_pt_interp_in_place(binary, &link_str)?;
Ok(link_path)
}
fn patch_pt_interp_in_place(binary: &Path, new_interp: &str) -> io::Result<()> {
let mut data = fs::read(binary)?;
if data.len() < 64 || data[0..4] != *b"\x7fELF" {
return Err(io::Error::new(io::ErrorKind::InvalidData, "not an ELF"));
}
if data[4] != 2 {
return Err(io::Error::new(io::ErrorKind::Unsupported, "not 64-bit ELF"));
}
let e_phoff = u64::from_le_bytes(data[32..40].try_into().unwrap()) as usize;
let e_phentsize = u16::from_le_bytes(data[54..56].try_into().unwrap()) as usize;
let e_phnum = u16::from_le_bytes(data[56..58].try_into().unwrap()) as usize;
let new_bytes = new_interp.as_bytes();
for i in 0..e_phnum {
let off = e_phoff + i * e_phentsize;
if off + e_phentsize > data.len() {
break;
}
let p_type = u32::from_le_bytes(data[off..off + 4].try_into().unwrap());
if p_type != 3 {
continue;
}
let p_offset = u64::from_le_bytes(data[off + 8..off + 16].try_into().unwrap()) as usize;
let p_filesz = u64::from_le_bytes(data[off + 32..off + 40].try_into().unwrap()) as usize;
if new_bytes.len() + 1 > p_filesz {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"new PT_INTERP ({} bytes) doesn't fit in slot ({} bytes)",
new_bytes.len() + 1,
p_filesz
),
));
}
if p_offset + p_filesz > data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"PT_INTERP slot extends past file",
));
}
let current_end = (p_offset..p_offset + p_filesz)
.find(|&i| data[i] == 0)
.unwrap_or(p_offset + p_filesz);
if &data[p_offset..current_end] == new_bytes {
return Ok(());
}
data[p_offset..p_offset + new_bytes.len()].copy_from_slice(new_bytes);
for j in (p_offset + new_bytes.len())..(p_offset + p_filesz) {
data[j] = 0;
}
let tmp = binary.with_extension("interp-patch");
fs::write(&tmp, &data)?;
let _ = fs::set_permissions(
&tmp,
<fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o755),
);
fs::rename(&tmp, binary)?;
return Ok(());
}
Err(io::Error::new(
io::ErrorKind::NotFound,
"no PT_INTERP entry",
))
}
fn simple_hash(data: &[u8]) -> u32 {
let mut h: u32 = 0x811c9dc5;
for &b in data {
h ^= b as u32;
h = h.wrapping_mul(0x01000193);
}
h
}