waterui_cli/workflows/
elf.rs1use std::path::Path;
11
12use eyre::bail;
13
14pub const REQUIRED_LOAD_ALIGNMENT: u64 = 0x4000;
17
18pub fn require_aligned_load_segments(path: &Path) -> eyre::Result<()> {
28 let data = std::fs::read(path).map_err(|error| {
29 eyre::eyre!(
30 "failed to read staged library {} for ELF alignment validation: {error}",
31 path.display()
32 )
33 })?;
34 check_load_segment_alignment(&data)
35 .map_err(|error| eyre::eyre!("staged library {}: {error}", path.display()))
36}
37
38pub async fn require_aligned_shared_libraries(directory: &Path) -> eyre::Result<()> {
45 let directory = directory.to_path_buf();
46 smol::unblock(move || {
47 for entry in std::fs::read_dir(&directory)? {
48 let path = entry?.path();
49 if path.extension() != Some(std::ffi::OsStr::new("so")) {
50 continue;
51 }
52 require_aligned_load_segments(&path)?;
53 }
54 Ok(())
55 })
56 .await
57}
58
59fn check_load_segment_alignment(data: &[u8]) -> eyre::Result<()> {
63 use object::read::elf::{ElfFile, FileHeader, ProgramHeader as _};
64
65 fn scan<Elf>(data: &[u8]) -> eyre::Result<()>
66 where
67 Elf: FileHeader<Endian = object::Endianness>,
68 {
69 let file = ElfFile::<Elf>::parse(data)
70 .map_err(|error| eyre::eyre!("not a parseable ELF file: {error}"))?;
71 let endian = file.endian();
72 let headers = file
73 .elf_header()
74 .program_headers(endian, data)
75 .map_err(|error| eyre::eyre!("cannot read ELF program headers: {error}"))?;
76 for header in headers {
77 if header.p_type(endian) != object::elf::PT_LOAD {
78 continue;
79 }
80 let align: u64 = header.p_align(endian).into();
81 if align < REQUIRED_LOAD_ALIGNMENT {
82 let offset: u64 = header.p_offset(endian).into();
83 bail!(
84 "PT_LOAD segment (offset {offset:#x}) is aligned to {align:#x}; \
85 Android 16 KB page-size support requires at least {REQUIRED_LOAD_ALIGNMENT:#x}",
86 );
87 }
88 }
89 Ok(())
90 }
91
92 match data.get(4) {
93 Some(&object::elf::ELFCLASS64) => {
94 scan::<object::elf::FileHeader64<object::Endianness>>(data)
95 }
96 Some(&object::elf::ELFCLASS32) => {
97 scan::<object::elf::FileHeader32<object::Endianness>>(data)
98 }
99 _ => bail!("not a parseable ELF file: missing or invalid ELF class byte"),
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::{
106 REQUIRED_LOAD_ALIGNMENT, check_load_segment_alignment, require_aligned_load_segments,
107 };
108
109 fn elf_with_load_align(align: u64) -> Vec<u8> {
112 let mut data = vec![0u8; 0x100];
113 data[..4].copy_from_slice(b"\x7fELF");
114 data[4] = 2; data[5] = 1; data[6] = 1; data[16..18].copy_from_slice(&3u16.to_le_bytes()); data[18..20].copy_from_slice(&0xb7u16.to_le_bytes()); data[20..24].copy_from_slice(&1u32.to_le_bytes()); data[32..40].copy_from_slice(&64u64.to_le_bytes()); data[52..54].copy_from_slice(&64u16.to_le_bytes()); data[54..56].copy_from_slice(&56u16.to_le_bytes()); data[56..58].copy_from_slice(&1u16.to_le_bytes()); data[64..68].copy_from_slice(&1u32.to_le_bytes()); data[68..72].copy_from_slice(&5u32.to_le_bytes()); data[72..80].copy_from_slice(&0u64.to_le_bytes()); data[112..120].copy_from_slice(&align.to_le_bytes()); data
130 }
131
132 #[test]
133 fn accepts_a_16k_aligned_load_segment() {
134 check_load_segment_alignment(&elf_with_load_align(REQUIRED_LOAD_ALIGNMENT))
135 .expect("16 KB-aligned LOAD must pass");
136 check_load_segment_alignment(&elf_with_load_align(0x10000))
137 .expect("larger alignment must pass");
138 }
139
140 #[test]
141 fn rejects_a_4k_aligned_load_segment() {
142 let error = check_load_segment_alignment(&elf_with_load_align(0x1000))
143 .expect_err("4 KB-aligned LOAD must fail");
144 let message = error.to_string();
145 assert!(
146 message.contains("0x1000"),
147 "message names the alignment: {message}"
148 );
149 assert!(
150 message.contains("0x4000"),
151 "message names the requirement: {message}"
152 );
153 }
154
155 #[test]
156 fn rejects_non_elf_data() {
157 let _ = check_load_segment_alignment(b"not an elf").expect_err("non-ELF must fail");
158 }
159
160 #[test]
161 fn require_aligned_load_segments_reports_the_file() {
162 let dir = tempfile::tempdir().expect("tempdir");
163 let path = dir.path().join("libbad.so");
164 std::fs::write(&path, elf_with_load_align(0x1000)).expect("write");
165 let error = require_aligned_load_segments(&path).expect_err("must fail");
166 assert!(error.to_string().contains("libbad.so"));
167 }
168}