diff --git a/ebpf/README.md b/ebpf/README.md new file mode 100644 index 00000000..6734ae8e --- /dev/null +++ b/ebpf/README.md @@ -0,0 +1,19 @@ +## wolfSSL eBPF Examples + +This directory contains **eBPF-based observability examples** demonstrating how Linux eBPF can be used to monitor system calls, user-space wolfSSL operations, network activity, and TLS behavior **without modifying application code**. + +These examples are designed for: + +* **debugging** +* **education** +* **performance tracing** +* **understanding TLS internals** +* **research and experimentation** + +All examples require: + +* Linux kernel with eBPF support (4.19+ recommended) +* clang/LLVM (for compiling `.bpf.c` programs) +* libbpf and libelf +* root privileges +* A recent installation of wolfSSL if running TLS examples \ No newline at end of file diff --git a/ebpf/syscall-write-trace/.gitignore b/ebpf/syscall-write-trace/.gitignore new file mode 100644 index 00000000..e4b9e9d0 --- /dev/null +++ b/ebpf/syscall-write-trace/.gitignore @@ -0,0 +1,13 @@ +# Binaries +client-tcp +server-tcp +write_tracer +*.o +*.bpf.o +*.log + +# Editor files +*~ +*.swp +.DS_Store + diff --git a/ebpf/syscall-write-trace/Makefile b/ebpf/syscall-write-trace/Makefile new file mode 100755 index 00000000..757267c9 --- /dev/null +++ b/ebpf/syscall-write-trace/Makefile @@ -0,0 +1,49 @@ +CC = gcc +CLANG = clang + +CFLAGS = -O2 -g -Wall +BPF_CFLAGS = -O2 -g -target bpf -D__TARGET_ARCH_$(ARCH) + +# Detect architecture for correct include path +ARCH := $(shell uname -m) +ifeq ($(ARCH),x86_64) + ARCH_DIR = x86_64-linux-gnu +else ifeq ($(ARCH),aarch64) + ARCH_DIR = aarch64-linux-gnu +else + ARCH_DIR = x86_64-linux-gnu +endif + +BPF_INCLUDES = -I/usr/include -I/usr/include/$(ARCH_DIR) +LIBBPF_LIBS = -lbpf -lelf -lz + +TARGETS = write_tracer write_tracer.bpf.o client-tcp server-tcp + +.PHONY: all clean help + +all: $(TARGETS) + +write_tracer.bpf.o: write_tracer.bpf.c + $(CLANG) $(BPF_CFLAGS) $(BPF_INCLUDES) -c $< -o $@ + +write_tracer: write_tracer.c write_tracer.bpf.o + $(CC) $(CFLAGS) write_tracer.c -lelf -lz -lbpf -o write_tracer + +client-tcp: client-tcp.c + $(CC) $(CFLAGS) client-tcp.c -o client-tcp + +server-tcp: server-tcp.c + $(CC) $(CFLAGS) server-tcp.c -o server-tcp + +clean: + rm -f *.o write_tracer client-tcp server-tcp + +help: + @echo "Targets:" + @echo " all - Build syscall tracer and TCP demo apps" + @echo " clean - Remove binaries" + @echo "" + @echo "Instructions:" + @echo " 1. sudo ./write_tracer" + @echo " 2. ./server-tcp" + @echo " 3. ./client-tcp 127.0.0.1" diff --git a/ebpf/syscall-write-trace/README.md b/ebpf/syscall-write-trace/README.md new file mode 100644 index 00000000..c44e9595 --- /dev/null +++ b/ebpf/syscall-write-trace/README.md @@ -0,0 +1,296 @@ +# syscall-write-trace + +### eBPF Example: Tracing Plaintext at the `write()` Syscall Boundary + +This example demonstrates how to use **Linux eBPF** to intercept the `write()` system call and extract plaintext data **before the kernel performs any buffering, encryption, or processing**. +It uses a simple TCP client/server pair to generate predictable network writes and an eBPF tracepoint program to observe them. + +This example is part of the **wolfSSL eBPF observability suite**, designed to help developers understand how plaintext flows through the system and how eBPF can be used to debug, monitor, or study application behavior without modifying application code. + +--- + +# πŸ“Œ **Problem** + +When debugging network applications, especially TLS applications, developers often want to inspect: + +* What plaintext is being written +* What data is being sent to the network +* Whether buffers contain what we expect +* Whether the application or kernel is modifying data +* Whether the problem is at the app layer, kernel layer, or crypto layer + +However, once an application calls: + +``` +write(fd, buffer, count) +``` + +the kernel: + +* does **not** expose the plaintext +* may buffer or coalesce writes +* may encrypt data (TLS offload, QUIC, etc.) +* hides memory from tools +* provides no visibility into the user buffer + +Traditional debugging tools (tcpdump, Wireshark, strace) **cannot see the plaintext** before encryption or kernel processing. + +This creates a visibility gap. + +--- + +# 🎯 **Solution** + +We attach an **eBPF tracepoint** to: + +``` +tracepoint/syscalls/sys_enter_write +``` + +This gives us: + +* access to the syscall arguments +* the calling process’s PID/TID +* the file descriptor +* the byte count +* the raw user pointer to the data +* ability to read the plaintext with `bpf_probe_read_user()` + +The eBPF program: + +1. Filters events only from a target process (`client-tcp`) +2. Copies up to 255 bytes of user-space buffer safely +3. Sends them to user-space via a perf buffer +4. The userspace loader prints ASCII and hex output + +This provides **perfect visibility** into the plaintext leaving the application. + +--- + +# 🧩 **Architecture** + +``` + TCP Client App (user space) + | + | 1. call write(fd, buf, count) + v + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ tracepoint: sys_enter_write β”‚ ← eBPF hook runs BEFORE write executes + β”‚ eBPF program: β”‚ + β”‚ - filters process name β”‚ + β”‚ - reads buffer from user mem β”‚ + β”‚ - emits event via perf buf β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + | + | 2. event (plaintext) + v + Userspace Loader (write_tracer) + -------------------------------- + - loads BPF program + - attaches tracepoint + - opens perf buffer + - prints plaintext + | + | 3. human-readable output + v + Terminal +``` + +--- + +# πŸ” **Detailed Walkthrough** + +## 1. The Sample Applications + +### `server-tcp.c` + +A simple TCP echo server on port 11111: + +* waits for a connection +* receives a message +* prints it +* echoes back a canned response +* loops + +### `client-tcp.c` + +A matching TCP client: + +* prompts user for input +* writes it to the server +* prints server response + +These programs provide **predictable write() calls** for tracing. + +--- + +## 2. The eBPF Program: `write_tracer.bpf.c` + +Hooks: + +``` +tracepoint/syscalls/sys_enter_write +``` + +Key details: + +### βœ” Event Filtering + +Checks the process name (`comm`) to avoid tracing all processes. + +### βœ” Safe Memory Access + +Uses: + +``` +bpf_probe_read_user() +``` + +to copy user memory safely, limited to 255 bytes (verifier-friendly bound). + +### βœ” Perf Buffer Emission + +Writes events to a ring buffer consumed by user-space. + +### βœ” Struct of event data + +Contains: + +* PID, TID +* FD +* count +* process name +* captured data + +--- + +## 3. The Userspace Loader: `write_tracer.c` + +It: + +1. Raises RLIMIT_MEMLOCK +2. Loads `write_tracer.bpf.o` +3. Attaches the tracepoint +4. Opens perf buffer +5. Pretty-prints events: + +``` +=== WRITE SYSCALL INTERCEPTED === +Process: client-tcp (PID: 1234) +FD: 3 +Count: 13 bytes +Data: "hello world!" +Hex: 68 65 6c ... +``` + +This gives full plaintext visibility. + +--- + +# πŸš€ **How to Build** + +Dependencies (Ubuntu): + +```bash +sudo apt install clang llvm libbpf-dev libelf-dev zlib1g-dev build-essential +``` + +Then: + +```bash +make +``` + +This compiles: + +* TCP client/server +* eBPF program (`write_tracer.bpf.o`) +* userspace loader (`write_tracer`) + +--- + +# ▢️ **How to Run** + +### 1. Terminal #1 β€” Start the tracer + +```bash +sudo ./write_tracer +``` + +### 2. Terminal #2 β€” Start the TCP server + +```bash +./server-tcp +``` + +### 3. Terminal #3 β€” Run client and type message + +```bash +./client-tcp 127.0.0.1 +``` + +Tracer output: + +``` +=== WRITE SYSCALL INTERCEPTED === +Process: client-tcp +File Descriptor: 3 +Write Count: 13 bytes +Data (first 13 bytes): hello world! +``` + +--- + +# 🎁 **Benefits of This Example** + +### βœ” Shows how to read plaintext before kernel/network/TLS processing + +### βœ” Demonstrates safe buffer access in eBPF + +### βœ” Demonstrates filtering (PID, process name) + +### βœ” Teaches core eBPF concepts: tracepoints, perf buffers, verifier constraints + +### βœ” Foundation for more advanced examples (TLS plaintext, handshake tracing) + +### βœ” Helps wolfSSL developers debug TLS behavior + +### βœ” Useful for application developers integrating wolfSSL + +--- + +# βš™οΈ **Nitty-Gritty Details** + +### 1. Why tracepoint instead of kprobe? + +Tracepoints are: + +* stable +* argument offsets fixed +* preferred for syscall entry tracing + +Allows verifier to analyze program more easily. + +### 2. Why use process name filtering? + +Without it, the tracer prints: + +* output from bash +* systemd +* everything reading/writing + +Filtering avoids noise. + +### 3. Why limit buffer to 255 bytes? + +Verifier restrictions require fixed bounded copy sizes. +A 255-byte buffer is safe and sufficient for demos. + +### 4. Why use perf buffer instead of ringbuf? + +Perf buffer is more compatible with older kernels (e.g., Ubuntu LTS). +Perfect for examples. + +### 5. Why use simple TCP client/server? + +Consistent, predictable write() calls make tracing easy to demo. diff --git a/ebpf/syscall-write-trace/client-tcp.c b/ebpf/syscall-write-trace/client-tcp.c new file mode 100755 index 00000000..38d6f3ad --- /dev/null +++ b/ebpf/syscall-write-trace/client-tcp.c @@ -0,0 +1,115 @@ +/* client-tcp.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* the usual suspects */ +#include +#include +#include + +/* socket includes */ +#include +#include +#include +#include + +#define DEFAULT_PORT 11111 + + + +int main(int argc, char** argv) +{ + int sockfd; + struct sockaddr_in servAddr; + char buff[256]; + size_t len; + int ret; + + + /* Check for proper calling convention */ + if (argc != 2) { + printf("usage: %s \n", argv[0]); + return 0; + } + + /* Create a socket that uses an internet IPv4 address, + * Sets the socket to be stream based (TCP), + * 0 means choose the default protocol. */ + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto end; + } + + /* Initialize the server address struct with zeros */ + memset(&servAddr, 0, sizeof(servAddr)); + + /* Fill in the server address */ + servAddr.sin_family = AF_INET; /* using IPv4 */ + servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */ + + /* Get the server IPv4 address from the command line call */ + if (inet_pton(AF_INET, argv[1], &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address\n"); + ret = -1; + goto end; + } + + /* Connect to the server */ + if ((ret = connect(sockfd, (struct sockaddr*) &servAddr, sizeof(servAddr))) + == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + goto end; + } + + /* Get a message for the server from stdin */ + printf("Message for server: "); + memset(buff, 0, sizeof(buff)); + if (fgets(buff, sizeof(buff), stdin) == NULL) { + fprintf(stderr, "ERROR: failed to get message for server\n"); + ret = -1; + goto socket_cleanup; + } + len = strnlen(buff, sizeof(buff)); + + /* Send the message to the server */ + if (write(sockfd, buff, len) != len) { + fprintf(stderr, "ERROR: failed to write\n"); + ret = -1; + goto socket_cleanup; + } + + /* Read the server data into our buff array */ + memset(buff, 0, sizeof(buff)); + if (read(sockfd, buff, sizeof(buff)-1) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + ret = -1; + goto socket_cleanup; + } + + /* Print to stdout any data the server sends */ + printf("Server: %s\n", buff); + + /* Cleanup and return */ +socket_cleanup: + close(sockfd); /* Close the connection to the server */ +end: + return ret; /* Return reporting a success */ +} diff --git a/ebpf/syscall-write-trace/server-tcp.c b/ebpf/syscall-write-trace/server-tcp.c new file mode 100755 index 00000000..1f2cbd25 --- /dev/null +++ b/ebpf/syscall-write-trace/server-tcp.c @@ -0,0 +1,149 @@ +/* server-tcp.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* socket includes */ +#include +#include +#include +#include + +#define DEFAULT_PORT 11111 + + + +int main() +{ + int ret; + int sockfd; + int connd; + struct sockaddr_in servAddr; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + size_t len; + int shutdown = 0; + const char* reply = "I hear ya fa shizzle!\n"; + + + + /* Create a socket that uses an internet IPv4 address, + * Sets the socket to be stream based (TCP), + * 0 means choose the default protocol. */ + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto end; + } + + + + /* Initialize the server address struct with zeros */ + memset(&servAddr, 0, sizeof(servAddr)); + + /* Fill in the server address */ + servAddr.sin_family = AF_INET; /* using IPv4 */ + servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */ + servAddr.sin_addr.s_addr = INADDR_ANY; /* from anywhere */ + + + + /* Bind the server socket to our port */ + if (bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; + goto servsocket_cleanup; + } + + /* Listen for a new connection, allow 5 pending connections */ + if (listen(sockfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; + goto servsocket_cleanup; + } + + + + /* Continue to accept clients until shutdown is issued */ + while (!shutdown) { + printf("Waiting for a connection...\n"); + + /* Accept client connections */ + if ((connd = accept(sockfd, (struct sockaddr*)&clientAddr, &size)) + == -1) { + fprintf(stderr, "ERROR: failed to accept the connection\n\n"); + ret = -1; + goto servsocket_cleanup; + } + + printf("Client connected successfully\n"); + + + + /* Read the client data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = read(connd, buff, sizeof(buff)-1)) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + goto clientsocket_cleanup; + } + + /* Print to stdout any data the client sends */ + printf("Client: %s\n", buff); + + /* Check for server shutdown command */ + if (strncmp(buff, "shutdown", 8) == 0) { + printf("Shutdown command issued!\n"); + shutdown = 1; + } + + + + /* Write our reply into buff */ + memset(buff, 0, sizeof(buff)); + memcpy(buff, reply, strlen(reply)); + len = strnlen(buff, sizeof(buff)); + + /* Reply back to the client */ + if ((ret = write(connd, buff, len)) != len) { + fprintf(stderr, "ERROR: failed to write\n"); + goto clientsocket_cleanup; + } + + + + /* Cleanup after this connection */ + close(connd); /* Close the connection to the client */ + } + + printf("Shutdown complete\n"); + + + /* Cleanup and return */ +clientsocket_cleanup: + close(connd); /* Close the connection to the client */ +servsocket_cleanup: + close(sockfd); /* Close the socket listening for clients */ +end: + return ret; /* Return reporting a success */ +} diff --git a/ebpf/syscall-write-trace/write_tracer.bpf.c b/ebpf/syscall-write-trace/write_tracer.bpf.c new file mode 100755 index 00000000..5c96ba44 --- /dev/null +++ b/ebpf/syscall-write-trace/write_tracer.bpf.c @@ -0,0 +1,117 @@ +/* wolfssl_tracer.bpf.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +char LICENSE[] SEC("license") = "GPL"; + +// Define the tracepoint structure based on kernel BTF +struct trace_entry { + unsigned short type; + unsigned char flags; + unsigned char preempt_count; + int pid; +}; + +struct trace_event_raw_sys_enter { + struct trace_entry ent; + long int id; + long unsigned int args[6]; +}; + +struct write_event { + __u32 pid; + __u32 tid; + int fd; + __u64 count; + char comm[16]; + char data[256]; +}; + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} events SEC(".maps"); + +// Use syscall tracepoint with BPF_PROG macro for proper argument extraction +SEC("tp/syscalls/sys_enter_write") +int trace_write_enter(struct trace_event_raw_sys_enter *ctx) +{ + struct write_event event = {}; + __u64 pid_tgid; + __u32 pid, tid; + char comm[16]; + long ret; + unsigned int fd; + char *buf; + unsigned long count; + + pid_tgid = bpf_get_current_pid_tgid(); + pid = pid_tgid >> 32; + tid = (__u32)pid_tgid; + + bpf_get_current_comm(&comm, sizeof(comm)); + + // Filter: only trace "client-tcp" process + // Unrolled string comparison for verifier + if (comm[0] != 'c' || comm[1] != 'l' || comm[2] != 'i' || + comm[3] != 'e' || comm[4] != 'n' || comm[5] != 't' || + comm[6] != '-' || comm[7] != 't' || comm[8] != 'c' || comm[9] != 'p') { + return 0; + } + + // The syscall arguments are stored in ctx->args[] + // args[0] = fd, args[1] = buf, args[2] = count + fd = (unsigned int)ctx->args[0]; + buf = (char *)ctx->args[1]; + count = (unsigned long)ctx->args[2]; + + event.pid = pid; + event.tid = tid; + event.fd = fd; + event.count = count; + + bpf_get_current_comm(&event.comm, sizeof(event.comm)); + + // Read user buffer data with bounds checking + if (count > 0 && buf != 0) { + unsigned long len = count; + if (len > 255) { + len = 255; + } + + // Use bpf_probe_read_user to safely read from user space + ret = bpf_probe_read_user(event.data, len, buf); + if (ret < 0) { + event.data[0] = '\0'; + } else if (len < 256) { + event.data[len] = '\0'; + } + } else { + event.data[0] = '\0'; + } + + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + return 0; +} diff --git a/ebpf/syscall-write-trace/write_tracer.c b/ebpf/syscall-write-trace/write_tracer.c new file mode 100755 index 00000000..c4ac4ac9 --- /dev/null +++ b/ebpf/syscall-write-trace/write_tracer.c @@ -0,0 +1,201 @@ +/* write_tracer.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct write_event { + __u32 pid; + __u32 tid; + int fd; + __u64 count; + char comm[16]; + char data[256]; +}; + +static volatile bool running = true; + +static void sig_handler(int sig) +{ + running = false; +} + +static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +{ + struct write_event *e = data; + + printf("=== WRITE SYSCALL INTERCEPTED ===\n"); + printf("Process: %s (PID: %u, TID: %u)\n", e->comm, e->pid, e->tid); + printf("File Descriptor: %d\n", e->fd); + printf("Write Count: %llu bytes\n", e->count); + printf("Data (first %zu bytes): ", strlen(e->data)); + + for (int i = 0; i < strlen(e->data) && i < 64; i++) { + char c = e->data[i]; + if (c >= 32 && c <= 126) { + printf("%c", c); + } else { + printf("\\x%02x", (unsigned char)c); + } + } + printf("\n"); + + printf("Data (hex): "); + for (int i = 0; i < strlen(e->data) && i < 32; i++) { + printf("%02x ", (unsigned char)e->data[i]); + } + printf("\n"); + printf("=====================================\n\n"); +} + +static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) +{ + printf("Lost %llu events on CPU #%d!\n", lost_cnt, cpu); +} + +static int bump_memlock_rlimit(void) +{ + struct rlimit rlim_new = { + .rlim_cur = RLIM_INFINITY, + .rlim_max = RLIM_INFINITY, + }; + + if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) { + fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit: %s\n", strerror(errno)); + fprintf(stderr, "You may need to run this program with sudo or adjust system limits.\n"); + return -1; + } + return 0; +} + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + /* Only print warnings and errors */ + if (level <= LIBBPF_WARN) + return vfprintf(stderr, format, args); + return 0; +} + +int main(int argc, char **argv) +{ + struct bpf_object *obj; + struct bpf_program *prog; + struct bpf_link *link; + struct perf_buffer *pb; + int map_fd; + int err; + + /* Set up libbpf logging callback */ + libbpf_set_print(libbpf_print_fn); + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + printf("Loading eBPF program to trace write() syscalls...\n"); + + /* Check if we're running as root */ + if (geteuid() != 0) { + fprintf(stderr, "Error: This program must be run as root (use sudo).\n"); + fprintf(stderr, "Example: sudo %s\n", argv[0]); + return 1; + } + + /* Bump RLIMIT_MEMLOCK to allow BPF map creation */ + if (bump_memlock_rlimit()) { + fprintf(stderr, "Failed to increase rlimit\n"); + return 1; + } + + printf("Press Ctrl+C to stop.\n\n"); + + obj = bpf_object__open_file("write_tracer.bpf.o", NULL); + if (libbpf_get_error(obj)) { + fprintf(stderr, "Failed to open BPF object file\n"); + return 1; + } + + err = bpf_object__load(obj); + if (err) { + fprintf(stderr, "Failed to load BPF object: %d\n", err); + goto cleanup; + } + + prog = bpf_object__find_program_by_name(obj, "trace_write_enter"); + if (!prog) { + fprintf(stderr, "Failed to find BPF program\n"); + err = 1; + goto cleanup; + } + + link = bpf_program__attach(prog); + if (libbpf_get_error(link)) { + fprintf(stderr, "Failed to attach BPF program\n"); + err = 1; + goto cleanup; + } + + map_fd = bpf_object__find_map_fd_by_name(obj, "events"); + if (map_fd < 0) { + fprintf(stderr, "Failed to find events map\n"); + err = 1; + goto cleanup_link; + } + + struct perf_buffer_opts pb_opts = { + .sample_cb = handle_event, + .lost_cb = handle_lost_events, + }; + + pb = perf_buffer__new(map_fd, 8, &pb_opts); + if (libbpf_get_error(pb)) { + fprintf(stderr, "Failed to create perf buffer\n"); + err = 1; + goto cleanup_link; + } + + printf("Successfully loaded and attached eBPF program!\n"); + printf("Monitoring write() syscalls...\n\n"); + + while (running) { + err = perf_buffer__poll(pb, 100); + if (err < 0 && err != -EINTR) { + fprintf(stderr, "Error polling perf buffer: %d\n", err); + break; + } + } + + printf("\nShutting down...\n"); + + perf_buffer__free(pb); +cleanup_link: + bpf_link__destroy(link); +cleanup: + bpf_object__close(obj); + return err; +} diff --git a/ebpf/tls-uprobe-trace/.gitignore b/ebpf/tls-uprobe-trace/.gitignore new file mode 100644 index 00000000..dda4a6fc --- /dev/null +++ b/ebpf/tls-uprobe-trace/.gitignore @@ -0,0 +1,13 @@ +# Binaries +client-tls +server-tls +wolfssl_uprobe +*.o +*.bpf.o +*.log + +# Editor files +*~ +*.swp +.DS_Store + diff --git a/ebpf/tls-uprobe-trace/Makefile b/ebpf/tls-uprobe-trace/Makefile new file mode 100644 index 00000000..3da5a294 --- /dev/null +++ b/ebpf/tls-uprobe-trace/Makefile @@ -0,0 +1,44 @@ +CC = gcc +CLANG = clang +CFLAGS = -O2 -g -Wall + +# Auto-detect host arch, convert to BPF target name +UNAME_M := $(shell uname -m) + +ifeq ($(UNAME_M), x86_64) + BPF_ARCH := x86 + UAPI_PATH := /usr/include/x86_64-linux-gnu +else ifeq ($(UNAME_M), aarch64) + BPF_ARCH := arm64 + UAPI_PATH := /usr/include/aarch64-linux-gnu +else + $(error Unsupported architecture: $(UNAME_M)) +endif + +BPF_CFLAGS = -O2 -g -target bpf -D__TARGET_ARCH_$(BPF_ARCH) + +LIBBPF_LIBS = -lbpf -lelf -lz + +TARGETS = client-tls server-tls wolfssl_uprobe wolfssl_uprobe.bpf.o + +all: $(TARGETS) + +# ===== TLS Programs ===== +client-tls: client-tls.c + $(CC) $(CFLAGS) $< -o $@ -lwolfssl + +server-tls: server-tls.c + $(CC) $(CFLAGS) $< -o $@ -lwolfssl + +# ===== eBPF Program ===== +wolfssl_uprobe.bpf.o: wolfssl_uprobe.bpf.c + $(CLANG) $(BPF_CFLAGS) \ + -I/usr/include \ + -I$(UAPI_PATH) \ + -c $< -o $@ + +wolfssl_uprobe: wolfssl_uprobe.c wolfssl_uprobe.bpf.o + $(CC) $(CFLAGS) $< -o $@ $(LIBBPF_LIBS) + +clean: + rm -f $(TARGETS) *.o *.log diff --git a/ebpf/tls-uprobe-trace/README.md b/ebpf/tls-uprobe-trace/README.md new file mode 100644 index 00000000..ef6f8907 --- /dev/null +++ b/ebpf/tls-uprobe-trace/README.md @@ -0,0 +1,232 @@ +# `tls-uprobe-trace` – Tracing `wolfSSL_write()` and `wolfSSL_read()` with eBPF + +This example demonstrates how to use **eBPF uprobes** to observe **plaintext TLS data** inside applications that use **wolfSSL**. +Because decryption happens *inside the process* (not in the kernel), traditional tools like `tcpdump` or kprobes **cannot see decrypted SSL/TLS plaintext**. +Uprobes make it possible to attach to userspace functions such as `wolfSSL_write()` and `wolfSSL_read()` and extract: + +* plaintext being written (encrypted later by TLS) +* plaintext being read (already decrypted by TLS) +* PID/TID +* SSL* internal pointer +* byte count + sample of the buffer +* process name + +This example includes: + +βœ”οΈ TLS client +βœ”οΈ TLS server +βœ”οΈ eBPF probe attached to `wolfSSL_write` +βœ”οΈ eBPF probe attached to `wolfSSL_read` (uretprobe) +βœ”οΈ Perf buffer event delivery to user space +βœ”οΈ Architecture-portable code (x86_64 & ARM64) +βœ”οΈ Symbol lookup (no hardcoding offsets) + +--- + +## 1. Problem + +When using TLS, all application data is encrypted before it leaves the process: + +``` +cleartext β†’ wolfSSL_write() β†’ encrypt β†’ kernel β†’ network +network β†’ kernel β†’ decrypt β†’ wolfSSL_read() β†’ cleartext +``` + +This means: + +* Kernel tools **cannot see decrypted payloads** +* tcpdump/wireshark **see only ciphertext** +* syscall tracing (e.g., `sys_enter_write`) **sees ciphertext** +* security & observability tools cannot inspect application-level data + +For debugging, auditing, or educational purposes, developers often need a way to inspect: + +* what plaintext the application *actually sent* +* what plaintext it *actually received* + +--- + +## 2. Solution: eBPF Uprobes on wolfSSL + +eBPF uprobes allow attaching to **any userspace function**. +By attaching to: + +* `wolfSSL_write()` β†’ entry probe +* `wolfSSL_read()` β†’ entry + return probes + +we can safely extract plaintext buffers from user-space memory *before encryption* or *after decryption*. + +This example uses: + +* `trace_wolfssl_write_enter` +* `trace_wolfssl_read_enter` +* `trace_wolfssl_read_exit` + +The eBPF program reads arguments from registers (`PT_REGS_PARM1`, etc.), copies user-space buffers via `bpf_probe_read_user()`, and emits structured events to a perf buffer. + +--- + +## 3. Benefits + +### βœ”οΈ Observe decrypted TLS data + +See exactly what `client-tls` and `server-tls` send and receive. + +### βœ”οΈ No modification to wolfSSL or application + +Zero instrumentation changes required. + +### βœ”οΈ Low overhead & safe + +eBPF verifier guarantees memory safety; uprobes only execute on function entry/exit. + +### βœ”οΈ Works with any application using wolfSSL + +Examples reuse standard wolfSSL examples with no changes. + +### βœ”οΈ Foundation for deeper wolfSSL observability + +Future variants can trace: + +* handshake events +* cipher suite negotiation +* session resumption +* key export callbacks +* user-defined I/O callbacks + +--- + +## 4. Architecture + +``` + +---------------------------+ + | TLS Application | + | (client-tls / server-tls)| + +-------------+-------------+ + | TLS API + v + wolfSSL_write() / wolfSSL_read() + | + -------- uprobe / uretprobe --------- + | + eBPF programs (BPF bytecode) + | + perf event ring buffer + | + userspace loader (wolfssl_uprobe) + | + human-readable terminal output +``` + +### Files included: + +| File | Purpose | +| ----------------------------------- | -------------------------------------------- | +| `client-tls.c` | TLS client using wolfSSL | +| `server-tls.c` | TLS server using wolfSSL | +| `wolfssl_uprobe.c` | eBPF loader + perf buffer consumer | +| `wolfssl_uprobe.bpf.c` | eBPF program (uprobes on wolfSSL_write/read) | +| `Makefile` | Build everything cleanly for x86 or ARM | +| `../../certs/ca-cert.pem` | CA certificate (shared example) | +| `../../certs/server-cert.pem` | Server certificate (shared example) | +| `../../certs/server-key.pem` | Server key (shared example) | + +--- + +## 5. Building + +### Requirements + +```bash +sudo apt install clang llvm libbpf-dev libelf-dev zlib1g-dev make +``` + +wolfSSL must be installed (shared library): + +```bash +sudo make install +sudo ldconfig +``` + +### Build everything + +```bash +make +``` + +This produces: + +* `client-tls` +* `server-tls` +* `wolfssl_uprobe` +* `wolfssl_uprobe.bpf.o` + +--- + +## 6. Running the Example + +### 1. Start the eBPF tracer + +(must run as root) + +```bash +sudo ./wolfssl_uprobe +``` + +You should see: + +``` +Successfully loaded and attached eBPF programs! +Monitoring wolfSSL_write() and wolfSSL_read() calls... +``` + +### 2. Start the TLS server + +```bash +./server-tls +``` + +### 3. Start the TLS client + +```bash +./client-tls 127.0.0.1 +Message for server: hello world! +``` + +### Expected tracer output + +#### From `wolfSSL_write`: + +``` +=== wolfSSL_write() INTERCEPTED === +Process: client-tls (PID: 2312) +Write Count: 12 bytes +Data: hello world! +===================================== +``` + +#### From `wolfSSL_read`: + +``` +=== wolfSSL_read() INTERCEPTED === +Process: client-tls (PID: 2312) +Bytes Read: 25 +Decrypted Data: I hear ya fa shizzle! +===================================== +``` + +--- + +## 7. Notes & Limitations + +* Tracing is implemented using **uprobes**, so only plaintext inside userspace is visible (by design). +* The example currently filters to process name `client-tls` to reduce noise. +* Return probe (`uretprobe`) only fires once TLS read returns to user space. + +--- + +## 8. Cleanup + +```bash +make clean +``` diff --git a/ebpf/tls-uprobe-trace/client-tls.c b/ebpf/tls-uprobe-trace/client-tls.c new file mode 100755 index 00000000..31d1150a --- /dev/null +++ b/ebpf/tls-uprobe-trace/client-tls.c @@ -0,0 +1,186 @@ +/* client-tls.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +/* socket includes */ +#include +#include +#include +#include + +/* wolfSSL */ +#include +#include + +#define DEFAULT_PORT 11111 + +#define CERT_FILE "../../certs/ca-cert.pem" + + + +int main(int argc, char** argv) +{ + int sockfd; + struct sockaddr_in servAddr; + char buff[256]; + size_t len; + int ret; + + /* declare wolfSSL objects */ + WOLFSSL_CTX* ctx; + WOLFSSL* ssl; + + + + /* Check for proper calling convention */ + if (argc != 2) { + printf("usage: %s \n", argv[0]); + return 0; + } + + /* Create a socket that uses an internet IPv4 address, + * Sets the socket to be stream based (TCP), + * 0 means choose the default protocol. */ + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto end; + } + + /* Initialize the server address struct with zeros */ + memset(&servAddr, 0, sizeof(servAddr)); + + /* Fill in the server address */ + servAddr.sin_family = AF_INET; /* using IPv4 */ + servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */ + + /* Get the server IPv4 address from the command line call */ + if (inet_pton(AF_INET, argv[1], &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address\n"); + ret = -1; + goto end; + } + + /* Connect to the server */ + if ((ret = connect(sockfd, (struct sockaddr*) &servAddr, sizeof(servAddr))) + == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + goto end; + } + + /*---------------------------------*/ + /* Start of wolfSSL initialization and configuration */ + /*---------------------------------*/ + /* Initialize wolfSSL */ + if ((ret = wolfSSL_Init()) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to initialize the library\n"); + goto socket_cleanup; + } + + /* Create and initialize WOLFSSL_CTX */ +#ifdef USE_TLSV13 + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); +#else + ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); +#endif + if (ctx == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto socket_cleanup; + } + + /* Load client certificates into WOLFSSL_CTX */ + if ((ret = wolfSSL_CTX_load_verify_locations(ctx, CERT_FILE, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", + CERT_FILE); + goto ctx_cleanup; + } + + /* Create a WOLFSSL object */ + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + goto ctx_cleanup; + } + + /* Attach wolfSSL to the socket */ + if ((ret = wolfSSL_set_fd(ssl, sockfd)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to set the file descriptor\n"); + goto cleanup; + } + + /* Connect to wolfSSL on the server side */ + if ((ret = wolfSSL_connect(ssl)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to connect to wolfSSL\n"); + goto cleanup; + } + + /* Get a message for the server from stdin */ + printf("Message for server: "); + memset(buff, 0, sizeof(buff)); + if (fgets(buff, sizeof(buff), stdin) == NULL) { + fprintf(stderr, "ERROR: failed to get message for server\n"); + ret = -1; + goto cleanup; + } + len = strnlen(buff, sizeof(buff)); + + /* Send the message to the server */ + if ((ret = wolfSSL_write(ssl, buff, len)) != len) { + fprintf(stderr, "ERROR: failed to write entire message\n"); + fprintf(stderr, "%d bytes of %d bytes were sent", ret, (int) len); + goto cleanup; + } + + /* Read the server data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = wolfSSL_read(ssl, buff, sizeof(buff)-1)) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + goto cleanup; + } + + /* Print to stdout any data the server sends */ + printf("Server: %s\n", buff); + + /* Bidirectional shutdown */ + while (wolfSSL_shutdown(ssl) == WOLFSSL_SHUTDOWN_NOT_DONE) { + printf("Shutdown not complete\n"); + } + + printf("Shutdown complete\n"); + + ret = 0; + + /* Cleanup and return */ +cleanup: + wolfSSL_free(ssl); /* Free the wolfSSL object */ +ctx_cleanup: + wolfSSL_CTX_free(ctx); /* Free the wolfSSL context object */ + wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */ +socket_cleanup: + close(sockfd); /* Close the connection to the server */ +end: + return ret; /* Return reporting a success */ +} diff --git a/ebpf/tls-uprobe-trace/server-tls.c b/ebpf/tls-uprobe-trace/server-tls.c new file mode 100755 index 00000000..1db0ba58 --- /dev/null +++ b/ebpf/tls-uprobe-trace/server-tls.c @@ -0,0 +1,229 @@ +/* server-tls.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* the usual suspects */ +#include +#include +#include + +/* socket includes */ +#include +#include +#include +#include + +/* wolfSSL */ +#include +#include + +#define DEFAULT_PORT 11111 + +#define CERT_FILE "../../certs/server-cert.pem" +#define KEY_FILE "../../certs/server-key.pem" + + + +int main() +{ + int sockfd = SOCKET_INVALID; + int connd = SOCKET_INVALID; + struct sockaddr_in servAddr; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + size_t len; + int shutdown = 0; + int ret; + const char* reply = "I hear ya fa shizzle!\n"; + + /* declare wolfSSL objects */ + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_CIPHER* cipher; + +#if 0 + wolfSSL_Debugging_ON(); +#endif + + /* Initialize wolfSSL */ + wolfSSL_Init(); + + + + /* Create a socket that uses an internet IPv4 address, + * Sets the socket to be stream based (TCP), + * 0 means choose the default protocol. */ + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + + + + /* Create and initialize WOLFSSL_CTX */ +#ifdef USE_TLSV13 + ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); +#else + ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method()); +#endif + if (ctx == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + + /* Load server certificates into WOLFSSL_CTX */ + if ((ret = wolfSSL_CTX_use_certificate_file(ctx, CERT_FILE, WOLFSSL_FILETYPE_PEM)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", + CERT_FILE); + goto exit; + } + + /* Load server key into WOLFSSL_CTX */ + if ((ret = wolfSSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, WOLFSSL_FILETYPE_PEM)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", + KEY_FILE); + goto exit; + } + + + + /* Initialize the server address struct with zeros */ + memset(&servAddr, 0, sizeof(servAddr)); + + /* Fill in the server address */ + servAddr.sin_family = AF_INET; /* using IPv4 */ + servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */ + servAddr.sin_addr.s_addr = INADDR_ANY; /* from anywhere */ + + + + /* Bind the server socket to our port */ + if (bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; + goto exit; + } + + /* Listen for a new connection, allow 5 pending connections */ + if (listen(sockfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; + goto exit; + } + + + + /* Continue to accept clients until shutdown is issued */ + while (!shutdown) { + printf("Waiting for a connection...\n"); + + /* Accept client connections */ + if ((connd = accept(sockfd, (struct sockaddr*)&clientAddr, &size)) + == -1) { + fprintf(stderr, "ERROR: failed to accept the connection\n\n"); + ret = -1; + goto exit; + } + + /* Create a WOLFSSL object */ + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + goto exit; + } + + /* Attach wolfSSL to the socket */ + wolfSSL_set_fd(ssl, connd); + + /* Establish TLS connection */ + ret = wolfSSL_accept(ssl); + if (ret != WOLFSSL_SUCCESS) { + fprintf(stderr, "wolfSSL_accept error = %d\n", + wolfSSL_get_error(ssl, ret)); + goto exit; + } + + + printf("Client connected successfully\n"); + + cipher = wolfSSL_get_current_cipher(ssl); + printf("SSL cipher suite is %s\n", wolfSSL_CIPHER_get_name(cipher)); + + + /* Read the client data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = wolfSSL_read(ssl, buff, sizeof(buff)-1)) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + goto exit; + } + + /* Print to stdout any data the client sends */ + printf("Client: %s\n", buff); + + /* Check for server shutdown command */ + if (strncmp(buff, "shutdown", 8) == 0) { + printf("Shutdown command issued!\n"); + shutdown = 1; + } + + + + /* Write our reply into buff */ + memset(buff, 0, sizeof(buff)); + memcpy(buff, reply, strlen(reply)); + len = strnlen(buff, sizeof(buff)); + + /* Reply back to the client */ + if ((ret = wolfSSL_write(ssl, buff, len)) != len) { + fprintf(stderr, "ERROR: failed to write\n"); + goto exit; + } + + /* Notify the client that the connection is ending */ + wolfSSL_shutdown(ssl); + printf("Shutdown complete\n"); + + /* Cleanup after this connection */ + wolfSSL_free(ssl); /* Free the wolfSSL object */ + ssl = NULL; + close(connd); /* Close the connection to the client */ + } + + ret = 0; + +exit: + /* Cleanup and return */ + if (ssl) + wolfSSL_free(ssl); /* Free the wolfSSL object */ + if (connd != SOCKET_INVALID) + close(connd); /* Close the connection to the client */ + if (sockfd != SOCKET_INVALID) + close(sockfd); /* Close the socket listening for clients */ + if (ctx) + wolfSSL_CTX_free(ctx); /* Free the wolfSSL context object */ + wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */ + + return ret; /* Return reporting a success */ +} diff --git a/ebpf/tls-uprobe-trace/wolfssl_uprobe.bpf.c b/ebpf/tls-uprobe-trace/wolfssl_uprobe.bpf.c new file mode 100755 index 00000000..0b220300 --- /dev/null +++ b/ebpf/tls-uprobe-trace/wolfssl_uprobe.bpf.c @@ -0,0 +1,239 @@ +/* wolfssl_uprobe.bpf.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include + +char LICENSE[] SEC("license") = "GPL"; + +struct wolfssl_event { + __u32 type; // 0 = write, 1 = read + __u32 pid; + __u32 tid; + __u64 ssl_ptr; + __u64 count; // bytes written or read + char comm[16]; + char data[256]; +}; + +#ifdef __TARGET_ARCH_x86 +struct pt_regs { + __u64 r15, r14, r13, r12, rbp, rbx, r11, r10; + __u64 r9, r8, rax, rcx, rdx, rsi, rdi, orig_rax; + __u64 rip, cs, eflags, rsp, ss; +}; +#endif + +#ifdef __TARGET_ARCH_arm64 +struct pt_regs { + __u64 regs[31]; + __u64 sp; + __u64 pc; + __u64 pstate; +}; +#endif + +// Store wolfSSL_read arguments while function executes +struct read_args { + void *buf; + int sz; +}; + +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(key_size, sizeof(__u32)); + __uint(value_size, sizeof(__u32)); +} events SEC(".maps"); + +// Map to store read function arguments indexed by thread ID +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1024); + __type(key, __u64); // pid_tgid + __type(value, struct read_args); +} read_args_map SEC(".maps"); + +// wolfSSL_write(WOLFSSL* ssl, const void* data, int sz) +// For ARM64 userspace functions: +// x0 = first arg (ssl) +// x1 = second arg (data) +// x2 = third arg (sz) +SEC("uprobe/wolfSSL_write") +int trace_wolfssl_write_enter(struct pt_regs *ctx) +{ + struct wolfssl_event event = {}; + __u64 pid_tgid; + __u32 pid, tid; + char comm[16]; + long ret; + void *ssl; + void *data; + int sz; + + pid_tgid = bpf_get_current_pid_tgid(); + pid = pid_tgid >> 32; + tid = (__u32)pid_tgid; + + bpf_get_current_comm(&comm, sizeof(comm)); + + // Filter: only trace "client-tls" process + // Manual unrolled comparison to avoid loop issues with verifier + if (comm[0] != 'c' || comm[1] != 'l' || comm[2] != 'i' || + comm[3] != 'e' || comm[4] != 'n' || comm[5] != 't' || + comm[6] != '-' || comm[7] != 't' || comm[8] != 'l' || comm[9] != 's') { + return 0; + } + + // Read function arguments from registers + ssl = (void *)PT_REGS_PARM1(ctx); + data = (void *)PT_REGS_PARM2(ctx); + sz = (int)PT_REGS_PARM3(ctx); + + event.type = 0; // write event + event.pid = pid; + event.tid = tid; + event.ssl_ptr = (__u64)ssl; + event.count = (__u64)sz; + + bpf_get_current_comm(&event.comm, sizeof(event.comm)); + + // Read user buffer data with bounds checking + if (sz > 0 && data != 0) { + unsigned long len = sz; + if (len > 255) { + len = 255; + } + + // Use bpf_probe_read_user to safely read from user space + ret = bpf_probe_read_user(event.data, len, data); + if (ret < 0) { + event.data[0] = '\0'; + } else if (len < 256) { + event.data[len] = '\0'; + } + } else { + event.data[0] = '\0'; + } + + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + return 0; +} + +// wolfSSL_read(WOLFSSL* ssl, void* data, int sz) +// Entry probe - save the arguments for later use in return probe +SEC("uprobe/wolfSSL_read") +int trace_wolfssl_read_enter(struct pt_regs *ctx) +{ + __u64 pid_tgid; + char comm[16]; + struct read_args args = {}; + + pid_tgid = bpf_get_current_pid_tgid(); + bpf_get_current_comm(&comm, sizeof(comm)); + + // Filter: only trace "client-tls" process + if (comm[0] != 'c' || comm[1] != 'l' || comm[2] != 'i' || + comm[3] != 'e' || comm[4] != 'n' || comm[5] != 't' || + comm[6] != '-' || comm[7] != 't' || comm[8] != 'l' || comm[9] != 's') { + return 0; + } + + // Save buffer pointer and size for the return probe + // x0 = ssl (not needed), x1 = buf, x2 = sz + args.buf = (void *)PT_REGS_PARM2(ctx); + args.sz = (int)PT_REGS_PARM3(ctx); + + bpf_map_update_elem(&read_args_map, &pid_tgid, &args, BPF_ANY); + return 0; +} + +// wolfSSL_read return probe - read the decrypted data from the buffer +SEC("uretprobe/wolfSSL_read") +int trace_wolfssl_read_exit(struct pt_regs *ctx) +{ + struct wolfssl_event event = {}; + __u64 pid_tgid; + __u32 pid, tid; + char comm[16]; + struct read_args *args; + int bytes_read; + long ret; + + pid_tgid = bpf_get_current_pid_tgid(); + pid = pid_tgid >> 32; + tid = (__u32)pid_tgid; + + bpf_get_current_comm(&comm, sizeof(comm)); + + // Filter: only trace "client-tls" process + if (comm[0] != 'c' || comm[1] != 'l' || comm[2] != 'i' || + comm[3] != 'e' || comm[4] != 'n' || comm[5] != 't' || + comm[6] != '-' || comm[7] != 't' || comm[8] != 'l' || comm[9] != 's') { + return 0; + } + + // Retrieve the saved arguments + args = bpf_map_lookup_elem(&read_args_map, &pid_tgid); + if (!args) { + return 0; + } + + bytes_read = (int)PT_REGS_RC(ctx); + + // Only process successful reads + if (bytes_read <= 0) { + bpf_map_delete_elem(&read_args_map, &pid_tgid); + return 0; + } + + event.type = 1; // read event + event.pid = pid; + event.tid = tid; + event.ssl_ptr = 0; // We didn't save SSL pointer, but could if needed + event.count = (__u64)bytes_read; + + bpf_get_current_comm(&event.comm, sizeof(event.comm)); + + // Read the decrypted data from the buffer + if (bytes_read > 0 && args->buf != 0) { + unsigned long len = bytes_read; + if (len > 255) { + len = 255; + } + + // Use bpf_probe_read_user to safely read from user space + ret = bpf_probe_read_user(event.data, len, args->buf); + if (ret < 0) { + event.data[0] = '\0'; + } else if (len < 256) { + event.data[len] = '\0'; + } + } else { + event.data[0] = '\0'; + } + + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event)); + + // Clean up the map entry + bpf_map_delete_elem(&read_args_map, &pid_tgid); + return 0; +} diff --git a/ebpf/tls-uprobe-trace/wolfssl_uprobe.c b/ebpf/tls-uprobe-trace/wolfssl_uprobe.c new file mode 100755 index 00000000..45f91c7e --- /dev/null +++ b/ebpf/tls-uprobe-trace/wolfssl_uprobe.c @@ -0,0 +1,356 @@ +/* wolfssl_uprobe.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct wolfssl_event { + __u32 type; // 0 = write, 1 = read + __u32 pid; + __u32 tid; + __u64 ssl_ptr; + __u64 count; // bytes written or read + char comm[16]; + char data[256]; +}; + +static volatile bool running = true; + +static void sig_handler(int sig) +{ + running = false; +} + +static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz) +{ + struct wolfssl_event *e = data; + + if (e->type == 0) { + // Write event + printf("=== wolfSSL_write() INTERCEPTED ===\n"); + printf("Process: %s (PID: %u, TID: %u)\n", e->comm, e->pid, e->tid); + printf("SSL Pointer: 0x%llx\n", e->ssl_ptr); + printf("Write Count: %llu bytes\n", e->count); + printf("Data (first %zu bytes): ", strlen(e->data)); + + for (int i = 0; i < strlen(e->data) && i < 64; i++) { + char c = e->data[i]; + if (c >= 32 && c <= 126) { + printf("%c", c); + } else { + printf("\\x%02x", (unsigned char)c); + } + } + printf("\n"); + + printf("Data (hex): "); + for (int i = 0; i < strlen(e->data) && i < 32; i++) { + printf("%02x ", (unsigned char)e->data[i]); + } + printf("\n"); + printf("=====================================\n\n"); + } else if (e->type == 1) { + // Read event + printf("=== wolfSSL_read() INTERCEPTED (RETURN) ===\n"); + printf("Process: %s (PID: %u, TID: %u)\n", e->comm, e->pid, e->tid); + printf("Bytes Read: %llu bytes\n", e->count); + printf("Decrypted Data (first %zu bytes): ", strlen(e->data)); + + for (int i = 0; i < strlen(e->data) && i < 64; i++) { + char c = e->data[i]; + if (c >= 32 && c <= 126) { + printf("%c", c); + } else { + printf("\\x%02x", (unsigned char)c); + } + } + printf("\n"); + + printf("Data (hex): "); + for (int i = 0; i < strlen(e->data) && i < 32; i++) { + printf("%02x ", (unsigned char)e->data[i]); + } + printf("\n"); + printf("============================================\n\n"); + } +} + +static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) +{ + printf("Lost %llu events on CPU #%d!\n", lost_cnt, cpu); +} + +static int bump_memlock_rlimit(void) +{ + struct rlimit rlim_new = { + .rlim_cur = RLIM_INFINITY, + .rlim_max = RLIM_INFINITY, + }; + + if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) { + fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit: %s\n", strerror(errno)); + fprintf(stderr, "You may need to run this program with sudo or adjust system limits.\n"); + return -1; + } + return 0; +} + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) +{ + /* Only print warnings and errors */ + if (level <= LIBBPF_WARN) + return vfprintf(stderr, format, args); + return 0; +} + +static long find_symbol_offset(const char *binary_path, const char *symbol_name) +{ + Elf *elf = NULL; + Elf_Scn *scn = NULL; + GElf_Shdr shdr; + int fd = -1; + long offset = -1; + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "Failed to init libelf\n"); + return -1; + } + + fd = open(binary_path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open %s: %s\n", binary_path, strerror(errno)); + return -1; + } + + elf = elf_begin(fd, ELF_C_READ, NULL); + if (!elf) { + fprintf(stderr, "elf_begin() failed: %s\n", elf_errmsg(-1)); + goto cleanup; + } + + while ((scn = elf_nextscn(elf, scn)) != NULL) { + if (gelf_getshdr(scn, &shdr) != &shdr) + continue; + if (shdr.sh_type != SHT_SYMTAB && shdr.sh_type != SHT_DYNSYM) + continue; + + Elf_Data *data = elf_getdata(scn, NULL); + if (!data) + continue; + + int symbols = shdr.sh_size / shdr.sh_entsize; + for (int i = 0; i < symbols; i++) { + GElf_Sym sym; + if (!gelf_getsym(data, i, &sym)) + continue; + const char *name = elf_strptr(elf, shdr.sh_link, sym.st_name); + if (!name) + continue; + if (strcmp(name, symbol_name) == 0) { + offset = sym.st_value; + goto cleanup; + } + } + } + +cleanup: + if (elf) + elf_end(elf); + if (fd >= 0) + close(fd); + + if (offset < 0) + fprintf(stderr, "Failed to locate symbol %s in %s\n", symbol_name, binary_path); + + return offset; +} + +int main(int argc, char **argv) +{ + struct bpf_object *obj; + struct bpf_program *prog_write, *prog_read_enter, *prog_read_exit; + struct bpf_link *link_write, *link_read_enter, *link_read_exit; + struct perf_buffer *pb; + int map_fd; + int err; + char *library_path = "/usr/local/lib/libwolfssl.so.44"; + pid_t target_pid = -1; // -1 means attach to all processes + + if (argc > 1) { + library_path = argv[1]; + } + + /* Set up libbpf logging callback */ + libbpf_set_print(libbpf_print_fn); + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + printf("Loading eBPF program to trace wolfSSL_write()...\n"); + + /* Check if we're running as root */ + if (geteuid() != 0) { + fprintf(stderr, "Error: This program must be run as root (use sudo).\n"); + fprintf(stderr, "Example: sudo %s\n", argv[0]); + return 1; + } + + /* Bump RLIMIT_MEMLOCK to allow BPF map creation */ + if (bump_memlock_rlimit()) { + fprintf(stderr, "Failed to increase rlimit\n"); + return 1; + } + + printf("Target library: %s\n", library_path); + printf("Attaching to function: wolfSSL_write\n"); + printf("Press Ctrl+C to stop.\n\n"); + + obj = bpf_object__open_file("wolfssl_uprobe.bpf.o", NULL); + if (libbpf_get_error(obj)) { + fprintf(stderr, "Failed to open BPF object file\n"); + return 1; + } + + err = bpf_object__load(obj); + if (err) { + fprintf(stderr, "Failed to load BPF object: %d\n", err); + goto cleanup; + } + + // Find all BPF programs + prog_write = bpf_object__find_program_by_name(obj, "trace_wolfssl_write_enter"); + if (!prog_write) { + fprintf(stderr, "Failed to find wolfSSL_write BPF program\n"); + err = 1; + goto cleanup; + } + + prog_read_enter = bpf_object__find_program_by_name(obj, "trace_wolfssl_read_enter"); + if (!prog_read_enter) { + fprintf(stderr, "Failed to find wolfSSL_read entry BPF program\n"); + err = 1; + goto cleanup; + } + + prog_read_exit = bpf_object__find_program_by_name(obj, "trace_wolfssl_read_exit"); + if (!prog_read_exit) { + fprintf(stderr, "Failed to find wolfSSL_read exit BPF program\n"); + err = 1; + goto cleanup; + } + + long write_offset = find_symbol_offset(library_path, "wolfSSL_write"); + if (write_offset < 0) { + err = 1; + goto cleanup; + } + long read_offset = find_symbol_offset(library_path, "wolfSSL_read"); + if (read_offset < 0) { + err = 1; + goto cleanup; + } + + // Attach uprobe/uretprobe to wolfSSL functions + link_write = bpf_program__attach_uprobe(prog_write, false, target_pid, library_path, + write_offset); + if (libbpf_get_error(link_write)) { + fprintf(stderr, "Failed to attach uprobe to wolfSSL_write\n"); + fprintf(stderr, "Make sure:\n"); + fprintf(stderr, " 1. The library path is correct: %s\n", library_path); + fprintf(stderr, " 2. wolfSSL_write symbol exists in the library\n"); + fprintf(stderr, " 3. You have sufficient permissions (run with sudo)\n"); + err = 1; + goto cleanup; + } + + // Attach uprobe to wolfSSL_read entry + link_read_enter = bpf_program__attach_uprobe(prog_read_enter, false, target_pid, library_path, + read_offset); + if (libbpf_get_error(link_read_enter)) { + fprintf(stderr, "Failed to attach uprobe to wolfSSL_read (entry)\n"); + err = 1; + goto cleanup_write; + } + + // Attach uretprobe to wolfSSL_read exit + link_read_exit = bpf_program__attach_uprobe(prog_read_exit, true, target_pid, library_path, + read_offset); + if (libbpf_get_error(link_read_exit)) { + fprintf(stderr, "Failed to attach uretprobe to wolfSSL_read (exit)\n"); + err = 1; + goto cleanup_read_enter; + } + + map_fd = bpf_object__find_map_fd_by_name(obj, "events"); + if (map_fd < 0) { + fprintf(stderr, "Failed to find events map\n"); + err = 1; + goto cleanup_read_exit; + } + + struct perf_buffer_opts pb_opts = { + .sample_cb = handle_event, + .lost_cb = handle_lost_events, + }; + + pb = perf_buffer__new(map_fd, 8, &pb_opts); + if (libbpf_get_error(pb)) { + fprintf(stderr, "Failed to create perf buffer\n"); + err = 1; + goto cleanup_read_exit; + } + + printf("Successfully loaded and attached eBPF programs!\n"); + printf("Monitoring wolfSSL_write() and wolfSSL_read() calls...\n\n"); + + while (running) { + err = perf_buffer__poll(pb, 100); + if (err < 0 && err != -EINTR) { + fprintf(stderr, "Error polling perf buffer: %d\n", err); + break; + } + } + + printf("\nShutting down...\n"); + + perf_buffer__free(pb); +cleanup_read_exit: + bpf_link__destroy(link_read_exit); +cleanup_read_enter: + bpf_link__destroy(link_read_enter); +cleanup_write: + bpf_link__destroy(link_write); +cleanup: + bpf_object__close(obj); + return err; +}