Skip to content

Commit

Permalink
libbpf-tools: Add new feature doublefree
Browse files Browse the repository at this point in the history
Add doublefree tool to detect double free. It could detect user level double
free error currently and can be expanded to detect kernel level double free
error. Followings are the usage and example.

Usage:

  $ ./doublefree -h
  Usage: doublefree [OPTION...]
  Detect and report doublefree error.

  -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

    -c, --command=COMMAND      Execute the command and detect doublefree
    -p, --pid=PID              Detect doublefree on the specified process
    -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 bugs to https://github.com/iovisor/bcc/tree/master/libbpf-tools.

Example:

  $ cat doublefree_generator.c
  #include <unistd.h>
  #include <stdlib.h>

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

  void bar(int* p) {
    free(p);
  }

  int main(int argc, char* argv[]) {
    sleep(10);
    int *val = foo();
    *val = 33;
    bar(val);
    *val = 84;
    bar(val);
    return 0;
  }
  $ gcc doublefree_generator.c
  $ sudo ./doublefree -c a.out
  2024-Feb-29 15:10:46 INFO Execute command: a.out(pid 216625)
  Tracing doublefree... Hit Ctrl-C to stop
  free(): double free detected in tcache 2

  Allocation:
          iovisor#1 0x005586f530f19b foo+0x12
          iovisor#2 0x005586f530f1e3 main+0x27
          iovisor#3 0x007f6990c29d90 [unknown]

  First deallocation:
          iovisor#1 0x007f6990ca53e0 free+0
          iovisor#2 0x005586f530f1fd main+0x41
          iovisor#3 0x007f6990c29d90 [unknown]

  Second deallocation:
          iovisor#1 0x007f6990ca53e0 free+0
          iovisor#2 0x005586f530f213 main+0x57
          iovisor#3 0x007f6990c29d90 [unknown]

  ^C
  $
  • Loading branch information
Bojun-Seo committed Feb 29, 2024
1 parent 699cd5f commit e661c38
Show file tree
Hide file tree
Showing 5 changed files with 666 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 @@ -14,6 +14,7 @@
/capable
/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 @@ -51,6 +51,7 @@ APPS = \
capable \
cpudist \
cpufreq \
doublefree \
drsnoop \
execsnoop \
exitsnoop \
Expand Down
183 changes: 183 additions & 0 deletions libbpf-tools/doublefree.bpf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright 2022 LG Electronics Inc. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
#include "doublefree.h"

struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__type(key, u32);
__type(value, u32);
} events SEC(".maps");

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

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

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

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

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

if (!address)
return 0;

info.stackid = bpf_get_stackid(ctx, &stack_traces, BPF_F_USER_STACK);
info.alloc_count = 1;
bpf_map_update_elem(&allocs, &address, &info, BPF_ANY);

return 0;
}

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

if (!info)
return 0;

stackid = bpf_get_stackid(ctx, &stack_traces, BPF_F_USER_STACK);

__sync_fetch_and_add(&info->alloc_count, -1);
if (info->alloc_count == 0) {
bpf_map_update_elem(&deallocs, &addr, &stackid, BPF_ANY);
} else if (info->alloc_count < 0) {
event.stackid = stackid;
event.addr = addr;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event,
sizeof(event));
} else {
event.err = -1;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event,
sizeof(event));
}

return 0;
}

SEC("uretprobe")
int BPF_KRETPROBE(malloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

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

SEC("uretprobe")
int BPF_KRETPROBE(calloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(realloc_entry, void *ptr, size_t size)
{
return gen_free_enter(ctx, ptr);
}

SEC("uretprobe")
int BPF_KRETPROBE(realloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
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("uretprobe")
int BPF_KRETPROBE(posix_memalign_return)
{
void *addr = NULL;
u64 addr64 = 0;
u64 pid = bpf_get_current_pid_tgid();
u64 *memptr64 = bpf_map_lookup_elem(&memptrs, &pid);

if (!memptr64)
return 0;

bpf_map_delete_elem(&memptrs, &pid);

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

addr64 = (u64)(size_t)addr;

return gen_alloc_exit(ctx, addr64);
}

SEC("uretprobe")
int BPF_KRETPROBE(aligned_alloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uretprobe")
int BPF_KRETPROBE(valloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uretprobe")
int BPF_KRETPROBE(memalign_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uretprobe")
int BPF_KRETPROBE(pvalloc_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

SEC("uprobe")
int BPF_KPROBE(reallocarray_entry, void *ptr, size_t nmemb, size_t size)
{
return gen_free_enter(ctx, ptr);
}

SEC("uretprobe")
int BPF_KRETPROBE(reallocarray_return)
{
return gen_alloc_exit(ctx, PT_REGS_RC(ctx));
}

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

0 comments on commit e661c38

Please sign in to comment.