File: test_corrupted.ml

package info (click to toggle)
ocaml 5.4.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 44,372 kB
  • sloc: ml: 370,196; ansic: 52,820; sh: 27,396; asm: 5,462; makefile: 3,679; python: 974; awk: 278; javascript: 273; perl: 59; fortran: 21; cs: 9
file content (134 lines) | stat: -rw-r--r-- 4,807 bytes parent folder | download | duplicates (2)
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
(* TEST
 include runtime_events;
 include unix;
 set OCAML_RUNTIME_EVENTS_PRESERVE = "1";
 hasunix;
 not-target-windows;
 {
   bytecode;
 }{
   native;
 }
*)

  let runtime_begin _ _ _ = ()
  let runtime_end _ _ _ = ()
  let runtime_counter _ _ _ _ = ()
  let alloc _ _ _ = ()
  let lifecycle _ _ _ _ = ()
  let lost_events _ _ = ()
  let callbacks = Runtime_events.Callbacks.create
    ~runtime_begin ~runtime_end ~runtime_counter ~alloc ~lifecycle ~lost_events ()

  let parse path_pid =
    let cursor =
        Runtime_events.create_cursor path_pid in
    let finally () = Runtime_events.free_cursor cursor in
    Fun.protect ~finally @@ fun () ->
    Runtime_events.read_poll cursor callbacks None

  let parse_corrupted path_pid =
    try let (_:int) = parse path_pid in ()
    with Failure _ | Invalid_argument _ ->
      (* parsing corrupted rings, raises exceptions,
         this is expected *)
      ()

  let buf = Bytes.create (8 * 8)

  let with_ring ring_file f =
    let fd = Unix.openfile ring_file [Unix.O_RDWR] 0 in
    let finally () = Unix.close fd in
    Fun.protect ~finally @@ fun () ->
    let size = Int64.to_int Unix.LargeFile.((fstat fd).st_size) in
    let n = Unix.read fd buf 0 (Bytes.length buf) in
    assert (n = Bytes.length buf);
    let version = Bytes.get_int64_ne buf 0 in
    assert (version = 1L);
    (* this needs to be updated if on-disk layout changes *)
    let data_offset = Bytes.get_int64_ne buf (6*8) in

    let write_event_header is_runtime event_type event_id event_length =
      let (<<:) i n = Int64.(shift_left (of_int i) n) and (|:) = Int64.logor in
      (* see runtime_events.h *)
      let event_header =
        (event_length <<: 54) |:
        (is_runtime <<: 53) |:
        (event_type <<: 49) |:
        (event_id <<: 36)
      in
      Bytes.set_int64_ne buf 0 event_header;
      let n = Unix.LargeFile.lseek fd data_offset Unix.SEEK_SET in
      assert (n = data_offset);
      let n = Unix.write fd buf 0 (Bytes.length buf) in
      assert (n = Bytes.length buf)
    in

    let write_metadata_header offset value =
      let offset = Int64.of_int offset in
      let n = Unix.LargeFile.lseek fd offset Unix.SEEK_SET in
      assert (n = offset);
      Bytes.set_int64_ne buf 0 value;
      let n = Unix.write fd buf 0 (Bytes.length buf) in
      assert (n = Bytes.length buf)
    in

    f ~size ~write_event_header ~write_metadata_header

  (* this tests the preservation of ring buffers after termination *)

  let () =
    (* start runtime_events now to avoid a race *)
    let parent_cwd = Sys.getcwd () in
    let child_pid = Unix.fork () in
    if child_pid == 0 then begin
      (* we are in the child, so start Runtime_events *)
      Runtime_events.start ();
      (* this creates a ring buffer. Now exit. *)
    end else begin
      (* now wait for our child to finish *)
      Unix.wait () |> ignore;
      (* child has finished. We now have a valid ring *)
      let ring_file =
          Filename.concat parent_cwd (string_of_int child_pid ^ ".events")
      and path_pid = Some (parent_cwd, child_pid);
         in
      let finally () = Unix.unlink ring_file in
      Fun.protect ~finally @@ fun () ->
      with_ring ring_file @@ fun ~size ~write_event_header ~write_metadata_header ->
      (* we must succeed parsing it as is *)
      let n = parse path_pid in
      assert (n > 0);
      let original = Bytes.to_string buf in

      (* now overwrite various fields, corrupting the ring,
         and check that we don't crash (raising exceptions is fine).
       *)
      for offset = 8 downto 0 do
        [0L; size * 3/4 |> Int64.of_int; size * 2 |> Int64.of_int;
         Int64.max_int; Int64.min_int; Int64.(shift_right_logical max_int 1)
        ] |> List.iter @@ fun value ->
        write_metadata_header (8 * offset) value;
        parse_corrupted path_pid;
        (* restore original, we only corrupt and test one offset at a time,
           otherwise we may not find missing bounds checks if we exit early
           due to bounds error on an earlier offset
         *)
        Bytes.blit_string original 0 buf 0 (Bytes.length buf);
      done;
      (* restore metadata header, so we have a valid ring again *)
      write_metadata_header 0 1L (* version *);

      for is_runtime = 0 to 1 do
        for event_type = 0 to 15 (* event type is 4 bits *) do
          for event_id = 0 to 64 (* event_id is 13 bits, but not all used yet *) do
            for length = 0 to 3 (* short lengths trigger uninit read bugs *) do
                (* modify just 1 event in the otherwise valid ring *)
                write_event_header is_runtime event_type event_id length;
                (* parse ring *)
                parse_corrupted path_pid;
            done
          done
        done;
      done;
    end