1use crate::{
4 elf,
5 glibc::{Kind, Snapshot},
6 version::Version,
7};
8use std::{
9 collections::{BTreeMap, BTreeSet},
10 fmt::Write as _,
11};
12
13#[derive(Debug, Eq, PartialEq)]
14pub struct Sources {
15 pub assembly: String,
16 pub versions: String,
17 pub selected: elf::Metadata,
18}
19
20pub fn render_glibc(
24 snapshot: &Snapshot,
25 library: &str,
26 reference: &elf::Metadata,
27) -> Result<Sources, String> {
28 snapshot.release.validate_release()?;
29 let soname = match library {
30 "c" => "libc.so.6",
31 "m" => "libm.so.6",
32 "ld" => "ld-linux-x86-64.so.2",
33 "resolv" => "libresolv.so.2",
34 "pthread" => "libpthread.so.0",
35 "dl" => "libdl.so.2",
36 "rt" => "librt.so.1",
37 "util" => "libutil.so.1",
38 _ => return Err("unsupported glibc library identity".into()),
39 };
40 if reference.soname != soname {
41 return Err(format!("expected SONAME {soname}"));
42 }
43 let expected = snapshot
44 .libraries
45 .get(library)
46 .ok_or("missing library ABI metadata")?;
47 let public = elf::Metadata {
48 soname: reference.soname.clone(),
49 symbols: reference
50 .symbols
51 .iter()
52 .filter(|s| {
53 matches!(s.visibility, 0 | 3)
54 && s.version
55 .as_deref()
56 .and_then(|v| v.strip_prefix("GLIBC_"))
57 .is_some_and(|v| v.parse::<Version>().is_ok())
58 })
59 .cloned()
60 .collect(),
61 };
62 if public.symbols.len() != expected.len() {
63 return Err("ELF/snapshot public version membership differs".into());
64 }
65 let mut actual = BTreeMap::new();
66 for s in &public.symbols {
67 if actual
68 .insert((s.name.as_str(), s.version.as_deref()), s)
69 .is_some()
70 {
71 return Err("duplicate ELF public definition".into());
72 }
73 }
74 let mut expected_keys = BTreeSet::new();
75 for entry in expected {
76 if !expected_keys.insert((entry.name.as_str(), entry.version)) {
77 return Err("duplicate ABI definition".into());
78 }
79 let version = format!("GLIBC_{}", entry.version);
80 let s = actual
81 .get(&(entry.name.as_str(), Some(version.as_str())))
82 .ok_or_else(|| format!("{}@{version}: missing ELF definition", entry.name))?;
83 let matches = match entry.kind {
84 Kind::Function => matches!(s.symbol_type, 2 | 10),
85 Kind::Object(size) => s.symbol_type == 1 && s.size == u64::from(size),
86 Kind::Tls(size) => s.symbol_type == 6 && s.size == u64::from(size),
87 };
88 if !matches {
89 return Err(format!(
90 "{}@{version}: ELF/snapshot kind or size differs",
91 entry.name
92 ));
93 }
94 }
95 let names: BTreeSet<_> = expected.iter().map(|s| s.name.as_str()).collect();
96 render(&public, &names.into_iter().collect::<Vec<_>>())
97}
98
99fn identifier(name: &str) -> bool {
100 let mut bytes = name.bytes();
101 bytes
102 .next()
103 .is_some_and(|b| b.is_ascii_alphabetic() || b == b'_')
104 && bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_')
105 && !name.starts_with("__abi_stub_")
106 && name.len() <= 1024
107}
108
109struct DataRange {
110 start: u64,
111 end: u64,
112 alignment: u64,
113 section: u16,
114 kind: u8,
115}
116
117fn data_layout(
118 symbols: &[elf::Symbol],
119) -> Result<(Vec<DataRange>, BTreeMap<usize, usize>), String> {
120 let mut by_section = BTreeMap::<(u16, u8), Vec<usize>>::new();
121 for (index, s) in symbols
122 .iter()
123 .enumerate()
124 .filter(|(_, s)| matches!(s.symbol_type, 1 | 6))
125 {
126 let alignment = s.section_alignment.max(1);
127 if alignment > 4096 || !alignment.is_power_of_two() {
128 return Err("unsupported data/TLS section alignment".into());
129 }
130 if s.size == 0 || s.size > 16 * 1024 * 1024 {
131 return Err("unsupported zero or oversized data/TLS symbol".into());
132 }
133 s.value
134 .checked_add(s.size)
135 .ok_or("ELF data range overflow")?;
136 by_section
137 .entry((s.section, s.symbol_type))
138 .or_default()
139 .push(index);
140 }
141 let mut groups: Vec<DataRange> = Vec::new();
142 let mut membership = BTreeMap::new();
143 for ((section, kind), mut indexes) in by_section {
144 indexes.sort_by_key(|&index| (symbols[index].value, symbols[index].size));
145 let mut active: Option<usize> = None;
146 for index in indexes {
147 let s = &symbols[index];
148 let end = s.value + s.size; let group = match active {
150 Some(group) if s.value < groups[group].end => {
151 groups[group].end = groups[group].end.max(end);
152 groups[group].alignment =
153 groups[group].alignment.max(s.section_alignment.max(1));
154 group
155 }
156 _ => {
157 let group = groups.len();
158 groups.push(DataRange {
159 start: s.value,
160 end,
161 alignment: s.section_alignment.max(1),
162 section,
163 kind,
164 });
165 active = Some(group);
166 group
167 }
168 };
169 if groups[group].end - groups[group].start > 16 * 1024 * 1024 {
170 return Err("data/TLS backing range exceeds 16 MiB".into());
171 }
172 membership.insert(index, group);
173 }
174 }
175 Ok((groups, membership))
176}
177
178pub fn render(reference: &elf::Metadata, names: &[&str]) -> Result<Sources, String> {
183 if names.is_empty() || names.len() > 10000 || reference.symbols.len() > 100000 {
184 return Err("stub requires 1..10000 names and at most 100000 ELF definitions".into());
185 }
186 let requested: BTreeSet<_> = names.iter().copied().collect();
187 if requested.len() != names.len() || requested.iter().any(|name| !identifier(name)) {
188 return Err("stub names must be unique C identifiers outside the reserved prefix".into());
189 }
190 let mut selected = elf::Metadata {
191 soname: reference.soname.clone(),
192 symbols: reference
193 .symbols
194 .iter()
195 .filter(|symbol| requested.contains(symbol.name.as_str()))
196 .cloned()
197 .collect(),
198 };
199 selected.symbols.sort();
200 let observed: BTreeSet<_> = selected.symbols.iter().map(|s| s.name.as_str()).collect();
201 if observed != requested {
202 return Err("requested stub symbol is absent from ELF evidence".into());
203 }
204 let mut keys = BTreeSet::new();
205 let mut defaults = BTreeSet::new();
206 let mut nodes = BTreeMap::<Version, BTreeSet<String>>::new();
207 let mut assembly = String::from("# Experimental link-only library; function bodies trap.\n");
208 let mut storage = BTreeMap::new();
209 let mut ranges = BTreeMap::<u16, BTreeMap<u64, u64>>::new();
210 let mut allocated = 0u64;
211 let (data_ranges, data_membership) = data_layout(&selected.symbols)?;
212 let mut emitted_data = BTreeSet::new();
213 for (index, symbol) in selected.symbols.iter().enumerate() {
214 if !matches!(symbol.binding, 1 | 2)
215 || !matches!(symbol.visibility, 0 | 3)
216 || !matches!(symbol.symbol_type, 1 | 2 | 6 | 10)
217 || symbol.section == 0
218 || symbol.section >= 0xff00
219 {
220 return Err(format!(
221 "{}: unsupported binding, visibility, type, or section",
222 symbol.name
223 ));
224 }
225 let alignment = symbol.section_alignment.max(1);
226 if alignment > 4096 || !alignment.is_power_of_two() {
227 return Err(format!("{}: unsupported alignment", symbol.name));
228 }
229 let version = symbol
230 .version
231 .as_deref()
232 .ok_or("unversioned stub definitions unsupported")?;
233 let numeric: Version = version
234 .strip_prefix("GLIBC_")
235 .ok_or("stub requires numeric GLIBC versions")?
236 .parse()?;
237 if format!("GLIBC_{numeric}") != version {
239 return Err("noncanonical GLIBC version".into());
240 }
241 let default = symbol
242 .default_version
243 .ok_or("missing default-version evidence")?;
244 if !keys.insert((&symbol.name, version)) || (default && !defaults.insert(&symbol.name)) {
245 return Err("duplicate definition or multiple default stub versions".into());
246 }
247 nodes
248 .entry(numeric)
249 .or_default()
250 .insert(symbol.name.clone());
251 let key = (
252 symbol.section,
253 symbol.value,
254 symbol.symbol_type,
255 symbol.size,
256 );
257 let emitted_size = if matches!(symbol.symbol_type, 2 | 10) {
258 2
259 } else {
260 symbol.size
261 };
262 if emitted_size == 0 || emitted_size > 16 * 1024 * 1024 {
263 return Err(format!(
264 "{}: unsupported zero or oversized storage",
265 symbol.name
266 ));
267 }
268 let old_storage = if symbol.size == 0 {
269 None
270 } else {
271 storage.get(&key).copied()
272 };
273 let location = if let Some(&group) = data_membership.get(&index) {
274 let range = &data_ranges[group];
275 if emitted_data.insert(group) {
276 let bytes = range.end - range.start;
277 let location = ranges.entry(range.section).or_default();
278 let overlaps_before = location
279 .range(..=range.start)
280 .next_back()
281 .is_some_and(|(_, &end)| end > range.start);
282 let overlaps_after = location
283 .range(range.start..)
284 .next()
285 .is_some_and(|(&start, _)| start < range.end);
286 if overlaps_before || overlaps_after {
287 return Err("incompatible overlapping storage types".into());
288 }
289 location.insert(range.start, range.end);
290 allocated = allocated
291 .checked_add(bytes + range.alignment - 1)
292 .ok_or("stub allocation overflow")?;
293 if allocated > 64 * 1024 * 1024 {
294 return Err("stub storage budget exceeds 64 MiB".into());
295 }
296 let section = if range.kind == 6 {
297 ".section .tbss,\"awT\",@nobits"
298 } else {
299 ".section .bss,\"aw\",@nobits"
300 };
301 writeln!(
302 assembly,
303 "{section}\n.balign {}\n.Ldata{group}:\n .zero {bytes}",
304 range.alignment
305 )
306 .expect("String write");
307 }
308 format!(".Ldata{group}+{}", symbol.value - range.start)
309 } else {
310 let slot = if let Some(slot) = old_storage {
311 slot
312 } else {
313 let end = symbol
315 .value
316 .checked_add(symbol.size)
317 .ok_or("ELF symbol range overflow")?;
318 let location = ranges.entry(symbol.section).or_default();
319 let overlaps_before = location
320 .range(..=symbol.value)
321 .next_back()
322 .is_some_and(|(_, &old_end)| old_end > symbol.value);
323 let overlaps_after = location
324 .range(symbol.value..)
325 .next()
326 .is_some_and(|(&start, _)| start < end);
327 if symbol.size != 0 && (overlaps_before || overlaps_after) {
328 return Err(format!(
329 "{}@{version}: partially overlapping symbol ranges unsupported at {}:{:#x}+{}",
330 symbol.name, symbol.section, symbol.value, symbol.size
331 ));
332 }
333 if symbol.size != 0 {
334 location.insert(symbol.value, end);
335 }
336 allocated = allocated
337 .checked_add(emitted_size + alignment - 1)
338 .ok_or("stub allocation overflow")?;
339 if allocated > 64 * 1024 * 1024 {
340 return Err("stub storage budget exceeds 64 MiB".into());
341 }
342 let slot = index;
343 if symbol.size != 0 {
344 storage.insert(key, slot);
345 }
346 let section = match symbol.symbol_type {
347 1 => ".section .bss,\"aw\",@nobits",
348 6 => ".section .tbss,\"awT\",@nobits",
349 _ => ".text",
350 };
351 writeln!(assembly, "{section}\n.balign {alignment}\n.Lstorage{slot}:")
352 .expect("String write");
353 if matches!(symbol.symbol_type, 2 | 10) {
354 assembly.push_str(" ud2\n");
355 } else {
356 writeln!(assembly, " .zero {emitted_size}").expect("String write");
357 }
358 slot
359 };
360 format!(".Lstorage{slot}")
361 };
362 let private = format!("__abi_stub_{index}");
363 let binding = if symbol.binding == 2 {
364 ".weak"
365 } else {
366 ".globl"
367 };
368 let kind = match symbol.symbol_type {
369 1 => "object",
370 6 => "tls_object",
371 10 => "gnu_indirect_function",
372 _ => "function",
373 };
374 writeln!(assembly, "{binding} {private}\n.set {private},{location}\n.type {private},@{kind}\n.size {private},{emitted_size}").expect("String write");
375 if symbol.visibility == 3 {
376 writeln!(assembly, ".protected {private}").expect("String write");
377 }
378 let separator = if default { "@@" } else { "@" };
379 writeln!(
380 assembly,
381 ".symver {private},{}{separator}{version}",
382 symbol.name
383 )
384 .expect("String write");
385 }
386 assembly.push_str(".section .note.GNU-stack,\"\",@progbits\n");
387 let mut versions = String::new();
388 for (index, (version, names)) in nodes.iter().enumerate() {
389 writeln!(versions, "GLIBC_{version} {{\n global:").expect("String write");
390 for name in names {
391 writeln!(versions, " {name};").expect("String write");
392 }
393 if index == 0 {
394 versions.push_str(" local: *;\n");
395 }
396 versions.push_str("};\n");
397 }
398 Ok(Sources {
399 assembly,
400 versions,
401 selected,
402 })
403}