Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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/
97 changes: 97 additions & 0 deletions bpf/container_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* 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>

/* 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 */
189 changes: 189 additions & 0 deletions bpf/container_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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;

/* simple dedup by host path */
char seen[MAX_CONTAINER_LIBS][PATH_MAX];
int seen_count = 0;
Comment thread
yoloyyh marked this conversation as resolved.
Outdated

proc_dir = opendir("/proc");
if (!proc_dir)
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);

entries[count].pid = pid;
strncpy(entries[count].lib_path, path, PATH_MAX);
Comment thread
yoloyyh marked this conversation as resolved.
Outdated
count++;

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

closedir(proc_dir);
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
Loading