Files
bpf-developer-tutorial/src/54-exec-image-inspector/exec_image_inspector.c
云微 90e93e05f6 Make exec image inspection a persistent monitor (#223)
* Rebase lesson 54 as a standalone monitor

* docs: preserve lesson 54 introduction

* docs: remove design aside from lesson 54

* Simplify lesson 54 to the monitor path

---------

Co-authored-by: LinuxDev9002 <i@littlefisher.me>
2026-07-22 12:29:09 -07:00

316 lines
7.2 KiB
C

// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <bpf/libbpf.h>
#include "exec_image_inspector.h"
#include "exec_image_inspector.skel.h"
struct event_context {
unsigned long long seen;
};
static bool verbose;
static volatile sig_atomic_t exiting;
static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
va_list args)
{
if (level == LIBBPF_DEBUG && !verbose)
return 0;
return vfprintf(stderr, format, args);
}
static void usage(FILE *stream, const char *program)
{
fprintf(stream,
"Usage: %s [--verbose]\n\n"
"Continuously inspect executable images installed by exec.\n"
"Press Ctrl-C to stop and print a summary.\n\n"
"Options:\n"
" -v, --verbose print libbpf diagnostics\n"
" -h, --help show this help\n",
program);
}
static int parse_args(int argc, char **argv)
{
static const struct option options[] = {
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
{},
};
int option;
while ((option = getopt_long(argc, argv, "+vh", options, NULL)) != -1) {
switch (option) {
case 'v':
verbose = true;
break;
case 'h':
usage(stdout, argv[0]);
exit(0);
default:
return -EINVAL;
}
}
if (optind != argc) {
fprintf(stderr, "unexpected argument: %s\n", argv[optind]);
return -EINVAL;
}
return 0;
}
static void handle_signal(int signal_number)
{
(void)signal_number;
exiting = 1;
}
static int install_signal_handlers(void)
{
struct sigaction action = {
.sa_handler = handle_signal,
};
sigemptyset(&action.sa_mask);
if (sigaction(SIGINT, &action, NULL) || sigaction(SIGTERM, &action, NULL))
return -errno;
return 0;
}
static int drain_events(struct ring_buffer *ring_buffer)
{
int error;
for (;;) {
error = ring_buffer__poll(ring_buffer, 0);
if (error == -EINTR)
continue;
if (error < 0) {
fprintf(stderr, "ring-buffer drain failed: %s\n",
strerror(-error));
return error;
}
if (!error)
return 0;
}
}
static const char *elf_class_name(unsigned char value)
{
switch (value) {
case 1:
return "ELF32";
case 2:
return "ELF64";
default:
return "UNKNOWN";
}
}
static const char *elf_data_name(unsigned char value)
{
switch (value) {
case 1:
return "LSB";
case 2:
return "MSB";
default:
return "UNKNOWN";
}
}
static const char *elf_type_name(unsigned short value)
{
switch (value) {
case 2:
return "ET_EXEC";
case 3:
return "ET_DYN";
default:
return "OTHER";
}
}
static const char *elf_machine_name(unsigned short value)
{
switch (value) {
case 3:
return "EM_386";
case 62:
return "EM_X86_64";
case 183:
return "EM_AARCH64";
default:
return "OTHER";
}
}
static int handle_event(void *context, void *data, size_t size)
{
const struct exec_event *event = data;
struct event_context *events = context;
if (size < sizeof(*event)) {
fprintf(stderr, "short ring-buffer event: %zu bytes\n", size);
return 0;
}
events->seen++;
printf("EXEC pid=%u tgid=%u comm=%.*s path=%.*s is_elf=%u "
"class=%s endian=%s type=%s(%u) machine=%s(%u) "
"header_error=%d path_error=%d latency_us=%llu\n",
event->pid, event->tgid, EXEC_COMM_LEN, event->comm,
EXEC_PATH_LEN, event->path, event->is_elf,
elf_class_name(event->elf_class), elf_data_name(event->elf_data),
elf_type_name(event->elf_type), event->elf_type,
elf_machine_name(event->elf_machine), event->elf_machine,
event->header_error, event->path_error,
event->latency_ns / 1000);
fflush(stdout);
return 0;
}
static int setup_inspector(struct event_context *events,
struct exec_image_inspector_bpf **skeleton,
struct ring_buffer **ring_buffer)
{
struct exec_image_inspector_bpf *skel;
struct ring_buffer *ring;
int error;
skel = exec_image_inspector_bpf__open();
if (!skel) {
fprintf(stderr, "failed to open BPF skeleton\n");
return -ENOMEM;
}
*skeleton = skel;
error = exec_image_inspector_bpf__load(skel);
if (error) {
fprintf(stderr,
"failed to load BPF object: %s\n"
"This monitor requires Linux 6.19+, BTF, BPF JIT, "
"CONFIG_BPF_LSM=y, and an active bpf LSM.\n"
"Check the active list with: cat /sys/kernel/security/lsm\n",
strerror(-error));
return error;
}
error = exec_image_inspector_bpf__attach(skel);
if (error) {
fprintf(stderr, "failed to attach bprm_committed_creds LSM hook: %s\n",
strerror(-error));
return error;
}
ring = ring_buffer__new(bpf_map__fd(skel->maps.events), handle_event,
events, NULL);
if (!ring) {
fprintf(stderr, "failed to create ring buffer: %s\n", strerror(errno));
return errno ? -errno : -ENOMEM;
}
*ring_buffer = ring;
return 0;
}
static int monitor_execs(struct ring_buffer *ring_buffer)
{
int error;
printf("READY scope=system-wide\n");
fflush(stdout);
while (!exiting) {
error = ring_buffer__poll(ring_buffer, 100);
if (error == -EINTR)
continue;
if (error < 0) {
fprintf(stderr, "ring-buffer poll failed: %s\n", strerror(-error));
return error;
}
}
return 0;
}
static int drain_pending_events(struct ring_buffer *ring_buffer,
const struct exec_image_inspector_bpf *skel)
{
int attempts, error;
for (attempts = 0; attempts < 10; attempts++) {
if (skel->bss->stats.completed >= skel->bss->stats.scheduled)
return drain_events(ring_buffer);
error = ring_buffer__poll(ring_buffer, 100);
if (error == -EINTR)
continue;
if (error < 0) {
fprintf(stderr, "ring-buffer shutdown poll failed: %s\n",
strerror(-error));
return error;
}
}
fprintf(stderr, "timed out waiting for %llu scheduled callbacks\n",
skel->bss->stats.scheduled - skel->bss->stats.completed);
return -ETIMEDOUT;
}
static void report_result(const struct exec_image_inspector_bpf *skel,
const struct event_context *events)
{
struct inspector_stats final_stats = skel->bss->stats;
printf("SUMMARY matched=%llu scheduled=%llu schedule_errors=%llu "
"callbacks=%llu completed=%llu header_errors=%llu path_errors=%llu "
"dropped=%llu cleanup_errors=%llu events=%llu\n",
final_stats.matched, final_stats.scheduled,
final_stats.schedule_errors, final_stats.callbacks,
final_stats.completed, final_stats.header_errors,
final_stats.path_errors,
final_stats.dropped, final_stats.cleanup_errors, events->seen);
}
int main(int argc, char **argv)
{
struct exec_image_inspector_bpf *skel = NULL;
struct event_context events = {};
struct ring_buffer *ring_buffer = NULL;
int error, result = 1;
error = parse_args(argc, argv);
if (error) {
usage(stderr, argv[0]);
return 2;
}
error = install_signal_handlers();
if (error) {
fprintf(stderr, "failed to install signal handlers: %s\n",
strerror(-error));
return 1;
}
libbpf_set_print(libbpf_print_fn);
error = setup_inspector(&events, &skel, &ring_buffer);
if (error)
goto cleanup;
error = monitor_execs(ring_buffer);
exec_image_inspector_bpf__detach(skel);
if (!error)
error = drain_pending_events(ring_buffer, skel);
report_result(skel, &events);
if (!error)
result = 0;
cleanup:
ring_buffer__free(ring_buffer);
exec_image_inspector_bpf__destroy(skel);
return result;
}