vivarium 0.3.1 → 0.3.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 150b5b98d7555e1954b0002ca2224b1e445b799fa5d163ab4bbcbc8cac42ea74
4
- data.tar.gz: 5984b875d3fab3600c86f9e1d961a0c1ab84945f1c1ea76e78e72248ec2fd9f7
3
+ metadata.gz: a0e7e80cef690342f163fde6c05fed6859c3ef81aa794fe532c87b00fbe11cd5
4
+ data.tar.gz: 4f2de93c9795fe93ecb0d8291f86a117ac0f595c8c12e2c7b41caabaae7c3273
5
5
  SHA512:
6
- metadata.gz: 17847e5f3852628e8bc58f4a31fc4e934b0b06d35d7961ac811d27d34ffcb4a8f17fd9e9c7f61253b9e7d37db09ee9d06354be637f8953c4bd1827baf8d3e0e7
7
- data.tar.gz: 1980ac521ccd83eadb68bbbb5b6cb6a6053f3737c2b8c7aaeded5b9ace3563b34666da472bfe133204caf5889a4e77febea2c9069900e4f040a728a6f609b75a
6
+ metadata.gz: 9a116f2c50b978a368a05cca351411f9724686746820085b651038d060c8452a8546170cb6e6ac885ab8a721aa20bcf42c5746639d32cbfea0ba8a9452b9687e
7
+ data.tar.gz: fb5b4b5307682fa84b77dcfaa2165a71a5e16deef8d7b7d21d6b3267e82b5a12ce1f73dee946193605e039a247d59613a9c2dffca15ef96ddb502a17c9ce742d
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "fiddle"
5
+ require "vivarium"
6
+
7
+ FILTER = {
8
+ include_events: %w[dlopen mmap_exec]
9
+ }.freeze
10
+
11
+ # Usage:
12
+ # 1) In another shell (root): sudo bundle exec vivariumd
13
+ # (dlopen uprobe is attached automatically when libc is found)
14
+ # 2) Run this script: bundle exec ruby examples/dlopen_demo.rb
15
+ #
16
+ # Expected output: "DL dlopen" and "DL mmap_exec" events for each
17
+ # library loaded via Fiddle.dlopen.
18
+ #
19
+ # You can disable the dlopen uprobe with `sudo vivariumd --no-dlopen-trace`
20
+ # or point at a specific libc with `sudo vivariumd --libc /lib/x86_64-linux-gnu/libc.so.6`.
21
+
22
+ Vivarium.observe(filter: FILTER) do
23
+ # libm: math functions — almost universally available
24
+ begin
25
+ libm = Fiddle.dlopen("libm.so.6")
26
+ sin_fn = Fiddle::Function.new(libm["sin"], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE)
27
+ puts "[dlopen_demo] sin(PI/4) = #{sin_fn.call(Math::PI / 4).round(6)}"
28
+ libm.close
29
+ rescue Fiddle::DLError => e
30
+ warn "[dlopen_demo] libm: #{e.message}"
31
+ end
32
+
33
+ # libsqlite3: a common library that may not be loaded at startup
34
+ begin
35
+ libsqlite3 = Fiddle.dlopen("libsqlite3.so.0")
36
+ puts "[dlopen_demo] libsqlite3 loaded: version = #{Fiddle::Function.new(libsqlite3["sqlite3_libversion"], [], Fiddle::TYPE_VOIDP).call}"
37
+ libsqlite3.close
38
+ rescue Fiddle::DLError => e
39
+ warn "[dlopen_demo] libsqlite3: #{e.message}"
40
+ end
41
+
42
+ # Spawn a child process that also calls dlopen — its events should
43
+ # appear under a PROC node in the tree (descendant PID tracking).
44
+ # Unbundle so Bundler does not spawn anything.
45
+ Bundler.with_unbundled_env do
46
+ system("ruby -e 'require \"fiddle\"; Fiddle.dlopen(\"libm.so.6\").close'")
47
+ end
48
+ end
49
+
50
+ puts "[dlopen_demo] done"
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Standalone demo: shows what DROP warning nodes look like in the TreeRenderer
5
+ # output. Does NOT require BPF or vivariumd — constructs RawEvent objects
6
+ # directly and feeds them to TreeRenderer.
7
+ #
8
+ # Usage:
9
+ # ruby examples/drop_demo.rb
10
+
11
+ $LOAD_PATH.unshift File.join(__dir__, "..", "lib")
12
+
13
+ require "vivarium"
14
+ require "vivarium/correlator"
15
+ require "vivarium/tree_renderer"
16
+ require "vivarium/display_filter"
17
+ require "vivarium_usdt"
18
+
19
+ t0 = 1_000_000_000 # base ktime_ns
20
+ pid = Process.pid
21
+ tid = Process.pid
22
+ method_id = 0x0001_0001
23
+
24
+ # span_start payload: method_id (8B) + file_id (8B) + lineno (8B)
25
+ span_start_payload = [method_id, 0, 10].pack("q<q<q<")
26
+ .ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00")
27
+
28
+ events = [
29
+ Vivarium::Correlator::RawEvent.new(
30
+ ktime_ns: t0,
31
+ pid: pid, tid: tid,
32
+ event_name: "span_start",
33
+ payload: span_start_payload,
34
+ dropped_since_last: 0
35
+ ),
36
+ # This event carries drop info: 5 events were lost before it arrived
37
+ Vivarium::Correlator::RawEvent.new(
38
+ ktime_ns: t0 + 10_000_000,
39
+ pid: pid, tid: tid,
40
+ event_name: "path_open",
41
+ payload: "/etc/passwd\x00".b.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
42
+ dropped_since_last: 5
43
+ ),
44
+ Vivarium::Correlator::RawEvent.new(
45
+ ktime_ns: t0 + 20_000_000,
46
+ pid: pid, tid: tid,
47
+ event_name: "dns_req",
48
+ payload: "\x06google\x03com\x00".b.ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
49
+ dropped_since_last: 0
50
+ ),
51
+ # Another burst: 12 events dropped just before this sock_connect
52
+ Vivarium::Correlator::RawEvent.new(
53
+ ktime_ns: t0 + 25_000_000,
54
+ pid: pid, tid: tid,
55
+ event_name: "sock_connect",
56
+ payload: [2, 443, 0x7f000001, 0].pack("S<nNN").ljust(Vivarium::EVENT_PAYLOAD_SIZE, "\x00"),
57
+ dropped_since_last: 12
58
+ ),
59
+ Vivarium::Correlator::RawEvent.new(
60
+ ktime_ns: t0 + 30_000_000,
61
+ pid: pid, tid: tid,
62
+ event_name: "span_stop",
63
+ payload: "\x00" * Vivarium::EVENT_PAYLOAD_SIZE,
64
+ dropped_since_last: 0
65
+ ),
66
+ ]
67
+
68
+ Vivarium::TreeRenderer.new(
69
+ events: events,
70
+ method_table: { method_id => "MyClass#my_method" },
71
+ observer_pid: pid,
72
+ main_tid: tid,
73
+ session_start_iso: "2026-06-02T00:00:00.000Z",
74
+ session_start_ktime: t0,
75
+ session_stop_iso: "2026-06-02T00:00:00.030Z",
76
+ session_stop_ktime: t0 + 30_000_000,
77
+ dest: $stdout
78
+ ).render
@@ -6,7 +6,7 @@ require "time"
6
6
  module Vivarium
