Staging
v0.5.1
v0.5.1
opam+https://opam.ocaml.org/packages/binsec/
senv.ml
(**************************************************************************)
(* This file is part of BINSEC. *)
(* *)
(* Copyright (C) 2016-2021 *)
(* CEA (Commissariat à l'énergie atomique et aux énergies *)
(* alternatives) *)
(* *)
(* you can redistribute it and/or modify it under the terms of the GNU *)
(* Lesser General Public License as published by the Free Software *)
(* Foundation, version 2.1. *)
(* *)
(* It is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU Lesser General Public License for more details. *)
(* *)
(* See the GNU Lesser General Public License version 2.1 *)
(* for more details (enclosed in the file licenses/LGPLv2.1). *)
(* *)
(**************************************************************************)
open Sse_options
module Query_stats = struct
module Preprocess = struct
let sat = ref 0
let unsat = ref 0
let const = ref 0
let total () = !sat + !unsat + !const
let reset () =
sat := 0;
unsat := 0;
const := 0
let pp ppf () =
let open Format in
fprintf ppf
"@[<v 2>@[<h>Preprocessing simplifications@]@,\
@[<h>total %d@]@,\
@[<h>sat %d@]@,\
@[<h>unsat %d@]@,\
@[<h>constant enum %d@]@]" (total ()) !sat !unsat !const
end
module SMT = struct
let sat = ref 0
let unsat = ref 0
let err = ref 0
let time = ref 0.0
let add_time t = time := !time +. t
let total () = !sat + !unsat + !err
let avg_time () = !time /. float (total ())
let reset () =
sat := 0;
unsat := 0;
err := 0;
time := 0.0
let pp ppf () =
let open Format in
fprintf ppf
"@[<v 2>@[<h>Satisfiability queries@]@,\
@[<h>total %d@]@,\
@[<h>sat %d@]@,\
@[<h>unsat %d@]@,\
@[<h>unknown %d@]@,\
@[<h>time %.2f@]@,\
@[<h>average %.2f@]@]" (total ()) !sat !unsat !err !time
(avg_time ())
end
let _reset () =
Preprocess.reset ();
SMT.reset ()
let pp ppf () =
let open Format in
fprintf ppf "@[<v 0>%a@,@,%a@,@]" Preprocess.pp () SMT.pp ()
end
let byte_size = Natural.to_int Basic_types.Constants.bytesize
module S = Basic_types.String.Map
type t = {
formula : Formula.formula;
(* SMT2 formula *)
vsymbols : Formula.bv_term S.t;
(* collection of visible symbols *)
vmemory : Formula.ax_term;
(* visible memory *)
fid : int;
(* unique indice counter *)
fvariables : Formula.bv_var list S.t;
(* collection of free variables *)
fmemory : Formula.ax_var;
(* initial memory *)
model : Smt_model.t; (* a model that satisfy constraints *)
}
(** Symbolic state *)
let empty () =
let word_size = Kernel_options.Machine.word_size () in
let fmemory = Formula.ax_var "__memory_0" word_size byte_size in
{
formula =
Formula.empty
|> Formula.push_back_declare @@ Formula.mk_ax_decl fmemory [];
vsymbols = S.empty;
vmemory = Formula.mk_ax_var fmemory;
fid = 1;
fvariables = S.empty;
fmemory;
model = Smt_model.empty;
}
let do_optimization ?(keep = Formula.VarSet.empty) fm =
let level = 3 in
if Formula.VarSet.is_empty keep then Logger.debug ~level "Optimize"
else
Logger.debug ~level "@[<v 2>Optimize but keep intact these variables:@ %a@]"
Formula_pp.pp_varset keep;
Formula_transformation.optimize_from_options ?is_controlled:None ~keep fm
let fresh name size state =
let v = Formula.bv_var (Printf.sprintf "%s_%d" name state.fid) size in
let fid = state.fid + 1 in
let h =
match S.find name state.fvariables with
| exception Not_found -> [ v ]
| h -> v :: h
in
let fvariables = S.add name h state.fvariables in
let vsymbols = S.add name (Formula.mk_bv_var v) state.vsymbols in
let formula =
state.formula |> Formula.push_front_declare @@ Formula.mk_bv_decl v []
in
{ state with formula; vsymbols; fid; fvariables }
let rec lookup name size state =
match S.find name state.vsymbols with
| exception Not_found -> lookup name size (fresh name size state)
| bv -> (bv, state)
let assign name value state =
let value_size = Formula_utils.bv_size value in
let var = Formula.bv_var (Printf.sprintf "%s_%d" name state.fid) value_size in
let fid = state.fid + 1 in
let vsymbols = S.add name (Formula.mk_bv_var var) state.vsymbols in
let formula =
state.formula |> Formula.push_front_define @@ Formula.mk_bv_def var [] value
in
{ state with formula; vsymbols; fid }
let write ~addr value state =
let addr_size = Formula_utils.bv_size addr
and write_size = Formula_utils.bv_size value / 8 in
let layer =
Formula.ax_var (Printf.sprintf "__memory_%d" state.fid) addr_size byte_size
in
let fid = state.fid + 1 in
let vmemory = Formula.mk_ax_var layer in
let formula =
state.formula
|> Formula.push_front_define @@ Formula.mk_ax_def layer []
@@ Formula.mk_store write_size state.vmemory addr value
in
{ state with formula; vmemory; fid }
let load_from ~addr size state =
let img = Kernel_functions.get_img () in
let at = Bitvector.to_uint addr in
let bytes = Bytes.create size in
for i = 0 to size - 1 do
Bytes.set bytes i @@ Char.unsafe_chr @@ Loader.read_address img @@ (at + i)
done;
let chunk =
byte_size * size
|> Bitvector.create @@ Z.of_bits @@ Bytes.unsafe_to_string bytes
in
let addr_size = Bitvector.size_of addr in
let layer =
Formula.ax_var (Printf.sprintf "__memory_%d" state.fid) addr_size byte_size
in
let vmemory = Formula.mk_ax_var layer in
let fid = state.fid + 1 in
let formula =
state.formula
|> Formula.push_front_define @@ Formula.mk_ax_def layer []
@@ Formula.mk_store size state.vmemory (Formula.mk_bv_cst addr)
(Formula.mk_bv_cst chunk)
in
{ state with formula; vmemory; fid }
module Translate = struct
open Dba
let unary e = function
| Unary_op.Not -> Formula.mk_bv_not
| Unary_op.UMinus -> Formula.mk_bv_neg
| Unary_op.Sext n -> Formula.mk_bv_sign_extend (n - Dba.Expr.size_of e)
| Unary_op.Uext n -> Formula.mk_bv_zero_extend (n - Dba.Expr.size_of e)
| Unary_op.Restrict interval -> Formula.mk_bv_extract interval
let as_bv bop e1 e2 = Formula.(mk_bv_ite (bop e1 e2) mk_bv_one mk_bv_zero)
let rotate_right_const n = Formula.mk_bv_rotate_right n
let rotate_left_const n = Formula.mk_bv_rotate_left n
let rotate shift_func rev_shift_func const_rot_func value shift =
let open Formula in
match shift.bv_term_desc with
| BvCst x ->
let op = Bitvector.value_of x |> Z.to_int |> const_rot_func in
op value
| _ ->
let part1 = shift_func value shift
and shift_size = Formula_utils.bv_size shift
and value_size = Formula_utils.bv_size value |> Z.of_int in
let value_size = Bitvector.create value_size shift_size |> mk_bv_cst in
let offset = mk_bv_sub value_size shift in
let part2 = rev_shift_func value offset in
mk_bv_or part1 part2
let rotate_right =
rotate Formula.mk_bv_lshr Formula.mk_bv_shl rotate_right_const
let rotate_left =
rotate Formula.mk_bv_shl Formula.mk_bv_lshr rotate_left_const
let binary op =
let open Binary_op in
match op with
| Plus -> Formula.mk_bv_add
| Minus -> Formula.mk_bv_sub
| Mult -> Formula.mk_bv_mul
| DivU -> Formula.mk_bv_udiv
| DivS -> Formula.mk_bv_sdiv
| ModU -> Formula.mk_bv_urem
| ModS -> Formula.mk_bv_srem
| Eq -> as_bv Formula.mk_bv_equal
| Diff -> as_bv Formula.mk_bv_distinct
| LeqU -> as_bv Formula.mk_bv_ule
| LtU -> as_bv Formula.mk_bv_ult
| GeqU -> as_bv Formula.mk_bv_uge
| GtU -> as_bv Formula.mk_bv_ugt
| LeqS -> as_bv Formula.mk_bv_sle
| LtS -> as_bv Formula.mk_bv_slt
| GeqS -> as_bv Formula.mk_bv_sge
| GtS -> as_bv Formula.mk_bv_sgt
| Xor -> Formula.mk_bv_xor
| And -> Formula.mk_bv_and
| Or -> Formula.mk_bv_or
| Concat -> Formula.mk_bv_concat
| LShift -> Formula.mk_bv_shl
| RShiftU -> Formula.mk_bv_lshr
| RShiftS -> Formula.mk_bv_ashr
| LeftRotate -> rotate_left
| RightRotate -> rotate_right
let get_attr img attr name =
match attr with
| Dba.VarTag.Value ->
Option.get
@@ Loader_utils.address_of_symbol_or_section_by_name ~name img
| Dba.VarTag.Size ->
Option.get @@ Loader_utils.size_of_symbol_or_section_by_name ~name img
| Dba.VarTag.Last ->
Int.pred @@ Virtual_address.to_int @@ snd @@ Option.get
@@ Loader_utils.interval_of_symbol_or_section_by_name ~name img
let rec expr state e =
let smt_unary = unary and smt_binary = binary in
let open Expr in
match e with
| Var { name; size; info = Symbol attr } ->
( Formula.mk_bv_cst @@ Bitvector.of_int ~size
@@ get_attr (Kernel_functions.get_img ()) attr name,
state )
| Var { name; size; _ } -> lookup name size state
| Cst bv -> (Formula.mk_bv_cst bv, state)
| Load (bytes, _endianness, e) ->
let smt_e, state = expr state e in
(Formula.mk_select bytes state.vmemory smt_e, state)
| Binary (bop, lop, rop) as e ->
Logger.debug ~level:6 "Translating binary %a"
Dba_printer.Ascii.pp_bl_term e;
let l_smt_e, state = expr state lop in
let r_smt_e, state = expr state rop in
(smt_binary bop l_smt_e r_smt_e, state)
| Unary (uop, e) ->
let v, state = expr state e in
(smt_unary e uop v, state)
| Ite (c, then_e, else_e) ->
let cond, state = expr state c in
let then_smt, state = expr state then_e in
let else_smt, state = expr state else_e in
let v =
Formula.(mk_bv_ite (mk_bv_equal cond mk_bv_one) then_smt else_smt)
in
(v, state)
end
module Solver = struct
let value_of_constant cst =
let open Smtlib in
match cst with
| CstHexadecimal s -> Bitvector.of_hexstring ("0x" ^ s)
| CstBinary s -> Bitvector.of_string ("0b" ^ s)
| CstDecimalSize (value, size) ->
Bitvector.create (Z.of_string value) (int_of_string size)
| CstNumeral _ | CstString _ | CstBool _ | CstDecimal _ ->
Logger.error
"Model construction: unexpected constant %a as bitvector value"
Smtlib_pp.pp_spec_constant cst;
exit 2
let extract_bv terms =
let open Smtlib in
match terms with
| [ (_, { term_desc = TermSpecConstant cst; _ }) ] -> value_of_constant cst
| _ -> assert false
let extract_model session vars memory =
let model = Smt_model.create () in
S.iter
(fun _ ->
List.iter (fun var ->
Smt_model.add_var model
(Formula_utils.bv_var_name var)
(extract_bv
(Solver.Session.get_value session
(Formula.mk_bv_term (Formula.mk_bv_var var))))))
vars;
Smt_model.add_memory_term model
(Solver.Session.get_value session
(Formula.mk_ax_term (Formula.mk_ax_var memory)));
model
let with_solver formula f =
let timeout = Formula_options.Solver.Timeout.get () in
let file =
if Sse_options.SMT_dir.is_set () then (
let filename = Sse_utils.temp_file () in
Logger.debug ~level:3 "@[<h>Using SMT script file %s@]" filename;
Some filename)
else None
in
let solver = Formula_options.Solver.get () in
let session = Solver.Session.create ?file ~timeout solver in
Logger.debug ~level:5 "Running %s (file %a)" (Prover.name_of solver)
(Print_utils.pp_opt Format.pp_print_string)
file;
try
Formula.iter_forward (Solver.Session.put_entry session) formula;
let v = Utils.time (fun () -> f session) in
Solver.Session.destroy session;
Some v
with
| Failure msg as e ->
Logger.warning "SMT solver failed on %s" msg;
Solver.Session.destroy session;
if not (Sse_options.KeepGoing.get ()) then
Logger.fatal ~e
"@[<v 0>@[SMT solver failed in %a@ with message:@].@ @[%s@]@ \
@[Aborting. Use -keep-going to ignore@]@]"
(Print_utils.pp_opt Format.pp_print_string)
file msg;
None
| e ->
Solver.Session.destroy session;
Logger.warning "Destroyed session (current SMT file %a)"
(Print_utils.pp_opt Format.pp_print_string)
file;
raise e
let check_satistifiability formula vars memory =
with_solver formula (fun session ->
Logger.debug ~level:5 "%a" Solver.Session.pp session;
match Solver.Session.check_sat session with
| Formula.SAT ->
incr Query_stats.SMT.sat;
Logger.debug ~level:4 "SMT query resulted in SAT";
Some (extract_model session vars memory)
| Formula.UNSAT ->
incr Query_stats.SMT.unsat;
Logger.debug ~level:4 "SMT query resulted in UNSAT";
None
| Formula.UNKNOWN | Formula.TIMEOUT ->
incr Query_stats.SMT.err;
Logger.warning ~level:0 "SMT query resulted in UNKNOWN";
failwith "unknown assertion")
|> function
| None -> None
| Some (time, r) ->
Query_stats.SMT.add_time time;
r
let enumerate e ?(n = 1 lsl Formula_utils.bv_size e) formula vars memory =
with_solver formula (fun session ->
Logger.debug ~level:5 "%a" Solver.Session.pp session;
let rec loop e' n enum =
if n = 0 then enum
else
match Solver.Session.check_sat session with
| Formula.SAT ->
incr Query_stats.SMT.sat;
let term = Solver.Session.get_value session e' in
let bv = extract_bv term in
Logger.debug ~level:5
"Solver returned %a ; %d solutions still to be found"
Bitvector.pp_hex bv (n - 1);
let model = extract_model session vars memory in
Solver.Session.put_entry session
@@ Formula.mk_assert
@@ Formula.mk_bv_distinct e (Formula.mk_bv_cst bv);
loop e' (n - 1) ((bv, model) :: enum)
| Formula.UNSAT ->
incr Query_stats.SMT.unsat;
Logger.debug ~level:4 "Solver returned UNSAT";
enum
| Formula.UNKNOWN | Formula.TIMEOUT ->
incr Query_stats.SMT.err;
Logger.warning ~level:0 "SMT query resulted in UNKNOWN";
failwith "unknown enumeration"
in
loop (Formula.mk_bv_term e) n [])
|> function
| None -> []
| Some (time, values) ->
Query_stats.SMT.add_time time;
values
end
let keep state =
S.fold
(fun _ e k ->
match e with
| { Formula.bv_term_desc = Formula.BvFun (v, []); _ } ->
Formula.VarSet.add (Formula.BvVar v) k
| _ -> assert false)
state.vsymbols
@@ S.fold
(fun _ l k ->
List.fold_left (fun k v -> Formula.VarSet.add (Formula.BvVar v) k) k l)
state.fvariables
@@
match state.vmemory with
| { Formula.ax_term_desc = Formula.AxFun (v, []); _ } ->
Formula.VarSet.add (Formula.AxVar v)
@@ Formula.VarSet.singleton (Formula.AxVar state.fmemory)
| _ -> assert false
let assume e state =
let var = Formula.bl_var (Printf.sprintf "__assume_%d" state.fid) in
let fid = state.fid + 1 in
let formula =
state.formula |> Formula.push_front_define @@ Formula.mk_bl_def var [] e
in
let keep = Formula.VarSet.add (Formula.BlVar var) @@ keep state in
let formula = do_optimization ~keep formula in
match Formula.peek_front formula with
| Some
{
entry_desc =
Formula.Define
{
def_desc =
Formula.BlDef (v, _, { bl_term_desc = Formula.BlTrue; _ });
_;
};
_;
} ->
assert (v = var);
incr Query_stats.Preprocess.sat;
Some { state with formula; fid }
| Some
{
entry_desc =
Formula.Define
{
def_desc =
Formula.BlDef (v, _, { bl_term_desc = Formula.BlFalse; _ });
_;
};
_;
} ->
assert (v = var);
incr Query_stats.Preprocess.unsat;
None
| _ -> (
let formula = Formula.push_front_assert (Formula.mk_bl_var var) formula in
match
Solver.check_satistifiability formula state.fvariables state.fmemory
with
| Some model -> Some { state with formula; fid; model }
| None -> None)
let split_on e ?n ?(except = []) state =
let size = Formula_utils.bv_size e in
let var = Formula.bv_var (Printf.sprintf "__enum_%d" state.fid) size in
let fid = state.fid + 1 in
let formula =
state.formula |> Formula.push_front_define @@ Formula.mk_bv_def var [] e
in
let keep = Formula.VarSet.add (Formula.BvVar var) @@ keep state in
let formula = do_optimization ~keep formula in
match Formula.peek_front formula with
| Some
{
entry_desc =
Formula.Define
{
def_desc =
Formula.BvDef (v, _, { bv_term_desc = Formula.BvCst bv; _ });
_;
};
_;
} ->
assert (v = var);
if Bitvector.is_one bv then incr Query_stats.Preprocess.sat
else if Bitvector.is_zero bv then incr Query_stats.Preprocess.unsat
else incr Query_stats.Preprocess.const;
Logger.debug ~level:4 "Enumeration of %a resolved to constant %a"
Formula_pp.pp_bv_term e Bitvector.pp bv;
[ (bv, { state with formula; fid }) ]
| _ ->
let evar = Formula.mk_bv_var var in
let formula =
List.fold_left
(fun f bv ->
Formula.push_front_assert
(Formula.mk_bv_distinct evar (Formula.mk_bv_cst bv))
f)
formula except
in
List.map (fun (bv, model) ->
let formula =
formula
|> Formula.push_front_assert
@@ Formula.mk_bv_equal evar (Formula.mk_bv_cst bv)
in
(bv, { state with formula; fid; model }))
@@ Solver.enumerate e ?n formula state.fvariables state.fmemory
let assign name e state =
let e, state = Translate.expr state e in
assign name e state
let byteswap e =
let rec loop e size e' =
if size = 0 then e'
else
loop e (size - 8)
(Formula.mk_bv_concat
(Formula.mk_bv_extract { lo = size - 8; hi = size - 1 } e)
e')
in
let size = Formula_utils.bv_size e in
loop e (size - 8) (Formula.mk_bv_extract { lo = size - 8; hi = size - 1 } e)
let write ~addr e dir state =
let addr, state = Translate.expr state addr in
let e, state = Translate.expr state e in
let e =
match dir with Machine.LittleEndian -> e | Machine.BigEndian -> byteswap e
in
write ~addr e state
let assume e state =
let e, state = Translate.expr state e in
assume (Formula.mk_bv_equal e Formula.mk_bv_one) state
let split_on e ?n ?except state =
let e, state = Translate.expr state e in
split_on e ?n ?except state
let pp ppf state = Smt_model.pp ppf state.model
let pp_smt ?slice ppf state =
match slice with
| None -> Formula_pp.pp_formula ppf state.formula
| Some l ->
let keep, state =
List.fold_left
(fun (keep, state) (e, n) ->
let state = assign n e state in
match Formula.peek_front state.formula with
| Some
{
entry_desc =
Formula.Define { def_desc = Formula.BvDef (v, _, _); _ };
_;
} ->
(Formula.VarSet.add (Formula.BvVar v) keep, state)
| _ -> assert false)
(Formula.VarSet.empty, state)
l
in
Formula_pp.pp_formula ppf (do_optimization ~keep state.formula)
let as_ascii name state =
let buf = Buffer.create 16 in
List.iter (fun var ->
let name = Formula_utils.bv_var_name var in
match Smt_model.find_variable state.model name with
| None -> Buffer.add_char buf '.'
| Some bv ->
assert (Bitvector.size_of bv mod byte_size = 0);
let rec iter bv =
let size = Bitvector.size_of bv in
if size = byte_size then Buffer.add_char buf (Bitvector.to_char bv)
else
let byte = Bitvector.extract bv { Interval.lo = 0; hi = 7 } in
Buffer.add_char buf (Bitvector.to_char byte);
iter (Bitvector.extract bv { Interval.lo = 8; hi = size - 1 })
in
iter bv)
@@ List.rev
@@ S.find name state.fvariables;
Buffer.contents buf