Skip to content

Commit

Permalink
feat(events): streamline event argument types
Browse files Browse the repository at this point in the history
1. Reduce the type variance via hardcoding possible types
2. Move argument types to the types package
3. Make corresponding changes in eBPF code
  • Loading branch information
NDStrahilevitz committed Nov 8, 2024
1 parent 1d99241 commit 523606d
Show file tree
Hide file tree
Showing 14 changed files with 980 additions and 1,009 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,5 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect
)

replace github.com/aquasecurity/tracee/types => ./types
134 changes: 46 additions & 88 deletions pkg/bufferdecoder/eventsreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,6 @@ import (
"github.com/aquasecurity/tracee/types/trace"
)

// argType is an enum that encodes the argument types that the BPF program may write to the shared buffer
// argument types should match defined values in ebpf code
type ArgType uint8

const (
noneT ArgType = iota
intT
uintT
longT
ulongT
offT
modeT
devT
sizeT
pointerT
strT
strArrT
sockAddrT
bytesT
u16T
credT
intArr2T
uint64ArrT
u8T
timespecT
)

// These types don't match the ones defined in the ebpf code since they are not being used by syscalls arguments.
// They have their own set of value to avoid collision in the future.
const (
argsArrT ArgType = iota + 0x80
boolT
)

