package bap-primus-promiscuous

  1. Overview
  2. Docs

Source file primus_promiscuous_main.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
open Core_kernel
open Bap.Std
open Monads.Std
open Bap_primus.Std
open Format
include Self()

let package = "bap"

(*

   In a general case a block is terminated by a sequence of jumps:

   {v
     when C1 jmp D1
     when C2 jmp D2
     ...
     when Cm jmp Dm
   v}

   The IR requires the following invariant:

   {v C1 \/ C2 \/ ... \/ Cm v}.

   The infeasible interpreter is a non-deterministic interperter, that
   for every block B that is terminated with m jumps, will fork m-1
   context after the last definition, so that under the n-th context
   the Dn destination will be taken by the interpreter.

   For the Dm-th destination to be taken, the following condition must
   hold: {v ~C1 /\ ~C2 /\ ... ~C(n-1) /\ Cn v}, where [~] symbol
   denotes logical negation.

   However, we would require an SMT solver to find such contexts. So,
   the interpreter requires a program to be in a trivial condition form
   (TCF). In TCF every jmp condition must be a single variable or a
   constant true.
*)

type assn = {
  use : tid;
  var : var;
  res : bool;
}

type id = Primus.Machine.id
type state = {
  conflicts : assn list;
  visited : Tid.Set.t;
}

let inspect_assn {use; var; res} =
  let var = sprintf "%s: %s" (Tid.to_string use)(Var.to_string var) in
  Sexp.List [Sexp.Atom var; sexp_of_bool res]

let inspect_conflict (assn) =
  Sexp.List (List.map assn ~f:inspect_assn)

let inspect_conflicts cs =
  Sexp.List (List.map ~f:inspect_conflict cs)

let init_visited prog =
  (object inherit [Tid.Set.t] Term.visitor
    method! enter_blk blk visited =
      if Term.has_attr blk Term.visited
      then Set.add visited (Term.tid blk)
      else visited
  end)#run prog Tid.Set.empty

let state = Primus.Machine.State.declare
    ~name:"conflicts"
    ~uuid:"58bb35f4-f259-4712-8d15-bdde1be3caa8" @@
  fun proj ->
  {
    conflicts=[];
    visited = init_visited (Project.program proj)
  }

let neg = List.map ~f:(fun assn -> {assn with res = not assn.res})

let assumptions blk =
  Term.enum jmp_t blk |> Seq.fold ~init:([],[])
    ~f:(fun (assns, assms) jmp -> match Jmp.cond jmp with
        | Bil.Var c ->
          let assn = {use=Term.tid jmp; var=c; res=true} in
          assn :: assns, (assn :: neg assns) :: assms
        | Bil.Int _ -> assns, (neg assns) :: assms
        | _ -> failwith "Not in TCF") |> snd


