Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ record.log.*
# Log files
*.log
docs/blog

node_modules/
99 changes: 99 additions & 0 deletions bpf/container_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* container_info.h — Userspace helpers for container metadata.
* Provides ns_pid and container_id extraction from /proc.
* Shared by process_new.c and sslsniff.c.
*/
#ifndef __CONTAINER_INFO_H
#define __CONTAINER_INFO_H
Comment thread
yoloyyh marked this conversation as resolved.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

/* Read namespace PID from /proc/<pid>/status NSpid line.
* Returns the innermost namespace PID, or -1 if not in a namespace. */
static int get_ns_pid(pid_t host_pid)
{
char path[64];
Comment thread
yoloyyh marked this conversation as resolved.
char line[256];
snprintf(path, sizeof(path), "/proc/%d/status", host_pid);
FILE *f = fopen(path, "r");
if (!f)
return -1;
int ns_pid = -1;
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, "NSpid:", 6) == 0) {
char *p = line + 6;
int last_pid = -1;
int count = 0;
while (*p) {
while (*p == '\t' || *p == ' ')
p++;
if (*p >= '0' && *p <= '9') {
last_pid = (int)strtol(p, &p, 10);
count++;
} else {
break;
}
}
if (count >= 2)
ns_pid = last_pid;
break;
}
}
fclose(f);
return ns_pid;
}

/* Read container ID from /proc/<pid>/cgroup (docker/containerd format).
* Writes short (12-char) container ID to out. Returns 0 on success. */
static int get_container_id(pid_t host_pid, char *out, size_t out_len)
{
char path[64];
char line[512];
snprintf(path, sizeof(path), "/proc/%d/cgroup", host_pid);
FILE *f = fopen(path, "r");
if (!f)
return -1;
out[0] = '\0';
while (fgets(line, sizeof(line), f)) {
char *p = line;
while (*p) {
int hex_len = 0;
char *start = p;
while ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f')) {
hex_len++;
p++;
}
if (hex_len == 64) {
int copy_len = (int)out_len - 1 < 12 ? (int)out_len - 1 : 12;
memcpy(out, start, copy_len);
out[copy_len] = '\0';
fclose(f);
return 0;
}
if (hex_len == 0)
p++;
}
}
fclose(f);
return -1;
}

/* Print container JSON fields: ,\"ns_pid\":N,\"container_id\":\"xxx\"
* Prints nothing if not in a container. */
static void print_container_fields(pid_t host_pid)
{
int ns_pid = get_ns_pid(host_pid);
if (ns_pid > 0) {
printf(",\"ns_pid\":%d", ns_pid);
char container_id[16];
if (get_container_id(host_pid, container_id, sizeof(container_id)) == 0 &&
container_id[0] != '\0') {
printf(",\"container_id\":\"%s\"", container_id);
}
}
}

#endif /* __CONTAINER_INFO_H */
201 changes: 201 additions & 0 deletions bpf/container_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
// Container-aware library path resolution for uprobe attachment.
// Resolves SSL library paths inside container mount namespaces
// via /proc/<pid>/maps and /proc/<pid>/root/.

#ifndef __CONTAINER_UTILS_H
#define __CONTAINER_UTILS_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <limits.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/types.h>
#include <bpf/libbpf.h>

#ifndef PATH_MAX
#define PATH_MAX 4096
#endif

#define MAX_CONTAINER_LIBS 64

/* ---------- data structures ---------- */

struct pid_lib_entry {
pid_t pid;
char lib_path[PATH_MAX]; /* host-perspective path */
};

struct dynamic_links {
struct bpf_link **links;
int count;
int capacity;
};

/* ---------- dynamic link management ---------- */

static struct dynamic_links g_extra_links = {
.links = NULL, .count = 0, .capacity = 0
};

