summaryrefslogtreecommitdiff
path: root/checklink/Fuzz.ml
blob: dc984934fc5611611b28bd2d2a227ecc9ccd96ce (plain)
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
open Check
open ELF_parsers
open ELF_types
open Frameworks
open Library

let fuzz_debug = ref false

let string_of_byte = Printf.sprintf "0x%02x"

let full_range_of_byte elfmap byte =
  let byte = Int32.of_int byte in
  List.find (fun (a, b, _, _) -> a <= byte && byte <= b) elfmap 

let range_of_byte elfmap byte =
  let (_, _, _, r) = full_range_of_byte elfmap byte in
  r

(** [fuzz_check] will print what happened on stderr, and report errors (that is,
    when the check went fine) to stdout.
*)
let fuzz_check elfmap bs byte old sdumps =
  let is_error = function ERROR(_) -> true | _ -> false in
  let (str, _, _) = bs in
  let fuzz_description =
    string_of_int32 (Int32.of_int byte) ^ " <- " ^
      string_of_byte (Char.code str.[byte]) ^ " (was " ^
      string_of_byte (Char.code old) ^ ") - " ^
      string_of_byte_chunk_desc (range_of_byte elfmap byte)
  in
  if !fuzz_debug
  then print_endline fuzz_description;
  try
    (* The point here is to go all the way through the checks, and see whether
       the checker returns an ERROR or raises an exception. If not, then we
       might be missing a bug!
    *)
    let elf = read_elf_bs bs in
    let efw = check_elf_nodump elf sdumps in
    if List.exists is_error efw.log
    then (* finding an ERROR is the expected behavior *)
      begin
        if !fuzz_debug
        then print_endline (
          string_of_log_entry false (List.find is_error efw.log)
        )
      end
    else (* not finding an ERROR is bad thus reported *)
      print_endline (fuzz_description ^ " DID NOT CAUSE AN ERROR!")
  with
  | Assert_failure(s, l, c) ->
      if !fuzz_debug
      then Printf.printf "fuzz_check failed an assertion at %s (%d, %d)\n" s l c
  | Exc.IntOverflow | Exc.Int32Overflow ->
      if !fuzz_debug
      then Printf.printf "fuzz_check raised an integer overflow exception\n"
  | Match_failure(s, l, c) ->
      if !fuzz_debug
      then Printf.printf "fuzz_check raised a match failure at %s (%d, %d)\n" s l c
  | Not_found ->
      if !fuzz_debug
      then Printf.printf "fuzz_check raised a not found exception\n"
  | Invalid_argument(s) ->
      if !fuzz_debug
      then Printf.printf "fuzz_check raised an invalid argument: %s\n" s
  | ELF_parsers.Unknown_endianness ->
      if !fuzz_debug
      then Printf.printf "fuzz_check raised an unknown endianness exception\n"

(** Tries to prevent some easy-to-catch false positives. Some known false
    positives are however hard to predict. For instance, when the virtual
    address of a stub is replaced by the virtual address of another exact
    same stub.
*)
let ok_fuzz elfmap str byte fuzz =
  let (a, b, _, r) = full_range_of_byte elfmap byte in
  let a = Safe32.to_int a in
  let old = Char.code str.[byte] in
  let fuz = Char.code fuzz in
  match r with
  | ELF_header         ->
      not (List.mem byte
             [
               0x18; 0x19; 0x1a; 0x1b; (* e_entry *)
               0x1c; 0x1d; 0x1e; 0x1f; (* e_phoff *)
               0x24; 0x25; 0x26; 0x27; (* e_flags *)
               0x2c; 0x2d (* e_phnum *)
             ]
      )
  | ELF_progtab        -> false
  | ELF_shtab          -> false
  | ELF_section_strtab -> false
  | ELF_symbol_strtab  -> false
  | Symtab_data(_)     ->
      (* False positive: switching from/to STT_NOTYPE *)
      not (byte = a + 12
          && ((old land 0xf = 0) || (fuz land 0xf = 0))
      )
  | Symtab_function(_) -> true
  | Data_symbol(_)     -> true
  | Function_symbol(_) ->
      let opcode = Char.code str.[byte - 3] in
      (* False positive: rlwinm with bitmask 0 31 = bitmask n (n - 1) *)
      not (0x54 <= opcode && opcode <= 0x57 && old = 0x3E
          && (fuz = 0x40 || fuz = 0x82 || fuz = 0xc4))
  | Zero_symbol        -> false
  | Stub(_)            -> true
  | Jumptable          -> true
  | Float_literal(_)   -> true
  | Float32_literal(_) -> true
  (* padding is allowed to be non-null, but won't be recognized as padding, but
     as unknown, which is not an ERROR *)
  | Padding            -> false
  | Unknown(_)         -> false

let fuzz_byte str byte_ndx =
  let rand = Char.chr (Random.int 255) in (* [0 - 254] *)
  if rand = str.[byte_ndx] (* if we picked a byte equal to the current *)
  then Char.chr 255        (* then replace with byte 255 *)
  else rand                (* else replace with the byte we picked *)

let rec find_byte_to_fuzz elfmap str byterange =
  let byte = Random.int byterange in
  let fuzz = fuzz_byte str byte in
  if ok_fuzz elfmap str byte fuzz
  then (byte, fuzz)
  else find_byte_to_fuzz elfmap str byterange

let get_elfmap elffilename =
  let ic = open_in (elffilename ^ ".elfmap") in
  let elfmap = input_value ic in
  close_in ic;
  elfmap

(** Randomly fuzz bytes forever *)
let fuzz_loop elffilename sdumps =
  let elfmap = get_elfmap elffilename in
  let (str, ofs, len) = Bitstring.bitstring_of_file elffilename in
  let rec fuzz_loop_aux () =
    let (byte, fuzz) = find_byte_to_fuzz elfmap str (len/8) in
    let str' = String.copy str in
    str'.[byte] <- fuzz;
    fuzz_check elfmap (str', ofs, len) byte str.[byte] sdumps;
    fuzz_loop_aux ()
  in fuzz_loop_aux ()

let rec fuzz_every_byte_once_aux elfmap bs sdumps (current: int): unit =
  let (str, ofs, len) = bs in
  if current = len / 8 (* len is in bits *)
  then ()
  else (
    let fuzz = fuzz_byte str current in
    if ok_fuzz elfmap str current fuzz
    then (
      let str' = String.copy str in
      str'.[current] <- fuzz;
      fuzz_check elfmap (str', ofs, len) current str.[current] sdumps
    );
    fuzz_every_byte_once_aux elfmap bs sdumps (current + 1)
  )

(** Fuzz each byte of the file once with a random new value *)
let fuzz_every_byte_once elffilename sdumps =
  let elfmap = get_elfmap elffilename in
  let bs = Bitstring.bitstring_of_file elffilename in
  fuzz_every_byte_once_aux elfmap bs sdumps 0

(** Fuzz each byte of the file, then loop *)
let fuzz_every_byte_loop elffilename sdumps =
  let elfmap = get_elfmap elffilename in
  let bs = Bitstring.bitstring_of_file elffilename in
  let rec fuzz_every_byte_loop_aux () =
    fuzz_every_byte_once_aux elfmap bs sdumps 0;
    fuzz_every_byte_loop_aux ()
  in fuzz_every_byte_loop_aux ()