this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Working tool

+346 -78
+2
.gitignore
··· 3 3 vmlinux.h 4 4 *.o 5 5 arch 6 + *.csv 7 + *.json
+6 -4
dune
··· 1 1 (executable 2 2 (name opentrace) 3 3 (public_name opentrace) 4 - (preprocess (pps ppx_blob)) 5 - (preprocessor_deps (file opentrace.bpf.o)) 6 - (libraries unix libbpf libbpf_maps)) 4 + (preprocess 5 + (pps ppx_blob)) 6 + (preprocessor_deps 7 + (file opentrace.bpf.o)) 8 + (libraries eio_main jsonm cmdliner unix libbpf libbpf_maps)) 7 9 8 10 (rule 9 11 (mode ··· 12 14 (deps arch opentrace.bpf.c) 13 15 (action 14 16 (system 15 - "NIX_HARDENING_ENABLE=\"\" clang -g -O2 -target bpf -I/usr/include/%{architecture}-linux-gnu/ -c opentrace.bpf.c -D__TARGET_ARCH_%{read:arch}"))) 17 + "NIX_HARDENING_ENABLE=\"\" clang -g -O3 -target bpf -I/usr/include/%{architecture}-linux-gnu/ -c opentrace.bpf.c -D__TARGET_ARCH_%{read:arch}"))) 16 18 17 19 (rule 18 20 (mode
+14
dune-project
··· 1 1 (lang dune 3.14) 2 2 3 3 (name opentrace) 4 + 5 + (generate_opam_files true) 6 + 7 + (package 8 + (name opentrace) 9 + (synopsis "Trace programs for the files they read and write") 10 + (description "") 11 + (depends 12 + (ocaml (>= 5.2.0)) 13 + eio_main 14 + jsonm 15 + libbpf 16 + libbpf_maps)) 17 +
+84 -45
opentrace.bpf.c
··· 4 4 char LICENSE[] SEC("license") = "Dual BSD/GPL"; 5 5 6 6 const volatile int pid_target = 0; 7 + const volatile int cgroup_target = 0; 7 8 8 9 struct { 9 10 __uint(type, BPF_MAP_TYPE_RINGBUF); 10 11 __uint(max_entries, 256 * 1024); 11 12 } rb SEC(".maps"); 12 13 13 - #define FILE_NAME_LEN 1024 14 + #define FILE_NAME_LEN 256 14 15 15 16 #define OPEN_KIND 0 16 17 #define OPENAT_KIND 1 ··· 20 21 struct open_event 21 22 { 22 23 uint32_t e_pid; 24 + uint64_t e_cgid; 25 + char e_comm[TASK_COMM_LEN]; 23 26 int e_kind; 24 27 int e_flags; 25 28 uint32_t e_mode; 26 29 char e_filename[FILE_NAME_LEN]; 30 + int e_ret; 27 31 }; 28 32 33 + // Temporary memory between the syscall event and the exit 34 + // of the event. 35 + struct { 36 + __uint(type, BPF_MAP_TYPE_HASH); 37 + __uint(max_entries, 10240); 38 + __type(key, u32); // PID 39 + __type(value, struct open_event); // The event 40 + } tmpmap SEC(".maps"); 41 + 29 42 SEC("tracepoint/syscalls/sys_enter_openat") 30 43 int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter* ctx) 31 44 { 32 45 u64 id = bpf_get_current_pid_tgid(); 33 46 u32 pid = id >> 32; 34 - 35 - char filename[FILE_NAME_LEN]; 36 - struct open_event *oet; 37 - 38 - oet = bpf_ringbuf_reserve(&rb, sizeof(struct open_event), 0); 39 - if (!oet) 40 - return 0; 41 47 42 48 if (pid_target && pid_target != pid) 43 49 return false; 44 50 45 - // Fill the open event 46 - oet->e_pid = id; 47 - oet->e_kind = OPENAT_KIND; 48 - oet->e_flags = (int)ctx->args[2]; 49 - oet->e_mode = (__u32)ctx->args[3]; 50 - bpf_probe_read(oet->e_filename, sizeof(filename), (char *) ctx->args[1]); 51 - 52 - bpf_ringbuf_submit(oet, 0); 51 + struct open_event oet = {0}; 52 + oet.e_flags = (int)ctx->args[2]; 53 + oet.e_mode = (__u32)ctx->args[3]; 54 + bpf_probe_read(oet.e_filename, FILE_NAME_LEN, (char *) ctx->args[1]); 55 + bpf_map_update_elem(&tmpmap, &pid, &oet, BPF_NOEXIST); 56 + 53 57 return 0; 54 58 } 55 59 ··· 59 63 u64 id = bpf_get_current_pid_tgid(); 60 64 u32 pid = id >> 32; 61 65 62 - char filename[FILE_NAME_LEN]; 63 - struct open_event *oet; 64 - 65 - oet = bpf_ringbuf_reserve(&rb, sizeof(struct open_event), 0); 66 - if (!oet) 67 - return 0; 68 - 69 66 if (pid_target && pid_target != pid) 70 67 return false; 71 68 72 - struct open_how how = {}; 69 + struct open_event oet = {0}; 70 + struct open_how how = {0}; 73 71 bpf_probe_read_user(&how, sizeof(how), (void *)ctx->args[2]); 74 - oet->e_flags = (int)how.flags; 75 - oet->e_mode = (__u32)how.mode; 76 - oet->e_kind = OPENAT2_KIND; 77 - // Fill the open event 78 - oet->e_pid = id; 79 - 80 - bpf_probe_read(oet->e_filename, sizeof(filename), (char *) ctx->args[1]); 72 + oet.e_flags = (int)how.flags; 73 + oet.e_mode = (__u32)how.mode; 74 + bpf_probe_read(oet.e_filename, FILE_NAME_LEN, (char *) ctx->args[1]); 81 75 82 - bpf_ringbuf_submit(oet, 0); 76 + // Add it to the hash map 77 + bpf_map_update_elem(&tmpmap, &pid, &oet, BPF_NOEXIST); 78 + 83 79 return 0; 84 80 } 85 81 ··· 88 84 { 89 85 u64 id = bpf_get_current_pid_tgid(); 90 86 u32 pid = id >> 32; 91 - 92 - char filename[FILE_NAME_LEN]; 93 - struct open_event *oet; 87 + 88 + if (pid_target && pid_target != pid) 89 + return false; 90 + 91 + struct open_event oet = {0}; 92 + oet.e_flags = (int)ctx->args[2]; 93 + oet.e_mode = (__u32)ctx->args[3]; 94 + bpf_probe_read(oet.e_filename, FILE_NAME_LEN, (char *) ctx->args[1]); 94 95 96 + // Add it to the hash map 97 + bpf_map_update_elem(&tmpmap, &pid, &oet, BPF_NOEXIST); 98 + 99 + return 0; 100 + } 101 + 102 + // SEC("tracepoint/syscalls/sys_exit_open") 103 + // int tracepoint__syscalls__sys_exit_open(struct syscall_trace_exit* ctx) 104 + // { 105 + // return complete_event(OPEN_KIND, ctx); 106 + // } 107 + 108 + SEC("tracepoint/syscalls/sys_exit_openat") 109 + int tracepoint__syscalls__sys_exit_openat(struct syscall_trace_exit* ctx) 110 + { 111 + struct open_event *ev; 112 + u64 id = bpf_get_current_pid_tgid(); 113 + u32 pid = id >> 32; 114 + 115 + struct open_event *oet; 95 116 oet = bpf_ringbuf_reserve(&rb, sizeof(struct open_event), 0); 96 117 if (!oet) 97 118 return 0; 98 119 99 - if (pid_target && pid_target != pid) 100 - return false; 120 + // Find our event 121 + ev = bpf_map_lookup_elem(&tmpmap, &pid); 122 + if (!ev) { 123 + // Tidy up 124 + bpf_ringbuf_discard(oet, 0); 125 + bpf_map_delete_elem(&tmpmap, &pid); 126 + return 0; 127 + } 101 128 102 - oet->e_flags = (int)ctx->args[2]; 103 - oet->e_mode = (__u32)ctx->args[3]; 104 - oet->e_kind = OPEN_KIND; 105 - // Fill the open event 106 - oet->e_pid = id; 107 - 108 - bpf_probe_read(oet->e_filename, sizeof(filename), (char *) ctx->args[1]); 129 + oet->e_ret = ctx->ret; 130 + oet->e_cgid = bpf_get_current_cgroup_id(); 131 + bpf_probe_read_str(&oet->e_filename, sizeof(oet->e_filename), ev->e_filename); 132 + bpf_get_current_comm(oet->e_comm, TASK_COMM_LEN); 133 + oet->e_flags = ev->e_flags; 134 + oet->e_pid = pid; 135 + oet->e_mode = ev->e_mode; 136 + oet->e_kind = OPENAT_KIND; 137 + 138 + // Tidy up 139 + bpf_map_delete_elem(&tmpmap, &pid); 109 140 141 + /* emit event */ 110 142 bpf_ringbuf_submit(oet, 0); 111 - return 0; 143 + 144 + return 0; 145 + } 146 + 147 + SEC("tracepoint/syscalls/sys_exit_openat2") 148 + int tracepoint__syscalls__sys_exit_openat2(struct syscall_trace_exit* ctx) 149 + { 150 + return 0; 112 151 }
+233 -15
opentrace.ml
··· 1 1 open Libbpf 2 2 open Libbpf_maps 3 3 4 + type format = Csv | Json 5 + 4 6 let obj_path = "opentrace.bpf.o" 5 7 let obj_file = [%blob "opentrace.bpf.o"] 6 8 ··· 8 10 [ 9 11 "tracepoint__syscalls__sys_enter_openat"; 10 12 "tracepoint__syscalls__sys_enter_openat2"; 11 - (* "tracepoint__syscalls__sys_enter_open"; *) 13 + "tracepoint__syscalls__sys_exit_openat"; 14 + "tracepoint__syscalls__sys_exit_openat2"; 12 15 ] 13 16 17 + let json_to_lexemes json : Jsonm.lexeme list = 18 + let rec loop acc = function 19 + | `String _ as s -> s :: acc 20 + | `Int i -> `Float (Int.to_float i) :: acc 21 + | `O assoc -> 22 + let lexemes = 23 + List.fold_left 24 + (fun vacc (k, v) -> loop (`Name k :: vacc) v) 25 + (`Os :: acc) assoc 26 + in 27 + `Oe :: lexemes 28 + in 29 + loop [] json |> List.rev 30 + 14 31 module Open_event = struct 15 32 open Ctypes 16 33 ··· 31 48 let t : t structure typ = Ctypes.structure "event" 32 49 let ( -: ) ty label = Ctypes.field t label ty 33 50 let pid = uint32_t -: "e_pid" 51 + let cgid = uint64_t -: "e_cgid" 52 + let comm = array 16 char -: "e_comm" 34 53 let kind = int -: "e_kind" 35 54 let flags = int -: "e_flags" 36 55 let mode = uint32_t -: "e_mode" 37 56 let filename = array 256 char -: "e_filename" 57 + let ret = int -: "e_ret" 38 58 let () = seal t 39 59 40 60 let char_array_as_string a = ··· 49 69 with Exit -> Buffer.contents b 50 70 51 71 let get_pid s = getf s pid |> Unsigned.UInt32.to_int 72 + let get_cgid s = getf s cgid |> Unsigned.UInt64.to_int64 73 + let get_comm s = getf s comm |> char_array_as_string 52 74 let get_flags s = getf s flags 53 75 let get_mode s = getf s mode |> Unsigned.UInt32.to_int 54 76 let get_fname s = getf s filename |> char_array_as_string 55 77 let get_kind s = getf s kind |> kind_of_int 78 + let get_ret s = getf s ret 79 + let csv_header = "pid,cgid,comm,kind,flags,mode,filename,return\n" 80 + 81 + let to_csv_row event = 82 + Format.sprintf "%i,%Ld,%S,%s,%i,%i,\"%s\",%i%!" (get_pid event) 83 + (get_cgid event) (get_comm event) 84 + (get_kind event |> kind_to_string) 85 + (get_flags event) (get_mode event) (get_fname event) (get_ret event) 86 + 87 + let to_json event = 88 + `O 89 + [ 90 + ("pid", `Int (get_pid event)); 91 + ("cgid", `Int (Int64.to_int @@ get_cgid event)); 92 + ("comm", `String (get_comm event)); 93 + ("kind", `String (get_kind event |> kind_to_string)); 94 + ("flags", `Int (get_flags event)); 95 + ("mode", `Int (get_mode event)); 96 + ("fname", `String (get_fname event)); 97 + ("ret", `Int (get_ret event)); 98 + ] 56 99 end 57 100 58 - let () = 101 + let run_ring_buffer bpf_callback = 59 102 let dir = Filename.temp_dir "opentrace-" "" in 60 103 let full_obj_path = Filename.concat dir obj_path in 61 - Out_channel.with_open_bin full_obj_path (fun oc -> Out_channel.output_string oc obj_file); 104 + Out_channel.with_open_bin full_obj_path (fun oc -> 105 + Out_channel.output_string oc obj_file); 106 + with_bpf_object_open_load_link ~obj_path:full_obj_path ~program_names 107 + bpf_callback 108 + 109 + let ringbuffer_polling_callback ~poll rb_cb exit_cb = 62 110 let bpf_callback obj _links = 63 111 (* Set signal handlers *) 64 112 let exitting = ref true in ··· 66 114 Sys.(set_signal sigint sig_handler); 67 115 Sys.(set_signal sigterm sig_handler); 68 116 69 - (* Print header *) 70 - Format.printf "pid,kind,flags,mode,filename\n"; 71 - 72 117 let map = Libbpf.bpf_object_find_map_by_name obj "rb" in 73 118 let callback : RingBuffer.callback = 74 119 fun _ data _ -> 75 120 let event = Ctypes.(!@(from_voidp Open_event.t data)) in 76 - Format.printf "%i,%s,%i,%i,\"%s\"\n%!" (Open_event.get_pid event) 77 - (Open_event.get_kind event |> Open_event.kind_to_string) 78 - (Open_event.get_flags event) 79 - (Open_event.get_mode event) 80 - (Open_event.get_fname event); 81 - 0 121 + rb_cb event 82 122 in 83 123 RingBuffer.init map ~callback @@ fun rb -> 84 124 while !exitting do 85 - Unix.sleepf 1.0; 86 125 let _ : int = RingBuffer.poll rb ~timeout:1 in 87 - () 126 + exit_cb exitting; 127 + Unix.sleepf poll 88 128 done 89 129 in 90 - with_bpf_object_open_load_link ~obj_path:full_obj_path ~program_names bpf_callback 130 + bpf_callback 131 + 132 + let all poll no_header = 133 + if no_header then () else Format.printf "%s" Open_event.csv_header; 134 + let callback event = 135 + Format.printf "%s\n%!" (Open_event.to_csv_row event); 136 + 0 137 + in 138 + let bpf_callback = ringbuffer_polling_callback ~poll callback (fun _ -> ()) in 139 + run_ring_buffer bpf_callback 140 + 141 + let exec format output user poll (prog, args) = 142 + let output = 143 + match output with 144 + | Some file -> file 145 + | None -> 146 + let ext = match format with Csv -> "csv" | Json -> "json" in 147 + "trace." ^ ext 148 + in 149 + let uid = 150 + match user with 151 + | None -> Unix.getenv "SUDO_UID" |> int_of_string 152 + | Some user -> ( 153 + match int_of_string_opt user with 154 + | Some uid -> uid 155 + | None -> (Unix.getpwnam user).pw_uid) 156 + in 157 + assert (uid <> 0); 158 + let start_process = Condition.create () in 159 + let mutex = Mutex.create () in 160 + let pid = Atomic.make None in 161 + let exit_status = Atomic.make None in 162 + let _domain = 163 + Domain.spawn @@ fun () -> 164 + Eio_main.run @@ fun env -> 165 + Mutex.lock mutex; 166 + Condition.wait start_process mutex; 167 + Eio.Switch.run @@ fun sw -> 168 + let p = 169 + Eio.Process.spawn ~sw ~uid (Eio.Stdenv.process_mgr env) (prog :: args) 170 + in 171 + Atomic.set pid (Some (Eio.Process.pid p)); 172 + let status = Eio.Process.await p in 173 + Atomic.set exit_status (Some status) 174 + in 175 + Out_channel.with_open_bin output @@ fun oc -> 176 + let encoder = Jsonm.encoder ~minify:false (`Channel oc) in 177 + let encode l = 178 + Jsonm.encode encoder (`Lexeme l) |> function `Ok -> () | _ -> assert false 179 + in 180 + let finish () = 181 + Jsonm.encode encoder `End |> function `Ok -> () | _ -> assert false 182 + in 183 + let () = 184 + (* Header *) 185 + match format with 186 + | Csv -> Out_channel.output_string oc Open_event.csv_header 187 + | Json -> encode `As 188 + in 189 + let callback event = 190 + match Atomic.get pid with 191 + | None -> 0 192 + | Some pid -> 193 + (if Int.equal (Open_event.get_pid event) pid then 194 + match format with 195 + | Csv -> 196 + Out_channel.output_string oc (Open_event.to_csv_row event); 197 + Out_channel.output_char oc '\n' 198 + | Json -> 199 + List.iter encode (json_to_lexemes (Open_event.to_json event))); 200 + 0 201 + in 202 + let spawned = ref false in 203 + let exit_callback exitting = 204 + if not !spawned then ( 205 + Condition.broadcast start_process; 206 + spawned := true); 207 + Option.iter 208 + (fun _ -> 209 + exitting := false; 210 + match format with 211 + | Json -> 212 + encode `Ae; 213 + finish () 214 + | _ -> ()) 215 + (Atomic.get exit_status) 216 + in 217 + let bpf_callback = ringbuffer_polling_callback ~poll callback exit_callback in 218 + run_ring_buffer bpf_callback 219 + 220 + open Cmdliner 221 + open Cmdliner.Term.Syntax 222 + 223 + let polling = 224 + let doc = "The number of seconds to sleep between polls of the ringbuffer" in 225 + Arg.(value & opt float 0.1 & info [ "p"; "poll" ] ~doc ~docv:"POLL") 226 + 227 + let no_header = 228 + let doc = "Disable printing the CSV header" in 229 + Arg.(value & flag & info [ "no-header" ] ~doc) 230 + 231 + let user = 232 + let doc = "Username or UID to execute program as" in 233 + Arg.(value & opt (some string) None & info [ "u"; "user" ] ~doc ~docv:"USER") 234 + 235 + let format_conv : format Arg.conv = 236 + let of_string s = 237 + match String.lowercase_ascii s with 238 + | "csv" -> Ok Csv 239 + | "json" -> Ok Json 240 + | _ -> Error (`Msg ("Unknown format: " ^ s)) 241 + in 242 + let to_string = function Csv -> "csv" | Json -> "json" in 243 + let pp ppf v = Fmt.string ppf (to_string v) in 244 + Arg.conv ~docv:"FORMAT" (of_string, pp) 245 + 246 + let format = 247 + let doc = "Output format" in 248 + Arg.(value & opt format_conv Csv & info [ "f"; "format" ] ~docv:"FORMAT" ~doc) 249 + 250 + let output = 251 + let doc = 252 + "Output file for trace. Defaults to trace.<csv|json> depending on the \ 253 + $(format)." 254 + in 255 + Arg.( 256 + value & opt (some string) None & info [ "o"; "output" ] ~docv:"OUTPUT" ~doc) 257 + 258 + let all_cmd = 259 + let doc = "Trace all open system calls" in 260 + let man = 261 + [ 262 + `P 263 + "All calls to open will be traced and written to standard out in CSV \ 264 + format."; 265 + ] 266 + in 267 + Cmd.v (Cmd.info ~doc ~man "all") 268 + @@ 269 + let+ polling = polling and+ no_header = no_header in 270 + all polling no_header 271 + 272 + let exec_cmd = 273 + let doc = "Execute a program and trace its open system calls" in 274 + let man = 275 + [ 276 + `P 277 + "$(b,opentrace exec -- COMMAND) will execute COMMAND and trace only \ 278 + those open calls from that program."; 279 + `P "Opentrace will include the children of the main process."; 280 + ] 281 + in 282 + Cmd.v (Cmd.info ~doc ~man "exec") 283 + @@ 284 + let+ prog = 285 + Arg.(required & pos 0 (some string) None & Arg.info [] ~docv:"PROG") 286 + and+ user = user 287 + and+ format = format 288 + and+ output = output 289 + and+ args = Arg.(value & pos_right 0 string [] & Arg.info [] ~docv:"ARGS") 290 + and+ poll = polling in 291 + exec format output user poll (prog, args) 292 + 293 + let opentrace_cmd = 294 + let doc = "Trace all open system calls" in 295 + let man = 296 + [ 297 + `S Manpage.s_description; 298 + `P "$(cmd) traces all open system calls"; 299 + `P 300 + "$(cmd) can be used either to run an executable directly or to trace \ 301 + all open calls"; 302 + ] 303 + in 304 + let default = Term.(ret (const (`Help (`Auto, None)))) in 305 + Cmd.group (Cmd.info ~doc ~man "opentrace") ~default [ all_cmd; exec_cmd ] 306 + 307 + let main () = Cmd.eval opentrace_cmd 308 + let () = if !Sys.interactive then () else exit (main ())
+7 -14
opentrace.opam
··· 1 + # This file is generated by dune, edit dune-project instead 1 2 opam-version: "2.0" 2 - synopsis: "Trace the opening of files" 3 - description: "A linux tool using eBPF for tracing calls to open files" 4 - maintainer: ["Patrick Ferris <patrick@sirref.org>"] 5 - authors: ["Patrick Ferris <patrick@sirref.org>"] 6 - license: "MIT" 7 - homepage: "https://tangled.sh/@patrick.sirref.org/opentrace" 3 + synopsis: "Trace programs for the files they read and write" 4 + description: "" 8 5 depends: [ 9 - "dune" {>= "3.17"} 10 - "ocaml" 6 + "dune" {>= "3.14"} 7 + "ocaml" {>= "5.2.0"} 8 + "eio_main" 9 + "jsonm" 11 10 "libbpf" 12 11 "libbpf_maps" 13 - "ppx_blob" 14 12 "odoc" {with-doc} 15 13 ] 16 14 build: [ ··· 27 25 "@doc" {with-doc} 28 26 ] 29 27 ] 30 - homepage: "https://tangled.sh/@patrick.sirref.org/opentrace" 31 - pin-depends:[ 32 - [ "libbpf.dev" "git+https://github.com/patricoferris/ocaml-libbpf#alpine" ] 33 - [ "libbpf_maps.dev" "git+https://github.com/patricoferris/ocaml-libbpf#alpine" ] 34 - ]