From 2278df1493e064c197913e49b5d1935942d83448 Mon Sep 17 00:00:00 2001 From: daniel Date: Tue, 6 May 2025 16:57:32 -0700 Subject: initial import --- src/proc_ledger.c | 344 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 src/proc_ledger.c (limited to 'src/proc_ledger.c') diff --git a/src/proc_ledger.c b/src/proc_ledger.c new file mode 100644 index 0000000..9faf2f6 --- /dev/null +++ b/src/proc_ledger.c @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#include "agent_context.h" +#include "proc_ledger.h" +#include "hash_ledger.h" +#include "output.h" +#include "error.h" +#include "json.h" +#include "proc.h" +#include "md5.h" +#include "sha256.h" +#include "time_common.h" + +// TODO these should probably be moved into agent context +extern unsigned long boot_time; +extern unsigned long maxsize; +extern long ticks; + + +bool proc_has_tty(pid_t pid) { + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "/proc/%d/fd/0", pid); + + struct stat sb; + if (stat(path, &sb) == -1) { + return false; + } + + return S_ISCHR(sb.st_mode); +} + +struct proc_ledger *proc_ledger_init(size_t num_buckets) { + struct proc_ledger *ledger = calloc(1, sizeof(struct proc_ledger)); + if (!ledger) { + error_fatal("ledger calloc: %s", strerror(errno)); + } + + ledger->buckets = calloc(num_buckets, sizeof(struct proc_ledger_entry *)); + if (!ledger->buckets) { + free(ledger); + error_fatal("proc ledger buckets calloc: %s", strerror(errno)); + } + + ledger->num_buckets = num_buckets; + + //if (pthread_mutex_init(&ledger->lock, NULL) != 0) { + // error("pthread_mutex_init: %s", strerror(errno)); + // free(ledger->buckets); + // free(ledger); + // exit(EXIT_FAILURE); + //} + + return ledger; +} + +void proc_ledger_destroy(struct proc_ledger *ledger) { + if (!ledger) { + return; + } + + for (size_t i = 0; i < ledger->num_buckets; i++) { + struct proc_ledger_entry *entry = ledger->buckets[i]; + while(entry) { + struct proc_ledger_entry *next = entry->next; + free(entry); + entry = next; + } + } + + free(ledger->buckets); + //pthread_mutex_destroy(&ledger->lock); + free(ledger); +} + +size_t proc_ledger_bucket(struct proc_ledger *ledger, pid_t pid) { + return ((size_t)pid) % ledger->num_buckets; +} + +bool proc_ledger_add(struct proc_ledger *ledger, struct proc_ledger_entry *new_entry) { + if (!ledger || !new_entry) { + return false; + } + + //pthread_mutex_lock(&ledger->lock); + + size_t idx = proc_ledger_bucket(ledger, new_entry->pid); + new_entry->next = ledger->buckets[idx]; + ledger->buckets[idx] = new_entry; + + //pthread_mutex_unlock(&ledger->lock); + + return true; +} + +bool proc_ledger_remove(struct proc_ledger *ledger, pid_t pid) { + if (!ledger) { + return false; + } + + //pthread_mutex_lock(&ledger->lock); + + size_t idx = proc_ledger_bucket(ledger, pid); + struct proc_ledger_entry *prev = NULL; + struct proc_ledger_entry *entry = ledger->buckets[idx]; + + while (entry) { + if (entry->pid == pid) { + if (prev) { + prev->next = entry->next; + } else { + ledger->buckets[idx] = entry->next; + } + + free(entry); + //pthread_mutex_unlock(&ledger->lock); + return true; + } + + prev = entry; + entry = entry->next; + } + + //pthread_mutex_unlock(&ledger->lock); + return false; // not found +} + +bool proc_ledger_replace(struct proc_ledger *ledger, struct proc_ledger_entry *new_entry) { + if (!ledger || !new_entry) { + return false; + } + + size_t idx = proc_ledger_bucket(ledger, new_entry->pid); + struct proc_ledger_entry **cur = &ledger->buckets[idx]; + + while (*cur) { + if ((*cur)->pid == new_entry->pid) { + new_entry->next = (*cur)->next; + free(*cur); + *cur = new_entry; + return true; + } + + cur = &(*cur)->next; + } + + // replace if found, otherwise add new entry to bucket + new_entry->next = ledger->buckets[idx]; + ledger->buckets[idx] = new_entry; + + return true; +} + +struct proc_ledger_entry *proc_ledger_find(struct proc_ledger *ledger, pid_t pid) { + size_t idx = proc_ledger_bucket(ledger, pid); + struct proc_ledger_entry *entry = ledger->buckets[idx]; + + if (!entry) { + return NULL; + } + + if (entry->pid == pid) { + return entry; + } + + // search list if necessary + for (entry = entry->next; entry; entry = entry->next) { + if (entry->pid == pid) { + return entry; + } + } + + return NULL; // not found +} + +struct proc_ledger_entry *proc_ledger_entry_create(pid_t pid, agent_context_t *ctx) { + struct proc_status ps = proc_get_status(pid); + if (ps.pid == -1) { // couldn't fetch status. + return NULL; + } + + struct proc_stat pstat = proc_parse_stat(pid); // TODO rename this? + + struct proc_ledger_entry *entry = calloc(1, sizeof(struct proc_ledger_entry)); + if (!entry) { + return NULL; + } + + entry->pid = ps.pid; + entry->tgid = ps.tgid; + entry->ppid = ps.ppid; + entry->uid = ps.uid; + entry->euid = ps.euid; + entry->gid = ps.gid; + entry->egid = ps.egid; + entry->start_time = boot_time + (pstat.starttime / ticks); + + char *exe_path = proc_get_exe_path(pid); + if (exe_path) { + strncpy(entry->exe, exe_path, sizeof(entry->exe) - 1); + entry->exe[sizeof(entry->exe) - 1] = '\0'; + + struct stat sb; + if (stat(exe_path, &sb) == 0 && sb.st_size < maxsize) { + hash_ledger_add_or_update(ctx->hash_ledger, exe_path, &sb); + } + } + + char *cmdline = proc_get_cmdline(pid); + if (cmdline) { + strncpy(entry->cmdline, cmdline, sizeof(entry->cmdline) - 1); + entry->cmdline[sizeof(entry->cmdline) - 1] = '\0'; + } + + char *cwd = proc_cwd(pid); + if (cwd) { + strncpy(entry->cwd, cwd, sizeof(entry->cwd) - 1); + entry->cwd[sizeof(entry->cwd) - 1] = '\0'; + } + + strncpy(entry->comm, ps.name, sizeof(entry->comm) - 1); + entry->comm[sizeof(entry->comm) - 1] = '\0'; + + entry->daemonized = (ps.ppid == 1); // child of init + entry->is_traced = (ps.tracer_pid != 0); // being ptraced + entry->tracer_pid = ps.tracer_pid; + entry->state = ps.state; + entry->seccomp = ps.seccomp; + entry->cap_eff = ps.cap_eff; + entry->threads = ps.threads; + entry->has_tty = proc_has_tty(pid); + + return entry; +} + +void proc_ledger_hydrate(agent_context_t *ctx) { + DIR *proc = opendir("/proc"); + struct dirent *de; + + if (!proc) { + error("opendir /proc: %s", strerror(errno)); + return; + } + + while ((de = readdir(proc)) != NULL) { + pid_t pid = atoi(de->d_name); + if (pid <= 0) { + continue; + } + + struct proc_ledger_entry *entry = proc_ledger_entry_create(pid, ctx); + if (!entry) { // TODO should this have a visible error? + continue; + } + + proc_ledger_add(ctx->proc_ledger, entry); + + struct stat sb; + if (entry->exe[0] && stat(entry->exe, &sb) == 0 && sb.st_size < maxsize) { + hash_ledger_add_or_update(ctx->hash_ledger, entry->exe, &sb); + } + + json_t json = proc_ledger_entry_to_json(entry, "hydrate", ctx); + output(json_get(&json)); + json_free(&json); + } + + closedir(proc); +} + +json_t proc_ledger_entry_to_json(struct proc_ledger_entry *entry, + const char *event_type, + struct agent_context *ctx) { + json_t buf = {0}; + json_init(&buf); + json_start_object(&buf); + + json_add_double(&buf, "timestamp", timestamp()); + json_add_string(&buf, "hostname", ctx->hostname); + json_add_string(&buf, "event_type", event_type); + json_add_int(&buf, "pid", entry->pid); + json_add_int(&buf, "ppid", entry->ppid); + json_add_int(&buf, "tgid", entry->tgid); + + if (entry->exe[0]) { + json_add_string(&buf, "exepath", entry->exe); + + struct hash_entry *he = hash_ledger_find(ctx->hash_ledger, entry->exe); + if (he) { + json_add_string_or_null(&buf, "md5", he->md5); + json_add_string_or_null(&buf, "sha256", he->sha256); + json_add_double(&buf, "entropy", he->entropy); + } + } else { + json_add_null(&buf, "exepath"); + json_add_null(&buf, "md5"); + json_add_null(&buf, "sha256"); + json_add_null(&buf, "entropy"); + } + + json_add_string_or_null(&buf, "comm", entry->comm); + json_add_string_or_null(&buf, "cmdline", entry->cmdline); + json_add_string_or_null(&buf, "cwd", entry->cwd); + + json_add_int(&buf, "uid", entry->uid); + json_add_int(&buf, "euid", entry->euid); + json_add_int(&buf, "gid", entry->gid); + json_add_int(&buf, "egid", entry->egid); + + json_add_int64(&buf, "start_time", entry->start_time); + json_add_int64(&buf, "cpu_user_ticks", entry->cpu_user_ticks); + json_add_int64(&buf, "cpu_kernel_ticks", entry->cpu_kernel_ticks); + json_add_int64(&buf, "rss_bytes", entry->rss); + json_add_int64(&buf, "vsize_bytes", entry->vsize); + + if (entry->daemonized) { + json_add_bool(&buf, "daemonized", entry->daemonized); + } + if (entry->is_traced) { + json_add_bool(&buf, "is_traced", entry->is_traced); + } + + json_add_int(&buf, "tracer_pid", entry->tracer_pid); + + char state[2] = { entry->state, '\0' }; + json_add_string(&buf, "state", state); + + json_add_int(&buf, "seccomp", entry->seccomp); + json_add_uint64(&buf, "cap_eff", entry->cap_eff); + json_add_int(&buf, "threads", entry->threads); + json_add_bool(&buf, "has_tty", entry->has_tty); + + json_end_object(&buf); + + return buf; +} -- cgit v1.2.3