7
7
  class Correlator
8
8
  RawEvent = Struct.new(
9
- :ktime_ns, :pid, :tid, :event_name, :payload,
9
+ :ktime_ns, :pid, :tid, :event_name, :payload, :dropped_since_last,
10
10
  keyword_init: true
11
11
  )
12
12
 
@@ -17,6 +17,7 @@ module Vivarium
17
17
  u32 tid;
18
18
  char event_name[16];
19
19
  char payload[256];
20
+ u64 dropped_since_last;
20
21
  };
21
22
  C
22
23
 
@@ -104,11 +105,12 @@ module Vivarium
104
105
  bytes = data[0, size].to_s.b
105
106
  bytes = bytes.ljust(Vivarium::EVENT_STRUCT_SIZE, "\x00") if bytes.bytesize < Vivarium::EVENT_STRUCT_SIZE
106
107
 
107
- ktime_ns = bytes[Vivarium::EVENT_TS_OFFSET, Vivarium::EVENT_TS_SIZE].unpack1("Q<")
108
- pid = bytes[Vivarium::EVENT_PID_OFFSET, 4].unpack1("L<")
109
- tid = bytes[Vivarium::EVENT_TID_OFFSET, 4].unpack1("L<")
110
- event_name = Vivarium.c_string(bytes[Vivarium::EVENT_NAME_OFFSET, Vivarium::EVENT_NAME_SIZE])
111
- payload = bytes[Vivarium::EVENT_PAYLOAD_OFFSET, Vivarium::EVENT_PAYLOAD_SIZE].to_s.b
108
+ ktime_ns = bytes[Vivarium::EVENT_TS_OFFSET, Vivarium::EVENT_TS_SIZE].unpack1("Q<")
109
+ pid = bytes[Vivarium::EVENT_PID_OFFSET, 4].unpack1("L<")
110
+ tid = bytes[Vivarium::EVENT_TID_OFFSET, 4].unpack1("L<")
111
+ event_name = Vivarium.c_string(bytes[Vivarium::EVENT_NAME_OFFSET, Vivarium::EVENT_NAME_SIZE])
112
+ payload = bytes[Vivarium::EVENT_PAYLOAD_OFFSET, Vivarium::EVENT_PAYLOAD_SIZE].to_s.b
113
+ dropped_since_last = bytes[Vivarium::EVENT_DROPPED_OFFSET, 8].unpack1("Q<")
112
114
 
