Skip to content

Commit 90f3a1e

Browse files
committed
Add eBPF examples
This commit adds two examples: 1. Add eBPF syscall-write-trace example Includes TCP client/server demo, write() syscall eBPF tracer, Makefile, and full README detailing problem, solution, architecture, and usage. 2. eBPF: add trace wolfSSL_write() and wolfSSL_read() using eBPF uprobes. Includes: - TLS client and server examples - eBPF programs for write/read entry and read return - userspace loader with perf buffer handling - automatic symbol lookup (no hardcoded offsets) - x86_64 and ARM64 register handling - full README with usage, architecture, and explanation Shows how to observe TLS plaintext inside applications without modifying wolfSSL or application code. Signed-off-by: sameeh.jubran <sameeh@wolfssl.com>
1 parent 1b5089b commit 90f3a1e

15 files changed

Lines changed: 2258 additions & 0 deletions

ebpf/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## wolfSSL eBPF Examples
2+
3+
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**.
4+
5+
These examples are designed for:
6+
7+
* **debugging**
8+
* **education**
9+
* **performance tracing**
10+
* **understanding TLS internals**
11+
* **research and experimentation**
12+
13+
All examples require:
14+
15+
* Linux kernel with eBPF support (4.19+ recommended)
16+
* clang/LLVM (for compiling `.bpf.c` programs)
17+
* libbpf and libelf
18+
* root privileges
19+
* A recent installation of wolfSSL if running TLS examples
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Binaries
2+
client-tcp
3+
server-tcp
4+
write_tracer
5+
*.o
6+
*.bpf.o
7+
*.log
8+
9+
# Editor files
10+
*~
11+
*.swp
12+
.DS_Store
13+

ebpf/syscall-write-trace/Makefile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
CC = gcc
2+
CLANG = clang
3+
4+
CFLAGS = -O2 -g -Wall
5+
BPF_CFLAGS = -O2 -g -target bpf -D__TARGET_ARCH_$(ARCH)
6+
7+
# Detect architecture for correct include path
8+
ARCH := $(shell uname -m)
9+
ifeq ($(ARCH),x86_64)
10+
ARCH_DIR = x86_64-linux-gnu
11+
else ifeq ($(ARCH),aarch64)
12+
ARCH_DIR = aarch64-linux-gnu
13+
else
14+
ARCH_DIR = x86_64-linux-gnu
15+
endif
16+
17+
BPF_INCLUDES = -I/usr/include -I/usr/include/$(ARCH_DIR)
18+
LIBBPF_LIBS = -lbpf -lelf -lz
19+
20+
TARGETS = write_tracer write_tracer.bpf.o client-tcp server-tcp
21+
22+
.PHONY: all clean help
23+
24+
all: $(TARGETS)
25+
26+
write_tracer.bpf.o: write_tracer.bpf.c
27+
$(CLANG) $(BPF_CFLAGS) $(BPF_INCLUDES) -c $< -o $@
28+
29+
write_tracer: write_tracer.c write_tracer.bpf.o
30+
$(CC) $(CFLAGS) write_tracer.c -lelf -lz -lbpf -o write_tracer
31+
32+
client-tcp: client-tcp.c
33+
$(CC) $(CFLAGS) client-tcp.c -o client-tcp
34+
35+
server-tcp: server-tcp.c
36+
$(CC) $(CFLAGS) server-tcp.c -o server-tcp
37+
38+
clean:
39+
rm -f *.o write_tracer client-tcp server-tcp
40+
41+
help:
42+
@echo "Targets:"
43+
@echo " all - Build syscall tracer and TCP demo apps"
44+
@echo " clean - Remove binaries"
45+
@echo ""
46+
@echo "Instructions:"
47+
@echo " 1. sudo ./write_tracer"
48+
@echo " 2. ./server-tcp"
49+
@echo " 3. ./client-tcp 127.0.0.1"

