Skip to content

Commit

Permalink
bpf-tools: Add new feature(doublefree)
Browse files Browse the repository at this point in the history
doublefree tool can detect double free on user space

Usage: doublefree [OPTION...]
Detect and report double free error.

Either -c or -p is a mandatory option
EXAMPLES:
    doublefree -p 1234             # Detect doublefree on process id 1234
    doublefree -c a.out            # Detect doublefree on a.out
    doublefree -c 'a.out arg'      # Detect doublefree on a.out with argument
    doublefree -c "a.out arg"      # Detect doublefree on a.out with argument
    doublefree -k                  # Detect doublefree on kernel

  -k, --kernel               Kernel threads only (no user threads)
  -c, --command=COMMAND      Execute and trace the specified command
  -i, --interval=INTERVAL    Set interval in second to detect leak
  -p, --pid=PID              Set pid
  -T, --top=TOP              Report only specified amount of backtraces
  -v, --verbose              Verbose debug output
  -?, --help                 Give this help list
      --usage                Give a short usage message
  -V, --version              Print program version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

Report example:
$ ~/test/doublefree_generator &
[1] 48310
$ sudo ./doublefree -p 48310
Warn: Is this process alive? pid: 48310
Found double free...
Allocation happended on:
stack_id: 50292
        iovisor#1 0x0055b302c34219 foo
        iovisor#2 0x0055b302c341d0 main
        iovisor#3 0x007f5d6379dd90 __libc_init_first

First deallocation happended on:
stack_id: 57265
        iovisor#1 0x007f5d63819460 free
        iovisor#2 0x0055b302c341ea main
        iovisor#3 0x007f5d6379dd90 __libc_init_first

Second deallocation happended on:
stack_id: 2974
        iovisor#1 0x007f5d63819460 free
        iovisor#2 0x0055b302c342eb baz
        iovisor#3 0x0055b302c34200 main
        iovisor#4 0x007f5d6379dd90 __libc_init_first

Source code of test program:
$ cat Makefile
OBJ = doublefree_generator.o foobar.o baz.o
TARGET = doublefree_generator

all: clean $(TARGET)

$(TARGET): $(OBJ)
	gcc -o $@ $^

%.o: %.c
	gcc -c $< -o $@

clean:
	rm -f $(OBJ) $(TARGET)

$ cat doublefree_generator.c
\#include <unistd.h>
\#include "foobar.h"
\#include "baz.h"

int main(int argc, char* argv[]) {
  sleep(50);
  int *val = foo();
  *val = 33;
  bar(val);
  *val = 84;
  baz(val);
  return 0;
}

$ cat foobar.h
\#include <stdio.h>

int* foo();
void bar(int* p);

$ cat foobar.c
\#include <stdlib.h>
\#include "foobar.h"

int* foo() {
  return (int*)malloc(sizeof(int));
}

void bar(int* p) {
  printf("bar: %p\n", p);
  free(p);
}

$ cat baz.h
\#include <stdio.h>

void baz(int* p);

$ cat baz.c
\#include <stdlib.h>
\#include <stdbool.h>
\#include "baz.h"

void func(int* p) {
  while (true) {
    if (p != NULL) {
      printf("free %d\n", *p);
      free(p);
      break;
    }
  }
}

void baz(int* p) {
  printf("baz: %p\n", p);
  printf("bazz: %d\n", *p);
  func(p);
}
  • Loading branch information
Bojun-Seo committed Nov 21, 2022
1 parent d661f8a commit 0b01ca7
Show file tree
Hide file tree
Showing 5 changed files with 796 additions and 0 deletions.
1 change: 1 addition & 0 deletions libbpf-tools/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
/cachestat
/cpudist
/cpufreq
/doublefree
/drsnoop
/execsnoop
/exitsnoop
Expand Down
1 change: 1 addition & 0 deletions libbpf-tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ APPS = \
cachestat \
cpudist \
cpufreq \
doublefree \
drsnoop \
execsnoop \
exitsnoop \
Expand Down
168 changes: 168 additions & 0 deletions libbpf-tools/doublefree.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (c) 2022 LG Electronics */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "doublefree.h"

