├── 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 |
--------------------------------------------------------------------------------