Skip to content

Commit

Permalink
Make the ebpf rate limiter generic and use it for ptrace
Browse files Browse the repository at this point in the history
  • Loading branch information
spikat committed Dec 19, 2024
1 parent b73a0fe commit 2af1c5f
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 153 deletions.
124 changes: 2 additions & 122 deletions pkg/security/ebpf/c/include/helpers/activity_dump.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "container.h"
#include "events.h"
#include "process.h"
#include "rate_limiter.h"

__attribute__((always_inline)) struct activity_dump_config *lookup_or_delete_traced_pid(u32 pid, u64 now, u64 *cookie) {
if (cookie == NULL) {
Expand Down Expand Up @@ -238,127 +239,6 @@ __attribute__((always_inline)) void cleanup_traced_state(u32 pid) {
bpf_map_delete_elem(&traced_pids, &pid);
}

enum rate_limiter_algo_ids
{
RL_ALGO_BASIC = 0,
RL_ALGO_BASIC_HALF,
RL_ALGO_DECREASING_DROPRATE,
RL_ALGO_INCREASING_DROPRATE,
RL_ALGO_TOTAL_NUMBER,
};

__attribute__((always_inline)) u8 activity_dump_rate_limiter_reset_period(u64 now, struct activity_dump_rate_limiter_ctx *rate_ctx_p) {
rate_ctx_p->current_period = now;
rate_ctx_p->counter = 0;
#ifndef __BALOUM__ // do not change algo during unit tests
rate_ctx_p->algo_id = now % RL_ALGO_TOTAL_NUMBER;
#endif /* __BALOUM__ */
return 1;
}

__attribute__((always_inline)) u8 activity_dump_rate_limiter_allow_basic(struct activity_dump_config *config, u64 now, struct activity_dump_rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > 1000000000) { // if more than 1 sec ellapsed we reset the period
return activity_dump_rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= config->events_rate) { // if we already allowed more than rate
return 0;
} else {
return 1;
}
}

__attribute__((always_inline)) u8 activity_dump_rate_limiter_allow_basic_half(struct activity_dump_config *config, u64 now, struct activity_dump_rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > 1000000000 / 2) { // if more than 0.5 sec ellapsed we reset the period
return activity_dump_rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= config->events_rate / 2) { // if we already allowed more than rate / 2
return 0;
} else {
return 1;
}
}

__attribute__((always_inline)) u8 activity_dump_rate_limiter_allow_decreasing_droprate(struct activity_dump_config *config, u64 now, struct activity_dump_rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > 1000000000) { // if more than 1 sec ellapsed we reset the period
return activity_dump_rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= config->events_rate) { // if we already allowed more than rate
return 0;
} else if (rate_ctx_p->counter < (config->events_rate / 4)) { // first 1/4 is not rate limited
return 1;
}

// if we are between rate / 4 and rate, apply a decreasing rate of:
// (counter * 100) / (rate) %
else if (now % ((rate_ctx_p->counter * 100) / config->events_rate) == 0) {
return 1;
}
return 0;
}

__attribute__((always_inline)) u8 activity_dump_rate_limiter_allow_increasing_droprate(struct activity_dump_config *config, u64 now, struct activity_dump_rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > 1000000000) { // if more than 1 sec ellapsed we reset the period
return activity_dump_rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= config->events_rate) { // if we already allowed more than rate
return 0;
} else if (rate_ctx_p->counter < (config->events_rate / 4)) { // first 1/4 is not rate limited
return 1;
}

// if we are between rate / 4 and rate, apply an increasing rate of:
// 100 - ((counter * 100) / (rate)) %
else if (now % (100 - ((rate_ctx_p->counter * 100) / config->events_rate)) == 0) {
return 1;
}
return 0;
}