ebpf/syscall-write-trace/README.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# syscall-write-trace
2+
3+
### eBPF Example: Tracing Plaintext at the `write()` Syscall Boundary
4+
5+
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**.
6+
It uses a simple TCP client/server pair to generate predictable network writes and an eBPF tracepoint program to observe them.
7+
8+
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.
9+
10+
---
11+
12+
# 📌 **Problem**
13+
14+
When debugging network applications, especially TLS applications, developers often want to inspect:
15+
16+
* What plaintext is being written
17+
* What data is being sent to the network
18+
* Whether buffers contain what we expect
19+
* Whether the application or kernel is modifying data
20+
* Whether the problem is at the app layer, kernel layer, or crypto layer
21+
22+
However, once an application calls:
23+
24+
```
25+
write(fd, buffer, count)
26+
```
27+
28+
the kernel:
29+
30+
* does **not** expose the plaintext
31+
* may buffer or coalesce writes
32+
* may encrypt data (TLS offload, QUIC, etc.)
33+
* hides memory from tools
34+
* provides no visibility into the user buffer
35+
36+
Traditional debugging tools (tcpdump, Wireshark, strace) **cannot see the plaintext** before encryption or kernel processing.
37+
38+
This creates a visibility gap.
39+
40+
---
41+
42+
# 🎯 **Solution**
43+
44+
We attach an **eBPF tracepoint** to:
45+
46+
```
47+
tracepoint/syscalls/sys_enter_write
48+
```
49+
50+
This gives us:
51+
52+
* access to the syscall arguments
53+
* the calling process’s PID/TID
54+
* the file descriptor
55+
* the byte count
56+
* the raw user pointer to the data
57+
* ability to read the plaintext with `bpf_probe_read_user()`
58+
59+
The eBPF program:
60+
61+
1. Filters events only from a target process (`client-tcp`)
62+
2. Copies up to 255 bytes of user-space buffer safely
63+
3. Sends them to user-space via a perf buffer
64+
4. The userspace loader prints ASCII and hex output
65+
66+
This provides **perfect visibility** into the plaintext leaving the application.
67+
68+
---
69+
70+
# 🧩 **Architecture**
71+
72+
```
73+
TCP Client App (user space)
74+
|
75+
| 1. call write(fd, buf, count)
76+
v
77+
┌──────────────────────────────┐
78+
│ tracepoint: sys_enter_write │ ← eBPF hook runs BEFORE write executes
79+
│ eBPF program: │
80+
│ - filters process name │
81+
│ - reads buffer from user mem │
82+
│ - emits event via perf buf │
83+
└───────────────┬──────────────┘
84+
|
85+
| 2. event (plaintext)
86+
v
87+
Userspace Loader (write_tracer)
88+
--------------------------------
89+
- loads BPF program
90+
- attaches tracepoint
91+
- opens perf buffer
92+
- prints plaintext
93+
|
94+
| 3. human-readable output
95+
v
96+
Terminal
97+
```
98+
99+
---
100+
101+
# 🔍 **Detailed Walkthrough**
102+
103+
## 1. The Sample Applications
104+
105+
### `server-tcp.c`
106+
107+
A simple TCP echo server on port 11111:
108+
109+
* waits for a connection
110+
* receives a message
111+
* prints it
112+
* echoes back a canned response
113+
* loops
114+
115+
### `client-tcp.c`
116+
117+
A matching TCP client:
118+
119+
* prompts user for input
120+
* writes it to the server
121+
* prints server response
122+
123+
These programs provide **predictable write() calls** for tracing.
124+
125+
---
126+
127+
## 2. The eBPF Program: `write_tracer.bpf.c`
128+
129+
Hooks:
130+
131+
```
132+
tracepoint/syscalls/sys_enter_write
133+
```
134+
135+
Key details:
136+
137+
### ✔ Event Filtering
138+
139+
Checks the process name (`comm`) to avoid tracing all processes.
140+
141+
### ✔ Safe Memory Access
142+
143+
Uses:
144+
145+
```
146+
bpf_probe_read_user()
147+
```
148+
149+
to copy user memory safely, limited to 255 bytes (verifier-friendly bound).
150+
151+
### ✔ Perf Buffer Emission
152+
153+
Writes events to a ring buffer consumed by user-space.
154+
155+
### ✔ Struct of event data
156+
157+
Contains:
158+
159+
* PID, TID
160+
* FD
161+
* count
162+
* process name
163+
* captured data
164+
165+
---
166+
167+
## 3. The Userspace Loader: `write_tracer.c`
168+
169+
It:
170+
171+
1. Raises RLIMIT_MEMLOCK
172+
2. Loads `write_tracer.bpf.o`
173+
3. Attaches the tracepoint
174+
4. Opens perf buffer
175+
5. Pretty-prints events:
176+
177+
```
178+
=== WRITE SYSCALL INTERCEPTED ===
179+
Process: client-tcp (PID: 1234)
180+
FD: 3
181+
Count: 13 bytes
182+
Data: "hello world!"
183+
Hex: 68 65 6c ...
184+
```
185+
186+
This gives full plaintext visibility.
187+
188+
---
189+
190+
# 🚀 **How to Build**
191+
192+
Dependencies (Ubuntu):
193+
194+
```bash
195+
sudo apt install clang llvm libbpf-dev libelf-dev zlib1g-dev build-essential
196+
```
197+
198+
Then:
199+
200+
```bash
201+
make
202+
```
203+
204+
This compiles:
205+
206+
* TCP client/server
207+
* eBPF program (`write_tracer.bpf.o`)
208+
* userspace loader (`write_tracer`)
209+
210+
---
211+
212+
# ▶️ **How to Run**
213+
214+
### 1. Terminal #1 — Start the tracer
215+
216+
```bash
217+
sudo ./write_tracer
218+
```
219+
220+
### 2. Terminal #2 — Start the TCP server
221+
222+
```bash
223+
./server-tcp
224+
```
225+
226+
### 3. Terminal #3 — Run client and type message
227+
228+
```bash
229+
./client-tcp 127.0.0.1
230+
```
231+
232+
Tracer output:
233+
234+
```
235+
=== WRITE SYSCALL INTERCEPTED ===
236+
Process: client-tcp
237+
File Descriptor: 3
238+
Write Count: 13 bytes
239+
Data (first 13 bytes): hello world!
240+
```
241+
242+
---
243+
244+
# 🎁 **Benefits of This Example**
245+
246+
### ✔ Shows how to read plaintext before kernel/network/TLS processing
247+
248+
### ✔ Demonstrates safe buffer access in eBPF
249+
250+
### ✔ Demonstrates filtering (PID, process name)
251+
252+
### ✔ Teaches core eBPF concepts: tracepoints, perf buffers, verifier constraints
253+
254+
### ✔ Foundation for more advanced examples (TLS plaintext, handshake tracing)
255+
256+
### ✔ Helps wolfSSL developers debug TLS behavior
257+
258+
### ✔ Useful for application developers integrating wolfSSL
259+
260+
---
261+
262+
# ⚙️ **Nitty-Gritty Details**
263+
264+
### 1. Why tracepoint instead of kprobe?
265+
266+
Tracepoints are:
267+
268+
* stable
269+
* argument offsets fixed
270+
* preferred for syscall entry tracing
271+
272+
Allows verifier to analyze program more easily.
273+
274+
### 2. Why use process name filtering?
275+
276+
Without it, the tracer prints:
277+
278+
* output from bash
279+
* systemd
280+
* everything reading/writing
281+
282+
Filtering avoids noise.
283+
284+
### 3. Why limit buffer to 255 bytes?
285+
286+
Verifier restrictions require fixed bounded copy sizes.
287+
A 255-byte buffer is safe and sufficient for demos.
288+
289+
### 4. Why use perf buffer instead of ringbuf?
290+
291+
Perf buffer is more compatible with older kernels (e.g., Ubuntu LTS).
292+
Perfect for examples.
293+
294+
### 5. Why use simple TCP client/server?
295+
296+
Consistent, predictable write() calls make tracing easy to demo.

0 commit comments

Comments
 (0)