113
115
  @events_mutex.synchronize do
114
116
  @events << RawEvent.new(
@@ -116,7 +118,8 @@ module Vivarium
116
118
  pid: pid,
117
119
  tid: tid,
118
120
  event_name: event_name,
119
- payload: payload
121
+ payload: payload,
122
+ dropped_since_last: dropped_since_last
120
123
  )
121
124
  end
122
125
  rescue StandardError => e
@@ -22,6 +22,7 @@ module Vivarium
22
22
  ].to_set.freeze
23
23
 
24
24
  UPROBE_EVENT_NAMES = %w[ssl_write].to_set.freeze
25
+ DL_EVENT_NAMES = %w[dlopen mmap_exec].to_set.freeze
25
26
 
26
27
  SYNTHETIC_SPAN_NAME = "<no-span>"
27
28
  UNRESOLVED_METHOD_PREFIX = "<method_id="
@@ -324,6 +325,7 @@ module Vivarium
324
325
  def build_span_children(span)
325
326
  proc_node_by_pid = {}
326
327
  root_children = []
328
+ prev_event_ktime = span.start_ktime
327
329
 
328
330
  span.events.each do |ev|
329
331
  target_text = nil
@@ -353,6 +355,7 @@ module Vivarium
353
355
  )
354
356
 
355
357
  parent_container = container_for_pid(ev.pid, span, proc_node_by_pid, root_children)
358
+ maybe_inject_drop_node(parent_container, ev, span, prev_event_ktime)
356
359
  append_event(parent_container, ev_node)
357
360
  else
358
361
  ev_node = EventNode.new(
@@ -363,12 +366,11 @@ module Vivarium
363
366
  child_proc: nil
364
367
  )
365
368
 
366
- if ev.pid == @observer_pid && ev.tid == span.tid
367
- append_event(root_children, ev_node)
369
+ container = if ev.pid == @observer_pid && ev.tid == span.tid
370
+ root_children
368
371
  elsif (node = proc_node_by_pid[ev.pid])
369
- append_event(node.children, ev_node)
372
+ node.children
370
373
  else
371
- # event from a descendant pid we haven't materialized — synthesize a stub PROC node
372
374
  stub = ProcNode.new(
373
375
  pid: ev.pid,
374
376
  comm: @pid_comm[ev.pid] || "?",
@@ -376,14 +378,19 @@ module Vivarium
376
378
  children: []
377
379
  )