__attribute__((always_inline)) u8 activity_dump_rate_limiter_allow(struct activity_dump_config *config, u64 cookie, u64 now, u8 should_count) {
struct activity_dump_rate_limiter_ctx *rate_ctx_p = bpf_map_lookup_elem(&activity_dump_rate_limiters, &cookie);
if (rate_ctx_p == NULL) {
struct activity_dump_rate_limiter_ctx rate_ctx = {
.current_period = now,
.counter = should_count,
.algo_id = now % RL_ALGO_TOTAL_NUMBER,
};
bpf_map_update_elem(&activity_dump_rate_limiters, &cookie, &rate_ctx, BPF_ANY);
return 1;
}

if (now < rate_ctx_p->current_period) { // this should never happen, ignore
return 0;
}
u64 delta = now - rate_ctx_p->current_period;

u8 allow;
switch (rate_ctx_p->algo_id) {
case RL_ALGO_BASIC:
allow = activity_dump_rate_limiter_allow_basic(config, now, rate_ctx_p, delta);
break;
case RL_ALGO_BASIC_HALF:
allow = activity_dump_rate_limiter_allow_basic_half(config, now, rate_ctx_p, delta);
break;
case RL_ALGO_DECREASING_DROPRATE:
allow = activity_dump_rate_limiter_allow_decreasing_droprate(config, now, rate_ctx_p, delta);
break;
case RL_ALGO_INCREASING_DROPRATE:
allow = activity_dump_rate_limiter_allow_increasing_droprate(config, now, rate_ctx_p, delta);
break;
default: // should never happen, ignore
return 0;
}

if (allow && should_count) {
__sync_fetch_and_add(&rate_ctx_p->counter, 1);
}
return (allow);
}

