1use serde::{Deserialize, Deserializer, Serialize};
4
5use crate::strict_rtti::{
6 StrictPointerObservation, StrictRttiConservation, StrictRttiGap, StrictRttiLimits,
7 StrictRttiObservation, StrictRttiObservationKind, StrictRttiOutcome,
8};
9use crate::{Error, Result};
10
11#[path = "strict_vtable_decoder.rs"]
12mod decoder;
13pub use decoder::{decode_strict_vtables, decode_strict_vtables_from_source};
14
15const HARD_RECORDS: u64 = 4_000_000;
16const HARD_WORDS: u64 = 64_000_000;
17const HARD_EVIDENCE_BYTES: u64 = 1 << 31;
18const HARD_INPUT_BYTES: u64 = 1 << 34;
19const HARD_SYMBOLS: u64 = 16_000_000;
20
21#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
23#[serde(deny_unknown_fields)]
24pub struct StrictVtableLimits {
25 pub max_input_bytes: u64,
27 pub max_symbols: u64,
29 pub max_records: u64,
31 pub max_words: u64,
33 pub max_evidence_bytes: u64,
35}
36
37impl Default for StrictVtableLimits {
38 fn default() -> Self {
39 Self {
40 max_input_bytes: 1 << 30,
41 max_symbols: 4_000_000,
42 max_records: 1_000_000,
43 max_words: 8_000_000,
44 max_evidence_bytes: 1 << 29,
45 }
46 }
47}
48
49impl StrictVtableLimits {
50 pub fn validate(self) -> Result<()> {
52 for (value, hard, name) in [
53 (self.max_input_bytes, HARD_INPUT_BYTES, "vtable input-byte"),
54 (self.max_symbols, HARD_SYMBOLS, "vtable symbol"),
55 (self.max_records, HARD_RECORDS, "vtable record"),
56 (self.max_words, HARD_WORDS, "vtable word"),
57 (
58 self.max_evidence_bytes,
59 HARD_EVIDENCE_BYTES,
60 "vtable evidence-byte",
61 ),
62 ] {
63 if value == 0 || value > hard {
64 return Err(Error::format(format!(
65 "{name} limit is zero or exceeds its hard maximum"
66 )));
67 }
68 }
69 Ok(())
70 }
71
72 pub(crate) fn reader_limits(self) -> StrictRttiLimits {
73 StrictRttiLimits {
74 max_input_bytes: self.max_input_bytes,
75 max_symbols: self.max_symbols,
76 max_records: self.max_records,
77 max_evidence_bytes: self.max_evidence_bytes,
78 ..StrictRttiLimits::default()
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
85#[serde(rename_all = "snake_case")]
86pub enum ItaniumVtableSymbolKind {
87 CompleteGroup,
89 ConstructionGroup,
91 Vtt,
93}
94
95#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
97#[serde(rename_all = "snake_case")]
98pub enum ItaniumVtableExtentSource {
99 NextDefinedSymbol,
101 SectionEnd,
103}
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case")]
108pub enum ItaniumVtableOffsetRole {
109 VcallOrVbase,
111}
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
115#[serde(rename_all = "snake_case")]
116pub enum ItaniumVtableAddressPointSource {
117 TypeinfoSymbol,
119 NullTypeinfoAtSymbolStart,
121}
122
123#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
125#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
126pub enum ItaniumThunkAdjustment {
127 NonVirtual {
129 offset: i64,
131 },
132 Virtual {
134 offset: i64,
136 virtual_offset: i64,
138 },
139}
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum ItaniumVtableSlotRole {
145 Function,
147 PureVirtual,
149 DeletedVirtual,
151 DeletingDestructor,
153 CompleteDestructor,
155 BaseDestructor,
157 NonVirtualThunk,
159 VirtualThunk,
161 CovariantThunk,
163 Null,
165 Unknown,
167}
168
169#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
171#[serde(deny_unknown_fields)]
172pub struct ItaniumVtableOffsetRecord {
173 pub word_ordinal: u64,
175 pub observation_ordinal: u64,
177 pub raw_value: u64,
179 pub signed_value: i64,
181 pub role: ItaniumVtableOffsetRole,
183}
184
185#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
187#[serde(deny_unknown_fields)]
188pub struct ItaniumVtableSlotRecord {
189 pub ordinal: u64,
191 pub word_ordinal: u64,
193 pub pointer: StrictPointerObservation,
195 pub target_symbol: Option<String>,
197 pub role: ItaniumVtableSlotRole,
199 pub this_adjustment: Option<ItaniumThunkAdjustment>,
201 pub return_adjustment: Option<ItaniumThunkAdjustment>,
203}
204
205#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
207#[serde(deny_unknown_fields)]
208pub struct ItaniumVtableAddressPointRecord {
209 pub ordinal: u64,
211 pub va: u64,
213 pub source: ItaniumVtableAddressPointSource,
215 pub offset_to_top_word: u64,
217 pub prefix_offsets: Vec<ItaniumVtableOffsetRecord>,
219 pub offset_to_top_observation_ordinal: u64,
221 pub offset_to_top: i64,
223 pub typeinfo: StrictPointerObservation,
225 pub slots: Vec<ItaniumVtableSlotRecord>,
227}
228
229#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
231#[serde(deny_unknown_fields)]
232pub struct ItaniumVtableAmbiguousWordRecord {
233 pub word_ordinal: u64,
235 pub observation_ordinal: u64,
237 pub raw_value: u64,
239}
240
241#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
243#[serde(deny_unknown_fields)]
244pub struct ItaniumVtableGroupRecord {
245 pub symbol: String,
247 pub kind: ItaniumVtableSymbolKind,
249 pub va: u64,
251 pub file_offset: u64,
253 pub byte_length: u64,
255 pub extent_source: ItaniumVtableExtentSource,
257 pub pointer_width: u8,
259 pub address_points: Vec<ItaniumVtableAddressPointRecord>,
261 pub ambiguous_words: Vec<ItaniumVtableAmbiguousWordRecord>,
263 pub weak_definition: bool,
265}
266
267#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
269#[serde(deny_unknown_fields)]
270pub struct ItaniumVttEntryRecord {
271 pub ordinal: u64,
273 pub address_point: StrictPointerObservation,
275}
276
277#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
279#[serde(deny_unknown_fields)]
280pub struct ItaniumVttRecord {
281 pub symbol: String,
283 pub va: u64,
285 pub file_offset: u64,
287 pub byte_length: u64,
289 pub extent_source: ItaniumVtableExtentSource,
291 pub entries: Vec<ItaniumVttEntryRecord>,
293 pub weak_definition: bool,
295}
296
297#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
299#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
300pub enum StrictVtableRecord {
301 Group {
303 record: Box<ItaniumVtableGroupRecord>,
305 },
306 Vtt {
308 record: Box<ItaniumVttRecord>,
310 },
311 External {
313 symbol_kind: ItaniumVtableSymbolKind,
315 symbol: String,
317 library_ordinal: i32,
319 weak: bool,
321 },
322}
323
324#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
326#[serde(deny_unknown_fields)]
327pub struct StrictVtableBatch {
328 pub outcome: StrictRttiOutcome,
330 pub records: Vec<StrictVtableRecord>,
332 pub observations: Vec<StrictRttiObservation>,
334 pub gaps: Vec<StrictRttiGap>,
336 pub conservation: StrictRttiConservation,
338}
339
340impl StrictVtableBatch {
341 pub(crate) fn validate(&self, limits: StrictVtableLimits) -> Result<()> {
342 limits.validate()?;
343 self.conservation.validate()?;
344 if self.observations.iter().enumerate().any(|(index, value)| {
345 value.ordinal != u64::try_from(index).unwrap_or(u64::MAX) || value.length == 0
346 }) {
347 return Err(Error::format(
348 "strict vtable observations are not canonical",
349 ));
350 }
351 let evidence = self.observations.iter().try_fold(0_u64, |total, value| {
352 total
353 .checked_add(value.length)
354 .ok_or_else(|| Error::format("strict vtable evidence overflows"))
355 })?;
356 if evidence > limits.max_evidence_bytes
357 || u64::try_from(self.records.len()).ok() != Some(self.conservation.included)
358 {
359 return Err(Error::format(
360 "strict vtable counts exceed limits or disagree",
361 ));
362 }
363 let expected_gaps = self
364 .conservation
365 .unknown
366 .checked_add(u64::from(self.conservation.excluded > 0))
367 .and_then(|value| {
368 value.checked_add(u64::from(
369 self.outcome == StrictRttiOutcome::Rejected && self.conservation.attempted == 0,
370 ))
371 })
372 .ok_or_else(|| Error::format("strict vtable gap count overflows"))?;
373 if u64::try_from(self.gaps.len()).ok() != Some(expected_gaps) {
374 return Err(Error::format(
375 "strict vtable gaps do not reconstruct conservation",
376 ));
377 }
378 let mut prior: Option<(String, u64)> = None;
379 let mut words = 0_u64;
380 for record in &self.records {
381 let (symbol, va, record_words) = match record {
382 StrictVtableRecord::Group { record } => {
383 self.validate_group(record)?;
384 (
385 &record.symbol,
386 record.va,
387 record.byte_length / u64::from(record.pointer_width),
388 )
389 }
390 StrictVtableRecord::Vtt { record } => {
391 self.validate_vtt(record)?;
392 (
393 &record.symbol,
394 record.va,
395 u64::try_from(record.entries.len()).unwrap_or(u64::MAX),
396 )
397 }
398 StrictVtableRecord::External {
399 symbol_kind,
400 symbol,
401 ..
402 } => {
403 if decoder::classify_symbol(symbol) != Some(*symbol_kind) {
404 return Err(Error::format("strict external vtable symbol is invalid"));
405 }
406 (symbol, 0, 0)
407 }
408 };
409 let key = (symbol.clone(), va);
410 if prior.as_ref().is_some_and(|value| value >= &key) {
411 return Err(Error::format(
412 "strict vtable records are not canonical and unique",
413 ));
414 }
415 prior = Some(key);
416 words = words
417 .checked_add(record_words)
418 .ok_or_else(|| Error::format("strict vtable word count overflows"))?;
419 }
420 if words > limits.max_words {
421 return Err(Error::format("strict vtable word limit exceeded"));
422 }
423 match self.outcome {
424 StrictRttiOutcome::Absent
425 if self.conservation.attempted == 0 && self.gaps.is_empty() => {}
426 StrictRttiOutcome::Complete
427 if self.conservation.attempted > 0
428 && self.gaps.is_empty()
429 && self.conservation.included == self.conservation.attempted => {}
430 StrictRttiOutcome::Rejected if !self.gaps.is_empty() => {}
431 _ => {
432 return Err(Error::format(
433 "strict vtable outcome and conservation disagree",
434 ));
435 }
436 }
437 Ok(())
438 }
439
440 fn validate_group(&self, record: &ItaniumVtableGroupRecord) -> Result<()> {
441 if !matches!(
442 record.kind,
443 ItaniumVtableSymbolKind::CompleteGroup | ItaniumVtableSymbolKind::ConstructionGroup
444 ) || decoder::classify_symbol(&record.symbol) != Some(record.kind)
445 || !matches!(record.pointer_width, 4 | 8)
446 || record.byte_length == 0
447 || record.byte_length % u64::from(record.pointer_width) != 0
448 || record.address_points.is_empty()
449 {
450 return Err(Error::format("strict vtable group shape is invalid"));
451 }
452 let mut used = std::collections::BTreeSet::new();
453 for (index, point) in record.address_points.iter().enumerate() {
454 if point.ordinal != index as u64
455 || point.va
456 != record.va + (point.offset_to_top_word + 2) * u64::from(record.pointer_width)
457 {
458 return Err(Error::format(
459 "strict vtable address point is not canonical",
460 ));
461 }
462 let source_matches = match point.source {
463 ItaniumVtableAddressPointSource::TypeinfoSymbol => !matches!(
464 point.typeinfo.target,
465 crate::strict_rtti::StrictPointerTarget::Null
466 ),
467 ItaniumVtableAddressPointSource::NullTypeinfoAtSymbolStart => {
468 index == 0
469 && point.offset_to_top_word == 0
470 && matches!(
471 point.typeinfo.target,
472 crate::strict_rtti::StrictPointerTarget::Null
473 )
474 }
475 };
476 if !source_matches {
477 return Err(Error::format(
478 "strict vtable address-point source disagrees with its header",
479 ));
480 }
481 self.validate_integer(
482 &record.symbol,
483 point.offset_to_top_observation_ordinal,
484 record.pointer_width,
485 &mut used,
486 )?;
487 self.validate_pointer(&record.symbol, &point.typeinfo, &mut used)?;
488 for offset in &point.prefix_offsets {
489 self.validate_integer(
490 &record.symbol,
491 offset.observation_ordinal,
492 record.pointer_width,
493 &mut used,
494 )?;
495 if offset.signed_value != sign_extend(offset.raw_value, record.pointer_width) {
496 return Err(Error::format(
497 "strict vtable offset sign extension disagrees",
498 ));
499 }
500 }
501 for (slot_index, slot) in point.slots.iter().enumerate() {
502 if slot.ordinal != slot_index as u64 {
503 return Err(Error::format("strict vtable slots are not canonical"));
504 }
505 self.validate_pointer(&record.symbol, &slot.pointer, &mut used)?;
506 decoder::validate_slot_semantics(slot)?;
507 }
508 }
509 for word in &record.ambiguous_words {
510 self.validate_integer(
511 &record.symbol,
512 word.observation_ordinal,
513 record.pointer_width,
514 &mut used,
515 )?;
516 }
517 let expected = record.byte_length / u64::from(record.pointer_width);
518 if u64::try_from(used.len()).ok() != Some(expected) {
519 return Err(Error::format(
520 "strict vtable group does not conserve every word exactly once",
521 ));
522 }
523 Ok(())
524 }
525
526 fn validate_vtt(&self, record: &ItaniumVttRecord) -> Result<()> {
527 if decoder::classify_symbol(&record.symbol) != Some(ItaniumVtableSymbolKind::Vtt)
528 || record.byte_length == 0
529 || record.entries.is_empty()
530 {
531 return Err(Error::format("strict VTT shape is invalid"));
532 }
533 let mut used = std::collections::BTreeSet::new();
534 for (index, entry) in record.entries.iter().enumerate() {
535 if entry.ordinal != index as u64 {
536 return Err(Error::format("strict VTT entries are not canonical"));
537 }
538 self.validate_pointer(&record.symbol, &entry.address_point, &mut used)?;
539 }
540 if record.byte_length
541 != record.entries.len() as u64 * u64::from(record.entries[0].address_point.width)
542 {
543 return Err(Error::format("strict VTT extent differs from its entries"));
544 }
545 Ok(())
546 }
547
548 fn validate_pointer(
549 &self,
550 symbol: &str,
551 pointer: &StrictPointerObservation,
552 used: &mut std::collections::BTreeSet<u64>,
553 ) -> Result<()> {
554 let observation = self
555 .observations
556 .get(pointer.observation_ordinal as usize)
557 .ok_or_else(|| Error::format("strict vtable pointer observation is absent"))?;
558 if observation.symbol != symbol
559 || observation.kind != StrictRttiObservationKind::Pointer
560 || observation.length != u64::from(pointer.width)
561 || !used.insert(pointer.observation_ordinal)
562 {
563 return Err(Error::format(
564 "strict vtable pointer observation disagrees or aliases",
565 ));
566 }
567 Ok(())
568 }
569
570 fn validate_integer(
571 &self,
572 symbol: &str,
573 ordinal: u64,
574 width: u8,
575 used: &mut std::collections::BTreeSet<u64>,
576 ) -> Result<()> {
577 let observation = self
578 .observations
579 .get(ordinal as usize)
580 .ok_or_else(|| Error::format("strict vtable integer observation is absent"))?;
581 if observation.symbol != symbol
582 || observation.kind != StrictRttiObservationKind::Integer
583 || observation.length != u64::from(width)
584 || !used.insert(ordinal)
585 {
586 return Err(Error::format(
587 "strict vtable integer observation disagrees or aliases",
588 ));
589 }
590 Ok(())
591 }
592}
593
594pub(crate) fn sign_extend(value: u64, width: u8) -> i64 {
595 if width == 4 {
596 i64::from(value as u32 as i32)
597 } else {
598 value as i64
599 }
600}
601
602#[derive(Deserialize)]
603#[serde(deny_unknown_fields)]
604struct StrictVtableBatchWire {
605 outcome: StrictRttiOutcome,
606 records: Vec<StrictVtableRecord>,
607 observations: Vec<StrictRttiObservation>,
608 gaps: Vec<StrictRttiGap>,
609 conservation: StrictRttiConservation,
610}
611
612impl<'de> Deserialize<'de> for StrictVtableBatch {
613 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
614 where
615 D: Deserializer<'de>,
616 {
617 let wire = StrictVtableBatchWire::deserialize(deserializer)?;
618 let value = Self {
619 outcome: wire.outcome,
620 records: wire.records,
621 observations: wire.observations,
622 gaps: wire.gaps,
623 conservation: wire.conservation,
624 };
625 value
626 .validate(StrictVtableLimits::default())
627 .map_err(serde::de::Error::custom)?;
628 Ok(value)
629 }
630}