378
380
  proc_node_by_pid[ev.pid] = stub
379
- append_event(stub.children, ev_node)
380
381
  root_children << stub
382
+ stub.children
381
383
  end
382
384
 
385
+ maybe_inject_drop_node(container, ev, span, prev_event_ktime)
386
+ append_event(container, ev_node)
387
+
383
388
  if ev.event_name == EXEC_EVENT_NAME && (node = proc_node_by_pid[ev.pid])
384
389
  node.comm = @pid_comm[ev.pid] || node.comm
385
390
  end
386
391
  end
392
+
393
+ prev_event_ktime = ev.ktime_ns
387
394
  end
388
395
 
389
396
  # Interleave child spans by start time among the event/proc nodes
@@ -429,6 +436,23 @@ module Vivarium
429
436
  container << ev_node
430
437
  end
431
438
 
439
+ def maybe_inject_drop_node(container, ev, span, prev_event_ktime = nil)
440
+ n = ev.dropped_since_last.to_i
441
+ return if n.zero?
442
+
443
+ # Show the start of the drop window (= time of last good event).
444
+ # The end is implicitly ev.ktime_ns (shown on the following event line).
445
+ drop_start_ns = prev_event_ktime ? (prev_event_ktime - span.start_ktime) : nil
446
+
447
+ container << EventNode.new(
448
+ kind: "DROP",
449
+ name: "dropped_events",
450
+ target: "#{n} event(s) lost (ringbuf overflow)",
451
+ offset_ns: drop_start_ns,
452
+ child_proc: nil
453
+ )
454
+ end
455
+
432
456
  def print_nodes(nodes, prefix)
433
457
  visible_nodes = nodes.select do |node|
434
458
  !node.is_a?(Span) || span_visible?(node)
@@ -491,6 +515,7 @@ module Vivarium
491
515
  return "EXCP" if ev.event_name == "span_raise"
492
516
  return "USDT" if SPAN_EVENT_NAMES.include?(ev.event_name)
493
517
  return "SSL" if ev.event_name == SSL_WRITE_EVENT_NAME
518
+ return "DL" if DL_EVENT_NAMES.include?(ev.event_name)
494
519
  return "LSM" if LSM_EVENT_NAMES.include?(ev.event_name)
495
520
  return "TP" if TP_EVENT_NAMES.include?(ev.event_name)
496
521
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vivarium
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.2"
5
5
  end
data/lib/vivarium.rb CHANGED
@@ -24,12 +24,13 @@ module Vivarium
24
24
  EVENT_TS_SIZE = 8
25
25
  PROC_EXEC_SLOT_SIZE = 64
26
26
  PROC_EXEC_SLOT_COUNT = 4
27
- EVENT_STRUCT_SIZE = 288
27
+ EVENT_STRUCT_SIZE = 296
28
28
  EVENT_TS_OFFSET = 0
29
29
  EVENT_PID_OFFSET = 8
30
30
  EVENT_TID_OFFSET = 12
31
31
  EVENT_NAME_OFFSET = 16
32
32
  EVENT_PAYLOAD_OFFSET = 32
33
+ EVENT_DROPPED_OFFSET = 288
33
34
  EVENTS_RINGBUF_PAGES = 256
34
35
 
35
36
  SSL_WRITE_PAYLOAD_DATA_LEN_OFFSET = 0
@@ -52,6 +53,16 @@ module Vivarium
52
53
  "/usr/lib/libssl.so.1.1"
53
54
  ].freeze
54
55
 
