Staging
v0.5.1
v0.5.1
opam+https://opam.ocaml.org/packages/binsec/
dba_types.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 Dba
module Logger = Logger.Make (struct
let name = "dba"
end)
type instruction_sequence = (Dba.address * Dba.Instr.t) list
let malloc_id = ref 0
module Caddress = struct
module X = struct
type t = Dba.address
let compare a1 a2 =
let c = Virtual_address.compare a1.base a2.base in
if c = 0 then a1.id - a2.id else c
end
include Basic_types.Collection_make.Default (X)
let compare = X.compare
let create base id =
assert (id >= 0);
{ base; id }
let rebase a base = create base a.id
let reid a id = create a.base id
let block_start bv = create bv 0
let base_value addr = addr.base
let equal caddr1 caddr2 = compare caddr1 caddr2 = 0
let pp_base ppf v = Format.fprintf ppf "%a" Virtual_address.pp v.base
let add_int a n = rebase a (Virtual_address.add_int n a.base)
let add_id a n = reid a (n + a.id)
let block_start_of_int n = block_start (Virtual_address.create n)
let default_init = ref (block_start_of_int 0)
let to_virtual_address caddr = base_value caddr
let of_virtual_address n = block_start n
end
module Call_stack = struct
exception Not_equal_stacks of int
type t = (Caddress.t * Caddress.t) list
let compare stack1 stack2 =
let len1 = List.length stack1 and len2 = List.length stack2 in
if len1 <> len2 then len1 - len2
else
try
List.iter2
(fun (a1, a2) (aa1, aa2) ->
if Caddress.compare a1 aa1 = 0 then
if Caddress.compare a2 aa2 = 0 then ()
else raise (Not_equal_stacks (Caddress.compare a2 aa2))
else raise (Not_equal_stacks (Caddress.compare a1 aa1)))
stack1 stack2;
0
with
| Not_equal_stacks c -> c
| Invalid_argument _ -> failwith "not equal lists in compare_stacks!"
let _equal stack1 stack2 = compare stack1 stack2 = 0
let rec pp fmt = function
| [] -> Format.fprintf fmt "main"
| (_caller, callee) :: stack ->
Format.fprintf fmt "%a %a" pp stack Dba_printer.Ascii.pp_code_address
callee
end
(* Region *)
module ComparableRegion = struct
type t = Dba.region
let compare r1 r2 =
match (r1, r2) with
| `Constant, `Constant -> 0
| `Stack, `Stack -> 0
| `Malloc ((id1, _), _), `Malloc ((id2, _), _) -> compare id1 id2
| `Constant, _ -> 1
| `Stack, `Constant -> -1
| `Stack, _ -> 1
| `Malloc _, _ -> -1
end
module Region = struct
include Basic_types.Collection_make.Default (ComparableRegion)
let malloc _sz =
let a = Caddress.block_start (Virtual_address.create (-1)) in
`Malloc ((-1, a), Z.zero)
let pp ppf = function
| `Constant -> Format.fprintf ppf "constant"
| `Malloc _ -> Format.fprintf ppf "heap"
| `Stack -> Format.fprintf ppf "stack"
end
(* Rights *)
module Rights = struct
type action = R | W | X
include Map.Make (struct
type t = action * Dba.region
let compare (a1, b1) (a2, b2) =
let i = Region.compare b1 b2 in
if i = 0 then compare a1 a2 else i
end)
let find_right right v m = find (right, v) m
let find_read_right v m = find_right R v m
let find_write_right v m = find_right W v m
let find_exec_right v m = find_right X v m
end
module Expr : sig
include Sigs.PRINTABLE with type t := Dba.Expr.t
type t = Dba.Expr.t
val var : string -> Size.Bit.t -> Dba.VarTag.t -> t
val flag : ?bits:Size.Bit.t -> string -> t
val temporary : string -> Size.Bit.t -> t
val sext : t -> Size.Bit.t -> t
val uext : t -> Size.Bit.t -> t
val bool_false : t
val bool_true : t
val temp : Size.Bit.t -> t
val is_symbolic : t -> bool
val of_lvalue : Dba.LValue.t -> t
val is_zero : t -> bool
val is_one : t -> bool
val is_max : t -> bool
val variables : t -> Basic_types.String.Set.t
val temporaries : t -> Basic_types.String.Set.t
end = struct
type t = Dba.Expr.t
let var name nbits tag =
let sz = Size.Bit.to_int nbits in
Dba.Expr.var name sz ~tag
let flag ?(bits = Size.Bit.bits1) flagname = var flagname bits Dba.VarTag.Flag
let temporary tempname nbits =
let size = Size.Bit.to_int nbits in
Dba.Expr.temporary ~size tempname
let pp = Dba_printer.Ascii.pp_bl_term
let sext e bitsize = Dba.Expr.sext (Size.Bit.to_int bitsize) e
let uext e bitsize = Dba.Expr.uext (Size.Bit.to_int bitsize) e
let temp size =
let name = Format.asprintf "temp%a" Size.Bit.pp size in
temporary name size
let bool_true = Dba.Expr.one
let bool_false = Dba.Expr.zero
open! Dba
let rec is_symbolic = function
| Expr.Var _ | Expr.Load _ -> true
| Expr.Unary (_, e) -> is_symbolic e
| Expr.Binary (_, e1, e2) -> is_symbolic e1 || is_symbolic e2
| Expr.Ite (c, e1, e2) -> is_symbolic c || is_symbolic e1 || is_symbolic e2
| _ -> false
let of_lvalue = function
| LValue.Var { name; size; info = tag } -> Expr.var name size ~tag
| LValue.Restrict (v, { Interval.lo = o1; Interval.hi = o2 }) ->
Expr.restrict o1 o2 (Expr.var v.name v.size ~tag:v.info)
| LValue.Store (sz, endiannness, e) ->
let bysz = Size.Byte.create sz in
Expr.load bysz endiannness e
let is_zero = function Expr.Cst bv -> Bitvector.is_zeros bv | _ -> false
let is_one = function Expr.Cst bv -> Bitvector.is_ones bv | _ -> false
let is_max = function
| Dba.Expr.Cst bv -> Bitvector.is_max_ubv bv
| _ -> false
let rec variables = function
| Dba.Expr.Cst _ -> Basic_types.String.Set.empty
| Dba.Expr.Unary (_, e) | Dba.Expr.Load (_, _, e) -> variables e
| Dba.Expr.Var { name; _ } -> Basic_types.String.Set.singleton name
| Dba.Expr.Binary (_, e1, e2) | Dba.Expr.Ite (_, e1, e2) ->
Basic_types.String.Set.union (variables e1) (variables e2)
let rec temporaries = function
| Dba.Expr.Cst _ -> Basic_types.String.Set.empty
| Dba.Expr.Unary (_, e) | Dba.Expr.Load (_, _, e) -> temporaries e
| Dba.Expr.Var { name; info = Dba.VarTag.Temp; _ } ->
Basic_types.String.Set.singleton name
| Dba.Expr.Var _ -> Basic_types.String.Set.empty
| Dba.Expr.Binary (_, e1, e2) | Dba.Expr.Ite (_, e1, e2) ->
Basic_types.String.Set.union (temporaries e1) (temporaries e2)
end
module LValue = struct
type t = LValue.t
module Map = Map.Make (struct
type t = Dba.LValue.t
let compare = compare
end)
let bitsize lval =
let sz =
match lval with
| LValue.Var v -> v.size
| LValue.Store (size, _, _) -> 8 * size
| LValue.Restrict (_, { Interval.lo; Interval.hi }) -> hi - lo + 1
in
Size.Bit.create sz
let unsafe_bitsize lval = bitsize lval |> Size.Bit.to_int
let _pp = Dba_printer.Ascii.pp_lhs
let name_of = function
| LValue.Var { name; _ } | LValue.Restrict ({ name; _ }, _) -> Some name
| LValue.Store _ -> None
let is_temporary = function
| LValue.Var { info = VarTag.Temp; _ } -> true
| LValue.Var _ | LValue.Restrict _ | LValue.Store _ -> false
let is_flag = function
| LValue.Var { info = VarTag.Flag; _ } -> true
| LValue.Var _ | LValue.Restrict _ | LValue.Store _ -> false
let variables = function
| LValue.Var { name; _ } | LValue.Restrict ({ name; _ }, _) ->
Basic_types.String.Set.singleton name
| LValue.Store (_, _, e) -> Expr.variables e
let temporaries = function
| LValue.Var { name; info = VarTag.Temp; _ } ->
Basic_types.String.Set.singleton name
| LValue.Var _ | LValue.Restrict _ -> Basic_types.String.Set.empty
(* Restrict cannot be applied to temporaries : check that! *)
| LValue.Store (_, _, e) -> Expr.temporaries e
end
module ComparableAddressStack = struct
type t = Caddress.t * Call_stack.t * int
let compare (a1, stack1, loop1) (a2, stack2, loop2) =
let c = Call_stack.compare stack1 stack2 in
if c = 0 then
let c' = Caddress.compare a1 a2 in
if c' = 0 then compare loop1 loop2 else c'
else c
end
module AddressStack = struct
include Basic_types.Collection_make.Default (ComparableAddressStack)
let pp fmt (caddr, call_stack, n) =
Format.fprintf fmt "@[<hov 2>%a@,<%a>@,(%d)@]"
Dba_printer.Ascii.pp_code_address caddr Call_stack.pp call_stack n
end
type 'a dbainstrmap = (Dba.Instr.t * 'a option) Caddress.Map.t
module Declarations = struct
type t = (Dba.size * Dba.VarTag.t) Basic_types.String.Map.t
open Basic_types.String.Map
let of_list declarations =
List.fold_left
(fun smap (id, size, opttags) -> add id (size, opttags) smap)
empty declarations
end
module Jump_target = struct
let outer_jumps = function
| JInner _ -> Virtual_address.Set.empty
| JOuter a -> Virtual_address.Set.singleton (Caddress.to_virtual_address a)
end
type ('a, 'b) defuse = { defs : 'a; uses : 'b }
module Instruction = struct
type t = Dba.Instr.t
let set_successor i id =
let open Dba.Instr in
match i with
| Assign (lv, e, _) -> assign lv e id
| If (c, jt, _) -> ite c jt id
| Assert (c, _) -> _assert c id
| Assume (c, _) -> assume c id
| NondetAssume (lvs, c, _) -> non_deterministic_assume lvs c id
| Nondet (lv, region, _) -> non_deterministic lv ~region id
| Undef (lv, _) -> undefined lv id
| Malloc (lv, e, _) -> malloc lv e id
| Free (e, _) -> free e id
| Print (ds, _) -> print ds id
| Stop _ | SJump _ | DJump _ -> i
let reloc =
let open Dba.Instr in
fun ?(outer = fun x -> x) ?(inner = fun x -> x) -> function
| Assign (lv, e, id) -> assign lv e (inner id)
| If (c, (Dba.JOuter _ as jt), id) -> ite c (outer jt) (inner id)
| If (c, Dba.JInner goto, id) ->
ite c (Dba.Jump_target.inner (inner goto)) (inner id)
| Assert (c, id) -> _assert c (inner id)
| Assume (c, id) -> assume c (inner id)
| NondetAssume (lvs, c, id) -> non_deterministic_assume lvs c (inner id)
| Nondet (lv, region, id) -> non_deterministic lv ~region (inner id)
| Undef (lv, id) -> undefined lv (inner id)
| Malloc (lv, e, id) -> malloc lv e (inner id)
| Free (e, id) -> free e (inner id)
| Print (ds, id) -> print ds (inner id)
| SJump (Dba.JInner goto, tag) -> static_inner_jump ?tag (inner goto)
| SJump ((Dba.JOuter _ as j), tag) -> static_jump ?tag (outer j)
| (DJump _ | Stop _) as i -> i
let generic_reset_successors ~p ~f instr =
let new_id id = if p id then f id else id in
let new_jt = function
| JOuter _ as jt -> jt
| JInner id -> Dba.Jump_target.inner (new_id id)
in
let open Dba.Instr in
match instr with
| Assign (lv, e, id) -> assign lv e (new_id id)
| If (c, jt, id) -> ite c (new_jt jt) (new_id id)
| Assert (c, id) -> _assert c (new_id id)
| Assume (c, id) -> assume c (new_id id)
| NondetAssume (lvs, c, id) -> non_deterministic_assume lvs c (new_id id)
| Nondet (lv, region, id) -> non_deterministic lv ~region (new_id id)
| Undef (lv, id) -> undefined lv (new_id id)
| Malloc (lv, e, id) -> malloc lv e (new_id id)
| Free (e, id) -> free e (new_id id)
| Print (ds, id) -> print ds (new_id id)
| SJump (jt, tag) -> static_jump ?tag (new_jt jt)
| Stop _ | DJump _ -> instr
let reset_successor ~src_id ~dst_id instr =
generic_reset_successors ~p:(( = ) src_id) ~f:(fun _ -> dst_id) instr
let successors = function
| Instr.Assign (_, _, i)
| Instr.Assert (_, i)
| Instr.Assume (_, i)
| Instr.NondetAssume (_, _, i)
| Instr.Nondet (_, _, i)
| Instr.Undef (_, i)
| Instr.Malloc (_, _, i)
| Instr.Free (_, i)
| Instr.Print (_, i) ->
[ Dba.Jump_target.inner i ]
| Instr.Stop _ -> []
| Instr.SJump (jt, _) -> [ jt ]
| Instr.If (_, jt, i) -> [ jt; Dba.Jump_target.inner i ]
| Instr.DJump _ -> []
let no_defs uses = { defs = Basic_types.String.Set.empty; uses }
let variables = function
| Instr.Malloc (lv, e, _) | Instr.Assign (lv, e, _) ->
{ defs = LValue.variables lv; uses = Expr.variables e }
| Instr.Print _ | Instr.Stop _ | Instr.SJump _ ->
no_defs Basic_types.String.Set.empty
| Instr.Free (e, _) | Instr.DJump (e, _) -> no_defs (Expr.variables e)
| Instr.If (c, _, _) | Instr.Assert (c, _) | Instr.Assume (c, _) ->
no_defs (Expr.variables c)
| Instr.NondetAssume (lvals, c, _) ->
{
defs =
List.fold_left
(fun s lv -> Basic_types.String.Set.union s (LValue.variables lv))
Basic_types.String.Set.empty lvals;
uses = Expr.variables c;
}
| Instr.Nondet (lval, _, _) | Instr.Undef (lval, _) ->
{ defs = LValue.variables lval; uses = Basic_types.String.Set.empty }
let temporaries = function
| Instr.Malloc (lv, e, _) | Instr.Assign (lv, e, _) ->
{ defs = LValue.temporaries lv; uses = Expr.temporaries e }
| Instr.Print _ | Instr.Stop _ | Instr.SJump _ ->
{
defs = Basic_types.String.Set.empty;
uses = Basic_types.String.Set.empty;
}
| Instr.Free (e, _) | Instr.DJump (e, _) ->
{ defs = Basic_types.String.Set.empty; uses = Expr.temporaries e }
| Instr.If (c, _, _) | Instr.Assert (c, _) | Instr.Assume (c, _) ->
{ defs = Basic_types.String.Set.empty; uses = Expr.temporaries c }
| Instr.NondetAssume (lvals, c, _) ->
{
defs =
List.fold_left
(fun s lv ->
Basic_types.String.Set.union s (LValue.temporaries lv))
Basic_types.String.Set.empty lvals;
uses = Expr.temporaries c;
}
| Instr.Nondet (lval, _, _) | Instr.Undef (lval, _) ->
{ defs = LValue.temporaries lval; uses = Basic_types.String.Set.empty }
let outer_jumps instr =
match instr with
| Instr.Assign _ | Instr.Stop _ | Instr.Assert _ | Instr.Assume _
| Instr.NondetAssume _ | Instr.Nondet _ | Instr.Undef _ | Instr.Free _
| Instr.Malloc _
| Instr.DJump (_, (Some Return | None))
| Instr.Print _ ->
Virtual_address.Set.empty
| Instr.DJump (_, Some (Call a)) ->
Virtual_address.Set.singleton (Caddress.to_virtual_address a)
| Instr.SJump (jt, Some (Call a)) ->
Virtual_address.Set.add
(Caddress.to_virtual_address a)
(Jump_target.outer_jumps jt)
| Instr.SJump (jt, (Some Return | None)) | Instr.If (_, jt, _) ->
Jump_target.outer_jumps jt
let is_call = function
| Instr.SJump (_, Some (Call _)) | Instr.DJump (_, Some (Call _)) -> true
| _ -> false
let is_return = function
| Instr.SJump (_, Some Return)
(* Returns are actually encoded mostly that way *)
| Instr.DJump (_, Some Return) ->
true
| _ -> false
end
module Statement = struct
type t = { location : Caddress.t; instruction : Instr.t }
let create a i = { location = a; instruction = i }
let location li = li.location
let instruction li = li.instruction
let set_instruction li instruction = { li with instruction }
let set_location li location = { li with location }
let pp ppf li =
Format.fprintf ppf "%a: %a" Dba_printer.Ascii.pp_code_address li.location
Dba_printer.Ascii.pp_instruction li.instruction
end
type read_perm = Read of bool
type write_perm = Write of bool
type exec_perm = Exec of bool
type permissions = Dba.Expr.t * (read_perm * write_perm * exec_perm)
type 'a program = {
start_address : Dba.address;
declarations : Declarations.t;
permissions : permissions list Region.Map.t * Dba.Expr.t Rights.t;
initializations : Dba.Instr.t list;
instructions : 'a dbainstrmap;
}