const volatile bool kernel_threads_only = false;

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
__type(key, u64);
__type(value, struct doublefree_info_t);
} allocs SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
__type(key, u64);
__type(value, u64);
} memptrs SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
__uint(max_entries, MAX_ENTRIES);
__uint(key_size, sizeof(u32));
} stack_traces SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
__type(key, u64);
__type(value, u32);
} first_deallocs SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10);
__type(key, u64);
__type(value, u32);
} second_deallocs SEC(".maps");

int gen_alloc_exit2(struct pt_regs *ctx, u64 address)
{
struct doublefree_info_t info = {0, };

if (address != 0) {
info.stack_id = bpf_get_stackid(ctx, &stack_traces,
kernel_threads_only ? 0 : BPF_F_USER_STACK);
info.is_available = 1;
bpf_map_update_elem(&allocs, &address, &info, BPF_ANY);
}
return 0;
}

int gen_alloc_exit(struct pt_regs *ctx)
{
return gen_alloc_exit2(ctx, PT_REGS_RC(ctx));
}

int gen_free_enter(struct pt_regs *ctx, void *address)
{
int stack_id = 0;
u64 addr = (u64)address;
struct doublefree_info_t *info = bpf_map_lookup_elem(&allocs, &addr);

if (!info)
return 0;

info->is_available -= 1;
stack_id = bpf_get_stackid(ctx, &stack_traces,
kernel_threads_only ? 0 : BPF_F_USER_STACK);
if (info->is_available == 0) {
bpf_map_update_elem(&first_deallocs, &addr, &stack_id, BPF_ANY);
} else if (info->is_available < 0) {
bpf_map_update_elem(&second_deallocs, &addr, &stack_id, BPF_ANY);
} else {
bpf_printk("This code should not be printed at any rate\n");
bpf_printk("Please check if something goes wrong\n");
}
return 0;
}

SEC("kretprobe/dummy_malloc")
int BPF_KRETPROBE(malloc_return)
{
return gen_alloc_exit(ctx);
}

SEC("kprobe/dummy_free")
int BPF_KPROBE(free_entry, void *address)
{
return gen_free_enter(ctx, address);
}

SEC("kretprobe/dummy_calloc")
int BPF_KRETPROBE(calloc_return)
{
return gen_alloc_exit(ctx);
}

SEC("kretprobe/dummy_realloc")
int BPF_KRETPROBE(realloc_return)
{
return gen_alloc_exit(ctx);
}

SEC("kprobe/dummy_posix_memalign")
int BPF_KPROBE(posix_memalign_entry, void **memptr, size_t alignment, size_t size)
{
u64 memptr64 = (u64)(size_t)memptr;
u64 pid = bpf_get_current_pid_tgid();

bpf_map_update_elem(&memptrs, &pid, &memptr64, BPF_ANY);
return 0;
}

SEC("kretprobe/dummy_posix_memalign")
int BPF_KRETPROBE(posix_memalign_return)
{
void *addr = NULL;
u64 pid = bpf_get_current_pid_tgid();
u64 *memptr64 = bpf_map_lookup_elem(&memptrs, &pid);

if (memptr64 == 0)
return 0;

bpf_map_delete_elem(&memptrs, &pid);
if (bpf_probe_read_user(&addr, sizeof(void *), (void *)(size_t)*memptr64))
return 0;

u64 addr64 = (u64)(size_t)addr;
return gen_alloc_exit2(ctx, addr64);
}

SEC("kretprobe/dummy_aligned_alloc")
int BPF_KRETPROBE(aligned_alloc_return)
{
return gen_alloc_exit(ctx);
}

SEC("kretprobe/dummy_valloc")
int BPF_KRETPROBE(valloc_return)
{
return gen_alloc_exit(ctx);
}

SEC("kretprobe/dummy_memalign")
int BPF_KRETPROBE(memalign_return)
{
return gen_alloc_exit(ctx);
}

SEC("kretprobe/dummy_pvalloc")
int BPF_KRETPROBE(pvalloc_return)
{
return gen_alloc_exit(ctx);
}

SEC("kretprobe/dummy_reallocarray")
int BPF_KRETPROBE(reallocarray_return)
{
return gen_alloc_exit(ctx);
}

char _license[] SEC("license") = "GPL";
Loading

0 comments on commit 0b01ca7

Please sign in to comment.