56
+ LIBC_SEARCH_PATHS = [
57
+ "/lib/x86_64-linux-gnu/libc.so.6",
58
+ "/lib/aarch64-linux-gnu/libc.so.6",
59
+ "/usr/lib/x86_64-linux-gnu/libc.so.6",
60
+ "/usr/lib/aarch64-linux-gnu/libc.so.6",
61
+ "/lib64/libc.so.6",
62
+ "/usr/lib64/libc.so.6",
63
+ "/lib/libc.so.6",
64
+ ].freeze
65
+
55
66
  SPAN_ALLOWCLASSES = [
56
67
  Socket,
57
68
  BasicSocket,
@@ -79,6 +90,7 @@ module Vivarium
79
90
  EVENT_SEVERITY_HIGH = %w[
80
91
  capable_check bprm_creds setid_change task_kill
81
92
  ptrace_check sb_mount kernel_read_file
93
+ dlopen
82
94
  ].freeze
83
95
 
84
96
  CAPABILITY_NAMES = {
@@ -463,6 +475,8 @@ module Vivarium
463
475
  when "ssl_write"
464
476
  decoded = decode_ssl_write_payload(event.payload)
465
477
  "data_len=#{decoded[:data_len]} cap_len=#{decoded[:cap_len]}"
478
+ when "dlopen", "mmap_exec"
479
+ strip_to_first_null(event.payload).inspect
466
480
  else
467
481
  strip_to_first_null(event.payload).inspect
468
482
  end
@@ -619,12 +633,14 @@ module Vivarium
619
633
  u32 tid;
620
634
  char event_name[16];
621
635
  char payload[#{EVENT_PAYLOAD_SIZE}];
636
+ u64 dropped_since_last;
622
637
  };
623
638
 
624
639
  BPF_HASH(config_root_targets, u32, u8, 1024);
625
640
  BPF_HASH(config_spawned_targets, u32, u8, 8192);
626
641
  BPF_HASH(dns_connected_tids, u32, u8, 8192);
627
642
  BPF_RINGBUF_OUTPUT(events, #{EVENTS_RINGBUF_PAGES});
643
+ BPF_ARRAY(drop_counter, u64, 1);
628
644
 
629
645
  static __always_inline int target_enabled(u32 pid, u32 tid)
630
646
  {
@@ -666,14 +682,27 @@ module Vivarium
666
682
 
667
683
  static __always_inline void submit_event(struct event_t *src)
668
684
  {
685
+ u32 key = 0;
686
+ u64 *cnt;
687
+
669
688
  struct event_t *ev = events.ringbuf_reserve(sizeof(struct event_t));
670
689
  if (!ev) {
690
+ cnt = drop_counter.lookup(&key);
691
+ if (cnt) {
692
+ __sync_fetch_and_add(cnt, 1);
693
+ }
671
694
  return;
672
695
  }
673
696
 
674
697
  __builtin_memcpy(ev, src, sizeof(*ev));
675
698
  ev->ktime_ns = bpf_ktime_get_ns();
676
699
  ev->tid = (u32)bpf_get_current_pid_tgid();
700
+ ev->dropped_since_last = 0;
701
+
702
+ cnt = drop_counter.lookup(&key);
703
+ if (cnt && *cnt > 0) {
704
+ ev->dropped_since_last = __sync_lock_test_and_set(cnt, 0);
705
+ }
677
706
 
678
707
  events.ringbuf_submit(ev, 0);
679
708
  }
@@ -807,6 +836,39 @@ module Vivarium
807
836
  return 0;
808
837
  }
809
838
 
839
+ LSM_PROBE(mmap_file, struct file *file, unsigned long reqprot,
840
+ unsigned long prot, unsigned long flags)
841
+ {
842
+ if (!file) {
843
+ return 0;
844
+ }
845
+ if (!((prot | reqprot) & 0x04)) { /* PROT_EXEC */
846
+ return 0;
847
+ }
848
+
849
+ u64 pid_tgid = bpf_get_current_pid_tgid();
850
+ u32 pid = pid_tgid >> 32;
851
+ u32 tid = (u32)pid_tgid;
852
+ if (!target_enabled(pid, tid)) {
853
+ return 0;
854
+ }
855
+
856
+ struct event_t ev = {};
857
+ int path_ret;
858
+ ev.pid = pid;
859
+ __builtin_memcpy(ev.event_name, "mmap_exec", 10);
860
+
861
+ path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
862
+ if (path_ret < 0) {
863
+ if (ev.payload[0] == 0) {
864
+ __builtin_memcpy(ev.payload, "<path_error>", 13);
865
+ }
866
+ }
867
+
868
+ submit_event(&ev);
869
+ return 0;
870
+ }
871
+
810
872
  LSM_PROBE(socket_create, int family, int type, int protocol, int kern)
811
873
  {
812
874
  u64 pid_tgid = bpf_get_current_pid_tgid();
@@ -1412,6 +1474,32 @@ module Vivarium
1412
1474
  return 0;
1413
1475
  }
1414
1476
 
1477
+ int on_dlopen(struct pt_regs *ctx)
1478
+ {
1479
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1480
+ u32 pid = pid_tgid >> 32;
1481
+ u32 tid = (u32)pid_tgid;
1482
+ if (!target_enabled(pid, tid)) {
1483
+ return 0;
1484
+ }
1485
+
1486
+ const char *filename = (const char *)PT_REGS_PARM1(ctx);
1487
+ if (!filename) {
1488
+ return 0;
1489
+ }
1490
+
1491
+ struct event_t ev = {};
1492
+ ev.pid = pid;
1493
+ __builtin_memcpy(ev.event_name, "dlopen", 7);
1494
+
1495
+ if (bpf_probe_read_user_str(ev.payload, sizeof(ev.payload), filename) < 0) {
1496
+ __builtin_memcpy(ev.payload, "<path_error>", 13);
1497
+ }
1498
+
1499
+ submit_event(&ev);
1500
+ return 0;
1501
+ }
1502
+
1415
1503
  int on_span_raise(struct pt_regs *ctx)
1416
1504
  {
1417
1505
  u64 pid_tgid = bpf_get_current_pid_tgid();
@@ -1443,10 +1531,13 @@ module Vivarium
1443
1531
  }
1444
1532
  CLANG
1445
1533
 
1446
- def initialize(pin_dir: Vivarium.bpf_pin_dir, ssl_trace: true, libssl_path: nil)
1447
- @pin_dir = pin_dir
1448
- @ssl_trace = ssl_trace
1449
- @libssl_path = libssl_path
1534
+ def initialize(pin_dir: Vivarium.bpf_pin_dir, ssl_trace: true, libssl_path: nil,
1535
+ dlopen_trace: true, libc_path: nil)
1536
+ @pin_dir = pin_dir
1537
+ @ssl_trace = ssl_trace
1538
+ @libssl_path = libssl_path
1539
+ @dlopen_trace = dlopen_trace
1540
+ @libc_path = libc_path
1450
1541
  end
1451
1542
 
1452
1543
  def run
@@ -1459,6 +1550,7 @@ module Vivarium
1459
1550
  .gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
1460
1551
  .gsub("__VIVARIUM_DENTRY_D_NAME_OFFSET__", d_name_offset.to_s)
1461
1552
 
1553
+ require "vivarium_usdt"
1462
1554
  usdt_so_path = ENV.fetch("VIVARIUM_USDT_SO_PATH") { Vivarium.locate_vivarium_usdt_so }
1463
1555
  usdt = RbBCC::USDT.new(path: usdt_so_path)
1464
1556
  usdt.enable_probe(probe: "start_probe", fn_name: "on_span_start")
@@ -1468,6 +1560,7 @@ module Vivarium
1468
1560
  bpf = RbBCC::BCC.new(text: program, usdt_contexts: [usdt])
1469
1561
 
1470
1562
  attach_ssl_write_uprobe(bpf) if @ssl_trace
1563
+ attach_dlopen_uprobe(bpf) if @dlopen_trace
1471
1564
 
1472
1565
  config_root_targets = bpf["config_root_targets"]
1473
1566
  config_spawned_targets = bpf["config_spawned_targets"]
@@ -1526,6 +1619,39 @@ module Vivarium
1526
1619
  LIBSSL_SEARCH_PATHS.find { |p| File.exist?(p) }
1527
1620
  end
1528
1621
 
1622
+ def attach_dlopen_uprobe(bpf)
1623
+ path = resolve_libc_path
1624
+ unless path
1625
+ warn "[vivariumd] libc not found; dlopen uprobe disabled " \
1626
+ "(set --libc PATH or VIVARIUM_LIBC_PATH to override)"
1627
+ return
1628
+ end
1629
+
1630
+ bpf.attach_uprobe(name: path, sym: "dlopen", fn_name: "on_dlopen")
1631
+ puts "[vivariumd] dlopen uprobe attached via #{path}"
1632
+ rescue StandardError => e
1633
+ warn "[vivariumd] dlopen uprobe attach failed: #{e.class}: #{e.message}"
1634
+ end
1635
+
1636
+ def resolve_libc_path
1637
+ if @libc_path
1638
+ return @libc_path if File.exist?(@libc_path)
1639
+
1640
+ warn "[vivariumd] --libc path does not exist: #{@libc_path}"
1641
+ return nil
1642
+ end
1643
+
1644
+ env_path = ENV["VIVARIUM_LIBC_PATH"]
1645
+ if env_path && !env_path.empty?
1646
+ return env_path if File.exist?(env_path)
1647
+
1648
+ warn "[vivariumd] VIVARIUM_LIBC_PATH does not exist: #{env_path}"
1649
+ return nil
1650
+ end
1651
+
1652
+ LIBC_SEARCH_PATHS.find { |p| File.exist?(p) }
1653
+ end
1654
+
1529
1655
  def ensure_root!
1530
1656
  return if Process.uid.zero?
1531
1657
 
@@ -1794,9 +1920,11 @@ module Vivarium
1794
1920
  end
1795
1921
 
1796
1922
  def self.run_daemon!(argv = ARGV)
1797
- options = { pin_dir: bpf_pin_dir, ssl_trace: true, libssl_path: nil }
1923
+ options = { pin_dir: bpf_pin_dir, ssl_trace: true, libssl_path: nil,
1924
+ dlopen_trace: true, libc_path: nil }
1798
1925
  OptionParser.new do |opts|
1799
- opts.banner = "Usage: vivariumd [--pin-dir PATH] [--no-ssl-trace] [--libssl PATH]"
1926
+ opts.banner = "Usage: vivariumd [--pin-dir PATH] [--no-ssl-trace] [--libssl PATH] " \
1927
+ "[--no-dlopen-trace] [--libc PATH]"
1800
1928
  opts.on("--pin-dir PATH", "Pinned map directory") { |v| options[:pin_dir] = v }
1801
1929
  opts.on("--[no-]ssl-trace", "Attach OpenSSL SSL_write uprobe (default: enabled)") do |v|
1802
1930
  options[:ssl_trace] = v
@@ -1804,12 +1932,20 @@ module Vivarium
1804
1932
  opts.on("--libssl PATH", "Path to libssl.so to attach SSL_write to") do |v|
1805
1933
  options[:libssl_path] = v
1806
1934
  end
1935
+ opts.on("--[no-]dlopen-trace", "Attach libc dlopen uprobe (default: enabled)") do |v|
1936
+ options[:dlopen_trace] = v
1937
+ end
1938
+ opts.on("--libc PATH", "Path to libc.so for dlopen uprobe") do |v|
1939
+ options[:libc_path] = v
1940
+ end
1807
1941
  end.parse!(argv)
1808
1942
 
1809
1943
  Daemon.new(
1810
1944
  pin_dir: options[:pin_dir],
1811
1945
  ssl_trace: options[:ssl_trace],
1812
- libssl_path: options[:libssl_path]
1946
+ libssl_path: options[:libssl_path],
1947
+ dlopen_trace: options[:dlopen_trace],
1948
+ libc_path: options[:libc_path]
1813
1949
  ).run
1814
1950
  end
1815
1951
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vivarium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio Kondo
@@ -64,6 +64,8 @@ files:
64
64
  - CONTEXT.md
65
65
  - README.md
66
66
  - Rakefile
67
+ - examples/dlopen_demo.rb
68
+ - examples/drop_demo.rb
67
69
  - examples/execve_demo.rb
68
70
  - examples/file_operation_demo.rb
69
71
  - examples/network_client_demo.rb