static void add_dynamic_link(struct bpf_link *link)
{
if (!link)
return;
if (g_extra_links.count >= g_extra_links.capacity) {
int new_cap = g_extra_links.capacity ? g_extra_links.capacity * 2 : 16;
struct bpf_link **tmp = realloc(g_extra_links.links,
new_cap * sizeof(struct bpf_link *));
if (!tmp) {
fprintf(stderr, "realloc dynamic_links failed\n");
return;
}
g_extra_links.links = tmp;
g_extra_links.capacity = new_cap;
}
g_extra_links.links[g_extra_links.count++] = link;
}

static void cleanup_dynamic_links(void)
{
for (int i = 0; i < g_extra_links.count; i++)
bpf_link__destroy(g_extra_links.links[i]);
free(g_extra_links.links);
g_extra_links.links = NULL;
g_extra_links.count = 0;
g_extra_links.capacity = 0;
}

/* ---------- container library path resolution ---------- */

/*
* Find the host-perspective path of a library loaded by a specific PID.
*
* For container processes the in-container path (e.g. /usr/lib/libssl.so.3)
* is translated to /proc/<pid>/root/usr/lib/libssl.so.3 so that
* bpf_program__attach_uprobe_opts() can locate the correct ELF on the host
* filesystem.
*/
static char *find_library_path_for_pid(pid_t pid, const char *libname,
bool verbose)
{
static char host_path[PATH_MAX];
char maps_path[64];
char line[4096];
FILE *fp;

snprintf(maps_path, sizeof(maps_path), "/proc/%d/maps", pid);
fp = fopen(maps_path, "r");
if (!fp) {
if (verbose)
fprintf(stderr, "Failed to open %s: %s\n",
maps_path, strerror(errno));
return NULL;
}

while (fgets(line, sizeof(line), fp)) {
/* only consider executable/read-only mappings with matching lib name */
if (strstr(line, libname) == NULL)
continue;
if (strstr(line, "r-xp") == NULL && strstr(line, "r--p") == NULL)
continue;

char *path = strchr(line, '/');
if (!path)
continue;

char *nl = strchr(path, '\n');
if (nl)
*nl = '\0';

/* convert to host-perspective path via /proc/<pid>/root */
snprintf(host_path, sizeof(host_path), "/proc/%d/root%s", pid, path);

if (access(host_path, R_OK) == 0) {
fclose(fp);
if (verbose)
fprintf(stderr,
"Found %s for PID %d: %s -> %s\n",
libname, pid, path, host_path);
return host_path;
}
}

fclose(fp);
return NULL;
}

/*
* Scan /proc for all processes that have loaded `libname`, returning
* deduplicated (by host path) entries.
*/
static int find_pids_with_library(const char *libname,
struct pid_lib_entry *entries,
int max_entries,
bool verbose)
{
DIR *proc_dir;
struct dirent *ent;
int count = 0;

/* Heap-allocated dedup array to avoid 256KB stack allocation */
char (*seen)[PATH_MAX] = calloc(MAX_CONTAINER_LIBS, PATH_MAX);
int seen_count = 0;

if (!seen) {
fprintf(stderr, "Failed to allocate dedup buffer\n");
return 0;
}

proc_dir = opendir("/proc");
if (!proc_dir) {
free(seen);
return 0;
}

while ((ent = readdir(proc_dir)) && count < max_entries) {
pid_t pid = atoi(ent->d_name);
if (pid <= 0)
continue;

char *path = find_library_path_for_pid(pid, libname, false);
if (!path)
continue;

/* dedup */
bool dup = false;
for (int i = 0; i < seen_count; i++) {
if (strcmp(seen[i], path) == 0) {
dup = true;
break;
}
}
if (dup)
continue;

if (seen_count < MAX_CONTAINER_LIBS) {
strncpy(seen[seen_count], path, PATH_MAX - 1);
seen[seen_count][PATH_MAX - 1] = '\0';
seen_count++;
}

entries[count].pid = pid;
strncpy(entries[count].lib_path, path, PATH_MAX - 1);
entries[count].lib_path[PATH_MAX - 1] = '\0';
count++;

if (verbose)
fprintf(stderr, "Discovered container SSL lib: %s (via PID %d)\n",
path, pid);
}

closedir(proc_dir);
free(seen);
return count;
}

