add support kfunc/modify_return bpf programs
authorYonghong Song <yhs@fb.com>
Wed, 27 Jan 2021 07:29:19 +0000 (23:29 -0800)
committeryonghong-song <ys114321@gmail.com>
Wed, 27 Jan 2021 21:26:30 +0000 (13:26 -0800)
Add support for tracing program with BPF_MODIFY_RETURN
attachment type. This is used for security oriented bpf
programs which can modify return values for certain
kernel functions:
  - whitelisted for error injection by checking
    within_error_injection_list including all syscalls.
  - lsm security function (prefix "security_").

Also extended load_func() API to allow specify bpf program
is sleepable and this will permits to use some sleepable
helpers like bpf_copy_from_user() and d_path().

examples/cpp/KModRetExample.cc [new file with mode: 0644]
src/cc/api/BPF.cc
src/cc/api/BPF.h
src/cc/bpf_module.cc
src/cc/bpf_module.h
src/cc/export/helpers.h
src/cc/libbpf.c

diff --git a/examples/cpp/KModRetExample.cc b/examples/cpp/KModRetExample.cc
new file mode 100644 (file)
index 0000000..b5c3a90
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) Facebook, Inc.
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ *
+ * Usage:
+ *   $ ./KModRetExample
+ *   opened file: /bin/true
+ *   security_file_open() is called 1 times, expecting 1
+ *
+ * Kfunc modify_ret support is only available at kernel version 5.6 and later.
+ * This example only works for x64. Currently, only the kernel functions can
+ * be attached with BPF_MODIFY_RETURN:
+ *   - Whitelisted for error injection by checking within_error_injection_list.
+ *     Similar discussions happened for the bpf_override_return helper.
+ *   - The LSM security hooks (kernel global function with prefix "security_").
+ */
+
+#include <fstream>
+#include <iostream>
+#include <iomanip>
+#include <string>
+
+#include <error.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "bcc_version.h"
+#include "BPF.h"
+
+const std::string BPF_PROGRAM = R"(
+#include <linux/fs.h>
+#include <asm/errno.h>
+
+BPF_ARRAY(target_pid, u32, 1);
+static bool match_target_pid()
+{
+  int key = 0, *val, tpid, cpid;
+
+  val = target_pid.lookup(&key);
+  if (!val)
+    return false;
+
+  tpid = *val;
+  cpid = bpf_get_current_pid_tgid() >> 32;
+  if (tpid == 0 || tpid != cpid)
+     return false;
+  return true;
+}
+
+struct fname_buf {
+  char buf[16];
+};
+BPF_ARRAY(fname_table, struct fname_buf, 1);
+
+KMOD_RET(__x64_sys_openat, struct pt_regs *regs, int ret)
+{
+  if (!match_target_pid())
+    return 0;
+
+  // openat syscall arguments:
+  //   int dfd, const char __user * filename, int flags, umode_t mode
+  char *filename = (char *)PT_REGS_PARM2_SYSCALL(regs);
+
+  int key = 0;
+  struct fname_buf *val;
+  val = fname_table.lookup(&key);
+  if (!val)
+    return false;
+
+  if (bpf_copy_from_user(val, sizeof(*val), filename) < 0)
+    return 0;
+
+  /* match target_pid, return -EINVAL. */
+  return -EINVAL;
+}
+
+BPF_ARRAY(count, u32, 1);
+KMOD_RET(security_file_open, struct file *file, int ret)
+{
+  if (!match_target_pid())
+    return 0;
+
+  int key = 0, *val;
+  val = count.lookup(&key);
+  if (!val)
+    return 0;
+
+  /* no modification, kernel func continues to execute after this. */
+  lock_xadd(val, 1);
+  return 0;
+}
+)";
+
+struct fname_buf {
+  char buf[16];
+};
+
+static int modify_return(ebpf::BPF &bpf) {
+  int prog_fd;
+  auto res = bpf.load_func("kmod_ret____x64_sys_openat",
+                           BPF_PROG_TYPE_TRACING, prog_fd, BPF_F_SLEEPABLE);
+  if (res.code() != 0) {
+    std::cerr << res.msg() << std::endl;
+    return 1;
+  }
+
+  int attach_fd = bpf_attach_kfunc(prog_fd);
+  if (attach_fd < 0) {
+    std::cerr << "bpf_attach_kfunc failed: " << attach_fd << std::endl;
+    return 1;
+  }
+
+  int ret = open("/bin/true", O_RDONLY);
+  if (ret >= 0 || errno != EINVAL) {
+    close(attach_fd);
+    std::cerr << "incorrect open result" << std::endl;
+    return 1;
+  }
+
+  auto fname_table = bpf.get_array_table<struct fname_buf>("fname_table");
+  uint32_t key = 0;
+  struct fname_buf val;
+  res = fname_table.get_value(key, val);
+  if (res.code() != 0) {
+    close(attach_fd);
+    std::cerr << res.msg() << std::endl;
+    return 1;
+  }
+  std::cout << "opened file: " << val.buf << std::endl;
+
+  // detach the kfunc.
+  close(attach_fd);
+  return 0;
+}
+
+static int not_modify_return(ebpf::BPF &bpf) {
+  int prog_fd;
+  auto res = bpf.load_func("kmod_ret__security_file_open",
+                            BPF_PROG_TYPE_TRACING, prog_fd);
+  if (res.code() != 0) {
+    std::cerr << res.msg() << std::endl;
+    return 1;
+  }
+
+  int attach_fd = bpf_attach_kfunc(prog_fd);
+  if (attach_fd < 0) {
+    std::cerr << "bpf_attach_kfunc failed: " << attach_fd << std::endl;
+    return 1;
+  }
+
+  int ret = open("/bin/true", O_RDONLY);
+  if (ret < 0) {
+    close(attach_fd);
+    std::cerr << "incorrect open result" << std::endl;
+    return 1;
+  }
+
+  auto count_table = bpf.get_array_table<uint32_t>("count");
+  uint32_t key = 0, val = 0;
+  res = count_table.get_value(key, val);
+  if (res.code() != 0) {
+    close(attach_fd);
+    std::cerr << res.msg() << std::endl;
+    return 1;
+  }
+
+  close(attach_fd);
+  std::cout << "security_file_open() is called " << val << " times, expecting 1\n";
+  return 0;
+}
+
+int main() {
+  ebpf::BPF bpf;
+  auto res = bpf.init(BPF_PROGRAM);
+  if (res.code() != 0) {
+    std::cerr << res.msg() << std::endl;
+    return 1;
+  }
+
+  uint32_t key = 0, val = getpid();
+  auto pid_table = bpf.get_array_table<uint32_t>("target_pid");
+  res = pid_table.update_value(key, val);
+  if (res.code() != 0) {
+    std::cerr << res.msg() << std::endl;
+    return 1;
+  }
+
+  if (modify_return(bpf))
+    return 1;
+
+  if (not_modify_return(bpf))
+    return 1;
+
+  return 0;
+}
index 485406b5c1a2b9cbf8eef83093ba36be526fe997..971cb5ac7645bf3c4964fc0ade31131aa96d2de5 100644 (file)
@@ -671,7 +671,7 @@ int BPF::poll_perf_buffer(const std::string& name, int timeout_ms) {
 }
 
 StatusTuple BPF::load_func(const std::string& func_name, bpf_prog_type type,
-                           int& fd) {
+                           int& fd, unsigned flags) {
   if (funcs_.find(func_name) != funcs_.end()) {
     fd = funcs_[func_name];
     return StatusTuple::OK();
@@ -692,7 +692,7 @@ StatusTuple BPF::load_func(const std::string& func_name, bpf_prog_type type,
   fd = bpf_module_->bcc_func_load(type, func_name.c_str(),
                      reinterpret_cast<struct bpf_insn*>(func_start), func_size,
                      bpf_module_->license(), bpf_module_->kern_version(),
-                     log_level, nullptr, 0);
+                     log_level, nullptr, 0, nullptr, flags);
 
   if (fd < 0)
     return StatusTuple(-1, "Failed to load %s: %d", func_name.c_str(), fd);
index 3038a112c5b5bb0be490b202d83004c70360211b..d6f3b2a97447255a5a8599decfabeed8d7d4d84f 100644 (file)
@@ -242,7 +242,7 @@ class BPF {
   int poll_perf_buffer(const std::string& name, int timeout_ms = -1);
 
   StatusTuple load_func(const std::string& func_name, enum bpf_prog_type type,
-                        int& fd);
+                        int& fd, unsigned flags = 0);
   StatusTuple unload_func(const std::string& func_name);
 
   StatusTuple attach_func(int prog_fd, int attachable_fd,
index c194b8152db230785722faa413c9660971b9a493..b0539d664ddb57903561b2db1a691bef9f21adae 100644 (file)
@@ -907,7 +907,7 @@ int BPFModule::bcc_func_load(int prog_type, const char *name,
                 const struct bpf_insn *insns, int prog_len,
                 const char *license, unsigned kern_version,
                 int log_level, char *log_buf, unsigned log_buf_size,
-                const char *dev_name) {
+                const char *dev_name, unsigned flags) {
   struct bpf_load_program_attr attr = {};
   unsigned func_info_cnt, line_info_cnt, finfo_rec_size, linfo_rec_size;
   void *func_info = NULL, *line_info = NULL;
@@ -921,6 +921,7 @@ int BPFModule::bcc_func_load(int prog_type, const char *name,
       attr.prog_type != BPF_PROG_TYPE_EXT) {
     attr.kern_version = kern_version;
   }
+  attr.prog_flags = flags;
   attr.log_level = log_level;
   if (dev_name)
     attr.prog_ifindex = if_nametoindex(dev_name);
index e40ef5ec41a1518deca6491b67394c1eeab5a797..d5729558b67804c22b569478aca796d01a5d5baf 100644 (file)
@@ -144,7 +144,8 @@ class BPFModule {
                     const struct bpf_insn *insns, int prog_len,
                     const char *license, unsigned kern_version,
                     int log_level, char *log_buf, unsigned log_buf_size,
-                    const char *dev_name = nullptr);
+                    const char *dev_name = nullptr,
+                    unsigned flags = 0);
   int bcc_func_attach(int prog_fd, int attachable_fd,
                       int attach_type, unsigned int flags);
   int bcc_func_detach(int prog_fd, int attachable_fd, int attach_type);
index 930a79fc0a7f74675b8ea398c52afa59594da796..0447a956e35690a0279f87e1918371d1cdb98e82 100644 (file)
@@ -1215,6 +1215,9 @@ static int ____##name(unsigned long long *ctx, ##args)
 #define KRETFUNC_PROBE(event, args...) \
         BPF_PROG(kretfunc__ ## event, args)
 
+#define KMOD_RET(event, args...) \
+        BPF_PROG(kmod_ret__ ## event, args)
+
 #define LSM_PROBE(event, args...) \
         BPF_PROG(lsm__ ## event, args)
 
index e5d36d4b3512089c1fa12076e3965220ae18d8b7..7186ad6066c07e4d5451b5167e2481c65ef68d67 100644 (file)
@@ -601,6 +601,9 @@ int bcc_prog_load_xattr(struct bpf_load_program_attr *attr, int prog_len,
     else if (strncmp(attr->name, "kfunc__", 7) == 0) {
       name_offset = 7;
       expected_attach_type = BPF_TRACE_FENTRY;
+    } else if (strncmp(attr->name, "kmod_ret__", 10) == 0) {
+      name_offset = 10;
+      expected_attach_type = BPF_MODIFY_RETURN;
     } else if (strncmp(attr->name, "kretfunc__", 10) == 0) {
       name_offset = 10;
       expected_attach_type = BPF_TRACE_FEXIT;