// readArgFromBuff read the next argument from the buffer.
// Return the index of the argument and the parsed argument.
func readArgFromBuff(id events.ID, ebpfMsgDecoder *EbpfDecoder, params []trace.ArgMeta,
Expand All @@ -69,47 +35,47 @@ func readArgFromBuff(id events.ID, ebpfMsgDecoder *EbpfDecoder, params []trace.A
argType := GetParamType(arg.Type)

switch argType {
case u8T:
case trace.U8_T:
var data uint8
err = ebpfMsgDecoder.DecodeUint8(&data)
res = data
case u16T:
case trace.U16_T:
var data uint16
err = ebpfMsgDecoder.DecodeUint16(&data)
res = data
case intT:
case trace.INT_T:
var data int32
err = ebpfMsgDecoder.DecodeInt32(&data)
res = data
case uintT, devT, modeT:
case trace.UINT_T:
var data uint32
err = ebpfMsgDecoder.DecodeUint32(&data)
res = data
case longT:
case trace.LONG_T:
var data int64
err = ebpfMsgDecoder.DecodeInt64(&data)
res = data
case ulongT, offT, sizeT:
case trace.ULONG_T:
var data uint64
err = ebpfMsgDecoder.DecodeUint64(&data)
res = data
case boolT:
case trace.BOOL_T:
var data bool
err = ebpfMsgDecoder.DecodeBool(&data)
res = data
case pointerT:
case trace.POINTER_T:
var data uint64
err = ebpfMsgDecoder.DecodeUint64(&data)
res = uintptr(data)
case sockAddrT:
case trace.SOCK_ADDR_T:
res, err = readSockaddrFromBuff(ebpfMsgDecoder)
case credT:
case trace.CRED_T:
var data SlimCred
err = ebpfMsgDecoder.DecodeSlimCred(&data)
res = trace.SlimCred(data) // here we cast to trace.SlimCred to ensure we send the public interface and not bufferdecoder.SlimCred
case strT:
case trace.STR_T:
res, err = readStringFromBuff(ebpfMsgDecoder)
case strArrT:
case trace.STR_ARR_T:
// TODO optimization: create slice after getting arrLen
var ss []string
var arrLen uint8
Expand All @@ -125,7 +91,7 @@ func readArgFromBuff(id events.ID, ebpfMsgDecoder *EbpfDecoder, params []trace.A
ss = append(ss, s)
}
res = ss
case argsArrT:
case trace.ARGS_ARR_T:
var ss []string
var arrLen uint32
var argNum uint32
Expand All @@ -150,7 +116,7 @@ func readArgFromBuff(id events.ID, ebpfMsgDecoder *EbpfDecoder, params []trace.A
ss = append(ss, "?")
}
res = ss
case bytesT:
case trace.BYTES_T:
var size uint32
err = ebpfMsgDecoder.DecodeUint32(&size)
if err != nil {
Expand All @@ -161,21 +127,21 @@ func readArgFromBuff(id events.ID, ebpfMsgDecoder *EbpfDecoder, params []trace.A
return uint(argIdx), arg, errfmt.Errorf("byte array size too big: %d", size)
}
res, err = ReadByteSliceFromBuff(ebpfMsgDecoder, int(size))
case intArr2T:
case trace.INT_ARR_2_T:
var intArray [2]int32
err = ebpfMsgDecoder.DecodeIntArray(intArray[:], 2)
if err != nil {
return uint(argIdx), arg, errfmt.Errorf("error reading int elements: %v", err)
}
res = intArray
case uint64ArrT:
case trace.UINT64_ARR_T:
ulongArray := make([]uint64, 0)
err := ebpfMsgDecoder.DecodeUint64Array(&ulongArray)
if err != nil {
return uint(argIdx), arg, errfmt.Errorf("error reading ulong elements: %v", err)
}
res = ulongArray
case timespecT:
case trace.TIMESPEC_T:
var sec int64
var nsec int64
err = ebpfMsgDecoder.DecodeInt64(&sec)
Expand All @@ -196,53 +162,45 @@ func readArgFromBuff(id events.ID, ebpfMsgDecoder *EbpfDecoder, params []trace.A
return uint(argIdx), arg, nil
}

func GetParamType(paramType string) ArgType {
func GetParamType(paramType string) trace.DecodeAs {
switch paramType {
case "int", "pid_t", "uid_t", "gid_t", "mqd_t", "clockid_t", "const clockid_t", "key_t", "key_serial_t", "timer_t":
return intT
case "unsigned int", "u32":
return uintT
case "int":
return trace.INT_T
case "unsigned int":
return trace.UINT_T
case "long":
return longT
case "unsigned long", "u64":
return ulongT
return trace.LONG_T
case "unsigned long":
return trace.ULONG_T
case "u16":
return trace.U16_T
case "u8":
return trace.U8_T
case "bool":
return boolT
case "off_t", "loff_t":
return offT
case "mode_t":
return modeT
case "dev_t":
return devT
case "size_t":
return sizeT
case "void*", "const void*":
return pointerT
case "char*", "const char*":
return strT
return trace.BOOL_T
case "void*":
return trace.POINTER_T
case "char*":
return trace.STR_T
case "const char*const*": // used by execve(at) argv and env
return strArrT
case "const char**": // used by sched_process_exec argv and envp
return argsArrT
case "const struct sockaddr*", "struct sockaddr*":
return sockAddrT
return trace.STR_ARR_T
case "const char**": // used by sched_process_exec argv and env
return trace.ARGS_ARR_T
case "struct sockaddr*":
return trace.SOCK_ADDR_T
case "bytes":
return bytesT
return trace.BYTES_T
case "int[2]":
return intArr2T
return trace.INT_ARR_2_T
case "slim_cred_t":
return credT
case "umode_t":
return u16T
case "u8":
return u8T
return trace.CRED_T
case "unsigned long[]", "[]trace.HookedSymbolData":
return uint64ArrT
case "struct timespec*", "const struct timespec*":
return timespecT
return trace.UINT64_ARR_T
case "struct timespec*":
return trace.TIMESPEC_T
default:
// Default to pointer (printed as hex) for unsupported types
return pointerT
return trace.POINTER_T
}
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/bufferdecoder/eventsreader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ func TestReadArgFromBuff(t *testing.T) {
input: []byte{0,
0xFF, 0xFF, 0xFF, 0xFF, // 4294967295
},
params: []trace.ArgMeta{{Type: "dev_t", Name: "devT0"}},
params: []trace.ArgMeta{{Type: "unsigned int", Name: "devT0"}},
expectedArg: uint32(4294967295),
},
{
name: "offT",
input: []byte{0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
},
params: []trace.ArgMeta{{Type: "off_t", Name: "offT0"}},
params: []trace.ArgMeta{{Type: "long", Name: "offT0"}},
expectedArg: uint64(18446744073709551615),
},
{
Expand Down Expand Up @@ -161,7 +161,7 @@ func TestReadArgFromBuff(t *testing.T) {
input: []byte{1,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
},
params: []trace.ArgMeta{{Type: "const char*", Name: "str0"}, {Type: "off_t", Name: "offT1"}},
params: []trace.ArgMeta{{Type: "const char*", Name: "str0"}, {Type: "long", Name: "offT1"}},
expectedArg: uint64(18446744073709551615),
},
}
Expand Down
77 changes: 43 additions & 34 deletions pkg/ebpf/c/common/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <vmlinux.h>

#include <types.h>
#include <common/context.h>
#include <common/hash.h>
#include <common/network.h>
Expand Down Expand Up @@ -30,35 +31,46 @@ statfunc buf_t *get_buf(int idx)
}

// biggest elem to be saved with 'save_to_submit_buf' should be defined here:
#define MAX_ELEMENT_SIZE bpf_core_type_size(struct sockaddr_un)
#define MAX_ELEMENT_SIZE sizeof(struct sockaddr_un)

statfunc int save_to_submit_buf(args_buffer_t *buf, void *ptr, u32 size, u8 index)
{
// Data saved to submit buf: [index][ ... buffer[size] ... ]

if (size == 0)
barrier();
// set argument size bounds
if (size == 0 || size > MAX_ELEMENT_SIZE || buf->offset >= ARGS_BUF_SIZE)
return 0;

barrier();
if (buf->offset > ARGS_BUF_SIZE - 1)
u32 buffer_index_offset = buf->offset + 1; // buffer offset after writing the index
if (buffer_index_offset > ARGS_BUF_SIZE)
return 0;

// Save argument index
// Save argument index (add 1 to index later)
buf->args[buf->offset] = index;

// Satisfy verifier
if (buf->offset > ARGS_BUF_SIZE - (MAX_ELEMENT_SIZE + 1))
// Force verifier to compare size register with a bound maximum
asm volatile("if %[size] < %[max_size] goto +1;\n"
"%[size] = %[max_size];\n"
:
: [size] "r"(size), [max_size] "i"(MAX_ELEMENT_SIZE));

u32 buffer_arg_offset = buffer_index_offset + size; // buffer offset after writing the ptr

// Satisfy verifier - offset+index+size(offset+size+1) must not go above ARGS_BUF_SIZE
if (buffer_arg_offset > ARGS_BUF_SIZE)
return 0;

// Read into buffer
if (bpf_probe_read(&(buf->args[buf->offset + 1]), size, ptr) == 0) {
// We update offset only if all writes were successful
buf->offset += size + 1;
buf->argnum++;
return 1;
}
// Read into buffer after the argument index
if (bpf_probe_read_kernel(&(buf->args[buffer_index_offset]), size, ptr) < 0)
return 0;

return 0;
// Update buffer only if all writes were successful:
// 1. Update the buffer offset
// 2. Increment the argument count
buf->offset = buffer_arg_offset;
buf->argnum++;
return 1;
}

statfunc int save_bytes_to_buf(args_buffer_t *buf, void *ptr, u32 size, u8 index)
Expand Down Expand Up @@ -260,8 +272,9 @@ statfunc int save_str_arr_to_buf(args_buffer_t *buf, const char __user *const __
statfunc int save_args_str_arr_to_buf(
args_buffer_t *buf, const char *start, const char *end, int elem_num, u8 index)
{
// Data saved to submit buf: [index][len][arg_len][arg #][null delimited string array]
// Note: This helper saves null (0x00) delimited string array into buf
// Data saved to submit buf: [index][len][arg_len][arg #][array of null delimited string]
// Note: This helper saves null (0x00) delimited string array into buf in the format:
// [[str1][\n][str2][\n][...][strn][\n])]

if (start >= end)
return 0;
Expand Down Expand Up @@ -339,39 +352,35 @@ statfunc int save_sockaddr_to_buf(args_buffer_t *buf, struct socket *sock, u8 in

#define DEC_ARG(n, enc_arg) ((enc_arg >> (8 * n)) & 0xFF)

// types whose arguments needs to be directly in type_size_table (arg = (void *) args->args[i])
#define BITMASK_INDIRECT_VALUE_TYPES \
((u64) 1 << STR_T | (u64) 1 << SOCKADDR_T | (u64) 1 << INT_ARR_2_T | (u64) 1 << TIMESPEC_T)
((u64) 1 << INT_ARR_2_T | (u64) 1 << STR_T | (u64) 1 << SOCKADDR_T | (u64) 1 << TIMESPEC_T)

// types whose arguments needs to be handled through their address in type_size_table
// ((arg = (void *) &args->args[i]))
#define BITMASK_COMMON_TYPES \
((u64) 1 << INT_T | (u64) 1 << UINT_T | (u64) 1 << LONG_T | (u64) 1 << ULONG_T | \
(u64) 1 << OFF_T_T | (u64) 1 << MODE_T_T | (u64) 1 << DEV_T_T | (u64) 1 << SIZE_T_T | \
(u64) 1 << POINTER_T | (u64) 1 << STR_ARR_T | (u64) 1 << BYTES_T | (u64) 1 << U16_T | \
(u64) 1 << CRED_T | (u64) 1 << UINT64_ARR_T | (u64) 1 << U8_T)

#define ARG_TYPE_MAX_ARRAY (u8) TIMESPEC_T // last element defined in argument_type_e
(u64) 1 << U16_T | (u64) 1 << U8_T | (u64) 1 << UINT64_ARR_T | (u64) 1 << POINTER_T | \
(u64) 1 << BYTES_T | (u64) 1 << STR_ARR_T | (u64) 1 << U8_T)

// Ensure that only values that can be held by an u8 are assigned to sizes.
// Ensure that only values that can be held by an u32 are assigned to sizes.
// If the size is greater than 255, assign 0 (making it evident) and handle it as a special case.
static u8 type_size_table[ARG_TYPE_MAX_ARRAY + 1] = {
static u32 type_size_table[ARG_TYPE_MAX_ARRAY + 1] = {
[NONE_T] = 0,
[INT_T] = sizeof(int),
[UINT_T] = sizeof(unsigned int),
[LONG_T] = sizeof(long),
[ULONG_T] = sizeof(unsigned long),
[OFF_T_T] = sizeof(off_t),
[MODE_T_T] = sizeof(mode_t),
[DEV_T_T] = sizeof(dev_t),
[SIZE_T_T] = sizeof(size_t),
[U16_T] = sizeof(unsigned short),
[U8_T] = sizeof(unsigned char),
[INT_ARR_2_T] = sizeof(int[2]),
[UINT64_ARR_T] = 0,
[POINTER_T] = sizeof(void *),
[BYTES_T] = 0,
[STR_T] = 0,
[STR_ARR_T] = 0,
[SOCKADDR_T] = sizeof(short),
[BYTES_T] = 0,
[U16_T] = sizeof(u16),
[CRED_T] = sizeof(struct cred),
[INT_ARR_2_T] = sizeof(int[2]),
[UINT64_ARR_T] = 0,
[U8_T] = sizeof(u8),
[TIMESPEC_T] = 0,
};

Expand Down
Loading

0 comments on commit 523606d

Please sign in to comment.