about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBaitinq <manuelpalenzuelamerino@gmail.com>2024-01-13 21:05:51 +0100
committerBaitinq <manuelpalenzuelamerino@gmail.com>2024-01-13 21:25:48 +0100
commitea9a3f1af5fe7547617821614b8194e4f484a8e1 (patch)
tree5652060b962ba3c839ac1c18e0e000efaf6bd3e2
downloadfs-tracer-ea9a3f1af5fe7547617821614b8194e4f484a8e1.tar.gz
fs-tracer-ea9a3f1af5fe7547617821614b8194e4f484a8e1.tar.bz2
fs-tracer-ea9a3f1af5fe7547617821614b8194e4f484a8e1.zip
Initial commit
-rw-r--r--.cargo/config.toml2
-rw-r--r--.envrc1
-rw-r--r--.gitignore18
-rw-r--r--Cargo.toml2
-rw-r--r--README.md26
-rw-r--r--flake.lock130
-rw-r--r--flake.nix34
-rw-r--r--fs-tracer-common/Cargo.toml14
-rw-r--r--fs-tracer-common/src/lib.rs1
-rw-r--r--fs-tracer-ebpf/.cargo/config.toml6
-rw-r--r--fs-tracer-ebpf/Cargo.toml32
-rw-r--r--fs-tracer-ebpf/rust-toolchain.toml13
-rw-r--r--fs-tracer-ebpf/src/main.rs26
-rw-r--r--fs-tracer/Cargo.toml19
-rw-r--r--fs-tracer/src/main.rs47
-rw-r--r--xtask/Cargo.toml8
-rw-r--r--xtask/src/build_ebpf.rs67
-rw-r--r--xtask/src/main.rs33
-rw-r--r--xtask/src/run.rs70
19 files changed, 549 insertions, 0 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..35049cb
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --"
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ee56924
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+### https://raw.github.com/github/gitignore/master/Rust.gitignore
+
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+.vim/
+.vscode/
+
+.direnv/
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3bcc665
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["xtask", "fs-tracer", "fs-tracer-common"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..74f5ba3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# fs-tracer
+
+## Prerequisites
+
+1. Install bpf-linker: `cargo install bpf-linker`
+
+## Build eBPF
+
+```bash
+cargo xtask build-ebpf
+```
+
+To perform a release build you can use the `--release` flag.
+You may also change the target architecture with the `--target` flag.
+
+## Build Userspace
+
+```bash
+cargo build
+```
+
+## Run
+
+```bash
+RUST_LOG=info cargo xtask run
+```
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..6c6cfb3
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,130 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1701680307,
+        "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_2": {
+      "inputs": {
+        "systems": "systems_2"
+      },
+      "locked": {
+        "lastModified": 1681202837,
+        "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1704722960,
+        "narHash": "sha256-mKGJ3sPsT6//s+Knglai5YflJUF2DGj7Ai6Ynopz0kI=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "317484b1ead87b9c1b8ac5261a8d2dd748a0492d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs_2": {
+      "locked": {
+        "lastModified": 1681358109,
+        "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs",
+        "rust-overlay": "rust-overlay"
+      }
+    },
+    "rust-overlay": {
+      "inputs": {
+        "flake-utils": "flake-utils_2",
+        "nixpkgs": "nixpkgs_2"
+      },
+      "locked": {
+        "lastModified": 1705112162,
+        "narHash": "sha256-IAM0+Uijh/fwlfoeDrOwau9MxcZW3zeDoUHc6Z3xfqM=",
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "9e0af26ffe52bf955ad5575888f093e41fba0104",
+        "type": "github"
+      },
+      "original": {
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "type": "github"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "systems_2": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..1ddec3c
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,34 @@
+{
+  description = "FS Tracer Devshell";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    rust-overlay.url = "github:oxalica/rust-overlay";
+    flake-utils.url = "github:numtide/flake-utils";
+  };
+
+  outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }:
+    flake-utils.lib.eachDefaultSystem (system:
+      let
+        overlays = [ (import rust-overlay) ];
+        pkgs = import nixpkgs {
+          inherit system overlays;
+        };
+      in
+      with pkgs;
+      {
+        devShells.default = mkShell {
+          buildInputs = [
+            rust-analyzer
+            openssl
+            pkg-config
+            (rust-bin.nightly.latest.default.override {
+              extensions = [ "rust-src" ];
+              targets = [ "x86_64-unknown-linux-gnu" "wasm32-unknown-unknown" ];
+            })
+            trunk
+          ];
+        };
+      }
+    );
+}
diff --git a/fs-tracer-common/Cargo.toml b/fs-tracer-common/Cargo.toml
new file mode 100644
index 0000000..2600868
--- /dev/null
+++ b/fs-tracer-common/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "fs-tracer-common"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+default = []
+user = ["aya"]
+
+[dependencies]
+aya = { git = "https://github.com/aya-rs/aya", optional = true }
+
+[lib]
+path = "src/lib.rs"
diff --git a/fs-tracer-common/src/lib.rs b/fs-tracer-common/src/lib.rs
new file mode 100644
index 0000000..0c9ac1a
--- /dev/null
+++ b/fs-tracer-common/src/lib.rs
@@ -0,0 +1 @@
+#![no_std]
diff --git a/fs-tracer-ebpf/.cargo/config.toml b/fs-tracer-ebpf/.cargo/config.toml
new file mode 100644
index 0000000..4302a7f
--- /dev/null
+++ b/fs-tracer-ebpf/.cargo/config.toml
@@ -0,0 +1,6 @@
+[build]
+target-dir = "../target"
+target = "bpfel-unknown-none"
+
+[unstable]
+build-std = ["core"]
diff --git a/fs-tracer-ebpf/Cargo.toml b/fs-tracer-ebpf/Cargo.toml
new file mode 100644
index 0000000..37af91d
--- /dev/null
+++ b/fs-tracer-ebpf/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "fs-tracer-ebpf"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+aya-bpf = { git = "https://github.com/aya-rs/aya" }
+aya-log-ebpf = { git = "https://github.com/aya-rs/aya" }
+fs-tracer-common = { path = "../fs-tracer-common" }
+
+[[bin]]
+name = "fs-tracer"
+path = "src/main.rs"
+
+[profile.dev]
+opt-level = 3
+debug = false
+debug-assertions = false
+overflow-checks = false
+lto = true
+panic = "abort"
+incremental = false
+codegen-units = 1
+rpath = false
+
+[profile.release]
+lto = true
+panic = "abort"
+codegen-units = 1
+
+[workspace]
+members = []
diff --git a/fs-tracer-ebpf/rust-toolchain.toml b/fs-tracer-ebpf/rust-toolchain.toml
new file mode 100644
index 0000000..24ce391
--- /dev/null
+++ b/fs-tracer-ebpf/rust-toolchain.toml
@@ -0,0 +1,13 @@
+[toolchain]
+channel = "nightly"
+# The source code of rustc, provided by the rust-src component, is needed for
+# building eBPF programs.
+components = [
+    "cargo",
+    "clippy",
+    "rust-docs",
+    "rust-src",
+    "rust-std",
+    "rustc",
+    "rustfmt",
+]
diff --git a/fs-tracer-ebpf/src/main.rs b/fs-tracer-ebpf/src/main.rs
new file mode 100644
index 0000000..f6d7dfb
--- /dev/null
+++ b/fs-tracer-ebpf/src/main.rs
@@ -0,0 +1,26 @@
+#![no_std]
+#![no_main]
+
+use aya_bpf::{
+    macros::tracepoint,
+    programs::TracePointContext,
+};
+use aya_log_ebpf::info;
+
+#[tracepoint]
+pub fn fs_tracer(ctx: TracePointContext) -> u32 {
+    match try_fs_tracer(ctx) {
+        Ok(ret) => ret,
+        Err(ret) => ret,
+    }
+}
+
+fn try_fs_tracer(ctx: TracePointContext) -> Result<u32, u32> {
+    info!(&ctx, "tracepoint syscalls called");
+    Ok(0)
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    unsafe { core::hint::unreachable_unchecked() }
+}
diff --git a/fs-tracer/Cargo.toml b/fs-tracer/Cargo.toml
new file mode 100644
index 0000000..03ca947
--- /dev/null
+++ b/fs-tracer/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "fs-tracer"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+aya = { git = "https://github.com/aya-rs/aya", features = ["async_tokio"] }
+aya-log = { git = "https://github.com/aya-rs/aya" }
+fs-tracer-common = { path = "../fs-tracer-common", features = ["user"] }
+anyhow = "1"
+env_logger = "0.10"
+libc = "0.2"
+log = "0.4"
+tokio = { version = "1.25", features = ["macros", "rt", "rt-multi-thread", "net", "signal"] }
+
+[[bin]]
+name = "fs-tracer"
+path = "src/main.rs"
diff --git a/fs-tracer/src/main.rs b/fs-tracer/src/main.rs
new file mode 100644
index 0000000..3bd29f7
--- /dev/null
+++ b/fs-tracer/src/main.rs
@@ -0,0 +1,47 @@
+use aya::programs::TracePoint;
+use aya::{include_bytes_aligned, Bpf};
+use aya_log::BpfLogger;
+use log::{info, warn, debug};
+use tokio::signal;
+
+#[tokio::main]
+async fn main() -> Result<(), anyhow::Error> {
+    env_logger::init();
+
+    // Bump the memlock rlimit. This is needed for older kernels that don't use the
+    // new memcg based accounting, see https://lwn.net/Articles/837122/
+    let rlim = libc::rlimit {
+        rlim_cur: libc::RLIM_INFINITY,
+        rlim_max: libc::RLIM_INFINITY,
+    };
+    let ret = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &rlim) };
+    if ret != 0 {
+        debug!("remove limit on locked memory failed, ret is: {}", ret);
+    }
+
+    // This will include your eBPF object file as raw bytes at compile-time and load it at
+    // runtime. This approach is recommended for most real-world use cases. If you would
+    // like to specify the eBPF program at runtime rather than at compile-time, you can
+    // reach for `Bpf::load_file` instead.
+    #[cfg(debug_assertions)]
+    let mut bpf = Bpf::load(include_bytes_aligned!(
+        "../../target/bpfel-unknown-none/debug/fs-tracer"
+    ))?;
+    #[cfg(not(debug_assertions))]
+    let mut bpf = Bpf::load(include_bytes_aligned!(
+        "../../target/bpfel-unknown-none/release/fs-tracer"
+    ))?;
+    if let Err(e) = BpfLogger::init(&mut bpf) {
+        // This can happen if you remove all log statements from your eBPF program.
+        warn!("failed to initialize eBPF logger: {}", e);
+    }
+    let program: &mut TracePoint = bpf.program_mut("fs_tracer").unwrap().try_into()?;
+    program.load()?;
+    program.attach("syscalls", "sys_enter_write")?;
+
+    info!("Waiting for Ctrl-C...");
+    signal::ctrl_c().await?;
+    info!("Exiting...");
+
+    Ok(())
+}
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
new file mode 100644
index 0000000..c4dea5d
--- /dev/null
+++ b/xtask/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1"
+clap = { version = "4.1", features = ["derive"] }
diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs
new file mode 100644
index 0000000..8da1111
--- /dev/null
+++ b/xtask/src/build_ebpf.rs
@@ -0,0 +1,67 @@
+use std::{path::PathBuf, process::Command};
+
+use clap::Parser;
+
+#[derive(Debug, Copy, Clone)]
+pub enum Architecture {
+    BpfEl,
+    BpfEb,
+}
+
+impl std::str::FromStr for Architecture {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s {
+            "bpfel-unknown-none" => Architecture::BpfEl,
+            "bpfeb-unknown-none" => Architecture::BpfEb,
+            _ => return Err("invalid target".to_owned()),
+        })
+    }
+}
+
+impl std::fmt::Display for Architecture {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            Architecture::BpfEl => "bpfel-unknown-none",
+            Architecture::BpfEb => "bpfeb-unknown-none",
+        })
+    }
+}
+
+#[derive(Debug, Parser)]
+pub struct Options {
+    /// Set the endianness of the BPF target
+    #[clap(default_value = "bpfel-unknown-none", long)]
+    pub target: Architecture,
+    /// Build the release target
+    #[clap(long)]
+    pub release: bool,
+}
+
+pub fn build_ebpf(opts: Options) -> Result<(), anyhow::Error> {
+    let dir = PathBuf::from("fs-tracer-ebpf");
+    let target = format!("--target={}", opts.target);
+    let mut args = vec![
+        "build",
+        target.as_str(),
+        "-Z",
+        "build-std=core",
+    ];
+    if opts.release {
+        args.push("--release")
+    }
+
+    // Command::new creates a child process which inherits all env variables. This means env
+    // vars set by the cargo xtask command are also inherited. RUSTUP_TOOLCHAIN is removed
+    // so the rust-toolchain.toml file in the -ebpf folder is honored.
+
+    let status = Command::new("cargo")
+        .current_dir(dir)
+        .env_remove("RUSTUP_TOOLCHAIN")
+        .args(&args)
+        .status()
+        .expect("failed to build bpf program");
+    assert!(status.success());
+    Ok(())
+}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
new file mode 100644
index 0000000..c1c594e
--- /dev/null
+++ b/xtask/src/main.rs
@@ -0,0 +1,33 @@
+mod build_ebpf;
+mod run;
+
+use std::process::exit;
+
+use clap::Parser;
+
+#[derive(Debug, Parser)]
+pub struct Options {
+    #[clap(subcommand)]
+    command: Command,
+}
+
+#[derive(Debug, Parser)]
+enum Command {
+    BuildEbpf(build_ebpf::Options),
+    Run(run::Options),
+}
+
+fn main() {
+    let opts = Options::parse();
+
+    use Command::*;
+    let ret = match opts.command {
+        BuildEbpf(opts) => build_ebpf::build_ebpf(opts),
+        Run(opts) => run::run(opts),
+    };
+
+    if let Err(e) = ret {
+        eprintln!("{e:#}");
+        exit(1);
+    }
+}
diff --git a/xtask/src/run.rs b/xtask/src/run.rs
new file mode 100644
index 0000000..f354f8f
--- /dev/null
+++ b/xtask/src/run.rs
@@ -0,0 +1,70 @@
+use std::process::Command;
+
+use anyhow::Context as _;
+use clap::Parser;
+
+use crate::build_ebpf::{build_ebpf, Architecture, Options as BuildOptions};
+
+#[derive(Debug, Parser)]
+pub struct Options {
+    /// Set the endianness of the BPF target
+    #[clap(default_value = "bpfel-unknown-none", long)]
+    pub bpf_target: Architecture,
+    /// Build and run the release target
+    #[clap(long)]
+    pub release: bool,
+    /// The command used to wrap your application
+    #[clap(short, long, default_value = "doas")]
+    pub runner: String,
+    /// Arguments to pass to your application
+    #[clap(name = "args", last = true)]
+    pub run_args: Vec<String>,
+}
+
+/// Build the project
+fn build(opts: &Options) -> Result<(), anyhow::Error> {
+    let mut args = vec!["build"];
+    if opts.release {
+        args.push("--release")
+    }
+    let status = Command::new("cargo")
+        .args(&args)
+        .status()
+        .expect("failed to build userspace");
+    assert!(status.success());
+    Ok(())
+}
+
+/// Build and run the project
+pub fn run(opts: Options) -> Result<(), anyhow::Error> {
+    // build our ebpf program followed by our application
+    build_ebpf(BuildOptions {
+        target: opts.bpf_target,
+        release: opts.release,
+    })
+    .context("Error while building eBPF program")?;
+    build(&opts).context("Error while building userspace application")?;
+
+    // profile we are building (release or debug)
+    let profile = if opts.release { "release" } else { "debug" };
+    let bin_path = format!("target/{profile}/fs-tracer");
+
+    // arguments to pass to the application
+    let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect();
+
+    // configure args
+    let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect();
+    args.push(bin_path.as_str());
+    args.append(&mut run_args);
+
+    // run the command
+    let status = Command::new(args.first().expect("No first argument"))
+        .args(args.iter().skip(1))
+        .status()
+        .expect("failed to run the command");
+
+    if !status.success() {
+        anyhow::bail!("Failed to run `{}`", args.join(" "));
+    }
+    Ok(())
+}