diff options
author | Baitinq <manuelpalenzuelamerino@gmail.com> | 2024-01-13 21:05:51 +0100 |
---|---|---|
committer | Baitinq <manuelpalenzuelamerino@gmail.com> | 2024-01-13 21:25:48 +0100 |
commit | ea9a3f1af5fe7547617821614b8194e4f484a8e1 (patch) | |
tree | 5652060b962ba3c839ac1c18e0e000efaf6bd3e2 | |
download | fs-tracer-ea9a3f1af5fe7547617821614b8194e4f484a8e1.tar.gz fs-tracer-ea9a3f1af5fe7547617821614b8194e4f484a8e1.tar.bz2 fs-tracer-ea9a3f1af5fe7547617821614b8194e4f484a8e1.zip |
Initial commit
-rw-r--r-- | .cargo/config.toml | 2 | ||||
-rw-r--r-- | .envrc | 1 | ||||
-rw-r--r-- | .gitignore | 18 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 26 | ||||
-rw-r--r-- | flake.lock | 130 | ||||
-rw-r--r-- | flake.nix | 34 | ||||
-rw-r--r-- | fs-tracer-common/Cargo.toml | 14 | ||||
-rw-r--r-- | fs-tracer-common/src/lib.rs | 1 | ||||
-rw-r--r-- | fs-tracer-ebpf/.cargo/config.toml | 6 | ||||
-rw-r--r-- | fs-tracer-ebpf/Cargo.toml | 32 | ||||
-rw-r--r-- | fs-tracer-ebpf/rust-toolchain.toml | 13 | ||||
-rw-r--r-- | fs-tracer-ebpf/src/main.rs | 26 | ||||
-rw-r--r-- | fs-tracer/Cargo.toml | 19 | ||||
-rw-r--r-- | fs-tracer/src/main.rs | 47 | ||||
-rw-r--r-- | xtask/Cargo.toml | 8 | ||||
-rw-r--r-- | xtask/src/build_ebpf.rs | 67 | ||||
-rw-r--r-- | xtask/src/main.rs | 33 | ||||
-rw-r--r-- | xtask/src/run.rs | 70 |
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(()) +} |