#endif /* __CONTAINER_UTILS_H */
Binary file added bpf/process
Binary file not shown.
41 changes: 29 additions & 12 deletions bpf/process.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,26 +104,43 @@ int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
unsigned long arg_end = BPF_CORE_READ(mm, arg_end);
unsigned long arg_len = arg_end - arg_start;

/* Limit to buffer size */
if (arg_len > MAX_COMMAND_LEN - 1)
arg_len = MAX_COMMAND_LEN - 1;

/* Read command line from userspace memory */
if (arg_len > 0) {
long ret = bpf_probe_read_user_str(&e->full_command, arg_len + 1, (void *)arg_start);
/*
* Read the full argv block using bpf_probe_read_user (not _str).
* _str stops at first \0 and only captures argv[0].
* _user reads raw bytes: "chmod\0+x\0/path\0" -- we get all args.
*
* We always read exactly MAX_COMMAND_LEN-1 bytes (a compile-time
* constant) so that BPF verifiers on all kernel versions can
* prove the access is bounded. This may read past arg_end into
* environment variables, but userspace trims to arg_len.
*
* NO LOOPS in BPF -- all post-processing (\0->space, trimming)
* is done in userspace to stay within the verifier instruction
* limit on kernel 5.15 (1,000,000 insns).
*/
long ret = bpf_probe_read_user(e->full_command,
MAX_COMMAND_LEN - 1,
(void *)arg_start);
if (ret < 0) {
/* Fallback to just comm if we can't read cmdline */
bpf_probe_read_kernel_str(&e->full_command, sizeof(e->full_command), e->comm);
bpf_probe_read_kernel_str(e->full_command,
sizeof(e->full_command),
e->comm);
e->full_command[MAX_COMMAND_LEN - 1] = '\0';
} else {
/* Replace null bytes with spaces for readability */
for (int i = 0; i < MAX_COMMAND_LEN - 1 && i < ret - 1; i++) {
if (e->full_command[i] == '\0')
e->full_command[i] = ' ';
}
e->full_command[MAX_COMMAND_LEN - 1] = '\0';
}
/* Store actual arg_len in exit_code for userspace trimming.
* exec events don't use exit_code, so this field is free. */
arg_len &= (MAX_COMMAND_LEN - 1);
e->exit_code = (unsigned)arg_len;
} else {
/* No arguments, use comm */
bpf_probe_read_kernel_str(&e->full_command, sizeof(e->full_command), e->comm);
bpf_probe_read_kernel_str(e->full_command,
sizeof(e->full_command), e->comm);
e->exit_code = 0;
}

/* successfully submit it to user-space for post-processing */
Expand Down
4 changes: 2 additions & 2 deletions bpf/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz)
printf("\"pid\":%d,", e->pid);
printf("\"ppid\":%d", e->ppid);
printf(",\"filename\":\"%s\"", e->filename);
printf(",\"full_command\":\"%s\"", e->full_command);
printf(",\"full_command\":\"%s\"", postprocess_full_command(e->full_command, MAX_COMMAND_LEN, e->exit_code));
printf("}\n");
fflush(stdout);
} else if (tracker->filter_mode == FILTER_MODE_FILTER) {
Expand All @@ -553,7 +553,7 @@ static int handle_event(void *ctx, void *data, size_t data_sz)
printf("\"pid\":%d,", e->pid);
printf("\"ppid\":%d", e->ppid);
printf(",\"filename\":\"%s\"", e->filename);
printf(",\"full_command\":\"%s\"", e->full_command);
printf(",\"full_command\":\"%s\"", postprocess_full_command(e->full_command, MAX_COMMAND_LEN, e->exit_code));
printf("}\n");
fflush(stdout);
}
Expand Down
Loading