diff --git a/.github/workflows/bcc-test.yml b/.github/workflows/bcc-test.yml index 903a6697542f..324495f7709e 100644 --- a/.github/workflows/bcc-test.yml +++ b/.github/workflows/bcc-test.yml @@ -1,13 +1,17 @@ name: BCC Build and tests -on: push +on: + push: + branches: + - master + pull_request: jobs: test_bcc: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] # 18.04.3 release has 5.0.0 kernel + os: [ubuntu-18.04, ubuntu-20.04] # 18.04.3 release has 5.0.0 kernel env: - TYPE: Debug PYTHON_TEST_LOGFILE: critical.log diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f737c8ce7248..b4ca2c733dc4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,6 +1,10 @@ name: Publish Build Artifacts -on: push +on: + push: + branches: + - master + pull_request: jobs: publish_images: @@ -95,7 +99,7 @@ jobs: - name: Build container image and publish to registry id: publish-registry - uses: elgohr/Publish-Docker-Github-Action@2.8 + uses: elgohr/Publish-Docker-Github-Action@v5 if: ${{ steps.vars.outputs.DOCKER_PUBLISH }} with: name: ${{ secrets.DOCKER_IMAGE }} diff --git a/CMakeLists.txt b/CMakeLists.txt index e33856c2446d..13abaec62a30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,10 @@ # Licensed under the Apache License, Version 2.0 (the "License") cmake_minimum_required(VERSION 2.8.7) +if (${CMAKE_VERSION} VERSION_EQUAL 3.12.0 OR ${CMAKE_VERSION} VERSION_GREATER 3.12.0) + cmake_policy(SET CMP0074 NEW) +endif() + project(bcc) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) @@ -73,7 +77,7 @@ endif() if(NOT PYTHON_ONLY) find_package(LLVM REQUIRED CONFIG) -message(STATUS "Found LLVM: ${LLVM_INCLUDE_DIRS} ${LLVM_PACKAGE_VERSION}") +message(STATUS "Found LLVM: ${LLVM_INCLUDE_DIRS} ${LLVM_PACKAGE_VERSION} (Use LLVM_ROOT envronment variable for another version of LLVM)") if(ENABLE_CLANG_JIT) find_package(BISON) diff --git a/INSTALL.md b/INSTALL.md index ad33440fe3ff..8001986dc024 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -235,11 +235,9 @@ sudo yum install bcc ## Amazon Linux 2 - Binary Use case 1. Install BCC for your AMI's default kernel (no reboot required): - Tested on Amazon Linux AMI release 2020.03 (kernel 4.14.154-128.181.amzn2.x86_64) + Tested on Amazon Linux AMI release 2021.11 (kernel 5.10.75-79.358.amzn2.x86_64) ``` -sudo amazon-linux-extras enable BCC -sudo yum install kernel-devel-$(uname -r) -sudo yum install bcc +sudo amazon-linux-extras install BCC ``` ## Alpine - Binary diff --git a/README.md b/README.md index e95532ba6022..076d127c5e42 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ pair of .c and .py files, and some are directories of files. - examples/tracing/[task_switch.py](examples/tracing/task_switch.py): Count task switches with from and to PIDs. - examples/tracing/[tcpv4connect.py](examples/tracing/tcpv4connect.py): Trace TCP IPv4 active connections. [Examples](examples/tracing/tcpv4connect_example.txt). - examples/tracing/[trace_fields.py](examples/tracing/trace_fields.py): Simple example of printing fields from traced events. +- examples/tracing/[undump.py](examples/tracing/undump.py): Dump UNIX socket packets. [Examples](examples/tracing/undump_example.txt) - examples/tracing/[urandomread.py](examples/tracing/urandomread.py): A kernel tracepoint example, which traces random:urandom_read. [Examples](examples/tracing/urandomread_example.txt). - examples/tracing/[vfsreadlat.py](examples/tracing/vfsreadlat.py) examples/tracing/[vfsreadlat.c](examples/tracing/vfsreadlat.c): VFS read latency distribution. [Examples](examples/tracing/vfsreadlat_example.txt). - examples/tracing/[kvm_hypercall.py](examples/tracing/kvm_hypercall.py): Conditional static kernel tracepoints for KVM entry, exit and hypercall [Examples](examples/tracing/kvm_hypercall.txt). diff --git a/docs/kernel-versions.md b/docs/kernel-versions.md index f92062730db1..1b4334569331 100644 --- a/docs/kernel-versions.md +++ b/docs/kernel-versions.md @@ -218,6 +218,7 @@ Helper | Kernel version | License | Commit | `BPF_FUNC_current_task_under_cgroup()` | 4.9 | | [`60d20f9195b2`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=60d20f9195b260bdf0ac10c275ae9f6016f9c069) `BPF_FUNC_d_path()` | 5.10 | | [`6e22ab9da793`](https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit?id=6e22ab9da79343532cd3cde39df25e5a5478c692) `BPF_FUNC_fib_lookup()` | 4.18 | GPL | [`87f5fc7e48dd`](https://git.kernel.org/cgit/linux/kernel/git/davem/net-next.git/commit/?id=87f5fc7e48dd3175b30dd03b41564e1a8e136323) +`BPF_FUNC_find_vma()` | 5.17 | | [`7c7e3d31e785`](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit?id=7c7e3d31e7856a8260a254f8c71db416f7f9f5a1) `BPF_FUNC_for_each_map_elem()` | 5.13 | | [`69c087ba6225`](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit?id=69c087ba6225b574afb6e505b72cb75242a3d844) `BPF_FUNC_get_attach_cookie()` | 5.15 | | [`7adfc6c9b315`](https://github.com/torvalds/linux/commit/7adfc6c9b315e174cf8743b21b7b691c8766791b) `BPF_FUNC_get_branch_snapshot()` | 5.16 | GPL | [`856c02dbce4f`](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit?id=856c02dbce4f8d6a5644083db22c11750aa11481) @@ -249,6 +250,7 @@ Helper | Kernel version | License | Commit | `BPF_FUNC_inode_storage_delete()` | 5.10 | | [`8ea636848aca`](https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit?id=8ea636848aca35b9f97c5b5dee30225cf2dd0fe6) `BPF_FUNC_inode_storage_get()` | 5.10 | | [`8ea636848aca`](https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit?id=8ea636848aca35b9f97c5b5dee30225cf2dd0fe6) `BPF_FUNC_jiffies64()` | 5.5 | | [`5576b991e9c1`](https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/commit/?id=5576b991e9c1a11d2cc21c4b94fc75ec27603896) +`BPF_FUNC_kallsyms_lookup_name()` | 5.16 | | [`d6aef08a872b`](https://github.com/torvalds/linux/commit/d6aef08a872b9e23eecc92d0e92393473b13c497) `BPF_FUNC_ktime_get_boot_ns()` | 5.7 | GPL | [`71d19214776e`](https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next/+/71d19214776e61b33da48f7c1b46e522c7f78221) `BPF_FUNC_ktime_get_coarse_ns()` | 5.11 | GPL | [`d05512618056`](https://github.com/torvalds/linux/commit/d055126180564a57fe533728a4e93d0cb53d49b3) `BPF_FUNC_ktime_get_ns()` | 4.1 | GPL | [`d9847d310ab4`](https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=d9847d310ab4003725e6ed1822682e24bd406908) diff --git a/examples/tracing/undump.py b/examples/tracing/undump.py old mode 100644 new mode 100755 diff --git a/examples/tracing/undump_example.txt b/examples/tracing/undump_example.txt new file mode 100644 index 000000000000..1d72aa4dd1f4 --- /dev/null +++ b/examples/tracing/undump_example.txt @@ -0,0 +1,39 @@ +Demonstrations of undump.py, the Linux eBPF/bcc version. + +This example trace the kernel function performing receive AP_UNIX socket +packet. Some example output: + +Terminal 1, UNIX Socket Server: + +``` +$ nc -lU /var/tmp/dsocket +# receive from Client +Hello, World +abcdefg +``` + +Terminal 2, UNIX socket Client: + +``` +$ nc -U /var/tmp/dsocket +# Input some lines +Hello, World +abcdefg +``` + +Terminal 3, receive tracing: + +``` +$ sudo python undump.py -p 49264 +Tracing PID=49264 UNIX socket packets ... Hit Ctrl-C to end + +# Here print bytes of receive +PID 49264 Recv 13 bytes + 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 0a +PID 49264 Recv 8 bytes + 61 62 63 64 65 66 67 0a +``` + +This output shows two packet received by PID 49264(nc -lU /var/tmp/dsocket), +`Hello, World` will be parsed as `48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 0a`, the +`0a` is `Enter`. `abcdefg` will be parsed as `61 62 63 64 65 66 67 0a`. diff --git a/introspection/bps.c b/introspection/bps.c index 6ec02e6cbc15..232b23d414f8 100644 --- a/introspection/bps.c +++ b/introspection/bps.c @@ -80,6 +80,7 @@ static const char * const map_type_strings[] = { [BPF_MAP_TYPE_RINGBUF] = "ringbuf", [BPF_MAP_TYPE_INODE_STORAGE] = "inode_storage", [BPF_MAP_TYPE_TASK_STORAGE] = "task_storage", + [BPF_MAP_TYPE_BLOOM_FILTER] = "bloom_filter", }; #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) diff --git a/man/man8/sslsniff.8 b/man/man8/sslsniff.8 index 7b945b00ee4f..df81664b0fd4 100644 --- a/man/man8/sslsniff.8 +++ b/man/man8/sslsniff.8 @@ -2,7 +2,8 @@ .SH NAME sslsniff \- Print data passed to OpenSSL, GnuTLS or NSS. Uses Linux eBPF/bcc. .SH SYNOPSIS -.B sslsniff [-h] [-p PID] [-c COMM] [-o] [-g] [-n] [-d] [--hexdump] +.B sslsniff [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] +.B [--hexdump] [--max-buffer-size SIZE] .SH DESCRIPTION sslsniff prints data sent to write/send and read/recv functions of OpenSSL, GnuTLS and NSS, allowing us to read plain text content before @@ -13,11 +14,47 @@ This works reading the second parameter of both functions (*buf). Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. +.SH OPTIONS +.TP +\-h +Print usage message. +.TP +\-p PID +Trace only functions in this process PID. +.TP +\-u UID +Trace only calls made by this UID. +.TP +\-x +Show extra fields: UID and TID. +.TP +\-c COMM +Show only processes that match this COMM exactly. +.TP +\-o, \-\-no-openssl +Do not trace OpenSSL functions. +.TP +\-g, \-\-no-gnutls +Do not trace GnuTLS functions. +.TP +\-n, \-\-no-nss +Do not trace GnuTLS functions. +.TP +\-\-hexdump +Show data as hexdump instead of trying to decode it as UTF-8 +.TP +\-\-max-buffer-size SIZE +Sets maximum buffer size of intercepted data. Longer values would be truncated. +Default value is 8 Kib, maximum possible value is a bit less than 32 Kib. .SH EXAMPLES .TP Print all calls to SSL write/send and read/recv system-wide: # .B sslsniff +.TP +Print only OpenSSL calls issued by user with UID 1000 +# +.B sslsniff -u 1000 --no-nss --no-gnutls .SH FIELDS .TP FUNC @@ -34,6 +71,12 @@ Process ID calling SSL. .TP LEN Bytes written or read by SSL functions. +.TP +UID +UID of the process, displayed only if launched with -x. +.TP +TID +Thread ID, displayed only if launched with -x. .SH SOURCE This is from bcc. .IP diff --git a/src/cc/compat/linux/virtual_bpf.h b/src/cc/compat/linux/virtual_bpf.h index 3193afe23a10..92f8da5a462a 100644 --- a/src/cc/compat/linux/virtual_bpf.h +++ b/src/cc/compat/linux/virtual_bpf.h @@ -907,6 +907,7 @@ enum bpf_map_type { BPF_MAP_TYPE_RINGBUF, BPF_MAP_TYPE_INODE_STORAGE, BPF_MAP_TYPE_TASK_STORAGE, + BPF_MAP_TYPE_BLOOM_FILTER, }; /* Note that tracing related programs such as @@ -1275,6 +1276,13 @@ union bpf_attr { * struct stored as the * map value */ + /* Any per-map-type extra fields + * + * BPF_MAP_TYPE_BLOOM_FILTER - the lowest 4 bits indicate the + * number of hash functions (if 0, the bloom filter will default + * to using 5 hash functions). + */ + __u64 map_extra; }; struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */ @@ -1737,7 +1745,7 @@ union bpf_attr { * if the maximum number of tail calls has been reached for this * chain of programs. This limit is defined in the kernel by the * macro **MAX_TAIL_CALL_CNT** (not accessible to user space), - * which is currently set to 32. + * which is currently set to 33. * Return * 0 on success, or a negative error in case of failure. * @@ -4916,6 +4924,40 @@ union bpf_attr { * Dynamically cast a *sk* pointer to a *unix_sock* pointer. * Return * *sk* if casting is valid, or **NULL** otherwise. + * + * long bpf_kallsyms_lookup_name(const char *name, int name_sz, int flags, u64 *res) + * Description + * Get the address of a kernel symbol, returned in *res*. *res* is + * set to 0 if the symbol is not found. + * Return + * On success, zero. On error, a negative value. + * + * **-EINVAL** if *flags* is not zero. + * + * **-EINVAL** if string *name* is not the same size as *name_sz*. + * + * **-ENOENT** if symbol is not found. + * + * **-EPERM** if caller does not have permission to obtain kernel address. + * + * long bpf_find_vma(struct task_struct *task, u64 addr, void *callback_fn, void *callback_ctx, u64 flags) + * Description + * Find vma of *task* that contains *addr*, call *callback_fn* + * function with *task*, *vma*, and *callback_ctx*. + * The *callback_fn* should be a static function and + * the *callback_ctx* should be a pointer to the stack. + * The *flags* is used to control certain aspects of the helper. + * Currently, the *flags* must be 0. + * + * The expected callback signature is + * + * long (\*callback_fn)(struct task_struct \*task, struct vm_area_struct \*vma, void \*callback_ctx); + * + * Return + * 0 on success. + * **-ENOENT** if *task->mm* is NULL, or no vma contains *addr*. + * **-EBUSY** if failed to try lock mmap_lock. + * **-EINVAL** for invalid **flags**. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5097,6 +5139,8 @@ union bpf_attr { FN(get_branch_snapshot), \ FN(trace_vprintk), \ FN(skc_to_unix_sock), \ + FN(kallsyms_lookup_name), \ + FN(find_vma), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5639,6 +5683,8 @@ struct bpf_map_info { __u32 btf_id; __u32 btf_key_type_id; __u32 btf_value_type_id; + __u32 :32; /* alignment pad */ + __u64 map_extra; } __attribute__((aligned(8))); struct bpf_btf_info { @@ -6271,6 +6317,7 @@ struct bpf_sk_lookup { __u32 local_ip4; /* Network byte order */ __u32 local_ip6[4]; /* Network byte order */ __u32 local_port; /* Host byte order */ + __u32 ingress_ifindex; /* The arriving interface. Determined by inet_iif. */ }; /* diff --git a/src/cc/export/helpers.h b/src/cc/export/helpers.h index 596f4a45c9e3..cc80cf0c6b08 100644 --- a/src/cc/export/helpers.h +++ b/src/cc/export/helpers.h @@ -914,6 +914,12 @@ static long (*bpf_trace_vprintk)(const char *fmt, __u32 fmt_size, const void *da (void *)BPF_FUNC_trace_vprintk; static struct unix_sock *(*bpf_skc_to_unix_sock)(void *sk) = (void *)BPF_FUNC_skc_to_unix_sock; +static long (*bpf_kallsyms_lookup_name)(const char *name, int name_sz, int flags, + __u64 *res) = + (void *)BPF_FUNC_kallsyms_lookup_name; +static long (*bpf_find_vma)(struct task_struct *task, __u64 addr, void *callback_fn, + void *callback_ctx, __u64 flags) = + (void *)BPF_FUNC_find_vma; /* llvm builtin functions that eBPF C program may use to * emit BPF_LD_ABS and BPF_LD_IND instructions @@ -1350,5 +1356,11 @@ static int ____##name(unsigned long long *ctx, ##args) bpf_probe_read((void *)dst, __length, (char *)args + __offset); \ } while (0); +#define TP_DATA_LOC_READ_STR(dst, field, length) \ + do { \ + unsigned short __offset = args->data_loc_##field & 0xFFFF; \ + bpf_probe_read_str((void *)dst, length, (char *)args + __offset); \ + } while (0); + #endif )********" diff --git a/src/cc/libbpf b/src/cc/libbpf index eaea2bce024f..94a49850c5ee 160000 --- a/src/cc/libbpf +++ b/src/cc/libbpf @@ -1 +1 @@ -Subproject commit eaea2bce024fa6ae0db54af1e78b4d477d422791 +Subproject commit 94a49850c5ee61ea02dfcbabf48013391e8cecdf diff --git a/src/cc/libbpf.c b/src/cc/libbpf.c index 6c2faed6ce90..6434ec486e0f 100644 --- a/src/cc/libbpf.c +++ b/src/cc/libbpf.c @@ -283,6 +283,8 @@ static struct bpf_helper helpers[] = { {"get_branch_snapshot", "5.16"}, {"trace_vprintk", "5.16"}, {"skc_to_unix_sock", "5.16"}, + {"kallsyms_lookup_name", "5.16"}, + {"find_vma", "5.17"}, }; static uint64_t ptr_to_u64(void *ptr) diff --git a/src/cc/libbpf.h b/src/cc/libbpf.h index b3608e22a4f3..8a49d6da58e2 100644 --- a/src/cc/libbpf.h +++ b/src/cc/libbpf.h @@ -151,6 +151,12 @@ int bcc_iter_attach(int prog_fd, union bpf_iter_link_info *link_info, int bcc_iter_create(int link_fd); int bcc_make_parent_dir(const char *path); int bcc_check_bpffs_path(const char *path); +int bpf_lookup_batch(int fd, __u32 *in_batch, __u32 *out_batch, void *keys, + void *values, __u32 *count); +int bpf_delete_batch(int fd, void *keys, __u32 *count); +int bpf_update_batch(int fd, void *keys, void *values, __u32 *count); +int bpf_lookup_and_delete_batch(int fd, __u32 *in_batch, __u32 *out_batch, + void *keys, void *values, __u32 *count); #define LOG_BUF_SIZE 65536 diff --git a/tests/cc/test_sk_storage.cc b/tests/cc/test_sk_storage.cc index c774f0419ef6..1ca1a4e5709a 100644 --- a/tests/cc/test_sk_storage.cc +++ b/tests/cc/test_sk_storage.cc @@ -25,7 +25,7 @@ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0) -TEST_CASE("test sk_storage map", "[sk_storage]") { +TEST_CASE("test sk_storage map", "[sk_storage][!mayfail]") { { const std::string BPF_PROGRAM = R"( BPF_SK_STORAGE(sk_pkt_cnt, __u64); diff --git a/tests/python/test_call1.py b/tests/python/test_call1.py index 68d68de5b0ae..6766cab3026f 100755 --- a/tests/python/test_call1.py +++ b/tests/python/test_call1.py @@ -10,6 +10,7 @@ import sys from time import sleep from unittest import main, TestCase +from utils import mayFail arg1 = sys.argv.pop(1) @@ -36,6 +37,7 @@ def setUp(self): self.jump[c_int(S_EOP)] = c_int(eop_fn.fd) self.stats = b.get_table("stats", c_int, c_ulonglong) + @mayFail("This may fail on github actions environment due to udp packet loss") def test_jumps(self): udp = socket(AF_INET, SOCK_DGRAM) udp.sendto(b"a" * 10, ("172.16.1.1", 5000)) diff --git a/tests/python/test_rlimit.py b/tests/python/test_rlimit.py index d3152d22334a..deda8a780638 100755 --- a/tests/python/test_rlimit.py +++ b/tests/python/test_rlimit.py @@ -8,9 +8,12 @@ from __future__ import print_function from bcc import BPF from unittest import main, skipUnless, TestCase +from utils import kernel_version_ge import distutils.version import os, resource +@skipUnless(not kernel_version_ge(5, 11), "Since d5299b67dd59 \"bpf: Memcg-based memory accounting for bpf maps\""\ + ",map mem has been counted against memcg, not rlimit") class TestRlimitMemlock(TestCase): def testRlimitMemlock(self): text = """ diff --git a/tests/python/test_tools_smoke.py b/tests/python/test_tools_smoke.py index ac83434b4ebd..6eedcae5c2bf 100755 --- a/tests/python/test_tools_smoke.py +++ b/tests/python/test_tools_smoke.py @@ -347,6 +347,7 @@ def test_trace(self): self.run_with_int("trace.py do_sys_open") @skipUnless(kernel_version_ge(4,4), "requires kernel >= 4.4") + @mayFail("This fails on github actions environment, and needs to be fixed") def test_ttysnoop(self): self.run_with_int("ttysnoop.py /dev/console") diff --git a/tools/hardirqs.py b/tools/hardirqs.py index e5924faddfeb..70fffbc2d28c 100755 --- a/tools/hardirqs.py +++ b/tools/hardirqs.py @@ -85,7 +85,7 @@ TRACEPOINT_PROBE(irq, irq_handler_entry) { irq_key_t key = {.slot = 0 /* ignore */}; - TP_DATA_LOC_READ_CONST(&key.name, name, sizeof(key.name)); + TP_DATA_LOC_READ_STR(&key.name, name, sizeof(key.name)); dist.atomic_increment(key); return 0; } @@ -98,7 +98,7 @@ u64 ts = bpf_ktime_get_ns(); irq_name_t name = {}; - TP_DATA_LOC_READ_CONST(&name.name, name, sizeof(name)); + TP_DATA_LOC_READ_STR(&name.name, name, sizeof(name)); irqnames.update(&tid, &name); start.update(&tid, &ts); return 0; diff --git a/tools/sslsniff.py b/tools/sslsniff.py index 02b736040598..8bc61ce7a658 100755 --- a/tools/sslsniff.py +++ b/tools/sslsniff.py @@ -4,7 +4,8 @@ # GnuTLS and NSS # For Linux, uses BCC, eBPF. # -# USAGE: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-d] +# USAGE: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] +# [--hexdump] [--max-buffer-size SIZE] # # Licensed under the Apache License, Version 2.0 (the "License") # @@ -23,17 +24,23 @@ examples = """examples: ./sslsniff # sniff OpenSSL and GnuTLS functions ./sslsniff -p 181 # sniff PID 181 only + ./sslsniff -u 1000 # sniff only UID 1000 ./sslsniff -c curl # sniff curl command only ./sslsniff --no-openssl # don't show OpenSSL calls ./sslsniff --no-gnutls # don't show GnuTLS calls ./sslsniff --no-nss # don't show NSS calls ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8 + ./sslsniff -x # show process UID and TID """ parser = argparse.ArgumentParser( description="Sniff SSL data", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.") +parser.add_argument("-u", "--uid", type=int, default=None, + help="sniff this UID only.") +parser.add_argument("-x", "--extra", action="store_true", + help="show extra fields (UID, TID)") parser.add_argument("-c", "--comm", help="sniff only commands matching string.") parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl", @@ -48,6 +55,8 @@ help=argparse.SUPPRESS) parser.add_argument("--hexdump", action="store_true", dest="hexdump", help="show data as hexdump instead of trying to decode it as UTF-8") +parser.add_argument('--max-buffer-size', type=int, default=8192, + help='Size of captured buffer') args = parser.parse_args() @@ -55,34 +64,58 @@ #include #include /* For TASK_COMM_LEN */ +#define MAX_BUF_SIZE __MAX_BUF_SIZE__ + struct probe_SSL_data_t { u64 timestamp_ns; u32 pid; - char comm[TASK_COMM_LEN]; - char v0[464]; + u32 tid; + u32 uid; u32 len; + int buf_filled; + char comm[TASK_COMM_LEN]; + u8 buf[MAX_BUF_SIZE]; }; +#define BASE_EVENT_SIZE ((size_t)(&((struct probe_SSL_data_t*)0)->buf)) +#define EVENT_SIZE(X) (BASE_EVENT_SIZE + ((size_t)(X))) + + +BPF_PERCPU_ARRAY(ssl_data, struct probe_SSL_data_t, 1); BPF_PERF_OUTPUT(perf_SSL_write); int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) { + int ret; + u32 zero = 0; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; + u32 tid = pid_tgid; + u32 uid = bpf_get_current_uid_gid(); - FILTER + PID_FILTER + UID_FILTER + struct probe_SSL_data_t *data = ssl_data.lookup(&zero); + if (!data) + return 0; - struct probe_SSL_data_t __data = {0}; - __data.timestamp_ns = bpf_ktime_get_ns(); - __data.pid = pid; - __data.len = num; + data->timestamp_ns = bpf_ktime_get_ns(); + data->pid = pid; + data->tid = tid; + data->uid = uid; + data->len = num; + data->buf_filled = 0; + bpf_get_current_comm(&data->comm, sizeof(data->comm)); + u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)num); - bpf_get_current_comm(&__data.comm, sizeof(__data.comm)); + if (buf != 0) + ret = bpf_probe_read_user(data->buf, buf_copy_size, buf); - if ( buf != 0) { - bpf_probe_read_user(&__data.v0, sizeof(__data.v0), buf); - } + if (!ret) + data->buf_filled = 1; + else + buf_copy_size = 0; - perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data)); + perf_SSL_write.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size)); return 0; } @@ -94,47 +127,74 @@ u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; + u32 uid = bpf_get_current_uid_gid(); - FILTER + PID_FILTER + UID_FILTER bufs.update(&tid, (u64*)&buf); return 0; } int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) { + u32 zero = 0; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; + u32 uid = bpf_get_current_uid_gid(); + int ret; - FILTER + PID_FILTER + UID_FILTER u64 *bufp = bufs.lookup(&tid); - if (bufp == 0) { + if (bufp == 0) return 0; - } - struct probe_SSL_data_t __data = {0}; - __data.timestamp_ns = bpf_ktime_get_ns(); - __data.pid = pid; - __data.len = PT_REGS_RC(ctx); + int len = PT_REGS_RC(ctx); + if (len <= 0) // read failed + return 0; + + struct probe_SSL_data_t *data = ssl_data.lookup(&zero); + if (!data) + return 0; - bpf_get_current_comm(&__data.comm, sizeof(__data.comm)); + data->timestamp_ns = bpf_ktime_get_ns(); + data->pid = pid; + data->tid = tid; + data->uid = uid; + data->len = (u32)len; + data->buf_filled = 0; + u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len); - if (bufp != 0) { - bpf_probe_read_user(&__data.v0, sizeof(__data.v0), (char *)*bufp); - } + bpf_get_current_comm(&data->comm, sizeof(data->comm)); + + if (bufp != 0) + ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp); bufs.delete(&tid); - perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data)); + if (!ret) + data->buf_filled = 1; + else + buf_copy_size = 0; + + perf_SSL_read.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size)); return 0; } """ if args.pid: - prog = prog.replace('FILTER', 'if (pid != %d) { return 0; }' % args.pid) + prog = prog.replace('PID_FILTER', 'if (pid != %d) { return 0; }' % args.pid) else: - prog = prog.replace('FILTER', '') + prog = prog.replace('PID_FILTER', '') + +if args.uid is not None: + prog = prog.replace('UID_FILTER', 'if (uid != %d) { return 0; }' % args.uid) +else: + prog = prog.replace('UID_FILTER', '') + +prog = prog.replace('__MAX_BUF_SIZE__', str(args.max_buffer_size)) if args.debug or args.ebpf: print(prog) @@ -179,14 +239,15 @@ fn_name="probe_SSL_read_exit", pid=args.pid or -1) # define output data structure in Python -TASK_COMM_LEN = 16 # linux/sched.h -MAX_BUF_SIZE = 464 # Limited by the BPF stack # header -print("%-12s %-18s %-16s %-7s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID", - "LEN")) +header = "%-12s %-18s %-16s %-7s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID", "LEN") +if args.extra: + header += " %-7s %-7s" % ("UID", "TID") + +print(header) # process event start = 0 @@ -202,6 +263,16 @@ def print_event_read(cpu, data, size): def print_event(cpu, data, size, rw, evt): global start event = b[evt].event(data) + if event.len <= args.max_buffer_size: + buf_size = event.len + else: + buf_size = args.max_buffer_size + + if event.buf_filled == 1: + buf = bytearray(event.buf[:buf_size]) + else: + buf_size = 0 + buf = b"" # Filter events by command if args.comm: @@ -216,19 +287,38 @@ def print_event(cpu, data, size, rw, evt): e_mark = "-" * 5 + " END DATA " + "-" * 5 - truncated_bytes = event.len - MAX_BUF_SIZE + truncated_bytes = event.len - buf_size if truncated_bytes > 0: e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \ " bytes lost) " + "-" * 5 - fmt = "%-12s %-18.9f %-16s %-7d %-6d\n%s\n%s\n%s\n\n" + base_fmt = "%(func)-12s %(time)-18.9f %(comm)-16s %(pid)-7d %(len)-6d" + + if args.extra: + base_fmt += " %(uid)-7d %(tid)-7d" + + fmt = ''.join([base_fmt, "\n%(begin)s\n%(data)s\n%(end)s\n\n"]) if args.hexdump: - unwrapped_data = binascii.hexlify(event.v0) - data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'),width=32) + unwrapped_data = binascii.hexlify(buf) + data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'), width=32) else: - data = event.v0.decode('utf-8', 'replace') - print(fmt % (rw, time_s, event.comm.decode('utf-8', 'replace'), - event.pid, event.len, s_mark, data, e_mark)) + data = buf.decode('utf-8', 'replace') + + fmt_data = { + 'func': rw, + 'time': time_s, + 'comm': event.comm.decode('utf-8', 'replace'), + 'pid': event.pid, + 'tid': event.tid, + 'uid': event.uid, + 'len': event.len, + 'begin': s_mark, + 'end': e_mark, + 'data': data + } + + print(fmt % fmt_data) + b["perf_SSL_write"].open_perf_buffer(print_event_write) b["perf_SSL_read"].open_perf_buffer(print_event_read) diff --git a/tools/sslsniff_example.txt b/tools/sslsniff_example.txt index 360561f7267f..fa36c40dfb5e 100644 --- a/tools/sslsniff_example.txt +++ b/tools/sslsniff_example.txt @@ -105,13 +105,16 @@ characters. USAGE message: -usage: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-n] [-d] [--hexdump] +usage: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] + [--hexdump] [--max-buffer-size MAX_BUFFER_SIZE] Sniff SSL data optional arguments: -h, --help show this help message and exit -p PID, --pid PID sniff this PID only. + -u UID, --uid UID sniff this UID only. + -x, --extra show extra fields (UID, TID) -c COMM, --comm COMM sniff only commands matching string. -o, --no-openssl do not show OpenSSL calls. -g, --no-gnutls do not show GnuTLS calls. @@ -119,12 +122,16 @@ optional arguments: -d, --debug debug mode. --hexdump show data as hexdump instead of trying to decode it as UTF-8 + --max-buffer-size MAX_BUFFER_SIZE + Size of captured buffer examples: ./sslsniff # sniff OpenSSL and GnuTLS functions ./sslsniff -p 181 # sniff PID 181 only + ./sslsniff -u 1000 # sniff only UID 1000 ./sslsniff -c curl # sniff curl command only ./sslsniff --no-openssl # don't show OpenSSL calls ./sslsniff --no-gnutls # don't show GnuTLS calls ./sslsniff --no-nss # don't show NSS calls ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8 + ./sslsniff -x # show process UID and TID