Skip to content

Commit 8c9e547

Browse files
author
Geyslan G. Bem
committed
event-auditor: introduce match() for patterns
This commit does a lot of changes attempting to shrink a basic match() function into the ka_ea_process.bpf.c program. match() treats only the '*' and '?' wildcards. Its algorithm is inspired by two sources [1-2], being modified to satisfy the ebpf verifier when it comes to bounded loops. Despite the algorithm design undertaken, the verifier still complained of 1 million instructions being exceeded. Two decisions were made: - reduce iterations (reducing MAX_FILENAME_LEN from to 256 to 42 and MAX_PATTERN_LEN (former PATTERN_MAX_LEN) from 16 to 7. - split the ebpf program logic into 3 making use of tail calls, leaving one entirely for the match() logic. After these, the verifier didn't complain. The first decision was taken as a temporary measure giving rise to questions about the design: - Would '#pragma unroll n' be a solution? (this committer was unable to find a magic n) - Should we take another route for pattern matching? This commit also adds: - ka_ea_process_jmp_map to ka_ea_process.bpf.c - global variables accessible from the tail programs through .bss map - get_task_filename - program logic split - get_task_ids() - program logic split - task_auditable_0() and task_auditable_1() - program logic split - ka_ea_sched_process_exec_0() and ka_ea_sched_process_exec_1() - program logic split - check_hash_elem() - handler for bpf_for_each_map_elem() iterator - callback_ctx struct - input/output for the check_hash_elem() - KABPFTailProg struct to ebpfCommon.go - and put it as a KABPFProg slice field - ProcessJMPMapElement struct and methods to sharedMaps.go - PopulateProcessJMPMap() to processSpecHandler.go This gets rid of: - task_context struct - get_task_context() [1] https://github.com/kirkjkrauss/MatchingWildcards/blob/master/Listing1.cpp [2] http://dodobyte.com/wildcard.html
1 parent b901f92 commit 8c9e547

8 files changed

Lines changed: 292 additions & 129 deletions

File tree

KubeArmor/BPF/ka_ea_process.bpf.c

Lines changed: 168 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77

88
#include "maps.bpf.h"
99

10+
struct {
11+
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
12+
__type(key, u32);
13+
__type(value, u32);
14+
__uint(max_entries, 2);
15+
} ka_ea_process_jmp_map SEC(".maps");
16+
1017
struct {
1118
__uint(type, BPF_MAP_TYPE_HASH);
1219
__type(key, struct pattern_key);
@@ -28,121 +35,169 @@ struct {
2835
__uint(max_entries, 1 << 10);
2936
} ka_ea_process_filter_map SEC(".maps");
3037

31-
// get_task_context fills *tctx with task and event data;
32-
// with latter only if it's not NULL
33-
static __always_inline
34-
long get_task_context(struct task_context *tctx,
35-
const struct trace_event_raw_sched_process_exec *ectx)
38+
unsigned int pid;
39+
unsigned int tid;
40+
unsigned int pid_ns;
41+
unsigned int mnt_ns;
42+
char comm[TASK_COMM_LEN];
43+
char filename[MAX_FILENAME_LEN];
44+
u32 pattern_id;
45+
46+
// get_task_filename fills global variable (.bss map) with task filename
47+
static
48+
long get_task_filename(const struct trace_event_raw_sched_process_exec *ctx)
3649
{
37-
if (!tctx)
38-
return -1;
50+
long ret;
51+
52+
ret = bpf_core_read_str(filename, sizeof(filename),
53+
get_dynamic_array(ctx, filename));
54+
if (ret < 0)
55+
return ret;
56+
57+
return 0;
58+
}
3959

60+
// get_task_ids fills global variables (.bss map) with task ids
61+
static
62+
long get_task_ids(const struct trace_event_raw_sched_process_exec *ctx)
63+
{
4064
long ret;
4165

42-
ret = bpf_get_current_comm(&tctx->comm, sizeof(tctx->comm));
66+
ret = bpf_get_current_comm(comm, sizeof(comm));
4367
if (ret < 0)
4468
return ret;
4569

46-
struct task_struct *task;
4770
u64 id;
4871

49-
task = (struct task_struct *) bpf_get_current_task();
50-
tctx->pid_ns = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, ns).inum;
51-
tctx->mnt_ns = BPF_CORE_READ(task, nsproxy, mnt_ns, ns).inum;
5272
id = bpf_get_current_pid_tgid();
53-
tctx->pid = id >> 32;
54-
tctx->tid = (u32) id;
73+
pid = id >> 32;
74+
tid = (u32) id;
5575

56-
if (!ectx)
57-
return 0;
58-
ret = bpf_core_read_str(&tctx->filename, sizeof(tctx->filename),
59-
get_dynamic_array(ectx, filename));
60-
if (ret < 0)
61-
return ret;
76+
struct task_struct *task;
77+
78+
task = (struct task_struct *) bpf_get_current_task();
79+
pid_ns = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, ns).inum;
80+
mnt_ns = BPF_CORE_READ(task, nsproxy, mnt_ns, ns).inum;
6281

6382
return 0;
6483
}
6584

66-
// basename returns pointer from a filename basename if filename is a path,
67-
// otherwise returns filename pointer
68-
static __always_inline
69-
const char *basename(const char *filename)
85+
// match was inspired by the Krauss and Kurts wildcard algorithms
86+
// https://github.com/kirkjkrauss/MatchingWildcards/blob/master/Listing1.cpp
87+
// http://dodobyte.com/wildcard.html
88+
static
89+
bool match(const char *pat, const char *str)
7090
{
71-
if (!filename)
72-
return NULL;
73-
74-
const char *base = filename;
75-
76-
// we should iterate up to i < MAX_FILENAME_LEN,
77-
// but increasing loop iterations results in "BPF program is too large"
78-
for (int i = 0; i < 32; i++) {
79-
if (!filename[i])
80-
break;
81-
if (filename[i] == '/')
82-
base = filename + i;
91+
int i = 0;
92+
int j = 0;
93+
int str_track = -1;
94+
int pat_track = -1;
95+
96+
while (j < MAX_PATTERN_LEN) {
97+
// this check makes verifier happy
98+
if (i >= MAX_FILENAME_LEN)
99+
return false;
100+
if (!pat[j] && !str[i])
101+
return true;
102+
103+
if (pat[j] == '*') {
104+
if (!str[i]) {
105+
while (j < MAX_PATTERN_LEN-1 && pat[j] == '*')
106+
j++;
107+
return !pat[j];
108+
}
109+
str_track = i;
110+
pat_track = j;
111+
j++;
112+
continue;
113+
}
114+
115+
if (pat[j] != '?' && pat[j] != str[i]) {
116+
if (pat_track == -1)
117+
return false;
118+
str_track++;
119+
i = str_track;
120+
j = pat_track;
121+
continue;
122+
}
123+
124+
i++;
125+
j++;
83126
}
84127

85-
if (base == filename)
86-
return filename;
128+
return false;
129+
}
130+
131+
// callback_ctx struct is used as check_hash_elem handler input/output
132+
struct callback_ctx {
133+
const char *filename;
134+
struct pattern_value *pvalue;
135+
};
136+
137+
// check_hash_elem is the handler required by bpf_for_each_map_elem iterator
138+
static
139+
u64 check_hash_elem(struct bpf_map *map,
140+
struct pattern_key *key, struct pattern_value *val,
141+
struct callback_ctx *data)
142+
{
143+
if (!key || !val || !data)
144+
return 0;
87145

88-
base++;
89-
if (!*base)
90-
return filename;
146+
if (match(key->pattern, data->filename)) {
147+
data->pvalue->pattern_id = val->pattern_id;
148+
return 1; // stop the iteration
149+
}
91150

92-
return base;
151+
return 0;
93152
}
94153

95-
// task_auditable checks if task must be audited
96-
static __always_inline
97-
bool task_auditable(const struct task_context *tctx)
154+
// task_auditable checks if task must be audited (phase 0)
155+
static
156+
bool task_auditable_0(void)
98157
{
99-
if (!tctx)
100-
return false;
101-
102-
struct pattern_value *pvalue;
158+
struct pattern_value pvalue = {
159+
.pattern_id = 0,
160+
};
161+
struct callback_ctx data = {
162+
.filename = (const char *) &filename,
163+
.pvalue = &pvalue,
164+
};
103165

104-
// we are just checking if a plain process filename (base) is auditable,
105-
// disregarding globs
106-
pvalue = bpf_map_lookup_elem(&ka_ea_pattern_map, basename(tctx->filename));
107-
if (!pvalue)
166+
// https://lwn.net/Articles/846504/
167+
long elem_num = bpf_for_each_map_elem(&ka_ea_pattern_map,
168+
check_hash_elem, &data, 0);
169+
if (elem_num < 0 || !data.pvalue->pattern_id)
108170
return false;
109171

110-
struct process_spec_key pskey = {
111-
.pid_ns = tctx->pid_ns,
112-
.mnt_ns = tctx->mnt_ns,
113-
.pattern_id = pvalue->pattern_id,
114-
};
172+
pattern_id = data.pvalue->pattern_id;
115173

116-
return !!bpf_map_lookup_elem(&ka_ea_process_spec_map, &pskey);
174+
return true;
117175
}
118176

119-
// task_audited checks if task is being audited
120-
static __always_inline
121-
bool task_audited(const struct task_context *tctx)
177+
// task_auditable checks if task must be audited (phase 1)
178+
static
179+
bool task_auditable_1(const struct trace_event_raw_sched_process_exec *ctx)
122180
{
123-
if (!tctx)
181+
if (get_task_ids(ctx) < 0)
124182
return false;
125183

126-
struct process_filter_key pfkey = {
127-
.pid_ns = tctx->pid_ns,
128-
.mnt_ns = tctx->mnt_ns,
129-
.host_pid = tctx->pid,
184+
struct process_spec_key pskey = {
185+
.pid_ns = pid_ns,
186+
.mnt_ns = mnt_ns,
187+
.pattern_id = pattern_id,
130188
};
131189

132-
return !!bpf_map_lookup_elem(&ka_ea_process_filter_map, &pfkey);
190+
return !!bpf_map_lookup_elem(&ka_ea_process_spec_map, &pskey);
133191
}
134192

135193
// set_task_for_audit set task for audit updating process filter map
136-
static __always_inline
137-
long set_task_for_audit(const struct task_context *tctx)
194+
static
195+
long set_task_for_audit(void)
138196
{
139-
if (!tctx)
140-
return 0;
141-
142197
struct process_filter_key pfkey = {
143-
.pid_ns = tctx->pid_ns,
144-
.mnt_ns = tctx->mnt_ns,
145-
.host_pid = tctx->pid,
198+
.pid_ns = pid_ns,
199+
.mnt_ns = mnt_ns,
200+
.host_pid = pid,
146201
};
147202

148203
struct process_filter_value pfvalue = {
@@ -152,60 +207,68 @@ long set_task_for_audit(const struct task_context *tctx)
152207
return bpf_map_update_elem(&ka_ea_process_filter_map, &pfkey, &pfvalue, BPF_ANY);
153208
}
154209

155-
// unset_task_for_audit unset task for audit deleting from process filter map
156-
static __always_inline
157-
long unset_task_for_audit(const struct task_context *tctx)
210+
SEC("tp/sched/sched_process_exec/1")
211+
int ka_ea_sched_process_exec_1(struct trace_event_raw_sched_process_exec *ctx)
158212
{
159-
if (!tctx)
213+
if (!task_auditable_1(ctx))
160214
return 0;
161215

162-
struct process_filter_key pfkey = {
163-
.pid_ns = tctx->pid_ns,
164-
.mnt_ns = tctx->mnt_ns,
165-
.host_pid = tctx->pid,
166-
};
216+
if (set_task_for_audit() < 0)
217+
bpf_printk("[ka-ea-process]: failure setting %s for audit", filename);
218+
else
219+
bpf_printk("[ka-ea-process]: %s set for audit", filename);
167220

168-
return bpf_map_delete_elem(&ka_ea_process_filter_map, &pfkey);
221+
return 0;
169222
}
170223

171-
SEC("tp/sched/sched_process_exec")
172-
int ka_ea_sched_process_exec(struct trace_event_raw_sched_process_exec *ectx)
224+
SEC("tp/sched/sched_process_exec/0")
225+
int ka_ea_sched_process_exec_0(struct trace_event_raw_sched_process_exec *ctx)
173226
{
174-
struct task_context tctx = {};
175-
176-
if (get_task_context(&tctx, ectx) < 0)
227+
if (!task_auditable_0())
177228
return 0;
178229

179-
if (!task_auditable(&tctx))
230+
bpf_tail_call(ctx, &ka_ea_process_jmp_map, 1);
231+
232+
return 0;
233+
}
234+
235+
SEC("tp/sched/sched_process_exec")
236+
int ka_ea_sched_process_exec(struct trace_event_raw_sched_process_exec *ctx)
237+
{
238+
if (get_task_filename(ctx) < 0)
180239
return 0;
181240

182-
if (set_task_for_audit(&tctx) < 0)
183-
bpf_printk("[ka-ea]: failure setting for audit");
184-
else
185-
bpf_printk("[ka-ea]: set for audit");
241+
// don't access the value returned from bpf_tail_call()
242+
// although the doc states that it is negative in case of an error,
243+
// one can get 'R0 !read_ok' when the call is successful
244+
bpf_tail_call(ctx, &ka_ea_process_jmp_map, 0);
186245

187246
return 0;
188247
}
189248

190249
SEC("tp/sched/sched_process_exit")
191-
int ka_ea_sched_process_exit(struct trace_event_raw_sched_process_template *ectx)
250+
int ka_ea_sched_process_exit(struct trace_event_raw_sched_process_template *ctx)
192251
{
193-
struct task_context tctx = {};
194-
195-
if (get_task_context(&tctx, NULL) < 0)
252+
if (get_task_ids(NULL) < 0)
196253
return 0;
197254

198255
// disregard threads
199-
if (tctx.pid != tctx.tid)
256+
if (pid != tid)
200257
return 0;
201258

202-
if (!task_audited(&tctx))
259+
struct process_filter_key pfkey = {
260+
.pid_ns = pid_ns,
261+
.mnt_ns = mnt_ns,
262+
.host_pid = pid,
263+
};
264+
265+
if (!bpf_map_lookup_elem(&ka_ea_process_filter_map, &pfkey))
203266
return 0;
204267

205-
if (unset_task_for_audit(&tctx) < 0)
206-
bpf_printk("[ka-ea]: failure unsetting for audit");
268+
if (bpf_map_delete_elem(&ka_ea_process_filter_map, &pfkey) < 0)
269+
bpf_printk("[ka-ea-process]: failure unsetting %u for audit", pid);
207270
else
208-
bpf_printk("[ka-ea]: unset for audit");
271+
bpf_printk("[ka-ea-process]: %u unset for audit", pid);
209272

210273
return 0;
211274
}

KubeArmor/BPF/maps.bpf.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "shared.h"
99

1010
struct pattern_key {
11-
char pattern[PATTERN_MAX_LEN];
11+
char pattern[MAX_PATTERN_LEN];
1212
};
1313

1414
struct pattern_value {

KubeArmor/BPF/shared.h

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,10 @@
55
#define __SHARED_H
66

77
#define TASK_COMM_LEN 16
8-
#define MAX_FILENAME_LEN 256
9-
#define PATTERN_MAX_LEN TASK_COMM_LEN
8+
#define MAX_FILENAME_LEN 42 // verifier limit for match() unrolling
9+
#define MAX_PATTERN_LEN 7 // verifier limit for match() unrolling
1010

1111
#define get_dynamic_array(entry, field) \
1212
((void *)entry + (entry->__data_loc_##field & 0xffff))
1313

14-
struct task_context {
15-
unsigned int pid;
16-
unsigned int tid;
17-
unsigned int pid_ns;
18-
unsigned int mnt_ns;
19-
char comm[TASK_COMM_LEN];
20-
char filename[MAX_FILENAME_LEN];
21-
};
22-
2314
#endif /* __SHARED_H */

0 commit comments

Comments
 (0)