module TrapPageFault(Machine : Primus.Machine.S) = struct
  module Code = Primus.Linker.Make(Machine)
  let exec =
    Code.unlink (`symbol Primus.Interpreter.pagefault_handler)
end

module DoNothing(Machine : Primus.Machine.S) = struct
  let exec = Machine.return ()
end

module Id = Monad.State.Multi.Id

module Forker(Machine : Primus.Machine.S) = struct
  open Machine.Syntax
  module Eval = Primus.Interpreter.Make(Machine)
  module Env = Primus.Env.Make(Machine)
  module Mem = Primus.Memory.Make(Machine)
  module Linker = Primus.Linker.Make(Machine)

  let assume assns =
    Machine.List.iter assns ~f:(fun assn ->
        Eval.const (Word.of_bool assn.res) >>= fun r ->
        Eval.get assn.var >>= fun r' ->
        let op = if assn.res then Bil.OR else Bil.AND in
        Eval.binop op r r' >>=
        Eval.set assn.var)

  let unsat_assumptions blk =
    Machine.List.map (assumptions blk)
      ~f:(Machine.List.filter ~f:(fun {var;res} ->
          Env.get var >>| Primus.Value.to_word >>| fun r ->
          Word.(r <> of_bool res)))

  let pp_id = Monad.State.Multi.Id.pp

  let do_fork blk ~child =
    Machine.current () >>= fun pid ->
    Machine.Global.get state >>= fun t ->
    if Set.mem t.visited (Term.tid blk)
    then Machine.return pid
    else
      Machine.fork () >>= fun () ->
      Machine.current () >>= fun id ->
      if Id.(pid = id) then Machine.return id
      else
        child () >>= fun () ->
        Machine.switch pid >>| fun () -> id


  let fork blk  =
    unsat_assumptions blk >>=
    Machine.List.iter ~f:(function
        | [] -> Machine.return ()
        | conflicts ->
          Machine.ignore_m @@
          do_fork blk ~child:(fun () ->
              assume  conflicts >>= fun () ->
              Machine.Local.update state ~f:(fun t -> {
                    t with conflicts})))

  let assume_returns blk call =
    match Call.return call with
    | Some (Direct dst) ->
      Machine.current () >>= fun pid ->
      do_fork blk ~child:Machine.return >>= fun id ->
      if Id.(id = pid) then Machine.return ()
      else
        Machine.Global.get state >>= fun {visited} ->
        if Set.mem visited dst
        then Eval.halt >>= never_returns
        else
          Linker.exec (`tid dst) >>= fun () ->
          Eval.halt >>= never_returns
    | _ -> Machine.return ()

  let fork_on_calls blk jmp = match Jmp.kind jmp with
    | Call c -> assume_returns blk c
    | _ -> Machine.return ()

  let is_last blk def = match Term.last def_t blk with
    | None -> true
    | Some last -> Term.same def last

  let has_no_def blk = Term.length def_t blk = 0

  let step level =
    let open Primus.Pos in
    match level with
    | Blk {me=blk} when has_no_def blk -> fork blk
    | Def {up={me=blk}; me=def} when is_last blk def ->
      fork blk
    | Jmp {up={me=blk}; me=jmp} -> fork_on_calls blk jmp
    | _ -> Machine.return ()

  let mark_visited blk =
    Machine.Global.update state ~f:(fun t -> {
          t with visited =
                   Set.add t.visited (Term.tid blk)
        })


  let init () = Machine.sequence [
      Primus.Interpreter.leave_pos >>> step;
      Primus.Interpreter.leave_blk >>> mark_visited;
    ]
end

module EnableDivisionByZero(Machine : Primus.Machine.S) = struct
  module Linker = Primus.Linker.Make(Machine)
  let init () =
    Linker.link ~name:Primus.Interpreter.division_by_zero_handler
      (module DoNothing)
end

let legacy_promiscous_mode_components = [
  "var-randomizer";
  "mem-randomizer";
  "arg-randomizer";
  "promiscuous-path-explorer";
  "division-by-zero-handler";
  "limit";
]

let enable_legacy_promiscuous_mode () =
  Primus.System.Repository.update ~package "legacy-main" ~f:(fun init ->
      List.fold legacy_promiscous_mode_components ~init
        ~f:(fun system component ->
            Primus.System.add_component system ~package component))

open Config;;

let desc =
  "When this mode is enabled the Primus Machine will venture into \
   paths with unsatisfied constraints. Basically, it means that on \
   every branch the state is duplicated."
;;

manpage [
  `S "DESCRIPTION";
  `P desc ;
  `P
    "The program will be translated into the Trivial Condition Form,
  where each compound condition expression is trivialized to a
  variable, that is bound earlier in the block."

]

let enabled = flag "mode" ~doc:"(DEPRECATED) Enable the mode."

let () = when_ready (fun {get=(!!)} ->
    Primus.Components.register_generic "promiscuous-path-explorer"
      (module Forker) ~package
      ~desc:"Forces execution of all linearly independent paths \
             by forcefully flipping the branch conditions.";
    Primus.Components.register_generic "division-by-zero-handler"
      (module EnableDivisionByZero) ~package
      ~desc:"Disables division by zero errors.";

    if !!enabled then enable_legacy_promiscuous_mode ());
OCaml

Innovation. Community. Security.