ebpf: support for long paths

Added support to report absolute path to a binary up to 4096 characters,
defined here:

https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/limits.h#L13
This commit is contained in:
Gustavo Iñiguez Goia 2022-06-27 14:42:54 +02:00
parent f54eb789ff
commit e7024e3fe0
8 changed files with 148 additions and 118 deletions

View file

@ -16,8 +16,8 @@ func NewExecEvent(pid, ppid, uid uint64, path string, comm [16]byte) *execEvent
UID: uid,
Comm: comm,
}
length := 128
if len(path) < 128 {
length := MaxPathLen
if len(path) < MaxPathLen {
length = len(path)
}
copy(ev.Filename[:], path[:length])

View file

@ -10,13 +10,20 @@ import (
elf "github.com/iovisor/gobpf/elf"
)
// MaxPathLen defines the maximum length of a path, as defined by the kernel:
// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/limits.h#L13
const MaxPathLen = 4096
// TaskCommLen is the maximum num of characters of the comm field
const TaskCommLen = 16
type execEvent struct {
Type uint64
PID uint64
PPID uint64
UID uint64
Filename [128]byte
Comm [16]byte
Filename [MaxPathLen]byte
Comm [TaskCommLen]byte
}
// Struct that holds the metadata of a connection.
@ -26,7 +33,7 @@ type networkEventT struct {
Pid uint64
UID uint64
Counter uint64
Comm [16]byte
Comm [TaskCommLen]byte
}
// List of supported events
@ -58,6 +65,7 @@ func initEventsStreamer() {
tracepoints := []string{
"tracepoint/sched/sched_process_exit",
//"tracepoint/sched/sched_process_exec",
//"tracepoint/syscalls/sys_enter_execve",
//"tracepoint/sched/sched_process_fork",
}
@ -130,7 +138,9 @@ func streamEventsWorker(id int, chn chan []byte, execEvents *eventsStore) {
case EV_TYPE_SCHED_EXIT:
//log.Warning("::: EXIT EVENT -> %d", event.PID)
if _, found := execEvents.isInStore(event.PID); found {
execEvents.delete(event.PID)
}
continue
}
// TODO: delete old events (by timeout)

View file

@ -1,7 +1,6 @@
package ebpf
import (
"bytes"
"encoding/binary"
"fmt"
"net"
@ -131,13 +130,13 @@ func getPidFromEbpf(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstP
return nil
}
comm := string(bytes.Trim(value.Comm[:], "\x00"))
comm := byteArrayToString(value.Comm[:])
proc = procmon.NewProcess(int(value.Pid), comm)
// use socket's UID. A process may have dropped privileges
proc.UID = int(value.UID)
if ev, found := execEvents.isInStore(value.Pid); found {
proc.Path = string(bytes.Trim(ev.Event.Filename[:], "\x00")) // ev.Proc.Path
proc.Path = byteArrayToString(ev.Event.Filename[:]) // ev.Proc.Path
proc.ReadCmdline()
proc.ReadCwd()
proc.ReadEnv()

View file

@ -1,6 +1,7 @@
package ebpf
import (
"bytes"
"fmt"
"unsafe"
@ -21,6 +22,21 @@ func mountDebugFS() error {
return nil
}
// Trim null characters, and return the left part of the byte array.
// NOTE: using BPF_MAP_TYPE_PERCPU_ARRAY does not initialize strings to 0,
// so we end up receiving events as follow:
// event.filename -> /usr/bin/iptables
// event.filename -> /bin/lsn/iptables (should be /bin/ls)
// It turns out, that there's a 0x00 character between "/bin/ls" and "n/iptables":
// [47 115 98 105 110 47 100 117 109 112 101 50 102 115 0 0 101 115
// ^^^
// TODO: investigate if there's any way of initializing the struct to 0
// like using __builtin_memset() (can't be used with PERCPU apparently)
func byteArrayToString(arr []byte) string {
temp := bytes.SplitAfter(arr, []byte("\x00"))[0]
return string(bytes.Trim(temp[:], "\x00"))
}
func deleteEbpfEntry(proto string, key unsafe.Pointer) bool {
if err := m.DeleteElement(ebpfMaps[proto].bpfmap, key); err != nil {
return false

View file

@ -5,7 +5,7 @@ cd opensnitch
wget https://github.com/torvalds/linux/archive/v5.8.tar.gz
tar -xf v5.8.tar.gz
patch linux-5.8/tools/lib/bpf/bpf_helpers.h < ebpf_prog/file.patch
cp ebpf_prog/opensnitch*.c ebpf_prog/Makefile linux-5.8/samples/bpf
cp ebpf_prog/opensnitch*.c ebpf_prog/common.h ebpf_prog/Makefile linux-5.8/samples/bpf
cd linux-5.8 && yes "" | make oldconfig && make prepare && make headers_install # (1 min)
cd samples/bpf && make
objdump -h opensnitch.o #you should see many section, number 1 should be called kprobe/tcp_v4_connect
@ -27,3 +27,12 @@ CONFIG_BPF_SYSCALL=y
CONFIG_BPF_EVENTS=y
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENTS=y
Also, in some distributions debugfs is not mounted automatically, so you need
to do it manually:
$ sudo mount -t debugfs none /sys/kernel/debug
In order to make it permanent add it to /etc/fstab:
debugfs /sys/kernel/debug debugfs defaults 0 0

82
ebpf_prog/common.h Normal file
View file

@ -0,0 +1,82 @@
#ifndef OPENSNITCH_COMMON_H
#define OPENSNITCH_COMMON_H
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
//https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/limits.h#L13
#ifndef MAX_PATH_LEN
#define MAX_PATH_LEN 4096
#endif
#define MAPSIZE 12000
#ifndef TASK_COMM_LEN
#define TASK_COMM_LEN 16
#endif
#define BUF_SIZE_MAP_NS 256
#define GLOBAL_MAP_NS "256"
//-------------------------------map definitions
// which github.com/iovisor/gobpf/elf expects
typedef struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int pinning;
char namespace[BUF_SIZE_MAP_NS];
} bpf_map_def;
enum bpf_pin_type {
PIN_NONE = 0,
PIN_OBJECT_NS,
PIN_GLOBAL_NS,
PIN_CUSTOM_NS,
};
//-----------------------------------
// even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32
typedef u64 pid_size_t;
typedef u64 uid_size_t;
enum events_type {
EVENT_NONE = 0,
EVENT_EXEC,
EVENT_FORK,
EVENT_SCHED_EXEC,
EVENT_SCHED_EXIT,
EVENT_BYTES_SENT,
EVENTS_BYTES_RECV
};
struct data_t {
u64 type;
u64 pid; // PID as in the userspace term (i.e. task->tgid in kernel)
u64 ppid; // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel)
u64 uid;
//u64 bytes_sent;
//u64 bytes_recv;
char filename[MAX_PATH_LEN];
char comm[TASK_COMM_LEN];
}__attribute__((packed));
//-----------------------------------------------------------------------------
// maps
struct bpf_map_def SEC("maps/heapstore") heapstore = {
.type = BPF_MAP_TYPE_PERCPU_ARRAY,
.key_size = sizeof(u32),
.value_size = sizeof(struct data_t),
.max_entries = 1
};
#endif

View file

@ -3,63 +3,7 @@
//uncomment if building on x86_32
//#define OPENSNITCH_x86_32
#include <linux/ptrace.h>
#include <uapi/linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#define ARGSIZE 128
#ifndef TASK_COMM_LEN
#define TASK_COMM_LEN 16
#endif
// even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32
typedef u64 pid_size_t;
typedef u64 uid_size_t;
//-------------------------------map definitions
// which github.com/iovisor/gobpf/elf expects
#define BUF_SIZE_MAP_NS 256
typedef struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int pinning;
char namespace[BUF_SIZE_MAP_NS];
} bpf_map_def;
enum bpf_pin_type {
PIN_NONE = 0,
PIN_OBJECT_NS,
PIN_GLOBAL_NS,
PIN_CUSTOM_NS,
};
//---------------------------------------------------------------------------//
enum events_type {
EVENT_NONE = 0,
EVENT_EXEC,
EVENT_FORK,
EVENT_SCHED_EXEC,
EVENT_SCHED_EXIT
};
struct data_t {
u64 type;
u64 pid; // PID as in the userspace term (i.e. task->tgid in kernel)
u64 ppid; // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel)
u64 uid;
char filename[ARGSIZE];
char comm[TASK_COMM_LEN];
}__attribute__((packed));
#include "common.h"
struct bpf_map_def SEC("maps/proc-events") events = {
// Since kernel 4.4
@ -73,7 +17,6 @@ static __always_inline void new_event(struct pt_regs *ctx, struct data_t* data)
{
// initializing variables with __builtin_memset() is required
// for compatibility with bpf on kernel 4.4
__builtin_memset(data, 0, sizeof(struct data_t));
struct task_struct *task={0};
struct task_struct *parent={0};
@ -99,27 +42,33 @@ SEC("kprobe/sys_execve")
int kprobe__sys_execve(struct pt_regs *ctx)
{
const char *filename = (const char *)PT_REGS_PARM2(ctx);
// TODO: use ringbuffer to allocate the absolute path[4096] + arguments
// TODO: extract args
//const char *argv = (const char *)PT_REGS_PARM3(ctx);
struct data_t data={0};
new_event(ctx, &data);
data.type = EVENT_EXEC;
bpf_probe_read_user_str(&data.filename, sizeof(data.filename), filename);
int zero = 0;
struct data_t *data = bpf_map_lookup_elem(&heapstore, &zero);
if (!data){ return 0; }
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(struct data_t));
new_event(ctx, data);
data->type = EVENT_EXEC;
bpf_probe_read_user_str(&data->filename, sizeof(data->filename), filename);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
return 0;
};
SEC("tracepoint/sched/sched_process_exit")
int tracepoint__sched_sched_process_exit(struct pt_regs *ctx)
{
struct data_t data={0};
__builtin_memset(&data, 0, sizeof(data));
new_event(ctx, &data);
data.type = EVENT_SCHED_EXIT;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(struct data_t));
int zero = 0;
struct data_t *data = bpf_map_lookup_elem(&heapstore, &zero);
if (!data){ return 0; }
//__builtin_memset(data, 0, sizeof(struct data_t));
new_event(ctx, data);
data->type = EVENT_SCHED_EXIT;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
return 0;
};
return 0;
};

View file

@ -3,48 +3,13 @@
//uncomment if building on x86_32
//#define OPENSNITCH_x86_32
#include <linux/sched.h>
#include <linux/ptrace.h>
#include "common.h"
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include <uapi/linux/tcp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <net/sock.h>
#include <net/udp_tunnel.h>
#include <net/inet_sock.h>
#define MAPSIZE 12000
#ifndef TASK_COMM_LEN
#define TASK_COMM_LEN 16
#endif
//-------------------------------map definitions
// which github.com/iovisor/gobpf/elf expects
#define BUF_SIZE_MAP_NS 256
typedef struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int pinning;
char namespace[BUF_SIZE_MAP_NS];
} bpf_map_def;
enum bpf_pin_type {
PIN_NONE = 0,
PIN_OBJECT_NS,
PIN_GLOBAL_NS,
PIN_CUSTOM_NS,
};
//-----------------------------------
// even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32
typedef u64 pid_size_t;
typedef u64 uid_size_t;
struct tcp_key_t {
u16 sport;