__attribute__((always_inline)) u32 is_activity_dump_running(void *ctx, u32 pid, u64 now, u32 event_type) {
u64 cookie = 0;
struct activity_dump_config *config = NULL;
Expand Down Expand Up @@ -393,7 +273,7 @@ __attribute__((always_inline)) u32 is_activity_dump_running(void *ctx, u32 pid,
return 0;
}

if (!activity_dump_rate_limiter_allow(config, cookie, now, 1)) {
if (!activity_dump_rate_limiter_allow(config->events_rate, cookie, now, 1)) {
return 0;
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/security/ebpf/c/include/helpers/approvers.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "constants/enums.h"
#include "maps.h"
#include "rate_limiter.h"

void __attribute__((always_inline)) monitor_event_approved(u64 event_type, u32 approver_type) {
struct bpf_map_def *approver_stats = select_buffer(&fb_approver_stats, &bb_approver_stats, APPROVER_MONITOR_KEY);
Expand Down Expand Up @@ -355,7 +356,7 @@ enum SYSCALL_STATE __attribute__((always_inline)) approve_syscall(struct syscall
struct activity_dump_config *config = lookup_or_delete_traced_pid(tgid, now, cookie);
if (config != NULL) {
// is this event type traced ?
if (mask_has_event(config->event_mask, syscall->type) && activity_dump_rate_limiter_allow(config, *cookie, now, 0)) {
if (mask_has_event(config->event_mask, syscall->type) && activity_dump_rate_limiter_allow(config->events_rate, *cookie, now, 0)) {
if (syscall->state == DISCARDED) {
syscall->resolver.flags |= SAVED_BY_ACTIVITY_DUMP;
}
Expand Down
165 changes: 165 additions & 0 deletions pkg/security/ebpf/c/include/helpers/rate_limiter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#ifndef _RATE_LIMITER_H_
#define _RATE_LIMITER_H_

#include "maps.h"
#include "constants/macros.h"
#include "structs/rate_limiter.h"

enum rate_limiter_algo_ids
{
RL_ALGO_BASIC = 0,
RL_ALGO_BASIC_HALF,
RL_ALGO_DECREASING_DROPRATE,
RL_ALGO_INCREASING_DROPRATE,
RL_ALGO_TOTAL_NUMBER,
};

__attribute__((always_inline)) u8 rate_limiter_reset_period(u64 now, struct rate_limiter_ctx *rate_ctx_p) {
rate_ctx_p->current_period = now;
rate_ctx_p->counter = 0;
#ifndef __BALOUM__ // do not change algo during unit tests
rate_ctx_p->algo_id = now % RL_ALGO_TOTAL_NUMBER;
#endif /* __BALOUM__ */
return 1;
}

__attribute__((always_inline)) u8 rate_limiter_allow_basic(u32 rate, u64 now, struct rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > SEC_TO_NS(1)) { // if more than 1 sec ellapsed we reset the period
return rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= rate) { // if we already allowed more than rate
return 0;
} else {
return 1;
}
}

__attribute__((always_inline)) u8 rate_limiter_allow_basic_half(u32 rate, u64 now, struct rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > SEC_TO_NS(1) / 2) { // if more than 0.5 sec ellapsed we reset the period
return rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= rate / 2) { // if we already allowed more than rate / 2
return 0;
} else {
return 1;
}
}

__attribute__((always_inline)) u8 rate_limiter_allow_decreasing_droprate(u32 rate, u64 now, struct rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > SEC_TO_NS(1)) { // if more than 1 sec ellapsed we reset the period
return rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= rate) { // if we already allowed more than rate
return 0;
} else if (rate_ctx_p->counter < (rate / 4)) { // first 1/4 is not rate limited
return 1;
}

// if we are between rate / 4 and rate, apply a decreasing rate of:
// (counter * 100) / (rate) %
else if (now % ((rate_ctx_p->counter * 100) / rate) == 0) {
return 1;
}
return 0;
}

__attribute__((always_inline)) u8 rate_limiter_allow_increasing_droprate(u32 rate, u64 now, struct rate_limiter_ctx *rate_ctx_p, u64 delta) {
if (delta > SEC_TO_NS(1)) { // if more than 1 sec ellapsed we reset the period
return rate_limiter_reset_period(now, rate_ctx_p);
}

if (rate_ctx_p->counter >= rate) { // if we already allowed more than rate
return 0;
} else if (rate_ctx_p->counter < (rate / 4)) { // first 1/4 is not rate limited
return 1;
}

// if we are between rate / 4 and rate, apply an increasing rate of:
// 100 - ((counter * 100) / (rate)) %
else if (now % (100 - ((rate_ctx_p->counter * 100) / rate)) == 0) {
return 1;
}
return 0;
}

__attribute__((always_inline)) u8 rate_limiter_allow_gen(struct rate_limiter_ctx *rate_ctx_p, u32 rate, u64 now, u8 should_count) {
if (now < rate_ctx_p->current_period) { // this should never happen, ignore
return 0;
}
u64 delta = now - rate_ctx_p->current_period;

u8 allow;
switch (rate_ctx_p->algo_id) {
case RL_ALGO_BASIC:
allow = rate_limiter_allow_basic(rate, now, rate_ctx_p, delta);
break;
case RL_ALGO_BASIC_HALF:
allow = rate_limiter_allow_basic_half(rate, now, rate_ctx_p, delta);
break;
case RL_ALGO_DECREASING_DROPRATE:
allow = rate_limiter_allow_decreasing_droprate(rate, now, rate_ctx_p, delta);
break;
case RL_ALGO_INCREASING_DROPRATE:
allow = rate_limiter_allow_increasing_droprate(rate, now, rate_ctx_p, delta);
break;
default: // should never happen, ignore
return 0;
}

if (allow && should_count) {
__sync_fetch_and_add(&rate_ctx_p->counter, 1);
}
return (allow);
}

// For now the generic rate is staticaly defined
// TODO: put it configurable
#define GENERIC_RATE_LIMITER_RATE 100

__attribute__((always_inline)) u8 rate_limiter_allow(u32 pid, u64 now, u8 should_count) {
if (now == 0) {
now = bpf_ktime_get_ns();
}
if (pid == 0) {
pid = bpf_get_current_pid_tgid() >> 32;
}

struct rate_limiter_ctx *rate_ctx_p = bpf_map_lookup_elem(&rate_limiters, &pid);
if (rate_ctx_p == NULL) {
struct rate_limiter_ctx rate_ctx = {
.current_period = now,
.counter = should_count,
.algo_id = now % RL_ALGO_TOTAL_NUMBER,
};
bpf_map_update_elem(&rate_limiters, &pid, &rate_ctx, BPF_ANY);
return 1;
}

u32 rate = GENERIC_RATE_LIMITER_RATE;
return rate_limiter_allow_gen(rate_ctx_p, rate, now, should_count);
}
#define rate_limiter_allow_simple() rate_limiter_allow(0, 0, 1)

__attribute__((always_inline)) u8 activity_dump_rate_limiter_allow(u32 rate, u64 cookie, u64 now, u8 should_count) {
if (now == 0) {
now = bpf_ktime_get_ns();
}

struct rate_limiter_ctx *rate_ctx_p = bpf_map_lookup_elem(&activity_dump_rate_limiters, &cookie);
if (rate_ctx_p == NULL) {
struct rate_limiter_ctx rate_ctx = {
.current_period = now,
.counter = should_count,
.algo_id = now % RL_ALGO_TOTAL_NUMBER,
};
bpf_map_update_elem(&activity_dump_rate_limiters, &cookie, &rate_ctx, BPF_ANY);
return 1;
}

return rate_limiter_allow_gen(rate_ctx_p, rate, now, should_count);
}

#endif /* _RATE_LIMITER_H_ */
22 changes: 22 additions & 0 deletions pkg/security/ebpf/c/include/hooks/ptrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,29 @@
#include "helpers/discarders.h"
#include "helpers/syscalls.h"

// list of requests we don't want to rate limit
const int important_reqs[] = {
PTRACE_ATTACH,
PTRACE_DETACH,
PTRACE_TRACEME,
PTRACE_SEIZE,
PTRACE_KILL,
PTRACE_SETOPTIONS,
};

HOOK_SYSCALL_ENTRY3(ptrace, u32, request, pid_t, pid, void *, addr) {
u8 found = 0;
for (int i = 0; i < sizeof(important_reqs) / sizeof(int); i++) {
if (request == important_reqs[i]) {
found = 1;
break;
}
}
if (!found && !rate_limiter_allow_simple()) {
// for other requests types than a define list, rate limit the events
return 0;
}

struct syscall_cache_t syscall = {
.type = EVENT_PTRACE,
.ptrace = {
Expand Down
3 changes: 2 additions & 1 deletion pkg/security/ebpf/c/include/maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ BPF_HASH_MAP(secprofs_syscalls, u64, struct security_profile_syscalls_t, 1) // m
BPF_HASH_MAP(auid_approvers, u32, struct event_mask_filter_t, 128)
BPF_HASH_MAP(auid_range_approvers, u32, struct u32_range_filter_t, EVENT_MAX)

BPF_LRU_MAP(activity_dump_rate_limiters, u64, struct activity_dump_rate_limiter_ctx, 1) // max entries will be overridden at runtime
BPF_LRU_MAP(activity_dump_rate_limiters, u64, struct rate_limiter_ctx, 1) // max entries will be overridden at runtime
BPF_LRU_MAP(rate_limiters, u32, struct rate_limiter_ctx, 1) // max entries will be overridden at runtime
BPF_LRU_MAP(mount_ref, u32, struct mount_ref_t, 64000)
BPF_LRU_MAP(bpf_maps, u32, struct bpf_map_t, 4096)
BPF_LRU_MAP(bpf_progs, u32, struct bpf_prog_t, 4096)
Expand Down
7 changes: 0 additions & 7 deletions pkg/security/ebpf/c/include/structs/activity_dump.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
#ifndef _STRUCTS_ACTIVITY_DUMP_H_
#define _STRUCTS_ACTIVITY_DUMP_H_

struct activity_dump_rate_limiter_ctx {
u64 current_period;
u32 counter;
u8 algo_id;
u8 padding[3];
};

struct activity_dump_config {
u64 event_mask;
u64 timeout;
Expand Down
12 changes: 12 additions & 0 deletions pkg/security/ebpf/c/include/structs/rate_limiter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef _STRUCTS_RATE_LIMITER_H_
#define _STRUCTS_RATE_LIMITER_H_

struct rate_limiter_ctx {
u64 current_period;
u32 counter;
u8 algo_id;
u8 padding[3];
};


#endif /* _STRUCTS_RATE_LIMITER_H_ */
Loading

0 comments on commit 2af1c5f

Please sign in to comment.