From 5db6b44b652790c63cbcf90b86728db5934c01d8 Mon Sep 17 00:00:00 2001 From: Mario Hewardt Date: Wed, 7 Jan 2026 16:18:11 -0800 Subject: [PATCH 1/2] Add EBPF event --- doc/08_adding_new_events.md | 699 +++++++++++++++++++++++++++++ ebpfKern/sysmonBPFLoad.c | 248 ++++++++++ ebpfKern/sysmonBPFLoad_rawtp.c | 74 +++ ebpfKern/sysmonBPFLoad_tp.c | 68 +++ ebpfKern/sysmonEBPFkern4.15.c | 1 + ebpfKern/sysmonEBPFkern4.16.c | 1 + ebpfKern/sysmonEBPFkern4.17-5.1.c | 1 + ebpfKern/sysmonEBPFkern5.2.c | 1 + ebpfKern/sysmonEBPFkern5.3-5.5.c | 1 + ebpfKern/sysmonEBPFkern5.6-.c | 1 + linuxTypes.h | 22 + sysmonCommon | 2 +- sysmonLogView/sysmonGetEventName.c | 12 +- sysmonforlinux.c | 45 +- 14 files changed, 1169 insertions(+), 7 deletions(-) create mode 100644 doc/08_adding_new_events.md create mode 100644 ebpfKern/sysmonBPFLoad.c create mode 100644 ebpfKern/sysmonBPFLoad_rawtp.c create mode 100644 ebpfKern/sysmonBPFLoad_tp.c diff --git a/doc/08_adding_new_events.md b/doc/08_adding_new_events.md new file mode 100644 index 0000000..fc3a82e --- /dev/null +++ b/doc/08_adding_new_events.md @@ -0,0 +1,699 @@ +# Adding New Events to Sysmon for Linux + +This guide provides a comprehensive, step-by-step walkthrough for adding new events to Sysmon for Linux. + +## Table of Contents + +1. [Overview](#overview) +2. [Architecture Understanding](#architecture-understanding) +3. [Step 1: Define the Event in manifest.xml](#step-1-define-the-event-in-manifestxml) +4. [Step 2: Define Data Structures in linuxTypes.h](#step-2-define-data-structures-in-linuxtypesh) +5. [Step 3: Create eBPF Kernel Programs](#step-3-create-ebpf-kernel-programs) +6. [Step 4: Register Syscall Handlers](#step-4-register-syscall-handlers) +7. [Step 5: Process Events in Userspace](#step-5-process-events-in-userspace) +8. [Step 6: Format Event Output](#step-6-format-event-output) +9. [Step 7: Enable Event via Configuration](#step-7-enable-event-via-configuration) +10. [Step 8: Build and Test](#step-8-build-and-test) + +--- + +## Overview + +Adding a new event to Sysmon for Linux requires modifications across multiple layers: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Space │ +│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │ +│ │ manifest.xml│ │eventsCommon. │ │ sysmonforlinux.c │ │ +│ │ (event def) │ │cpp (format) │ │ (event dispatch) │ │ +│ └─────────────┘ └──────────────┘ └────────────────────┘ │ +├─────────────────────────────────────────────────────────────┤ +│ Perf Ring Buffer │ +├─────────────────────────────────────────────────────────────┤ +│ Kernel Space │ +│ ┌─────────────────────────────────────────────────────────┐│ +│ │ eBPF Programs (sysmon*_rawtp.c) ││ +│ │ Attached to syscall tracepoints ││ +│ └─────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Architecture Understanding + +### Event Flow + +1. **Syscall occurs** → eBPF program attached to tracepoint fires +2. **eBPF program** → Collects data, populates event structure, sends via perf buffer +3. **Userspace handler** → Receives event from perf buffer +4. **Event processing** → Transforms/enriches data if needed +5. **Event formatting** → Converts to XML for syslog output +6. **Rules engine** → Filters based on configuration + +### Key Concepts + +#### Synthetic Syscall Numbers +For events triggered by tracepoints (not syscalls), Sysmon uses synthetic syscall numbers defined in `linuxTypes.h`: + +```c +#define __NR_NETWORK 400 // Network tracepoint events +#define __NR_PROCTERM 401 // Process termination +#define __NR_RAWACCESS 402 // Raw device access +#define __NR_CREATE 403 // File create events +``` + +These are NOT real syscall numbers - they're internal identifiers for the telemetry library. + +#### Event Types +- **LinuxFileOpen**, **LinuxNetworkEvent**, **LinuxEBPFEvent** - Internal Linux event types (0xFF01, 0xFF02, 0xFF03) +- These get transformed to standard Sysmon event types before output + +--- + +## Step 1: Define the Event in manifest.xml + +Location: `sysmonCommon/manifest.xml` + +### 1.1 Add Event Template + +Find the `` section and add your event template: + +```xml + +``` + +**Important**: The order of `` elements defines the field order in output. + +### 1.2 Add Event Definition + +Find the `` section and add your event: + +```xml + +``` + +Key attributes: +- `value="100"` - The Event ID (must be unique) +- `rulename="EbpfEvent"` - Name used in configuration rules +- `ifdef="__linux__"` - Linux-only event (optional) + +### 1.3 Add Task Definition + +In the `` section: + +```xml + +``` + +### 1.4 Add String Resources + +In the `` section: + +```xml + + +``` + +### 1.5 Regenerate Headers + +After modifying manifest.xml, regenerate the headers: + +```bash +cd build +cmake .. # This runs the T4 template processor +``` + +This generates: +- `sysmonevents.h` - Event structures and field enums +- `sysmonmsg.h` - Message definitions + +--- + +## Step 2: Define Data Structures in linuxTypes.h + +Location: `linuxTypes.h` + +### 2.1 Define Internal Event Type + +Add near other Linux event type definitions: + +```c +#define LinuxEBPFEvent 0xFF03 +``` + +### 2.2 Define Extension Enum + +Extensions are variable-length fields that follow the fixed event structure: + +```c +typedef enum { + LINUX_EBPF_Sid, // User ID (8 bytes) + LINUX_EBPF_ImagePath, // Process image path (string) + LINUX_EBPF_ProgName, // BPF program name (string) + LINUX_EBPF_ExtMax +} LINUX_EBPF_Extensions; +``` + +### 2.3 Define Event Structure + +```c +typedef struct { + LARGE_INTEGER m_EventTime; + ULONG m_ProcessId; + ULONG m_BpfCmd; // BPF command (e.g., BPF_PROG_LOAD) + ULONG m_ProgType; // BPF program type + ULONG m_ProgId; // BPF program FD/ID + ULONG m_Extensions[LINUX_EBPF_ExtMax]; +} SYSMON_LINUX_EBPF_EVENT, *PSYSMON_LINUX_EBPF_EVENT; +``` + +**Critical**: The `m_Extensions` array stores the LENGTH of each variable-length field, NOT the data itself. Data follows the structure in memory. + +### 2.4 Add to Event Body Union + +In `SYSMON_EVENT_BODY`: + +```c +typedef union _SYSMON_EVENT_BODY { + // ... existing events ... + SYSMON_LINUX_EBPF_EVENT m_EBPFEvent; +} SYSMON_EVENT_BODY, *PSYSMON_EVENT_BODY; +``` + +--- + +## Step 3: Create eBPF Kernel Programs + +### 3.1 Create Main Logic File + +Location: `ebpfKern/sysmonBPFLoad.c` + +```c +/* + SysmonForLinux - sysmonBPFLoad.c + + eBPF program to monitor BPF syscall for program loads +*/ + +#include "sysmonEBPF_common.h" +#include + +// Define constants if not available from system headers +#ifndef BPF_PROG_LOAD +#define BPF_PROG_LOAD 5 +#endif + +#ifndef BPF_PROG_ATTACH +#define BPF_PROG_ATTACH 8 +#endif + +// Program type definitions (from linux/bpf.h) +#define BPF_PROG_TYPE_UNSPEC 0 +#define BPF_PROG_TYPE_SOCKET_FILTER 1 +#define BPF_PROG_TYPE_KPROBE 2 +// ... add all types you need + +#ifndef BPF_OBJ_NAME_LEN +#define BPF_OBJ_NAME_LEN 16 +#endif + +__attribute__((always_inline)) +static inline char* set_BPFLoad_info( + PSYSMON_EVENT_HEADER eventHdr, + const ebpfConfig *config, + uint64_t pidTid, + uint32_t cpuId, + const argsStruct *eventArgs + ) +{ + // Get process info + const void *task = (const void *)bpf_get_current_task(); + + // Set event type + eventHdr->m_EventType = LinuxEBPFEvent; + + PSYSMON_LINUX_EBPF_EVENT event = + (PSYSMON_LINUX_EBPF_EVENT)&eventHdr->m_EventBody; + char *ptr = (char *)(event + 1); // Extensions follow the struct + + // Set timestamp + event->m_EventTime.QuadPart = + (config->bootNsSinceEpoch + bpf_ktime_get_ns()) / 100; + + // Set PID + event->m_ProcessId = pidTid >> 32; + + // Get BPF command from syscall args + uint32_t cmd = (uint32_t)eventArgs->a[0]; + event->m_BpfCmd = cmd; + + // Initialize extensions + memset(event->m_Extensions, 0, sizeof(event->m_Extensions)); + + // Extension 0: SID (User ID) + if (ptr <= (char *)eventHdr + SYSMON_MAX_EVENT_SIZE - sizeof(uint64_t)) { + *(uint64_t *)ptr = getUid(task); + event->m_Extensions[LINUX_EBPF_Sid] = sizeof(uint64_t); + ptr += sizeof(uint64_t); + } + + // Extension 1: Image path + ptr = copyExePath(ptr, task, &event->m_Extensions[LINUX_EBPF_ImagePath], + (char *)eventHdr + SYSMON_MAX_EVENT_SIZE); + + // Extension 2: Program name (for BPF_PROG_LOAD) + if (cmd == BPF_PROG_LOAD) { + // Get union bpf_attr pointer from syscall arg 1 + const void *attr = (const void *)eventArgs->a[1]; + + // Get program type (offset 0 in bpf_attr for PROG_LOAD) + uint32_t prog_type = 0; + bpf_probe_read(&prog_type, sizeof(prog_type), attr); + event->m_ProgType = prog_type; + + // Get program FD from return value + event->m_ProgId = (uint32_t)eventArgs->returnCode; + + // Get program name (offset 48 in union bpf_attr for PROG_LOAD) + char prog_name[BPF_OBJ_NAME_LEN + 1] = {}; + bpf_probe_read_str(prog_name, sizeof(prog_name), attr + 48); + + size_t name_len = 0; + #pragma unroll + for (int i = 0; i < BPF_OBJ_NAME_LEN; i++) { + if (prog_name[i] == '\0') break; + name_len++; + } + + if (name_len > 0 && ptr + name_len + 1 <= + (char *)eventHdr + SYSMON_MAX_EVENT_SIZE) { + memcpy(ptr, prog_name, name_len); + ptr[name_len] = '\0'; + event->m_Extensions[LINUX_EBPF_ProgName] = name_len + 1; + ptr += name_len + 1; + } + } + + return ptr; +} +``` + +### 3.2 Create Raw Tracepoint Handler + +Location: `ebpfKern/sysmonBPFLoad_rawtp.c` + +```c +/* + SysmonForLinux - sysmonBPFLoad_rawtp.c + + Raw tracepoint handler for BPF syscall (kernel 4.17+) +*/ + +#include "sysmonEBPF_common.h" +#include + +// Include main logic +#include "sysmonBPFLoad.c" + +SEC("raw_tracepoint/sys_exit") +int BPFLoadRawExit(struct bpf_our_raw_tracepoint_args *ctx) +{ + uint64_t pidTid = bpf_get_current_pid_tgid(); + uint32_t cpuId = bpf_get_smp_processor_id(); + + argsStruct *eventArgs = bpf_map_lookup_elem(&argsHash, &pidTid); + if (!eventArgs) + return 0; + + // Only handle bpf syscall + if (eventArgs->syscallId != __NR_bpf) + return 0; + + // Get registers for return code + struct pt_regs *regs = (struct pt_regs *)ctx->args[0]; + + // Set return code + if (bpf_probe_read(&eventArgs->returnCode, sizeof(int64_t), + (void *)&SYSCALL_PT_REGS_RC(regs)) != 0) { + BPF_PRINTK("ERROR: failed to get return code for bpf syscall\n"); + } + + // Skip failed syscalls + if (eventArgs->returnCode < 0) { + bpf_map_delete_elem(&argsHash, &pidTid); + return 0; + } + + // Get BPF command + uint32_t cmd = (uint32_t)eventArgs->a[0]; + + // Only monitor BPF_PROG_LOAD and BPF_PROG_ATTACH + if (cmd != BPF_PROG_LOAD && cmd != BPF_PROG_ATTACH) { + bpf_map_delete_elem(&argsHash, &pidTid); + return 0; + } + + // Get config + uint32_t configId = 0; + ebpfConfig *config = bpf_map_lookup_elem(&configMap, &configId); + if (!config) { + bpf_map_delete_elem(&argsHash, &pidTid); + return 0; + } + + // Get event header + PSYSMON_EVENT_HEADER eventHdr; + if (!getEventHdr(&eventHdr, cpuId)) { + bpf_map_delete_elem(&argsHash, &pidTid); + return 0; + } + + // Populate event + char *ptr = set_BPFLoad_info(eventHdr, config, pidTid, cpuId, eventArgs); + + if (ptr != NULL && ptr > (char *)eventHdr) { + eventHdr->m_EventSize = (uint32_t)((void *)ptr - (void *)eventHdr); + checkAndSendEvent((void *)ctx, eventHdr, config); + } + + // Cleanup + bpf_map_delete_elem(&argsHash, &pidTid); + return 0; +} +``` + +### 3.3 Create Tracepoint Handler (for older kernels) + +Location: `ebpfKern/sysmonBPFLoad_tp.c` + +```c +/* + SysmonForLinux - sysmonBPFLoad_tp.c + + Traditional tracepoint handler for BPF syscall (kernel < 4.17) +*/ + +#include "sysmonEBPF_common.h" +#include + +#include "sysmonBPFLoad.c" + +SEC("tracepoint/syscalls/sys_exit_bpf") +int BPFLoadExit(struct tracepoint__syscalls__sys_exit *args) +{ + // Similar to raw tracepoint but uses args->ret directly + // ...implementation similar to above... +} +``` + +### 3.4 Include in Kernel Objects + +Add includes to each kernel version file: + +`ebpfKern/sysmonEBPFkern4.15.c` and `sysmonEBPFkern4.16.c`: +```c +#include "sysmonBPFLoad_tp.c" +``` + +`ebpfKern/sysmonEBPFkern4.17-5.1.c`, `sysmonEBPFkern5.2.c`, `sysmonEBPFkern5.3-5.5.c`, `sysmonEBPFkern5.6-.c`: +```c +#include "sysmonBPFLoad_rawtp.c" +``` + +--- + +## Step 4: Register Syscall Handlers + +Location: `sysmonforlinux.c` + +### 4.1 Add to Tracepoint Program Arrays + +For traditional tracepoints (kernel 4.15-4.16): +```c +const ebpfSyscallTPprog TPexitProgs[] = { + // ... existing entries ... + {__NR_bpf, "BPFLoadExit"} +}; +``` + +For raw tracepoints (kernel 4.17+): +```c +const ebpfSyscallRTPprog RTPexitProgs[] = { + // ... existing entries ... + {"BPFLoadRawExit", __NR_bpf} +}; +``` + +### 4.2 Add Syscall Activation + +In `SetSyscallActive()`: +```c +void SetSyscallActive(bool *s, ULONG eventId) +{ + switch(eventId) { + // ... existing cases ... + + case SYSMONEVENT_EBPF_EVENT_EVENT_value: + s[__NR_bpf] = true; + break; + } +} +``` + +**Note**: Only use real syscall numbers here. Synthetic numbers (like `__NR_NETWORK`) are for tracepoint-based events that don't hook a specific syscall. + +--- + +## Step 5: Process Events in Userspace + +Location: `sysmonforlinux.c` + +### 5.1 Add Event Handler Function + +```c +//-------------------------------------------------------------------- +// +// processEBPFEvent +// +// Handles eBPF program load events (Event ID 100) +// +//-------------------------------------------------------------------- +void processEBPFEvent(CONST PSYSMON_EVENT_HEADER eventHdr) +{ + if (eventHdr == NULL) { + fprintf(stderr, "processEBPFEvent invalid params\n"); + return; + } + + // For simple events, just dispatch directly + // For complex events, you might transform the data first + DispatchEvent(eventHdr); +} +``` + +### 5.2 Add to Event Dispatcher + +In `handleEvent()`: +```c +static void handleEvent(void *ctx, int cpu, void *data, uint32_t size) +{ + // ... validation code ... + + switch ((DWORD)eventHdr->m_EventType) { + // ... existing cases ... + + case LinuxEBPFEvent: + processEBPFEvent(eventHdr); + break; + + default: + DispatchEvent(eventHdr); + } +} +``` + +--- + +## Step 6: Format Event Output + +Location: `sysmonCommon/eventsCommon.cpp` + +### 6.1 Add Event Formatting Case + +In `EventWriteExt()`, find the switch statement for event types and add: + +```cpp +#if defined(__linux__) + // Handle Linux-specific event types + if (eventHeader->m_EventType == LinuxEBPFEvent) { + PSYSMON_LINUX_EBPF_EVENT ebpfEvent = + &eventHeader->m_EventBody.m_EBPFEvent; + const char *extPtr = (const char *)(ebpfEvent + 1); + TCHAR userBuf[256]; + const char *bpfCmdStr = "UNKNOWN"; + const char *bpfProgTypeStr = "UNKNOWN"; + + // Get BPF command name + switch (ebpfEvent->m_BpfCmd) { + case 0: bpfCmdStr = "BPF_MAP_CREATE"; break; + case 1: bpfCmdStr = "BPF_MAP_LOOKUP_ELEM"; break; + case 2: bpfCmdStr = "BPF_MAP_UPDATE_ELEM"; break; + case 3: bpfCmdStr = "BPF_MAP_DELETE_ELEM"; break; + case 4: bpfCmdStr = "BPF_MAP_GET_NEXT_KEY"; break; + case 5: bpfCmdStr = "BPF_PROG_LOAD"; break; + case 6: bpfCmdStr = "BPF_OBJ_PIN"; break; + case 7: bpfCmdStr = "BPF_OBJ_GET"; break; + case 8: bpfCmdStr = "BPF_PROG_ATTACH"; break; + // ... add all commands up to latest kernel ... + default: bpfCmdStr = "UNKNOWN"; break; + } + + // Get BPF program type name + switch (ebpfEvent->m_ProgType) { + case 0: bpfProgTypeStr = "UNSPEC"; break; + case 1: bpfProgTypeStr = "SOCKET_FILTER"; break; + case 2: bpfProgTypeStr = "KPROBE"; break; + case 3: bpfProgTypeStr = "SCHED_CLS"; break; + // ... add all program types ... + default: bpfProgTypeStr = "UNKNOWN"; break; + } + + // Get User from SID extension + const char *sidPtr = extPtr; + extPtr += ebpfEvent->m_Extensions[LINUX_EBPF_Sid]; + uid_t uid = *(uint32_t *)sidPtr; + struct passwd *pw = getpwuid(uid); + if (pw) { + _sntprintf(userBuf, _countof(userBuf), _T("%s"), pw->pw_name); + } else { + _sntprintf(userBuf, _countof(userBuf), _T("%d"), uid); + } + + // Get Image path + const char *imagePath = extPtr; + extPtr += ebpfEvent->m_Extensions[LINUX_EBPF_ImagePath]; + + // Get Program name + const char *progName = extPtr; + + // Set all event fields + EventSetFieldX(eventBuffer, F_EE_UtcTime, N_LargeTime, + ebpfEvent->m_EventTime); + EventSetFieldX(eventBuffer, F_EE_ProcessGuid, N_ProcessId, + ebpfEvent->m_ProcessId); + EventSetFieldX(eventBuffer, F_EE_ProcessId, N_ProcessId, + ebpfEvent->m_ProcessId); + EventSetFieldS(eventBuffer, F_EE_Image, imagePath, FALSE); + EventSetFieldS(eventBuffer, F_EE_User, userBuf, FALSE); + EventSetFieldS(eventBuffer, F_EE_BpfCommand, bpfCmdStr, FALSE); + EventSetFieldS(eventBuffer, F_EE_BpfProgramType, bpfProgTypeStr, FALSE); + EventSetFieldX(eventBuffer, F_EE_BpfProgramId, N_Ulong, + ebpfEvent->m_ProgId); + EventSetFieldS(eventBuffer, F_EE_BpfProgramName, progName, FALSE); + + EventProcess(&SYSMONEVENT_EBPF_EVENT_Type, eventBuffer, + eventHeader, NULL); + break; + } +#endif +``` + +**Important**: Field names like `F_EE_BpfCommand` are auto-generated from manifest.xml. The pattern is `F__`. + +--- + +## Step 7: Enable Event via Configuration + +### 7.1 Default Event State + +In `sysmonCommon/manifest.xml`, the `rulename` attribute controls if the event is included by default: + +```xml + +``` + +Events are **excluded by default** unless explicitly included in rules. + +### 7.2 Configuration File Example + +```xml + + + + + + - + + + + + + + libbpf_ + + + + +``` + +--- + +## Step 8: Build and Test + +### 8.1 Build + +```bash +cd build +cmake .. +make +``` + +### 8.2 Install and Test + +```bash +# Stop existing sysmon +sudo ./sysmon -u force + +# Install with config +sudo ./sysmon -i /path/to/config.xml + +# Watch logs +sudo tail -f /var/log/syslog | grep -i sysmon + +# Trigger event (for eBPF event) +sudo bpftool prog load /tmp/test.o /sys/fs/bpf/test_prog +``` + +### 8.3 Validate with strace + +```bash +sudo strace -e bpf bpftool prog load /tmp/test.o /sys/fs/bpf/test_prog +``` + +--- + diff --git a/ebpfKern/sysmonBPFLoad.c b/ebpfKern/sysmonBPFLoad.c new file mode 100644 index 0000000..0e291b0 --- /dev/null +++ b/ebpfKern/sysmonBPFLoad.c @@ -0,0 +1,248 @@ +/* + SysmonForLinux + + Copyright (c) Microsoft Corporation + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +//==================================================================== +// +// sysmonBPFLoad.c +// +// Report eBPF program load events via the bpf() syscall. +// This event is Linux-only and detects when eBPF programs are loaded. +// +//==================================================================== + +#include "missingdefs.h" + +// BPF commands from linux/bpf.h +#define BPF_MAP_CREATE 0 +#define BPF_MAP_LOOKUP_ELEM 1 +#define BPF_MAP_UPDATE_ELEM 2 +#define BPF_MAP_DELETE_ELEM 3 +#define BPF_MAP_GET_NEXT_KEY 4 +#define BPF_PROG_LOAD 5 +#define BPF_OBJ_PIN 6 +#define BPF_OBJ_GET 7 +#define BPF_PROG_ATTACH 8 +#define BPF_PROG_DETACH 9 +#define BPF_PROG_RUN 10 +#define BPF_PROG_GET_NEXT_ID 11 +#define BPF_MAP_GET_NEXT_ID 12 +#define BPF_PROG_GET_FD_BY_ID 13 +#define BPF_MAP_GET_FD_BY_ID 14 +#define BPF_OBJ_GET_INFO_BY_FD 15 + +// BPF program types from linux/bpf.h +#define BPF_PROG_TYPE_UNSPEC 0 +#define BPF_PROG_TYPE_SOCKET_FILTER 1 +#define BPF_PROG_TYPE_KPROBE 2 +#define BPF_PROG_TYPE_SCHED_CLS 3 +#define BPF_PROG_TYPE_SCHED_ACT 4 +#define BPF_PROG_TYPE_TRACEPOINT 5 +#define BPF_PROG_TYPE_XDP 6 +#define BPF_PROG_TYPE_PERF_EVENT 7 +#define BPF_PROG_TYPE_CGROUP_SKB 8 +#define BPF_PROG_TYPE_CGROUP_SOCK 9 +#define BPF_PROG_TYPE_LWT_IN 10 +#define BPF_PROG_TYPE_LWT_OUT 11 +#define BPF_PROG_TYPE_LWT_XMIT 12 +#define BPF_PROG_TYPE_SOCK_OPS 13 +#define BPF_PROG_TYPE_SK_SKB 14 +#define BPF_PROG_TYPE_CGROUP_DEVICE 15 +#define BPF_PROG_TYPE_SK_MSG 16 +#define BPF_PROG_TYPE_RAW_TRACEPOINT 17 +#define BPF_PROG_TYPE_CGROUP_SOCK_ADDR 18 +#define BPF_PROG_TYPE_LWT_SEG6LOCAL 19 +#define BPF_PROG_TYPE_LIRC_MODE2 20 +#define BPF_PROG_TYPE_SK_REUSEPORT 21 +#define BPF_PROG_TYPE_FLOW_DISSECTOR 22 +#define BPF_PROG_TYPE_CGROUP_SYSCTL 23 +#define BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE 24 +#define BPF_PROG_TYPE_CGROUP_SOCKOPT 25 +#define BPF_PROG_TYPE_TRACING 26 +#define BPF_PROG_TYPE_STRUCT_OPS 27 +#define BPF_PROG_TYPE_EXT 28 +#define BPF_PROG_TYPE_LSM 29 +#define BPF_PROG_TYPE_SK_LOOKUP 30 +#define BPF_PROG_TYPE_SYSCALL 31 + +// Maximum length for BPF program name +#ifndef BPF_OBJ_NAME_LEN +#define BPF_OBJ_NAME_LEN 16 +#endif + +// BPF attr structure for PROG_LOAD command (from linux/bpf.h UAPI) +// This is a user-space structure passed to the bpf() syscall. +// Note: The bpf_attr union layout is defined in UAPI headers and is ABI-stable. +// For CO-RE, we define the relevant portion of the structure to enable +// proper field access even though UAPI structures don't typically relocate. +struct bpf_attr_prog_load { + __u32 prog_type; + __u32 insn_cnt; + __u64 insns; + __u64 license; + __u32 log_level; + __u32 log_size; + __u64 log_buf; + __u32 kern_version; + __u32 prog_flags; + char prog_name[BPF_OBJ_NAME_LEN]; + // Additional fields follow but are not needed +}; + +__attribute__((always_inline)) +static inline char* set_BPFLoad_info( + PSYSMON_EVENT_HEADER eventHdr, + const ebpfConfig *config, + uint64_t pidTid, + uint32_t cpuId, + const argsStruct *eventArgs + ) +{ + const void *task = NULL; + char *ptr = NULL; + uint64_t extLen = 0; + int cmd = 0; + uint32_t prog_type = 0; + char prog_name[BPF_OBJ_NAME_LEN + 1]; + + if (eventHdr == NULL || config == NULL || eventArgs == NULL) + return (char *)eventHdr; + + // Get the BPF command from first argument + cmd = (int)eventArgs->a[0]; + + // Only monitor BPF_PROG_LOAD and BPF_PROG_ATTACH commands + // These are the most security-relevant operations + if (cmd != BPF_PROG_LOAD && cmd != BPF_PROG_ATTACH) { + return (char *)eventHdr; + } + + // For PROG_LOAD, the return value is the file descriptor (>=0 on success) + // For PROG_ATTACH, return value is 0 on success + if (cmd == BPF_PROG_LOAD && eventArgs->returnCode < 0) { + return (char *)eventHdr; + } + if (cmd == BPF_PROG_ATTACH && eventArgs->returnCode != 0) { + return (char *)eventHdr; + } + + // get the task struct + task = (const void *)bpf_get_current_task(); + if (!task) + return (char *)eventHdr; + + // initialise event + eventHdr->m_FieldFiltered = 0; + eventHdr->m_PreFiltered = 0; + eventHdr->m_SequenceNumber = 0; + eventHdr->m_SessionId = 0; + + eventHdr->m_EventType = LinuxEBPFEvent; + PSYSMON_LINUX_EBPF_EVENT event = (PSYSMON_LINUX_EBPF_EVENT)&eventHdr->m_EventBody; + + // set the pid + event->m_ProcessId = pidTid >> 32; + + // set event time - this is in nanoseconds and we want 100ns intervals + event->m_EventTime.QuadPart = (bpf_ktime_get_ns() + config->bootNsSinceEpoch) / 100; + + // set the BPF command + event->m_BpfCmd = cmd; + + // Read program type and name from the bpf_attr union (second argument) + // The bpf_attr is a UAPI structure passed from user space. + // We use bpf_probe_read for user memory access. + // For CO-RE builds, we use the defined structure for proper field offsets. + + if (cmd == BPF_PROG_LOAD) { + const struct bpf_attr_prog_load *attr = + (const struct bpf_attr_prog_load *)eventArgs->a[1]; + if (attr != NULL) { +#ifdef EBPF_CO_RE + // CO-RE: Use BPF_CORE_READ for proper field relocation + // Note: bpf_attr is UAPI so offsets are stable, but this follows + // the project's pattern for kernel structure access. + bpf_probe_read_user(&prog_type, sizeof(prog_type), &attr->prog_type); +#else + // Non-CO-RE: Read prog_type (first field in the union for PROG_LOAD) + bpf_probe_read(&prog_type, sizeof(prog_type), attr); +#endif + event->m_ProgType = prog_type; + + // For successful loads, return code is the FD + event->m_ProgId = (ULONG)eventArgs->returnCode; + } + } else { + event->m_ProgType = 0; + event->m_ProgId = 0; + } + + ptr = (char *)(event + 1); + memset(event->m_Extensions, 0, sizeof(event->m_Extensions)); + + // Insert the UID as the SID + // Note: getUid() is CO-RE aware internally + *(uint64_t *)ptr = getUid((struct task_struct*) task, config) & 0xFFFFFFFF; + event->m_Extensions[LINUX_EBPF_Sid] = sizeof(uint64_t); + ptr += sizeof(uint64_t); + + // Copy the executable path + // Note: copyExePath() is CO-RE aware internally + extLen = copyExePath(ptr, task, config); + event->m_Extensions[LINUX_EBPF_ImagePath] = extLen; + asm volatile("%[extLen] &= " XSTR(PATH_MAX - 1) "\n" + "%[ptr] += %[extLen]" + :[extLen]"+&r"(extLen), [ptr]"+&r"(ptr) + ); + + // Read the program name from bpf_attr for BPF_PROG_LOAD commands + memset(prog_name, 0, sizeof(prog_name)); + if (cmd == BPF_PROG_LOAD) { + const struct bpf_attr_prog_load *attr = + (const struct bpf_attr_prog_load *)eventArgs->a[1]; + if (attr != NULL) { +#ifdef EBPF_CO_RE + // CO-RE: Use bpf_probe_read_user for proper user memory access + bpf_probe_read_user(prog_name, BPF_OBJ_NAME_LEN, &attr->prog_name); +#else + // Non-CO-RE: Read prog_name at calculated offset + // prog_type(4) + insn_cnt(4) + insns(8) + license(8) + + // log_level(4) + log_size(4) + log_buf(8) + kern_version(4) + prog_flags(4) = 48 + bpf_probe_read(prog_name, BPF_OBJ_NAME_LEN, (const char *)attr + 48); +#endif + prog_name[BPF_OBJ_NAME_LEN] = '\0'; + } + } + + // Copy program name to extensions + size_t name_len = 0; + #pragma unroll + for (int i = 0; i < BPF_OBJ_NAME_LEN; i++) { + if (prog_name[i] == '\0') break; + ptr[i] = prog_name[i]; + name_len++; + } + ptr[name_len] = '\0'; + event->m_Extensions[LINUX_EBPF_ProgName] = name_len + 1; + ptr += name_len + 1; + + return ptr; +} diff --git a/ebpfKern/sysmonBPFLoad_rawtp.c b/ebpfKern/sysmonBPFLoad_rawtp.c new file mode 100644 index 0000000..b748632 --- /dev/null +++ b/ebpfKern/sysmonBPFLoad_rawtp.c @@ -0,0 +1,74 @@ +/* + SysmonForLinux + + Copyright (c) Microsoft Corporation + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +//==================================================================== +// +// sysmonBPFLoad_rawtp.c +// +// Raw tracepoint program for eBPF load events. +// +//==================================================================== + +#include "sysmonEBPF_common.h" +#include +#include "sysmonHelpers.c" +#include "sysmonBPFLoad.c" + +SEC("raw_tracepoint/sys_exit") +__attribute__((flatten)) +int BPFLoadRawExit(struct bpf_our_raw_tracepoint_args *ctx) +{ + uint64_t pidTid = bpf_get_current_pid_tgid(); + uint32_t cpuId = bpf_get_smp_processor_id(); + PSYSMON_EVENT_HEADER eventHdr = NULL; + argsStruct *eventArgs = NULL; + const struct pt_regs *regs = (const struct pt_regs *)ctx->args[0]; + const ebpfConfig *config; + char *ptr = NULL; + + if (!setUpEvent(&config, &eventArgs)) + return 0; + + // only handle bpf syscall events + if (eventArgs->syscallId != __NR_bpf) { + return 0; + } + + // set the return code + if (bpf_probe_read(&eventArgs->returnCode, sizeof(int64_t), (void *)&SYSCALL_PT_REGS_RC(regs)) != 0){ + BPF_PRINTK("ERROR, failed to get return code for bpf syscall\n"); + } + + if (!getEventHdr(&eventHdr, cpuId)) + return 0; + + ptr = set_BPFLoad_info(eventHdr, config, pidTid, cpuId, eventArgs); + if (ptr != NULL && ptr > (char *)eventHdr) { + eventHdr->m_EventSize = (uint32_t)((void *)ptr - (void *)eventHdr); + checkAndSendEvent((void *)ctx, eventHdr, config); + } + + // Cleanup hash as we handled this event + bpf_map_delete_elem(&argsHash, &pidTid); + + return 0; +} diff --git a/ebpfKern/sysmonBPFLoad_tp.c b/ebpfKern/sysmonBPFLoad_tp.c new file mode 100644 index 0000000..0bc87a6 --- /dev/null +++ b/ebpfKern/sysmonBPFLoad_tp.c @@ -0,0 +1,68 @@ +/* + SysmonForLinux + + Copyright (c) Microsoft Corporation + + All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +//==================================================================== +// +// sysmonBPFLoad_tp.c +// +// Traditional tracepoint program for eBPF load events. +// +//==================================================================== + +#include "sysmonEBPF_common.h" +#include +#include "sysmonHelpers.c" +#include "sysmonBPFLoad.c" + + +// sys_exit +SEC("tracepoint/syscalls/sys_exit_bpf") +__attribute__((flatten)) +int BPFLoadExit(struct tracepoint__syscalls__sys_exit *args) +{ + uint64_t pidTid = bpf_get_current_pid_tgid(); + uint32_t cpuId = bpf_get_smp_processor_id(); + PSYSMON_EVENT_HEADER eventHdr = NULL; + argsStruct *eventArgs = NULL; + const ebpfConfig *config; + char *ptr = NULL; + + if (!setUpEvent(&config, &eventArgs)) + return 0; + + // set the return code + eventArgs->returnCode = args->ret; + + if (!getEventHdr(&eventHdr, cpuId)) + return 0; + + ptr = set_BPFLoad_info(eventHdr, config, pidTid, cpuId, eventArgs); + if (ptr != NULL && ptr > (char *)eventHdr) { + eventHdr->m_EventSize = (uint32_t)((void *)ptr - (void *)eventHdr); + checkAndSendEvent((void *)args, eventHdr, config); + } + + // Cleanup + bpf_map_delete_elem(&argsHash, &pidTid); + + return 0; +} diff --git a/ebpfKern/sysmonEBPFkern4.15.c b/ebpfKern/sysmonEBPFkern4.15.c index 1dd3733..76e8ccb 100644 --- a/ebpfKern/sysmonEBPFkern4.15.c +++ b/ebpfKern/sysmonEBPFkern4.15.c @@ -47,5 +47,6 @@ #include "sysmonUDPsend.c" #include "sysmonUDPrecv_tp.c" #include "sysmonCloseFD_tp.c" +#include "sysmonBPFLoad_tp.c" char _license[] SEC("license") = "GPL"; diff --git a/ebpfKern/sysmonEBPFkern4.16.c b/ebpfKern/sysmonEBPFkern4.16.c index b28e3fb..3470906 100644 --- a/ebpfKern/sysmonEBPFkern4.16.c +++ b/ebpfKern/sysmonEBPFkern4.16.c @@ -47,5 +47,6 @@ #include "sysmonUDPsend.c" #include "sysmonUDPrecv_tp.c" #include "sysmonCloseFD_tp.c" +#include "sysmonBPFLoad_tp.c" char _license[] SEC("license") = "GPL"; diff --git a/ebpfKern/sysmonEBPFkern4.17-5.1.c b/ebpfKern/sysmonEBPFkern4.17-5.1.c index d3e018f..fce0a24 100644 --- a/ebpfKern/sysmonEBPFkern4.17-5.1.c +++ b/ebpfKern/sysmonEBPFkern4.17-5.1.c @@ -47,5 +47,6 @@ #include "sysmonUDPsend.c" #include "sysmonUDPrecv_rawtp.c" #include "sysmonCloseFD_rawtp.c" +#include "sysmonBPFLoad_rawtp.c" char _license[] SEC("license") = "GPL"; diff --git a/ebpfKern/sysmonEBPFkern5.2.c b/ebpfKern/sysmonEBPFkern5.2.c index d05cfa5..02544cd 100644 --- a/ebpfKern/sysmonEBPFkern5.2.c +++ b/ebpfKern/sysmonEBPFkern5.2.c @@ -46,5 +46,6 @@ #include "sysmonUDPsend.c" #include "sysmonUDPrecv_rawtp.c" #include "sysmonCloseFD_rawtp.c" +#include "sysmonBPFLoad_rawtp.c" char _license[] SEC("license") = "GPL"; diff --git a/ebpfKern/sysmonEBPFkern5.3-5.5.c b/ebpfKern/sysmonEBPFkern5.3-5.5.c index cd92ea7..4b8698d 100644 --- a/ebpfKern/sysmonEBPFkern5.3-5.5.c +++ b/ebpfKern/sysmonEBPFkern5.3-5.5.c @@ -44,5 +44,6 @@ #include "sysmonUDPsend.c" #include "sysmonUDPrecv_rawtp.c" #include "sysmonCloseFD_rawtp.c" +#include "sysmonBPFLoad_rawtp.c" char _license[] SEC("license") = "GPL"; diff --git a/ebpfKern/sysmonEBPFkern5.6-.c b/ebpfKern/sysmonEBPFkern5.6-.c index bdf5c94..025a225 100644 --- a/ebpfKern/sysmonEBPFkern5.6-.c +++ b/ebpfKern/sysmonEBPFkern5.6-.c @@ -44,5 +44,6 @@ #include "sysmonUDPsend.c" #include "sysmonUDPrecv_rawtp.c" #include "sysmonCloseFD_rawtp.c" +#include "sysmonBPFLoad_rawtp.c" char _license[] SEC("license") = "GPL"; diff --git a/linuxTypes.h b/linuxTypes.h index db71d0c..1319aa4 100644 --- a/linuxTypes.h +++ b/linuxTypes.h @@ -547,6 +547,7 @@ typedef struct _SECURITY_LOGON_SESSION_DATA { #define LinuxFileOpen 0xFF01 #define LinuxNetworkEvent 0xFF02 +#define LinuxEBPFEvent 0xFF03 #define __NR_NETWORK 400 #define __NR_PROCTERM 401 @@ -593,5 +594,26 @@ typedef struct { } SYSMON_LINUX_NETWORK_EVENT, *PSYSMON_LINUX_NETWORK_EVENT; +// eBPF program load/run event - Linux only +// BPF commands from linux/bpf.h +#define BPF_CMD_PROG_LOAD 5 + +typedef enum { + LINUX_EBPF_Sid, + LINUX_EBPF_ImagePath, + LINUX_EBPF_ProgName, + LINUX_EBPF_ExtMax +} LINUX_EBPF_Extensions; + +typedef struct { + ULONG m_ProcessId; + LARGE_INTEGER m_EventTime; + ULONG m_BpfCmd; // BPF command (e.g., BPF_PROG_LOAD) + ULONG m_ProgType; // BPF program type + ULONG m_ProgId; // BPF program ID (returned by kernel) + ULONG m_Extensions[LINUX_EBPF_ExtMax]; +} SYSMON_LINUX_EBPF_EVENT, *PSYSMON_LINUX_EBPF_EVENT; + + diff --git a/sysmonCommon b/sysmonCommon index 28ff6f4..5595478 160000 --- a/sysmonCommon +++ b/sysmonCommon @@ -1 +1 @@ -Subproject commit 28ff6f4522f1c031d5bf4a63e5038cb8d602760e +Subproject commit 5595478f301c91537048d04b319898ea4b322a1f diff --git a/sysmonLogView/sysmonGetEventName.c b/sysmonLogView/sysmonGetEventName.c index e234a06..612b362 100644 --- a/sysmonLogView/sysmonGetEventName.c +++ b/sysmonLogView/sysmonGetEventName.c @@ -16,9 +16,12 @@ //==================================================================== // -// sysmonGetEventName +// sysmonGetEventName.c // // Converts an event ID into its string name. +// This file must be compiled as C (not C++) because SYSMON_EVENT_C +// causes sysmonevents.h to define arrays of string literals, which +// C++ treats as const char* but the generated code uses char*. // //==================================================================== @@ -30,7 +33,12 @@ const char *eventName(unsigned int eventId) { - return AllEvents[eventId]->EventName; + // Use EventTypesById which is indexed by event ID + // Check bounds and NULL pointer + if (eventId >= EventTypesByIdCount || EventTypesById[eventId] == NULL) { + return "UNKNOWN_EVENT"; + } + return EventTypesById[eventId]->EventName; } diff --git a/sysmonforlinux.c b/sysmonforlinux.c index 215e62f..3bca9aa 100644 --- a/sysmonforlinux.c +++ b/sysmonforlinux.c @@ -114,7 +114,8 @@ const ebpfSyscallTPprog TPexitProgs[] = {__NR_recvmsg, "UDPrecvExit"}, {__NR_recvmmsg, "UDPrecvExit"}, {__NR_read, "UDPrecvExit"}, - {__NR_close, "CloseFDExit"} + {__NR_close, "CloseFDExit"}, + {__NR_bpf, "BPFLoadExit"} }; const ebpfSyscallRTPprog RTPenterProgs[] = @@ -142,7 +143,8 @@ const ebpfSyscallRTPprog RTPexitProgs[] = {"UDPrecvRawExit", __NR_recvmsg}, {"UDPrecvRawExit", __NR_recvmmsg}, {"UDPrecvRawExit", __NR_read}, - {"CloseFDRawExit", __NR_close} + {"CloseFDRawExit", __NR_close}, + {"BPFLoadRawExit", __NR_bpf} }; const ebpfTracepointProg otherTPprogs4_15[] = @@ -204,7 +206,7 @@ void telemetryReady() unsigned int *hashTypePtr = OPT_VALUE( HashAlgorithms ); hashType = *hashTypePtr; } - TCHAR buff[256]; + TCHAR buff[256] = {0}; LinuxGetFileHash(hashType, configFile, buff, _countof(buff)); SendConfigEvent( configFile, buff ); } else { @@ -456,6 +458,35 @@ pid_t getPidFromTid(pid_t tid) return -1; } +//-------------------------------------------------------------------- +// +// processEBPFEvent +// +// Handles eBPF program load events. This is a Linux-only event +// (Event ID 100) that tracks when eBPF programs are loaded. +// +//-------------------------------------------------------------------- +void processEBPFEvent(CONST PSYSMON_EVENT_HEADER eventHdr) +{ + if (eventHdr == NULL) { + fprintf(stderr, "processEBPFEvent invalid params\n"); + return; + } + + // The LinuxEBPFEvent is processed and logged directly + // The event contains: + // - m_ProcessId: PID of the process loading the BPF program + // - m_EventTime: Timestamp + // - m_BpfCmd: BPF command (e.g., BPF_PROG_LOAD) + // - m_ProgType: BPF program type + // - m_ProgId: BPF program file descriptor/ID + // - Extensions: Sid, ImagePath, ProgName + + // For now, dispatch directly to the event system + // Additional processing could be added here if needed + DispatchEvent(eventHdr); +} + //-------------------------------------------------------------------- // // processProcessAccess @@ -726,6 +757,9 @@ static void handleEvent(void *ctx, int cpu, void *data, uint32_t size) DispatchEvent(eventHdr); break; } + case LinuxEBPFEvent: + processEBPFEvent(eventHdr); + break; default: DispatchEvent(eventHdr); } @@ -889,6 +923,9 @@ void SetSyscallActive(bool *s, ULONG eventId) case SYSMONEVENT_ACCESS_PROCESS_EVENT_value: s[__NR_ptrace] = true; break; + case SYSMONEVENT_EBPF_EVENT_EVENT_value: + s[__NR_bpf] = true; + break; default: break; } @@ -1133,7 +1170,7 @@ void configChange() unsigned int *hashTypePtr = OPT_VALUE( HashAlgorithms ); hashType = *hashTypePtr; } - TCHAR buff[256]; + TCHAR buff[256] = {0}; LinuxGetFileHash(hashType, configFile, buff, _countof(buff)); SendConfigEvent( configFile, buff ); From 5da2c8ecffa019d1a32829bf443d4255e2e4521e Mon Sep 17 00:00:00 2001 From: Mario Hewardt Date: Sun, 25 Jan 2026 13:25:55 -0800 Subject: [PATCH 2/2] Update sysmonCommon submodule to latest Microsoft/SysmonCommon --- sysmonCommon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sysmonCommon b/sysmonCommon index 5595478..342e22c 160000 --- a/sysmonCommon +++ b/sysmonCommon @@ -1 +1 @@ -Subproject commit 5595478f301c91537048d04b319898ea4b322a1f +Subproject commit 342e22c084b5134dc4aaa5203c32cfdfb3667541