Class: Vivarium::Daemon

Inherits:
Object
  • Object
show all
Defined in:
lib/vivarium.rb

Constant Summary collapse

BPF_PROGRAM_TEMPLATE =
<<~CLANG
  #include <linux/socket.h>
  #include <uapi/linux/in.h>
  #include <uapi/linux/in6.h>
  #include <uapi/linux/ip.h>
  #include <uapi/linux/udp.h>

  #ifndef SOCK_STREAM
  #define SOCK_STREAM 1
  #endif
  #ifndef SOCK_DGRAM
  #define SOCK_DGRAM 2
  #endif

  struct net;
  struct sock;
  struct sk_buff;

  struct path {
    void *mnt;
    void *dentry;
  };
  struct file {
    char __off[__VIVARIUM_F_PATH_OFFSET__];
    struct path f_path;
  };

  struct sockaddr_t {
    u16 sa_family;
    unsigned char sa_data[14];
  };

  struct sockaddr_in_t {
    u16 sin_family;
    u16 sin_port;
    u32 sin_addr;
    unsigned char pad[8];
  };

  struct sockaddr_in6_t {
    u16 sin6_family;
    u16 sin6_port;
    u32 sin6_flowinfo;
    unsigned char sin6_addr[16];
    u32 sin6_scope_id;
  };

  struct sockaddr_port_t {
    u16 family;
    u16 port;
  };

  struct iovec_t {
    void *iov_base;
    unsigned long iov_len;
  };

  struct user_msghdr_t {
    void *msg_name;
    int msg_namelen;
    struct iovec_t *msg_iov;
    unsigned long msg_iovlen;
    void *msg_control;
    unsigned long msg_controllen;
    unsigned int msg_flags;
  };

  struct mmsghdr_t {
    struct user_msghdr_t msg_hdr;
    unsigned int msg_len;
  };

  struct sk_buff_t {
    unsigned char *head;
    unsigned char *data;
    u32 len;
    u16 mac_header;
    u16 network_header;
    u16 transport_header;
  };

  struct event_t {
    u64 ktime_ns;
    u32 pid;
    char event_name[16];
    char payload[#{EVENT_PAYLOAD_SIZE}];
  };

  BPF_HASH(config_root_targets, u32, u8, 1024);
  BPF_HASH(config_spawned_targets, u32, u8, 8192);
  BPF_HASH(dns_connected_tids, u32, u8, 8192);
  BPF_ARRAY(event_invoked, struct event_t, #{EVENT_CAPACITY});
  BPF_ARRAY(event_write_pos, u32, 1);

  static __always_inline int target_enabled(u32 pid, u32 tid)
  {
    u8 *enabled_root = config_root_targets.lookup(&pid);
    if (enabled_root && *enabled_root == 1) {
      return 1;
    }

    u8 *enabled_spawned = config_spawned_targets.lookup(&tid);
    if (enabled_spawned && *enabled_spawned == 1) {
      return 1;
    }

    return 0;
  }

  static __always_inline void submit_event(struct event_t *ev)
  {
    u32 zero = 0;
    u32 *write_pos = event_write_pos.lookup(&zero);
    if (!write_pos) {
      return;
    }

    ev->ktime_ns = bpf_ktime_get_ns();

    u32 idx = *write_pos % #{EVENT_CAPACITY};
    __sync_fetch_and_add(write_pos, 1);
    event_invoked.update(&idx, ev);
  }

  static __always_inline int is_dns_destination(void *addr)
  {
    u16 family = 0;
    bpf_probe_read_user(&family, sizeof(family), addr);

    if (family == AF_INET) {
      struct sockaddr_in_t sin = {};
      bpf_probe_read_user(&sin, sizeof(sin), addr);
      return sin.sin_port == __constant_htons(53);
    }

    if (family == AF_INET6) {
      struct sockaddr_in6_t sin6 = {};
      bpf_probe_read_user(&sin6, sizeof(sin6), addr);
      return sin6.sin6_port == __constant_htons(53);
    }

    return 0;
  }

  static __always_inline void submit_dns_req(u32 pid, unsigned char *payload, unsigned int payload_len)
  {
    unsigned int copy_len = payload_len;

    if (copy_len <= 12) {
      return;
    }

    copy_len -= 12;
    if (copy_len > 64) {
      copy_len = 64;
    }

    struct event_t ev = {};
    ev.pid = pid;
    __builtin_memcpy(ev.event_name, "dns_req", 8);
    bpf_probe_read_user(&ev.payload[0], copy_len, payload + 12);
    submit_event(&ev);
  }

  TRACEPOINT_PROBE(sched, sched_process_fork)
  {
    u32 parent = args->parent_pid;
    u32 child = args->child_pid;
    u8 one = 1;

    u8 *enabled_root = config_root_targets.lookup(&parent);
    if (enabled_root && *enabled_root == 1) {
      config_spawned_targets.update(&child, &one);
      return 0;
    }

    u8 *enabled_spawned = config_spawned_targets.lookup(&parent);
    if (enabled_spawned && *enabled_spawned == 1) {
      config_spawned_targets.update(&child, &one);
    }

    return 0;
  }

  TRACEPOINT_PROBE(sched, sched_process_exit)
  {
    u32 tid = (u32)bpf_get_current_pid_tgid();
    config_spawned_targets.delete(&tid);
    dns_connected_tids.delete(&tid);
    return 0;
  }

  LSM_PROBE(file_open, struct file *file)
  {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u32 tid = (u32)pid_tgid;
    bpf_trace_printk("vivarium: invoked pid=%d\\n", pid);
    if (!target_enabled(pid, tid)) {
      return 0;
    }

    struct event_t ev = {};
    int path_ret;
    ev.pid = pid;
    __builtin_memcpy(ev.event_name, "path_open", 9);

    path_ret = bpf_d_path(&file->f_path, ev.payload, sizeof(ev.payload));
    if (path_ret < 0) {
      if (ev.payload[0] == 0) {
        __builtin_memcpy(ev.payload, "<path_error>", 13);
        bpf_trace_printk("vivarium: failed to obtain full path. pid=%d path=%s\\n", pid, ev.payload);
      }
    }

    bpf_trace_printk("vivarium: pid=%d path=%s\\n", pid, ev.payload);
    submit_event(&ev);

    return 0;
  }

  LSM_PROBE(socket_create, int family, int type, int protocol, int kern)
  {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u32 tid = (u32)pid_tgid;

    if (!target_enabled(pid, tid)) {
      return 0;
    }

    if ((family == AF_INET || family == AF_INET6) && (type == SOCK_STREAM || type == SOCK_DGRAM)) {
      return 0;
    }

    struct event_t ev = {};
    u16 family16 = family;
    u16 type16 = type;
    u16 proto16 = protocol;

    ev.pid = pid;
    __builtin_memcpy(ev.event_name, "odd_socket", 11);
    __builtin_memcpy(&ev.payload[0], &family16, sizeof(family16));
    __builtin_memcpy(&ev.payload[2], &type16, sizeof(type16));
    __builtin_memcpy(&ev.payload[4], &proto16, sizeof(proto16));
    submit_event(&ev);

    return 0;
  }

  LSM_PROBE(socket_connect, struct socket *sock, struct sockaddr *address, int addrlen)
  {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u32 tid = (u32)pid_tgid;
    u16 family = 0;
    u8 one = 1;

    if (!target_enabled(pid, tid)) {
      return 0;
    }

    if (!address) {
      return 0;
    }

    bpf_probe_read_kernel(&family, sizeof(family), address);

    struct event_t ev = {};
    ev.pid = pid;
    __builtin_memcpy(ev.event_name, "sock_connect", 13);
    __builtin_memcpy(&ev.payload[0], &family, sizeof(family));

    if (family == AF_INET) {
      struct sockaddr_in_t sin = {};
      bpf_probe_read_kernel(&sin, sizeof(sin), address);
      __builtin_memcpy(&ev.payload[2], &sin.sin_port, sizeof(sin.sin_port));
      __builtin_memcpy(&ev.payload[4], &sin.sin_addr, sizeof(sin.sin_addr));
      if (sin.sin_port == __constant_htons(53)) {
        dns_connected_tids.update(&tid, &one);
      }
    } else if (family == AF_INET6) {
      struct sockaddr_in6_t sin6 = {};
      bpf_probe_read_kernel(&sin6, sizeof(sin6), address);
      __builtin_memcpy(&ev.payload[2], &sin6.sin6_port, sizeof(sin6.sin6_port));
      __builtin_memcpy(&ev.payload[4], &sin6.sin6_addr, sizeof(sin6.sin6_addr));
      if (sin6.sin6_port == __constant_htons(53)) {
        dns_connected_tids.update(&tid, &one);
      }
    }

    submit_event(&ev);

    return 0;
  }

  TRACEPOINT_PROBE(syscalls, sys_enter_sendmsg)
  {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u32 tid = (u32)pid_tgid;
    struct user_msghdr_t msg = {};
    struct iovec_t iov = {};

    if (!target_enabled(pid, tid)) {
      return 0;
    }

    if (!args->msg) {
      return 0;
    }

    bpf_probe_read_user(&msg, sizeof(msg), args->msg);
    if (!msg.msg_iov || msg.msg_iovlen == 0) {
      return 0;
    }

    if (msg.msg_name && !is_dns_destination(msg.msg_name)) {
      return 0;
    }

    bpf_probe_read_user(&iov, sizeof(iov), msg.msg_iov);
    if (!iov.iov_base) {
      return 0;
    }

    submit_dns_req(pid, (unsigned char *)iov.iov_base, (unsigned int)iov.iov_len);

    return 0;
  }

  TRACEPOINT_PROBE(syscalls, sys_enter_sendto)
  {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u32 tid = (u32)pid_tgid;
    unsigned char *buff = args->buff;
    int dns_match = 0;

    if (!target_enabled(pid, tid)) {
      return 0;
    }

    if (!buff) {
      return 0;
    }

    if (args->addr) {
      dns_match = is_dns_destination(args->addr);
    } else {
      u8 *connected = dns_connected_tids.lookup(&tid);
      dns_match = connected && *connected == 1;
    }

    if (!dns_match) {
      return 0;
    }

    submit_dns_req(pid, buff, args->len);
    dns_connected_tids.delete(&tid);

    return 0;
  }

  TRACEPOINT_PROBE(syscalls, sys_enter_sendmmsg)
  {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 pid = pid_tgid >> 32;
    u32 tid = (u32)pid_tgid;
    struct mmsghdr_t mmsg = {};
    struct iovec_t iov = {};

    if (!target_enabled(pid, tid)) {
      return 0;
    }

    if (!args->mmsg) {
      return 0;
    }

    bpf_probe_read_user(&mmsg, sizeof(mmsg), args->mmsg);
    if (mmsg.msg_hdr.msg_name && !is_dns_destination(mmsg.msg_hdr.msg_name)) {
      return 0;
    }

    if (!mmsg.msg_hdr.msg_iov || mmsg.msg_hdr.msg_iovlen == 0) {
      return 0;
    }

    bpf_probe_read_user(&iov, sizeof(iov), mmsg.msg_hdr.msg_iov);
    if (!iov.iov_base) {
      return 0;
    }

    submit_dns_req(pid, (unsigned char *)iov.iov_base, (unsigned int)iov.iov_len);

    return 0;
  }
CLANG

Instance Method Summary collapse

Constructor Details

#initialize(pin_dir: Vivarium.bpf_pin_dir) ⇒ Daemon

Returns a new instance of Daemon.



635
636
637
# File 'lib/vivarium.rb', line 635

def initialize(pin_dir: Vivarium.bpf_pin_dir)
  @pin_dir = pin_dir
end

Instance Method Details

#runObject



639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/vivarium.rb', line 639

def run
  ensure_root!
  FileUtils.mkdir_p(@pin_dir)

  f_path_offset = detect_f_path_offset
  program = BPF_PROGRAM_TEMPLATE.gsub("__VIVARIUM_F_PATH_OFFSET__", f_path_offset.to_s)

  bpf = RbBCC::BCC.new(text: program)
  kprint_thread = start_kprint_logger(bpf)

  config_root_targets = bpf["config_root_targets"]
  config_spawned_targets = bpf["config_spawned_targets"]
  event_invoked = bpf["event_invoked"]
  event_write_pos = bpf["event_write_pos"]

  clear_event_slots(event_invoked)
  event_write_pos[0] = 0
  config_spawned_targets.clear

  pin_map(config_root_targets, File.join(@pin_dir, "config_root_targets"))
  pin_map(config_spawned_targets, File.join(@pin_dir, "config_spawned_targets"))
  pin_map(event_invoked, File.join(@pin_dir, "event_invoked"))
  pin_map(event_write_pos, File.join(@pin_dir, "event_write_pos"))

  puts "[vivariumd] started"
  puts "[vivariumd] pinned maps in #{@pin_dir}"
  puts "[vivariumd] watching LSM file_open (f_path offset=#{f_path_offset})"
  puts "[vivariumd] kprint logger enabled"

  loop do
    sleep 1
  end
rescue Interrupt
  puts "\n[vivariumd] stopping"
ensure
  if kprint_thread
    kprint_thread.kill
    kprint_thread.join(0.2)
  end
end