vivarium 0.3.0 → 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.
data/lib/vivarium.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "fiddle"
4
4
  require "fileutils"
5
+ require "net/http"
5
6
  require "optparse"
6
7
  require "pathname"
7
8
  require "rbbcc"
@@ -23,13 +24,45 @@ module Vivarium
23
24
  EVENT_TS_SIZE = 8
24
25
  PROC_EXEC_SLOT_SIZE = 64
25
26
  PROC_EXEC_SLOT_COUNT = 4
26
- EVENT_STRUCT_SIZE = 288
27
+ EVENT_STRUCT_SIZE = 296
27
28
  EVENT_TS_OFFSET = 0
28
29
  EVENT_PID_OFFSET = 8
29
30
  EVENT_TID_OFFSET = 12
30
31
  EVENT_NAME_OFFSET = 16
31
32
  EVENT_PAYLOAD_OFFSET = 32
33
+ EVENT_DROPPED_OFFSET = 288
32
34
  EVENTS_RINGBUF_PAGES = 256
35
+
36
+ SSL_WRITE_PAYLOAD_DATA_LEN_OFFSET = 0
37
+ SSL_WRITE_PAYLOAD_CAP_LEN_OFFSET = 4
38
+ SSL_WRITE_PAYLOAD_DATA_OFFSET = 8
39
+ SSL_WRITE_PAYLOAD_DATA_MAX = EVENT_PAYLOAD_SIZE - SSL_WRITE_PAYLOAD_DATA_OFFSET
40
+
41
+ LIBSSL_SEARCH_PATHS = [
42
+ "/lib/x86_64-linux-gnu/libssl.so.3",
43
+ "/lib/x86_64-linux-gnu/libssl.so.1.1",
44
+ "/lib/aarch64-linux-gnu/libssl.so.3",
45
+ "/lib/aarch64-linux-gnu/libssl.so.1.1",
46
+ "/usr/lib/x86_64-linux-gnu/libssl.so.3",
47
+ "/usr/lib/x86_64-linux-gnu/libssl.so.1.1",
48
+ "/usr/lib/aarch64-linux-gnu/libssl.so.3",
49
+ "/usr/lib/aarch64-linux-gnu/libssl.so.1.1",
50
+ "/usr/lib64/libssl.so.3",
51
+ "/usr/lib64/libssl.so.1.1",
52
+ "/usr/lib/libssl.so.3",
53
+ "/usr/lib/libssl.so.1.1"
54
+ ].freeze
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
+
33
66
  SPAN_ALLOWCLASSES = [
34
67
  Socket,
35
68
  BasicSocket,
@@ -43,6 +76,7 @@ module Vivarium
43
76
  Process,
44
77
  Process::UID,
45
78
  Process::GID,
79
+ Net::HTTP,
46
80
  ]
