use std::collections::{BTreeSet, HashSet};
use std::sync::{Arc, RwLock};
use applevisor as av;
use crate::core::*;
use crate::crash::*;
use crate::error::*;
use crate::hooks::*;
use crate::memory::*;
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct CoverageRange(pub(crate) std::ops::Range<u64>);
impl CoverageRange {
pub fn new(start: u64, end: u64) -> Self {
Self(start..end)
}
}
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
pub struct Coverage {
pub set: BTreeSet<u128>,
}
impl Coverage {
pub fn new() -> Self {
Self {
set: BTreeSet::new(),
}
}
pub fn clear(&mut self) {
self.set.clear();
}
pub fn count(&self) -> usize {
self.set.len()
}
}
impl Default for Coverage {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct GlobalCoverageInner {
pub(crate) coverage: Coverage,
ranges: Vec<CoverageRange>,
known_crashes: HashSet<u64>,
}
impl GlobalCoverageInner {
fn new(ranges: Vec<CoverageRange>) -> Self {
Self {
coverage: Coverage::new(),
ranges,
known_crashes: HashSet::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct GlobalCoverage {
pub inner: Arc<RwLock<GlobalCoverageInner>>,
}
impl GlobalCoverage {
const B_INSNS: &'static [&'static str] = &[
"b.eq", "b.ne", "b.cs", "b.cc", "b.mi", "b.pl", "b.vs", "b.vc", "b.hi", "b.ls", "b.ge",
"b.lt", "b.gt", "b.le", "b.al", "cbnz", "cbz", "tbnz", "tbz", "blr", "br",
];
const CMP_INSNS: &'static [&'static str] = &["cmp", "subs"];
pub fn new(ranges: Vec<CoverageRange>) -> Self {
Self {
inner: Arc::new(RwLock::new(GlobalCoverageInner::new(ranges))),
}
}
pub fn cloned(&self) -> Coverage {
let inner = self.inner.read().unwrap();
inner.coverage.clone()
}
pub fn count(&self) -> usize {
let inner = self.inner.read().unwrap();
inner.coverage.count()
}
pub fn add_hooks<LD: Clone, GD: Clone>(
&self,
vma: &VirtMemAllocator,
hooks: &mut Hooks<LD, GD>,
comparison_unrolling: bool,
) -> Result<()> {
let inner = self.inner.read().unwrap();
for CoverageRange(range) in inner.ranges.iter() {
for addr in range.clone().step_by(4) {
let mut code = [0; 4];
vma.read(addr, &mut code)?;
let (cov, cmp) = CSE.with(|cs| {
let insns = cs
.disasm_count(&code, addr, 1)
.expect("could not disassemble while adding coverage hooks");
if let Some(insn) = insns.as_ref().get(0) {
(
Self::B_INSNS.contains(&insn.mnemonic().unwrap()),
Self::CMP_INSNS.contains(&insn.mnemonic().unwrap()),
)
} else {
(false, false)
}
});
if cov {
hooks.add_coverage_hook(addr, GlobalCoverage::hook_coverage);
} else if comparison_unrolling && cmp {
hooks.add_coverage_hook(addr, GlobalCoverage::hook_cmp);
}
}
}
Ok(())
}
pub fn update_new_crashes(&mut self, hash: u64) -> bool {
let inner = self.inner.read().unwrap();
if inner.known_crashes.contains(&hash) {
false
} else {
drop(inner);
let mut inner = self.inner.write().unwrap();
inner.known_crashes.insert(hash);
true
}
}
pub fn update_new_coverage(&self, other: &Coverage) -> Option<u64> {
let inner = self.inner.read().unwrap();
if other.set.is_subset(&inner.coverage.set) {
None
} else {
drop(inner);
let mut inner = self.inner.write().unwrap();
if other.set.is_subset(&inner.coverage.set) {
None
} else {
let old_size = inner.coverage.set.len();
inner.coverage.set = inner.coverage.set.union(&other.set).copied().collect();
Some((inner.coverage.set.len() - old_size) as u64)
}
}
}
pub fn hook_coverage<LD, GD>(args: &mut HookArgs<LD, GD>) -> Result<ExitKind> {
args.cdata.set.insert(args.addr as u128);
Ok(ExitKind::Continue)
}
pub fn hook_cmp<LD, GD>(args: &mut HookArgs<LD, GD>) -> Result<ExitKind> {
let addr = args.addr as u128;
let value = Comparisons::get_value(args.insn_int, args.vcpu)?;
match value {
ComparisonResult::U64(reg_value, cmp_value) => {
for i in (0u128..8u128).rev() {
if (reg_value >> (i * 8)) & 0xff == (cmp_value >> (i * 8)) & 0xff {
let pc = ((i + 1) << 0x30) | addr;
args.cdata.set.insert(pc);
} else {
break;
}
}
}
ComparisonResult::U32(reg_value, cmp_value) => {
for i in (0u128..4u128).rev() {
if (reg_value >> (i * 8)) & 0xff == (cmp_value >> (i * 8)) & 0xff {
let pc = ((i + 1) << 0x30) | addr;
args.cdata.set.insert(pc);
} else {
break;
}
}
}
_ => {}
}
Ok(ExitKind::Continue)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
enum ComparisonResult {
U32(u32, u32),
U64(u64, u64),
Other,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
struct Comparisons;
impl Comparisons {
fn get_value(insn: u32, vcpu: &av::Vcpu) -> Result<ComparisonResult> {
match insn {
i if (i >> 23) & 0xff == 0b11100010 => Self::subs_immediate(i, vcpu),
i if (i >> 24) & 0x7f == 0b1101011 => Self::subs_shifted_reg(i, vcpu),
_ => Ok(ComparisonResult::Other),
}
}
fn get_operand(vcpu: &av::Vcpu, rd: u32) -> Result<u64> {
match rd {
0 => Ok(vcpu.get_reg(av::Reg::X0)?),
1 => Ok(vcpu.get_reg(av::Reg::X1)?),
2 => Ok(vcpu.get_reg(av::Reg::X2)?),
3 => Ok(vcpu.get_reg(av::Reg::X3)?),
4 => Ok(vcpu.get_reg(av::Reg::X4)?),
5 => Ok(vcpu.get_reg(av::Reg::X5)?),
6 => Ok(vcpu.get_reg(av::Reg::X6)?),
7 => Ok(vcpu.get_reg(av::Reg::X7)?),
8 => Ok(vcpu.get_reg(av::Reg::X8)?),
9 => Ok(vcpu.get_reg(av::Reg::X9)?),
10 => Ok(vcpu.get_reg(av::Reg::X10)?),
11 => Ok(vcpu.get_reg(av::Reg::X11)?),
12 => Ok(vcpu.get_reg(av::Reg::X12)?),
13 => Ok(vcpu.get_reg(av::Reg::X13)?),
14 => Ok(vcpu.get_reg(av::Reg::X14)?),
15 => Ok(vcpu.get_reg(av::Reg::X15)?),
16 => Ok(vcpu.get_reg(av::Reg::X16)?),
17 => Ok(vcpu.get_reg(av::Reg::X17)?),
18 => Ok(vcpu.get_reg(av::Reg::X18)?),
19 => Ok(vcpu.get_reg(av::Reg::X19)?),
20 => Ok(vcpu.get_reg(av::Reg::X20)?),
21 => Ok(vcpu.get_reg(av::Reg::X21)?),
22 => Ok(vcpu.get_reg(av::Reg::X22)?),
23 => Ok(vcpu.get_reg(av::Reg::X23)?),
24 => Ok(vcpu.get_reg(av::Reg::X24)?),
25 => Ok(vcpu.get_reg(av::Reg::X25)?),
26 => Ok(vcpu.get_reg(av::Reg::X26)?),
27 => Ok(vcpu.get_reg(av::Reg::X27)?),
28 => Ok(vcpu.get_reg(av::Reg::X28)?),
29 => Ok(vcpu.get_reg(av::Reg::X29)?),
30 => Ok(vcpu.get_reg(av::Reg::LR)?),
31 => Ok(vcpu.get_reg(av::Reg::PC)?),
_ => unreachable!("invalid operand"),
}
}
#[inline]
fn subs_immediate(insn: u32, vcpu: &av::Vcpu) -> Result<ComparisonResult> {
let imm = (insn >> 10) & 0xfff;
let sh = (insn >> 22) & 1;
let value = imm << (12 * sh);
let sf = ((insn >> 31) & 1) == 1;
let rn = (insn >> 5) & 0x1f;
let rn_val = Self::get_operand(vcpu, rn)?;
if sf {
Ok(ComparisonResult::U64(rn_val, value as u64))
} else {
Ok(ComparisonResult::U32(rn_val as u32, value))
}
}
#[inline]
fn subs_shifted_reg(insn: u32, vcpu: &av::Vcpu) -> Result<ComparisonResult> {
let rn = (insn >> 5) & 0x1f;
let rn_val = Self::get_operand(vcpu, rn)?;
let rm = (insn >> 16) & 0x1f;
let rm_val = Self::get_operand(vcpu, rm)?;
let shift_amount = (insn >> 10) & 0x3f;
let shift_type = (insn >> 22) & 3;
let sf = ((insn >> 31) & 1) == 1;
if sf && (shift_amount >> 5) == 1 {
return Ok(ComparisonResult::Other);
}
if sf {
Ok(match shift_type {
0b00 => ComparisonResult::U64(rn_val, (rm_val as u64) << shift_amount),
0b01 => ComparisonResult::U64(rn_val, (rm_val as u64) >> shift_amount),
0b10 => ComparisonResult::U64(rn_val, ((rm_val as i64) >> shift_amount) as u64),
_ => return Ok(ComparisonResult::Other),
})
} else {
Ok(match shift_type {
0b00 => ComparisonResult::U32(rn_val as u32, (rm_val as u32) << shift_amount),
0b01 => ComparisonResult::U32(rn_val as u32, (rm_val as u32) >> shift_amount),
0b10 => {
ComparisonResult::U32(rn_val as u32, ((rm_val as i32) >> shift_amount) as u32)
}
_ => return Ok(ComparisonResult::Other),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cov_cmp_subs_immediate() {
let _vm = av::VirtualMachine::new().unwrap();
let vcpu = av::Vcpu::new().unwrap();
vcpu.set_reg(av::Reg::X0, 0xdeadbeefdeadbeef).unwrap();
let insn = 0xf1048c1fu32;
let value = Comparisons::get_value(insn, &vcpu);
assert_eq!(value, Ok(ComparisonResult::U64(0xdeadbeefdeadbeef, 0x123)));
let insn = 0x710d141fu32;
let value = Comparisons::get_value(insn, &vcpu);
assert_eq!(value, Ok(ComparisonResult::U32(0xdeadbeef, 0x345)));
let insn = 0xf159e01fu32;
let value = Comparisons::get_value(insn, &vcpu);
assert_eq!(
value,
Ok(ComparisonResult::U64(0xdeadbeefdeadbeef, 0x678000))
);
let insn = 0x7164481fu32;
let value = Comparisons::get_value(insn, &vcpu);
assert_eq!(value, Ok(ComparisonResult::U32(0xdeadbeef, 0x912000)));
}
}