@@ -2105,6 +2105,9 @@ extern const struct bpf_func_proto bpf_for_each_map_elem_proto;
extern const struct bpf_func_proto bpf_btf_find_by_name_kind_proto;
extern const struct bpf_func_proto bpf_sk_setsockopt_proto;
extern const struct bpf_func_proto bpf_sk_getsockopt_proto;
+extern const struct bpf_func_proto bpf_create_slub_cache_proto;
+extern const struct bpf_func_proto bpf_cache_alloc_proto;
+extern const struct bpf_func_proto bpf_jmp_next_proto;
const struct bpf_func_proto *tracing_prog_func_proto(
enum bpf_func_id func_id, const struct bpf_prog *prog);
@@ -4877,6 +4877,25 @@ union bpf_attr {
* Get the struct pt_regs associated with **task**.
* Return
* A pointer to struct pt_regs.
+ *
+ * long bpf_create_slub_cache(u32 size, u32 gfp_flags, u64 ip_size_priv_type)
+ * Description
+ * Create a new slab cache according to the ip/size/privilege/zone
+ * Return
+ * The cache's address or NULL.
+ *
+ * long bpf_cache_alloc(u64 cache, u32 gfp_flags)
+ * Description
+ * allocate memory objects from the slab cache
+ * Return
+ * The allocated memory or NULL.
+ *
+ * long bpf_jmp_next(struct pt_regs *ctx, u64 nextip, u64 ret)
+ * Description
+ * jump the `call kmalloc`'s next instruction, and set rax.
+ * Return
+ * 0.
+ *
*/
#define __BPF_FUNC_MAPPER(FN) \
FN(unspec), \
@@ -5055,6 +5074,9 @@ union bpf_attr {
FN(get_func_ip), \
FN(get_attach_cookie), \
FN(task_pt_regs), \
+ FN(create_slub_cache), \
+ FN(cache_alloc), \
+ FN(jmp_next), \
/* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -1017,6 +1017,73 @@ static const struct bpf_func_proto bpf_get_attach_cookie_proto_pe = {
.arg1_type = ARG_PTR_TO_CTX,
};
+#ifdef CONFIG_HOTBPF
+
+BPF_CALL_3(bpf_create_slub_cache, u32, size, u32, gfp_flags, u64, ip_size_priv_type)
+{
+ slab_flags_t sflags = 0;
+ struct kmem_cache *new_cache;
+ char new_cache_name[64];
+
+ if (gfp_flags & ___GFP_DMA)
+ sflags |= SLAB_CACHE_DMA;
+
+ if (gfp_flags & ___GFP_RECLAIMABLE)
+ sflags |= SLAB_RECLAIM_ACCOUNT;
+
+ if (gfp_flags & ___GFP_ACCOUNT)
+ sflags |= SLAB_ACCOUNT;
+
+ snprintf(new_cache_name, sizeof(new_cache_name), "hotbpf_0x%x_%llx",
+ size, ip_size_priv_type);
+ new_cache = kmem_cache_create(new_cache_name, size, 4096, sflags, NULL);
+
+ return (u64)new_cache;
+}
+
+const struct bpf_func_proto bpf_create_slub_cache_proto = {
+ .func = bpf_create_slub_cache,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_ANYTHING,
+ .arg2_type = ARG_ANYTHING,
+ .arg3_type = ARG_ANYTHING,
+};
+
+BPF_CALL_2(bpf_cache_alloc, u64, cache, u32, gfp_flags)
+{
+ struct kmem_cache *s = (struct kmem_cache *) cache;
+
+ return (u64)kmem_cache_alloc(s, gfp_flags);
+}
+
+const struct bpf_func_proto bpf_cache_alloc_proto = {
+ .func = bpf_cache_alloc,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_ANYTHING,
+ .arg2_type = ARG_ANYTHING,
+};
+
+BPF_CALL_3(bpf_jmp_next, struct pt_regs *, ctx, u64, nextip, u64, ret)
+{
+ ctx->ip = nextip;
+ ctx->ax = ret;
+ return 0;
+}
+
+const struct bpf_func_proto bpf_jmp_next_proto = {
+ .func = bpf_jmp_next,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_CTX,
+ .arg1_type = ARG_ANYTHING,
+ .arg2_type = ARG_ANYTHING,
+};
+
+#endif
+
+
static const struct bpf_func_proto *
bpf_tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
@@ -1132,6 +1199,14 @@ bpf_tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
return &bpf_snprintf_proto;
case BPF_FUNC_get_func_ip:
return &bpf_get_func_ip_proto_tracing;
+#ifdef CONFIG_HOTBPF
+ case BPF_FUNC_create_slub_cache:
+ return &bpf_create_slub_cache_proto;
+ case BPF_FUNC_cache_alloc:
+ return &bpf_cache_alloc_proto;
+ case BPF_FUNC_jmp_next:
+ return &bpf_jmp_next_proto;
+#endif
default:
return bpf_base_func_proto(func_id);
}
@@ -1,6 +1,24 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Kernel hacking"
+menu "HotBPF heap vulnerability fast patch"
+
+config HOTBPF
+ bool "activate HotBPF for kmalloc"
+ depends on BPF && FUNCTION_ERROR_INJECTION && SLUB
+ help
+ selecting this option activates HotBPF, a feature designed to
+ inhibit kernel heap-based exploitation by utilizing eBPF programs.
+ It mitigates the risk of overlap between victim and attack payload
+ objects by isolating victim objects in a designated slub cache.
+ Each cache is uniquely distinguished based on its allocation address,
+ user privileges, size, and zone.
+
+ examples are in samples/bpf/hotbpf_example
+
+
+endmenu
+
menu "printk and dmesg options"
config PRINTK_TIME
new file mode 100644
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HotBPF example kernel side
+ * Copyright Zicheng Wang, Yueqi Chen
+ */
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+#include <uapi/linux/ptrace.h>
+#include <uapi/linux/bpf.h>
+#include <linux/version.h>
+#include <bpf/bpf_helpers.h>
+
+#define ___GFP_DMA 0x01u
+#define ___GFP_RECLAIMABLE 0x10u
+#define ___GFP_ACCOUNT 0x400000u
+
+
+struct kmem_cache {
+ unsigned long cpu_slab;
+ unsigned int flags;
+ unsigned long min_partial;
+ unsigned int size;
+ unsigned int object_size;
+ unsigned long reciprocal_size;
+ unsigned int offset;
+ unsigned int cpu_partial;
+ unsigned int oo;
+ unsigned int max;
+ unsigned int min;
+ unsigned int allocflags;
+ int refcount;
+ /* size: 8408, cachelines: 132, members: 26 */
+ /* sum members: 8392, holes: 4, sum holes: 16 */
+ /* paddings: 1, sum paddings: 2 */
+ /* last cacheline: 24 bytes */
+};
+
+
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 40960);
+ __type(key, u64); // ip^size^priv^zone
+ __type(value, u64); // cache addr
+} key2cache SEC(".maps");
+
+u64 get_key(u64 ip, u32 size, u32 uid, u32 zone)
+{
+ return ip ^ (u64) size ^ (u64) uid ^ (u64) zone;
+}
+
+u32 get_size(u32 size)
+{
+ u32 ret = (size + 4096) / 4096;
+
+ return (ret + 1) * 4096;
+}
+
+u32 get_zone(u32 gfp_flags)
+{
+ u32 ret = 0;
+
+ if (gfp_flags & ___GFP_DMA)
+ ret = 1;
+ else if (gfp_flags & ___GFP_RECLAIMABLE)
+ ret = 2;
+ else if (gfp_flags & ___GFP_ACCOUNT)
+ ret = 3;
+
+ return ret;
+}
+
+// ffffffff813371a0 <single_open>:
+// ...
+// ffffffff813371c5: call ffffffff812d61b0 <kmem_cache_alloc_trace>
+// ffffffff813371ca: test %rax,%rax
+
+// void *kmem_cache_alloc_trace(struct kmem_cache *s, gfp_t gfpflags, size_t size)
+// python3 -c 'print(hex(0xffffffff813371c5-0xffffffff813371a0))'
+SEC("kprobe/single_open+0x25")
+int BPF_KPROBE(HANDLE_kmem_cache_alloc_trace)
+{
+ u64 ip = ctx->ip;
+ u64 *pv;
+ struct kmem_cache *cache = (struct kmem_cache *) ctx->di;
+ u64 alloc_size = BPF_CORE_READ(cache, size);
+ u32 gfp_flags = (u32) ctx->si;
+ u32 uid = bpf_get_current_uid_gid() >> 32;
+ u32 zone = get_zone(gfp_flags);
+ u64 key = get_key(ip, alloc_size, uid, zone);
+ u64 cache_addr = 0;
+ u64 alloc_addr = 0;
+ int err = 0;
+
+ // if there is a slab cache
+ u64 *pcache = bpf_map_lookup_elem(&key2cache, &key);
+
+ if (!pcache) {
+ cache_addr = bpf_create_slub_cache(alloc_size, gfp_flags, key);
+ if (!cache_addr) {
+ bpf_printk("probe create cache failed\n");
+ return -1;
+ }
+ err = bpf_map_update_elem(&key2cache, &key, &cache_addr, BPF_ANY);
+ if (err < 0) {
+ bpf_printk("update key2cache failed: %d\n", err);
+ return err;
+ }
+ }
+
+ // alloc a new object
+ cache_addr = *pcache;
+ alloc_addr = bpf_cache_alloc(cache_addr, gfp_flags);
+ if (alloc_addr == 0) {
+ bpf_printk("probe kmalloc failed\n");
+ return -1;
+ }
+ bpf_printk("===HotBPF isolate single_open %lx===\n", alloc_addr);
+
+ bpf_jmp_next(ctx, ip + 0x4, alloc_addr);
+
+ return 0;
+}
+
+
+// ffffffff81534d70 <bio_kmalloc>:
+// ...
+// ffffffff81534d9a: call ffffffff812d70e0 <__kmalloc>
+// ffffffff81534d9f: test %rax,%rax
+
+// void *__kmalloc(size_t size, gfp_t flags)
+// python3 -c 'print(hex(0xffffffff81534d9a-0xffffffff81534d70))'
+SEC("kprobe/bio_kmalloc+0x2a")
+int BPF_KPROBE(HANDLE___kmalloc)
+{
+ u64 ip = ctx->ip;
+ u64 *pv;
+ u64 alloc_size = get_size(ctx->di);
+ u32 gfp_flags = (u32) ctx->si;
+ u32 uid = bpf_get_current_uid_gid() >> 32;
+ u32 zone = get_zone(gfp_flags);
+ u64 key = get_key(ip, alloc_size, uid, zone);
+ u64 cache_addr = 0;
+ u64 alloc_addr = 0;
+ int err = 0;
+
+ // if there is a slab cache
+ u64 *pcache = bpf_map_lookup_elem(&key2cache, &key);
+
+ if (!pcache) {
+ cache_addr = bpf_create_slub_cache(alloc_size, gfp_flags, key);
+ if (!cache_addr) {
+ bpf_printk("probe create cache failed\n");
+ return -1;
+ }
+ err = bpf_map_update_elem(&key2cache, &key, &cache_addr, BPF_ANY);
+ if (err < 0) {
+ bpf_printk("update key2cache failed: %d\n", err);
+ return err;
+ }
+ } else {
+ cache_addr = *pcache;
+ }
+
+ // alloc a new object
+ alloc_addr = bpf_cache_alloc(cache_addr, gfp_flags);
+ if (alloc_addr == 0) {
+ bpf_printk("probe kmalloc failed\n");
+ return -1;
+ }
+ bpf_printk("===HotBPF isolate bio_kmalloc %lx===\n", alloc_addr);
+
+ bpf_jmp_next(ctx, ip + 0x4, alloc_addr);
+
+ return 0;
+}
+
+
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
new file mode 100644
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HotBPF example user side
+ * Copyright Zicheng Wang, Yueqi Chen
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/resource.h>
+#include <bpf/libbpf.h>
+#include <fcntl.h>
+static sig_atomic_t stop;
+
+static void sig_int(int signo)
+{
+ stop = 1;
+}
+
+int main(int argc, char **argv)
+{
+ struct bpf_link *links[2];
+ struct bpf_program *prog;
+ struct bpf_object *obj;
+ char filename[256];
+ int j = 0;
+ int trace_fd;
+
+ trace_fd = open("/sys/kernel/debug/tracing/trace_pipe", O_RDONLY, 0);
+ if (trace_fd < 0) {
+ printf("cannot open trace_pipe %d\n", trace_fd);
+ return trace_fd;
+ }
+
+ snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
+
+ obj = bpf_object__open_file(filename, NULL);
+ if (libbpf_get_error(obj)) {
+ fprintf(stderr, "ERROR: opening BPF object file failed\n");
+ return 0;
+ }
+
+ /* load BPF program */
+ if (bpf_object__load(obj)) {
+ fprintf(stderr, "ERROR: loading BPF object file failed\n");
+ goto cleanup;
+ }
+
+
+ bpf_object__for_each_program(prog, obj) {
+ links[j] = bpf_program__attach(prog);
+ if (libbpf_get_error(links[j])) {
+ fprintf(stderr, "ERROR: bpf_program__attach failed\n");
+ links[j] = NULL;
+ goto cleanup;
+ }
+ j++;
+ }
+
+ if (signal(SIGINT, sig_int) == SIG_ERR) {
+ fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
+ goto cleanup;
+ }
+
+ printf("Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
+ "to see output of the BPF programs.\n");
+
+
+ printf("start tracing\n");
+ while (!stop) {
+ static char buf[4096];
+ ssize_t sz;
+
+ sz = read(trace_fd, buf, sizeof(buf) - 1);
+ if (sz > 0) {
+ buf[sz] = '\0';
+ puts(buf);
+ }
+ }
+
+cleanup:
+ for (j--; j >= 0; j--)
+ bpf_link__destroy(links[j]);
+ bpf_object__close(obj);
+ close(trace_fd);
+
+ return 0;
+}