47
81
  SPAN_ALLOWLIST = [
48
82
  "Kernel#system",
@@ -56,6 +90,7 @@ module Vivarium
56
90
  EVENT_SEVERITY_HIGH = %w[
57
91
  capable_check bprm_creds setid_change task_kill
58
92
  ptrace_check sb_mount kernel_read_file
93
+ dlopen
59
94
  ].freeze
60
95
 
61
96
  CAPABILITY_NAMES = {
@@ -342,6 +377,17 @@ module Vivarium
342
377
  result
343
378
  end
344
379
 
380
+ def self.decode_ssl_write_payload(raw_payload)
381
+ bytes = raw_payload.to_s.b
382
+ return { data_len: 0, cap_len: 0, data: "".b } if bytes.bytesize < SSL_WRITE_PAYLOAD_DATA_OFFSET
383
+
384
+ data_len = bytes[SSL_WRITE_PAYLOAD_DATA_LEN_OFFSET, 4].unpack1("L<")
385
+ cap_len = bytes[SSL_WRITE_PAYLOAD_CAP_LEN_OFFSET, 4].unpack1("L<")
386
+ cap_len = SSL_WRITE_PAYLOAD_DATA_MAX if cap_len > SSL_WRITE_PAYLOAD_DATA_MAX
387
+ data = bytes[SSL_WRITE_PAYLOAD_DATA_OFFSET, cap_len] || "".b
388
+ { data_len: data_len, cap_len: cap_len, data: data }
389
+ end
390
+
345
391
  def self.decode_span_raise_payload(raw_payload)
346
392
  bytes = raw_payload.to_s.b
347
393
  return "" if bytes.bytesize < 8
@@ -426,6 +472,11 @@ module Vivarium
426
472
  when "file_getdents"
427
473
  decoded = decode_file_getdents_payload(event.payload)
428
474
  decoded.empty? ? event.payload.inspect : decoded
475
+ when "ssl_write"
476
+ decoded = decode_ssl_write_payload(event.payload)
477
+ "data_len=#{decoded[:data_len]} cap_len=#{decoded[:cap_len]}"
478
+ when "dlopen", "mmap_exec"
479
+ strip_to_first_null(event.payload).inspect
429
480
  else
430
481
  strip_to_first_null(event.payload).inspect
431
482
  end
@@ -582,12 +633,14 @@ module Vivarium
582
633
  u32 tid;
583
634
  char event_name[16];
584
635
  char payload[#{EVENT_PAYLOAD_SIZE}];
636
+ u64 dropped_since_last;
585
637
  };
586
638
 
587
639
  BPF_HASH(config_root_targets, u32, u8, 1024);
588
640
  BPF_HASH(config_spawned_targets, u32, u8, 8192);
589
641
  BPF_HASH(dns_connected_tids, u32, u8, 8192);
590
642
  BPF_RINGBUF_OUTPUT(events, #{EVENTS_RINGBUF_PAGES});
643
+ BPF_ARRAY(drop_counter, u64, 1);
591
644
 
592
645
  static __always_inline int target_enabled(u32 pid, u32 tid)
593
646
  {
@@ -629,14 +682,27 @@ module Vivarium
629
682
 
630
683
  static __always_inline void submit_event(struct event_t *src)
631
684
  {
685
+ u32 key = 0;
686
+ u64 *cnt;
687
+
632
688
  struct event_t *ev = events.ringbuf_reserve(sizeof(struct event_t));
633
689
  if (!ev) {
690
+ cnt = drop_counter.lookup(&key);
691
+ if (cnt) {
692
+ __sync_fetch_and_add(cnt, 1);
693
+ }
634
694
  return;
635
695
  }
636
696
 
637
697
  __builtin_memcpy(ev, src, sizeof(*ev));
638
698
  ev->ktime_ns = bpf_ktime_get_ns();
639
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
+ }
640
706
 
641
707
  events.ringbuf_submit(ev, 0);
642
708
  }
@@ -770,6 +836,39 @@ module Vivarium
770
836
  return 0;
771
837
  }
772
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
+
773
872
  LSM_PROBE(socket_create, int family, int type, int protocol, int kern)
774
873
  {
775
874
  u64 pid_tgid = bpf_get_current_pid_tgid();
@@ -1339,6 +1438,68 @@ module Vivarium
1339
1438
  return 0;
1340
1439
  }
1341
1440
 
1441
+ int on_ssl_write(struct pt_regs *ctx)
1442
+ {
1443
+ u64 pid_tgid = bpf_get_current_pid_tgid();
1444
+ u32 pid = pid_tgid >> 32;
1445
+ u32 tid = (u32)pid_tgid;
1446
+
1447
+ if (!target_enabled(pid, tid)) {
1448
+ return 0;
1449
+ }
1450
+
1451
+ const char *buf = (const char *)PT_REGS_PARM2(ctx);
1452
+ int num = (int)PT_REGS_PARM3(ctx);
1453
+ if (!buf || num <= 0) {
1454
+ return 0;
1455
+ }
1456
+
1457
+ struct event_t ev = {};
1458
+ ev.pid = pid;
1459
+ __builtin_memcpy(ev.event_name, "ssl_write", 10);
1460
+
1461
+ u32 data_len = (u32)num;
1462
+ u32 cap = data_len;
1463
+ if (cap > #{SSL_WRITE_PAYLOAD_DATA_MAX}) {
1464
+ cap = #{SSL_WRITE_PAYLOAD_DATA_MAX};
1465
+ }
1466
+ __builtin_memcpy(&ev.payload[#{SSL_WRITE_PAYLOAD_DATA_LEN_OFFSET}], &data_len, sizeof(data_len));
1467
+ __builtin_memcpy(&ev.payload[#{SSL_WRITE_PAYLOAD_CAP_LEN_OFFSET}], &cap, sizeof(cap));
1468
+ if (bpf_probe_read_user(&ev.payload[#{SSL_WRITE_PAYLOAD_DATA_OFFSET}], cap, buf) < 0) {
1469
+ u32 zero = 0;
1470
+ __builtin_memcpy(&ev.payload[#{SSL_WRITE_PAYLOAD_CAP_LEN_OFFSET}], &zero, sizeof(zero));
1471
+ }
1472
+
1473
+ submit_event(&ev);
1474
+ return 0;
1475
+ }
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
+
1342
1503
  int on_span_raise(struct pt_regs *ctx)
1343
1504
  {
1344
1505
  u64 pid_tgid = bpf_get_current_pid_tgid();
@@ -1370,8 +1531,13 @@ module Vivarium
1370
1531
  }
1371
1532
  CLANG
1372
1533
 
1373
- def initialize(pin_dir: Vivarium.bpf_pin_dir)
1374
- @pin_dir = pin_dir
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
1375
1541
  end
1376
1542
 
1377
1543
  def run
@@ -1384,6 +1550,7 @@ module Vivarium
1384
1550
  .gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)
1385
1551
  .gsub("__VIVARIUM_DENTRY_D_NAME_OFFSET__", d_name_offset.to_s)
1386
1552
 
1553
+ require "vivarium_usdt"
1387
1554
  usdt_so_path = ENV.fetch("VIVARIUM_USDT_SO_PATH") { Vivarium.locate_vivarium_usdt_so }
1388
1555
  usdt = RbBCC::USDT.new(path: usdt_so_path)
1389
1556
  usdt.enable_probe(probe: "start_probe", fn_name: "on_span_start")
@@ -1392,6 +1559,9 @@ module Vivarium
1392
1559
 
1393
1560
  bpf = RbBCC::BCC.new(text: program, usdt_contexts: [usdt])
1394
1561
 
1562
+ attach_ssl_write_uprobe(bpf) if @ssl_trace
1563
+ attach_dlopen_uprobe(bpf) if @dlopen_trace
1564
+
1395
1565
  config_root_targets = bpf["config_root_targets"]
1396
1566
  config_spawned_targets = bpf["config_spawned_targets"]
1397
1567
  events_ringbuf = bpf["events"]
@@ -1416,6 +1586,72 @@ module Vivarium
1416
1586
 
1417
1587
  private
1418
1588
 
1589
+ def attach_ssl_write_uprobe(bpf)
1590
+ path = resolve_libssl_path
1591
+ unless path
1592
+ warn "[vivariumd] libssl not found; SSL_write uprobe disabled " \
1593
+ "(set --libssl PATH or VIVARIUM_LIBSSL_PATH to override)"
1594
+ return
1595
+ end
1596
+
1597
+ bpf.attach_uprobe(name: path, sym: "SSL_write", fn_name: "on_ssl_write")
1598
+ puts "[vivariumd] SSL_write uprobe attached via #{path}"
1599
+ rescue StandardError => e
1600
+ warn "[vivariumd] SSL_write uprobe attach failed: #{e.class}: #{e.message}"
1601
+ end
1602
+
1603
+ def resolve_libssl_path
1604
+ if @libssl_path
1605
+ return @libssl_path if File.exist?(@libssl_path)
1606
+
1607
+ warn "[vivariumd] --libssl path does not exist: #{@libssl_path}"
1608
+ return nil
1609
+ end
1610
+
1611
+ env_path = ENV["VIVARIUM_LIBSSL_PATH"]
1612
+ if env_path && !env_path.empty?
1613
+ return env_path if File.exist?(env_path)
1614
+
1615
+ warn "[vivariumd] VIVARIUM_LIBSSL_PATH does not exist: #{env_path}"
1616
+ return nil
1617
+ end
1618
+
1619
+ LIBSSL_SEARCH_PATHS.find { |p| File.exist?(p) }
1620
+ end
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
+
1419
1655
  def ensure_root!
1420
1656
  return if Process.uid.zero?
1421
1657
 
@@ -1557,13 +1793,13 @@ module Vivarium
1557
1793
  end
1558
1794
  end
1559
1795
 
1560
- def self.observe(pin_dir: bpf_pin_dir, dest: $stdout, &block)
1561
- return scoped_observe(pin_dir: pin_dir, dest: dest, &block) if block_given?
1796
+ def self.observe(pin_dir: bpf_pin_dir, dest: $stdout, filter: nil, &block)
1797
+ return scoped_observe(pin_dir: pin_dir, dest: dest, filter: filter, &block) if block_given?
1562
1798
 
1563
- top_observe(pin_dir: pin_dir, dest: dest)
1799
+ top_observe(pin_dir: pin_dir, dest: dest, filter: filter)
1564
1800
  end
1565
1801
 
1566
- def self.top_observe(pin_dir: bpf_pin_dir, dest: $stdout)
1802
+ def self.top_observe(pin_dir: bpf_pin_dir, dest: $stdout, filter: nil)
1567
1803
  require "vivarium_usdt"
1568
1804
 
1569
1805
  store = MapStore.new(pin_dir: pin_dir)
@@ -1578,6 +1814,7 @@ module Vivarium
1578
1814
  observer_pid: pid,
1579
1815
  main_tid: main_tid,
1580
1816
  method_id_queue: method_id_queue,
1817
+ filter: filter,
1581
1818
  dest: dest
1582
1819
  )
1583
1820
  correlator.start
@@ -1592,7 +1829,7 @@ module Vivarium
1592
1829
  session
1593
1830
  end
1594
1831
 
1595
- def self.scoped_observe(pin_dir:, dest:)
1832
+ def self.scoped_observe(pin_dir:, dest:, filter: nil)
1596
1833
  require "vivarium_usdt"
1597
1834
 
1598
1835
  store = MapStore.new(pin_dir: pin_dir)
@@ -1607,6 +1844,7 @@ module Vivarium
1607
1844
  observer_pid: pid,
1608
1845
  main_tid: main_tid,
1609
1846
  method_id_queue: method_id_queue,
1847
+ filter: filter,
1610
1848
  dest: dest
1611
1849
  )
1612
1850
  correlator.start
@@ -1626,6 +1864,11 @@ module Vivarium
1626
1864
  allowlist = SPAN_ALLOWLIST
1627
1865
  TracePoint.new(:call, :c_call, :return, :c_return, :raise) do |tp|
1628
1866
  if tp.event == :raise
1867
+ # FIXME: handle threaded events in the future
1868
+ if tp.raised_exception.kind_of?(ThreadError)
1869
+ next
1870
+ end
1871
+
1629
1872
  Vivarium::Usdt.raise(
1630
1873
  tp.raised_exception.class.to_s,
1631
1874
  tp.raised_exception.message.to_s,
@@ -1677,15 +1920,36 @@ module Vivarium
1677
1920
  end
1678
1921
 
1679
1922
  def self.run_daemon!(argv = ARGV)
1680
- options = { pin_dir: bpf_pin_dir }
1923
+ options = { pin_dir: bpf_pin_dir, ssl_trace: true, libssl_path: nil,
1924
+ dlopen_trace: true, libc_path: nil }
1681
1925
  OptionParser.new do |opts|
1682
- opts.banner = "Usage: vivariumd [--pin-dir PATH]"
1926
+ opts.banner = "Usage: vivariumd [--pin-dir PATH] [--no-ssl-trace] [--libssl PATH] " \
1927
+ "[--no-dlopen-trace] [--libc PATH]"
1683
1928
  opts.on("--pin-dir PATH", "Pinned map directory") { |v| options[:pin_dir] = v }
1929
+ opts.on("--[no-]ssl-trace", "Attach OpenSSL SSL_write uprobe (default: enabled)") do |v|
1930
+ options[:ssl_trace] = v
1931
+ end
1932
+ opts.on("--libssl PATH", "Path to libssl.so to attach SSL_write to") do |v|
1933
+ options[:libssl_path] = v
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
1684
1941
  end.parse!(argv)
1685
1942
 
1686
- Daemon.new(pin_dir: options[:pin_dir]).run
1943
+ Daemon.new(
1944
+ pin_dir: options[:pin_dir],
1945
+ ssl_trace: options[:ssl_trace],
1946
+ libssl_path: options[:libssl_path],
1947
+ dlopen_trace: options[:dlopen_trace],
1948
+ libc_path: options[:libc_path]
1949
+ ).run
1687
1950
  end
1688
1951
  end
1689
1952
 
1690
1953
  require_relative "vivarium/correlator"
1954
+ require_relative "vivarium/display_filter"
1691
1955
  require_relative "vivarium/tree_renderer"
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.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio Kondo
@@ -64,12 +64,15 @@ 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
70
72
  - examples/privilege_event_demo.rb
71
73
  - examples/raise_demo.rb
72
74
  - examples/signal_kill_demo.rb
75
+ - examples/ssl_write_demo.rb
73
76
  - examples/sudo_attempt_demo.rb
74
77
  - exe/vivarium
75
78
  - exe/vivariumd
@@ -77,6 +80,8 @@ files:
77
80
  - lib/vivarium.rb
78
81
  - lib/vivarium/cli.rb
79
82
  - lib/vivarium/correlator.rb
83
+ - lib/vivarium/display_filter.rb
84
+ - lib/vivarium/http_decoder.rb
80
85
  - lib/vivarium/tree_renderer.rb
81
86
  - lib/vivarium/version.rb
82
87
  - logo-simple.png