Commit af32d275 by Enkelmann Committed by Enkelmann

Adapt the analysis modules to use the internal IR instead of the BAP IR.

parent d701731b
...@@ -5,11 +5,13 @@ all: ...@@ -5,11 +5,13 @@ all:
cp target/release/libcwe_checker_rs.so src/dllcwe_checker_rs.so cp target/release/libcwe_checker_rs.so src/dllcwe_checker_rs.so
dune build dune build
dune install dune install
cd plugins/cwe_checker; make all; cd ../.. cd plugins/cwe_checker && make all
cd plugins/cwe_checker_emulation; make all; cd ../.. cd plugins/cwe_checker_emulation && make all
cd plugins/cwe_checker_type_inference; make all; cd ../.. cd plugins/cwe_checker_type_inference && make all
cd plugins/cwe_checker_type_inference_print; make all; cd ../.. cd plugins/cwe_checker_type_inference_print && make all
cd plugins/cwe_checker_pointer_inference_debug; make all; cd ../.. cd plugins/cwe_checker_pointer_inference_debug && make all
mkdir ${HOME}/.config/cwe_checker
cp src/utils/registers.json ${HOME}/.config/cwe_checker/registers.json
test: test:
cargo test cargo test
...@@ -43,6 +45,7 @@ uninstall: ...@@ -43,6 +45,7 @@ uninstall:
cd plugins/cwe_checker_type_inference; make uninstall; cd ../.. cd plugins/cwe_checker_type_inference; make uninstall; cd ../..
cd plugins/cwe_checker_type_inference_print; make uninstall; cd ../.. cd plugins/cwe_checker_type_inference_print; make uninstall; cd ../..
cd plugins/cwe_checker_pointer_inference_debug; make uninstall; cd ../.. cd plugins/cwe_checker_pointer_inference_debug; make uninstall; cd ../..
rm -f -r ${HOME}/.config/cwe_checker
documentation: documentation:
dune build @doc dune build @doc
......
...@@ -15,6 +15,7 @@ fnv = "1.0" # a faster hash function for small keys like integers ...@@ -15,6 +15,7 @@ fnv = "1.0" # a faster hash function for small keys like integers
anyhow = "1.0" # for easy error types anyhow = "1.0" # for easy error types
crossbeam-channel = "0.4" crossbeam-channel = "0.4"
derive_more = "0.99" derive_more = "0.99"
directories = "3.0"
[lib] [lib]
name = "cwe_checker_rs" name = "cwe_checker_rs"
......
use super::{AbstractDomain, HasBitSize, HasTop, RegisterDomain}; use super::{AbstractDomain, HasByteSize, HasTop, RegisterDomain};
use crate::bil::*; use crate::bil::BitSize;
use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
/// The `BitvectorDomain` is a simple abstract domain describing a bitvector of known length. /// The `BitvectorDomain` is a simple abstract domain describing a bitvector of known length.
/// ///
/// As values it can only assume a known bitvector or *Top(bitsize)*. /// As values it can only assume a known bitvector or *Top(bytesize)*.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub enum BitvectorDomain { pub enum BitvectorDomain {
Top(BitSize), Top(ByteSize),
Value(Bitvector), Value(Bitvector),
} }
...@@ -28,27 +29,27 @@ impl AbstractDomain for BitvectorDomain { ...@@ -28,27 +29,27 @@ impl AbstractDomain for BitvectorDomain {
} }
impl HasTop for BitvectorDomain { impl HasTop for BitvectorDomain {
/// Return a *Top* value with the same bitsize as `self`. /// Return a *Top* value with the same bytesize as `self`.
fn top(&self) -> BitvectorDomain { fn top(&self) -> BitvectorDomain {
BitvectorDomain::Top(self.bitsize()) BitvectorDomain::Top(self.bytesize())
} }
} }
impl HasBitSize for BitvectorDomain { impl HasByteSize for BitvectorDomain {
/// Return the bitsize of `self`. /// Return the bytesize of `self`.
fn bitsize(&self) -> BitSize { fn bytesize(&self) -> ByteSize {
use BitvectorDomain::*; use BitvectorDomain::*;
match self { match self {
Top(bitsize) => *bitsize, Top(bytesize) => *bytesize,
Value(bitvec) => bitvec.width().to_usize() as u16, Value(bitvec) => bitvec.width().into(),
} }
} }
} }
impl RegisterDomain for BitvectorDomain { impl RegisterDomain for BitvectorDomain {
/// Get a *Top* element with the given bitsize. /// Get a *Top* element with the given bitsize.
fn new_top(bitsize: BitSize) -> BitvectorDomain { fn new_top(bytesize: ByteSize) -> BitvectorDomain {
BitvectorDomain::Top(bitsize) BitvectorDomain::Top(bytesize)
} }
/// Evaluate the given binary operation. /// Evaluate the given binary operation.
...@@ -57,27 +58,83 @@ impl RegisterDomain for BitvectorDomain { ...@@ -57,27 +58,83 @@ impl RegisterDomain for BitvectorDomain {
fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self { fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self {
use BinOpType::*; use BinOpType::*;
match op { match op {
LSHIFT | RSHIFT | ARSHIFT => (), Piece | IntLeft | IntRight | IntSRight => (),
_ => assert_eq!(self.bitsize(), rhs.bitsize()), _ => assert_eq!(self.bytesize(), rhs.bytesize()),
} }
match (self, rhs) { match (self, rhs) {
(BitvectorDomain::Value(lhs_bitvec), BitvectorDomain::Value(rhs_bitvec)) => match op { (BitvectorDomain::Value(lhs_bitvec), BitvectorDomain::Value(rhs_bitvec)) => match op {
PLUS => BitvectorDomain::Value(lhs_bitvec + rhs_bitvec), Piece => {
MINUS => BitvectorDomain::Value(lhs_bitvec - rhs_bitvec), let new_bitwidth = BitSize::from(self.bytesize() + rhs.bytesize());
TIMES => BitvectorDomain::Value(lhs_bitvec * rhs_bitvec), let upper_bits = lhs_bitvec
DIVIDE => BitvectorDomain::Value( .clone()
.into_zero_extend(new_bitwidth as usize)
.unwrap()
.into_checked_shl(BitSize::from(rhs.bytesize()) as usize)
.unwrap();
let lower_bits = rhs_bitvec
.clone()
.into_zero_extend(new_bitwidth as usize)
.unwrap();
BitvectorDomain::Value(upper_bits | &lower_bits)
}
IntAdd => BitvectorDomain::Value(lhs_bitvec + rhs_bitvec),
IntSub => BitvectorDomain::Value(lhs_bitvec - rhs_bitvec),
IntCarry => {
let result = lhs_bitvec + rhs_bitvec;
if result.checked_ult(lhs_bitvec).unwrap()
|| result.checked_ult(rhs_bitvec).unwrap()
{
Bitvector::from_u8(1).into()
} else {
Bitvector::from_u8(0).into()
}
}
IntSCarry => {
let result = apint::Int::from(lhs_bitvec + rhs_bitvec);
let lhs_bitvec = apint::Int::from(lhs_bitvec.clone());
let rhs_bitvec = apint::Int::from(rhs_bitvec.clone());
if (result.is_negative()
&& lhs_bitvec.is_positive()
&& rhs_bitvec.is_positive())
|| (!result.is_negative()
&& lhs_bitvec.is_negative()
&& rhs_bitvec.is_negative())
{
Bitvector::from_u8(1).into()
} else {
Bitvector::from_u8(0).into()
}
}
IntSBorrow => {
let result = apint::Int::from(lhs_bitvec - rhs_bitvec);
let lhs_bitvec = apint::Int::from(lhs_bitvec.clone());
let rhs_bitvec = apint::Int::from(rhs_bitvec.clone());
if (result.is_negative()
&& !lhs_bitvec.is_positive()
&& rhs_bitvec.is_negative())
|| (result.is_positive()
&& lhs_bitvec.is_negative()
&& rhs_bitvec.is_positive())
{
Bitvector::from_u8(1).into()
} else {
Bitvector::from_u8(0).into()
}
}
IntMult => BitvectorDomain::Value(lhs_bitvec * rhs_bitvec),
IntDiv => BitvectorDomain::Value(
lhs_bitvec.clone().into_checked_udiv(rhs_bitvec).unwrap(), lhs_bitvec.clone().into_checked_udiv(rhs_bitvec).unwrap(),
), ),
SDIVIDE => BitvectorDomain::Value( IntSDiv => BitvectorDomain::Value(
lhs_bitvec.clone().into_checked_sdiv(rhs_bitvec).unwrap(), lhs_bitvec.clone().into_checked_sdiv(rhs_bitvec).unwrap(),
), ),
MOD => BitvectorDomain::Value( IntRem => BitvectorDomain::Value(
lhs_bitvec.clone().into_checked_urem(rhs_bitvec).unwrap(), lhs_bitvec.clone().into_checked_urem(rhs_bitvec).unwrap(),
), ),
SMOD => BitvectorDomain::Value( IntSRem => BitvectorDomain::Value(
lhs_bitvec.clone().into_checked_srem(rhs_bitvec).unwrap(), lhs_bitvec.clone().into_checked_srem(rhs_bitvec).unwrap(),
), ),
LSHIFT => { IntLeft => {
let shift_amount = rhs_bitvec.try_to_u64().unwrap() as usize; let shift_amount = rhs_bitvec.try_to_u64().unwrap() as usize;
if shift_amount < lhs_bitvec.width().to_usize() { if shift_amount < lhs_bitvec.width().to_usize() {
BitvectorDomain::Value( BitvectorDomain::Value(
...@@ -87,7 +144,7 @@ impl RegisterDomain for BitvectorDomain { ...@@ -87,7 +144,7 @@ impl RegisterDomain for BitvectorDomain {
BitvectorDomain::Value(Bitvector::zero(lhs_bitvec.width())) BitvectorDomain::Value(Bitvector::zero(lhs_bitvec.width()))
} }
} }
RSHIFT => { IntRight => {
let shift_amount = rhs_bitvec.try_to_u64().unwrap() as usize; let shift_amount = rhs_bitvec.try_to_u64().unwrap() as usize;
if shift_amount < lhs_bitvec.width().to_usize() { if shift_amount < lhs_bitvec.width().to_usize() {
BitvectorDomain::Value( BitvectorDomain::Value(
...@@ -97,7 +154,7 @@ impl RegisterDomain for BitvectorDomain { ...@@ -97,7 +154,7 @@ impl RegisterDomain for BitvectorDomain {
BitvectorDomain::Value(Bitvector::zero(lhs_bitvec.width())) BitvectorDomain::Value(Bitvector::zero(lhs_bitvec.width()))
} }
} }
ARSHIFT => { IntSRight => {
let shift_amount = rhs_bitvec.try_to_u64().unwrap() as usize; let shift_amount = rhs_bitvec.try_to_u64().unwrap() as usize;
if shift_amount < lhs_bitvec.width().to_usize() { if shift_amount < lhs_bitvec.width().to_usize() {
BitvectorDomain::Value( BitvectorDomain::Value(
...@@ -114,34 +171,48 @@ impl RegisterDomain for BitvectorDomain { ...@@ -114,34 +171,48 @@ impl RegisterDomain for BitvectorDomain {
} }
} }
} }
AND => BitvectorDomain::Value(lhs_bitvec & rhs_bitvec), IntAnd | BoolAnd => BitvectorDomain::Value(lhs_bitvec & rhs_bitvec),
OR => BitvectorDomain::Value(lhs_bitvec | rhs_bitvec), IntOr | BoolOr => BitvectorDomain::Value(lhs_bitvec | rhs_bitvec),
XOR => BitvectorDomain::Value(lhs_bitvec ^ rhs_bitvec), IntXOr | BoolXOr => BitvectorDomain::Value(lhs_bitvec ^ rhs_bitvec),
EQ => { IntEqual => {
assert_eq!(lhs_bitvec.width(), rhs_bitvec.width()); assert_eq!(lhs_bitvec.width(), rhs_bitvec.width());
BitvectorDomain::Value(Bitvector::from(lhs_bitvec == rhs_bitvec)) BitvectorDomain::Value(Bitvector::from(lhs_bitvec == rhs_bitvec))
} }
NEQ => { IntNotEqual => {
assert_eq!(lhs_bitvec.width(), rhs_bitvec.width()); assert_eq!(lhs_bitvec.width(), rhs_bitvec.width());
BitvectorDomain::Value(Bitvector::from(lhs_bitvec != rhs_bitvec)) BitvectorDomain::Value(Bitvector::from(lhs_bitvec != rhs_bitvec))
} }
LT => BitvectorDomain::Value(Bitvector::from( IntLess => BitvectorDomain::Value(Bitvector::from(
lhs_bitvec.checked_ult(rhs_bitvec).unwrap(), lhs_bitvec.checked_ult(rhs_bitvec).unwrap(),
)), )),
LE => BitvectorDomain::Value(Bitvector::from( IntLessEqual => BitvectorDomain::Value(Bitvector::from(
lhs_bitvec.checked_ule(rhs_bitvec).unwrap(), lhs_bitvec.checked_ule(rhs_bitvec).unwrap(),
)), )),
SLT => BitvectorDomain::Value(Bitvector::from( IntSLess => BitvectorDomain::Value(Bitvector::from(
lhs_bitvec.checked_slt(rhs_bitvec).unwrap(), lhs_bitvec.checked_slt(rhs_bitvec).unwrap(),
)), )),
SLE => BitvectorDomain::Value(Bitvector::from( IntSLessEqual => BitvectorDomain::Value(Bitvector::from(
lhs_bitvec.checked_sle(rhs_bitvec).unwrap(), lhs_bitvec.checked_sle(rhs_bitvec).unwrap(),
)), )),
FloatEqual | FloatNotEqual | FloatLess | FloatLessEqual => {
// TODO: Implement floating point comparison operators!
BitvectorDomain::new_top(ByteSize::new(1))
}
FloatAdd | FloatSub | FloatMult | FloatDiv => {
// TODO: Implement floating point arithmetic operators!
BitvectorDomain::new_top(self.bytesize())
}
}, },
_ => match op { _ => match op {
PLUS | MINUS | TIMES | DIVIDE | SDIVIDE | MOD | SMOD | LSHIFT | RSHIFT Piece => BitvectorDomain::new_top(self.bytesize() + rhs.bytesize()),
| ARSHIFT | AND | OR | XOR => BitvectorDomain::new_top(self.bitsize()), IntAdd | IntSub | IntMult | IntDiv | IntSDiv | IntRem | IntSRem | IntLeft
EQ | NEQ | LT | LE | SLT | SLE => BitvectorDomain::new_top(1), | IntRight | IntSRight | IntAnd | IntOr | IntXOr | FloatAdd | FloatSub
| FloatMult | FloatDiv => BitvectorDomain::new_top(self.bytesize()),
IntEqual | IntNotEqual | IntLess | IntLessEqual | IntSLess | IntSLessEqual
| IntCarry | IntSCarry | IntSBorrow | BoolAnd | BoolOr | BoolXOr | FloatEqual
| FloatNotEqual | FloatLess | FloatLessEqual => {
BitvectorDomain::new_top(ByteSize::new(1))
}
}, },
} }
} }
...@@ -151,82 +222,72 @@ impl RegisterDomain for BitvectorDomain { ...@@ -151,82 +222,72 @@ impl RegisterDomain for BitvectorDomain {
use UnOpType::*; use UnOpType::*;
if let BitvectorDomain::Value(bitvec) = self { if let BitvectorDomain::Value(bitvec) = self {
match op { match op {
NEG => BitvectorDomain::Value(-bitvec), Int2Comp => BitvectorDomain::Value(-bitvec),
NOT => BitvectorDomain::Value(bitvec.clone().into_bitnot()), IntNegate => BitvectorDomain::Value(bitvec.clone().into_bitnot()),
BoolNegate => {
if bitvec.is_zero() {
BitvectorDomain::Value(Bitvector::from_u8(1))
} else {
BitvectorDomain::Value(Bitvector::from_u8(0))
}
}
FloatNegate | FloatAbs | FloatSqrt | FloatCeil | FloatFloor | FloatRound
| FloatNaN => BitvectorDomain::new_top(self.bytesize()),
} }
} else { } else {
BitvectorDomain::new_top(self.bitsize()) match op {
BoolNegate => BitvectorDomain::new_top(ByteSize::new(1)),
_ => BitvectorDomain::new_top(self.bytesize()),
}
} }
} }
/// Extract a sub-bitvector out of a bitvector /// Extract a sub-bitvector out of a bitvector
fn extract(&self, low_bit: BitSize, high_bit: BitSize) -> Self { fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self {
if let BitvectorDomain::Value(bitvec) = self { if let BitvectorDomain::Value(bitvec) = self {
BitvectorDomain::Value( BitvectorDomain::Value(
bitvec bitvec
.clone() .clone()
.into_checked_lshr(low_bit as usize) .into_checked_lshr(BitSize::from(low_byte) as usize)
.unwrap() .unwrap()
.into_truncate((high_bit - low_bit + 1) as usize) .into_truncate(BitSize::from(size) as usize)
.unwrap(), .unwrap(),
) )
} else { } else {
BitvectorDomain::new_top(high_bit - low_bit + 1) BitvectorDomain::new_top(size)
} }
} }
/// Perform a size-changing cast on a bitvector. /// Perform a size-changing cast on a bitvector.
fn cast(&self, kind: CastType, width: BitSize) -> Self { fn cast(&self, kind: CastOpType, width: ByteSize) -> Self {
if let BitvectorDomain::Value(bitvec) = self { if let BitvectorDomain::Value(bitvec) = self {
use CastType::*; use CastOpType::*;
match kind { match kind {
UNSIGNED => { IntZExt => BitvectorDomain::Value(
BitvectorDomain::Value(bitvec.clone().into_zero_extend(width as usize).unwrap())
}
SIGNED => {
BitvectorDomain::Value(bitvec.clone().into_sign_extend(width as usize).unwrap())
}
HIGH => BitvectorDomain::Value(
bitvec bitvec
.clone() .clone()
.into_checked_lshr((self.bitsize() - width) as usize) .into_zero_extend(apint::BitWidth::from(width))
.unwrap()
.into_truncate(width as usize)
.unwrap(), .unwrap(),
), ),
LOW => { IntSExt => BitvectorDomain::Value(
BitvectorDomain::Value(bitvec.clone().into_truncate(width as usize).unwrap()) bitvec
} .clone()
.into_sign_extend(apint::BitWidth::from(width))
.unwrap(),
),
Int2Float | Float2Float | Trunc => BitvectorDomain::new_top(width),
} }
} else { } else {
BitvectorDomain::new_top(width) BitvectorDomain::new_top(width)
} }
} }
/// Concatenate two bitvectors.
fn concat(&self, other: &Self) -> Self {
match (self, other) {
(BitvectorDomain::Value(left_bitvec), BitvectorDomain::Value(right_bitvec)) => {
let new_bitwidth = (self.bitsize() + other.bitsize()) as usize;
let upper_bits = left_bitvec
.clone()
.into_zero_extend(new_bitwidth)
.unwrap()
.into_checked_shl(other.bitsize() as usize)
.unwrap();
let lower_bits = right_bitvec.clone().into_zero_extend(new_bitwidth).unwrap();
BitvectorDomain::Value(upper_bits | &lower_bits)
}
_ => BitvectorDomain::new_top(self.bitsize() + other.bitsize()),
}
}
} }
impl std::ops::Add for BitvectorDomain { impl std::ops::Add for BitvectorDomain {
type Output = BitvectorDomain; type Output = BitvectorDomain;
fn add(self, rhs: Self) -> Self { fn add(self, rhs: Self) -> Self {
self.bin_op(crate::bil::BinOpType::PLUS, &rhs) self.bin_op(BinOpType::IntAdd, &rhs)
} }
} }
...@@ -234,7 +295,7 @@ impl std::ops::Sub for BitvectorDomain { ...@@ -234,7 +295,7 @@ impl std::ops::Sub for BitvectorDomain {
type Output = BitvectorDomain; type Output = BitvectorDomain;
fn sub(self, rhs: Self) -> Self { fn sub(self, rhs: Self) -> Self {
self.bin_op(crate::bil::BinOpType::MINUS, &rhs) self.bin_op(BinOpType::IntSub, &rhs)
} }
} }
...@@ -242,7 +303,7 @@ impl std::ops::Neg for BitvectorDomain { ...@@ -242,7 +303,7 @@ impl std::ops::Neg for BitvectorDomain {
type Output = BitvectorDomain; type Output = BitvectorDomain;
fn neg(self) -> Self { fn neg(self) -> Self {
self.un_op(crate::bil::UnOpType::NEG) self.un_op(UnOpType::Int2Comp)
} }
} }
...@@ -286,55 +347,55 @@ mod tests { ...@@ -286,55 +347,55 @@ mod tests {
#[test] #[test]
fn bitvector_domain_as_value_domain() { fn bitvector_domain_as_value_domain() {
use crate::bil::BinOpType::*; use BinOpType::*;
use crate::bil::CastType::*; use UnOpType::*;
use crate::bil::UnOpType::*;
let eight = bv(8); let eight = bv(8);
let sixteen = bv(16); let sixteen = bv(16);
assert_eq!(sixteen.bin_op(PLUS, &eight), bv(24)); assert_eq!(sixteen.bin_op(IntAdd, &eight), bv(24));
assert_eq!(sixteen.bin_op(MINUS, &eight), bv(8)); assert_eq!(sixteen.bin_op(IntSub, &eight), bv(8));
assert_eq!(sixteen.bin_op(TIMES, &eight), bv(16 * 8)); assert_eq!(sixteen.bin_op(IntMult, &eight), bv(16 * 8));
assert_eq!(sixteen.bin_op(DIVIDE, &eight), bv(2)); assert_eq!(sixteen.bin_op(IntDiv, &eight), bv(2));
assert_eq!(sixteen.bin_op(SDIVIDE, &eight), bv(2)); assert_eq!(sixteen.bin_op(IntSDiv, &eight), bv(2));
assert_eq!(sixteen.bin_op(MOD, &eight), bv(0)); assert_eq!(sixteen.bin_op(IntRem, &eight), bv(0));
assert_eq!(sixteen.bin_op(SMOD, &eight), bv(0)); assert_eq!(sixteen.bin_op(IntSRem, &eight), bv(0));
assert_eq!(sixteen.bin_op(LSHIFT, &bv(2)), bv(64)); assert_eq!(sixteen.bin_op(IntLeft, &bv(2)), bv(64));
assert_eq!(sixteen.bin_op(RSHIFT, &bv(2)), bv(4)); assert_eq!(sixteen.bin_op(IntRight, &bv(2)), bv(4));
assert_eq!(sixteen.bin_op(ARSHIFT, &bv(2)), bv(4)); assert_eq!(sixteen.bin_op(IntSRight, &bv(2)), bv(4));
assert_eq!(sixteen.bin_op(AND, &eight), bv(0)); assert_eq!(sixteen.bin_op(IntAnd, &eight), bv(0));
assert_eq!(sixteen.bin_op(OR, &eight), bv(24)); assert_eq!(sixteen.bin_op(IntOr, &eight), bv(24));
assert_eq!(sixteen.bin_op(XOR, &eight), bv(24)); assert_eq!(sixteen.bin_op(IntXOr, &eight), bv(24));
assert_eq!( assert_eq!(
sixteen.bin_op(EQ, &bv(16)), sixteen.bin_op(IntEqual, &bv(16)),
BitvectorDomain::Value(Bitvector::from_bit(true)) BitvectorDomain::Value(Bitvector::from_bit(true))
); );
assert_eq!( assert_eq!(
sixteen.bin_op(NEQ, &bv(16)), sixteen.bin_op(IntNotEqual, &bv(16)),
BitvectorDomain::Value(Bitvector::from_bit(false)) BitvectorDomain::Value(Bitvector::from_bit(false))
); );
assert_eq!(sixteen.un_op(NEG), bv(-16)); assert_eq!(sixteen.un_op(Int2Comp), bv(-16));
assert_eq!(bv(0).un_op(NOT), bv(-1)); assert_eq!(bv(0).un_op(IntNegate), bv(-1));
assert_eq!( assert_eq!(
sixteen.extract(0, 31), sixteen.subpiece(ByteSize::new(0), ByteSize::new(4)),
BitvectorDomain::Value(Bitvector::from_i32(16)) BitvectorDomain::Value(Bitvector::from_i32(16))
); );
assert_eq!( assert_eq!(
sixteen.extract(32, 63), sixteen.subpiece(ByteSize::new(4), ByteSize::new(4)),
BitvectorDomain::Value(Bitvector::from_i32(0)) BitvectorDomain::Value(Bitvector::from_i32(0))
); );
assert_eq!( assert_eq!(
BitvectorDomain::Value(Bitvector::from_i32(2)), BitvectorDomain::Value(Bitvector::from_i32(2)),
BitvectorDomain::Value(Bitvector::from_i64(2 << 32)).cast(HIGH, 32) BitvectorDomain::Value(Bitvector::from_i64(2 << 32))
.subpiece(ByteSize::new(4), ByteSize::new(4))
); );
assert_eq!( assert_eq!(
BitvectorDomain::Value(Bitvector::from_i32(-1)) BitvectorDomain::Value(Bitvector::from_i32(-1))
.concat(&BitvectorDomain::Value(Bitvector::from_i32(-1))), .bin_op(Piece, &BitvectorDomain::Value(Bitvector::from_i32(-1))),
bv(-1) bv(-1)
); );
} }
...@@ -342,32 +403,35 @@ mod tests { ...@@ -342,32 +403,35 @@ mod tests {
#[test] #[test]
fn bitvector_domain_as_abstract_domain() { fn bitvector_domain_as_abstract_domain() {
assert_eq!(bv(17).merge(&bv(17)), bv(17)); assert_eq!(bv(17).merge(&bv(17)), bv(17));
assert_eq!(bv(17).merge(&bv(16)), BitvectorDomain::new_top(64)); assert_eq!(
bv(17).merge(&bv(16)),
BitvectorDomain::new_top(ByteSize::new(8))
);
assert!(!bv(17).is_top()); assert!(!bv(17).is_top());
assert!(BitvectorDomain::new_top(64).is_top()); assert!(BitvectorDomain::new_top(ByteSize::new(8)).is_top());
} }
#[test] #[test]
fn arshift() { fn arshift() {
use crate::bil::BinOpType::ARSHIFT; use BinOpType::IntSRight;
let positive_x = BitvectorDomain::Value(Bitvector::from_i64(31)); let positive_x = BitvectorDomain::Value(Bitvector::from_i64(31));
let negative_x = BitvectorDomain::Value(Bitvector::from_i64(-31)); let negative_x = BitvectorDomain::Value(Bitvector::from_i64(-31));
let shift_3 = BitvectorDomain::Value(Bitvector::from_u8(3)); let shift_3 = BitvectorDomain::Value(Bitvector::from_u8(3));
let shift_70 = BitvectorDomain::Value(Bitvector::from_u8(70)); let shift_70 = BitvectorDomain::Value(Bitvector::from_u8(70));
assert_eq!( assert_eq!(
positive_x.bin_op(ARSHIFT, &shift_3), positive_x.bin_op(IntSRight, &shift_3),
BitvectorDomain::Value(Bitvector::from_i64(3)) BitvectorDomain::Value(Bitvector::from_i64(3))
); );
assert_eq!( assert_eq!(
positive_x.bin_op(ARSHIFT, &shift_70), positive_x.bin_op(IntSRight, &shift_70),
BitvectorDomain::Value(Bitvector::from_i64(0)) BitvectorDomain::Value(Bitvector::from_i64(0))
); );
assert_eq!( assert_eq!(
negative_x.bin_op(ARSHIFT, &shift_3), negative_x.bin_op(IntSRight, &shift_3),
BitvectorDomain::Value(Bitvector::from_i64(-4)) BitvectorDomain::Value(Bitvector::from_i64(-4))
); );
assert_eq!( assert_eq!(
negative_x.bin_op(ARSHIFT, &shift_70), negative_x.bin_op(IntSRight, &shift_70),
BitvectorDomain::Value(Bitvector::from_i64(-1)) BitvectorDomain::Value(Bitvector::from_i64(-1))
); );
} }
......
use super::{ use super::{
AbstractDomain, AbstractIdentifier, HasBitSize, HasTop, PointerDomain, RegisterDomain, AbstractDomain, AbstractIdentifier, HasByteSize, HasTop, PointerDomain, RegisterDomain,
}; };
use crate::bil::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Display; use std::fmt::Display;
...@@ -10,7 +10,7 @@ use std::fmt::Display; ...@@ -10,7 +10,7 @@ use std::fmt::Display;
/// Both non-pointer values and offsets of pointers are represented by the same abstract domain `T`. /// Both non-pointer values and offsets of pointers are represented by the same abstract domain `T`.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum DataDomain<T: RegisterDomain> { pub enum DataDomain<T: RegisterDomain> {
Top(BitSize), Top(ByteSize),
Pointer(PointerDomain<T>), Pointer(PointerDomain<T>),
Value(T), Value(T),
} }
...@@ -54,7 +54,7 @@ impl<T: RegisterDomain> DataDomain<T> { ...@@ -54,7 +54,7 @@ impl<T: RegisterDomain> DataDomain<T> {
}) })
.collect(); .collect();
if remaining_targets.is_empty() { if remaining_targets.is_empty() {
*self = Self::new_top(self.bitsize()); *self = Self::new_top(self.bytesize());
} else { } else {
*self = Self::Pointer(PointerDomain::with_targets(remaining_targets)); *self = Self::Pointer(PointerDomain::with_targets(remaining_targets));
} }
...@@ -62,14 +62,14 @@ impl<T: RegisterDomain> DataDomain<T> { ...@@ -62,14 +62,14 @@ impl<T: RegisterDomain> DataDomain<T> {
} }
} }
impl<T: RegisterDomain> HasBitSize for DataDomain<T> { impl<T: RegisterDomain> HasByteSize for DataDomain<T> {
// Return the bitsize of `self`. // Return the bitsize of `self`.
fn bitsize(&self) -> BitSize { fn bytesize(&self) -> ByteSize {
use DataDomain::*; use DataDomain::*;
match self { match self {
Top(size) => *size, Top(size) => *size,
Pointer(pointer) => pointer.bitsize(), Pointer(pointer) => pointer.bytesize(),
Value(bitvec) => bitvec.bitsize(), Value(bitvec) => bitvec.bytesize(),
} }
} }
} }
...@@ -77,14 +77,14 @@ impl<T: RegisterDomain> HasBitSize for DataDomain<T> { ...@@ -77,14 +77,14 @@ impl<T: RegisterDomain> HasBitSize for DataDomain<T> {
impl<T: RegisterDomain> HasTop for DataDomain<T> { impl<T: RegisterDomain> HasTop for DataDomain<T> {
// Generate a new *Top* element with the same bitsize as `self`. // Generate a new *Top* element with the same bitsize as `self`.
fn top(&self) -> Self { fn top(&self) -> Self {
DataDomain::new_top(self.bitsize()) DataDomain::new_top(self.bytesize())
} }
} }
impl<T: RegisterDomain> RegisterDomain for DataDomain<T> { impl<T: RegisterDomain> RegisterDomain for DataDomain<T> {
// Return a new *Top* element with the given bitsize // Return a new *Top* element with the given bytesize
fn new_top(bitsize: BitSize) -> Self { fn new_top(bytesize: ByteSize) -> Self {
Self::Top(bitsize) Self::Top(bytesize)
} }
/// Compute the (abstract) result of a binary operation /// Compute the (abstract) result of a binary operation
...@@ -93,40 +93,58 @@ impl<T: RegisterDomain> RegisterDomain for DataDomain<T> { ...@@ -93,40 +93,58 @@ impl<T: RegisterDomain> RegisterDomain for DataDomain<T> {
use DataDomain::*; use DataDomain::*;
match (self, op, rhs) { match (self, op, rhs) {
(Value(left), _, Value(right)) => Value(left.bin_op(op, right)), (Value(left), _, Value(right)) => Value(left.bin_op(op, right)),
(Pointer(pointer), PLUS, Value(value)) | (Value(value), PLUS, Pointer(pointer)) => { (Pointer(pointer), IntAdd, Value(value)) | (Value(value), IntAdd, Pointer(pointer)) => {
Pointer(pointer.add_to_offset(value)) Pointer(pointer.add_to_offset(value))
} }
(Pointer(pointer), MINUS, Value(value)) => Pointer(pointer.sub_from_offset(value)), (Pointer(pointer), IntSub, Value(value)) => Pointer(pointer.sub_from_offset(value)),
(Pointer(pointer_lhs), MINUS, Pointer(pointer_rhs)) => { (Pointer(pointer_lhs), IntSub, Pointer(pointer_rhs)) => {
if pointer_lhs.ids().len() == 1 && pointer_rhs.ids().len() == 1 { if pointer_lhs.ids().len() == 1 && pointer_rhs.ids().len() == 1 {
let (id_lhs, offset_lhs) = pointer_lhs.targets().iter().next().unwrap(); let (id_lhs, offset_lhs) = pointer_lhs.targets().iter().next().unwrap();
let (id_rhs, offset_rhs) = pointer_rhs.targets().iter().next().unwrap(); let (id_rhs, offset_rhs) = pointer_rhs.targets().iter().next().unwrap();
if id_lhs == id_rhs { if id_lhs == id_rhs {
Self::Value(offset_lhs.bin_op(MINUS, offset_rhs)) Self::Value(offset_lhs.bin_op(IntSub, offset_rhs))
} else { } else {
Self::Top(self.bitsize()) Self::Top(self.bytesize())
} }
} else { } else {
// We cannot be sure that both pointers point to the same target // We cannot be sure that both pointers point to the same target
Self::Top(self.bitsize()) Self::Top(self.bytesize())
} }
} }
(_, EQ, _) | (_, NEQ, _) | (_, LT, _) | (_, LE, _) | (_, SLT, _) | (_, SLE, _) => { (_, IntEqual, _)
T::new_top(1).into() | (_, IntNotEqual, _)
} | (_, IntLess, _)
(_, PLUS, _) | (_, IntLessEqual, _)
| (_, MINUS, _) | (_, IntSLess, _)
| (_, TIMES, _) | (_, IntSLessEqual, _)
| (_, DIVIDE, _) | (_, IntCarry, _)
| (_, SDIVIDE, _) | (_, IntSCarry, _)
| (_, MOD, _) | (_, IntSBorrow, _)
| (_, SMOD, _) | (_, BoolXOr, _)
| (_, LSHIFT, _) | (_, BoolOr, _)
| (_, RSHIFT, _) | (_, BoolAnd, _)
| (_, ARSHIFT, _) | (_, FloatEqual, _)
| (_, AND, _) | (_, FloatNotEqual, _)
| (_, OR, _) | (_, FloatLess, _)
| (_, XOR, _) => Self::new_top(self.bitsize()), | (_, FloatLessEqual, _) => T::new_top(ByteSize::new(1)).into(),
(_, IntAdd, _)
| (_, IntSub, _)
| (_, IntMult, _)
| (_, IntDiv, _)
| (_, IntSDiv, _)
| (_, IntRem, _)
| (_, IntSRem, _)
| (_, IntLeft, _)
| (_, IntRight, _)
| (_, IntSRight, _)
| (_, IntAnd, _)
| (_, IntOr, _)
| (_, IntXOr, _)
| (_, FloatAdd, _)
| (_, FloatSub, _)
| (_, FloatMult, _)
| (_, FloatDiv, _) => Self::new_top(self.bytesize()),
(_, Piece, _) => Self::new_top(self.bytesize() + rhs.bytesize()),
} }
} }
...@@ -135,41 +153,28 @@ impl<T: RegisterDomain> RegisterDomain for DataDomain<T> { ...@@ -135,41 +153,28 @@ impl<T: RegisterDomain> RegisterDomain for DataDomain<T> {
if let Self::Value(value) = self { if let Self::Value(value) = self {
Self::Value(value.un_op(op)) Self::Value(value.un_op(op))
} else { } else {
Self::new_top(self.bitsize()) Self::new_top(self.bytesize())
} }
} }
/// extract a sub-bitvector /// extract a sub-bitvector
fn extract(&self, low_bit: BitSize, high_bit: BitSize) -> Self { fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self {
if let Self::Value(value) = self { if let Self::Value(value) = self {
Self::Value(value.extract(low_bit, high_bit)) Self::Value(value.subpiece(low_byte, size))
} else { } else {
Self::new_top(high_bit - low_bit + 1) Self::new_top(size)
} }
} }
/// Extend or shrink a bitvector using the given cast type /// Cast a bitvector using the given cast type
fn cast(&self, kind: CastType, width: BitSize) -> Self { fn cast(&self, kind: CastOpType, width: ByteSize) -> Self {
if self.bitsize() == width {
// The cast is a no-op.
return self.clone();
}
if let Self::Value(value) = self { if let Self::Value(value) = self {
Self::Value(value.cast(kind, width)) Self::Value(value.cast(kind, width))
} else { } else {
// The result of extending or shrinking pointers is undefined. // The result of casting pointers is undefined.
Self::new_top(width) Self::new_top(width)
} }
} }
/// Concatenate two bitvectors.
fn concat(&self, other: &Self) -> Self {
if let (Self::Value(upper_bits), Self::Value(lower_bits)) = (self, other) {
Self::Value(upper_bits.concat(lower_bits))
} else {
Self::new_top(self.bitsize() + other.bitsize())
}
}
} }
impl<T: RegisterDomain> AbstractDomain for DataDomain<T> { impl<T: RegisterDomain> AbstractDomain for DataDomain<T> {
...@@ -177,10 +182,10 @@ impl<T: RegisterDomain> AbstractDomain for DataDomain<T> { ...@@ -177,10 +182,10 @@ impl<T: RegisterDomain> AbstractDomain for DataDomain<T> {
fn merge(&self, other: &Self) -> Self { fn merge(&self, other: &Self) -> Self {
use DataDomain::*; use DataDomain::*;
match (self, other) { match (self, other) {
(Top(bitsize), _) | (_, Top(bitsize)) => Top(*bitsize), (Top(bytesize), _) | (_, Top(bytesize)) => Top(*bytesize),
(Pointer(pointer1), Pointer(pointer2)) => Pointer(pointer1.merge(pointer2)), (Pointer(pointer1), Pointer(pointer2)) => Pointer(pointer1.merge(pointer2)),
(Value(val1), Value(val2)) => Value(val1.merge(val2)), (Value(val1), Value(val2)) => Value(val1.merge(val2)),
(Pointer(_), Value(_)) | (Value(_), Pointer(_)) => Top(self.bitsize()), (Pointer(_), Value(_)) | (Value(_), Pointer(_)) => Top(self.bytesize()),
} }
} }
...@@ -245,7 +250,7 @@ mod tests { ...@@ -245,7 +250,7 @@ mod tests {
fn new_id(name: &str) -> AbstractIdentifier { fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new( AbstractIdentifier::new(
Tid::new("time0"), Tid::new("time0"),
AbstractLocation::Register(name.into(), 64), AbstractLocation::Register(name.into(), ByteSize::new(8)),
) )
} }
...@@ -267,10 +272,10 @@ mod tests { ...@@ -267,10 +272,10 @@ mod tests {
let pointer = new_pointer("Rax".into(), 0); let pointer = new_pointer("Rax".into(), 0);
let data = new_value(42); let data = new_value(42);
assert_eq!(pointer.merge(&pointer), pointer); assert_eq!(pointer.merge(&pointer), pointer);
assert_eq!(pointer.merge(&data), Data::new_top(64)); assert_eq!(pointer.merge(&data), Data::new_top(ByteSize::new(8)));
assert_eq!( assert_eq!(
data.merge(&new_value(41)), data.merge(&new_value(41)),
Data::Value(BitvectorDomain::new_top(64)) Data::Value(BitvectorDomain::new_top(ByteSize::new(8)))
); );
let other_pointer = new_pointer("Rbx".into(), 0); let other_pointer = new_pointer("Rbx".into(), 0);
...@@ -282,27 +287,30 @@ mod tests { ...@@ -282,27 +287,30 @@ mod tests {
#[test] #[test]
fn data_register_domain() { fn data_register_domain() {
use crate::bil::BinOpType::*; use BinOpType::*;
let data = new_value(42); let data = new_value(42);
assert_eq!(data.bitsize(), 64); assert_eq!(data.bytesize(), ByteSize::new(8));
let three = new_value(3); let three = new_value(3);
let pointer = new_pointer("Rax".into(), 0); let pointer = new_pointer("Rax".into(), 0);
assert_eq!(data.bin_op(PLUS, &three), new_value(45)); assert_eq!(data.bin_op(IntAdd, &three), new_value(45));
assert_eq!(pointer.bin_op(PLUS, &three), new_pointer("Rax".into(), 3)); assert_eq!(pointer.bin_op(IntAdd, &three), new_pointer("Rax".into(), 3));
assert_eq!(three.un_op(crate::bil::UnOpType::NEG), new_value(-3)); assert_eq!(three.un_op(UnOpType::Int2Comp), new_value(-3));
assert_eq!( assert_eq!(
three.extract(0, 31), three.subpiece(ByteSize::new(0), ByteSize::new(4)),
Data::Value(BitvectorDomain::Value(Bitvector::from_i32(3))) Data::Value(BitvectorDomain::Value(Bitvector::from_i32(3)))
); );
assert_eq!(data.cast(crate::bil::CastType::SIGNED, 128).bitsize(), 128); assert_eq!(
data.cast(CastOpType::IntSExt, ByteSize::new(16)).bytesize(),
ByteSize::new(16)
);
let one = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(1))); let one = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(1)));
let two = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(2))); let two = Data::Value(BitvectorDomain::Value(Bitvector::from_i32(2)));
let concat = new_value((1 << 32) + 2); let concat = new_value((1 << 32) + 2);
assert_eq!(one.concat(&two), concat); assert_eq!(one.bin_op(Piece, &two), concat);
} }
#[test] #[test]
......
use crate::bil::variable::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use derive_more::Deref; use derive_more::Deref;
use std::sync::Arc; use std::sync::Arc;
...@@ -57,7 +57,7 @@ impl std::fmt::Display for AbstractIdentifier { ...@@ -57,7 +57,7 @@ impl std::fmt::Display for AbstractIdentifier {
/// It is also impossible to accidently describe circular references. /// It is also impossible to accidently describe circular references.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum AbstractLocation { pub enum AbstractLocation {
Register(String, BitSize), Register(String, ByteSize),
Pointer(String, AbstractMemoryLocation), Pointer(String, AbstractMemoryLocation),
} }
...@@ -81,7 +81,7 @@ impl AbstractLocation { ...@@ -81,7 +81,7 @@ impl AbstractLocation {
} }
Ok(AbstractLocation::Register( Ok(AbstractLocation::Register(
variable.name.clone(), variable.name.clone(),
variable.bitsize()?, variable.size,
)) ))
} }
} }
......
use super::{AbstractDomain, HasBitSize, HasTop, RegisterDomain}; use super::{AbstractDomain, HasByteSize, HasTop, RegisterDomain};
use crate::bil::{BitSize, Bitvector}; use crate::bil::Bitvector;
use crate::intermediate_representation::ByteSize;
use apint::{Int, Width}; use apint::{Int, Width};
use derive_more::Deref; use derive_more::Deref;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
...@@ -20,17 +21,17 @@ use std::sync::Arc; ...@@ -20,17 +21,17 @@ use std::sync::Arc;
/// To allow cheap cloning of a `MemRegion`, the actual data is wrapped inside an `Arc`. /// To allow cheap cloning of a `MemRegion`, the actual data is wrapped inside an `Arc`.
#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq, Deref)] #[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq, Deref)]
#[deref(forward)] #[deref(forward)]
pub struct MemRegion<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug>( pub struct MemRegion<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug>(
Arc<MemRegionData<T>>, Arc<MemRegionData<T>>,
); );
impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> DerefMut for MemRegion<T> { impl<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug> DerefMut for MemRegion<T> {
fn deref_mut(&mut self) -> &mut MemRegionData<T> { fn deref_mut(&mut self) -> &mut MemRegionData<T> {
Arc::make_mut(&mut self.0) Arc::make_mut(&mut self.0)
} }
} }
impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> AbstractDomain impl<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug> AbstractDomain
for MemRegion<T> for MemRegion<T>
{ {
/// Short-circuting the `MemRegionData::merge` function if `self==other`, /// Short-circuting the `MemRegionData::merge` function if `self==other`,
...@@ -49,39 +50,39 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> Abstract ...@@ -49,39 +50,39 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> Abstract
} }
} }
impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> HasTop for MemRegion<T> { impl<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug> HasTop for MemRegion<T> {
/// Return a new, empty memory region with the same address bitsize as `self`, representing the *Top* element of the abstract domain. /// Return a new, empty memory region with the same address bytesize as `self`, representing the *Top* element of the abstract domain.
fn top(&self) -> Self { fn top(&self) -> Self {
Self::new(self.get_address_bitsize()) Self::new(self.get_address_bytesize())
} }
} }
impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegion<T> { impl<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug> MemRegion<T> {
// Create a new, empty memory region. // Create a new, empty memory region.
pub fn new(address_bitsize: BitSize) -> Self { pub fn new(address_bytesize: ByteSize) -> Self {
MemRegion(Arc::new(MemRegionData::new(address_bitsize))) MemRegion(Arc::new(MemRegionData::new(address_bytesize)))
} }
} }
/// The internal data of a memory region. See the description of `MemRegion` for more. /// The internal data of a memory region. See the description of `MemRegion` for more.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct MemRegionData<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> { pub struct MemRegionData<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug> {
address_bitsize: BitSize, address_bytesize: ByteSize,
values: BTreeMap<i64, T>, values: BTreeMap<i64, T>,
} }
impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegionData<T> { impl<T: AbstractDomain + HasByteSize + RegisterDomain + std::fmt::Debug> MemRegionData<T> {
/// create a new, empty MemRegion /// create a new, empty MemRegion
pub fn new(address_bitsize: BitSize) -> MemRegionData<T> { pub fn new(address_bytesize: ByteSize) -> MemRegionData<T> {
MemRegionData { MemRegionData {
address_bitsize, address_bytesize,
values: BTreeMap::new(), values: BTreeMap::new(),
} }
} }
/// Get the bitsize of pointers for the address space that the memory region belongs to. /// Get the bitsize of pointers for the address space that the memory region belongs to.
pub fn get_address_bitsize(&self) -> BitSize { pub fn get_address_bytesize(&self) -> ByteSize {
self.address_bitsize self.address_bytesize
} }
/// Remove all elements intersecting the provided interval. /// Remove all elements intersecting the provided interval.
...@@ -91,7 +92,7 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio ...@@ -91,7 +92,7 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio
if let Some((prev_pos, prev_size)) = self if let Some((prev_pos, prev_size)) = self
.values .values
.range(..position) .range(..position)
.map(|(pos, elem)| (*pos, elem.bitsize() as i64 / 8)) .map(|(pos, elem)| (*pos, u64::from(elem.bytesize()) as i64))
.last() .last()
{ {
if prev_pos + prev_size > position { if prev_pos + prev_size > position {
...@@ -111,10 +112,9 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio ...@@ -111,10 +112,9 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio
/// Add a value to the memory region. /// Add a value to the memory region.
pub fn add(&mut self, value: T, position: Bitvector) { pub fn add(&mut self, value: T, position: Bitvector) {
assert_eq!(position.width().to_usize(), self.address_bitsize as usize); assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
let position = Int::from(position).try_to_i64().unwrap(); let position = Int::from(position).try_to_i64().unwrap();
assert!(value.bitsize() % 8 == 0); let size_in_bytes = u64::from(value.bytesize()) as i64;
let size_in_bytes = value.bitsize() as i64 / 8;
assert!(size_in_bytes > 0); assert!(size_in_bytes > 0);
self.clear_interval(position, size_in_bytes); self.clear_interval(position, size_in_bytes);
...@@ -126,24 +126,21 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio ...@@ -126,24 +126,21 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio
/// Get the value at the given position. /// Get the value at the given position.
/// If there is no value at the position or the size of the element is not the same as the provided size, return `T::new_top()`. /// If there is no value at the position or the size of the element is not the same as the provided size, return `T::new_top()`.
pub fn get(&self, position: Bitvector, size_in_bytes: u64) -> T { pub fn get(&self, position: Bitvector, size_in_bytes: ByteSize) -> T {
assert_eq!(position.width().to_usize(), self.address_bitsize as usize); assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
let position = Int::from(position).try_to_i64().unwrap(); let position = Int::from(position).try_to_i64().unwrap();
let size = size_in_bytes as i64;
assert!(size > 0);
if let Some(elem) = self.values.get(&position) { if let Some(elem) = self.values.get(&position) {
if (elem.bitsize() as i64) == (size * 8) { if elem.bytesize() == size_in_bytes {
return elem.clone(); return elem.clone();
} }
} }
let bitsize = 8 * size as u16; T::new_top(size_in_bytes)
T::new_top(bitsize)
} }
/// Remove all elements intersecting the provided interval. /// Remove all elements intersecting the provided interval.
pub fn remove(&mut self, position: Bitvector, size_in_bytes: Bitvector) { pub fn remove(&mut self, position: Bitvector, size_in_bytes: Bitvector) {
assert_eq!(position.width().to_usize(), self.address_bitsize as usize); assert_eq!(ByteSize::from(position.width()), self.address_bytesize);
let position = Int::from(position).try_to_i64().unwrap(); let position = Int::from(position).try_to_i64().unwrap();
let size = Int::from(size_in_bytes).try_to_i64().unwrap(); let size = Int::from(size_in_bytes).try_to_i64().unwrap();
assert!(size > 0); assert!(size > 0);
...@@ -156,14 +153,14 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio ...@@ -156,14 +153,14 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio
/// Values at the same position and with the same size get merged via their merge function. /// Values at the same position and with the same size get merged via their merge function.
/// Other values are *not* added to the merged region, because they could be anything in at least one of the two regions. /// Other values are *not* added to the merged region, because they could be anything in at least one of the two regions.
pub fn merge(&self, other: &MemRegionData<T>) -> MemRegionData<T> { pub fn merge(&self, other: &MemRegionData<T>) -> MemRegionData<T> {
assert_eq!(self.address_bitsize, other.address_bitsize); assert_eq!(self.address_bytesize, other.address_bytesize);
let mut merged_values: BTreeMap<i64, T> = BTreeMap::new(); let mut merged_values: BTreeMap<i64, T> = BTreeMap::new();
// add all elements contained in both memory regions // add all elements contained in both memory regions
for (pos_left, elem_left) in self.values.iter() { for (pos_left, elem_left) in self.values.iter() {
if let Some((_pos_right, elem_right)) = other.values.get_key_value(pos_left) { if let Some((_pos_right, elem_right)) = other.values.get_key_value(pos_left) {
if elem_left.bitsize() == elem_right.bitsize() { if elem_left.bytesize() == elem_right.bytesize() {
let merged_val = elem_left.merge(&elem_right); let merged_val = elem_left.merge(&elem_right);
if !merged_val.is_top() { if !merged_val.is_top() {
// we discard top()-values, as they don't contain information // we discard top()-values, as they don't contain information
...@@ -174,7 +171,7 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio ...@@ -174,7 +171,7 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio
} }
MemRegionData { MemRegionData {
address_bitsize: self.address_bitsize, address_bytesize: self.address_bytesize,
values: merged_values, values: merged_values,
} }
} }
...@@ -221,9 +218,11 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio ...@@ -221,9 +218,11 @@ impl<T: AbstractDomain + HasBitSize + RegisterDomain + std::fmt::Debug> MemRegio
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::bil::Bitvector;
use crate::intermediate_representation::*;
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, PartialOrd, Ord)] #[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, PartialOrd, Ord)]
struct MockDomain(i64, BitSize); struct MockDomain(i64, ByteSize);
impl AbstractDomain for MockDomain { impl AbstractDomain for MockDomain {
fn merge(&self, other: &Self) -> Self { fn merge(&self, other: &Self) -> Self {
...@@ -240,8 +239,8 @@ mod tests { ...@@ -240,8 +239,8 @@ mod tests {
} }
} }
impl HasBitSize for MockDomain { impl HasByteSize for MockDomain {
fn bitsize(&self) -> BitSize { fn bytesize(&self) -> ByteSize {
self.1 self.1
} }
} }
...@@ -253,33 +252,29 @@ mod tests { ...@@ -253,33 +252,29 @@ mod tests {
} }
impl RegisterDomain for MockDomain { impl RegisterDomain for MockDomain {
fn new_top(bitsize: BitSize) -> MockDomain { fn new_top(bytesize: ByteSize) -> MockDomain {
MockDomain(0, bitsize) MockDomain(0, bytesize)
} }
fn bin_op(&self, _op: crate::bil::BinOpType, _rhs: &Self) -> Self { fn bin_op(&self, _op: BinOpType, _rhs: &Self) -> Self {
Self::new_top(self.1) Self::new_top(self.1)
} }
fn un_op(&self, _op: crate::bil::UnOpType) -> Self { fn un_op(&self, _op: UnOpType) -> Self {
Self::new_top(self.1) Self::new_top(self.1)
} }
fn cast(&self, _kind: crate::bil::CastType, width: BitSize) -> Self { fn cast(&self, _kind: CastOpType, width: ByteSize) -> Self {
Self::new_top(width) Self::new_top(width)
} }
fn extract(&self, low_bit: BitSize, high_bit: BitSize) -> Self { fn subpiece(&self, _low_byte: ByteSize, size: ByteSize) -> Self {
Self::new_top(high_bit - low_bit + 1) Self::new_top(size)
}
fn concat(&self, other: &Self) -> Self {
Self::new_top(self.bitsize() + other.bitsize())
} }
} }
fn mock(val: i64, bitsize: BitSize) -> MockDomain { fn mock(val: i64, bytesize: impl Into<ByteSize>) -> MockDomain {
MockDomain(val, bitsize) MockDomain(val, bytesize.into())
} }
fn bv(val: i64) -> Bitvector { fn bv(val: i64) -> Bitvector {
...@@ -288,41 +283,56 @@ mod tests { ...@@ -288,41 +283,56 @@ mod tests {
#[test] #[test]
fn mem_region() { fn mem_region() {
let mut region: MemRegion<MockDomain> = MemRegion::new(64); let mut region: MemRegion<MockDomain> = MemRegion::new(ByteSize::from(8u64));
region.add(mock(5, 3 * 8), bv(5)); region.add(mock(5, 3u64), bv(5));
assert_eq!(region.get(bv(5), 3), mock(5, 3 * 8)); assert_eq!(region.get(bv(5), ByteSize::from(3u64)), mock(5, 3u64));
region.add(mock(7, 2 * 8), bv(8)); region.add(mock(7, 2u64), bv(8));
assert_eq!(region.get(bv(8), 2), mock(7, 2 * 8)); assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
assert_eq!(region.get(bv(5), 3), mock(5, 3 * 8)); assert_eq!(region.get(bv(5), ByteSize::from(3u64)), mock(5, 3u64));
assert_eq!(region.get(bv(5), 2), MockDomain::new_top(2 * 8)); assert_eq!(
region.add(mock(9, 2 * 8), bv(6)); region.get(bv(5), ByteSize::from(2u64)),
assert_eq!(region.get(bv(6), 2), mock(9, 2 * 8)); MockDomain::new_top(ByteSize::new(2))
assert_eq!(region.get(bv(5), 3), MockDomain::new_top(3 * 8)); );
assert_eq!(region.get(bv(8), 2), mock(7, 2 * 8)); region.add(mock(9, 2u64), bv(6));
region.add(mock(9, 11 * 8), bv(-3)); assert_eq!(region.get(bv(6), ByteSize::from(2u64)), mock(9, 2u64));
assert_eq!(region.get(bv(-3), 11), mock(9, 11 * 8)); assert_eq!(
assert_eq!(region.get(bv(6), 2), MockDomain::new_top(2 * 8)); region.get(bv(5), ByteSize::from(3u64)),
assert_eq!(region.get(bv(8), 2), mock(7, 2 * 8)); MockDomain::new_top(ByteSize::new(3))
);
let mut other_region = MemRegion::new(64); assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
other_region.add(mock(7, 2 * 8), bv(8)); region.add(mock(9, 11u64), bv(-3));
assert_eq!(region.get(bv(-3), ByteSize::from(11u64)), mock(9, 11u64));
assert_eq!(
region.get(bv(6), ByteSize::from(2u64)),
MockDomain::new_top(ByteSize::new(2))
);
assert_eq!(region.get(bv(8), ByteSize::from(2u64)), mock(7, 2u64));
let mut other_region = MemRegion::new(ByteSize::from(8u64));
other_region.add(mock(7, 2u64), bv(8));
assert!(region != other_region); assert!(region != other_region);
let merged_region = region.merge(&other_region); let merged_region = region.merge(&other_region);
assert_eq!(merged_region.get(bv(8), 2), mock(7, 2 * 8)); assert_eq!(
assert_eq!(merged_region.get(bv(-3), 11), MockDomain::new_top(11 * 8)); merged_region.get(bv(8), ByteSize::from(2u64)),
other_region.add(mock(9, 11 * 8), bv(-3)); mock(7, 2u64)
);
assert_eq!(
merged_region.get(bv(-3), ByteSize::from(11u64)),
MockDomain::new_top(ByteSize::from(11u64))
);
other_region.add(mock(9, 11u64), bv(-3));
assert_eq!(region, other_region); assert_eq!(region, other_region);
} }
#[test] #[test]
fn do_not_save_top_elements() { fn do_not_save_top_elements() {
let mut region: MemRegionData<MockDomain> = MemRegionData::new(64); let mut region: MemRegionData<MockDomain> = MemRegionData::new(ByteSize::from(8u64));
region.add(MockDomain::new_top(4 * 8), bv(5)); region.add(MockDomain::new_top(ByteSize::from(4u64)), bv(5));
assert_eq!(region.values.len(), 0); assert_eq!(region.values.len(), 0);
let mut other_region: MemRegionData<MockDomain> = MemRegionData::new(64); let mut other_region: MemRegionData<MockDomain> = MemRegionData::new(ByteSize::from(8u64));
region.add(mock(5, 4 * 8), bv(5)); region.add(mock(5, 4u64), bv(5));
other_region.add(mock(7, 4 * 8), bv(5)); other_region.add(mock(7, 4u64), bv(5));
let merged_region = region.merge(&other_region); let merged_region = region.merge(&other_region);
assert_eq!(region.values.len(), 1); assert_eq!(region.values.len(), 1);
assert_eq!(other_region.values.len(), 1); assert_eq!(other_region.values.len(), 1);
...@@ -331,12 +341,12 @@ mod tests { ...@@ -331,12 +341,12 @@ mod tests {
#[test] #[test]
fn value_removals() { fn value_removals() {
let mut region: MemRegionData<MockDomain> = MemRegionData::new(64); let mut region: MemRegionData<MockDomain> = MemRegionData::new(ByteSize::from(8u64));
region.add(mock(1, 64), bv(0)); region.add(mock(1, 8u64), bv(0));
region.add(mock(2, 64), bv(8)); region.add(mock(2, 8u64), bv(8));
region.add(mock(3, 64), bv(16)); region.add(mock(3, 8u64), bv(16));
region.add(mock(4, 64), bv(24)); region.add(mock(4, 8u64), bv(24));
region.add(mock(5, 64), bv(32)); region.add(mock(5, 8u64), bv(32));
assert_eq!(region.values.len(), 5); assert_eq!(region.values.len(), 5);
region.remove(bv(2), bv(3)); region.remove(bv(2), bv(3));
...@@ -352,12 +362,12 @@ mod tests { ...@@ -352,12 +362,12 @@ mod tests {
assert_eq!(region.values.len(), 2); assert_eq!(region.values.len(), 2);
for val in region.values_mut() { for val in region.values_mut() {
if *val == mock(5, 64) { if *val == mock(5, 8u64) {
*val = mock(0, 64); // This is a *Top* element *val = mock(0, 8u64); // This is a *Top* element
} }
} }
region.clear_top_values(); region.clear_top_values();
assert_eq!(region.values.len(), 1); assert_eq!(region.values.len(), 1);
assert_eq!(region.get(bv(24), 8), mock(4, 64)); assert_eq!(region.get(bv(24), ByteSize::from(8u64)), mock(4, 8u64));
} }
} }
//! This module defines traits describing general properties of abstract domains //! This module defines traits describing general properties of abstract domains
//! as well as several abstract domain types implementing these traits. //! as well as several abstract domain types implementing these traits.
use crate::bil::*; use crate::intermediate_representation::*;
mod bitvector; mod bitvector;
pub use bitvector::*; pub use bitvector::*;
...@@ -29,14 +29,14 @@ pub trait AbstractDomain: Sized + Eq + Clone { ...@@ -29,14 +29,14 @@ pub trait AbstractDomain: Sized + Eq + Clone {
fn is_top(&self) -> bool; fn is_top(&self) -> bool;
} }
/// A trait for types representing values with a fixed size (in bits). /// A trait for types representing values with a fixed size (in bytes).
/// ///
/// For abstract domains, the bitsize is a parameter of the domain itself, /// For abstract domains, the bytesize is a parameter of the domain itself,
/// i.e. you cannot merge values of different bitsizes, /// i.e. you cannot merge values of different bytesizes,
/// since they lie in different posets (one for each bitsize). /// since they lie in different posets (one for each bytesize).
pub trait HasBitSize { pub trait HasByteSize {
/// Return the size of the represented value in bits. /// Return the size of the represented value in bytes.
fn bitsize(&self) -> BitSize; fn bytesize(&self) -> ByteSize;
} }
/// An abstract domain implementing this trait has a global maximum, i.e. a *Top* element. /// An abstract domain implementing this trait has a global maximum, i.e. a *Top* element.
...@@ -52,11 +52,11 @@ pub trait HasTop { ...@@ -52,11 +52,11 @@ pub trait HasTop {
/// A trait for abstract domains that can represent values loaded into CPU register. /// A trait for abstract domains that can represent values loaded into CPU register.
/// ///
/// The domain implements all general operations used to manipulate register values. /// The domain implements all general operations used to manipulate register values.
/// The domain is parametrized by its bitsize (which represents the size of the register). /// The domain is parametrized by its bytesize (which represents the size of the register).
/// It has a *Top* element, which is only characterized by its bitsize. /// It has a *Top* element, which is only characterized by its bytesize.
pub trait RegisterDomain: AbstractDomain + HasBitSize + HasTop { pub trait RegisterDomain: AbstractDomain + HasByteSize + HasTop {
/// Return a new top element with the given bitsize /// Return a new top element with the given bytesize
fn new_top(bitsize: BitSize) -> Self; fn new_top(bytesize: ByteSize) -> Self;
/// Compute the (abstract) result of a binary operation /// Compute the (abstract) result of a binary operation
fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self; fn bin_op(&self, op: BinOpType, rhs: &Self) -> Self;
...@@ -64,12 +64,9 @@ pub trait RegisterDomain: AbstractDomain + HasBitSize + HasTop { ...@@ -64,12 +64,9 @@ pub trait RegisterDomain: AbstractDomain + HasBitSize + HasTop {
/// Compute the (abstract) result of a unary operation /// Compute the (abstract) result of a unary operation
fn un_op(&self, op: UnOpType) -> Self; fn un_op(&self, op: UnOpType) -> Self;
/// extract a sub-bitvector /// Extract a sub-bitvector
fn extract(&self, low_bit: BitSize, high_bit: BitSize) -> Self; fn subpiece(&self, low_byte: ByteSize, size: ByteSize) -> Self;
/// Extend a bitvector using the given cast type /// Perform a typecast to extend a bitvector or to cast between integer and floating point types.
fn cast(&self, kind: CastType, width: BitSize) -> Self; fn cast(&self, kind: CastOpType, width: ByteSize) -> Self;
/// Concatenate two bitvectors
fn concat(&self, other: &Self) -> Self;
} }
use super::{AbstractDomain, AbstractIdentifier, HasBitSize, RegisterDomain}; use super::{AbstractDomain, AbstractIdentifier, HasByteSize, RegisterDomain};
use crate::bil::BinOpType; use crate::intermediate_representation::{BinOpType, ByteSize};
use crate::prelude::*; use crate::prelude::*;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::Display; use std::fmt::Display;
...@@ -38,15 +38,15 @@ impl<T: RegisterDomain> AbstractDomain for PointerDomain<T> { ...@@ -38,15 +38,15 @@ impl<T: RegisterDomain> AbstractDomain for PointerDomain<T> {
} }
} }
impl<T: RegisterDomain> HasBitSize for PointerDomain<T> { impl<T: RegisterDomain> HasByteSize for PointerDomain<T> {
/// Return the bitsize of the pointer. /// Return the bitsize of the pointer.
/// Should always equal the pointer size of the CPU architecture. /// Should always equal the pointer size of the CPU architecture.
fn bitsize(&self) -> BitSize { fn bytesize(&self) -> ByteSize {
self.0 self.0
.values() .values()
.next() .next()
.expect("Pointer without targets encountered") .expect("Pointer without targets encountered")
.bitsize() .bytesize()
} }
} }
...@@ -84,7 +84,7 @@ impl<T: RegisterDomain> PointerDomain<T> { ...@@ -84,7 +84,7 @@ impl<T: RegisterDomain> PointerDomain<T> {
offset_adjustment: &T, offset_adjustment: &T,
) { ) {
if let Some(old_offset) = self.0.get(&old_id) { if let Some(old_offset) = self.0.get(&old_id) {
let new_offset = old_offset.bin_op(BinOpType::PLUS, offset_adjustment); let new_offset = old_offset.bin_op(BinOpType::IntAdd, offset_adjustment);
self.0.remove(old_id); self.0.remove(old_id);
self.0.insert(new_id.clone(), new_offset); self.0.insert(new_id.clone(), new_offset);
} }
...@@ -94,7 +94,7 @@ impl<T: RegisterDomain> PointerDomain<T> { ...@@ -94,7 +94,7 @@ impl<T: RegisterDomain> PointerDomain<T> {
pub fn add_to_offset(&self, value: &T) -> PointerDomain<T> { pub fn add_to_offset(&self, value: &T) -> PointerDomain<T> {
let mut result = self.clone(); let mut result = self.clone();
for offset in result.0.values_mut() { for offset in result.0.values_mut() {
*offset = offset.bin_op(BinOpType::PLUS, value); *offset = offset.bin_op(BinOpType::IntAdd, value);
} }
result result
} }
...@@ -103,7 +103,7 @@ impl<T: RegisterDomain> PointerDomain<T> { ...@@ -103,7 +103,7 @@ impl<T: RegisterDomain> PointerDomain<T> {
pub fn sub_from_offset(&self, value: &T) -> PointerDomain<T> { pub fn sub_from_offset(&self, value: &T) -> PointerDomain<T> {
let mut result = self.clone(); let mut result = self.clone();
for offset in result.0.values_mut() { for offset in result.0.values_mut() {
*offset = offset.bin_op(BinOpType::MINUS, value); *offset = offset.bin_op(BinOpType::IntSub, value);
} }
result result
} }
...@@ -149,7 +149,7 @@ mod tests { ...@@ -149,7 +149,7 @@ mod tests {
fn new_id(name: &str) -> AbstractIdentifier { fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new( AbstractIdentifier::new(
Tid::new("time0"), Tid::new("time0"),
AbstractLocation::Register(name.into(), 64), AbstractLocation::Register(name.into(), ByteSize::new(8)),
) )
} }
......
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
//! The artificial *CallReturn* nodes enable enriching the information flowing through a return edge //! The artificial *CallReturn* nodes enable enriching the information flowing through a return edge
//! with information recovered from the corresponding callsite during a fixpoint computation. //! with information recovered from the corresponding callsite during a fixpoint computation.
use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::term::*;
use petgraph::graph::{DiGraph, NodeIndex}; use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
...@@ -166,19 +166,22 @@ impl<'a> GraphBuilder<'a> { ...@@ -166,19 +166,22 @@ impl<'a> GraphBuilder<'a> {
jump: &'a Term<Jmp>, jump: &'a Term<Jmp>,
untaken_conditional: Option<&'a Term<Jmp>>, untaken_conditional: Option<&'a Term<Jmp>>,
) { ) {
match &jump.term.kind { match &jump.term {
JmpKind::Goto(Label::Direct(tid)) => { Jmp::Branch(tid)
| Jmp::CBranch {
target: tid,
condition: _,
} => {
self.graph.add_edge( self.graph.add_edge(
source, source,
self.jump_targets[&tid].0, self.jump_targets[&tid].0,
Edge::Jump(jump, untaken_conditional), Edge::Jump(jump, untaken_conditional),
); );
} }
JmpKind::Goto(Label::Indirect(_)) => (), // TODO: add handling of indirect edges! Jmp::BranchInd(_) => (), // TODO: add handling of indirect edges!
JmpKind::Call(ref call) => { Jmp::Call { target, return_ } => {
if let Label::Direct(ref target_tid) = call.target { if self.extern_subs.contains(target) {
if self.extern_subs.contains(target_tid) { if let Some(return_tid) = return_ {
if let Some(Label::Direct(ref return_tid)) = call.return_ {
self.graph.add_edge( self.graph.add_edge(
source, source,
self.jump_targets[&return_tid].0, self.jump_targets[&return_tid].0,
...@@ -186,27 +189,32 @@ impl<'a> GraphBuilder<'a> { ...@@ -186,27 +189,32 @@ impl<'a> GraphBuilder<'a> {
); );
} }
} else { } else {
if let Some(target) = self.jump_targets.get(&target_tid) { if let Some(target_node) = self.jump_targets.get(&target) {
self.graph.add_edge(source, target.0, Edge::Call(jump)); self.graph.add_edge(source, target_node.0, Edge::Call(jump));
} } // TODO: Log message for the else-case?
if let Some(ref return_tid) = return_ {
if let Some(Label::Direct(ref return_tid)) = call.return_ {
let return_index = self.jump_targets[return_tid].0; let return_index = self.jump_targets[return_tid].0;
self.return_addresses self.return_addresses
.entry(target_tid.clone()) .entry(target.clone())
.and_modify(|vec| vec.push((source, return_index))) .and_modify(|vec| vec.push((source, return_index)))
.or_insert_with(|| vec![(source, return_index)]); .or_insert_with(|| vec![(source, return_index)]);
} }
// TODO: Non-returning calls and tail calls both have no return target in BAP.
// Thus we need to distinguish them somehow to correctly handle tail calls.
} }
} }
Jmp::CallInd {
target: _,
return_: _,
} => {
// TODO: add handling of indirect calls!
}
Jmp::CallOther {
description: _,
return_: _,
} => {
// TODO: Decide how to represent CallOther edges.
// Right now they are dead ends in the control flow graph.
} }
JmpKind::Interrupt { Jmp::Return(_) => {} // return edges are handled in a different function
value: _,
return_addr: _,
} => (), // TODO: Add some handling for interrupts
JmpKind::Return(_) => {} // return edges are handled in a different function
} }
} }
...@@ -244,7 +252,7 @@ impl<'a> GraphBuilder<'a> { ...@@ -244,7 +252,7 @@ impl<'a> GraphBuilder<'a> {
.term .term
.jmps .jmps
.iter() .iter()
.find(|jump| matches!(jump.term.kind, JmpKind::Call(_))) .find(|jump| matches!(jump.term, Jmp::Call{..}))
.unwrap(); .unwrap();
let cr_combine_node = self.graph.add_node(Node::CallReturn { let cr_combine_node = self.graph.add_node(Node::CallReturn {
call: call_block, call: call_block,
...@@ -267,7 +275,7 @@ impl<'a> GraphBuilder<'a> { ...@@ -267,7 +275,7 @@ impl<'a> GraphBuilder<'a> {
.term .term
.jmps .jmps
.iter() .iter()
.any(|jmp| matches!(jmp.term.kind, JmpKind::Return(_))) .any(|jmp| matches!(jmp.term, Jmp::Return(_)))
{ {
let return_from_node = self.jump_targets[&block.tid].1; let return_from_node = self.jump_targets[&block.tid].1;
self.add_call_return_node_and_edges(sub, return_from_node); self.add_call_return_node_and_edges(sub, return_from_node);
...@@ -326,29 +334,18 @@ mod tests { ...@@ -326,29 +334,18 @@ mod tests {
use super::*; use super::*;
fn mock_program() -> Term<Program> { fn mock_program() -> Term<Program> {
use Label::*;
let call = Call {
target: Direct(Tid::new("sub2")),
return_: Some(Direct(Tid::new("sub1_blk2"))),
};
let call_term = Term { let call_term = Term {
tid: Tid::new("call".to_string()), tid: Tid::new("call".to_string()),
term: Jmp { term: Jmp::Call {
condition: None, target: Tid::new("sub2"),
kind: JmpKind::Call(call), return_: Some(Tid::new("sub1_blk2")),
}, },
}; };
let return_term = Term { let return_term = Term {
tid: Tid::new("return".to_string()), tid: Tid::new("return".to_string()),
term: Jmp { term: Jmp::Return(Expression::Const(Bitvector::zero(64.into()))), // The return term does not matter
condition: None,
kind: JmpKind::Return(Direct(Tid::new("sub1_blk2"))),
},
};
let jmp = Jmp {
condition: None,
kind: JmpKind::Goto(Direct(Tid::new("sub1_blk1"))),
}; };
let jmp = Jmp::Branch(Tid::new("sub1_blk1"));
let jmp_term = Term { let jmp_term = Term {
tid: Tid::new("jump"), tid: Tid::new("jump"),
term: jmp, term: jmp,
......
...@@ -13,9 +13,8 @@ ...@@ -13,9 +13,8 @@
use super::fixpoint::Context as GeneralFPContext; use super::fixpoint::Context as GeneralFPContext;
use super::graph::*; use super::graph::*;
use crate::bil::Expression; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::term::*;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use petgraph::graph::{EdgeIndex, NodeIndex}; use petgraph::graph::{EdgeIndex, NodeIndex};
use std::marker::PhantomData; use std::marker::PhantomData;
......
use super::object::ObjectType; use super::object::ObjectType;
use crate::abstract_domain::*; use crate::abstract_domain::*;
use crate::analysis::graph::Graph; use crate::analysis::graph::Graph;
use crate::bil::Expression; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::term::symbol::ExternSymbol;
use crate::term::*;
use crate::utils::log::*; use crate::utils::log::*;
use std::collections::{BTreeMap, BTreeSet, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashSet};
...@@ -84,10 +82,10 @@ impl<'a> Context<'a> { ...@@ -84,10 +82,10 @@ impl<'a> Context<'a> {
return_term: &Term<Jmp>, return_term: &Term<Jmp>,
) { ) {
let expected_stack_pointer_offset = match self.project.cpu_architecture.as_str() { let expected_stack_pointer_offset = match self.project.cpu_architecture.as_str() {
"x86" | "x86_64" => Bitvector::from_u16(self.project.get_pointer_bitsize() / 8) "x86" | "x86_64" => Bitvector::from_u64(u64::from(self.project.get_pointer_bytesize()))
.into_zero_extend(self.project.get_pointer_bitsize() as usize) .into_truncate(apint::BitWidth::from(self.project.get_pointer_bytesize()))
.unwrap(), .unwrap(),
_ => Bitvector::zero((self.project.get_pointer_bitsize() as usize).into()), _ => Bitvector::zero(apint::BitWidth::from(self.project.get_pointer_bytesize())),
}; };
match state_before_return.get_register(&self.project.stack_pointer_register) { match state_before_return.get_register(&self.project.stack_pointer_register) {
Ok(Data::Pointer(pointer)) => { Ok(Data::Pointer(pointer)) => {
...@@ -134,21 +132,18 @@ impl<'a> Context<'a> { ...@@ -134,21 +132,18 @@ impl<'a> Context<'a> {
call.tid.clone(), call.tid.clone(),
AbstractLocation::from_var(return_register).unwrap(), AbstractLocation::from_var(return_register).unwrap(),
); );
let address_bitsize = self.project.stack_pointer_register.bitsize().unwrap(); let address_bytesize = self.project.get_pointer_bytesize();
state.memory.add_abstract_object( state.memory.add_abstract_object(
object_id.clone(), object_id.clone(),
Bitvector::zero((address_bitsize as usize).into()).into(), Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
super::object::ObjectType::Heap, super::object::ObjectType::Heap,
address_bitsize, address_bytesize,
); );
let pointer = PointerDomain::new( let pointer = PointerDomain::new(
object_id, object_id,
Bitvector::zero((address_bitsize as usize).into()).into(), Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
);
self.log_debug(
state.set_register(return_register, pointer.into()),
Some(&call.tid),
); );
state.set_register(return_register, pointer.into());
Some(state) Some(state)
} }
Err(err) => { Err(err) => {
...@@ -159,6 +154,27 @@ impl<'a> Context<'a> { ...@@ -159,6 +154,27 @@ impl<'a> Context<'a> {
} }
} }
/// Evaluate the value of a parameter of an extern symbol for the given state.
fn eval_parameter_arg(&self, state: &State, parameter: &Arg) -> Result<Data, Error> {
match parameter {
Arg::Register(var) => state.eval(&Expression::Var(var.clone())),
Arg::Stack { offset, size } => state.load_value(
&Expression::BinOp {
op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(self.project.stack_pointer_register.clone())),
rhs: Box::new(Expression::Const(
Bitvector::from_i64(*offset)
.into_truncate(apint::BitWidth::from(
self.project.get_pointer_bytesize(),
))
.unwrap(),
)),
},
*size,
),
}
}
/// Mark the object that the parameter of a call is pointing to as freed. /// Mark the object that the parameter of a call is pointing to as freed.
/// If the object may have been already freed, generate a CWE warning. /// If the object may have been already freed, generate a CWE warning.
/// This models the behaviour of `free` and similar functions. /// This models the behaviour of `free` and similar functions.
...@@ -170,7 +186,9 @@ impl<'a> Context<'a> { ...@@ -170,7 +186,9 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
) -> Option<State> { ) -> Option<State> {
match extern_symbol.get_unique_parameter() { match extern_symbol.get_unique_parameter() {
Ok(parameter_expression) => match state.eval(parameter_expression) { Ok(parameter) => {
let parameter_value = self.eval_parameter_arg(state, parameter);
match parameter_value {
Ok(memory_object_pointer) => { Ok(memory_object_pointer) => {
if let Data::Pointer(pointer) = memory_object_pointer { if let Data::Pointer(pointer) = memory_object_pointer {
if let Err(possible_double_frees) = if let Err(possible_double_frees) =
...@@ -206,7 +224,8 @@ impl<'a> Context<'a> { ...@@ -206,7 +224,8 @@ impl<'a> Context<'a> {
self.log_debug(Err(err), Some(&call.tid)); self.log_debug(Err(err), Some(&call.tid));
Some(new_state) Some(new_state)
} }
}, }
}
Err(err) => { Err(err) => {
// We do not know which memory object to free // We do not know which memory object to free
self.log_debug(Err(err), Some(&call.tid)); self.log_debug(Err(err), Some(&call.tid));
...@@ -222,12 +241,8 @@ impl<'a> Context<'a> { ...@@ -222,12 +241,8 @@ impl<'a> Context<'a> {
call: &Term<Jmp>, call: &Term<Jmp>,
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
) { ) {
for argument in extern_symbol for parameter in extern_symbol.parameters.iter() {
.arguments match self.eval_parameter_arg(state, parameter) {
.iter()
.filter(|arg| arg.intent.is_input())
{
match state.eval(&argument.location) {
Ok(value) => { Ok(value) => {
if state.memory.is_dangling_pointer(&value, true) { if state.memory.is_dangling_pointer(&value, true) {
let warning = CweWarning { let warning = CweWarning {
...@@ -247,8 +262,8 @@ impl<'a> Context<'a> { ...@@ -247,8 +262,8 @@ impl<'a> Context<'a> {
} }
Err(err) => self.log_debug( Err(err) => self.log_debug(
Err(err.context(format!( Err(err.context(format!(
"Function argument expression {:?} could not be evaluated", "Function parameter {:?} could not be evaluated",
argument.location parameter
))), ))),
Some(&call.tid), Some(&call.tid),
), ),
...@@ -266,11 +281,11 @@ impl<'a> Context<'a> { ...@@ -266,11 +281,11 @@ impl<'a> Context<'a> {
extern_symbol: &ExternSymbol, extern_symbol: &ExternSymbol,
) -> Option<State> { ) -> Option<State> {
self.log_debug( self.log_debug(
new_state.clear_stack_parameter(extern_symbol), new_state.clear_stack_parameter(extern_symbol, &self.project.stack_pointer_register),
Some(&call.tid), Some(&call.tid),
); );
let mut possible_referenced_ids = BTreeSet::new(); let mut possible_referenced_ids = BTreeSet::new();
if extern_symbol.arguments.is_empty() { if extern_symbol.parameters.is_empty() && extern_symbol.return_values.is_empty() {
// We assume here that we do not know the parameters and approximate them by all possible parameter registers. // We assume here that we do not know the parameters and approximate them by all possible parameter registers.
// This approximation is wrong if the function is known but has neither parameters nor return values. // This approximation is wrong if the function is known but has neither parameters nor return values.
// We cannot distinguish these two cases yet. // We cannot distinguish these two cases yet.
...@@ -280,12 +295,8 @@ impl<'a> Context<'a> { ...@@ -280,12 +295,8 @@ impl<'a> Context<'a> {
} }
} }
} else { } else {
for parameter in extern_symbol for parameter in extern_symbol.parameters.iter() {
.arguments if let Ok(data) = self.eval_parameter_arg(state, parameter) {
.iter()
.filter(|arg| arg.intent.is_input())
{
if let Ok(data) = state.eval(&parameter.location) {
possible_referenced_ids.append(&mut data.referenced_ids()); possible_referenced_ids.append(&mut data.referenced_ids());
} }
} }
...@@ -312,13 +323,13 @@ impl<'a> Context<'a> { ...@@ -312,13 +323,13 @@ impl<'a> Context<'a> {
if *stack_id == state.stack_id { if *stack_id == state.stack_id {
stack_offset_domain.clone() stack_offset_domain.clone()
} else { } else {
BitvectorDomain::new_top(stack_pointer.bitsize()) BitvectorDomain::new_top(stack_pointer.bytesize())
} }
} else { } else {
BitvectorDomain::new_top(self.project.stack_pointer_register.bitsize().unwrap()) BitvectorDomain::new_top(self.project.stack_pointer_register.size)
} }
} else { } else {
BitvectorDomain::new_top(self.project.stack_pointer_register.bitsize().unwrap()) BitvectorDomain::new_top(self.project.stack_pointer_register.size)
} }
} }
} }
......
use super::*; use super::*;
use crate::bil::variable::*;
fn bv(value: i64) -> BitvectorDomain { fn bv(value: i64) -> BitvectorDomain {
BitvectorDomain::Value(Bitvector::from_i64(value)) BitvectorDomain::Value(Bitvector::from_i64(value))
...@@ -8,59 +7,51 @@ fn bv(value: i64) -> BitvectorDomain { ...@@ -8,59 +7,51 @@ fn bv(value: i64) -> BitvectorDomain {
fn new_id(time: &str, reg_name: &str) -> AbstractIdentifier { fn new_id(time: &str, reg_name: &str) -> AbstractIdentifier {
AbstractIdentifier::new( AbstractIdentifier::new(
Tid::new(time), Tid::new(time),
AbstractLocation::Register(reg_name.to_string(), 64), AbstractLocation::Register(reg_name.to_string(), ByteSize::new(8)),
) )
} }
fn mock_extern_symbol(name: &str) -> ExternSymbol { fn mock_extern_symbol(name: &str) -> ExternSymbol {
use crate::bil; let arg = Arg::Register(register("RAX"));
let arg = Arg {
var: register("RAX"),
location: bil::Expression::Var(register("RAX")),
intent: ArgIntent::Both,
};
ExternSymbol { ExternSymbol {
tid: Tid::new("extern_".to_string() + name), tid: Tid::new("extern_".to_string() + name),
address: "somewhere".into(),
name: name.into(), name: name.into(),
calling_convention: None, calling_convention: None,
arguments: vec![arg], parameters: vec![arg.clone()],
return_values: vec![arg],
no_return: false,
} }
} }
fn register(name: &str) -> Variable { fn register(name: &str) -> Variable {
Variable { Variable {
name: name.into(), name: name.into(),
type_: crate::bil::variable::Type::Immediate(64), size: ByteSize::new(8),
is_temp: false, is_temp: false,
} }
} }
fn reg_add_term(name: &str, value: i64, tid_name: &str) -> Term<Def> { fn reg_add_term(name: &str, value: i64, tid_name: &str) -> Term<Def> {
let add_expr = Expression::BinOp { let add_expr = Expression::BinOp {
op: crate::bil::BinOpType::PLUS, op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(register(name))), lhs: Box::new(Expression::Var(register(name))),
rhs: Box::new(Expression::Const(Bitvector::from_i64(value))), rhs: Box::new(Expression::Const(Bitvector::from_i64(value))),
}; };
Term { Term {
tid: Tid::new(format!("{}", tid_name)), tid: Tid::new(format!("{}", tid_name)),
term: Def { term: Def::Assign {
lhs: register(name), var: register(name),
rhs: add_expr, value: add_expr,
}, },
} }
} }
fn call_term(target_name: &str) -> Term<Jmp> { fn call_term(target_name: &str) -> Term<Jmp> {
let call = Call {
target: Label::Direct(Tid::new(target_name)),
return_: None,
};
Term { Term {
tid: Tid::new(format!("call_{}", target_name)), tid: Tid::new(format!("call_{}", target_name)),
term: Jmp { term: Jmp::Call {
condition: None, target: Tid::new(target_name),
kind: JmpKind::Call(call), return_: None,
}, },
} }
} }
...@@ -68,10 +59,10 @@ fn call_term(target_name: &str) -> Term<Jmp> { ...@@ -68,10 +59,10 @@ fn call_term(target_name: &str) -> Term<Jmp> {
fn return_term(target_name: &str) -> Term<Jmp> { fn return_term(target_name: &str) -> Term<Jmp> {
Term { Term {
tid: Tid::new(format!("return")), tid: Tid::new(format!("return")),
term: Jmp { term: Jmp::Return(Expression::Unknown {
condition: None, description: target_name.into(),
kind: JmpKind::Return(Label::Direct(Tid::new(target_name))), size: ByteSize::new(8),
}, }),
} }
} }
...@@ -102,7 +93,6 @@ fn mock_project() -> Project { ...@@ -102,7 +93,6 @@ fn mock_project() -> Project {
fn context_problem_implementation() { fn context_problem_implementation() {
use crate::analysis::interprocedural_fixpoint::Context as IpFpContext; use crate::analysis::interprocedural_fixpoint::Context as IpFpContext;
use crate::analysis::pointer_inference::Data; use crate::analysis::pointer_inference::Data;
use crate::bil::*;
use Expression::*; use Expression::*;
let project = mock_project(); let project = mock_project();
...@@ -113,10 +103,10 @@ fn context_problem_implementation() { ...@@ -113,10 +103,10 @@ fn context_problem_implementation() {
let def = Term { let def = Term {
tid: Tid::new("def"), tid: Tid::new("def"),
term: Def { term: Def::Assign {
lhs: register("RSP"), var: register("RSP"),
rhs: BinOp { value: BinOp {
op: BinOpType::PLUS, op: BinOpType::IntAdd,
lhs: Box::new(Var(register("RSP"))), lhs: Box::new(Var(register("RSP"))),
rhs: Box::new(Const(Bitvector::from_i64(-16))), rhs: Box::new(Const(Bitvector::from_i64(-16))),
}, },
...@@ -124,15 +114,9 @@ fn context_problem_implementation() { ...@@ -124,15 +114,9 @@ fn context_problem_implementation() {
}; };
let store_term = Term { let store_term = Term {
tid: Tid::new("store"), tid: Tid::new("store"),
term: Def { term: Def::Store {
lhs: register("memory"), // technically false, but not checked at the moment address: Var(register("RSP")),
rhs: Store { value: Const(Bitvector::from_i64(42)),
address: Box::new(Var(register("RSP"))),
endian: Endianness::LittleEndian,
memory: Box::new(Var(register("memory"))), // This is technically false, but the field is ignored at the moment
value: Box::new(Const(Bitvector::from_i64(42))),
size: 64,
},
}, },
}; };
...@@ -183,12 +167,8 @@ fn context_problem_implementation() { ...@@ -183,12 +167,8 @@ fn context_problem_implementation() {
state.get_register(&register("RSP")).unwrap() state.get_register(&register("RSP")).unwrap()
); );
state state.set_register(&register("callee_saved_reg"), Data::Value(bv(13)));
.set_register(&register("callee_saved_reg"), Data::Value(bv(13))) state.set_register(&register("other_reg"), Data::Value(bv(14)));
.unwrap();
state
.set_register(&register("other_reg"), Data::Value(bv(14)))
.unwrap();
let malloc = call_term("extern_malloc"); let malloc = call_term("extern_malloc");
let mut state_after_malloc = context.update_call_stub(&state, &malloc).unwrap(); let mut state_after_malloc = context.update_call_stub(&state, &malloc).unwrap();
...@@ -205,7 +185,7 @@ fn context_problem_implementation() { ...@@ -205,7 +185,7 @@ fn context_problem_implementation() {
state state
.get_register(&register("RSP")) .get_register(&register("RSP"))
.unwrap() .unwrap()
.bin_op(BinOpType::PLUS, &Data::Value(bv(8))) .bin_op(BinOpType::IntAdd, &Data::Value(bv(8)))
); );
assert_eq!( assert_eq!(
state_after_malloc state_after_malloc
...@@ -218,15 +198,13 @@ fn context_problem_implementation() { ...@@ -218,15 +198,13 @@ fn context_problem_implementation() {
.unwrap() .unwrap()
.is_top()); .is_top());
state_after_malloc state_after_malloc.set_register(
.set_register(
&register("callee_saved_reg"), &register("callee_saved_reg"),
Data::Pointer(PointerDomain::new( Data::Pointer(PointerDomain::new(
new_id("call_extern_malloc", "RAX"), new_id("call_extern_malloc", "RAX"),
bv(0), bv(0),
)), )),
) );
.unwrap();
let free = call_term("extern_free"); let free = call_term("extern_free");
let state_after_free = context let state_after_free = context
.update_call_stub(&state_after_malloc, &free) .update_call_stub(&state_after_malloc, &free)
...@@ -254,7 +232,7 @@ fn context_problem_implementation() { ...@@ -254,7 +232,7 @@ fn context_problem_implementation() {
state state
.get_register(&register("RSP")) .get_register(&register("RSP"))
.unwrap() .unwrap()
.bin_op(BinOpType::PLUS, &Data::Value(bv(8))) .bin_op(BinOpType::IntAdd, &Data::Value(bv(8)))
); );
assert_eq!( assert_eq!(
state_after_other_fn state_after_other_fn
...@@ -290,7 +268,7 @@ fn update_return() { ...@@ -290,7 +268,7 @@ fn update_return() {
callsite_id.clone(), callsite_id.clone(),
bv(0).into(), bv(0).into(),
ObjectType::Stack, ObjectType::Stack,
64, ByteSize::new(8),
); );
state_before_return state_before_return
.caller_stack_ids .caller_stack_ids
...@@ -304,7 +282,7 @@ fn update_return() { ...@@ -304,7 +282,7 @@ fn update_return() {
other_callsite_id.clone(), other_callsite_id.clone(),
bv(0).into(), bv(0).into(),
ObjectType::Stack, ObjectType::Stack,
64, ByteSize::new(8),
); );
state_before_return state_before_return
.caller_stack_ids .caller_stack_ids
...@@ -312,15 +290,13 @@ fn update_return() { ...@@ -312,15 +290,13 @@ fn update_return() {
state_before_return state_before_return
.ids_known_to_caller .ids_known_to_caller
.insert(other_callsite_id.clone()); .insert(other_callsite_id.clone());
state_before_return state_before_return.set_register(
.set_register(
&register("RAX"), &register("RAX"),
Data::Pointer(PointerDomain::new( Data::Pointer(PointerDomain::new(
new_id("call_callee_other", "RSP"), new_id("call_callee_other", "RSP"),
bv(-32), bv(-32),
)), )),
) );
.unwrap();
let state_before_call = State::new(&register("RSP"), Tid::new("original_caller_id")); let state_before_call = State::new(&register("RSP"), Tid::new("original_caller_id"));
let mut state_before_call = context let mut state_before_call = context
...@@ -334,7 +310,7 @@ fn update_return() { ...@@ -334,7 +310,7 @@ fn update_return() {
caller_caller_id.clone(), caller_caller_id.clone(),
bv(0).into(), bv(0).into(),
ObjectType::Stack, ObjectType::Stack,
64, ByteSize::new(8),
); );
state_before_call state_before_call
.caller_stack_ids .caller_stack_ids
......
...@@ -16,7 +16,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -16,7 +16,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
/// Update the state according to the effects of the given `Def` term. /// Update the state according to the effects of the given `Def` term.
fn update_def(&self, state: &Self::Value, def: &Term<Def>) -> Option<Self::Value> { fn update_def(&self, state: &Self::Value, def: &Term<Def>) -> Option<Self::Value> {
// first check for use-after-frees // first check for use-after-frees
if state.contains_access_of_dangling_memory(&def.term.rhs) { if state.contains_access_of_dangling_memory(&def.term) {
let warning = CweWarning { let warning = CweWarning {
name: "CWE416".to_string(), name: "CWE416".to_string(),
version: VERSION.to_string(), version: VERSION.to_string(),
...@@ -32,63 +32,27 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -32,63 +32,27 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
self.cwe_collector.send(warning).unwrap(); self.cwe_collector.send(warning).unwrap();
} }
match &def.term.rhs { match &def.term {
Expression::IfThenElse { Def::Store { address, value } => {
condition,
true_exp,
false_exp,
} => {
// IfThenElse needs special handling, because it may encode conditional store instructions.
let mut true_state = state.clone();
if let Expression::Store { .. } = **true_exp {
self.log_debug(true_state.handle_store_exp(true_exp), Some(&def.tid));
} else {
self.log_debug(
true_state.handle_register_assign(&def.term.lhs, true_exp),
Some(&def.tid),
);
};
let mut false_state = state.clone();
if let Expression::Store { .. } = **false_exp {
self.log_debug(false_state.handle_store_exp(false_exp), Some(&def.tid));
} else {
self.log_debug(
false_state.handle_register_assign(&def.term.lhs, false_exp),
Some(&def.tid),
);
};
match state.eval(condition) {
Ok(Data::Value(cond)) if !cond.is_top() => {
if cond == Bitvector::from_bit(true).into() {
Some(true_state)
} else if cond == Bitvector::from_bit(false).into() {
Some(false_state)
} else {
panic!("IfThenElse with wrong condition bitsize encountered")
}
}
Ok(_) => Some(true_state.merge(&false_state)),
Err(err) => panic!("IfThenElse-Condition evaluation failed: {}", err),
}
}
Expression::Store { .. } => {
let mut state = state.clone(); let mut state = state.clone();
self.log_debug(state.handle_store_exp(&def.term.rhs), Some(&def.tid)); self.log_debug(state.handle_store(address, value), Some(&def.tid));
Some(state) Some(state)
} }
expression => { Def::Assign { var, value } => {
let mut new_state = state.clone(); let mut new_state = state.clone();
self.log_debug( self.log_debug(new_state.handle_register_assign(var, value), Some(&def.tid));
new_state.handle_register_assign(&def.term.lhs, expression), Some(new_state)
Some(&def.tid), }
); Def::Load { var, address } => {
let mut new_state = state.clone();
self.log_debug(new_state.handle_load(var, address), Some(&def.tid));
Some(new_state) Some(new_state)
} }
} }
} }
/// Update the state according to the effects of the given `Jmp` term. /// Update the state according to the effects of the given `Jmp` term.
/// Right now this only removes virtual registers from the state, /// Right now the state is not changed,
/// as specialization for conditional jumps is not implemented yet. /// as specialization for conditional jumps is not implemented yet.
fn update_jump( fn update_jump(
&self, &self,
...@@ -97,8 +61,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -97,8 +61,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
_untaken_conditional: Option<&Term<Jmp>>, _untaken_conditional: Option<&Term<Jmp>>,
_target: &Term<Blk>, _target: &Term<Blk>,
) -> Option<State> { ) -> Option<State> {
let mut new_value = value.clone(); let new_value = value.clone();
new_value.remove_virtual_register();
Some(new_value) Some(new_value)
} }
...@@ -110,13 +73,11 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -110,13 +73,11 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
call_term: &Term<Jmp>, call_term: &Term<Jmp>,
_target_node: &crate::analysis::graph::Node, _target_node: &crate::analysis::graph::Node,
) -> Option<State> { ) -> Option<State> {
let call = if let JmpKind::Call(ref call) = call_term.term.kind { if let Jmp::Call {
call target: ref callee_tid,
} else { return_: _,
panic!("Malformed control flow graph: Encountered call edge with a non-call jump term.") } = call_term.term
}; {
if let Label::Direct(ref callee_tid) = call.target {
let callee_stack_id = AbstractIdentifier::new( let callee_stack_id = AbstractIdentifier::new(
callee_tid.clone(), callee_tid.clone(),
AbstractLocation::from_var(&self.project.stack_pointer_register).unwrap(), AbstractLocation::from_var(&self.project.stack_pointer_register).unwrap(),
...@@ -126,7 +87,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -126,7 +87,7 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
AbstractLocation::from_var(&self.project.stack_pointer_register).unwrap(), AbstractLocation::from_var(&self.project.stack_pointer_register).unwrap(),
); );
let stack_offset_adjustment = self.get_current_stack_offset(state); let stack_offset_adjustment = self.get_current_stack_offset(state);
let address_bitsize = self.project.stack_pointer_register.bitsize().unwrap(); let address_bytesize = self.project.stack_pointer_register.size;
let mut callee_state = state.clone(); let mut callee_state = state.clone();
callee_state.remove_virtual_register(); callee_state.remove_virtual_register();
...@@ -140,25 +101,21 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -140,25 +101,21 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
// add a new memory object for the callee stack frame // add a new memory object for the callee stack frame
callee_state.memory.add_abstract_object( callee_state.memory.add_abstract_object(
callee_stack_id.clone(), callee_stack_id.clone(),
Bitvector::zero(apint::BitWidth::new(address_bitsize as usize).unwrap()).into(), Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
ObjectType::Stack, ObjectType::Stack,
address_bitsize, address_bytesize,
); );
// set the new stack_id // set the new stack_id
callee_state.stack_id = callee_stack_id.clone(); callee_state.stack_id = callee_stack_id.clone();
// Set the stack pointer register to the callee stack id. // Set the stack pointer register to the callee stack id.
// At the beginning of a function this is the only known pointer to the new stack frame. // At the beginning of a function this is the only known pointer to the new stack frame.
self.log_debug(
callee_state.set_register( callee_state.set_register(
&self.project.stack_pointer_register, &self.project.stack_pointer_register,
PointerDomain::new( PointerDomain::new(
callee_stack_id.clone(), callee_stack_id.clone(),
Bitvector::zero(apint::BitWidth::new(address_bitsize as usize).unwrap()) Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
.into(),
) )
.into(), .into(),
),
Some(&call_term.tid),
); );
// set the list of caller stack ids to only this caller id // set the list of caller stack ids to only this caller id
callee_state.caller_stack_ids = BTreeSet::new(); callee_state.caller_stack_ids = BTreeSet::new();
...@@ -171,8 +128,10 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -171,8 +128,10 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
callee_state.ids_known_to_caller.remove(&callee_stack_id); callee_state.ids_known_to_caller.remove(&callee_stack_id);
Some(callee_state) Some(callee_state)
} else { } else if let Jmp::CallInd { .. } = call_term.term {
panic!("Indirect call edges not yet supported.") panic!("Indirect call edges not yet supported.")
} else {
panic!("Malformed control flow graph: Call edge was not a call.")
} }
} }
...@@ -245,8 +204,9 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -245,8 +204,9 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
/// Update the state according to the effect of a call to an extern symbol. /// Update the state according to the effect of a call to an extern symbol.
fn update_call_stub(&self, state: &State, call: &Term<Jmp>) -> Option<State> { fn update_call_stub(&self, state: &State, call: &Term<Jmp>) -> Option<State> {
let mut new_state = state.clone(); let mut new_state = state.clone();
let call_target = match &call.term.kind { let call_target = match &call.term {
JmpKind::Call(call_inner) => &call_inner.target, Jmp::Call { target, .. } => target,
Jmp::CallInd { .. } => panic!("Indirect calls to extern symbols not yet supported."),
_ => panic!("Malformed control flow graph encountered."), _ => panic!("Malformed control flow graph encountered."),
}; };
// Clear non-callee-saved registers from the state. // Clear non-callee-saved registers from the state.
...@@ -258,47 +218,33 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a> ...@@ -258,47 +218,33 @@ impl<'a> crate::analysis::interprocedural_fixpoint::Context<'a> for Context<'a>
let stack_pointer = state.get_register(stack_register).unwrap(); let stack_pointer = state.get_register(stack_register).unwrap();
match self.project.cpu_architecture.as_str() { match self.project.cpu_architecture.as_str() {
"x86" | "x86_64" => { "x86" | "x86_64" => {
let offset = Bitvector::from_u16(stack_register.bitsize().unwrap() / 8) let offset = Bitvector::from_u64(stack_register.size.into())
.into_zero_extend(stack_register.bitsize().unwrap() as usize) .into_truncate(apint::BitWidth::from(stack_register.size))
.unwrap(); .unwrap();
self.log_debug(
new_state.set_register( new_state.set_register(
stack_register, stack_register,
stack_pointer.bin_op(crate::bil::BinOpType::PLUS, &offset.into()), stack_pointer.bin_op(BinOpType::IntAdd, &offset.into()),
),
Some(&call.tid),
); );
} }
_ => self.log_debug( _ => new_state.set_register(stack_register, stack_pointer),
new_state.set_register(stack_register, stack_pointer),
Some(&call.tid),
),
} }
if let Some(extern_symbol) = self.extern_symbol_map.get(call_target) {
match call_target {
Label::Direct(tid) => {
if let Some(extern_symbol) = self.extern_symbol_map.get(tid) {
// Check parameter for possible use-after-frees // Check parameter for possible use-after-frees
self.check_parameter_register_for_dangling_pointer(state, call, extern_symbol); self.check_parameter_register_for_dangling_pointer(state, call, extern_symbol);
match extern_symbol.name.as_str() { match extern_symbol.name.as_str() {
"malloc" | "calloc" | "realloc" | "xmalloc" => self "malloc" | "calloc" | "realloc" | "xmalloc" => {
.add_new_object_in_call_return_register(new_state, call, extern_symbol), self.add_new_object_in_call_return_register(new_state, call, extern_symbol)
"free" => self.mark_parameter_object_as_freed( }
state, "free" => {
new_state, self.mark_parameter_object_as_freed(state, new_state, call, extern_symbol)
call, }
extern_symbol,
),
_ => self.handle_generic_extern_call(state, new_state, call, extern_symbol), _ => self.handle_generic_extern_call(state, new_state, call, extern_symbol),
} }
} else { } else {
panic!("Extern symbol not found."); panic!("Extern symbol not found.");
} }
} }
Label::Indirect(_) => unimplemented!("Handling of indirect edges not yet implemented"), // Right now this case should not exist. Decide how to handle only after it can actually occur.
}
}
/// Update the state with the knowledge that some conditional evaluated to true or false. /// Update the state with the knowledge that some conditional evaluated to true or false.
/// Currently not implemented, this function just returns the state as it is. /// Currently not implemented, this function just returns the state as it is.
......
...@@ -14,8 +14,7 @@ ...@@ -14,8 +14,7 @@
use super::interprocedural_fixpoint::{Computation, NodeValue}; use super::interprocedural_fixpoint::{Computation, NodeValue};
use crate::abstract_domain::{BitvectorDomain, DataDomain}; use crate::abstract_domain::{BitvectorDomain, DataDomain};
use crate::analysis::graph::{Graph, Node}; use crate::analysis::graph::{Graph, Node};
use crate::prelude::*; use crate::intermediate_representation::*;
use crate::term::*;
use crate::utils::log::*; use crate::utils::log::*;
use petgraph::graph::NodeIndex; use petgraph::graph::NodeIndex;
use petgraph::visit::IntoNodeReferences; use petgraph::visit::IntoNodeReferences;
......
...@@ -22,8 +22,8 @@ impl DerefMut for AbstractObject { ...@@ -22,8 +22,8 @@ impl DerefMut for AbstractObject {
impl AbstractObject { impl AbstractObject {
/// Create a new abstract object with given object type and address bitsize. /// Create a new abstract object with given object type and address bitsize.
pub fn new(type_: ObjectType, address_bitsize: BitSize) -> AbstractObject { pub fn new(type_: ObjectType, address_bytesize: ByteSize) -> AbstractObject {
AbstractObject(Arc::new(AbstractObjectInfo::new(type_, address_bitsize))) AbstractObject(Arc::new(AbstractObjectInfo::new(type_, address_bytesize)))
} }
/// Short-circuits the `AbstractObjectInfo::merge` function if `self==other`. /// Short-circuits the `AbstractObjectInfo::merge` function if `self==other`.
...@@ -53,20 +53,19 @@ pub struct AbstractObjectInfo { ...@@ -53,20 +53,19 @@ pub struct AbstractObjectInfo {
impl AbstractObjectInfo { impl AbstractObjectInfo {
/// Create a new abstract object with known object type and address bitsize /// Create a new abstract object with known object type and address bitsize
pub fn new(type_: ObjectType, address_bitsize: BitSize) -> AbstractObjectInfo { pub fn new(type_: ObjectType, address_bytesize: ByteSize) -> AbstractObjectInfo {
AbstractObjectInfo { AbstractObjectInfo {
pointer_targets: BTreeSet::new(), pointer_targets: BTreeSet::new(),
is_unique: true, is_unique: true,
state: Some(ObjectState::Alive), state: Some(ObjectState::Alive),
type_: Some(type_), type_: Some(type_),
memory: MemRegion::new(address_bitsize), memory: MemRegion::new(address_bytesize),
} }
} }
/// Read the value at the given offset of the given size (in bits, not bytes) inside the memory region. /// Read the value at the given offset of the given size (in bits, not bytes) inside the memory region.
pub fn get_value(&self, offset: Bitvector, bitsize: BitSize) -> Data { pub fn get_value(&self, offset: Bitvector, bytesize: ByteSize) -> Data {
assert_eq!(bitsize % 8, 0); self.memory.get(offset, bytesize)
self.memory.get(offset, (bitsize / 8) as u64)
} }
/// Write a value at the given offset to the memory region. /// Write a value at the given offset to the memory region.
...@@ -83,12 +82,12 @@ impl AbstractObjectInfo { ...@@ -83,12 +82,12 @@ impl AbstractObjectInfo {
} else { } else {
let merged_value = self let merged_value = self
.memory .memory
.get(concrete_offset.clone(), (value.bitsize() / 8) as u64) .get(concrete_offset.clone(), value.bytesize())
.merge(&value); .merge(&value);
self.memory.add(merged_value, concrete_offset.clone()); self.memory.add(merged_value, concrete_offset.clone());
}; };
} else { } else {
self.memory = MemRegion::new(self.memory.get_address_bitsize()); self.memory = MemRegion::new(self.memory.get_address_bytesize());
} }
Ok(()) Ok(())
} }
...@@ -101,11 +100,11 @@ impl AbstractObjectInfo { ...@@ -101,11 +100,11 @@ impl AbstractObjectInfo {
if let BitvectorDomain::Value(ref concrete_offset) = offset { if let BitvectorDomain::Value(ref concrete_offset) = offset {
let merged_value = self let merged_value = self
.memory .memory
.get(concrete_offset.clone(), (value.bitsize() / 8) as u64) .get(concrete_offset.clone(), value.bytesize())
.merge(&value); .merge(&value);
self.memory.add(merged_value, concrete_offset.clone()); self.memory.add(merged_value, concrete_offset.clone());
} else { } else {
self.memory = MemRegion::new(self.memory.get_address_bitsize()); self.memory = MemRegion::new(self.memory.get_address_bytesize());
} }
} }
...@@ -164,7 +163,7 @@ impl AbstractObjectInfo { ...@@ -164,7 +163,7 @@ impl AbstractObjectInfo {
/// Represents the effect of unknown write instructions to the object /// Represents the effect of unknown write instructions to the object
/// which may include writing pointers to targets from the `additional_targets` set to the object. /// which may include writing pointers to targets from the `additional_targets` set to the object.
pub fn assume_arbitrary_writes(&mut self, additional_targets: &BTreeSet<AbstractIdentifier>) { pub fn assume_arbitrary_writes(&mut self, additional_targets: &BTreeSet<AbstractIdentifier>) {
self.memory = MemRegion::new(self.memory.get_address_bitsize()); self.memory = MemRegion::new(self.memory.get_address_bytesize());
self.pointer_targets self.pointer_targets
.extend(additional_targets.iter().cloned()); .extend(additional_targets.iter().cloned());
} }
...@@ -294,7 +293,7 @@ mod tests { ...@@ -294,7 +293,7 @@ mod tests {
is_unique: true, is_unique: true,
state: Some(ObjectState::Alive), state: Some(ObjectState::Alive),
type_: Some(ObjectType::Heap), type_: Some(ObjectType::Heap),
memory: MemRegion::new(64), memory: MemRegion::new(ByteSize::new(8)),
}; };
AbstractObject(Arc::new(obj_info)) AbstractObject(Arc::new(obj_info))
} }
...@@ -310,7 +309,7 @@ mod tests { ...@@ -310,7 +309,7 @@ mod tests {
fn new_id(tid: &str, reg_name: &str) -> AbstractIdentifier { fn new_id(tid: &str, reg_name: &str) -> AbstractIdentifier {
AbstractIdentifier::new( AbstractIdentifier::new(
Tid::new(tid), Tid::new(tid),
AbstractLocation::Register(reg_name.into(), 64), AbstractLocation::Register(reg_name.into(), ByteSize::new(8)),
) )
} }
...@@ -321,19 +320,22 @@ mod tests { ...@@ -321,19 +320,22 @@ mod tests {
let offset = bv(-15); let offset = bv(-15);
object.set_value(three, &offset).unwrap(); object.set_value(three, &offset).unwrap();
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-16), 64), object.get_value(Bitvector::from_i64(-16), ByteSize::new(8)),
Data::Top(64) Data::Top(ByteSize::new(8))
);
assert_eq!(
object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
new_data(3)
); );
assert_eq!(object.get_value(Bitvector::from_i64(-15), 64), new_data(3));
object.set_value(new_data(4), &bv(-12)).unwrap(); object.set_value(new_data(4), &bv(-12)).unwrap();
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), 64), object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
Data::Top(64) Data::Top(ByteSize::new(8))
); );
object.merge_value(new_data(5), &bv(-12)); object.merge_value(new_data(5), &bv(-12));
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-12), 64), object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)),
Data::Value(BitvectorDomain::new_top(64)) Data::Value(BitvectorDomain::new_top(ByteSize::new(8)))
); );
let mut other_object = new_abstract_object(); let mut other_object = new_abstract_object();
...@@ -341,11 +343,11 @@ mod tests { ...@@ -341,11 +343,11 @@ mod tests {
other_object.set_value(new_data(0), &bv(0)).unwrap(); other_object.set_value(new_data(0), &bv(0)).unwrap();
let merged_object = object.merge(&other_object); let merged_object = object.merge(&other_object);
assert_eq!( assert_eq!(
merged_object.get_value(Bitvector::from_i64(-12), 64), merged_object.get_value(Bitvector::from_i64(-12), ByteSize::new(8)),
Data::Top(64) Data::Top(ByteSize::new(8))
); );
assert_eq!( assert_eq!(
merged_object.get_value(Bitvector::from_i64(0), 64), merged_object.get_value(Bitvector::from_i64(0), ByteSize::new(8)),
new_data(0) new_data(0)
); );
} }
...@@ -370,7 +372,7 @@ mod tests { ...@@ -370,7 +372,7 @@ mod tests {
target_map.remove(&new_id("time_1", "RAX")); target_map.remove(&new_id("time_1", "RAX"));
let modified_pointer = PointerDomain::with_targets(target_map); let modified_pointer = PointerDomain::with_targets(target_map);
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), 64), object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer.into() modified_pointer.into()
); );
...@@ -384,7 +386,7 @@ mod tests { ...@@ -384,7 +386,7 @@ mod tests {
target_map.insert(new_id("time_234", "RBX"), bv(50)); target_map.insert(new_id("time_234", "RBX"), bv(50));
let modified_pointer = PointerDomain::with_targets(target_map); let modified_pointer = PointerDomain::with_targets(target_map);
assert_eq!( assert_eq!(
object.get_value(Bitvector::from_i64(-15), 64), object.get_value(Bitvector::from_i64(-15), ByteSize::new(8)),
modified_pointer.into() modified_pointer.into()
); );
} }
......
...@@ -22,15 +22,15 @@ impl AbstractObjectList { ...@@ -22,15 +22,15 @@ impl AbstractObjectList {
/// The offset into the stack object will be set to zero. /// The offset into the stack object will be set to zero.
pub fn from_stack_id( pub fn from_stack_id(
stack_id: AbstractIdentifier, stack_id: AbstractIdentifier,
address_bitsize: BitSize, address_bytesize: ByteSize,
) -> AbstractObjectList { ) -> AbstractObjectList {
let mut objects = BTreeMap::new(); let mut objects = BTreeMap::new();
let stack_object = AbstractObject::new(ObjectType::Stack, address_bitsize); let stack_object = AbstractObject::new(ObjectType::Stack, address_bytesize);
objects.insert( objects.insert(
stack_id, stack_id,
( (
stack_object, stack_object,
Bitvector::zero((address_bitsize as usize).into()).into(), Bitvector::zero(apint::BitWidth::from(address_bytesize)).into(),
), ),
); );
AbstractObjectList { objects } AbstractObjectList { objects }
...@@ -68,7 +68,7 @@ impl AbstractObjectList { ...@@ -68,7 +68,7 @@ impl AbstractObjectList {
/// If the address is not unique, merge the value of all possible addresses. /// If the address is not unique, merge the value of all possible addresses.
/// ///
/// Returns an error if the address is a `Data::Value`, i.e. not a pointer. /// Returns an error if the address is a `Data::Value`, i.e. not a pointer.
pub fn get_value(&self, address: &Data, size: BitSize) -> Result<Data, Error> { pub fn get_value(&self, address: &Data, size: ByteSize) -> Result<Data, Error> {
match address { match address {
Data::Value(value) => Err(anyhow!("Load from non-pointer value:\n{:?}", value)), Data::Value(value) => Err(anyhow!("Load from non-pointer value:\n{:?}", value)),
Data::Top(_) => Ok(Data::new_top(size)), Data::Top(_) => Ok(Data::new_top(size)),
...@@ -158,9 +158,9 @@ impl AbstractObjectList { ...@@ -158,9 +158,9 @@ impl AbstractObjectList {
object_id: AbstractIdentifier, object_id: AbstractIdentifier,
initial_offset: BitvectorDomain, initial_offset: BitvectorDomain,
type_: ObjectType, type_: ObjectType,
address_bitsize: BitSize, address_bytesize: ByteSize,
) { ) {
let new_object = AbstractObject::new(type_, address_bitsize); let new_object = AbstractObject::new(type_, address_bytesize);
if let Some((object, offset)) = self.objects.get_mut(&object_id) { if let Some((object, offset)) = self.objects.get_mut(&object_id) {
// If the identifier already exists, we have to assume that more than one object may be referenced by this identifier. // If the identifier already exists, we have to assume that more than one object may be referenced by this identifier.
object.is_unique = false; object.is_unique = false;
...@@ -333,13 +333,14 @@ mod tests { ...@@ -333,13 +333,14 @@ mod tests {
fn new_id(name: &str) -> AbstractIdentifier { fn new_id(name: &str) -> AbstractIdentifier {
AbstractIdentifier::new( AbstractIdentifier::new(
Tid::new("time0"), Tid::new("time0"),
AbstractLocation::Register(name.into(), 64), AbstractLocation::Register(name.into(), ByteSize::new(8)),
) )
} }
#[test] #[test]
fn abstract_object_list() { fn abstract_object_list() {
let mut obj_list = AbstractObjectList::from_stack_id(new_id("RSP".into()), 64); let mut obj_list =
AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8));
assert_eq!(obj_list.objects.len(), 1); assert_eq!(obj_list.objects.len(), 1);
assert_eq!(obj_list.objects.values().next().unwrap().1, bv(0)); assert_eq!(obj_list.objects.values().next().unwrap().1, bv(0));
...@@ -349,12 +350,13 @@ mod tests { ...@@ -349,12 +350,13 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
obj_list obj_list
.get_value(&Data::Pointer(pointer.clone()), 64) .get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Value(bv(42)) Data::Value(bv(42))
); );
let mut other_obj_list = AbstractObjectList::from_stack_id(new_id("RSP".into()), 64); let mut other_obj_list =
AbstractObjectList::from_stack_id(new_id("RSP".into()), ByteSize::new(8));
let second_pointer = PointerDomain::new(new_id("RSP".into()), bv(-8)); let second_pointer = PointerDomain::new(new_id("RSP".into()), bv(-8));
other_obj_list other_obj_list
.set_value(pointer.clone(), Data::Value(bv(42))) .set_value(pointer.clone(), Data::Value(bv(42)))
...@@ -364,12 +366,17 @@ mod tests { ...@@ -364,12 +366,17 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
other_obj_list other_obj_list
.get_value(&Data::Pointer(second_pointer.clone()), 64) .get_value(&Data::Pointer(second_pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Value(bv(35)) Data::Value(bv(35))
); );
other_obj_list.add_abstract_object(new_id("RAX".into()), bv(0), ObjectType::Heap, 64); other_obj_list.add_abstract_object(
new_id("RAX".into()),
bv(0),
ObjectType::Heap,
ByteSize::new(8),
);
let heap_pointer = PointerDomain::new(new_id("RAX".into()), bv(8)); let heap_pointer = PointerDomain::new(new_id("RAX".into()), bv(8));
other_obj_list other_obj_list
.set_value(heap_pointer.clone(), Data::Value(bv(3))) .set_value(heap_pointer.clone(), Data::Value(bv(3)))
...@@ -378,19 +385,19 @@ mod tests { ...@@ -378,19 +385,19 @@ mod tests {
let mut merged = obj_list.merge(&other_obj_list); let mut merged = obj_list.merge(&other_obj_list);
assert_eq!( assert_eq!(
merged merged
.get_value(&Data::Pointer(pointer.clone()), 64) .get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Value(bv(42)) Data::Value(bv(42))
); );
assert_eq!( assert_eq!(
merged merged
.get_value(&Data::Pointer(second_pointer.clone()), 64) .get_value(&Data::Pointer(second_pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::new_top(64) Data::new_top(ByteSize::new(8))
); );
assert_eq!( assert_eq!(
merged merged
.get_value(&Data::Pointer(heap_pointer.clone()), 64) .get_value(&Data::Pointer(heap_pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Value(bv(3)) Data::Value(bv(3))
); );
...@@ -401,13 +408,13 @@ mod tests { ...@@ -401,13 +408,13 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
merged merged
.get_value(&Data::Pointer(pointer.clone()), 64) .get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Value(BitvectorDomain::new_top(64)) Data::Value(BitvectorDomain::new_top(ByteSize::new(8)))
); );
assert_eq!( assert_eq!(
merged merged
.get_value(&Data::Pointer(heap_pointer.clone()), 64) .get_value(&Data::Pointer(heap_pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Value(bv(3)) Data::Value(bv(3))
); );
...@@ -435,7 +442,7 @@ mod tests { ...@@ -435,7 +442,7 @@ mod tests {
other_obj_list.replace_abstract_id(&new_id("RAX".into()), &new_id("ID2".into()), &bv(0)); other_obj_list.replace_abstract_id(&new_id("RAX".into()), &new_id("ID2".into()), &bv(0));
assert_eq!( assert_eq!(
other_obj_list other_obj_list
.get_value(&Data::Pointer(pointer.clone()), 64) .get_value(&Data::Pointer(pointer.clone()), ByteSize::new(8))
.unwrap(), .unwrap(),
Data::Pointer(modified_heap_pointer.clone()) Data::Pointer(modified_heap_pointer.clone())
); );
...@@ -481,10 +488,16 @@ mod tests { ...@@ -481,10 +488,16 @@ mod tests {
#[test] #[test]
fn append_unknown_objects_test() { fn append_unknown_objects_test() {
let mut obj_list = AbstractObjectList::from_stack_id(new_id("stack"), 64); let mut obj_list = AbstractObjectList::from_stack_id(new_id("stack"), ByteSize::new(8));
let mut other_obj_list = AbstractObjectList::from_stack_id(new_id("stack"), 64); let mut other_obj_list =
other_obj_list.add_abstract_object(new_id("heap_obj"), bv(0).into(), ObjectType::Heap, 64); AbstractObjectList::from_stack_id(new_id("stack"), ByteSize::new(8));
other_obj_list.add_abstract_object(
new_id("heap_obj"),
bv(0).into(),
ObjectType::Heap,
ByteSize::new(8),
);
obj_list.append_unknown_objects(&other_obj_list); obj_list.append_unknown_objects(&other_obj_list);
assert_eq!(obj_list.objects.len(), 2); assert_eq!(obj_list.objects.len(), 2);
......
...@@ -8,7 +8,7 @@ impl State { ...@@ -8,7 +8,7 @@ impl State {
if let Some(data) = self.register.get(variable) { if let Some(data) = self.register.get(variable) {
Ok(data.clone()) Ok(data.clone())
} else { } else {
Ok(Data::new_top(variable.bitsize()?)) Ok(Data::new_top(variable.size))
} }
} }
...@@ -28,17 +28,12 @@ impl State { ...@@ -28,17 +28,12 @@ impl State {
/// Set the value of a register. /// Set the value of a register.
/// ///
/// Returns an error if the variable is not a register. /// Returns an error if the variable is not a register.
pub fn set_register(&mut self, variable: &Variable, value: Data) -> Result<(), Error> { pub fn set_register(&mut self, variable: &Variable, value: Data) {
if let variable::Type::Immediate(_bitsize) = variable.type_ {
if !value.is_top() { if !value.is_top() {
self.register.insert(variable.clone(), value); self.register.insert(variable.clone(), value);
} else { } else {
self.register.remove(variable); self.register.remove(variable);
} }
Ok(())
} else {
Err(anyhow!("Variable is not a register type"))
}
} }
/// Evaluate expression on the given state and write the result to the target register. /// Evaluate expression on the given state and write the result to the target register.
...@@ -47,21 +42,13 @@ impl State { ...@@ -47,21 +42,13 @@ impl State {
target: &Variable, target: &Variable,
expression: &Expression, expression: &Expression,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Expression::Var(variable) = expression {
if target == variable {
// The assign does nothing. Occurs as "do nothing"-path in conditional stores.
// Needs special handling, since it is the only case where the target is allowed
// to denote memory instead of a register.
return Ok(());
}
}
match self.eval(expression) { match self.eval(expression) {
Ok(new_value) => { Ok(new_value) => {
self.set_register(target, new_value)?; self.set_register(target, new_value);
Ok(()) Ok(())
} }
Err(err) => { Err(err) => {
self.set_register(target, Data::new_top(target.bitsize()?))?; self.set_register(target, Data::new_top(target.size));
Err(err) Err(err)
} }
} }
...@@ -103,31 +90,38 @@ impl State { ...@@ -103,31 +90,38 @@ impl State {
} }
} }
/// Evaluate the given store expression on the given state and return the resulting state. /// Evaluate the given store instruction on the given state and return the resulting state.
/// ///
/// The function panics if given anything else than a store expression. /// The function panics if given anything else than a store expression.
pub fn handle_store_exp(&mut self, store_exp: &Expression) -> Result<(), Error> { pub fn handle_store(&mut self, address: &Expression, value: &Expression) -> Result<(), Error> {
if let Expression::Store {
memory: _,
address,
value,
endian: _,
size,
} = store_exp
{
match self.eval(value) { match self.eval(value) {
Ok(data) => { Ok(data) => self.write_to_address(address, &data),
assert_eq!(data.bitsize(), *size);
self.write_to_address(address, &data)
}
Err(err) => { Err(err) => {
// we still need to write to the target location before reporting the error // we still need to write to the target location before reporting the error
self.write_to_address(address, &Data::new_top(*size))?; self.write_to_address(address, &Data::new_top(value.bytesize()))?;
Err(err) Err(err)
} }
} }
} else { }
panic!("Expected store expression")
/// Evaluate the given load instruction and return the data read on success.
pub fn load_value(&self, address: &Expression, size: ByteSize) -> Result<Data, Error> {
Ok(self
.memory
.get_value(&self.adjust_pointer_for_read(&self.eval(address)?), size)?)
}
/// Handle a load instruction by assigning the value loaded from the address given by the `address` expression to `var`.
pub fn handle_load(&mut self, var: &Variable, address: &Expression) -> Result<(), Error> {
match self.load_value(address, var.size) {
Ok(data) => {
self.set_register(var, data);
Ok(())
}
Err(err) => {
self.set_register(var, Data::new_top(var.size));
Err(err)
}
} }
} }
...@@ -151,7 +145,7 @@ impl State { ...@@ -151,7 +145,7 @@ impl State {
new_targets.insert(id.clone(), offset.clone()); new_targets.insert(id.clone(), offset.clone());
} }
} }
BitvectorDomain::Top(_bitsize) => { BitvectorDomain::Top(_bytesize) => {
for caller_id in self.caller_stack_ids.iter() { for caller_id in self.caller_stack_ids.iter() {
new_targets.insert(caller_id.clone(), offset.clone()); new_targets.insert(caller_id.clone(), offset.clone());
} }
...@@ -175,130 +169,39 @@ impl State { ...@@ -175,130 +169,39 @@ impl State {
match expression { match expression {
Var(variable) => self.get_register(&variable), Var(variable) => self.get_register(&variable),
Const(bitvector) => Ok(bitvector.clone().into()), Const(bitvector) => Ok(bitvector.clone().into()),
// TODO: implement handling of endianness for loads and writes!
Load {
memory: _,
address,
endian: _,
size,
} => Ok(self
.memory
.get_value(&self.adjust_pointer_for_read(&self.eval(address)?), *size)?),
Store { .. } => {
// This does not return an error, but panics outright.
// If this would return an error, it would hide a side effect, which is not allowed to happen.
panic!("Store expression cannot be evaluated!")
}
BinOp { op, lhs, rhs } => { BinOp { op, lhs, rhs } => {
if *op == crate::bil::BinOpType::XOR && lhs == rhs { if *op == BinOpType::IntXOr && lhs == rhs {
// the result of `x XOR x` is always zero. // the result of `x XOR x` is always zero.
return Ok(Bitvector::zero(apint::BitWidth::new( return Ok(Bitvector::zero(apint::BitWidth::from(lhs.bytesize())).into());
self.eval(lhs)?.bitsize() as usize
)?)
.into());
} }
let (left, right) = (self.eval(lhs)?, self.eval(rhs)?); let (left, right) = (self.eval(lhs)?, self.eval(rhs)?);
Ok(left.bin_op(*op, &right)) Ok(left.bin_op(*op, &right))
} }
UnOp { op, arg } => Ok(self.eval(arg)?.un_op(*op)), UnOp { op, arg } => Ok(self.eval(arg)?.un_op(*op)),
Cast { kind, width, arg } => Ok(self.eval(arg)?.cast(*kind, *width)), Cast { op, size, arg } => Ok(self.eval(arg)?.cast(*op, *size)),
Let { Unknown {
var: _, description: _,
bound_exp: _, size,
body_exp: _, } => Ok(Data::new_top(*size)),
} => Err(anyhow!("Let binding expression handling not implemented")), Subpiece {
Unknown { description, type_ } => { low_byte,
if let crate::bil::variable::Type::Immediate(bitsize) = type_ { size,
Ok(Data::new_top(*bitsize))
} else {
Err(anyhow!("Unknown Memory operation: {}", description))
}
}
IfThenElse {
condition,
true_exp,
false_exp,
} => match self.eval(condition)? {
x if x == Bitvector::from_bit(false).into() => self.eval(false_exp),
x if x == Bitvector::from_bit(true).into() => self.eval(true_exp),
_ => Ok(self.eval(true_exp)?.merge(&self.eval(false_exp)?)),
},
Extract {
low_bit,
high_bit,
arg, arg,
} => Ok(self.eval(arg)?.extract(*low_bit, *high_bit)), } => Ok(self.eval(arg)?.subpiece(*low_byte, *size)),
Concat { left, right } => Ok(self.eval(left)?.concat(&self.eval(right)?)),
} }
} }
/// Check if an expression contains a use-after-free /// Check if an expression contains a use-after-free
pub fn contains_access_of_dangling_memory(&self, expression: &Expression) -> bool { pub fn contains_access_of_dangling_memory(&self, def: &Def) -> bool {
use Expression::*; match def {
match expression { Def::Load { address, .. } | Def::Store { address, .. } => {
Var(_) | Const(_) | Unknown { .. } => false, if let Ok(pointer) = self.eval(address) {
Load {
address: address_exp,
..
} => {
if let Ok(pointer) = self.eval(address_exp) {
self.memory.is_dangling_pointer(&pointer, true) self.memory.is_dangling_pointer(&pointer, true)
|| self.contains_access_of_dangling_memory(address_exp)
} else { } else {
false false
} }
} }
Store { _ => false,
memory: _,
address: address_exp,
value: value_exp,
..
} => {
let address_check = if let Ok(pointer) = self.eval(address_exp) {
self.memory.is_dangling_pointer(&pointer, true)
} else {
false
};
address_check
|| self.contains_access_of_dangling_memory(address_exp)
|| self.contains_access_of_dangling_memory(value_exp)
}
BinOp { op: _, lhs, rhs } => {
self.contains_access_of_dangling_memory(lhs)
|| self.contains_access_of_dangling_memory(rhs)
}
UnOp { op: _, arg } => self.contains_access_of_dangling_memory(arg),
Cast {
kind: _,
width: _,
arg,
} => self.contains_access_of_dangling_memory(arg),
Let {
var: _,
bound_exp,
body_exp,
} => {
self.contains_access_of_dangling_memory(bound_exp)
|| self.contains_access_of_dangling_memory(body_exp)
}
IfThenElse {
condition,
true_exp,
false_exp,
} => {
self.contains_access_of_dangling_memory(condition)
|| self.contains_access_of_dangling_memory(true_exp)
|| self.contains_access_of_dangling_memory(false_exp)
}
Extract {
low_bit: _,
high_bit: _,
arg,
} => self.contains_access_of_dangling_memory(arg),
Concat { left, right } => {
self.contains_access_of_dangling_memory(left)
|| self.contains_access_of_dangling_memory(right)
}
} }
} }
......
use super::object_list::AbstractObjectList; use super::object_list::AbstractObjectList;
use super::Data; use super::Data;
use crate::abstract_domain::*; use crate::abstract_domain::*;
use crate::bil::*; use crate::intermediate_representation::*;
use crate::prelude::*; use crate::prelude::*;
use crate::term::symbol::ExternSymbol;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
mod access_handling; mod access_handling;
...@@ -50,16 +49,13 @@ impl State { ...@@ -50,16 +49,13 @@ impl State {
stack_register.clone(), stack_register.clone(),
PointerDomain::new( PointerDomain::new(
stack_id.clone(), stack_id.clone(),
Bitvector::zero((stack_register.bitsize().unwrap() as usize).into()).into(), Bitvector::zero(apint::BitWidth::from(stack_register.size)).into(),
) )
.into(), .into(),
); );
State { State {
register, register,
memory: AbstractObjectList::from_stack_id( memory: AbstractObjectList::from_stack_id(stack_id.clone(), stack_register.size),
stack_id.clone(),
stack_register.bitsize().unwrap(),
),
stack_id, stack_id,
caller_stack_ids: BTreeSet::new(), caller_stack_ids: BTreeSet::new(),
ids_known_to_caller: BTreeSet::new(), ids_known_to_caller: BTreeSet::new(),
...@@ -89,18 +85,27 @@ impl State { ...@@ -89,18 +85,27 @@ impl State {
/// Mark those parameter values of an extern function call, that are passed on the stack, /// Mark those parameter values of an extern function call, that are passed on the stack,
/// as unknown data (since the function may modify them). /// as unknown data (since the function may modify them).
pub fn clear_stack_parameter(&mut self, extern_call: &ExternSymbol) -> Result<(), Error> { pub fn clear_stack_parameter(
&mut self,
extern_call: &ExternSymbol,
stack_pointer_register: &Variable,
) -> Result<(), Error> {
let mut result_log = Ok(()); let mut result_log = Ok(());
for arg in &extern_call.arguments { for arg in &extern_call.parameters {
match &arg.location { match arg {
Expression::Var(_) => {} Arg::Register(_) => (),
location_expression => { Arg::Stack { offset, size } => {
let arg_size = arg let data_top = Data::new_top(*size);
.var let location_expression = Expression::BinOp {
.bitsize() lhs: Box::new(Expression::Var(stack_pointer_register.clone())),
.expect("Encountered argument with unknown size"); op: BinOpType::IntAdd,
let data_top = Data::new_top(arg_size); rhs: Box::new(Expression::Const(
if let Err(err) = self.write_to_address(location_expression, &data_top) { Bitvector::from_i64(*offset)
.into_truncate(apint::BitWidth::from(stack_pointer_register.size))
.unwrap(),
)),
};
if let Err(err) = self.write_to_address(&location_expression, &data_top) {
result_log = Err(err); result_log = Err(err);
} }
} }
......
...@@ -7,21 +7,21 @@ fn bv(value: i64) -> BitvectorDomain { ...@@ -7,21 +7,21 @@ fn bv(value: i64) -> BitvectorDomain {
fn new_id(time: &str, register: &str) -> AbstractIdentifier { fn new_id(time: &str, register: &str) -> AbstractIdentifier {
AbstractIdentifier::new( AbstractIdentifier::new(
Tid::new(time), Tid::new(time),
AbstractLocation::Register(register.into(), 64), AbstractLocation::Register(register.into(), ByteSize::new(8)),
) )
} }
fn register(name: &str) -> Variable { fn register(name: &str) -> Variable {
Variable { Variable {
name: name.into(), name: name.into(),
type_: crate::bil::variable::Type::Immediate(64), size: ByteSize::new(8),
is_temp: false, is_temp: false,
} }
} }
fn reg_add(name: &str, value: i64) -> Expression { fn reg_add(name: &str, value: i64) -> Expression {
Expression::BinOp { Expression::BinOp {
op: BinOpType::PLUS, op: BinOpType::IntAdd,
lhs: Box::new(Expression::Var(register(name))), lhs: Box::new(Expression::Var(register(name))),
rhs: Box::new(Expression::Const(Bitvector::from_i64(value))), rhs: Box::new(Expression::Const(Bitvector::from_i64(value))),
} }
...@@ -29,51 +29,16 @@ fn reg_add(name: &str, value: i64) -> Expression { ...@@ -29,51 +29,16 @@ fn reg_add(name: &str, value: i64) -> Expression {
fn reg_sub(name: &str, value: i64) -> Expression { fn reg_sub(name: &str, value: i64) -> Expression {
Expression::BinOp { Expression::BinOp {
op: BinOpType::MINUS, op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(register(name))), lhs: Box::new(Expression::Var(register(name))),
rhs: Box::new(Expression::Const(Bitvector::from_i64(value))), rhs: Box::new(Expression::Const(Bitvector::from_i64(value))),
} }
} }
fn store_exp(address: Expression, value: Expression) -> Expression {
let mem_var = Variable {
name: "mem".into(),
type_: crate::bil::variable::Type::Memory {
addr_size: 64,
elem_size: 64,
},
is_temp: false,
};
Expression::Store {
memory: Box::new(Expression::Var(mem_var)),
address: Box::new(address),
value: Box::new(value),
endian: Endianness::LittleEndian,
size: 64,
}
}
fn load_exp(address: Expression) -> Expression {
let mem_var = Variable {
name: "mem".into(),
type_: crate::bil::variable::Type::Memory {
addr_size: 64,
elem_size: 64,
},
is_temp: false,
};
Expression::Load {
memory: Box::new(Expression::Var(mem_var)),
address: Box::new(address),
endian: Endianness::LittleEndian,
size: 64,
}
}
#[test] #[test]
fn state() { fn state() {
use crate::analysis::pointer_inference::object::*; use crate::analysis::pointer_inference::object::*;
use crate::bil::Expression::*; use Expression::*;
let mut state = State::new(&register("RSP"), Tid::new("time0")); let mut state = State::new(&register("RSP"), Tid::new("time0"));
let stack_id = new_id("time0", "RSP"); let stack_id = new_id("time0", "RSP");
let stack_addr = Data::Pointer(PointerDomain::new(stack_id.clone(), bv(8))); let stack_addr = Data::Pointer(PointerDomain::new(stack_id.clone(), bv(8)));
...@@ -81,13 +46,12 @@ fn state() { ...@@ -81,13 +46,12 @@ fn state() {
.store_value(&stack_addr, &Data::Value(bv(42))) .store_value(&stack_addr, &Data::Value(bv(42)))
.unwrap(); .unwrap();
state.register.insert(register("RSP"), stack_addr.clone()); state.register.insert(register("RSP"), stack_addr.clone());
let load_expr = Load { assert_eq!(
memory: Box::new(Var(register("RSP"))), // This is wrong, but the memory var is not checked at the moment (since we have only the one for RAM) state
address: Box::new(Var(register("RSP"))), .load_value(&Var(register("RSP")), ByteSize::new(8))
endian: Endianness::LittleEndian, .unwrap(),
size: 64 as BitSize, Data::Value(bv(42))
}; );
assert_eq!(state.eval(&load_expr).unwrap(), Data::Value(bv(42)));
let mut other_state = State::new(&register("RSP"), Tid::new("time0")); let mut other_state = State::new(&register("RSP"), Tid::new("time0"));
state.register.insert(register("RAX"), Data::Value(bv(42))); state.register.insert(register("RAX"), Data::Value(bv(42)));
...@@ -103,12 +67,20 @@ fn state() { ...@@ -103,12 +67,20 @@ fn state() {
let merged_state = state.merge(&other_state); let merged_state = state.merge(&other_state);
assert_eq!(merged_state.register[&register("RAX")], Data::Value(bv(42))); assert_eq!(merged_state.register[&register("RAX")], Data::Value(bv(42)));
assert_eq!(merged_state.register.get(&register("RBX")), None); assert_eq!(merged_state.register.get(&register("RBX")), None);
assert_eq!(merged_state.eval(&load_expr).unwrap(), Data::new_top(64)); assert_eq!(
merged_state
.load_value(&Var(register("RSP")), ByteSize::new(8))
.unwrap(),
Data::new_top(ByteSize::new(8))
);
// Test pointer adjustment on reads // Test pointer adjustment on reads
state state.memory.add_abstract_object(
.memory new_id("time0", "caller"),
.add_abstract_object(new_id("time0", "caller"), bv(0), ObjectType::Stack, 64); bv(0),
ObjectType::Stack,
ByteSize::new(8),
);
state.caller_stack_ids.insert(new_id("time0", "caller")); state.caller_stack_ids.insert(new_id("time0", "caller"));
state state
.store_value(&stack_addr, &Data::Value(bv(15))) .store_value(&stack_addr, &Data::Value(bv(15)))
...@@ -118,26 +90,41 @@ fn state() { ...@@ -118,26 +90,41 @@ fn state() {
.memory .memory
.get_value( .get_value(
&Data::Pointer(PointerDomain::new(new_id("time0", "caller"), bv(8))), &Data::Pointer(PointerDomain::new(new_id("time0", "caller"), bv(8))),
64 ByteSize::new(8)
) )
.unwrap(), .unwrap(),
Data::Value(bv(15)) Data::Value(bv(15))
); );
assert_eq!(state.eval(&load_expr).unwrap(), Data::Value(bv(15))); assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8))
.unwrap(),
Data::Value(bv(15))
);
// Test replace_abstract_id // Test replace_abstract_id
let pointer = Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16))); let pointer = Data::Pointer(PointerDomain::new(stack_id.clone(), bv(-16)));
state.register.insert(register("RSP"), pointer.clone()); state.register.insert(register("RSP"), pointer.clone());
state.store_value(&pointer, &Data::Value(bv(7))).unwrap(); state.store_value(&pointer, &Data::Value(bv(7))).unwrap();
assert_eq!(state.eval(&load_expr).unwrap(), Data::Value(bv(7))); assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8))
.unwrap(),
Data::Value(bv(7))
);
state.replace_abstract_id(&stack_id, &new_id("time0", "callee"), &bv(-8)); state.replace_abstract_id(&stack_id, &new_id("time0", "callee"), &bv(-8));
assert_eq!(state.eval(&load_expr).unwrap(), Data::Value(bv(7))); assert_eq!(
state
.load_value(&Var(register("RSP")), ByteSize::new(8))
.unwrap(),
Data::Value(bv(7))
);
assert_eq!( assert_eq!(
state state
.memory .memory
.get_value( .get_value(
&Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-8))), &Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-8))),
64 ByteSize::new(8)
) )
.unwrap(), .unwrap(),
Data::Value(bv(7)) Data::Value(bv(7))
...@@ -147,15 +134,18 @@ fn state() { ...@@ -147,15 +134,18 @@ fn state() {
.memory .memory
.get_value( .get_value(
&Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-16))), &Data::Pointer(PointerDomain::new(new_id("time0", "callee"), bv(-16))),
64 ByteSize::new(8)
) )
.unwrap(), .unwrap(),
Data::new_top(64) Data::new_top(ByteSize::new(8))
); );
state state.memory.add_abstract_object(
.memory new_id("time0", "heap_obj"),
.add_abstract_object(new_id("time0", "heap_obj"), bv(0), ObjectType::Heap, 64); bv(0),
ObjectType::Heap,
ByteSize::new(8),
);
assert_eq!(state.memory.get_num_objects(), 3); assert_eq!(state.memory.get_num_objects(), 3);
state.remove_unreferenced_objects(); state.remove_unreferenced_objects();
assert_eq!(state.memory.get_num_objects(), 2); assert_eq!(state.memory.get_num_objects(), 2);
...@@ -163,7 +153,7 @@ fn state() { ...@@ -163,7 +153,7 @@ fn state() {
#[test] #[test]
fn handle_store() { fn handle_store() {
use crate::bil::Expression::*; use Expression::*;
let mut state = State::new(&register("RSP"), Tid::new("time0")); let mut state = State::new(&register("RSP"), Tid::new("time0"));
let stack_id = new_id("time0", "RSP"); let stack_id = new_id("time0", "RSP");
assert_eq!( assert_eq!(
...@@ -187,31 +177,34 @@ fn handle_store() { ...@@ -187,31 +177,34 @@ fn handle_store() {
); );
state state
.handle_store_exp(&store_exp(reg_add("RSP", 8), Const(Bitvector::from_i64(1)))) .handle_store(&reg_add("RSP", 8), &Const(Bitvector::from_i64(1)))
.unwrap(); .unwrap();
state state
.handle_store_exp(&store_exp(reg_sub("RSP", 8), Const(Bitvector::from_i64(2)))) .handle_store(&reg_sub("RSP", 8), &Const(Bitvector::from_i64(2)))
.unwrap(); .unwrap();
state state
.handle_store_exp(&store_exp( .handle_store(&reg_add("RSP", -16), &Const(Bitvector::from_i64(3)))
reg_add("RSP", -16),
Const(Bitvector::from_i64(3)),
))
.unwrap(); .unwrap();
state state
.handle_register_assign(&register("RSP"), &reg_sub("RSP", 4)) .handle_register_assign(&register("RSP"), &reg_sub("RSP", 4))
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
state.eval(&load_exp(reg_add("RSP", 12))).unwrap(), state
.load_value(&reg_add("RSP", 12), ByteSize::new(8))
.unwrap(),
bv(1).into() bv(1).into()
); );
assert_eq!( assert_eq!(
state.eval(&load_exp(reg_sub("RSP", 4))).unwrap(), state
.load_value(&reg_sub("RSP", 4), ByteSize::new(8))
.unwrap(),
bv(2).into() bv(2).into()
); );
assert_eq!( assert_eq!(
state.eval(&load_exp(reg_add("RSP", -12))).unwrap(), state
.load_value(&reg_add("RSP", -12), ByteSize::new(8))
.unwrap(),
bv(3).into() bv(3).into()
); );
} }
...@@ -219,44 +212,54 @@ fn handle_store() { ...@@ -219,44 +212,54 @@ fn handle_store() {
#[test] #[test]
fn handle_caller_stack_stores() { fn handle_caller_stack_stores() {
use super::super::object::ObjectType; use super::super::object::ObjectType;
use crate::bil::Expression::*; use Expression::*;
let mut state = State::new(&register("RSP"), Tid::new("time0")); let mut state = State::new(&register("RSP"), Tid::new("time0"));
state state.memory.add_abstract_object(
.memory new_id("caller1", "RSP"),
.add_abstract_object(new_id("caller1", "RSP"), bv(0), ObjectType::Stack, 64); bv(0),
state ObjectType::Stack,
.memory ByteSize::new(8),
.add_abstract_object(new_id("caller2", "RSP"), bv(0), ObjectType::Stack, 64); );
state.memory.add_abstract_object(
new_id("caller2", "RSP"),
bv(0),
ObjectType::Stack,
ByteSize::new(8),
);
state.caller_stack_ids.insert(new_id("caller1", "RSP")); state.caller_stack_ids.insert(new_id("caller1", "RSP"));
state.caller_stack_ids.insert(new_id("caller2", "RSP")); state.caller_stack_ids.insert(new_id("caller2", "RSP"));
// store something on the caller stack // store something on the caller stack
state state
.handle_store_exp(&store_exp( .handle_store(&reg_add("RSP", 8), &Const(Bitvector::from_i64(42)))
reg_add("RSP", 8),
Const(Bitvector::from_i64(42)),
))
.unwrap(); .unwrap();
// check that it was saved in all caller objects and not on the callee stack object // check that it was saved in all caller objects and not on the callee stack object
let pointer = PointerDomain::new(new_id("time0", "RSP"), bv(8)).into(); let pointer = PointerDomain::new(new_id("time0", "RSP"), bv(8)).into();
assert_eq!( assert_eq!(
state.memory.get_value(&pointer, 64).unwrap(), state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
Data::new_top(64) Data::new_top(ByteSize::new(8))
); );
let pointer = PointerDomain::new(new_id("caller1", "RSP"), bv(8)).into(); let pointer = PointerDomain::new(new_id("caller1", "RSP"), bv(8)).into();
assert_eq!(state.memory.get_value(&pointer, 64).unwrap(), bv(42).into()); assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
bv(42).into()
);
let pointer = PointerDomain::new(new_id("caller2", "RSP"), bv(8)).into(); let pointer = PointerDomain::new(new_id("caller2", "RSP"), bv(8)).into();
assert_eq!(state.memory.get_value(&pointer, 64).unwrap(), bv(42).into()); assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
bv(42).into()
);
// accessing through a positive stack register offset should yield the value of the caller stacks // accessing through a positive stack register offset should yield the value of the caller stacks
assert_eq!( assert_eq!(
state.eval(&load_exp(reg_add("RSP", 8))).unwrap(), state
.load_value(&reg_add("RSP", 8), ByteSize::new(8))
.unwrap(),
bv(42).into() bv(42).into()
); );
} }
#[test] #[test]
fn clear_parameters_on_the_stack_on_extern_calls() { fn clear_parameters_on_the_stack_on_extern_calls() {
use crate::bil::Expression::*; use Expression::*;
use crate::term::{Arg, ArgIntent};
let mut state = State::new(&register("RSP"), Tid::new("time0")); let mut state = State::new(&register("RSP"), Tid::new("time0"));
state.register.insert( state.register.insert(
register("RSP"), register("RSP"),
...@@ -264,33 +267,35 @@ fn clear_parameters_on_the_stack_on_extern_calls() { ...@@ -264,33 +267,35 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
); );
// write something onto the stack // write something onto the stack
state state
.handle_store_exp(&store_exp( .handle_store(&reg_add("RSP", 8), &Const(Bitvector::from_i64(42)))
reg_add("RSP", 8),
Const(Bitvector::from_i64(42)),
))
.unwrap(); .unwrap();
// create an extern symbol which uses the value on the stack as a parameter // create an extern symbol which uses the value on the stack as a parameter
let argument = Arg { let stack_param = Arg::Stack {
var: register("my_argument"), offset: 8,
location: reg_add("RSP", 8), size: ByteSize::new(8),
intent: ArgIntent::Input,
}; };
let extern_symbol = ExternSymbol { let extern_symbol = ExternSymbol {
tid: Tid::new("symbol"), tid: Tid::new("symbol"),
address: "some_address".into(),
name: "my_extern_symbol".into(), name: "my_extern_symbol".into(),
calling_convention: None, calling_convention: None,
arguments: vec![argument], parameters: vec![stack_param],
return_values: Vec::new(),
no_return: false,
}; };
// check the value before // check the value before
let pointer = PointerDomain::new(new_id("time0", "RSP"), bv(-12)).into(); let pointer = PointerDomain::new(new_id("time0", "RSP"), bv(-12)).into();
assert_eq!(state.memory.get_value(&pointer, 64).unwrap(), bv(42).into()); assert_eq!(
state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
bv(42).into()
);
// clear stack parameter // clear stack parameter
state.clear_stack_parameter(&extern_symbol).unwrap(); state
.clear_stack_parameter(&extern_symbol, &register("RSP"))
.unwrap();
// check the value after // check the value after
assert_eq!( assert_eq!(
state.memory.get_value(&pointer, 64).unwrap(), state.memory.get_value(&pointer, ByteSize::new(8)).unwrap(),
Data::new_top(64) Data::new_top(ByteSize::new(8))
); );
} }
...@@ -298,9 +303,12 @@ fn clear_parameters_on_the_stack_on_extern_calls() { ...@@ -298,9 +303,12 @@ fn clear_parameters_on_the_stack_on_extern_calls() {
fn merge_callee_stack_to_caller_stack() { fn merge_callee_stack_to_caller_stack() {
use super::super::object::ObjectType; use super::super::object::ObjectType;
let mut state = State::new(&register("RSP"), Tid::new("callee")); let mut state = State::new(&register("RSP"), Tid::new("callee"));
state state.memory.add_abstract_object(
.memory new_id("callsite", "RSP"),
.add_abstract_object(new_id("callsite", "RSP"), bv(52), ObjectType::Stack, 64); bv(52),
ObjectType::Stack,
ByteSize::new(8),
);
state.caller_stack_ids.insert(new_id("callsite", "RSP")); state.caller_stack_ids.insert(new_id("callsite", "RSP"));
// check the state before merging to the caller stack // check the state before merging to the caller stack
assert_eq!( assert_eq!(
......
...@@ -12,7 +12,7 @@ fn run_pointer_inference(program_jsonbuilder_val: ocaml::Value) -> (Vec<CweWarni ...@@ -12,7 +12,7 @@ fn run_pointer_inference(program_jsonbuilder_val: ocaml::Value) -> (Vec<CweWarni
serde_json::from_value(program_json).expect("Project deserialization failed"); serde_json::from_value(program_json).expect("Project deserialization failed");
project.replace_let_bindings(); project.replace_let_bindings();
crate::analysis::pointer_inference::run(&project, false) crate::analysis::pointer_inference::run(&project.into(), false)
} }
caml!(rs_run_pointer_inference(program_jsonbuilder_val) { caml!(rs_run_pointer_inference(program_jsonbuilder_val) {
...@@ -31,7 +31,7 @@ fn run_pointer_inference_and_print_debug(program_jsonbuilder_val: ocaml::Value) ...@@ -31,7 +31,7 @@ fn run_pointer_inference_and_print_debug(program_jsonbuilder_val: ocaml::Value)
serde_json::from_value(program_json).expect("Project deserialization failed"); serde_json::from_value(program_json).expect("Project deserialization failed");
project.replace_let_bindings(); project.replace_let_bindings();
crate::analysis::pointer_inference::run(&project, true); // Note: This discard all CweWarnings and log messages. crate::analysis::pointer_inference::run(&project.into(), true); // Note: This discard all CweWarnings and log messages.
} }
caml!(rs_run_pointer_inference_and_print_debug(program_jsonbuilder_val) { caml!(rs_run_pointer_inference_and_print_debug(program_jsonbuilder_val) {
......
...@@ -52,6 +52,32 @@ pub enum Expression { ...@@ -52,6 +52,32 @@ pub enum Expression {
}, },
} }
impl Expression {
/// Return the size (in bytes) of the result value of the expression.
pub fn bytesize(&self) -> ByteSize {
use BinOpType::*;
use Expression::*;
match self {
Var(var) => var.size,
Const(bitvec) => bitvec.width().into(),
BinOp { op, lhs, rhs } => match op {
Piece => lhs.bytesize() + rhs.bytesize(),
IntEqual | IntNotEqual | IntLess | IntSLess | IntLessEqual | IntSLessEqual
| IntCarry | IntSCarry | IntSBorrow | BoolXOr | BoolOr | BoolAnd | FloatEqual
| FloatNotEqual | FloatLess | FloatLessEqual => ByteSize::new(1),
IntAdd | IntSub | IntAnd | IntOr | IntXOr | IntLeft | IntRight | IntSRight
| IntMult | IntDiv | IntRem | IntSDiv | IntSRem | FloatAdd | FloatSub
| FloatMult | FloatDiv => lhs.bytesize(),
},
UnOp { op, arg } => match op {
UnOpType::FloatNaN => ByteSize::new(1),
_ => arg.bytesize(),
},
Cast { size, .. } | Unknown { size, .. } | Subpiece { size, .. } => *size,
}
}
}
/// The type/mnemonic of a binary operation /// The type/mnemonic of a binary operation
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum BinOpType { pub enum BinOpType {
......
...@@ -73,6 +73,12 @@ impl From<ByteSize> for apint::BitWidth { ...@@ -73,6 +73,12 @@ impl From<ByteSize> for apint::BitWidth {
} }
} }
impl From<apint::BitWidth> for ByteSize {
fn from(bitwidth: apint::BitWidth) -> ByteSize {
ByteSize::new(bitwidth.to_usize() as u64 / 8)
}
}
impl ByteSize { impl ByteSize {
pub fn new(value: u64) -> ByteSize { pub fn new(value: u64) -> ByteSize {
ByteSize(value) ByteSize(value)
......
...@@ -181,6 +181,30 @@ pub struct ExternSymbol { ...@@ -181,6 +181,30 @@ pub struct ExternSymbol {
pub no_return: bool, pub no_return: bool,
} }
impl ExternSymbol {
/// If the extern symbol has exactly one return value that is passed in a register,
/// return the register.
pub fn get_unique_return_register(&self) -> Result<&Variable, Error> {
if self.return_values.len() == 1 {
match self.return_values[0] {
Arg::Register(ref var) => Ok(var),
Arg::Stack { .. } => Err(anyhow!("Return value is passed on the stak")),
}
} else {
Err(anyhow!("Wrong number of return values"))
}
}
/// If the extern symbol has exactly one parameter, return the parameter.
pub fn get_unique_parameter(&self) -> Result<&Arg, Error> {
if self.parameters.len() == 1 {
Ok(&self.parameters[0])
} else {
Err(anyhow!("Wrong number of parameter values"))
}
}
}
/// The `Program` structure represents a disassembled binary. /// The `Program` structure represents a disassembled binary.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
pub struct Program { pub struct Program {
...@@ -205,4 +229,19 @@ pub struct Project { ...@@ -205,4 +229,19 @@ pub struct Project {
pub cpu_architecture: String, pub cpu_architecture: String,
/// The stack pointer register for the given CPU architecture. /// The stack pointer register for the given CPU architecture.
pub stack_pointer_register: Variable, pub stack_pointer_register: Variable,
/// The names of callee-saved registers for the standard calling convention
/// for the given CPU architecture.
/// Note that this field may be removed in the future.
pub callee_saved_registers: Vec<String>,
/// The names of parameter registers for the standard calling convention
/// for the given CPU architecture.
/// Note that this field may be removed in the future.
pub parameter_registers: Vec<String>,
}
impl Project {
/// Return the size (in bytes) for pointers of the given architecture.
pub fn get_pointer_bytesize(&self) -> ByteSize {
self.stack_pointer_register.size
}
} }
...@@ -9,7 +9,7 @@ use crate::prelude::*; ...@@ -9,7 +9,7 @@ use crate::prelude::*;
/// Temporary variables are only valid until the end of the current assembly instruction. /// Temporary variables are only valid until the end of the current assembly instruction.
/// However, one assembly instruction may span more than one basic block in the intermediate representation /// However, one assembly instruction may span more than one basic block in the intermediate representation
/// (but never more than one function). /// (but never more than one function).
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct Variable { pub struct Variable {
pub name: String, pub name: String,
pub size: ByteSize, pub size: ByteSize,
......
...@@ -21,6 +21,7 @@ mod prelude { ...@@ -21,6 +21,7 @@ mod prelude {
pub use serde::{Deserialize, Serialize}; pub use serde::{Deserialize, Serialize};
pub use crate::bil::{BitSize, Bitvector}; pub use crate::bil::{BitSize, Bitvector};
pub use crate::intermediate_representation::ByteSize;
pub use crate::intermediate_representation::{Term, Tid}; pub use crate::intermediate_representation::{Term, Tid};
pub use anyhow::{anyhow, Error}; pub use anyhow::{anyhow, Error};
} }
...@@ -375,10 +375,15 @@ impl From<Project> for IrProject { ...@@ -375,10 +375,15 @@ impl From<Project> for IrProject {
tid: project.program.tid, tid: project.program.tid,
term: project.program.term.into(), term: project.program.term.into(),
}; };
let (params, callee_saved) = crate::utils::get_generic_parameter_and_callee_saved_register(
&project.cpu_architecture,
);
IrProject { IrProject {
program, program,
cpu_architecture: project.cpu_architecture, cpu_architecture: project.cpu_architecture,
stack_pointer_register: project.stack_pointer_register.into(), stack_pointer_register: project.stack_pointer_register.into(),
callee_saved_registers: callee_saved,
parameter_registers: params,
} }
} }
} }
...@@ -657,11 +662,11 @@ mod tests { ...@@ -657,11 +662,11 @@ mod tests {
} }
}, },
"stack_pointer_register": { "stack_pointer_register": {
"name": "ESP", "name": "RSP",
"size": 32, "size": 8,
"is_virtual": false "is_virtual": false
}, },
"cpu_architecture": "x86_32" "cpu_architecture": "x86_64"
} }
"#, "#,
) )
......
...@@ -358,6 +358,8 @@ impl From<Project> for IrProject { ...@@ -358,6 +358,8 @@ impl From<Project> for IrProject {
program, program,
cpu_architecture: project.cpu_architecture, cpu_architecture: project.cpu_architecture,
stack_pointer_register: project.stack_pointer_register.into(), stack_pointer_register: project.stack_pointer_register.into(),
callee_saved_registers: project.callee_saved_registers,
parameter_registers: project.parameter_registers,
} }
} }
} }
......
pub mod log; pub mod log;
/// Get the names of parameter registers and callee saved registers
/// of the standard calling convention for the given architecture.
///
/// The registers are read from a configuration file.
pub fn get_generic_parameter_and_callee_saved_register(
cpu_architecture: &str,
) -> (Vec<String>, Vec<String>) {
let project_dirs = directories::ProjectDirs::from("", "", "cwe_checker")
.expect("Could not discern location of configuration files.");
let config_dir = project_dirs.config_dir();
let register_config_path = config_dir.join("registers.json");
let file = std::fs::read_to_string(register_config_path)
.expect("Could not read register configuration file");
let mut registers_json: serde_json::Value = serde_json::from_str(&file).unwrap();
match cpu_architecture {
"x86" | "x86_32" => registers_json = registers_json["elf"]["x86"]["cdecl"].clone(),
_ => registers_json = registers_json["elf"][cpu_architecture].clone(),
}
let mut callee_saved: Vec<String> =
serde_json::from_value(registers_json["callee_saved"].clone()).unwrap();
let mut callee_saved_float: Vec<String> =
serde_json::from_value(registers_json["float_callee_saved"].clone()).unwrap();
callee_saved.append(&mut callee_saved_float);
let mut params: Vec<String> = serde_json::from_value(registers_json["params"].clone()).unwrap();
let mut params_float: Vec<String> =
serde_json::from_value(registers_json["float_params"].clone()).unwrap();
params.append(&mut params_float);
(params, callee_saved)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment