Skip to content

Commit

Permalink
String: Support longer exact match strings
Browse files Browse the repository at this point in the history
String matching is limited to strings of length 144 due to the way the
hash look up tables were implemented (6 key sizes, 24 byte increments).

This commit adds maps to support larger key/string sizes: 256, 512,
1024, 2048, and 4096. This will therefore permit exact string matches
for strings of length up to 4096 (not including null terminator).

Unfortunately, kernels v5.10 and lower restrict hash key lengths to 512
bytes, so on these kernels the maximum string length is 510 characters
(512 - 2 bytes for embedded string length).

And on older kernels (<5.3) the instruction and complexity limits mean
the new approach is not feasible. Therefore on these kernels, the
maximum string length is 144 characters (as before).

Includes tests at map limits.

Signed-off-by: Kevin Sheldrake <[email protected]>
  • Loading branch information
kevsecurity authored and jrfastab committed Feb 8, 2024
1 parent 08458a2 commit 834b5fe
Show file tree
Hide file tree
Showing 14 changed files with 615 additions and 114 deletions.
21 changes: 18 additions & 3 deletions bpf/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ PROCESS = bpf_execve_event.o bpf_execve_event_v53.o bpf_fork.o bpf_exit.o bpf_ge
bpf_generic_tracepoint_v61.o \
bpf_multi_kprobe_v61.o bpf_multi_retkprobe_v61.o \
bpf_generic_uprobe_v61.o \
bpf_execve_event_v511.o \
bpf_generic_kprobe_v511.o bpf_generic_retkprobe_v511.o \
bpf_generic_tracepoint_v511.o \
bpf_multi_kprobe_v511.o bpf_multi_retkprobe_v511.o \
bpf_generic_uprobe_v511.o \
bpf_loader.o \
bpf_killer.o bpf_multi_killer.o bpf_fmodret_killer.o

Expand Down Expand Up @@ -66,6 +71,7 @@ endef
# Generic build targets for each sub-dir

$(eval $(call DEFINE_VARIANT,v53))
$(eval $(call DEFINE_VARIANT,v511))
$(eval $(call DEFINE_VARIANT,v61))

# ALIGNCHECKER
Expand Down Expand Up @@ -117,13 +123,22 @@ $(DEPSDIR)%_v53.d:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@

objs/bpf_multi_kprobe_v61.ll objs/bpf_multi_retkprobe_v61.ll:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__V61_BPF_PROG -D__MULTI_KPROBE -c $< -o $@
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__LARGE_MAP_KEYS -D__V61_BPF_PROG -D__MULTI_KPROBE -c $< -o $@

objs/%_v61.ll:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__V61_BPF_PROG -c $< -o $@
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__LARGE_MAP_KEYS -D__V61_BPF_PROG -c $< -o $@

$(DEPSDIR)%_v61.d:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__V61_BPF_PROG -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__LARGE_MAP_KEYS -D__V61_BPF_PROG -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@

objs/bpf_multi_kprobe_v511.ll objs/bpf_multi_retkprobe_v511.ll:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__LARGE_MAP_KEYS -D__MULTI_KPROBE -c $< -o $@

objs/%_v511.ll:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__LARGE_MAP_KEYS -c $< -o $@

$(DEPSDIR)%_v511.d:
$(CLANG) $(CLANG_FLAGS) -D__LARGE_BPF_PROG -D__LARGE_MAP_KEYS -MM -MP -MT $(patsubst $(DEPSDIR)%.d, $(OBJSDIR)%.ll, $@) $< > $@

# BPFTESTDIR
objs/%.ll: $(BPFTESTDIR)%.c
Expand Down
103 changes: 93 additions & 10 deletions bpf/process/string_maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,35 @@
* up are equally padded to the smallest key size that can accommodate them, and then
* looked up in the related map.
*
* The chosen key sizes are 25, 49, 73, 97, 121, 145 (6 maps).
* The chosen key sizes are 25, 49, 73, 97, 121, 145, 258, 514, 1026, 2050, 4098 (11 maps).
* The first 6 are sized for common uses and to minimise the hashing of empty bytes. The
* following 5 maps notionally double in size, with lengths equal to 2^k + 2. On kernels
* <5.11, the last four maps are replaced with a single map with key size 512. This is due
* to key size limitations on kernels <5.11.
*
* In order to distinguish between character buffers that end in 0s and similar buffers
* that are padded with 0s, each string will be prefixed by its length stored in a
* single byte.
* single byte (for first 6 maps) or as a little endian u16 (latter maps).
*/
#define STRING_MAPS_KEY_INC_SIZE 24
#define STRING_MAPS_SIZE_0 1 * STRING_MAPS_KEY_INC_SIZE + 1
#define STRING_MAPS_SIZE_1 2 * STRING_MAPS_KEY_INC_SIZE + 1
#define STRING_MAPS_SIZE_2 3 * STRING_MAPS_KEY_INC_SIZE + 1
#define STRING_MAPS_SIZE_3 4 * STRING_MAPS_KEY_INC_SIZE + 1
#define STRING_MAPS_SIZE_4 5 * STRING_MAPS_KEY_INC_SIZE + 1
#define STRING_MAPS_SIZE_5 6 * STRING_MAPS_KEY_INC_SIZE + 1
#define STRING_MAPS_SIZE_0 (1 * STRING_MAPS_KEY_INC_SIZE + 1)
#define STRING_MAPS_SIZE_1 (2 * STRING_MAPS_KEY_INC_SIZE + 1)
#define STRING_MAPS_SIZE_2 (3 * STRING_MAPS_KEY_INC_SIZE + 1)
#define STRING_MAPS_SIZE_3 (4 * STRING_MAPS_KEY_INC_SIZE + 1)
#define STRING_MAPS_SIZE_4 (5 * STRING_MAPS_KEY_INC_SIZE + 1)
#define STRING_MAPS_SIZE_5 (6 * STRING_MAPS_KEY_INC_SIZE + 1)
#define STRING_MAPS_SIZE_6 (256 + 2)
#ifdef __LARGE_MAP_KEYS
#define STRING_MAPS_SIZE_7 (512 + 2)
#define STRING_MAPS_SIZE_8 (1024 + 2)
#define STRING_MAPS_SIZE_9 (2048 + 2)
#define STRING_MAPS_SIZE_10 (4096 + 2)
#else
#define STRING_MAPS_SIZE_7 (512)
#endif
#define STRING_MAPS_HEAP_SIZE 16384
#define STRING_MAPS_HEAP_MASK (8192 - 1)
#define STRING_MAPS_COPY_MASK 4095

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
Expand Down Expand Up @@ -124,18 +140,85 @@ struct {
});
} string_maps_5 SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, STRING_MAPS_OUTER_MAX_ENTRIES);
__uint(key_size, sizeof(__u32));
__array(
values, struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, __u8[STRING_MAPS_SIZE_6]);
__type(value, __u8);
});
} string_maps_6 SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, STRING_MAPS_OUTER_MAX_ENTRIES);
__uint(key_size, sizeof(__u32));
__array(
values, struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, __u8[STRING_MAPS_SIZE_7]);
__type(value, __u8);
});
} string_maps_7 SEC(".maps");

#ifdef __LARGE_MAP_KEYS
struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, STRING_MAPS_OUTER_MAX_ENTRIES);
__uint(key_size, sizeof(__u32));
__array(
values, struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, __u8[STRING_MAPS_SIZE_8]);
__type(value, __u8);
});
} string_maps_8 SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, STRING_MAPS_OUTER_MAX_ENTRIES);
__uint(key_size, sizeof(__u32));
__array(
values, struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, __u8[STRING_MAPS_SIZE_9]);
__type(value, __u8);
});
} string_maps_9 SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
__uint(max_entries, STRING_MAPS_OUTER_MAX_ENTRIES);
__uint(key_size, sizeof(__u32));
__array(
values, struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, __u8[STRING_MAPS_SIZE_10]);
__type(value, __u8);
});
} string_maps_10 SEC(".maps");
#endif

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__uint(key_size, sizeof(__u32));
__uint(value_size, 512);
__uint(value_size, STRING_MAPS_HEAP_SIZE);
} string_maps_heap SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
__uint(max_entries, 1);
__uint(key_size, sizeof(__u32));
__uint(value_size, 512);
__uint(value_size, STRING_MAPS_HEAP_SIZE);
} string_maps_ro_zero SEC(".maps");

#define STRING_PREFIX_MAX_LENGTH 256
Expand Down
Loading

0 comments on commit 834b5fe

Please sign in to comment.