diff --git a/Makefile.am b/Makefile.am index 95672bd8..6e723199 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,6 +31,7 @@ strace_SOURCES = \ aio.c \ bjm.c \ block.c \ + bpf.c \ cacheflush.c \ capability.c \ caps0.h \ diff --git a/bpf.c b/bpf.c new file mode 100644 index 00000000..f7156f07 --- /dev/null +++ b/bpf.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2015 Dmitry V. Levin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "defs.h" + +#ifdef HAVE_LINUX_BPF_H +# include +#endif + +#include "xlat/bpf_commands.h" +#include "xlat/bpf_map_types.h" +#include "xlat/bpf_prog_types.h" +#include "xlat/bpf_map_update_elem_flags.h" + +static int +bpf_map_create(struct tcb *tcp, const long addr, unsigned int size) +{ + struct { + uint32_t map_type, key_size, value_size, max_entries; + } attr = {}; + + if (!size) { + printaddr(addr); + return RVAL_DECODED | RVAL_FD; + } + if (size > sizeof(attr)) + size = sizeof(attr); + if (umoven_or_printaddr(tcp, addr, size, &attr)) + return RVAL_DECODED | RVAL_FD; + + tprints("{map_type="); + printxval(bpf_map_types, attr.map_type, "BPF_MAP_TYPE_???"); + tprintf(", key_size=%u, value_size=%u, max_entries=%u}", + attr.key_size, attr.value_size, attr.max_entries); + + return RVAL_DECODED | RVAL_FD; +} + +static void +bpf_map_update_elem(struct tcb *tcp, const long addr, unsigned int size) +{ + struct { + uint32_t map_fd; + uint64_t ATTRIBUTE_ALIGNED(8) key; + uint64_t ATTRIBUTE_ALIGNED(8) value; + uint64_t flags; + } attr = {}; + + if (!size) { + printaddr(addr); + return; + } + if (size > sizeof(attr)) + size = sizeof(attr); + if (umoven_or_printaddr(tcp, addr, size, &attr)) + return; + + tprints("{map_fd="); + printfd(tcp, attr.map_fd); + tprintf(", key=%#" PRIx64 ", value=%#" PRIx64 ", flags=", + attr.key, attr.value); + printxval(bpf_map_update_elem_flags, attr.flags, "BPF_???"); + tprints("}"); +} + +static void +bpf_map_delete_elem(struct tcb *tcp, const long addr, unsigned int size) +{ + struct { + uint32_t map_fd; + uint64_t ATTRIBUTE_ALIGNED(8) key; + } attr = {}; + + if (!size) { + printaddr(addr); + return; + } + if (size > sizeof(attr)) + size = sizeof(attr); + if (umoven_or_printaddr(tcp, addr, size, &attr)) + return; + + tprints("{map_fd="); + printfd(tcp, attr.map_fd); + tprintf(", key=%#" PRIx64 "}", attr.key); +} + +static int +bpf_map_io(struct tcb *tcp, const long addr, unsigned int size, const char *text) +{ + struct bpf_io_elem_struct { + uint32_t map_fd; + uint64_t ATTRIBUTE_ALIGNED(8) key; + uint64_t ATTRIBUTE_ALIGNED(8) value; + } attr = {}; + + if (exiting(tcp)) { + if (!syserror(tcp) && !umove_or_printaddr(tcp, addr, &attr)) + tprintf(", %s=%#" PRIx64, text, attr.value); + tprints("}"); + return RVAL_DECODED; + } + + if (!size) { + printaddr(addr); + return RVAL_DECODED; + } + if (size > sizeof(attr)) + size = sizeof(attr); + if (umoven_or_printaddr(tcp, addr, size, &attr)) + return RVAL_DECODED; + + tprints("{map_fd="); + printfd(tcp, attr.map_fd); + tprintf(", key=%#" PRIx64, attr.key); + + return 0; +} + +static int +bpf_prog_load(struct tcb *tcp, const long addr, unsigned int size) +{ + struct { + uint32_t prog_type, insn_cnt; + uint64_t ATTRIBUTE_ALIGNED(8) insns, license; + uint32_t log_level, log_size; + uint64_t ATTRIBUTE_ALIGNED(8) log_buf; + uint32_t kern_version; + } attr = {}; + + if (!size) { + printaddr(addr); + return RVAL_DECODED | RVAL_FD; + } + if (size > sizeof(attr)) + size = sizeof(attr); + if (umoven_or_printaddr(tcp, addr, size, &attr)) + return RVAL_DECODED | RVAL_FD; + + tprints("{prog_type="); + printxval(bpf_prog_types, attr.prog_type, "BPF_PROG_TYPE_???"); + tprintf(", insn_cnt=%u, insns=%#" PRIx64 ", license=", + attr.insn_cnt, attr.insns); + printstr(tcp, attr.license, -1); + tprintf(", log_level=%u, log_size=%u, log_buf=%#" PRIx64 ", kern_version=%u}", + attr.log_level, attr.log_size, attr.log_buf, attr.kern_version); + + return RVAL_DECODED | RVAL_FD; +} + +SYS_FUNC(bpf) +{ + const int cmd = tcp->u_arg[0]; + const long addr = tcp->u_arg[1]; + const unsigned int size = tcp->u_arg[2]; + int rc = RVAL_DECODED; + + if (entering(tcp)) { + printxval(bpf_commands, cmd, "BPF_???"); + tprints(", "); + } + + switch (cmd) { + case BPF_MAP_CREATE: + rc = bpf_map_create(tcp, addr, size); + break; + case BPF_MAP_LOOKUP_ELEM: + rc = bpf_map_io(tcp, addr, size, "value"); + break; + case BPF_MAP_UPDATE_ELEM: + bpf_map_update_elem(tcp, addr, size); + break; + case BPF_MAP_DELETE_ELEM: + bpf_map_delete_elem(tcp, addr, size); + break; + case BPF_MAP_GET_NEXT_KEY: + rc = bpf_map_io(tcp, addr, size, "next_key"); + break; + case BPF_PROG_LOAD: + rc = bpf_prog_load(tcp, addr, size); + break; + default: + printaddr(addr); + break; + } + + if (rc & RVAL_DECODED) + tprintf(", %u", size); + + return rc; +} diff --git a/configure.ac b/configure.ac index 85749399..20fae61c 100644 --- a/configure.ac +++ b/configure.ac @@ -253,6 +253,7 @@ AC_CHECK_HEADERS(m4_normalize([ elf.h inttypes.h ioctls.h + linux/bpf.h linux/bsg.h linux/falloc.h linux/filter.h diff --git a/linux/dummy.h b/linux/dummy.h index fcc315ea..0a3db913 100644 --- a/linux/dummy.h +++ b/linux/dummy.h @@ -32,7 +32,6 @@ #endif /* still unfinished */ -#define sys_bpf printargs #define sys_execveat printargs #define sys_ioperm printargs #define sys_iopl printargs diff --git a/pathtrace.c b/pathtrace.c index 4ca031cb..bfa2df06 100644 --- a/pathtrace.c +++ b/pathtrace.c @@ -318,6 +318,7 @@ pathtrace_match(struct tcb *tcp) return 0; } + case SEN_bpf: case SEN_epoll_create: case SEN_eventfd2: case SEN_eventfd: diff --git a/tests/.gitignore b/tests/.gitignore index 449e64cb..98f6e8a2 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,3 +1,4 @@ +bpf caps fanotify_mark filter-unavailable diff --git a/tests/Makefile.am b/tests/Makefile.am index 3295f9cf..e25b1b2a 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,6 +9,7 @@ AM_CPPFLAGS = -I$(top_builddir)/$(OS)/$(ARCH) \ -I$(top_srcdir)/$(OS) check_PROGRAMS = \ + bpf \ caps \ fanotify_mark \ filter-unavailable \ @@ -61,6 +62,7 @@ TESTS = \ strace-f.test \ qual_syscall.test \ bexecve.test \ + bpf.test \ caps.test \ dumpio.test \ fanotify_mark.test \ diff --git a/tests/bpf.c b/tests/bpf.c new file mode 100644 index 00000000..b2e019da --- /dev/null +++ b/tests/bpf.c @@ -0,0 +1,109 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#if defined HAVE_LINUX_BPF_H && defined __NR_bpf +# include + +static const struct bpf_insn insns[] = { + { .code = BPF_JMP | BPF_EXIT } +}; + +static char log_buf[4096]; + +static int +map_create(void) +{ + union bpf_attr attr = { + .key_size = 4, + .value_size = 8, + .max_entries = 256 + }; + return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); +} + +static int +map_any(int cmd) +{ + union bpf_attr attr = { + .map_fd = -1, + .key = 0xdeadbeef, + .value = 0xbadc0ded + }; + return syscall(__NR_bpf, cmd, &attr, sizeof(attr)); +} + +static int +prog_load(void) +{ + union bpf_attr attr = { + .insn_cnt = sizeof(insns) / sizeof(insns[0]), + .insns = (unsigned long) insns, + .license = (unsigned long) "GPL", + .log_level = 42, + .log_size = sizeof(log_buf), + .log_buf = (unsigned long) log_buf + }; + return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); +} + +int +main(void) +{ + if (!map_create()) + return 77; + printf("bpf\\(BPF_MAP_CREATE, " + "\\{map_type=BPF_MAP_TYPE_UNSPEC, key_size=4, value_size=8, max_entries=256\\}, " + "%u\\) += -1 .*\n", + (unsigned) sizeof(union bpf_attr)); + + if (!map_any(BPF_MAP_LOOKUP_ELEM)) + return 77; + printf("bpf\\(BPF_MAP_LOOKUP_ELEM, " + "\\{map_fd=-1, key=0xdeadbeef\\}, %u\\) += -1 .*\n", + (unsigned) sizeof(union bpf_attr)); + + if (!map_any(BPF_MAP_UPDATE_ELEM)) + return 77; + printf("bpf\\(BPF_MAP_UPDATE_ELEM, " + "\\{map_fd=-1, key=0xdeadbeef, value=0xbadc0ded, flags=BPF_ANY\\}, " + "%u\\) += -1 .*\n", + (unsigned) sizeof(union bpf_attr)); + + if (!map_any(BPF_MAP_DELETE_ELEM)) + return 77; + printf("bpf\\(BPF_MAP_DELETE_ELEM, " + "\\{map_fd=-1, key=0xdeadbeef\\}, %u\\) += -1 .*\n", + (unsigned) sizeof(union bpf_attr)); + + if (!map_any(BPF_MAP_GET_NEXT_KEY)) + return 77; + printf("bpf\\(BPF_MAP_GET_NEXT_KEY, " + "\\{map_fd=-1, key=0xdeadbeef\\}, %u\\) += -1 .*\n", + (unsigned) sizeof(union bpf_attr)); + + if (!prog_load()) + return 77; + printf("bpf\\(BPF_PROG_LOAD, " + "\\{prog_type=BPF_PROG_TYPE_UNSPEC, insn_cnt=1, insns=%p, " + "license=\"GPL\", log_level=42, log_size=4096, log_buf=%p, " + "kern_version=0\\}, %u\\) += -1 .*\n", + insns, log_buf, (unsigned) sizeof(union bpf_attr)); + + return 0; +} + +#else + +int +main(void) +{ + return 77; +} + +#endif diff --git a/tests/bpf.test b/tests/bpf.test new file mode 100755 index 00000000..87254686 --- /dev/null +++ b/tests/bpf.test @@ -0,0 +1,13 @@ +#!/bin/sh + +# Check bpf syscall decoding. + +. "${srcdir=.}/init.sh" + +run_prog > /dev/null +OUT="$LOG.out" +run_strace -ebpf $args > "$OUT" +match_grep "$LOG" "$OUT" +rm -f "$OUT" + +exit 0 diff --git a/xlat/bpf_commands.in b/xlat/bpf_commands.in new file mode 100644 index 00000000..5c3035fd --- /dev/null +++ b/xlat/bpf_commands.in @@ -0,0 +1,6 @@ +BPF_MAP_CREATE 0 +BPF_MAP_LOOKUP_ELEM 1 +BPF_MAP_UPDATE_ELEM 2 +BPF_MAP_DELETE_ELEM 3 +BPF_MAP_GET_NEXT_KEY 4 +BPF_PROG_LOAD 5 diff --git a/xlat/bpf_map_types.in b/xlat/bpf_map_types.in new file mode 100644 index 00000000..afd24e07 --- /dev/null +++ b/xlat/bpf_map_types.in @@ -0,0 +1,4 @@ +BPF_MAP_TYPE_UNSPEC 0 +BPF_MAP_TYPE_HASH 1 +BPF_MAP_TYPE_ARRAY 2 +BPF_MAP_TYPE_PROG_ARRAY 3 diff --git a/xlat/bpf_map_update_elem_flags.in b/xlat/bpf_map_update_elem_flags.in new file mode 100644 index 00000000..6b1d61fc --- /dev/null +++ b/xlat/bpf_map_update_elem_flags.in @@ -0,0 +1,3 @@ +BPF_ANY 0 +BPF_NOEXIST 1 +BPF_EXIST 2 diff --git a/xlat/bpf_prog_types.in b/xlat/bpf_prog_types.in new file mode 100644 index 00000000..a36b28b8 --- /dev/null +++ b/xlat/bpf_prog_types.in @@ -0,0 +1,5 @@ +BPF_PROG_TYPE_UNSPEC 0 +BPF_PROG_TYPE_SOCKET_FILTER 1 +BPF_PROG_TYPE_KPROBE 2 +BPF_PROG_TYPE_SCHED_CLS 3 +BPF_PROG_TYPE_SCHED_ACT 4