Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions fact-ebpf/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,46 @@ use std::{
};

fn compile_bpf(out_dir: &Path) -> anyhow::Result<()> {
let obj = match out_dir.join("main.o").into_os_string().into_string() {
Ok(s) => s,
Err(os_string) => anyhow::bail!("Failed to convert path to string {:?}", os_string),
let target_arch = match env::var("CARGO_CFG_TARGET_ARCH")?.as_str() {
"x86_64" => "x86",
"aarch64" => "arm64",
arch => unimplemented!("Unsupported arch {arch}"),
};
let target_arch = format!("-D__TARGET_ARCH_{target_arch}");
let base_args = [
"-target",
"bpf",
"-O2",
"-g",
"-c",
"-Wall",
"-Werror",
&target_arch,
];

let target_arch = format!("-D__TARGET_ARCH_{}", env::var("CARGO_CFG_TARGET_ARCH")?);
for name in ["main", "checks"] {
let obj = match out_dir
.join(format!("{name}.o"))
.into_os_string()
.into_string()
{
Ok(s) => s,
Err(os_string) => anyhow::bail!("Failed to convert path to string {:?}", os_string),
};

match Command::new("clang")
.args([
"-target",
"bpf",
"-O2",
"-g",
"-c",
"-Wall",
"-Werror",
&target_arch,
"src/bpf/main.c",
"-o",
&obj,
])
.status()
{
Ok(status) => {
if !status.success() {
anyhow::bail!("Failed to compile eBPF. See stderr for details.");
match Command::new("clang")
.args(base_args)
.arg(format!("src/bpf/{name}.c"))
.args(["-o", &obj])
.status()
{
Ok(status) => {
if !status.success() {
anyhow::bail!("Failed to compile eBPF. See stderr for details.");
}
}
Err(e) => anyhow::bail!("Failed to execute clang: {}", e),
}
Err(e) => anyhow::bail!("Failed to execute clang: {}", e),
}
Ok(())
}
Expand Down
8 changes: 6 additions & 2 deletions fact-ebpf/src/bpf/bound_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// clang-format off
#include "types.h"
#include "maps.h"
#include "d_path.h"

#include "vmlinux.h"

Expand All @@ -21,10 +22,13 @@ __always_inline static void path_write_char(char* p, unsigned int offset, char c
*path_safe_access(p, offset) = c;
}

__always_inline static struct bound_path_t* path_read(struct path* path) {
__always_inline static struct bound_path_t* path_read(struct path* path, bool use_bpf_d_path) {
struct bound_path_t* bound_path = get_bound_path();
if (bound_path == NULL) {
return NULL;
}

bound_path->len = bpf_d_path(path, bound_path->path, PATH_MAX);
bound_path->len = use_bpf_d_path ? bpf_d_path(path, bound_path->path, PATH_MAX) : d_path(path, bound_path->path, PATH_MAX);
if (bound_path->len <= 0) {
return NULL;
}
Expand Down
20 changes: 20 additions & 0 deletions fact-ebpf/src/bpf/checks.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// clang-format off
#include "vmlinux.h"

#include "bound_path.h"

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
// clang-format on

SEC("lsm/file_open")
int BPF_PROG(check_lsm_support, struct file* file) {
return 0;
}

SEC("lsm/path_unlink")
int BPF_PROG(check_path_unlink_supports_bpf_d_path, struct path* dir, struct dentry* dentry) {
struct bound_path_t* p = path_read(dir, true);
bpf_printk("dir: %s", p->path);
return 0;
}
81 changes: 81 additions & 0 deletions fact-ebpf/src/bpf/d_path.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#pragma once

// clang-format off
#include "maps.h"
#include "vmlinux.h"

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
// clang-format on

/**
* Reimplementation of the kernel d_path function.
*
* We should attempt to use bpf_d_path when possible, but you can't on
* values that have been read using the bpf_probe_* helpers.
*/
__always_inline static long d_path(const struct path* path, char* buf, int buflen) {
if (buflen <= 0) {
return -1;
}

struct helper_t* helper = get_helper();
if (helper == NULL) {
return -1;
}

struct task_struct* task = (struct task_struct*)bpf_get_current_task();
int offset = (buflen - 1) & (PATH_MAX - 1);
helper->buf[offset] = '\0'; // Ensure null termination

struct path root;
BPF_CORE_READ_INTO(&root, task, fs, root);
struct mount* mnt = container_of(path->mnt, struct mount, mnt);
struct dentry* dentry = BPF_CORE_READ(path, dentry);

for (int i = 0; i < 16 && (dentry != root.dentry || &mnt->mnt != root.mnt); i++) {
struct dentry* parent = BPF_CORE_READ(dentry, d_parent);
struct dentry* mnt_root = BPF_CORE_READ(mnt, mnt.mnt_root);

if (dentry == mnt_root) {
struct mount* m = BPF_CORE_READ(mnt, mnt_parent);
if (m != mnt) {
dentry = BPF_CORE_READ(mnt, mnt_mountpoint);
mnt = m;
continue;
}
break;
}

if (dentry == parent) {
return -1;
}

struct qstr d_name;
BPF_CORE_READ_INTO(&d_name, dentry, d_name);
int len = d_name.len;
if (len <= 0 || len >= buflen) {
return -1;
}

offset -= len;
if (offset <= 0) {
return -1;
}

if (bpf_probe_read_kernel(&helper->buf[offset], len, d_name.name) != 0) {
return -1;
}

offset--;
if (offset <= 0) {
return -1;
}
helper->buf[offset] = '/';

dentry = parent;
}

bpf_probe_read_str(buf, buflen, &helper->buf[offset]);
return buflen - offset;
}
6 changes: 6 additions & 0 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma once

#include <bpf/bpf_helpers.h>

#include "maps.h"
Expand All @@ -17,6 +19,10 @@ __always_inline static void submit_event(struct metrics_by_hook_t* m, file_activ
bpf_probe_read_str(event->filename, PATH_MAX, filename);

struct helper_t* helper = get_helper();
if (helper == NULL) {
goto error;
}

const char* p = get_host_path(helper->buf, dentry);
if (p != NULL) {
bpf_probe_read_str(event->host_file, PATH_MAX, p);
Expand Down
67 changes: 1 addition & 66 deletions fact-ebpf/src/bpf/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,79 +5,14 @@

#include "bound_path.h"
#include "builtins.h"
#include "d_path.h"
#include "types.h"
#include "maps.h"

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
// clang-format on

/**
* Reimplementation of the kernel d_path function.
*
* We should attempt to use bpf_d_path when possible, but you can't on
* values that have been read using the bpf_probe_* helpers.
*/
__always_inline static char* d_path(const struct path* path, char* buf, int buflen) {
if (buflen <= 0) {
return NULL;
}

struct task_struct* task = (struct task_struct*)bpf_get_current_task();
int offset = (buflen - 1) & (PATH_MAX - 1);
buf[offset] = '\0'; // Ensure null termination

struct path root;
BPF_CORE_READ_INTO(&root, task, fs, root);
struct mount* mnt = container_of(path->mnt, struct mount, mnt);
struct dentry* dentry = BPF_CORE_READ(path, dentry);

for (int i = 0; i < 16 && (dentry != root.dentry || &mnt->mnt != root.mnt); i++) {
struct dentry* parent = BPF_CORE_READ(dentry, d_parent);
struct dentry* mnt_root = BPF_CORE_READ(mnt, mnt.mnt_root);

if (dentry == mnt_root) {
struct mount* m = BPF_CORE_READ(mnt, mnt_parent);
if (m != mnt) {
dentry = BPF_CORE_READ(mnt, mnt_mountpoint);
mnt = m;
continue;
}
return &buf[offset];
}

if (dentry == parent) {
return NULL;
}

struct qstr d_name;
BPF_CORE_READ_INTO(&d_name, dentry, d_name);
int len = d_name.len;
if (len <= 0 || len >= buflen) {
return NULL;
}

offset -= len;
if (offset <= 0) {
return NULL;
}

if (bpf_probe_read_kernel(&buf[offset], len, d_name.name) != 0) {
return NULL;
}

offset--;
if (offset <= 0) {
return NULL;
}
buf[offset] = '/';

dentry = parent;
}

return &buf[offset];
}

__always_inline static char* get_host_path(char buf[PATH_MAX * 2], struct dentry* d) {
int offset = PATH_MAX - 1;
buf[PATH_MAX - 1] = '\0';
Expand Down
47 changes: 39 additions & 8 deletions fact-ebpf/src/bpf/main.c
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// clang-format off
#include "vmlinux.h"

#include "file.h"
#include "types.h"
#include "process.h"
#include "maps.h"
#include "events.h"
#include "bound_path.h"

#include "vmlinux.h"

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
Expand All @@ -19,9 +19,11 @@ char _license[] SEC("license") = "Dual MIT/GPL";
#define FMODE_PWRITE ((fmode_t)(1 << 4))
#define FMODE_CREATED ((fmode_t)(1 << 20))

SEC("lsm/file_open")
int BPF_PROG(trace_file_open, struct file* file) {
__always_inline static int do_file_open(struct file* file, bool use_bpf_d_path) {
struct metrics_t* m = get_metrics();
if (m == NULL) {
return 0;
}

m->file_open.total++;

Expand All @@ -34,7 +36,7 @@ int BPF_PROG(trace_file_open, struct file* file) {
goto ignored;
}

struct bound_path_t* path = path_read(&file->f_path);
struct bound_path_t* path = path_read(&file->f_path, use_bpf_d_path);
if (path == NULL) {
bpf_printk("Failed to read path");
m->file_open.error++;
Expand All @@ -55,13 +57,29 @@ int BPF_PROG(trace_file_open, struct file* file) {
return 0;
}

SEC("lsm/path_unlink")
int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
SEC("kprobe/security_file_open")
int BPF_KPROBE(kprobe_file_open, struct file* file) {
struct file f;
BPF_CORE_READ_INTO(&f.f_mode, file, f_mode);
BPF_CORE_READ_INTO(&f.f_path, file, f_path);
return do_file_open(&f, false);
}

SEC("lsm/file_open")
int BPF_PROG(trace_file_open, struct file* file) {
return do_file_open(file, true);
}

__always_inline int do_path_unlink(struct path* dir, struct dentry* dentry, bool use_bpf_d_path) {
struct metrics_t* m = get_metrics();
if (m == NULL) {
return 0;
}

m->path_unlink.total++;

struct bound_path_t* path = path_read(dir);
struct bound_path_t* path = path_read(dir, use_bpf_d_path);

if (path == NULL) {
bpf_printk("Failed to read path");
goto error;
Expand Down Expand Up @@ -91,3 +109,16 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
m->path_unlink.error++;
return 0;
}

SEC("kprobe/security_path_unlink")
int BPF_KPROBE(kprobe_path_unlink, struct path* dir, struct dentry* dentry) {
struct path p;
p.mnt = BPF_CORE_READ(dir, mnt);
p.dentry = BPF_CORE_READ(dir, dentry);
return do_path_unlink(&p, dentry, false);
}

SEC("lsm/path_unlink")
int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
return do_path_unlink(dir, dentry, path_unlink_supports_bpf_d_path);
}
Loading
Loading