├── Dockerfile ├── .github └── workflows │ └── build.yml ├── CONTRIBUTION.md ├── LICENSE.md ├── setup-logrotate.sh ├── ERRORS.md ├── examples └── test-container-escape.sh ├── Makefile ├── src ├── detector.bpf.c └── loader.c └── README.md /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | FROM ubuntu:22.04 3 | 4 | # Install dependencies 5 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 6 | clang-14 \ 7 | llvm-14 \ 8 | libbpf-dev \ 9 | bpftool \ 10 | linux-headers-6.8.0-1021-aws \ 11 | kmod \ 12 | git \ 13 | make 14 | 15 | # Set working environment 16 | WORKDIR /app 17 | ENV PATH="/usr/lib/llvm-14/bin:${PATH}" 18 | 19 | # Clone repo (or use bind mount) 20 | CMD ["bash"] 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: BPF Verification 2 | 3 | on: [push] 4 | 5 | jobs: 6 | verify: 7 | runs-on: ubuntu-22.04 8 | steps: 9 | - uses: actions/checkout@v3 10 | 11 | - name: Setup BPF Toolchain 12 | run: | 13 | sudo apt-get update 14 | sudo apt-get install -y clang llvm libbpf-dev linux-headers-generic 15 | 16 | - name: Build Project 17 | run: | 18 | clang -O2 -target bpf -c src/detector.bpf.c -o src/detector.bpf.o 19 | bpftool prog load src/detector.bpf.o /sys/fs/bpf/detector 20 | 21 | - name: Verify BPF 22 | run: | 23 | bpftool prog show | grep "detect_container_escape" 24 | sudo rm /sys/fs/bpf/detector 25 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | **CONTRIBUTING.md** 2 | ```markdown 3 | # Contribution Guide 4 | 5 | We welcome help with this early-stage project! Current priorities: 6 | 7 | 1. Improve detection logic for common escapes: 8 | - Namespace breakout (unshare, nsenter) 9 | - Mount abuse 10 | - Privileged container escapes 11 | 12 | 2. Reduce false positives in process tracing 13 | 14 | 3. Build reliable alerting pipeline 15 | 16 | ## Guidelines 17 | - Open an issue before submitting PRs 18 | - Keep BPF programs kernel 6.8+ compatible 19 | - Use libbpf-based loading (no BCC) 20 | - Document all detection patterns 21 | 22 | ## Development Setup 23 | ```bash 24 | # Ubuntu 22.04+ required 25 | make build 26 | sudo ./bin/loader 27 | 28 | # Test with: 29 | make test 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | **LICENSE.md** 2 | ```markdown 3 | # GNU GENERAL PUBLIC LICENSE 4 | Version 3, 29 June 2007 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | -------------------------------------------------------------------------------- /setup-logrotate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Log file path 4 | LOG_FILE="/var/log/ebpf-container-security.log" 5 | 6 | # Logrotate config path 7 | LOGROTATE_CONFIG="/etc/logrotate.d/ebpf-container-security" 8 | 9 | # Create log file if it doesn't exist 10 | if [ ! -f "$LOG_FILE" ]; then 11 | sudo touch "$LOG_FILE" 12 | sudo chmod 644 "$LOG_FILE" 13 | echo "✅ Created log file: $LOG_FILE" 14 | fi 15 | 16 | # Write logrotate configuration 17 | sudo tee "$LOGROTATE_CONFIG" > /dev/null <` to loader.c - because even kernel hackers need naps 15 | 16 | ### Directory Disappearing Act 17 | ``` 18 | **Error:** `Cannot save file into non-existent directory: 'reports'` 19 | **Fix:** 20 | ```c 21 | // Modern directories don't grow on trees 22 | #include 23 | mkdir("reports", 0755); 24 | ``` 25 | 26 | [View full error list](https://yourdomain.com/ebpf-container-security-errors) 27 | 28 | ## Runtime Headscratchers 29 | ### Silent Treatment 30 | ``` 31 | **Symptom:** Loader runs but no alerts 32 | **Debug:** 33 | ```bash 34 | sudo cat /sys/kernel/debug/tracing/trace_pipe | grep DEBUG 35 | # Turns out containers lie about their parents 36 | ``` 37 | 38 | [Full troubleshooting guide →](https://yourdomain.com/ebpf-troubleshooting) 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /examples/test-container-escape.sh: -------------------------------------------------------------------------------- 1 | # examples/test-container-escape.sh 2 | #!/bin/bash 3 | 4 | set -e 5 | 6 | echo "Starting container escape tests..." 7 | 8 | # Run the loader in the background 9 | sudo ./bin/loader & 10 | LOADER_PID=$! 11 | sleep 2 # Give the loader time to start 12 | 13 | # Test 1: Simulate a container escape using unshare 14 | echo "Running unshare test..." 15 | if sudo unshare -UrmC bash -c "echo 'Inside unshared namespace'"; then 16 | echo "unshare test succeeded" 17 | else 18 | echo "unshare test failed (may require privileged permissions)" 19 | fi 20 | 21 | # Test 2: Simulate a container escape using nsenter 22 | echo "Running nsenter test..." 23 | if sudo nsenter --mount=/proc/1/ns/mnt bash -c "echo 'Inside nsenter namespace'"; then 24 | echo "nsenter test succeeded" 25 | else 26 | echo "nsenter test failed (may require privileged permissions)" 27 | fi 28 | 29 | # Test 3: Simulate a shell escape 30 | echo "Running shell escape test..." 31 | if sudo docker run --rm -it alpine sh -c "echo 'Inside container shell'"; then 32 | echo "docker shell test succeeded" 33 | else 34 | echo "docker shell test failed (is Docker installed and running?)" 35 | fi 36 | 37 | # Stop the loader 38 | echo "Stopping loader..." 39 | sudo pkill -f loader 40 | 41 | echo "Tests completed!" 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ### Makefile ### 2 | CC := clang 3 | ARCH := $(shell uname -m | sed 's/x86_64/x86/') 4 | BPF_CFLAGS := -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) -I. -I/usr/include/$(ARCH)-linux-gnu 5 | USER_CFLAGS := -Wall -O2 -lbpf -lelf -lz 6 | 7 | ### Directories ### 8 | SRC_DIR := src 9 | BIN_DIR := bin 10 | 11 | ### File Targets ### 12 | BPF_OBJ := $(SRC_DIR)/detector.bpf.o 13 | SKEL_H := $(SRC_DIR)/detector.skel.h 14 | LOADER := $(BIN_DIR)/loader 15 | 16 | ### Phony Targets ### 17 | .PHONY: all build test clean install-logrotate uninstall-logrotate 18 | 19 | ### Main Targets ### 20 | all: build install-logrotate 21 | 22 | build: $(LOADER) 23 | 24 | ### Build Rules (TABS REQUIRED BELOW) ### 25 | $(LOADER): $(SRC_DIR)/loader.c $(SKEL_H) | $(BIN_DIR) 26 | $(CC) $(USER_CFLAGS) -o $@ $< 27 | 28 | $(SKEL_H): $(BPF_OBJ) 29 | bpftool gen skeleton $< > $@ 30 | 31 | $(BPF_OBJ): $(SRC_DIR)/detector.bpf.c vmlinux.h 32 | $(CC) $(BPF_CFLAGS) -c $< -o $@ 33 | 34 | $(BIN_DIR): 35 | @mkdir -p $@ 36 | 37 | ### Test Rule ### 38 | test: build 39 | @sudo $(LOADER) & 40 | @sleep 1 41 | @sudo -E ./examples/test-container-escape.sh || true 42 | @pkill -f $(LOADER) 43 | 44 | ### Clean Rule ### 45 | clean: 46 | @rm -rf $(SRC_DIR)/*.o $(SRC_DIR)/*.skel.h $(BIN_DIR) 47 | @rm -f vmlinux.h 48 | 49 | ### vmlinux.h Generation ### 50 | vmlinux.h: 51 | @bpftool btf dump file /sys/kernel/btf/vmlinux format c > $@ 52 | 53 | install-logrotate: 54 | ./setup-logrotate.sh 55 | 56 | -------------------------------------------------------------------------------- /src/detector.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | 6 | char _license[] SEC("license") = "GPL"; 7 | 8 | struct { 9 | __uint(type, BPF_MAP_TYPE_RINGBUF); 10 | __uint(max_entries, 256 * 1024); 11 | } events SEC(".maps"); 12 | 13 | struct event { 14 | char comm[16]; 15 | char pcomm[16]; 16 | u32 pid; 17 | }; 18 | 19 | static __always_inline 20 | int custom_strncmp(const char *s1, unsigned int n, const char *s2) 21 | { 22 | for (unsigned int i = 0; i < n; i++) { 23 | if (s1[i] != s2[i]) return -1; 24 | if (s1[i] == 0) return 0; 25 | } 26 | return 0; 27 | } 28 | 29 | SEC("tracepoint/syscalls/sys_enter_execve") 30 | int detect_container_escape(struct trace_event_raw_sys_enter *ctx) 31 | { 32 | struct event *e; 33 | char comm[TASK_COMM_LEN] = {0}; 34 | char pcomm[TASK_COMM_LEN] = {0}; 35 | struct task_struct *task = (struct task_struct *)bpf_get_current_task(); 36 | struct task_struct *parent = NULL; 37 | u32 pid = bpf_get_current_pid_tgid() >> 32; 38 | 39 | bpf_get_current_comm(&comm, sizeof(comm)); 40 | 41 | if (BPF_CORE_READ(task, real_parent)) { 42 | BPF_CORE_READ_INTO(&parent, task, real_parent); 43 | } 44 | if (!parent) return 0; 45 | 46 | bpf_probe_read_kernel_str(&pcomm, sizeof(pcomm), parent->comm); 47 | 48 | if (custom_strncmp(pcomm, 15, "containerd-shim") == 0 || 49 | custom_strncmp(pcomm, 9, "containerd") == 0 || 50 | custom_strncmp(pcomm, 3, "ctr") == 0 || 51 | custom_strncmp(pcomm, 3, "run") == 0) { 52 | 53 | if (custom_strncmp(comm, 7, "unshare") == 0 || 54 | custom_strncmp(comm, 6, "nsenter") == 0 || 55 | custom_strncmp(comm, 3, "sh") == 0 || 56 | custom_strncmp(comm, 4, "bash") == 0) { 57 | 58 | e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); 59 | if (e) { 60 | __builtin_memcpy(e->comm, comm, sizeof(comm)); 61 | __builtin_memcpy(e->pcomm, pcomm, sizeof(pcomm)); 62 | e->pid = pid; 63 | bpf_ringbuf_submit(e, 0); 64 | } 65 | } 66 | } 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eBPF Container Security Monitor (Early Development) 2 | 3 | 🔍 *eBPF-based container escape detection prototype | Kernel 6.8+ | Early development stage | Not production-ready* 4 | 5 | ⚠️ **Experimental Project** 6 | This is a work-in-progress eBPF-based container escape detection system. Currently in active development - detection logic and alerting are not fully functional yet. 7 | 8 | ## Current State 9 | - Basic eBPF program loading works 10 | - Syscall tracing infrastructure in place 11 | - Detection patterns under development 12 | - **No reliable alerts generated yet** 13 | 14 | ## Prerequisites 15 | - Ubuntu 22.04+ (AWS EC2 tested) 16 | - Linux kernel 6.8+ 17 | - clang 14+, libbpf-dev, bpftool 18 | 19 | ## Installation 20 | ```bash 21 | sudo apt update && sudo apt install -y clang llvm libbpf-dev linux-headers-$(uname -r) bpftoolgit clone https://github.com/yourusername/ebpf-container-security.git 22 | git clone https://github.com/elijahu1/ebpf-container-security.git 23 | make build 24 | ``` 25 | 26 | ## Usage 27 | ```bash 28 | # Load detector 29 | sudo ./bin/loader 30 | 31 | # In another terminal, monitor logs 32 | sudo cat /sys/kernel/debug/tracing/trace_pipe 33 | 34 | # Trigger test (no alerts expected yet) 35 | docker run --rm ubuntu unshare --user 36 | ``` 37 | 38 | ## Docker Development Environment 39 | Rebuild the exact testing environment: 40 | ```bash 41 | # Build image (from project root) 42 | docker build -t ebpf-monitor-dev . 43 | 44 | # Run with host kernel headers access 45 | docker run -it --rm \ 46 | -v /lib/modules:/lib/modules:ro \ 47 | -v /usr/src:/usr/src:ro \ 48 | -v $(pwd):/app \ 49 | ebpf-monitor-dev 50 | 51 | # Inside container: 52 | make build && sudo ./bin/loader 53 | ``` 54 | 55 | **Key Limitations**: 56 | - Requires host kernel 6.8+ 57 | - Bind mounts needed for kernel headers 58 | - BPF programs interact directly with host kernel 59 | 60 | ## Troubleshooting 61 | If you get no output: 62 | - Verify kernel version matches headers: `uname -r` 63 | - Check BPF program load: `sudo bpftool prog list` 64 | - Ensure tracing is enabled: `sudo sh -c 'echo 1 > /sys/kernel/debug/tracing/tracing_on'` 65 | 66 | ## Roadmap 67 | - [ ] Basic container escape detection 68 | - [ ] Alert filtering 69 | - [ ] Integration with container runtimes 70 | - [ ] Production deployment guide 71 | 72 | ## License 73 | This project is licensed under [GNU GPLv3](LICENSE.md) 74 | 75 | --- 76 | 77 | **Contributions welcome!** See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines. 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /src/loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include // Fixed include 10 | 11 | static volatile bool running = true; 12 | static FILE *log_file = NULL; 13 | 14 | struct event { 15 | char comm[16]; 16 | char pcomm[16]; 17 | uint32_t pid; // Fixed type 18 | }; 19 | 20 | void log_message(const char *message) { 21 | time_t now = time(NULL); 22 | struct tm *tm = localtime(&now); 23 | fprintf(log_file, "[%04d-%02d-%02d %02d:%02d:%02d] %s\n", 24 | tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, 25 | tm->tm_hour, tm->tm_min, tm->tm_sec, message); 26 | fflush(log_file); 27 | } 28 | 29 | static int handle_event(void *ctx, void *data, size_t size) { 30 | struct event *e = data; 31 | char log_msg[256]; 32 | snprintf(log_msg, sizeof(log_msg), "ALERT: %s (PID: %d) spawned by %s", 33 | e->comm, e->pid, e->pcomm); 34 | log_message(log_msg); 35 | return 0; 36 | } 37 | 38 | void handle_signal(int sig) { 39 | running = false; 40 | if (log_file) fclose(log_file); 41 | } 42 | 43 | int main() { 44 | struct ring_buffer *rb = NULL; 45 | struct bpf_object *obj = NULL; 46 | int err; 47 | 48 | log_file = fopen("/var/log/ebpf-container-security.log", "a"); 49 | if (!log_file) { 50 | fprintf(stderr, "Failed to open log file: %s\n", strerror(errno)); 51 | return 1; 52 | } 53 | 54 | struct rlimit rlim = {RLIM_INFINITY, RLIM_INFINITY}; 55 | setrlimit(RLIMIT_MEMLOCK, &rlim); 56 | 57 | obj = bpf_object__open("src/detector.bpf.o"); 58 | if (!obj) { 59 | fprintf(stderr, "Failed to open BPF object: %s\n", strerror(-errno)); 60 | goto cleanup; 61 | } 62 | 63 | if ((err = bpf_object__load(obj))) { 64 | fprintf(stderr, "Failed to load BPF object: %s\n", strerror(-err)); 65 | goto cleanup; 66 | } 67 | 68 | rb = ring_buffer__new(bpf_object__find_map_fd_by_name(obj, "events"), 69 | handle_event, NULL, NULL); 70 | if (!rb) { 71 | fprintf(stderr, "Failed to create ring buffer\n"); 72 | goto cleanup; 73 | } 74 | 75 | signal(SIGINT, handle_signal); 76 | signal(SIGTERM, handle_signal); 77 | 78 | printf("Monitoring container escapes... (Ctrl+C to exit)\n"); 79 | 80 | while (running) { 81 | err = ring_buffer__poll(rb, 100); 82 | if (err == -EINTR) break; 83 | if (err < 0) { 84 | fprintf(stderr, "Error polling ring buffer: %s\n", strerror(-err)); 85 | break; 86 | } 87 | } 88 | 89 | cleanup: 90 | ring_buffer__free(rb); 91 | bpf_object__close(obj); 92 | return 0; 93 | } 94 | --------------------------------------------------------------------------------