vivarium 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/vivarium.rb CHANGED
@@ -5,6 +5,7 @@ require "fileutils"
5
5
  require "optparse"
6
6
  require "pathname"
7
7
  require "rbbcc"
8
+ require "socket"
8
9
  require_relative "vivarium/version"
9
10
  require_relative "vivarium/logger"
10
11
 
@@ -20,8 +21,57 @@ module Vivarium
20
21
 
21
22
  EVENT_NAME_SIZE = 16
22
23
  EVENT_PAYLOAD_SIZE = 256
23
- EVENT_STRUCT_SIZE = 4 + EVENT_NAME_SIZE + EVENT_PAYLOAD_SIZE
24
- EVENT_CAPACITY = 64
24
+ EVENT_TS_SIZE = 8
25
+ PROC_EXEC_SLOT_SIZE = 64
26
+ PROC_EXEC_SLOT_COUNT = 4
27
+ EVENT_STRUCT_SIZE = 288
28
+ EVENT_TS_OFFSET = 0
29
+ EVENT_PID_OFFSET = 8
30
+ EVENT_NAME_OFFSET = 12
31
+ EVENT_PAYLOAD_OFFSET = 28
32
+ EVENT_CAPACITY = 1024
33
+ EVENT_SEVERITY_HIGH = %w[
34
+ capable_check bprm_creds setid_change task_kill
35
+ ptrace_check sb_mount kernel_read_file
36
+ ].freeze
37
+
38
+ CAPABILITY_NAMES = {
39
+ 0 => "CAP_CHOWN",
40
+ 1 => "CAP_DAC_OVERRIDE",
41
+ 2 => "CAP_DAC_READ_SEARCH",
42
+ 3 => "CAP_FOWNER",
43
+ 4 => "CAP_FSETID",
44
+ 5 => "CAP_KILL",
45
+ 6 => "CAP_SETGID",
46
+ 7 => "CAP_SETUID",
47
+ 8 => "CAP_SETPCAP",
48
+ 9 => "CAP_LINUX_IMMUTABLE",
49
+ 10 => "CAP_NET_BIND_SERVICE",
50
+ 12 => "CAP_NET_ADMIN",
51
+ 13 => "CAP_NET_RAW",
52
+ 16 => "CAP_SYS_MODULE",
53
+ 17 => "CAP_SYS_RAWIO",
54
+ 18 => "CAP_SYS_CHROOT",
55
+ 19 => "CAP_SYS_PTRACE",
56
+ 21 => "CAP_SYS_ADMIN",
57
+ 22 => "CAP_SYS_BOOT",
58
+ 23 => "CAP_SYS_NICE",
59
+ 24 => "CAP_SYS_RESOURCE",
60
+ 25 => "CAP_SYS_TIME",
61
+ 27 => "CAP_MKNOD",
62
+ 29 => "CAP_AUDIT_WRITE",
63
+ 37 => "CAP_AUDIT_READ",
64
+ 38 => "CAP_PERFMON",
65
+ 39 => "CAP_BPF",
66
+ 40 => "CAP_CHECKPOINT_RESTORE"
67
+ }.freeze
68
+
69
+ SETID_FLAG_NAMES = {
70
+ 0x01 => "LSM_SETID_ID",
71
+ 0x02 => "LSM_SETID_RE",
72
+ 0x04 => "LSM_SETID_RES",
73
+ 0x08 => "LSM_SETID_FS"
74
+ }.freeze
25
75
 
26
76
  @bpf_pin_dir = PIN_DIR
27
77
 
@@ -33,201 +83,1183 @@ module Vivarium
33
83
  end
34
84
  end
35
85
 
36
- Event = Struct.new(:pid, :event_name, :payload, keyword_init: true) do
86
+ Event = Struct.new(:ktime_ns, :pid, :event_name, :payload, keyword_init: true) do
37
87
  def empty?
38
- pid.to_i.zero? && event_name.to_s.empty? && payload.to_s.empty?
88
+ ktime_ns.to_i.zero? && pid.to_i.zero? && event_name.to_s.empty? && payload.to_s.empty?
89
+ end
90
+
91
+ def severity
92
+ Vivarium.event_severity(event_name)
39
93
  end
40
94
 
41
95
  def self.from_binary(raw)
42
96
  bytes = raw.to_s.b
43
97
  bytes = bytes.ljust(EVENT_STRUCT_SIZE, "\x00")
44
98
 
45
- pid = bytes[0, 4].unpack1("L<")
46
- event_name = c_string(bytes[4, EVENT_NAME_SIZE])
47
- payload = c_string(bytes[4 + EVENT_NAME_SIZE, EVENT_PAYLOAD_SIZE])
99
+ ktime_ns = bytes[EVENT_TS_OFFSET, EVENT_TS_SIZE].unpack1("Q<")
100
+ pid = bytes[EVENT_PID_OFFSET, 4].unpack1("L<")
101
+ event_name = c_string(bytes[EVENT_NAME_OFFSET, EVENT_NAME_SIZE])
102
+ raw_payload = bytes[EVENT_PAYLOAD_OFFSET, EVENT_PAYLOAD_SIZE]
103
+ raw_payload_events = %w[
104
+ dns_req sock_connect odd_socket proc_exec
105
+ file_symlink file_hardlink file_rename file_chmod file_getdents
106
+ ptrace_check sb_mount kernel_read_file task_kill
107
+ setid_change capable_check bprm_creds
108
+ ]
109
+ payload = if raw_payload_events.include?(event_name)
110
+ raw_payload
111
+ else
112
+ c_string(raw_payload)
113
+ end
114
+
115
+ new(ktime_ns: ktime_ns, pid: pid, event_name: event_name, payload: payload)
116
+ end
117
+
118
+ def self.c_string(bytes)
119
+ str = bytes.to_s.b
120
+ nul = str.index("\x00")
121
+ return str if nul.nil?
122
+
123
+ str[0, nul]
124
+ end
125
+ end
126
+
127
+ def self.c_string(bytes)
128
+ Event.c_string(bytes)
129
+ end
130
+
131
+ def self.event_severity(event_name)
132
+ EVENT_SEVERITY_HIGH.include?(event_name.to_s) ? "high" : "medium"
133
+ end
134
+
135
+ def self.decode_dns_qname(raw_payload)
136
+ bytes = raw_payload.to_s.b.bytes
137
+ labels = []
138
+ idx = 0
139
+
140
+ while idx < bytes.length
141
+ length = bytes[idx]
142
+ break if length.nil? || length.zero?
143
+ break if length > 63
144
+
145
+ idx += 1
146
+ break if (idx + length) > bytes.length
147
+
148
+ label = bytes[idx, length].pack("C*")
149
+ labels << label
150
+ idx += length
151
+ end
152
+
153
+ return "" if labels.empty?
154
+
155
+ labels.join(".")
156
+ end
157
+
158
+ def self.decode_sock_connect_payload(raw_payload)
159
+ bytes = raw_payload.to_s.b
160
+ return "" if bytes.bytesize < 20
161
+
162
+ family = bytes[0, 2].unpack1("S<")
163
+ port = bytes[2, 2].unpack1("n")
164
+ addr = bytes[4, 16]
165
+
166
+ case family
167
+ when 2 # AF_INET
168
+ ipv4 = addr[0, 4].bytes.join(".")
169
+ "#{ipv4}:#{port} (#{socket_const_name("AF_", family)})"
170
+ when 10 # AF_INET6
171
+ words = addr.unpack("n8")
172
+ ipv6 = words.map { |w| format("%x", w) }.join(":")
173
+ "[#{ipv6}]:#{port} (#{socket_const_name("AF_", family)})"
174
+ else
175
+ "family=#{family}(#{socket_const_name("AF_", family)}) port=#{port}"
176
+ end
177
+ end
178
+
179
+ def self.decode_odd_socket_payload(raw_payload)
180
+ bytes = raw_payload.to_s.b
181
+ return "" if bytes.bytesize < 6
182
+
183
+ family = bytes[0, 2].unpack1("S<")
184
+ type = bytes[2, 2].unpack1("S<")
185
+ protocol = bytes[4, 2].unpack1("S<")
186
+ family_name = socket_const_name("AF_", family)
187
+ type_name = socket_const_name("SOCK_", type)
188
+ protocol_name = socket_const_name("IPPROTO_", protocol)
189
+ "family=#{family}(#{family_name}) type=#{type}(#{type_name}) protocol=#{protocol}(#{protocol_name})"
190
+ end
191
+
192
+ def self.socket_const_name(prefix, value)
193
+ return "UNKNOWN" unless defined?(Socket)
194
+
195
+ key = Socket.constants.find do |name|
196
+ name.to_s.start_with?(prefix) && Socket.const_get(name) == value
197
+ rescue NameError
198
+ false
199
+ end
200
+
201
+ key ? key.to_s : "UNKNOWN"
202
+ end
203
+
204
+ def self.decode_bad_socket_payload(raw_payload)
205
+ decode_odd_socket_payload(raw_payload)
206
+ end
207
+
208
+ def self.decode_file_symlink_payload(raw_payload)
209
+ bytes = raw_payload.to_s.b
210
+ target = c_string(bytes[0, 128])
211
+ link_name = c_string(bytes[128, 128])
212
+ "target=#{target.inspect} link_name=#{link_name.inspect}"
213
+ end
214
+
215
+ def self.decode_file_hardlink_payload(raw_payload)
216
+ bytes = raw_payload.to_s.b
217
+ old_path = c_string(bytes[0, 128])
218
+ new_name = c_string(bytes[128, 128])
219
+ "old_path=#{old_path.inspect} new_name=#{new_name.inspect}"
220
+ end
221
+
222
+ def self.decode_file_rename_payload(raw_payload)
223
+ bytes = raw_payload.to_s.b
224
+ old_name = c_string(bytes[0, 128])
225
+ new_name = c_string(bytes[128, 128])
226
+ "old_name=#{old_name.inspect} new_name=#{new_name.inspect}"
227
+ end
228
+
229
+ def self.decode_file_chmod_payload(raw_payload)
230
+ bytes = raw_payload.to_s.b
231
+ return "" if bytes.bytesize < 2
232
+
233
+ mode = bytes[0, 2].unpack1("S<")
234
+ path = c_string(bytes[2, 254])
235
+ "mode=#{format('0o%o', mode)} path=#{path.inspect}"
236
+ end
237
+
238
+ def self.decode_file_getdents_payload(raw_payload)
239
+ bytes = raw_payload.to_s.b
240
+ return "" if bytes.bytesize < 8
241
+
242
+ fd = bytes[0, 4].unpack1("L<")
243
+ count = bytes[4, 4].unpack1("L<")
244
+ "fd=#{fd} count=#{count}"
245
+ end
246
+
247
+ def self.decode_proc_exec_payload(raw_payload)
248
+ bytes = raw_payload.to_s.b
249
+ slots = PROC_EXEC_SLOT_COUNT.times.map do |index|
250
+ offset = index * PROC_EXEC_SLOT_SIZE
251
+ c_string(bytes[offset, PROC_EXEC_SLOT_SIZE])
252
+ end
253
+ slots.reject!(&:empty?)
254
+ return "" if slots.empty?
255
+
256
+ filename = slots.shift
257
+ argv = slots
258
+ "filename=#{filename.inspect} argv=[#{argv.map(&:inspect).join(', ')}]"
259
+ end
260
+
261
+ def self.decode_ptrace_check_payload(raw_payload)
262
+ bytes = raw_payload.to_s.b
263
+ return "" if bytes.bytesize < 4
264
+
265
+ mode = bytes[0, 4].unpack1("L<")
266
+ "mode=0x#{mode.to_s(16)}"
267
+ end
268
+
269
+ def self.decode_sb_mount_payload(raw_payload)
270
+ bytes = raw_payload.to_s.b
271
+ return "" if bytes.bytesize < 248
272
+
273
+ flags = bytes[0, 8].unpack1("Q<")
274
+ dev_name = c_string(bytes[8, 120])
275
+ fs_type = c_string(bytes[128, 120])
276
+ "flags=0x#{flags.to_s(16)} dev_name=#{dev_name.inspect} fs_type=#{fs_type.inspect}"
277
+ end
278
+
279
+ def self.decode_kernel_read_file_payload(raw_payload)
280
+ bytes = raw_payload.to_s.b
281
+ return "" if bytes.bytesize < 8
282
+
283
+ id = bytes[0, 4].unpack1("L<")
284
+ contents = bytes[4, 4].unpack1("L<")
285
+ "id=#{id} contents=#{contents}"
286
+ end
287
+
288
+ def self.decode_task_kill_payload(raw_payload)
289
+ bytes = raw_payload.to_s.b
290
+ return "" if bytes.bytesize < 4
291
+
292
+ sig = bytes[0, 4].unpack1("l<")
293
+ signame = begin
294
+ Signal.signame(sig)
295
+ rescue ArgumentError
296
+ nil
297
+ end
298
+
299
+ signame ? "sig=#{sig} signame=#{signame}" : "sig=#{sig}"
300
+ end
301
+
302
+ def self.decode_setid_change_payload(raw_payload)
303
+ bytes = raw_payload.to_s.b
304
+ return "" if bytes.bytesize < 4
305
+
306
+ flags = bytes[0, 4].unpack1("L<")
307
+ names = SETID_FLAG_NAMES.each_with_object([]) do |(bit, name), acc|
308
+ acc << name if (flags & bit) != 0
309
+ end
310
+ names << "UNKNOWN" if names.empty?
311
+ "flags=0x#{flags.to_s(16)} kinds=[#{names.join(', ')}]"
312
+ end
313
+
314
+ def self.decode_capable_check_payload(raw_payload)
315
+ bytes = raw_payload.to_s.b
316
+ return "" if bytes.bytesize < 8
317
+
318
+ cap = bytes[0, 4].unpack1("L<")
319
+ opts = bytes[4, 4].unpack1("L<")
320
+ cap_name = CAPABILITY_NAMES.fetch(cap, "UNKNOWN")
321
+ "cap=#{cap}(#{cap_name}) opts=0x#{opts.to_s(16)}"
322
+ end
323
+
324
+ def self.decode_bprm_creds_payload(raw_payload)
325
+ bytes = raw_payload.to_s.b
326
+ return "" if bytes.bytesize < 2
327
+
328
+ has_file = bytes.getbyte(0).to_i
329
+ path = c_string(bytes[1, EVENT_PAYLOAD_SIZE - 1])
330
+ "has_file=#{has_file} file=#{path.inspect}"
331
+ end
332
+
333
+ def self.render_event_payload(event)
334
+ case event.event_name
335
+ when "dns_req"
336
+ decoded = decode_dns_qname(event.payload)
337
+ decoded.empty? ? event.payload.inspect : decoded
338
+ when "sock_connect"
339
+ decoded = decode_sock_connect_payload(event.payload)
340
+ decoded.empty? ? event.payload.inspect : decoded
341
+ when "odd_socket"
342
+ decoded = decode_odd_socket_payload(event.payload)
343
+ decoded.empty? ? event.payload.inspect : decoded
344
+ when "proc_exec"
345
+ decoded = decode_proc_exec_payload(event.payload)
346
+ decoded.empty? ? event.payload.inspect : decoded
347
+ when "ptrace_check"
348
+ decoded = decode_ptrace_check_payload(event.payload)
349
+ decoded.empty? ? event.payload.inspect : decoded
350
+ when "sb_mount"
351
+ decoded = decode_sb_mount_payload(event.payload)
352
+ decoded.empty? ? event.payload.inspect : decoded
353
+ when "kernel_read_file"
354
+ decoded = decode_kernel_read_file_payload(event.payload)
355
+ decoded.empty? ? event.payload.inspect : decoded
356
+ when "task_kill"
357
+ decoded = decode_task_kill_payload(event.payload)
358
+ decoded.empty? ? event.payload.inspect : decoded
359
+ when "setid_change"
360
+ decoded = decode_setid_change_payload(event.payload)
361
+ decoded.empty? ? event.payload.inspect : decoded
362
+ when "capable_check"
363
+ decoded = decode_capable_check_payload(event.payload)
364
+ decoded.empty? ? event.payload.inspect : decoded
365
+ when "bprm_creds"
366
+ decoded = decode_bprm_creds_payload(event.payload)
367
+ decoded.empty? ? event.payload.inspect : decoded
368
+ when "file_symlink"
369
+ decoded = decode_file_symlink_payload(event.payload)
370
+ decoded.empty? ? event.payload.inspect : decoded
371
+ when "file_hardlink"
372
+ decoded = decode_file_hardlink_payload(event.payload)
373
+ decoded.empty? ? event.payload.inspect : decoded
374
+ when "file_rename"
375
+ decoded = decode_file_rename_payload(event.payload)
376
+ decoded.empty? ? event.payload.inspect : decoded
377
+ when "file_chmod"
378
+ decoded = decode_file_chmod_payload(event.payload)
379
+ decoded.empty? ? event.payload.inspect : decoded
380
+ when "file_getdents"
381
+ decoded = decode_file_getdents_payload(event.payload)
382
+ decoded.empty? ? event.payload.inspect : decoded
383
+ else
384
+ event.payload.inspect
385
+ end
386
+ end
387
+
388
+ class MapStore
389
+ def initialize(pin_dir: Vivarium.bpf_pin_dir)
390
+ @pin_dir = pin_dir
391
+ @config_root_targets = RbBCC::HashTable.from_pin(
392
+ File.join(@pin_dir, "config_root_targets"),
393
+ "unsigned int",
394
+ "unsigned char",
395
+ keysize: 4,
396
+ leafsize: 1
397
+ )
398
+ @config_spawned_targets = RbBCC::HashTable.from_pin(
399
+ File.join(@pin_dir, "config_spawned_targets"),
400
+ "unsigned int",
401
+ "unsigned char",
402
+ keysize: 4,
403
+ leafsize: 1
404
+ )
405
+ @event_invoked = RbBCC::ArrayTable.from_pin(
406
+ File.join(@pin_dir, "event_invoked"),
407
+ "unsigned int",
408
+ "char[#{EVENT_STRUCT_SIZE}]",
409
+ keysize: 4,
410
+ leafsize: EVENT_STRUCT_SIZE
411
+ )
412
+ @event_write_pos = RbBCC::ArrayTable.from_pin(
413
+ File.join(@pin_dir, "event_write_pos"),
414
+ "unsigned int",
415
+ "unsigned int",
416
+ keysize: 4,
417
+ leafsize: 4
418
+ )
419
+ rescue StandardError => e
420
+ raise Error, "failed to open pinned maps under #{@pin_dir}: #{e.class}: #{e.message}"
421
+ end
422
+
423
+ def register_pid(pid)
424
+ @config_root_targets[pid] = 1
425
+ end
426
+
427
+ def unregister_pid(pid)
428
+ @config_root_targets.delete(pid)
429
+ @config_spawned_targets.clear
430
+ rescue KeyError
431
+ nil
432
+ end
433
+
434
+ def drain_events
435
+ events = []
436
+ EVENT_CAPACITY.times do |idx|
437
+ ptr = @event_invoked[idx]
438
+ next unless ptr
439
+
440
+ event = Event.from_binary(ptr[0, EVENT_STRUCT_SIZE])
441
+ next if event.empty?
442
+
443
+ events << event
444
+ @event_invoked[idx] = zeroed_event_ptr
445
+ end
446
+
447
+ @event_write_pos[0] = 0
448
+ events
449
+ end
450
+
451
+ private
452
+
453
+ def zeroed_event_ptr
454
+ ptr = Fiddle::Pointer.malloc(EVENT_STRUCT_SIZE)
455
+ ptr[0, EVENT_STRUCT_SIZE] = "\x00" * EVENT_STRUCT_SIZE
456
+ ptr
457
+ end
458
+ end
459
+
460
+ class Daemon
461
+ BPF_PROGRAM_TEMPLATE = <<~CLANG
462
+ #include <linux/socket.h>
463
+ #include <uapi/linux/in.h>
464
+ #include <uapi/linux/in6.h>
465
+ #include <uapi/linux/ip.h>
466
+ #include <uapi/linux/udp.h>
467
+
468
+ #ifndef SOCK_STREAM
469
+ #define SOCK_STREAM 1
470
+ #endif
471
+ #ifndef SOCK_DGRAM
472
+ #define SOCK_DGRAM 2
473
+ #endif
474
+
475
+ struct net;
476
+ struct sock;
477
+ struct sk_buff;
478
+ struct task_struct;
479
+ struct kernel_siginfo;
480
+ struct cred;
481
+ struct user_namespace;
482
+ struct linux_binprm;
483
+
484
+ struct path {
485
+ void *mnt;
486
+ void *dentry;
487
+ };
488
+ struct file {
489
+ char __off[__VIVARIUM_F_PATH_OFFSET__];
490
+ struct path f_path;
491
+ };
492
+
493
+ struct qstr {
494
+ union {
495
+ struct {
496
+ u64 hash_len;
497
+ };
498
+ struct {
499
+ u32 hash;
500
+ u32 len;
501
+ };
502
+ };
503
+ const unsigned char *name;
504
+ };
505
+
506
+ struct dentry_base {
507
+ char __pad[__VIVARIUM_DENTRY_D_NAME_OFFSET__];
508
+ struct qstr d_name;
509
+ };
510
+
511
+ struct sockaddr_t {
512
+ u16 sa_family;
513
+ unsigned char sa_data[14];
514
+ };
515
+
516
+ struct sockaddr_in_t {
517
+ u16 sin_family;
518
+ u16 sin_port;
519
+ u32 sin_addr;
520
+ unsigned char pad[8];
521
+ };
522
+
523
+ struct sockaddr_in6_t {
524
+ u16 sin6_family;
525
+ u16 sin6_port;
526
+ u32 sin6_flowinfo;
527
+ unsigned char sin6_addr[16];
528
+ u32 sin6_scope_id;
529
+ };
530
+
531
+ struct sockaddr_port_t {
532
+ u16 family;
533
+ u16 port;
534
+ };
535
+
536
+ struct iovec_t {
537
+ void *iov_base;
538
+ unsigned long iov_len;
539
+ };
540
+
541
+ struct user_msghdr_t {
542
+ void *msg_name;
543
+ int msg_namelen;
544
+ struct iovec_t *msg_iov;
545
+ unsigned long msg_iovlen;
546
+ void *msg_control;
547
+ unsigned long msg_controllen;
548
+ unsigned int msg_flags;
549
+ };
550
+
551
+ struct mmsghdr_t {
552
+ struct user_msghdr_t msg_hdr;
553
+ unsigned int msg_len;
554
+ };
555
+
556
+ struct sk_buff_t {
557
+ unsigned char *head;
558
+ unsigned char *data;
559
+ u32 len;
560
+ u16 mac_header;
561
+ u16 network_header;
562
+ u16 transport_header;
563
+ };
564
+
565
+ struct event_t {
566
+ u64 ktime_ns;
567
+ u32 pid;
568
+ char event_name[16];
569
+ char payload[#{EVENT_PAYLOAD_SIZE}];
570
+ };
571
+
572
+ BPF_HASH(config_root_targets, u32, u8, 1024);
573
+ BPF_HASH(config_spawned_targets, u32, u8, 8192);
574
+ BPF_HASH(dns_connected_tids, u32, u8, 8192);
575
+ BPF_ARRAY(event_invoked, struct event_t, #{EVENT_CAPACITY});
576
+ BPF_ARRAY(event_write_pos, u32, 1);
577
+
578
+ static __always_inline int target_enabled(u32 pid, u32 tid)
579
+ {
580
+ u8 *enabled_root = config_root_targets.lookup(&pid);
581
+ if (enabled_root && *enabled_root == 1) {
582
+ return 1;
583
+ }
584
+
585
+ u8 *enabled_spawned = config_spawned_targets.lookup(&tid);
586
+ if (enabled_spawned && *enabled_spawned == 1) {
587
+ return 1;
588
+ }
589
+
590
+ return 0;
591
+ }
592
+
593
+ static __always_inline int monitored_capability(int cap)
594
+ {
595
+ switch (cap) {
596
+ case 1: /* CAP_DAC_OVERRIDE */
597
+ case 2: /* CAP_DAC_READ_SEARCH */
598
+ case 6: /* CAP_SETGID */
599
+ case 7: /* CAP_SETUID */
600
+ case 12: /* CAP_NET_ADMIN */
601
+ case 16: /* CAP_SYS_MODULE */
602
+ case 17: /* CAP_SYS_RAWIO */
603
+ case 19: /* CAP_SYS_PTRACE */
604
+ case 21: /* CAP_SYS_ADMIN */
605
+ case 22: /* CAP_SYS_BOOT */
606
+ case 25: /* CAP_SYS_TIME */
607
+ case 38: /* CAP_PERFMON */
608
+ case 39: /* CAP_BPF */
609
+ case 40: /* CAP_CHECKPOINT_RESTORE */
610
+ return 1;
611
+ default:
612
+ return 0;
613
+ }
614
+ }
615
+
616
+ static __always_inline void submit_event(struct event_t *ev)
617
+ {
618
+ u32 zero = 0;
619
+ u32 *write_pos = event_write_pos.lookup(&zero);
620
+ if (!write_pos) {
621
+ return;
622
+ }
623
+
624
+ ev->ktime_ns = bpf_ktime_get_ns();
625
+
626
+ u32 idx = *write_pos % #{EVENT_CAPACITY};
627
+ __sync_fetch_and_add(write_pos, 1);
628
+ event_invoked.update(&idx, ev);
629
+ }
630
+
631
+ static __always_inline int is_dns_destination(void *addr)
632
+ {
633
+ u16 family = 0;
634
+ bpf_probe_read_user(&family, sizeof(family), addr);
635
+
636
+ if (family == AF_INET) {
637
+ struct sockaddr_in_t sin = {};
638
+ bpf_probe_read_user(&sin, sizeof(sin), addr);
639
+ return sin.sin_port == __constant_htons(53);
640
+ }
641
+
642
+ if (family == AF_INET6) {
643
+ struct sockaddr_in6_t sin6 = {};
644
+ bpf_probe_read_user(&sin6, sizeof(sin6), addr);
645
+ return sin6.sin6_port == __constant_htons(53);
646
+ }
647
+
648
+ return 0;
649
+ }
650
+
651
+ static __always_inline void submit_dns_req(u32 pid, unsigned char *payload, unsigned int payload_len)
652
+ {
653
+ unsigned int copy_len = payload_len;
654
+
655
+ if (copy_len <= 12) {
656
+ return;
657
+ }
658
+
659
+ copy_len -= 12;
660
+ if (copy_len > 64) {
661
+ copy_len = 64;
662
+ }
663
+
664
+ struct event_t ev = {};
665
+ ev.pid = pid;
666
+ __builtin_memcpy(ev.event_name, "dns_req", 8);
667
+ bpf_probe_read_user(&ev.payload[0], copy_len, payload + 12);
668
+ submit_event(&ev);
669
+ }
670
+
671
+ static __always_inline int read_dentry_name(struct dentry *dentry, char *buffer, size_t max)
672
+ {
673
+ struct dentry_base d = {};
674
+ struct qstr qname = {};
675
+
676
+ if (!dentry || !buffer) {
677
+ return -1;
678
+ }
679
+
680
+ bpf_probe_read_kernel(&d, sizeof(d), (void *)dentry);
681
+ if (!d.d_name.name) {
682
+ return -1;
683
+ }
684
+
685
+ unsigned int len = d.d_name.len;
686
+ if (len > max) {
687
+ len = max;
688
+ }
689
+
690
+ bpf_probe_read_kernel_str(buffer, len + 1, (void *)d.d_name.name);
691
+ return len;
692
+ }
693
+
694
+ TRACEPOINT_PROBE(sched, sched_process_fork)
695
+ {
696
+ u32 parent = args->parent_pid;
697
+ u32 child = args->child_pid;
698
+ u8 one = 1;
699
+
700
+ u8 *enabled_root = config_root_targets.lookup(&parent);
701
+ if (enabled_root && *enabled_root == 1) {
702
+ config_spawned_targets.update(&child, &one);
703
+ return 0;
704
+ }
705
+
706
+ u8 *enabled_spawned = config_spawned_targets.lookup(&parent);
707
+ if (enabled_spawned && *enabled_spawned == 1) {
708
+ config_spawned_targets.update(&child, &one);
709
+ }
710
+
711
+ return 0;
712
+ }
713
+
714
+ TRACEPOINT_PROBE(sched, sched_process_exit)
715
+ {
716
+ u32 tid = (u32)bpf_get_current_pid_tgid();
717
+ config_spawned_targets.delete(&tid);
718
+ dns_connected_tids.delete(&tid);
719
+ return 0;
720
+ }
721
+
722
+ LSM_PROBE(file_open, struct file *file)
723
+ {
724
+ u64 pid_tgid = bpf_get_current_pid_tgid();
725
+ u32 pid = pid_tgid >> 32;
726
+ u32 tid = (u32)pid_tgid;
727
+ bpf_trace_printk("vivarium: invoked pid=%d\\n", pid);
728
+ if (!target_enabled(pid, tid)) {
729
+ return 0;
730
+ }
731
+
732
+ struct event_t ev = {};
733
+ int path_ret;
734
+ ev.pid = pid;
735
+ __builtin_memcpy(ev.event_name, "path_open", 9);
736
+
737
+ path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
738
+ if (path_ret < 0) {
739
+ if (ev.payload[0] == 0) {
740
+ __builtin_memcpy(ev.payload, "<path_error>", 13);
741
+ bpf_trace_printk("vivarium: failed to obtain full path. pid=%d path=%s\\n", pid, ev.payload);
742
+ }
743
+ }
744
+
745
+ bpf_trace_printk("vivarium: pid=%d path=%s\\n", pid, ev.payload);
746
+ submit_event(&ev);
747
+
748
+ return 0;
749
+ }
750
+
751
+ LSM_PROBE(socket_create, int family, int type, int protocol, int kern)
752
+ {
753
+ u64 pid_tgid = bpf_get_current_pid_tgid();
754
+ u32 pid = pid_tgid >> 32;
755
+ u32 tid = (u32)pid_tgid;
756
+
757
+ if (!target_enabled(pid, tid)) {
758
+ return 0;
759
+ }
760
+
761
+ if ((family == AF_INET || family == AF_INET6) && (type == SOCK_STREAM || type == SOCK_DGRAM)) {
762
+ return 0;
763
+ }
764
+
765
+ struct event_t ev = {};
766
+ u16 family16 = family;
767
+ u16 type16 = type;
768
+ u16 proto16 = protocol;
769
+
770
+ ev.pid = pid;
771
+ __builtin_memcpy(ev.event_name, "odd_socket", 11);
772
+ __builtin_memcpy(&ev.payload[0], &family16, sizeof(family16));
773
+ __builtin_memcpy(&ev.payload[2], &type16, sizeof(type16));
774
+ __builtin_memcpy(&ev.payload[4], &proto16, sizeof(proto16));
775
+ submit_event(&ev);
776
+
777
+ return 0;
778
+ }
779
+
780
+ LSM_PROBE(socket_connect, struct socket *sock, struct sockaddr *address, int addrlen)
781
+ {
782
+ u64 pid_tgid = bpf_get_current_pid_tgid();
783
+ u32 pid = pid_tgid >> 32;
784
+ u32 tid = (u32)pid_tgid;
785
+ u16 family = 0;
786
+ u8 one = 1;
787
+
788
+ if (!target_enabled(pid, tid)) {
789
+ return 0;
790
+ }
791
+
792
+ if (!address) {
793
+ return 0;
794
+ }
795
+
796
+ bpf_probe_read_kernel(&family, sizeof(family), address);
797
+
798
+ struct event_t ev = {};
799
+ ev.pid = pid;
800
+ __builtin_memcpy(ev.event_name, "sock_connect", 13);
801
+ __builtin_memcpy(&ev.payload[0], &family, sizeof(family));
802
+
803
+ if (family == AF_INET) {
804
+ struct sockaddr_in_t sin = {};
805
+ bpf_probe_read_kernel(&sin, sizeof(sin), address);
806
+ __builtin_memcpy(&ev.payload[2], &sin.sin_port, sizeof(sin.sin_port));
807
+ __builtin_memcpy(&ev.payload[4], &sin.sin_addr, sizeof(sin.sin_addr));
808
+ if (sin.sin_port == __constant_htons(53)) {
809
+ dns_connected_tids.update(&tid, &one);
810
+ }
811
+ } else if (family == AF_INET6) {
812
+ struct sockaddr_in6_t sin6 = {};
813
+ bpf_probe_read_kernel(&sin6, sizeof(sin6), address);
814
+ __builtin_memcpy(&ev.payload[2], &sin6.sin6_port, sizeof(sin6.sin6_port));
815
+ __builtin_memcpy(&ev.payload[4], &sin6.sin6_addr, sizeof(sin6.sin6_addr));
816
+ if (sin6.sin6_port == __constant_htons(53)) {
817
+ dns_connected_tids.update(&tid, &one);
818
+ }
819
+ }
820
+
821
+ submit_event(&ev);
822
+
823
+ return 0;
824
+ }
825
+
826
+ TRACEPOINT_PROBE(syscalls, sys_enter_sendmsg)
827
+ {
828
+ u64 pid_tgid = bpf_get_current_pid_tgid();
829
+ u32 pid = pid_tgid >> 32;
830
+ u32 tid = (u32)pid_tgid;
831
+ struct user_msghdr_t msg = {};
832
+ struct iovec_t iov = {};
833
+
834
+ if (!target_enabled(pid, tid)) {
835
+ return 0;
836
+ }
837
+
838
+ if (!args->msg) {
839
+ return 0;
840
+ }
841
+
842
+ bpf_probe_read_user(&msg, sizeof(msg), args->msg);
843
+ if (!msg.msg_iov || msg.msg_iovlen == 0) {
844
+ return 0;
845
+ }
846
+
847
+ if (msg.msg_name && !is_dns_destination(msg.msg_name)) {
848
+ return 0;
849
+ }
850
+
851
+ bpf_probe_read_user(&iov, sizeof(iov), msg.msg_iov);
852
+ if (!iov.iov_base) {
853
+ return 0;
854
+ }
855
+
856
+ submit_dns_req(pid, (unsigned char *)iov.iov_base, (unsigned int)iov.iov_len);
857
+
858
+ return 0;
859
+ }
860
+
861
+ TRACEPOINT_PROBE(syscalls, sys_enter_sendto)
862
+ {
863
+ u64 pid_tgid = bpf_get_current_pid_tgid();
864
+ u32 pid = pid_tgid >> 32;
865
+ u32 tid = (u32)pid_tgid;
866
+ unsigned char *buff = args->buff;
867
+ int dns_match = 0;
868
+
869
+ if (!target_enabled(pid, tid)) {
870
+ return 0;
871
+ }
872
+
873
+ if (!buff) {
874
+ return 0;
875
+ }
876
+
877
+ if (args->addr) {
878
+ dns_match = is_dns_destination(args->addr);
879
+ } else {
880
+ u8 *connected = dns_connected_tids.lookup(&tid);
881
+ dns_match = connected && *connected == 1;
882
+ }
883
+
884
+ if (!dns_match) {
885
+ return 0;
886
+ }
887
+
888
+ submit_dns_req(pid, buff, args->len);
889
+ dns_connected_tids.delete(&tid);
890
+
891
+ return 0;
892
+ }
893
+
894
+ TRACEPOINT_PROBE(syscalls, sys_enter_sendmmsg)
895
+ {
896
+ u64 pid_tgid = bpf_get_current_pid_tgid();
897
+ u32 pid = pid_tgid >> 32;
898
+ u32 tid = (u32)pid_tgid;
899
+ struct mmsghdr_t mmsg = {};
900
+ struct iovec_t iov = {};
901
+
902
+ if (!target_enabled(pid, tid)) {
903
+ return 0;
904
+ }
905
+
906
+ if (!args->mmsg) {
907
+ return 0;
908
+ }
909
+
910
+ bpf_probe_read_user(&mmsg, sizeof(mmsg), args->mmsg);
911
+ if (mmsg.msg_hdr.msg_name && !is_dns_destination(mmsg.msg_hdr.msg_name)) {
912
+ return 0;
913
+ }
914
+
915
+ if (!mmsg.msg_hdr.msg_iov || mmsg.msg_hdr.msg_iovlen == 0) {
916
+ return 0;
917
+ }
918
+
919
+ bpf_probe_read_user(&iov, sizeof(iov), mmsg.msg_hdr.msg_iov);
920
+ if (!iov.iov_base) {
921
+ return 0;
922
+ }
923
+
924
+ submit_dns_req(pid, (unsigned char *)iov.iov_base, (unsigned int)iov.iov_len);
925
+
926
+ return 0;
927
+ }
928
+
929
+ TRACEPOINT_PROBE(syscalls, sys_enter_execve)
930
+ {
931
+ u64 pid_tgid = bpf_get_current_pid_tgid();
932
+ u32 pid = pid_tgid >> 32;
933
+ u32 tid = (u32)pid_tgid;
934
+ const char *argv0 = 0;
935
+ const char *argv1 = 0;
936
+ const char *argv2 = 0;
937
+
938
+ if (!target_enabled(pid, tid)) {
939
+ return 0;
940
+ }
941
+
942
+ if (!args->filename) {
943
+ return 0;
944
+ }
945
+
946
+ struct event_t ev = {};
947
+ ev.pid = pid;
948
+ __builtin_memcpy(ev.event_name, "proc_exec", 10);
949
+ bpf_probe_read_user_str(&ev.payload[0], #{PROC_EXEC_SLOT_SIZE}, args->filename);
950
+
951
+ if (args->argv) {
952
+ bpf_probe_read_user(&argv0, sizeof(argv0), &args->argv[0]);
953
+ bpf_probe_read_user(&argv1, sizeof(argv1), &args->argv[1]);
954
+ bpf_probe_read_user(&argv2, sizeof(argv2), &args->argv[2]);
955
+
956
+ if (argv0) {
957
+ bpf_probe_read_user_str(&ev.payload[#{PROC_EXEC_SLOT_SIZE}], #{PROC_EXEC_SLOT_SIZE}, argv0);
958
+ }
959
+ if (argv1) {
960
+ bpf_probe_read_user_str(&ev.payload[#{PROC_EXEC_SLOT_SIZE * 2}], #{PROC_EXEC_SLOT_SIZE}, argv1);
961
+ }
962
+ if (argv2) {
963
+ bpf_probe_read_user_str(&ev.payload[#{PROC_EXEC_SLOT_SIZE * 3}], #{PROC_EXEC_SLOT_SIZE}, argv2);
964
+ }
965
+ }
966
+
967
+ submit_event(&ev);
968
+ return 0;
969
+ }
970
+
971
+ LSM_PROBE(ptrace_access_check, struct task_struct *child, unsigned int mode)
972
+ {
973
+ u64 pid_tgid = bpf_get_current_pid_tgid();
974
+ u32 pid = pid_tgid >> 32;
975
+ u32 tid = (u32)pid_tgid;
976
+
977
+ if (!target_enabled(pid, tid)) {
978
+ return 0;
979
+ }
980
+
981
+ struct event_t ev = {};
982
+ u32 mode32 = mode;
983
+
984
+ ev.pid = pid;
985
+ __builtin_memcpy(ev.event_name, "ptrace_check", 13);
986
+ __builtin_memcpy(&ev.payload[0], &mode32, sizeof(mode32));
987
+ submit_event(&ev);
988
+
989
+ return 0;
990
+ }
991
+
992
+ LSM_PROBE(sb_mount, const char *dev_name, const struct path *path, const char *type, unsigned long flags, void *data)
993
+ {
994
+ u64 pid_tgid = bpf_get_current_pid_tgid();
995
+ u32 pid = pid_tgid >> 32;
996
+ u32 tid = (u32)pid_tgid;
997
+
998
+ if (!target_enabled(pid, tid)) {
999
+ return 0;
1000
+ }
1001
+
1002
+ struct event_t ev = {};
1003
+ u64 flags64 = flags;
1004
+
1005
+ ev.pid = pid;
1006
+ __builtin_memcpy(ev.event_name, "sb_mount", 9);
1007
+ __builtin_memcpy(&ev.payload[0], &flags64, sizeof(flags64));
1008
+
1009
+ if (dev_name) {
1010
+ bpf_probe_read_kernel_str(&ev.payload[8], 120, dev_name);
1011
+ }
1012
+ if (type) {
1013
+ bpf_probe_read_kernel_str(&ev.payload[128], 120, type);
1014
+ }
1015
+
1016
+ submit_event(&ev);
1017
+
1018
+ return 0;
1019
+ }
1020
+
1021
+ LSM_PROBE(kernel_read_file, struct file *file, int id, int contents)
1022
+ {
1023
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1024
+ u32 pid = pid_tgid >> 32;
1025
+ u32 tid = (u32)pid_tgid;
1026
+
1027
+ if (!target_enabled(pid, tid)) {
1028
+ return 0;
1029
+ }
1030
+
1031
+ struct event_t ev = {};
1032
+ u32 id32 = id;
1033
+ u32 contents32 = contents;
1034
+
1035
+ ev.pid = pid;
1036
+ __builtin_memcpy(ev.event_name, "kernel_read_file", 16);
1037
+ __builtin_memcpy(&ev.payload[0], &id32, sizeof(id32));
1038
+ __builtin_memcpy(&ev.payload[4], &contents32, sizeof(contents32));
1039
+ submit_event(&ev);
1040
+
1041
+ return 0;
1042
+ }
1043
+
1044
+ LSM_PROBE(task_kill, struct task_struct *p, struct kernel_siginfo *info, int sig, const struct cred *cred)
1045
+ {
1046
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1047
+ u32 pid = pid_tgid >> 32;
1048
+ u32 tid = (u32)pid_tgid;
1049
+
1050
+ if (!target_enabled(pid, tid)) {
1051
+ return 0;
1052
+ }
48
1053
 
49
- new(pid: pid, event_name: event_name, payload: payload)
50
- end
1054
+ struct event_t ev = {};
51
1055
 
52
- def self.c_string(bytes)
53
- str = bytes.to_s.b
54
- nul = str.index("\x00")
55
- return str if nul.nil?
1056
+ ev.pid = pid;
1057
+ __builtin_memcpy(ev.event_name, "task_kill", 10);
1058
+ __builtin_memcpy(&ev.payload[0], &sig, sizeof(sig));
1059
+ submit_event(&ev);
56
1060
 
57
- str[0, nul]
58
- end
59
- end
1061
+ return 0;
1062
+ }
60
1063
 
61
- class MapStore
62
- def initialize(pin_dir: Vivarium.bpf_pin_dir)
63
- @pin_dir = pin_dir
64
- @config_root_targets = RbBCC::HashTable.from_pin(
65
- File.join(@pin_dir, "config_root_targets"),
66
- "unsigned int",
67
- "unsigned char",
68
- keysize: 4,
69
- leafsize: 1
70
- )
71
- @config_spawned_targets = RbBCC::HashTable.from_pin(
72
- File.join(@pin_dir, "config_spawned_targets"),
73
- "unsigned int",
74
- "unsigned char",
75
- keysize: 4,
76
- leafsize: 1
77
- )
78
- @event_invoked = RbBCC::ArrayTable.from_pin(
79
- File.join(@pin_dir, "event_invoked"),
80
- "unsigned int",
81
- "char[#{EVENT_STRUCT_SIZE}]",
82
- keysize: 4,
83
- leafsize: EVENT_STRUCT_SIZE
84
- )
85
- @event_write_pos = RbBCC::ArrayTable.from_pin(
86
- File.join(@pin_dir, "event_write_pos"),
87
- "unsigned int",
88
- "unsigned int",
89
- keysize: 4,
90
- leafsize: 4
91
- )
92
- rescue StandardError => e
93
- raise Error, "failed to open pinned maps under #{@pin_dir}: #{e.class}: #{e.message}"
94
- end
1064
+ LSM_PROBE(task_fix_setuid, struct cred *new, const struct cred *old, int flags)
1065
+ {
1066
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1067
+ u32 pid = pid_tgid >> 32;
1068
+ u32 tid = (u32)pid_tgid;
95
1069
 
96
- def register_pid(pid)
97
- @config_root_targets[pid] = 1
98
- end
1070
+ if (!target_enabled(pid, tid)) {
1071
+ return 0;
1072
+ }
99
1073
 
100
- def unregister_pid(pid)
101
- @config_root_targets.delete(pid)
102
- @config_spawned_targets.clear
103
- rescue KeyError
104
- nil
105
- end
1074
+ struct event_t ev = {};
1075
+ u32 flags32 = flags;
106
1076
 
107
- def drain_events
108
- events = []
109
- EVENT_CAPACITY.times do |idx|
110
- ptr = @event_invoked[idx]
111
- next unless ptr
1077
+ ev.pid = pid;
1078
+ __builtin_memcpy(ev.event_name, "setid_change", 13);
1079
+ __builtin_memcpy(&ev.payload[0], &flags32, sizeof(flags32));
1080
+ submit_event(&ev);
112
1081
 
113
- event = Event.from_binary(ptr[0, EVENT_STRUCT_SIZE])
114
- next if event.empty?
1082
+ return 0;
1083
+ }
115
1084
 
116
- events << event
117
- @event_invoked[idx] = zeroed_event_ptr
118
- end
1085
+ LSM_PROBE(capable, const struct cred *cred, struct user_namespace *targ_ns, int cap, unsigned int opts)
1086
+ {
1087
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1088
+ u32 pid = pid_tgid >> 32;
1089
+ u32 tid = (u32)pid_tgid;
119
1090
 
120
- @event_write_pos[0] = 0
121
- events
122
- end
1091
+ if (!target_enabled(pid, tid)) {
1092
+ return 0;
1093
+ }
123
1094
 
124
- private
1095
+ if (!monitored_capability(cap)) {
1096
+ return 0;
1097
+ }
125
1098
 
126
- def zeroed_event_ptr
127
- ptr = Fiddle::Pointer.malloc(EVENT_STRUCT_SIZE)
128
- ptr[0, EVENT_STRUCT_SIZE] = "\x00" * EVENT_STRUCT_SIZE
129
- ptr
130
- end
131
- end
1099
+ struct event_t ev = {};
1100
+ u32 cap32 = cap;
1101
+ u32 opts32 = opts;
132
1102
 
133
- class Daemon
134
- BPF_PROGRAM_TEMPLATE = <<~CLANG
135
- struct path {
136
- void *mnt;
137
- void *dentry;
138
- };
139
- struct file {
140
- char __off[__VIVARIUM_F_PATH_OFFSET__];
141
- struct path f_path;
142
- };
1103
+ ev.pid = pid;
1104
+ __builtin_memcpy(ev.event_name, "capable_check", 14);
1105
+ __builtin_memcpy(&ev.payload[0], &cap32, sizeof(cap32));
1106
+ __builtin_memcpy(&ev.payload[4], &opts32, sizeof(opts32));
1107
+ submit_event(&ev);
143
1108
 
144
- struct event_t {
145
- u32 pid;
146
- char event_name[16];
147
- char payload[#{EVENT_PAYLOAD_SIZE}];
148
- };
1109
+ return 0;
1110
+ }
149
1111
 
150
- BPF_HASH(config_root_targets, u32, u8, 1024);
151
- BPF_HASH(config_spawned_targets, u32, u8, 8192);
152
- BPF_ARRAY(event_invoked, struct event_t, 1024);
153
- BPF_ARRAY(event_write_pos, u32, 1);
1112
+ LSM_PROBE(bprm_creds_from_file, struct linux_binprm *bprm, struct file *file)
1113
+ {
1114
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1115
+ u32 pid = pid_tgid >> 32;
1116
+ u32 tid = (u32)pid_tgid;
154
1117
 
155
- static __always_inline int target_enabled(u32 pid, u32 tid)
1118
+ if (!target_enabled(pid, tid)) {
1119
+ return 0;
1120
+ }
1121
+
1122
+ struct event_t ev = {};
1123
+ u8 has_file = 0;
1124
+
1125
+ ev.pid = pid;
1126
+ __builtin_memcpy(ev.event_name, "bprm_creds", 11);
1127
+
1128
+ if (file) {
1129
+ has_file = 1;
1130
+ bpf_d_path(&file->f_path, &ev.payload[1], sizeof(ev.payload) - 1);
1131
+ }
1132
+
1133
+ __builtin_memcpy(&ev.payload[0], &has_file, sizeof(has_file));
1134
+ submit_event(&ev);
1135
+
1136
+ return 0;
1137
+ }
1138
+
1139
+ LSM_PROBE(inode_symlink, struct inode *dir, struct dentry *dentry, const char *oldname)
156
1140
  {
157
- u8 *enabled_root = config_root_targets.lookup(&pid);
158
- if (enabled_root && *enabled_root == 1) {
159
- return 1;
1141
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1142
+ u32 pid = pid_tgid >> 32;
1143
+ u32 tid = (u32)pid_tgid;
1144
+
1145
+ if (!target_enabled(pid, tid)) {
1146
+ return 0;
160
1147
  }
161
1148
 
162
- u8 *enabled_spawned = config_spawned_targets.lookup(&tid);
163
- if (enabled_spawned && *enabled_spawned == 1) {
164
- return 1;
1149
+ struct event_t ev = {};
1150
+ ev.pid = pid;
1151
+ __builtin_memcpy(ev.event_name, "file_symlink", 13);
1152
+
1153
+ if (oldname) {
1154
+ bpf_probe_read_user_str(&ev.payload[0], 128, oldname);
1155
+ }
1156
+
1157
+ if (dentry) {
1158
+ read_dentry_name(dentry, &ev.payload[128], 128);
165
1159
  }
166
1160
 
1161
+ submit_event(&ev);
167
1162
  return 0;
168
1163
  }
169
1164
 
170
- TRACEPOINT_PROBE(sched, sched_process_fork)
1165
+ LSM_PROBE(inode_link, struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
171
1166
  {
172
- u32 parent = args->parent_pid;
173
- u32 child = args->child_pid;
174
- u8 one = 1;
1167
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1168
+ u32 pid = pid_tgid >> 32;
1169
+ u32 tid = (u32)pid_tgid;
175
1170
 
176
- u8 *enabled_root = config_root_targets.lookup(&parent);
177
- if (enabled_root && *enabled_root == 1) {
178
- config_spawned_targets.update(&child, &one);
1171
+ if (!target_enabled(pid, tid)) {
179
1172
  return 0;
180
1173
  }
181
1174
 
182
- u8 *enabled_spawned = config_spawned_targets.lookup(&parent);
183
- if (enabled_spawned && *enabled_spawned == 1) {
184
- config_spawned_targets.update(&child, &one);
1175
+ struct event_t ev = {};
1176
+ ev.pid = pid;
1177
+ __builtin_memcpy(ev.event_name, "file_hardlink", 14);
1178
+
1179
+ if (old_dentry) {
1180
+ read_dentry_name(old_dentry, &ev.payload[0], 128);
1181
+ }
1182
+
1183
+ if (new_dentry) {
1184
+ read_dentry_name(new_dentry, &ev.payload[128], 128);
185
1185
  }
186
1186
 
1187
+ submit_event(&ev);
187
1188
  return 0;
188
1189
  }
189
1190
 
190
- TRACEPOINT_PROBE(sched, sched_process_exit)
1191
+ LSM_PROBE(inode_rename, struct inode *old_dir, struct dentry *old_dentry,
1192
+ struct inode *new_dir, struct dentry *new_dentry, unsigned int flags)
191
1193
  {
192
- u32 tid = (u32)bpf_get_current_pid_tgid();
193
- config_spawned_targets.delete(&tid);
1194
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1195
+ u32 pid = pid_tgid >> 32;
1196
+ u32 tid = (u32)pid_tgid;
1197
+
1198
+ if (!target_enabled(pid, tid)) {
1199
+ return 0;
1200
+ }
1201
+
1202
+ struct event_t ev = {};
1203
+ ev.pid = pid;
1204
+ __builtin_memcpy(ev.event_name, "file_rename", 12);
1205
+
1206
+ if (old_dentry) {
1207
+ read_dentry_name(old_dentry, &ev.payload[0], 128);
1208
+ }
1209
+
1210
+ if (new_dentry) {
1211
+ read_dentry_name(new_dentry, &ev.payload[128], 128);
1212
+ }
1213
+
1214
+ submit_event(&ev);
194
1215
  return 0;
195
1216
  }
196
1217
 
197
- LSM_PROBE(file_open, struct file *file)
1218
+ LSM_PROBE(path_chmod, struct path *path, umode_t mode)
198
1219
  {
199
1220
  u64 pid_tgid = bpf_get_current_pid_tgid();
200
1221
  u32 pid = pid_tgid >> 32;
201
1222
  u32 tid = (u32)pid_tgid;
202
- bpf_trace_printk("vivarium: invoked pid=%d\\n", pid);
1223
+
203
1224
  if (!target_enabled(pid, tid)) {
204
1225
  return 0;
205
1226
  }
206
1227
 
207
- u32 zero = 0;
208
- u32 *write_pos = event_write_pos.lookup(&zero);
209
- if (!write_pos) {
1228
+ if (!path) {
210
1229
  return 0;
211
1230
  }
212
1231
 
213
- u32 idx = *write_pos & 1023;
214
- __sync_fetch_and_add(write_pos, 1);
215
1232
  struct event_t ev = {};
216
- int path_ret;
1233
+ u16 mode_short = mode & 0xFFFF;
217
1234
  ev.pid = pid;
218
- __builtin_memcpy(ev.event_name, "path_open", 9);
1235
+ __builtin_memcpy(ev.event_name, "file_chmod", 11);
1236
+ __builtin_memcpy(&ev.payload[0], &mode_short, sizeof(mode_short));
219
1237
 
220
- path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
221
- if (path_ret < 0) {
222
- if (ev.payload[0] == 0) {
223
- __builtin_memcpy(ev.payload, "<path_error>", 13);
224
- bpf_trace_printk("vivarium: failed to obtain full path. pid=%d path=%s\\n", pid, ev.payload);
225
- }
1238
+ bpf_d_path(path, &ev.payload[2], sizeof(ev.payload) - 2);
1239
+ submit_event(&ev);
1240
+ return 0;
1241
+ }
1242
+
1243
+ TRACEPOINT_PROBE(syscalls, sys_enter_getdents64)
1244
+ {
1245
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1246
+ u32 pid = pid_tgid >> 32;
1247
+ u32 tid = (u32)pid_tgid;
1248
+
1249
+ if (!target_enabled(pid, tid)) {
1250
+ return 0;
226
1251
  }
227
1252
 
228
- bpf_trace_printk("vivarium: pid=%d path=%s\\n", pid, ev.payload);
229
- event_invoked.update(&idx, &ev);
1253
+ struct event_t ev = {};
1254
+ u32 fd = args->fd;
1255
+ u32 count = args->count;
230
1256
 
1257
+ ev.pid = pid;
1258
+ __builtin_memcpy(ev.event_name, "file_getdents", 14);
1259
+ __builtin_memcpy(&ev.payload[0], &fd, sizeof(fd));
1260
+ __builtin_memcpy(&ev.payload[4], &count, sizeof(count));
1261
+
1262
+ submit_event(&ev);
231
1263
  return 0;
232
1264
  }
233
1265
  CLANG
@@ -241,7 +1273,10 @@ module Vivarium
241
1273
  FileUtils.mkdir_p(@pin_dir)
242
1274
 
243
1275
  f_path_offset = detect_f_path_offset
244
- program = BPF_PROGRAM_TEMPLATE.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
1276
+ d_name_offset = detect_dentry_d_name_offset
1277
+ program = BPF_PROGRAM_TEMPLATE
1278
+ .gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
1279
+ .gsub("__VIVARIUM_DENTRY_D_NAME_OFFSET__", d_name_offset.to_s)
245
1280
 
246
1281
  bpf = RbBCC::BCC.new(text: program)
247
1282
  kprint_thread = start_kprint_logger(bpf)
@@ -377,6 +1412,56 @@ module Vivarium
377
1412
  rescue Errno::ENOENT
378
1413
  raise Error, "bpftool is required to resolve struct file::f_path offset"
379
1414
  end
1415
+
1416
+ def detect_dentry_d_name_offset
1417
+ env_offset = ENV["VIVARIUM_DENTRY_D_NAME_OFFSET"]
1418
+ return Integer(env_offset, 10) if env_offset
1419
+
1420
+ raw = IO.popen(
1421
+ %w[bpftool btf dump file /sys/kernel/btf/vmlinux format raw],
1422
+ err: IO::NULL,
1423
+ &:read
1424
+ )
1425
+
1426
+ in_dentry_struct = false
1427
+ d_name_bits_offset = nil
1428
+
1429
+ raw.each_line do |line|
1430
+ if line =~ /^\[\d+\] STRUCT 'dentry' /
1431
+ in_dentry_struct = true
1432
+ next
1433
+ end
1434
+
1435
+ if in_dentry_struct && line.start_with?("[")
1436
+ break
1437
+ end
1438
+
1439
+ next unless in_dentry_struct
1440
+
1441
+ if (match = line.match(/'d_name'.*bits_offset=(\d+)/))
1442
+ d_name_bits_offset = Integer(match[1], 10)
1443
+ break
1444
+ end
1445
+ end
1446
+
1447
+ if d_name_bits_offset
1448
+ if (d_name_bits_offset % 8).positive?
1449
+ raise Error, "unsupported d_name bits offset=#{d_name_bits_offset}"
1450
+ end
1451
+
1452
+ if d_name_bits_offset >= 1024
1453
+ warn "[vivariumd] suspicious d_name offset=#{d_name_bits_offset / 8}, fallback to offset=32"
1454
+ return 32
1455
+ end
1456
+
1457
+ return d_name_bits_offset / 8
1458
+ end
1459
+
1460
+ warn "[vivariumd] could not find struct dentry::d_name in BTF, fallback to offset=32"
1461
+ 32
1462
+ rescue Errno::ENOENT
1463
+ raise Error, "bpftool is required to resolve struct dentry::d_name offset"
1464
+ end
380
1465
  end
381
1466
 
382
1467
  class ObservationSession