├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── fsprobe │ ├── cmd │ │ ├── args.go │ │ ├── cmd.go │ │ ├── fsprobe.go │ │ ├── model.go │ │ └── output.go │ └── main.go └── inotify │ └── inotify.go ├── documentation ├── FSProbe benchmark.pdf ├── fragments.png ├── maximum_rates.png ├── perf_buffer.png ├── perf_buffer_execution_overhead.png ├── perf_buffer_memory_overhead.png └── single_fragments.png ├── ebpf ├── bpf │ ├── bpf.h │ ├── bpf_helpers.h │ └── bpf_map.h ├── const.h ├── dentry.h ├── events │ ├── events.h │ ├── link.h │ ├── mkdir.h │ ├── modify.h │ ├── open.h │ ├── rename.h │ ├── rmdir.h │ ├── setattr.h │ └── unlink.h ├── filter.h ├── main.c ├── main.h ├── process.h └── structs.h ├── go.mod ├── go.sum ├── pkg ├── assets │ └── probe.go ├── fsprobe │ ├── fsprobe.go │ └── monitor │ │ ├── fs │ │ └── fs.go │ │ └── register.go ├── inotify │ ├── inotify.go │ ├── inotify_poller.go │ ├── model.go │ └── recursive_inotify.go ├── model │ ├── const.go │ ├── events.go │ ├── fsprobe.go │ ├── maps.go │ ├── monitor.go │ ├── options.go │ ├── perfmap.go │ ├── probe.go │ └── resolver.go └── utils │ └── utils.go ├── tests ├── open_test.go └── paths_generator.go └── tools.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | bin/ 4 | vendor/ 5 | tmp/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build run 2 | 3 | build-ebpf: 4 | mkdir -p ebpf/bin 5 | clang -D__KERNEL__ -D__ASM_SYSREG_H \ 6 | -Wno-unused-value \ 7 | -Wno-pointer-sign \ 8 | -Wno-compare-distinct-pointer-types \ 9 | -Wunused \ 10 | -Wall \ 11 | -Werror \ 12 | -I/lib/modules/$$(uname -r)/build/include \ 13 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 14 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 15 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 16 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 17 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 18 | -O2 -emit-llvm \ 19 | ebpf/main.c \ 20 | -c -o - | llc -march=bpf -filetype=obj -o ebpf/bin/probe.o 21 | go run github.com/shuLhan/go-bindata/cmd/go-bindata -pkg assets -prefix "ebpf/bin" -o "pkg/assets/probe.go" "ebpf/bin/probe.o" 22 | 23 | build: 24 | mkdir -p bin/ 25 | go build -o bin/ ./cmd/... 26 | 27 | run: 28 | sudo ./bin/fsprobe /tmp 29 | 30 | install: 31 | sudo cp ./bin/fsprobe /usr/bin/ 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## FSProbe 2 | 3 | FSProbe is a file system events notifier based on eBPF. Instead of hooking at the syscall level (like other eBPF solutions: [opensnoop](https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py), [Falco](https://github.com/falcosecurity/falco), ...), FSProbe works by listening for events at the VFS level. Paths are resolved at runtime by going through the `dentry` tree up to the mount point of the filesystem. One of the main advantages of this solution is that the paths provided by FSProbe are absolute and resolved, while a syscall based strategy would only export syscall parameters (and thus potentially attacker controlled data). 4 | 5 | ### Requirements 6 | 7 | - golang 1.16+ 8 | - This project was built on a Linux Kernel 5.3 and should be compatible with Kernels 5.0+. 9 | - Kernel headers are expected to be installed in `lib/modules/$(uname -r)`, update the `Makefile` with their location otherwise. 10 | - clang & llvm (version 8.0.1) 11 | 12 | ### Getting Started 13 | 14 | 1) If you need to rebuild the eBPF programs, use the following command: 15 | 16 | ```shell script 17 | make build-ebpf 18 | ``` 19 | 20 | 2) To build FSProbe, run: 21 | 22 | ```shell script 23 | make build 24 | ``` 25 | 26 | 3) To install FSProbe (copy to /usr/bin/fsprobe) run: 27 | ```shell script 28 | make install 29 | ``` 30 | 31 | 4) FSProbe needs to run as root. Run `sudo fsprobe -h` to get help. 32 | 33 | ```shell script 34 | # ~ ./bin/fsprobe -h 35 | FSProbe is a file system events notifier based on eBPF 36 | 37 | FSProbe relies on eBPF to capture file system events on dentry kernel structures. 38 | More information about the project can be found on github: https://github.com/Gui774ume/fsprobe 39 | 40 | Usage: 41 | fsprobe [paths] [flags] 42 | 43 | Examples: 44 | sudo fsprobe /tmp 45 | 46 | Flags: 47 | -s, --chan-size int User space channel size (default 1000) 48 | --dentry-resolution-mode string In-kernel dentry resolution mode. Can be either "fragments", 49 | "single_fragment" or "perf_buffer" (default "perf_buffer") 50 | -e, --event string Listens for specific event(s) only. This option can be specified 51 | more than once. If omitted, all the events will be activated except the modify one. 52 | Available options: open, mkdir, link, rename, setattr, unlink, 53 | rmdir, modify (default "[]") 54 | --follow When activated, FSProbe will keep watching the files that were 55 | initially in a watched directory and were moved to a location 56 | that is not necessarily watched. In other words, files are followed 57 | even after a move (default true) 58 | -f, --format string Defines the output format. 59 | Options are: table, json, none (default "table") 60 | -h, --help help for fsprobe 61 | -o, --output string Outputs events to the provided file rather than 62 | stdout 63 | --paths-filtering When activated, FSProbe will only notify events on the paths 64 | provided to the Watch function. When deactivated, FSProbe 65 | will notify events on the entire file system (default true) 66 | --perf-buffer-size int Perf ring buffer size for kernel-space to user-space 67 | communication (default 128) 68 | -r, --recursive Watches all subdirectories of any directory passed as argument. 69 | Watches will be set up recursively to an unlimited depth. 70 | Symbolic links are not traversed. Newly created subdirectories 71 | will also be watched. When this option is not provided, only 72 | the immediate children of a provided directory are watched (default true) 73 | ``` 74 | 75 | ### Dentry resolution mode 76 | 77 | FSProbe can be configured to use one of 3 different `dentry` resolution modes. A performance benchmark can be found below to understand the overhead of each solution in kernel space and user space. All three methods are implemented in [dentry.h](ebpf/dentry.h). 78 | 79 | #### Fragments 80 | 81 | ##### Architecture 82 | 83 | ![Fragments solution architecture](documentation/fragments.png) 84 | 85 | ##### Cache 86 | 87 | The `path_fragments` hashmap is used as an in-kernel cache. This means that the dentry resolver will not always insert a fragment if it is already present. Similarly, the dentry resolution will stop as soon as a path is found in cache. 88 | 89 | #### Single Fragment 90 | 91 | ##### Architecture 92 | 93 | ![Fragments solution architecture](documentation/single_fragments.png) 94 | 95 | ##### Cache 96 | 97 | Just like `path_fragments`, `single_fragments` is used as an in-kernel cache. If an inode has already been resolved it will not be resolved a second time. 98 | 99 | #### Perf buffer 100 | 101 | ##### Architecture 102 | 103 | ![Fragments solution architecture](documentation/perf_buffer.png) 104 | 105 | ##### Cache 106 | 107 | This method relies on a user space cache and the `cached_inodes` eBPF hashmap to decide where the in-kernel resolution should stop. Since the `cached_inodes` map is queried on each parent of a file, the resolution can stop right in the middle, in which case the event sent back to user space will only contain the missing part. For example, on the graph below, `/etc` was in the cache but the `passwd` file was not; the event sent on the ring buffer contains the missing part of the path (`passwd`) and the inode & mount ID of the cached prefix (`{43, 27}`). In order to avoid resolving `passwd` again, a new entry in both the user space cache and the `cached_inodes` eBPF hashmap are added (see the doted lines). 108 | 109 | #### Benchmark 110 | 111 | ##### Important metrics 112 | 113 | Each resolution method described above has its strengths and its weaknesses. Depending on the use case, choosing the right resolution method might yield better performances. We based our benchmark on 3 important metrics: 114 | 115 | - **The in-kernel execution overhead**: as the dentry resolution happens within FSProbe's eBPF programs, a captured FS syscall will only return once the path has been resolved. The faster our eBPF programs are, the smaller the runtime overhead and delay will be for the applications in production. This overhead is measured in nanosecond per operation (ns/op), where an "operation" is made of an `Open` and a `Close` syscall. 116 | - **The user space CPU & memory usage**: bringing a stream of events back to user space has serious performance implications on FSProbe's user space program. The more resource FSProbe needs to bring those events back, the less resource will be available for the rest of the host. This overhead is measured in bytes per operation (B/op), where the calculated amount of bytes is the total amount of memory that was allocated to bring 1 event back to user space. Although this doesn't produce any metric on the CPU per se, allocating memory and copying data put a lot of pressure on the CPU, and are the main consumers of CPU resources (since the benchmark doesn't do anything once an event is brought back). 117 | - **The maximum events rate sustained over 10 seconds**: one of the biggest concerns with a monitoring tool based on eBPF & perf ring buffers is for the kernel to drop some events because the buffers are full. This metric is important to determine the maximum rate of events per second above which events will be dropped. 118 | 119 | ##### Benchmark parameters 120 | 121 | We quickly realized that only 3 parameters have a real impact on the usage of resources: 122 | 123 | - The depths of the paths that are being watched. 124 | - The total number of nodes that are being watched (a node designates a folder or a file). 125 | - The size of the perf ring buffer `fs_events`, used to send events back to user space. 126 | 127 | Based on this observation, we decided that all our benchmarks would use the following parameters: 128 | 129 | - All the paths used for the benchmark will be made of nodes of identical size: 10 randomly generated letters or numbers. 130 | - All the user space channels will have a buffer of 1,000 entries. 131 | - Each benchmark will be done with `b.N` set to 120,000 iterations. 132 | - The paths generator will randomly choose a file to open in the pool of generated paths, at each iteration. 133 | - The paths generator will generate paths with constant depths: there can only be one child folder per folder and only the last folder contains one or multiple files. 134 | - In-kernel caches will be set to 40,000 entries for each method, and the inodes filter will be set to allow up to 120,000 inodes. 135 | 136 | Finally, we ran the benchmark through 6 scenarios: 137 | 138 | 1) `depths = 10 / breadth = 1 / file_count = 1` 139 | 2) `depths = 60 / breadth = 1 / file_count = 1` 140 | 3) `depths = 10 / breadth = 1 / file_count = 120,000` (all 120,000 files are in the same leaf folder) 141 | 4) `depths = 10 / breadth = 4,000 / file_count = 80,000` (2 files per leaf folder) 142 | 5) `depths = 60 / breadth = 1,000 / file_count = 60,000` (1 file per leaf folder) 143 | 6) `depths = 5 / breadth = 8,000 / file_count = 80,000` (2 files per leaf folder) 144 | 145 | For each scenario we retried the benchmark with a different perf rin buffer size. We tested the following sizes: [8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]. 146 | 147 | ##### Results 148 | 149 | The entire output of the benchmark is available [here](documentation/FSProbe%20benchmark.pdf), the following table contains only the high level output: 150 | 151 | | scenario | fragments | single_fragment | perf_buffer | 152 | | :---: | :--- | :--- | :--- | 153 | | 1 | **Best ns/op**: next is 10% slower (**Evt drop limit**: 256) | **Best ns/op**: next is 10% slower (**Evt drop limit**: 2048) | **Lowest B/op**: others are 100% - 400% higher (**Evt drop limit**: 64) | 154 | | 2 | **Best ns/op**: next is 5% slower (**Evt drop limit**: 256) | **Second best ns/op**: next is 20% slower (**Evt drop limit**: 512) | **Lowest B/op**: others are 500% - 1,000% higher (**Evt drop limit**: 32) | 155 | | 3 | **Best ns/op**: next is 9% slower (**Evt drop limit**: 1024) | **Evt drop limit**: 2048 | **Second best ns/op**: next is 30% slower. **Lowest B/op**: others are 250% - 450% higher (**Evt drop limit**: 64) | 156 | | 4 | **Evt drop limit**: 128 | **Evt drop limit**: 2048 | **Best ns/op**: others are 30% - 55% slower. **Lowest B/op**: others are 300% - 400% higher (**Evt drop limit**: 64) | 157 | | 5 | **Best ns/op**: next is 30% slower. **Lowest B/op**: others are 63% higher (**Evt drop limit**: 512) | **Evt drop limit**: 2048 | **Second best ns/op**: next is 20% slower. **Second lowest B/op**: next is 325% higher (**Evt drop limit**: 32) | 158 | | 6 | **Evt drop limit**: 128 | **Best ns/op**: next is 6% slower (**Evt drop limit**: 1024) | **Second best ns/op**: next is 1% slower. **Lowest B/op**: others are 300% - 350% higher (**Evt drop limit**: 64) | 159 | 160 | Based on this benchmark the `perf_buffer` method seems to be the most memory efficient one in most situations. Although its in-kernel overhead is not always the best, this method has such a lower memory footprint that we argue that the tradeoff is worth it. You will find below the output of the benchmark for the `perf_buffer` method, and for the scenario number 6 (we consider this scenario to be the most interesting one since it puts a lot of pressure on the caches). 161 | 162 | ![Perf buffer scenario 6 - execution overhead](documentation/perf_buffer_execution_overhead.png) 163 | 164 | ![Perf buffer scenario 6 - memory overhead](documentation/perf_buffer_memory_overhead.png) 165 | 166 | The last part of the benchmark is about the maximum sustainable rates of events per second. The following results were calculated for the parameters used in scenario 6. It is worth noting that the `single_fragment` method was unable to complete the benchmark, as it kept dropping events. 167 | 168 | ![Maximum rates of events per seconds (sustained over 10 seconds without losing events)](documentation/maximum_rates.png) 169 | 170 | ### Capabilities Matrix 171 | 172 | | Feature | [Inotify](https://www.man7.org/linux/man-pages/man7/inotify.7.html) | [FSProbe](https://github.com/Gui774ume/fsprobe) | [Opensnoop](https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py) | [Perf](http://www.brendangregg.com/perf.html) | [Falco](https://github.com/falcosecurity/falco) 173 | | --- | :---: | :--- | :---: | :--- | :---: | 174 | | Process context | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 175 | | User / Group context | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 176 | | Recursive feature | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 177 | | Resolved paths | :white_check_mark: | :white_check_mark: (max depth: 70) | :x: | :white_check_mark: (max depth: 9) | :x: | 178 | | Absolute paths | :white_check_mark: | :white_check_mark: (max depth: 70) | :x: | :white_check_mark: (max depth: 9) | :x: | 179 | | Inode context | :x: | :white_check_mark: | :x: | :white_check_mark: | :x: | 180 | | Mount point context | :x: | :white_check_mark: | :x: | :white_check_mark: | :x: | 181 | | In-kernel filtering | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | 182 | | Container context | :x: | :x: (not implemented yet) | :x: | :x: | :white_check_mark: | 183 | | Follow files after move | :x: | :white_check_mark: | :x: | :x: | :x: | 184 | 185 | ### Known issues 186 | 187 | - Depending on the activated events the cache might get corrupted after removes / unlinks / rmdir. 188 | - Paths are resolved up to the root of each mountpoint. 189 | 190 | ### Future work 191 | 192 | - Support for [CO-RE](https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html) will be added shortly so that eBPF compilation won't be needed anymore. 193 | - Mount point resolution in order to support containers. 194 | 195 | ### Real world example 196 | 197 | The [Datadog agent](https://github.com/DataDog/datadog-agent/tree/52cb2185d7188d965078ecf0a8c04db228374525/pkg/security) is currently using an improved version of the `fragments` method (with less overhead and better cache handling). 198 | -------------------------------------------------------------------------------- /cmd/fsprobe/cmd/args.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "fmt" 20 | 21 | "github.com/Gui774ume/fsprobe/pkg/model" 22 | ) 23 | 24 | type EventsValue struct { 25 | events *[]model.EventName 26 | } 27 | 28 | func NewEventsValue(events *[]model.EventName) *EventsValue { 29 | return &EventsValue{ 30 | events: events, 31 | } 32 | } 33 | 34 | func (ev *EventsValue) String() string { 35 | return fmt.Sprintf("%v", *ev.events) 36 | } 37 | 38 | func (ev *EventsValue) Set(val string) error { 39 | switch val { 40 | case "open": 41 | *ev.events = append(*ev.events, model.Open) 42 | case "mkdir": 43 | *ev.events = append(*ev.events, model.Mkdir) 44 | case "link": 45 | *ev.events = append(*ev.events, model.Link) 46 | case "rename": 47 | *ev.events = append(*ev.events, model.Rename) 48 | case "setattr": 49 | *ev.events = append(*ev.events, model.SetAttr) 50 | case "unlink": 51 | *ev.events = append(*ev.events, model.Unlink) 52 | case "rmdir": 53 | *ev.events = append(*ev.events, model.Rmdir) 54 | case "modify": 55 | *ev.events = append(*ev.events, model.Modify) 56 | default: 57 | return fmt.Errorf("unknown event type: %v", val) 58 | } 59 | return nil 60 | } 61 | 62 | func (ev *EventsValue) Type() string { 63 | return "string" 64 | } 65 | 66 | type DentryResolutionModeValue struct { 67 | mode *model.DentryResolutionMode 68 | } 69 | 70 | func NewDentryResolutionModeValue(mode *model.DentryResolutionMode) *DentryResolutionModeValue { 71 | // Defaults to Perf buffer resolution mode 72 | *mode = model.DentryResolutionPerfBuffer 73 | return &DentryResolutionModeValue{ 74 | mode: mode, 75 | } 76 | } 77 | 78 | func (drm *DentryResolutionModeValue) String() string { 79 | switch *drm.mode { 80 | case model.DentryResolutionFragments: 81 | return "fragments" 82 | case model.DentryResolutionSingleFragment: 83 | return "single_fragment" 84 | default: 85 | return "perf_buffer" 86 | } 87 | } 88 | 89 | func (drm *DentryResolutionModeValue) Set(val string) error { 90 | switch val { 91 | case "fragments": 92 | *drm.mode = model.DentryResolutionFragments 93 | case "single_fragment": 94 | *drm.mode = model.DentryResolutionSingleFragment 95 | case "perf_buffer": 96 | *drm.mode = model.DentryResolutionPerfBuffer 97 | default: 98 | return fmt.Errorf("unknown dentry resolution mode: %v", val) 99 | } 100 | return nil 101 | } 102 | 103 | func (drm *DentryResolutionModeValue) Type() string { 104 | return "string" 105 | } 106 | -------------------------------------------------------------------------------- /cmd/fsprobe/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | // FSProbeCmd represents the base command when called without any subcommands 23 | var FSProbeCmd = &cobra.Command{ 24 | Use: "fsprobe [paths]", 25 | Short: "A file system events notifier based on eBPF", 26 | Long: `FSProbe is a file system events notifier based on eBPF 27 | 28 | FSProbe relies on eBPF to capture file system events on dentry kernel structures. 29 | More information about the project can be found on github: https://github.com/Gui774ume/fsprobe`, 30 | RunE: runFSProbeCmd, 31 | Example: "sudo fsprobe /tmp", 32 | } 33 | 34 | // options - CLI options 35 | var options CLIOptions 36 | 37 | func init() { 38 | FSProbeCmd.Flags().Var( 39 | NewDentryResolutionModeValue(&options.FSOptions.DentryResolutionMode), 40 | "dentry-resolution-mode", 41 | `In-kernel dentry resolution mode. Can be either "fragments", 42 | "single_fragment" or "perf_buffer"`) 43 | FSProbeCmd.Flags().BoolVarP( 44 | &options.FSOptions.Recursive, 45 | "recursive", 46 | "r", 47 | true, 48 | `Watches all subdirectories of any directory passed as argument. 49 | Watches will be set up recursively to an unlimited depth. 50 | Symbolic links are not traversed. Newly created subdirectories 51 | will also be watched. When this option is not provided, only 52 | the immediate children of a provided directory are watched`) 53 | FSProbeCmd.Flags().BoolVar( 54 | &options.FSOptions.PathsFiltering, 55 | "paths-filtering", 56 | true, 57 | `When activated, FSProbe will only notify events on the paths 58 | provided to the Watch function. When deactivated, FSProbe 59 | will notify events on the entire file system`) 60 | FSProbeCmd.Flags().BoolVar( 61 | &options.FSOptions.FollowRenames, 62 | "follow", 63 | true, 64 | `When activated, FSProbe will keep watching the files that were 65 | initially in a watched directory and were moved to a location 66 | that is not necessarily watched. In other words, files are followed 67 | even after a move`) 68 | FSProbeCmd.Flags().VarP( 69 | NewEventsValue(&options.FSOptions.Events), 70 | "event", 71 | "e", 72 | `Listens for specific event(s) only. This option can be specified 73 | more than once. If omitted, all the events will be activated except the modify one. 74 | Available options: open, mkdir, link, rename, setattr, unlink, 75 | rmdir, modify`) 76 | FSProbeCmd.Flags().IntVarP( 77 | &options.FSOptions.UserSpaceChanSize, 78 | "chan-size", 79 | "s", 80 | 1000, 81 | "User space channel size") 82 | FSProbeCmd.Flags().IntVar( 83 | &options.FSOptions.PerfBufferSize, 84 | "perf-buffer-size", 85 | 128, 86 | `Perf ring buffer size for kernel-space to user-space 87 | communication`) 88 | FSProbeCmd.Flags().StringVarP( 89 | &options.Format, 90 | "format", 91 | "f", 92 | "table", 93 | `Defines the output format. 94 | Options are: table, json, none`) 95 | FSProbeCmd.Flags().StringVarP( 96 | &options.OutputFilePath, 97 | "output", 98 | "o", 99 | "", 100 | `Outputs events to the provided file rather than 101 | stdout`) 102 | } 103 | -------------------------------------------------------------------------------- /cmd/fsprobe/cmd/fsprobe.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "os" 22 | "os/signal" 23 | 24 | "github.com/sirupsen/logrus" 25 | "github.com/spf13/cobra" 26 | 27 | "github.com/Gui774ume/fsprobe/pkg/fsprobe" 28 | ) 29 | 30 | func runFSProbeCmd(cmd *cobra.Command, args []string) error { 31 | // 0) Sanitize the provided options 32 | if err := sanitizeOptions(args); err != nil { 33 | return err 34 | } 35 | 36 | // 1) Prepare events output handler 37 | output, err := NewOutput(options) 38 | if err != nil { 39 | logrus.Fatalf("couldn't create FSEvent output: %v", err) 40 | } 41 | 42 | // 2) Set the output channel to FSProbe's output channel 43 | options.FSOptions.EventChan = output.EvtChan 44 | options.FSOptions.LostChan = output.LostChan 45 | 46 | // 3) Instantiates FSProbe 47 | probe := fsprobe.NewFSProbeWithOptions(options.FSOptions) 48 | 49 | // 4) Start listening for events 50 | if err := probe.Watch(args...); err != nil { 51 | logrus.Fatalf("couldn't start watching the filesystem: %v", err) 52 | } 53 | 54 | // 5) Wait until interrupt signal 55 | wait() 56 | 57 | // Stop fsprobe 58 | if err := probe.Stop(); err != nil { 59 | logrus.Fatalf("couldn't gracefully shutdown fsprobe: %v", err) 60 | } 61 | 62 | // Close the output 63 | output.Close() 64 | return nil 65 | } 66 | 67 | // sanitizeOptions - Sanitizes the provided options 68 | func sanitizeOptions(args []string) error { 69 | if options.FSOptions.PathsFiltering && len(args) == 0 { 70 | return errors.New("paths filtering is activated but no path was provided") 71 | } 72 | if len(args) > 0 { 73 | options.FSOptions.PathsFiltering = true 74 | } 75 | return nil 76 | } 77 | 78 | // wait - Waits until an interrupt or kill signal is sent 79 | func wait() { 80 | sig := make(chan os.Signal, 1) 81 | signal.Notify(sig, os.Interrupt, os.Kill) 82 | <-sig 83 | fmt.Println() 84 | } 85 | -------------------------------------------------------------------------------- /cmd/fsprobe/cmd/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import "github.com/Gui774ume/fsprobe/pkg/model" 19 | 20 | // CLIOptions - Command line options 21 | type CLIOptions struct { 22 | Format string 23 | OutputFilePath string 24 | Paths []string 25 | FSOptions model.FSProbeOptions 26 | } 27 | -------------------------------------------------------------------------------- /cmd/fsprobe/cmd/output.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package cmd 17 | 18 | import ( 19 | "context" 20 | "encoding/json" 21 | "fmt" 22 | "github.com/sirupsen/logrus" 23 | "io" 24 | "os" 25 | "sync" 26 | 27 | "github.com/Gui774ume/fsprobe/pkg/model" 28 | ) 29 | 30 | type Output struct { 31 | EvtChan chan *model.FSEvent 32 | LostChan chan *model.LostEvt 33 | wg *sync.WaitGroup 34 | ctx context.Context 35 | cancel context.CancelFunc 36 | writer OutputWriter 37 | } 38 | 39 | // NewOutput - Returns an output instance configured with the requested format & output 40 | func NewOutput(options CLIOptions) (*Output, error) { 41 | writer, err := newOutputWriter(options) 42 | if err != nil { 43 | return nil, err 44 | } 45 | ctx, cancel := context.WithCancel(context.Background()) 46 | output := Output{ 47 | EvtChan: make(chan *model.FSEvent, options.FSOptions.UserSpaceChanSize), 48 | LostChan: make(chan *model.LostEvt, options.FSOptions.UserSpaceChanSize), 49 | wg: &sync.WaitGroup{}, 50 | ctx: ctx, 51 | cancel: cancel, 52 | writer: writer, 53 | } 54 | output.Start() 55 | return &output, nil 56 | } 57 | 58 | func (o *Output) Callback() { 59 | o.wg.Add(1) 60 | var evt *model.FSEvent 61 | var lost *model.LostEvt 62 | var ok bool 63 | var count int 64 | for { 65 | select { 66 | case <-o.ctx.Done(): 67 | logrus.Printf("%v events captured", count) 68 | o.wg.Done() 69 | return 70 | case lost, ok = <-o.LostChan: 71 | if !ok { 72 | logrus.Printf("%v events captured", count) 73 | o.wg.Done() 74 | return 75 | } 76 | logrus.Warnf("lost %v events from %v", lost.Count, lost.Map) 77 | break 78 | case evt, ok = <-o.EvtChan: 79 | if !ok { 80 | logrus.Printf("%v events captured", count) 81 | o.wg.Done() 82 | return 83 | } 84 | count++ 85 | // Handle event 86 | if err := o.writer.Write(evt); err != nil { 87 | logrus.Errorf("couldn't write event to output: %v", err) 88 | } 89 | break 90 | } 91 | } 92 | } 93 | 94 | func (o *Output) Start() { 95 | go o.Callback() 96 | } 97 | 98 | func (o *Output) Close() { 99 | o.cancel() 100 | close(o.EvtChan) 101 | close(o.LostChan) 102 | o.wg.Wait() 103 | } 104 | 105 | // OutputWriter - Data output interface 106 | type OutputWriter interface { 107 | Write(event *model.FSEvent) error 108 | } 109 | 110 | func newOutputWriter(options CLIOptions) (OutputWriter, error) { 111 | var writer io.Writer 112 | var err error 113 | if options.OutputFilePath == "" { 114 | writer = os.Stdout 115 | } else { 116 | writer, err = os.Open(options.OutputFilePath) 117 | if err != nil { 118 | return nil, err 119 | } 120 | } 121 | switch options.Format { 122 | case "json": 123 | return JSONOutput{output: writer}, nil 124 | case "none": 125 | return DummyOutput{}, nil 126 | default: 127 | return NewTableOutput(writer), nil 128 | } 129 | } 130 | 131 | // JSONOutput - JSON output writer 132 | type JSONOutput struct { 133 | output io.Writer 134 | } 135 | 136 | // Write - Write the event to the output writer 137 | func (so JSONOutput) Write(event *model.FSEvent) error { 138 | data, err := json.Marshal(event) 139 | if err != nil { 140 | return err 141 | } 142 | if _, err := so.output.Write(data); err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | 148 | // TableOutput - Table output writer 149 | type TableOutput struct { 150 | output io.Writer 151 | fmt string 152 | tsFmt string 153 | } 154 | 155 | func NewTableOutput(writer io.Writer) TableOutput { 156 | out := TableOutput{ 157 | output: writer, 158 | fmt: "%7v %7v %6v %6v %6v %6v %16v %6v %7v %6v %6v %16v %s\n", 159 | tsFmt: "3:04PM", 160 | } 161 | out.PrintHeader() 162 | return out 163 | } 164 | 165 | // Write - Write the event to the output writer 166 | func (to TableOutput) Write(event *model.FSEvent) error { 167 | fmt.Printf( 168 | to.fmt, 169 | event.EventType, 170 | event.Timestamp.Format(to.tsFmt), 171 | event.Pid, 172 | event.Tid, 173 | event.UID, 174 | event.GID, 175 | event.Comm, 176 | event.SrcInode, 177 | event.SrcMountID, 178 | model.ErrValueToString(event.Retval), 179 | event.PrintMode(), 180 | event.PrintFlags(), 181 | event.PrintFilenames(), 182 | ) 183 | return nil 184 | } 185 | 186 | // PrintHeader - Prints table header 187 | func (to TableOutput) PrintHeader() { 188 | fmt.Printf(to.fmt, "EVT", "TS", "PID", "TID", "UID", "GID", "CMD", "INODE", "MOUNTID", "RET", "MODE", "FLAG", "PATH") 189 | } 190 | 191 | // DummyOutput - Dummy output for the none format 192 | type DummyOutput struct{} 193 | 194 | // Write - Write the event to the output writer 195 | func (do DummyOutput) Write(event *model.FSEvent) error { 196 | return nil 197 | } 198 | -------------------------------------------------------------------------------- /cmd/fsprobe/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "github.com/Gui774ume/fsprobe/cmd/fsprobe/cmd" 20 | "github.com/sirupsen/logrus" 21 | ) 22 | 23 | func main() { 24 | logrus.SetFormatter(&logrus.TextFormatter{ 25 | FullTimestamp: true, 26 | TimestampFormat: "2006-01-02T15:04:05Z", 27 | DisableLevelTruncation: true, 28 | }) 29 | cmd.FSProbeCmd.Execute() 30 | } 31 | -------------------------------------------------------------------------------- /cmd/inotify/inotify.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "fmt" 21 | "github.com/Gui774ume/fsprobe/pkg/inotify" 22 | "log" 23 | "os" 24 | "os/signal" 25 | ) 26 | 27 | func main() { 28 | watcher, err := inotify.NewRWatcher() 29 | if err != nil { 30 | log.Println(err) 31 | } 32 | 33 | flag.Parse() 34 | paths := flag.Args() 35 | for _, path := range paths { 36 | watcher.AddRecursive(path) 37 | } 38 | 39 | for { 40 | e := <-watcher.Events 41 | fmt.Println(e) 42 | } 43 | 44 | sig := make(chan os.Signal, 1) 45 | signal.Notify(sig, os.Interrupt, os.Kill) 46 | <-sig 47 | } 48 | -------------------------------------------------------------------------------- /documentation/FSProbe benchmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/FSProbe benchmark.pdf -------------------------------------------------------------------------------- /documentation/fragments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/fragments.png -------------------------------------------------------------------------------- /documentation/maximum_rates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/maximum_rates.png -------------------------------------------------------------------------------- /documentation/perf_buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/perf_buffer.png -------------------------------------------------------------------------------- /documentation/perf_buffer_execution_overhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/perf_buffer_execution_overhead.png -------------------------------------------------------------------------------- /documentation/perf_buffer_memory_overhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/perf_buffer_memory_overhead.png -------------------------------------------------------------------------------- /documentation/single_fragments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gui774ume/fsprobe/97d2b70097769fb45eb36084ff3fdef8fd288eda/documentation/single_fragments.png -------------------------------------------------------------------------------- /ebpf/bpf/bpf_helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef __BPF_HELPERS_H 17 | #define __BPF_HELPERS_H 18 | 19 | #define member_address(source_struct, source_member) \ 20 | ({ \ 21 | void *__ret; \ 22 | __ret = (void *)(((char *)source_struct) + offsetof(typeof(*source_struct), source_member)); \ 23 | __ret; \ 24 | }) 25 | 26 | #define bpf_printk(fmt, ...) \ 27 | ({ \ 28 | char ____fmt[] = fmt; \ 29 | bpf_trace_printk(____fmt, sizeof(____fmt), \ 30 | ##__VA_ARGS__); \ 31 | }) 32 | 33 | /* helper macro to place programs, maps, license in 34 | * different sections in elf_bpf file. Section names 35 | * are interpreted by elf_bpf loader 36 | */ 37 | #define SEC(NAME) __attribute__((section(NAME), used)) 38 | 39 | /* helper functions called from eBPF programs written in C */ 40 | static void *(*bpf_map_lookup_elem)(void *map, void *key) = 41 | (void *)BPF_FUNC_map_lookup_elem; 42 | static int (*bpf_map_update_elem)(void *map, void *key, void *value, 43 | unsigned long long flags) = 44 | (void *)BPF_FUNC_map_update_elem; 45 | static int (*bpf_map_delete_elem)(void *map, void *key) = 46 | (void *)BPF_FUNC_map_delete_elem; 47 | static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) = 48 | (void *)BPF_FUNC_probe_read; 49 | static int (*bpf_probe_read_str)(void *dst, int size, void *unsafe_ptr) = 50 | (void *)BPF_FUNC_probe_read_str; 51 | static unsigned long long (*bpf_ktime_get_ns)(void) = 52 | (void *)BPF_FUNC_ktime_get_ns; 53 | static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = 54 | (void *)BPF_FUNC_trace_printk; 55 | static void (*bpf_tail_call)(void *ctx, void *map, int index) = 56 | (void *)BPF_FUNC_tail_call; 57 | static unsigned long long (*bpf_get_smp_processor_id)(void) = 58 | (void *)BPF_FUNC_get_smp_processor_id; 59 | static unsigned long long (*bpf_get_current_pid_tgid)(void) = 60 | (void *)BPF_FUNC_get_current_pid_tgid; 61 | static unsigned long long (*bpf_get_current_uid_gid)(void) = 62 | (void *)BPF_FUNC_get_current_uid_gid; 63 | static int (*bpf_get_current_comm)(void *buf, int buf_size) = 64 | (void *)BPF_FUNC_get_current_comm; 65 | static unsigned long long (*bpf_get_current_task)(void) = 66 | (void *)BPF_FUNC_get_current_task; 67 | static unsigned long long (*bpf_perf_event_read)(void *map, 68 | unsigned long long flags) = 69 | (void *)BPF_FUNC_perf_event_read; 70 | static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) = 71 | (void *)BPF_FUNC_clone_redirect; 72 | static int (*bpf_redirect)(int ifindex, int flags) = 73 | (void *)BPF_FUNC_redirect; 74 | static int (*bpf_redirect_map)(void *map, int key, int flags) = 75 | (void *)BPF_FUNC_redirect_map; 76 | static int (*bpf_perf_event_output)(void *ctx, void *map, 77 | unsigned long long flags, void *data, 78 | int size) = 79 | (void *)BPF_FUNC_perf_event_output; 80 | static int (*bpf_get_socket_cookie)(void *ctx) = 81 | (void *)BPF_FUNC_get_socket_cookie; 82 | static unsigned int (*bpf_get_socket_uid)(void *ctx) = 83 | (void *)BPF_FUNC_get_socket_uid; 84 | static int (*bpf_get_stackid)(void *ctx, void *map, int flags) = 85 | (void *)BPF_FUNC_get_stackid; 86 | static int (*bpf_probe_write_user)(void *dst, void *src, int size) = 87 | (void *)BPF_FUNC_probe_write_user; 88 | static int (*bpf_current_task_under_cgroup)(void *map, int index) = 89 | (void *)BPF_FUNC_current_task_under_cgroup; 90 | static u64 (*bpf_get_cgroup_classid)(void *ctx) = 91 | (void *)BPF_FUNC_get_cgroup_classid; 92 | static int (*bpf_skb_get_tunnel_key)(void *ctx, void *key, int size, int flags) = 93 | (void *)BPF_FUNC_skb_get_tunnel_key; 94 | static int (*bpf_skb_set_tunnel_key)(void *ctx, void *key, int size, int flags) = 95 | (void *)BPF_FUNC_skb_set_tunnel_key; 96 | static int (*bpf_skb_get_tunnel_opt)(void *ctx, void *md, int size) = 97 | (void *)BPF_FUNC_skb_get_tunnel_opt; 98 | static int (*bpf_skb_set_tunnel_opt)(void *ctx, void *md, int size) = 99 | (void *)BPF_FUNC_skb_set_tunnel_opt; 100 | static unsigned long long (*bpf_get_prandom_u32)(void) = 101 | (void *)BPF_FUNC_get_prandom_u32; 102 | static int (*bpf_xdp_adjust_head)(void *ctx, int offset) = 103 | (void *)BPF_FUNC_xdp_adjust_head; 104 | static int (*bpf_xdp_adjust_meta)(void *ctx, int offset) = 105 | (void *)BPF_FUNC_xdp_adjust_meta; 106 | static int (*bpf_setsockopt)(void *ctx, int level, int optname, void *optval, 107 | int optlen) = 108 | (void *)BPF_FUNC_setsockopt; 109 | static int (*bpf_getsockopt)(void *ctx, int level, int optname, void *optval, 110 | int optlen) = 111 | (void *)BPF_FUNC_getsockopt; 112 | static int (*bpf_sk_redirect_map)(void *ctx, void *map, int key, int flags) = 113 | (void *)BPF_FUNC_sk_redirect_map; 114 | static int (*bpf_sock_map_update)(void *map, void *key, void *value, 115 | unsigned long long flags) = 116 | (void *)BPF_FUNC_sock_map_update; 117 | static int (*bpf_perf_event_read_value)(void *map, unsigned long long flags, 118 | void *buf, unsigned int buf_size) = 119 | (void *)BPF_FUNC_perf_event_read_value; 120 | static int (*bpf_perf_prog_read_value)(void *ctx, void *buf, 121 | unsigned int buf_size) = 122 | (void *)BPF_FUNC_perf_prog_read_value; 123 | static int (*bpf_override_return)(void *ctx, unsigned long rc) = 124 | (void *)BPF_FUNC_override_return; 125 | 126 | /* llvm builtin functions that eBPF C program may use to 127 | * emit BPF_LD_ABS and BPF_LD_IND instructions 128 | */ 129 | struct sk_buff; 130 | unsigned long long load_byte(void *skb, 131 | unsigned long long off) asm("llvm.bpf.load.byte"); 132 | unsigned long long load_half(void *skb, 133 | unsigned long long off) asm("llvm.bpf.load.half"); 134 | unsigned long long load_word(void *skb, 135 | unsigned long long off) asm("llvm.bpf.load.word"); 136 | 137 | static int (*bpf_skb_load_bytes)(void *ctx, int off, void *to, int len) = 138 | (void *)BPF_FUNC_skb_load_bytes; 139 | static int (*bpf_skb_store_bytes)(void *ctx, int off, void *from, int len, int flags) = 140 | (void *)BPF_FUNC_skb_store_bytes; 141 | static int (*bpf_l3_csum_replace)(void *ctx, int off, int from, int to, int flags) = 142 | (void *)BPF_FUNC_l3_csum_replace; 143 | static int (*bpf_l4_csum_replace)(void *ctx, int off, int from, int to, int flags) = 144 | (void *)BPF_FUNC_l4_csum_replace; 145 | static int (*bpf_skb_under_cgroup)(void *ctx, void *map, int index) = 146 | (void *)BPF_FUNC_skb_under_cgroup; 147 | static int (*bpf_skb_change_head)(void *, int len, int flags) = 148 | (void *)BPF_FUNC_skb_change_head; 149 | 150 | /* Scan the ARCH passed in from ARCH env variable (see Makefile) */ 151 | #if defined(__TARGET_ARCH_x86) 152 | #define bpf_target_x86 153 | #define bpf_target_defined 154 | #elif defined(__TARGET_ARCH_s930x) 155 | #define bpf_target_s930x 156 | #define bpf_target_defined 157 | #elif defined(__TARGET_ARCH_arm64) 158 | #define bpf_target_arm64 159 | #define bpf_target_defined 160 | #elif defined(__TARGET_ARCH_mips) 161 | #define bpf_target_mips 162 | #define bpf_target_defined 163 | #elif defined(__TARGET_ARCH_powerpc) 164 | #define bpf_target_powerpc 165 | #define bpf_target_defined 166 | #elif defined(__TARGET_ARCH_sparc) 167 | #define bpf_target_sparc 168 | #define bpf_target_defined 169 | #else 170 | #undef bpf_target_defined 171 | #endif 172 | 173 | /* Fall back to what the compiler says */ 174 | #ifndef bpf_target_defined 175 | #if defined(__x86_64__) 176 | #define bpf_target_x86 177 | #elif defined(__s390x__) 178 | #define bpf_target_s930x 179 | #elif defined(__aarch64__) 180 | #define bpf_target_arm64 181 | #elif defined(__mips__) 182 | #define bpf_target_mips 183 | #elif defined(__powerpc__) 184 | #define bpf_target_powerpc 185 | #elif defined(__sparc__) 186 | #define bpf_target_sparc 187 | #endif 188 | #endif 189 | 190 | #if defined(bpf_target_x86) 191 | 192 | #define PT_REGS_PARM1(x) ((x)->di) 193 | #define PT_REGS_PARM2(x) ((x)->si) 194 | #define PT_REGS_PARM3(x) ((x)->dx) 195 | #define PT_REGS_PARM4(x) ((x)->cx) 196 | #define PT_REGS_PARM5(x) ((x)->r8) 197 | #define PT_REGS_RET(x) ((x)->sp) 198 | #define PT_REGS_FP(x) ((x)->bp) 199 | #define PT_REGS_RC(x) ((x)->ax) 200 | #define PT_REGS_SP(x) ((x)->sp) 201 | #define PT_REGS_IP(x) ((x)->ip) 202 | 203 | #elif defined(bpf_target_s390x) 204 | 205 | #define PT_REGS_PARM1(x) ((x)->gprs[2]) 206 | #define PT_REGS_PARM2(x) ((x)->gprs[3]) 207 | #define PT_REGS_PARM3(x) ((x)->gprs[4]) 208 | #define PT_REGS_PARM4(x) ((x)->gprs[5]) 209 | #define PT_REGS_PARM5(x) ((x)->gprs[6]) 210 | #define PT_REGS_RET(x) ((x)->gprs[14]) 211 | #define PT_REGS_FP(x) ((x)->gprs[11]) /* Works only with CONFIG_FRAME_POINTER */ 212 | #define PT_REGS_RC(x) ((x)->gprs[2]) 213 | #define PT_REGS_SP(x) ((x)->gprs[15]) 214 | #define PT_REGS_IP(x) ((x)->psw.addr) 215 | 216 | #elif defined(bpf_target_arm64) 217 | 218 | #define PT_REGS_PARM1(x) ((x)->regs[0]) 219 | #define PT_REGS_PARM2(x) ((x)->regs[1]) 220 | #define PT_REGS_PARM3(x) ((x)->regs[2]) 221 | #define PT_REGS_PARM4(x) ((x)->regs[3]) 222 | #define PT_REGS_PARM5(x) ((x)->regs[4]) 223 | #define PT_REGS_RET(x) ((x)->regs[30]) 224 | #define PT_REGS_FP(x) ((x)->regs[29]) /* Works only with CONFIG_FRAME_POINTER */ 225 | #define PT_REGS_RC(x) ((x)->regs[0]) 226 | #define PT_REGS_SP(x) ((x)->sp) 227 | #define PT_REGS_IP(x) ((x)->pc) 228 | 229 | #elif defined(bpf_target_mips) 230 | 231 | #define PT_REGS_PARM1(x) ((x)->regs[4]) 232 | #define PT_REGS_PARM2(x) ((x)->regs[5]) 233 | #define PT_REGS_PARM3(x) ((x)->regs[6]) 234 | #define PT_REGS_PARM4(x) ((x)->regs[7]) 235 | #define PT_REGS_PARM5(x) ((x)->regs[8]) 236 | #define PT_REGS_RET(x) ((x)->regs[31]) 237 | #define PT_REGS_FP(x) ((x)->regs[30]) /* Works only with CONFIG_FRAME_POINTER */ 238 | #define PT_REGS_RC(x) ((x)->regs[1]) 239 | #define PT_REGS_SP(x) ((x)->regs[29]) 240 | #define PT_REGS_IP(x) ((x)->cp0_epc) 241 | 242 | #elif defined(bpf_target_powerpc) 243 | 244 | #define PT_REGS_PARM1(x) ((x)->gpr[3]) 245 | #define PT_REGS_PARM2(x) ((x)->gpr[4]) 246 | #define PT_REGS_PARM3(x) ((x)->gpr[5]) 247 | #define PT_REGS_PARM4(x) ((x)->gpr[6]) 248 | #define PT_REGS_PARM5(x) ((x)->gpr[7]) 249 | #define PT_REGS_RC(x) ((x)->gpr[3]) 250 | #define PT_REGS_SP(x) ((x)->sp) 251 | #define PT_REGS_IP(x) ((x)->nip) 252 | 253 | #elif defined(bpf_target_sparc) 254 | 255 | #define PT_REGS_PARM1(x) ((x)->u_regs[UREG_I0]) 256 | #define PT_REGS_PARM2(x) ((x)->u_regs[UREG_I1]) 257 | #define PT_REGS_PARM3(x) ((x)->u_regs[UREG_I2]) 258 | #define PT_REGS_PARM4(x) ((x)->u_regs[UREG_I3]) 259 | #define PT_REGS_PARM5(x) ((x)->u_regs[UREG_I4]) 260 | #define PT_REGS_RET(x) ((x)->u_regs[UREG_I7]) 261 | #define PT_REGS_RC(x) ((x)->u_regs[UREG_I0]) 262 | #define PT_REGS_SP(x) ((x)->u_regs[UREG_FP]) 263 | 264 | /* Should this also be a bpf_target check for the sparc case? */ 265 | #if defined(__arch64__) 266 | #define PT_REGS_IP(x) ((x)->tpc) 267 | #else 268 | #define PT_REGS_IP(x) ((x)->pc) 269 | #endif 270 | 271 | #endif 272 | 273 | #ifdef bpf_target_powerpc 274 | #define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; }) 275 | #define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP 276 | #elif bpf_target_sparc 277 | #define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = PT_REGS_RET(ctx); }) 278 | #define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP 279 | #else 280 | #define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ bpf_probe_read(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); }) 281 | #define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ bpf_probe_read(&(ip), sizeof(ip), \ 282 | (void *)(PT_REGS_FP(ctx) + sizeof(ip))); }) 283 | #endif 284 | 285 | #endif 286 | -------------------------------------------------------------------------------- /ebpf/bpf/bpf_map.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #define BUF_SIZE_MAP_NS 256 17 | 18 | typedef struct bpf_map_def 19 | { 20 | unsigned int type; 21 | unsigned int key_size; 22 | unsigned int value_size; 23 | unsigned int max_entries; 24 | unsigned int map_flags; 25 | unsigned int inner_map_idx; 26 | unsigned int pinning; 27 | char namespace[BUF_SIZE_MAP_NS]; 28 | } bpf_map_def; 29 | 30 | enum bpf_pin_type 31 | { 32 | PIN_NONE = 0, 33 | PIN_OBJECT_NS, 34 | PIN_GLOBAL_NS, 35 | PIN_CUSTOM_NS, 36 | }; 37 | -------------------------------------------------------------------------------- /ebpf/const.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _CONST_H_ 17 | #define _CONST_H_ 18 | 19 | #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 20 | 21 | // load_dentry_resolution_mode - Loads the dentry resolution mode 22 | __attribute__((always_inline)) static u64 load_dentry_resolution_mode() { 23 | u64 dentry_resolution_mode = 0; 24 | LOAD_CONSTANT("dentry_resolution_mode", dentry_resolution_mode); 25 | return dentry_resolution_mode; 26 | } 27 | 28 | // load_inode_filtering_mode - Loads the inode filtering mode 29 | __attribute__((always_inline)) static u64 load_inode_filtering_mode() { 30 | u64 inode_filtering_mode = 0; 31 | LOAD_CONSTANT("inode_filtering_mode", inode_filtering_mode); 32 | return inode_filtering_mode; 33 | } 34 | 35 | // load_follow_mode - Loads the follow mode 36 | __attribute__((always_inline)) static u64 load_follow_mode() { 37 | u64 follow_mode = 0; 38 | LOAD_CONSTANT("follow_mode", follow_mode); 39 | return follow_mode; 40 | } 41 | 42 | // load_recursive_mode - Loads the recursive mode 43 | __attribute__((always_inline)) static u64 load_recursive_mode() { 44 | u64 recursive_mode = 0; 45 | LOAD_CONSTANT("recursive_mode", recursive_mode); 46 | return recursive_mode; 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /ebpf/events/events.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _EVENTS_H_ 17 | #define _EVENTS_H_ 18 | 19 | #include "link.h" 20 | #include "mkdir.h" 21 | #include "modify.h" 22 | #include "open.h" 23 | #include "rename.h" 24 | #include "rmdir.h" 25 | #include "setattr.h" 26 | #include "unlink.h" 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /ebpf/events/link.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _LINK_H_ 17 | #define _LINK_H_ 18 | 19 | // trace_link - Traces a file system link event. 20 | // @ctx: registers context 21 | // @old_dentry: pointer to the dentry structure of the source file 22 | // @new_dir: pointer to the inode structure of the destination directory 23 | // @new_dentry: pointer to the dentry structure of the destination file 24 | __attribute__((always_inline)) static int trace_link(struct pt_regs *ctx, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) 25 | { 26 | u32 cpu = bpf_get_smp_processor_id(); 27 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 28 | if (!data_cache) 29 | return 0; 30 | // Reset pathname keys (could mess up resolution if there was some leftover data) 31 | data_cache->fs_event.src_path_key = 0; 32 | data_cache->fs_event.target_path_key = 0; 33 | data_cache->cursor = 0; 34 | // Add process data 35 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 36 | // Probe type 37 | data_cache->fs_event.event = EVENT_LINK; 38 | 39 | // Add old inode data 40 | data_cache->fs_event.src_inode = get_dentry_ino(old_dentry); 41 | // Generate a fake key for the old inode as the inode will be reused 42 | data_cache->fs_event.src_path_key = bpf_get_prandom_u32(); 43 | // Add old mount ID 44 | struct inode *old_inode = get_dentry_inode(old_dentry); 45 | data_cache->fs_event.src_mount_id = get_inode_mount_id(old_inode); 46 | 47 | // Dentry data 48 | data_cache->src_dentry = old_dentry; 49 | data_cache->target_dir = new_dir; 50 | data_cache->target_dentry = new_dentry; 51 | 52 | // Filter 53 | if (filter(data_cache, FILTER_SRC) || filter(data_cache, FILTER_TARGET)) { 54 | // Resolve source 55 | resolve_paths(ctx, data_cache, RESOLVE_SRC); 56 | // cache data 57 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | // trace_link_ret - Traces the return of a file system link event. 64 | // @ctx: registers context 65 | __attribute__((always_inline)) static int trace_link_ret(struct pt_regs *ctx) 66 | { 67 | u64 key = bpf_get_current_pid_tgid(); 68 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 69 | if (!data_cache) 70 | return 0; 71 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 72 | 73 | // Add target inode data 74 | data_cache->fs_event.target_inode = get_dentry_ino(data_cache->target_dentry); 75 | // Add target mount ID 76 | data_cache->fs_event.target_mount_id = get_inode_mount_id(data_cache->target_dir); 77 | 78 | // Resolve Paths 79 | resolve_paths(ctx, data_cache, RESOLVE_TARGET | EMIT_EVENT); 80 | bpf_map_delete_elem(&dentry_cache, &key); 81 | return 0; 82 | } 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /ebpf/events/mkdir.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _MKDIR_H_ 17 | #define _MKDIR_H_ 18 | 19 | // trace_mkdir - Traces a file system mkdir event. 20 | // @ctx: registers context 21 | // @dir: pointer to the inode of the containing directory 22 | // @dentry: pointer to the dentry structure of the new directory 23 | // @mode: mode of the mkdir call 24 | __attribute__((always_inline)) static int trace_mkdir(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry, umode_t mode) 25 | { 26 | u32 cpu = bpf_get_smp_processor_id(); 27 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 28 | if (!data_cache) 29 | return 0; 30 | // Reset pathname keys (could mess up resolution if there was some leftover data) 31 | data_cache->fs_event.src_path_key = 0; 32 | data_cache->fs_event.target_path_key = 0; 33 | data_cache->cursor = 0; 34 | // Add process data 35 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 36 | // Probe type 37 | data_cache->fs_event.event = EVENT_MKDIR; 38 | 39 | // Add mode 40 | data_cache->fs_event.mode = (int)mode; 41 | 42 | // Mount ID 43 | data_cache->fs_event.src_mount_id = get_inode_mount_id(dir); 44 | 45 | // Dentry data 46 | data_cache->src_dentry = dentry; 47 | 48 | // Filter 49 | if (!filter(data_cache, FILTER_SRC)) 50 | return 0; 51 | 52 | // Send to cache 53 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 54 | return 0; 55 | } 56 | 57 | // trace_mkdir_ret - Traces the return of a file system mkdir event. 58 | // @ctx: registers context 59 | __attribute__((always_inline)) static int trace_mkdir_ret(struct pt_regs *ctx) 60 | { 61 | u64 key = bpf_get_current_pid_tgid(); 62 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 63 | if (!data_cache) 64 | return 0; 65 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 66 | 67 | // Add inode data 68 | data_cache->fs_event.src_inode = get_dentry_ino(data_cache->src_dentry); 69 | 70 | // Resolve paths 71 | resolve_paths(ctx, data_cache, RESOLVE_SRC | EMIT_EVENT); 72 | 73 | // Check recursive mode and insert inode if necessary 74 | u64 recursive = load_recursive_mode(); 75 | if (recursive) { 76 | u8 value = 0; 77 | bpf_map_update_elem(&inodes_filter, &data_cache->fs_event.src_inode, &value, BPF_ANY); 78 | } 79 | 80 | bpf_map_delete_elem(&dentry_cache, &key); 81 | return 0; 82 | } 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /ebpf/events/modify.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _MODIFY_H_ 17 | #define _MODIFY_H_ 18 | 19 | // trace_modify - Traces a file modification event. 20 | // @ctx: registers context 21 | // @dentry: pointer to the dentry of the file 22 | __attribute__((always_inline)) static int trace_modify(struct pt_regs *ctx, struct dentry *dentry, __u32 mask) 23 | { 24 | // We only care about file modification (id est FS_MODIFY) 25 | if (mask != 2) 26 | { 27 | return 0; 28 | } 29 | u32 cpu = bpf_get_smp_processor_id(); 30 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 31 | if (!data_cache) 32 | return 0; 33 | // Reset pathname keys (could mess up resolution if there was some leftover data) 34 | data_cache->fs_event.src_path_key = 0; 35 | data_cache->fs_event.target_path_key = 0; 36 | data_cache->cursor = 0; 37 | // Add process data 38 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 39 | // Probe type 40 | data_cache->fs_event.event = EVENT_MODIFY; 41 | 42 | // Add inode data 43 | data_cache->fs_event.src_inode = get_dentry_ino(dentry); 44 | // Add mount ID 45 | struct inode *inode = get_dentry_inode(dentry); 46 | data_cache->fs_event.src_mount_id = get_inode_mount_id(inode); 47 | 48 | // Dentry data 49 | data_cache->src_dentry = dentry; 50 | 51 | // Filter 52 | if (!filter(data_cache, FILTER_SRC)) 53 | return 0; 54 | 55 | // Send to cache 56 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 57 | return 0; 58 | } 59 | 60 | // trace_modify_ret - Traces the return of a file modification event. 61 | // @ctx: registers context 62 | __attribute__((always_inline)) static int trace_modify_ret(struct pt_regs *ctx) 63 | { 64 | u64 key = bpf_get_current_pid_tgid(); 65 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 66 | if (!data_cache) 67 | return 0; 68 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 69 | 70 | // Resolve paths 71 | resolve_paths(ctx, data_cache, RESOLVE_SRC | EMIT_EVENT); 72 | bpf_map_delete_elem(&dentry_cache, &key); 73 | return 0; 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /ebpf/events/open.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _OPEN_H_ 17 | #define _OPEN_H_ 18 | 19 | // trace_open - Traces a file system open event. 20 | // @ctx: registers context 21 | // @path: pointer to the file path structure 22 | __attribute__((always_inline)) static int trace_open(struct pt_regs *ctx, struct path *path) 23 | { 24 | u32 cpu = bpf_get_smp_processor_id(); 25 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 26 | if (!data_cache) 27 | return 0; 28 | // Reset pathname keys (could mess up resolution if there was some leftover data) 29 | data_cache->fs_event.src_path_key = 0; 30 | data_cache->fs_event.target_path_key = 0; 31 | data_cache->cursor = 0; 32 | // Add process data 33 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 34 | // Probe type 35 | data_cache->fs_event.event = EVENT_OPEN; 36 | 37 | // Add inode data 38 | struct dentry *dentry; 39 | bpf_probe_read(&dentry, sizeof(struct dentry *), &path->dentry); 40 | data_cache->fs_event.src_inode = get_dentry_ino(dentry); 41 | // Mount ID 42 | struct vfsmount *mnt; 43 | bpf_probe_read(&mnt, sizeof(struct vfsmount *), &path->mnt); 44 | bpf_probe_read(&data_cache->fs_event.src_mount_id, sizeof(int), (void *)mnt + 252); 45 | 46 | // Dentry data 47 | data_cache->src_dentry = dentry; 48 | 49 | // Filter 50 | if (!filter(data_cache, FILTER_SRC)) 51 | return 0; 52 | 53 | // Send to cache 54 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 55 | return 0; 56 | } 57 | 58 | // trace_open_ret - Traces the return of a file system open event. 59 | // @ctx: registers context 60 | __attribute__((always_inline)) static int trace_open_ret(struct pt_regs *ctx) 61 | { 62 | u64 key = bpf_get_current_pid_tgid(); 63 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 64 | if (!data_cache) 65 | return 0; 66 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 67 | 68 | // Resolve paths 69 | resolve_paths(ctx, data_cache, RESOLVE_SRC | EMIT_EVENT); 70 | load_dentry_resolution_mode(); 71 | bpf_map_delete_elem(&dentry_cache, &key); 72 | return 0; 73 | } 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /ebpf/events/rename.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _RENAME_H_ 17 | #define _RENAME_H_ 18 | 19 | // trace_rename - Traces a file system rename event. 20 | // @ctx: registers context 21 | // @old_dentry: pointer to the dentry structure of the source file 22 | // @new_dir: pointer to the inode structure of the destination directory 23 | // @new_dentry: pointer to the dentry structure of the destination file 24 | __attribute__((always_inline)) static int trace_rename(struct pt_regs *ctx, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) 25 | { 26 | u32 cpu = bpf_get_smp_processor_id(); 27 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 28 | if (!data_cache) 29 | return 0; 30 | // Reset pathname keys (could mess up resolution if there was some leftover data) 31 | data_cache->fs_event.src_path_key = 0; 32 | data_cache->fs_event.target_path_key = 0; 33 | data_cache->cursor = 0; 34 | // Add process data 35 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 36 | // Probe type 37 | data_cache->fs_event.event = EVENT_RENAME; 38 | 39 | data_cache->fs_event.src_inode = get_dentry_ino(old_dentry); 40 | // Generate a fake key for the old inode as the inode will be reused 41 | data_cache->fs_event.src_path_key = bpf_get_prandom_u32(); 42 | // Add old mount ID 43 | struct inode *old_inode = get_dentry_inode(old_dentry); 44 | data_cache->fs_event.src_mount_id = get_inode_mount_id(old_inode); 45 | 46 | // Dentry data 47 | data_cache->src_dentry = old_dentry; 48 | data_cache->target_dir = new_dir; 49 | data_cache->target_dentry = new_dentry; 50 | 51 | // Filter 52 | if (filter(data_cache, FILTER_SRC) ||filter(data_cache, FILTER_TARGET) ) { 53 | // Resolve source 54 | resolve_paths(ctx, data_cache, RESOLVE_SRC); 55 | // Send to cache 56 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 57 | } 58 | return 0; 59 | } 60 | 61 | // trace_rename_ret - Traces the return of a file system rename event. 62 | // @ctx: registers context 63 | __attribute__((always_inline)) static int trace_rename_ret(struct pt_regs *ctx) 64 | { 65 | u64 key = bpf_get_current_pid_tgid(); 66 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 67 | if (!data_cache) 68 | return 0; 69 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 70 | 71 | // Add target inode data 72 | data_cache->fs_event.target_inode = get_dentry_ino(data_cache->src_dentry); 73 | // Add target mount ID 74 | data_cache->fs_event.target_mount_id = get_inode_mount_id(data_cache->target_dir); 75 | 76 | // Resolve paths 77 | resolve_paths(ctx, data_cache, RESOLVE_TARGET | EMIT_EVENT); 78 | 79 | // Check follow mode and insert inode if necessary 80 | u64 follow_mode = load_follow_mode(); 81 | if (follow_mode) { 82 | u8 value = 0; 83 | bpf_map_update_elem(&inodes_filter, &data_cache->fs_event.target_inode, &value, BPF_ANY); 84 | } 85 | 86 | // Delete cache entry 87 | bpf_map_delete_elem(&dentry_cache, &key); 88 | return 0; 89 | } 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /ebpf/events/rmdir.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _RMDIR_H_ 17 | #define _RMDIR_H_ 18 | 19 | // trace_rmdir - Traces a file system rmdir event. 20 | // @ctx: registers context 21 | // @dir: pointer to the directory that contains the directory to delete 22 | // @dentry: pointer to the dentry of the directory to delete 23 | __attribute__((always_inline)) static int trace_rmdir(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) 24 | { 25 | u32 cpu = bpf_get_smp_processor_id(); 26 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 27 | if (!data_cache) 28 | return 0; 29 | // Reset pathname keys (could mess up resolution if there was some leftover data) 30 | data_cache->fs_event.src_path_key = 0; 31 | data_cache->fs_event.target_path_key = 0; 32 | data_cache->cursor = 0; 33 | // Add process data 34 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 35 | // Probe type 36 | data_cache->fs_event.event = EVENT_RMDIR; 37 | 38 | // Add inode data 39 | data_cache->fs_event.src_inode = get_dentry_ino(dentry); 40 | // Add mount ID 41 | data_cache->fs_event.src_mount_id = get_inode_mount_id(dir); 42 | 43 | // Dentry data 44 | data_cache->src_dentry = dentry; 45 | 46 | // Filter 47 | if (!filter(data_cache, FILTER_SRC)) 48 | return 0; 49 | 50 | // Send to cache 51 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 52 | return 0; 53 | } 54 | 55 | // trace_rmdir_ret - Traces the return of a file system rmdir event. 56 | // @ctx: registers context 57 | __attribute__((always_inline)) static int trace_rmdir_ret(struct pt_regs *ctx) 58 | { 59 | u64 key = bpf_get_current_pid_tgid(); 60 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 61 | if (!data_cache) 62 | return 0; 63 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 64 | 65 | // Resolve paths 66 | resolve_paths(ctx, data_cache, RESOLVE_SRC | EMIT_EVENT); 67 | bpf_map_delete_elem(&dentry_cache, &key); 68 | return 0; 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /ebpf/events/setattr.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _SETATTR_H_ 17 | #define _SETATTR_H_ 18 | 19 | // trace_security_inode_setattr - Traces a file system setattr event. 20 | // @ctx: registers context 21 | // @dentry: pointer to the dentry of the file 22 | // @attr: pointer to the iattr structure explaining what happened to the file 23 | __attribute__((always_inline)) static int trace_setattr(struct pt_regs *ctx, struct dentry *dentry, struct iattr *attr) 24 | { 25 | u32 cpu = bpf_get_smp_processor_id(); 26 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 27 | if (!data_cache) 28 | return 0; 29 | // Reset pathname keys (could mess up resolution if there was some leftover data) 30 | data_cache->fs_event.src_path_key = 0; 31 | data_cache->fs_event.target_path_key = 0; 32 | data_cache->cursor = 0; 33 | // Probe type 34 | data_cache->fs_event.event = EVENT_SETATTR; 35 | 36 | // Process data 37 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 38 | 39 | // SetAttr data 40 | bpf_probe_read(&data_cache->fs_event.flags, sizeof(attr->ia_valid), &attr->ia_valid); 41 | bpf_probe_read(&data_cache->fs_event.mode, sizeof(attr->ia_mode), &attr->ia_mode); 42 | 43 | // Add inode data 44 | data_cache->fs_event.src_inode = get_dentry_ino(dentry); 45 | // Add mount ID 46 | struct inode *inode = get_dentry_inode(dentry); 47 | data_cache->fs_event.src_mount_id = get_inode_mount_id(inode); 48 | 49 | // Dentry cache 50 | data_cache->src_dentry = dentry; 51 | 52 | // Filter 53 | if (!filter(data_cache, FILTER_SRC)) 54 | return 0; 55 | 56 | // Send to cache 57 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 58 | return 0; 59 | } 60 | 61 | // trace_setattr_ret - Traces the return of a file system setattr event. 62 | // @ctx: registers context 63 | __attribute__((always_inline)) static int trace_setattr_ret(struct pt_regs *ctx) 64 | { 65 | u64 key = bpf_get_current_pid_tgid(); 66 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 67 | if (!data_cache) 68 | return 0; 69 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 70 | 71 | // Resolve paths 72 | resolve_paths(ctx, data_cache, RESOLVE_SRC | EMIT_EVENT); 73 | bpf_map_delete_elem(&dentry_cache, &key); 74 | return 0; 75 | } 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /ebpf/events/unlink.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _UNLINK_H_ 17 | #define _UNLINK_H_ 18 | 19 | // trace_unlink - Traces a file system unlink event. 20 | // @ctx: registers context 21 | // @dir: pointer to the inode structure of the directory containing the file to delete 22 | // @dentry: pointer to the dentry structure of the file to delete 23 | __attribute__((always_inline)) static int trace_unlink(struct pt_regs *ctx, struct inode *dir, struct dentry *dentry) 24 | { 25 | u32 cpu = bpf_get_smp_processor_id(); 26 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache_builder, &cpu); 27 | if (!data_cache) 28 | return 0; 29 | // Reset pathname keys (could mess up resolution if there was some leftover data) 30 | data_cache->fs_event.src_path_key = 0; 31 | data_cache->fs_event.target_path_key = 0; 32 | data_cache->cursor = 0; 33 | // Add process data 34 | u64 key = fill_process_data(&data_cache->fs_event.process_data); 35 | // Probe type 36 | data_cache->fs_event.event = EVENT_UNLINK; 37 | 38 | // Add inode data 39 | data_cache->fs_event.src_inode = get_dentry_ino(dentry); 40 | // Add mount ID 41 | data_cache->fs_event.src_mount_id = get_inode_mount_id(dir); 42 | 43 | // Dentry cache 44 | data_cache->src_dentry = dentry; 45 | 46 | // Filter 47 | if (!filter(data_cache, FILTER_SRC)) 48 | return 0; 49 | 50 | // Send to cache 51 | bpf_map_update_elem(&dentry_cache, &key, data_cache, BPF_ANY); 52 | return 0; 53 | } 54 | 55 | // trace_unlink_ret - Traces the return of a file system unlink event. 56 | // @ctx: registers context 57 | __attribute__((always_inline)) static int trace_unlink_ret(struct pt_regs *ctx) 58 | { 59 | u64 key = bpf_get_current_pid_tgid(); 60 | struct dentry_cache_t *data_cache = bpf_map_lookup_elem(&dentry_cache, &key); 61 | if (!data_cache) 62 | return 0; 63 | data_cache->fs_event.retval = PT_REGS_RC(ctx); 64 | 65 | // Resolve paths 66 | resolve_paths(ctx, data_cache, RESOLVE_SRC | EMIT_EVENT); 67 | bpf_map_delete_elem(&dentry_cache, &key); 68 | return 0; 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /ebpf/filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _FILTER_H_ 17 | #define _FILTER_H_ 18 | 19 | #define FILTER_SRC 1 << 1 20 | #define FILTER_TARGET 1 << 2 21 | 22 | __attribute__((always_inline)) static int filter_src(struct dentry_cache_t *data_cache) 23 | { 24 | // Look for the inode in the cached_inodes map 25 | if (bpf_map_lookup_elem(&inodes_filter, &data_cache->fs_event.src_inode) == NULL) { 26 | // Look for the parent inode 27 | struct dentry *d_parent; 28 | bpf_probe_read(&d_parent, sizeof(d_parent), &data_cache->src_dentry->d_parent); 29 | u32 ino = get_dentry_ino(d_parent); 30 | if (bpf_map_lookup_elem(&inodes_filter, &ino) == NULL) { 31 | return 0; 32 | } 33 | } 34 | return 1; 35 | } 36 | 37 | __attribute__((always_inline)) static int filter_target(struct dentry_cache_t *data_cache) 38 | { 39 | // Look for the inode in the cached_inodes map 40 | if (bpf_map_lookup_elem(&inodes_filter, &data_cache->fs_event.target_inode) == NULL) { 41 | // Look for the parent inode 42 | struct dentry *d_parent; 43 | bpf_probe_read(&d_parent, sizeof(d_parent), &data_cache->target_dentry->d_parent); 44 | u32 ino = get_dentry_ino(d_parent); 45 | if (bpf_map_lookup_elem(&inodes_filter, &ino) == NULL) { 46 | return 0; 47 | } 48 | } 49 | return 1; 50 | } 51 | 52 | __attribute__((always_inline)) static int filter(struct dentry_cache_t *data_cache, u8 flag) 53 | { 54 | u64 inode_filtering_mode = load_inode_filtering_mode(); 55 | if (inode_filtering_mode == 0) { 56 | return 1; 57 | } 58 | if ((flag & FILTER_SRC) == FILTER_SRC) { 59 | if (!filter_src(data_cache)) { 60 | return 0; 61 | } 62 | } 63 | if ((flag & FILTER_TARGET) == FILTER_TARGET) { 64 | if (!filter_target(data_cache)) { 65 | return 0; 66 | } 67 | } 68 | return 1; 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /ebpf/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #include "main.h" 17 | 18 | // Hook points definition 19 | // ---------------------- 20 | // Below is the list of all the kernel hook points used by FSProbe. 21 | 22 | // OPEN 23 | 24 | SEC("kprobe/vfs_open") 25 | int kprobe_vfs_open(struct pt_regs *ctx) 26 | { 27 | struct path *path = (struct path *)PT_REGS_PARM1(ctx); 28 | return trace_open(ctx, path); 29 | } 30 | 31 | SEC("kretprobe/vfs_open") 32 | int kretprobe_vfs_open(struct pt_regs *ctx) 33 | { 34 | return trace_open_ret(ctx); 35 | } 36 | 37 | // MKDIR 38 | 39 | SEC("kprobe/vfs_mkdir") 40 | int kprobe_vfs_mkdir(struct pt_regs *ctx) 41 | { 42 | struct inode *dir = (struct inode *)PT_REGS_PARM1(ctx); 43 | struct dentry *dentry = (struct dentry *)PT_REGS_PARM2(ctx); 44 | umode_t mode = (umode_t)PT_REGS_PARM3(ctx); 45 | return trace_mkdir(ctx, dir, dentry, mode); 46 | } 47 | 48 | SEC("kretprobe/vfs_mkdir") 49 | int kretprobe_vfs_mkdir(struct pt_regs *ctx) 50 | { 51 | return trace_mkdir_ret(ctx); 52 | } 53 | 54 | // UNLINK 55 | 56 | SEC("kprobe/vfs_unlink") 57 | int kprobe_vfs_unlink(struct pt_regs *ctx) 58 | { 59 | struct inode *dir = (struct inode *)PT_REGS_PARM1(ctx); 60 | struct dentry *dentry = (struct dentry *)PT_REGS_PARM2(ctx); 61 | return trace_unlink(ctx, dir, dentry); 62 | } 63 | 64 | SEC("kretprobe/vfs_unlink") 65 | int kretprobe_vfs_unlink(struct pt_regs *ctx) 66 | { 67 | return trace_unlink_ret(ctx); 68 | } 69 | 70 | // RMDIR 71 | 72 | SEC("kprobe/vfs_rmdir") 73 | int kprobe_vfs_rmdir(struct pt_regs *ctx) 74 | { 75 | struct inode *dir = (struct inode *)PT_REGS_PARM1(ctx); 76 | struct dentry *dentry = (struct dentry *)PT_REGS_PARM2(ctx); 77 | return trace_rmdir(ctx, dir, dentry); 78 | } 79 | 80 | SEC("kretprobe/vfs_rmdir") 81 | int kretprobe_vfs_rmdir(struct pt_regs *ctx) 82 | { 83 | return trace_rmdir_ret(ctx); 84 | } 85 | 86 | // LINK 87 | 88 | SEC("kprobe/vfs_link") 89 | int kprobe_vfs_link(struct pt_regs *ctx) 90 | { 91 | struct dentry *old_dentry = (struct dentry *)PT_REGS_PARM1(ctx); 92 | struct inode *new_dir = (struct inode *)PT_REGS_PARM2(ctx); 93 | struct dentry *new_dentry = (struct dentry *)PT_REGS_PARM3(ctx); 94 | return trace_link(ctx, old_dentry, new_dir, new_dentry); 95 | } 96 | 97 | SEC("kretprobe/vfs_link") 98 | int kretprobe_vfs_link(struct pt_regs *ctx) 99 | { 100 | return trace_link_ret(ctx); 101 | } 102 | 103 | // RENAME 104 | 105 | SEC("kprobe/vfs_rename") 106 | int kprobe_vfs_rename(struct pt_regs *ctx) 107 | { 108 | struct dentry *old_dentry = (struct dentry *)PT_REGS_PARM2(ctx); 109 | struct inode *new_dir = (struct inode *)PT_REGS_PARM3(ctx); 110 | struct dentry *new_dentry = (struct dentry *)PT_REGS_PARM4(ctx); 111 | return trace_rename(ctx, old_dentry, new_dir, new_dentry); 112 | } 113 | 114 | SEC("kretprobe/vfs_rename") 115 | int kretprobe_vfs_rename(struct pt_regs *ctx) 116 | { 117 | return trace_rename_ret(ctx); 118 | } 119 | 120 | // MODIFY 121 | 122 | SEC("kprobe/__fsnotify_parent") 123 | int kprobe_fsnotify_parent(struct pt_regs *ctx) 124 | { 125 | struct dentry *dentry = (struct dentry *)PT_REGS_PARM2(ctx); 126 | __u32 mask = (__u32)PT_REGS_PARM3(ctx); 127 | return trace_modify(ctx, dentry, mask); 128 | } 129 | 130 | SEC("kretprobe/__fsnotify_parent") 131 | int kretprobe_fsnotify_parent(struct pt_regs *ctx) 132 | { 133 | return trace_modify_ret(ctx); 134 | } 135 | 136 | // SETATTR 137 | 138 | SEC("kprobe/security_inode_setattr") 139 | int kprobe_security_inode_setattr(struct pt_regs *ctx) 140 | { 141 | struct dentry *dentry = (struct dentry *)PT_REGS_PARM1(ctx); 142 | struct iattr *attr = (struct iattr *)PT_REGS_PARM2(ctx); 143 | return trace_setattr(ctx, dentry, attr); 144 | } 145 | 146 | SEC("kretprobe/security_inode_setattr") 147 | int kretprobe_security_inode_setattr(struct pt_regs *ctx) 148 | { 149 | return trace_setattr_ret(ctx); 150 | } 151 | 152 | char _license[] SEC("license") = "GPL"; 153 | __u32 _version SEC("version") = 0xFFFFFFFE; 154 | -------------------------------------------------------------------------------- /ebpf/main.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #include 17 | #include 18 | 19 | #pragma clang diagnostic push 20 | #pragma clang diagnostic ignored "-Waddress-of-packed-member" 21 | #pragma clang diagnostic ignored "-Warray-bounds" 22 | #pragma clang diagnostic ignored "-Wunused-label" 23 | #pragma clang diagnostic ignored "-Wframe-address" 24 | 25 | /* In Linux 5.4 asm_inline was introduced, but it's not supported by clang. 26 | * Redefine it to just asm to enable successful compilation. 27 | */ 28 | #ifdef asm_inline 29 | #undef asm_inline 30 | #define asm_inline asm 31 | #endif 32 | /* Before bpf_helpers.h is included, uapi bpf.h has been 33 | * included, which references linux/types.h. This may bring 34 | * in asm_volatile_goto definition if permitted based on 35 | * compiler setup and kernel configs. 36 | * 37 | * clang does not support "asm volatile goto" yet. 38 | * So redefine asm_volatile_goto to some invalid asm code. 39 | * If asm_volatile_goto is actually used by the bpf program, 40 | * a compilation error will appear. 41 | */ 42 | #ifdef asm_volatile_goto 43 | #undef asm_volatile_goto 44 | #endif 45 | #define asm_volatile_goto(x...) asm volatile("invalid use of asm_volatile_goto") 46 | 47 | #include 48 | #pragma clang diagnostic pop 49 | 50 | #pragma clang diagnostic push 51 | #pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end" 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #include 63 | #include 64 | #include 65 | #pragma clang diagnostic pop 66 | 67 | // Custom eBPF includes 68 | #include "bpf/bpf.h" 69 | #include "bpf/bpf_map.h" 70 | #include "bpf/bpf_helpers.h" 71 | #include "process.h" 72 | #include "structs.h" 73 | #include "const.h" 74 | #include "dentry.h" 75 | #include "filter.h" 76 | #include "events/events.h" 77 | -------------------------------------------------------------------------------- /ebpf/process.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _PROCESS_H_ 17 | #define _PROCESS_H_ 18 | 19 | // TTY_NAME_LEN - Maximum length of the TTY name 20 | #define TTY_NAME_LEN 16 21 | 22 | // process_ctx_t - Contains all the process context collected for a file system event 23 | struct process_ctx_t 24 | { 25 | // Process data 26 | u64 timestamp; 27 | u32 pid; 28 | u32 tid; 29 | u32 uid; 30 | u32 gid; 31 | char comm[TASK_COMM_LEN]; 32 | }; 33 | 34 | // fill_process_data - Fills the provided process_ctx_t with the process context available from eBPF 35 | __attribute__((always_inline)) static u64 fill_process_data(struct process_ctx_t *data) 36 | { 37 | // Comm 38 | bpf_get_current_comm(&data->comm, sizeof(data->comm)); 39 | 40 | // Pid & Tid 41 | u64 id = bpf_get_current_pid_tgid(); 42 | data->pid = id >> 32; 43 | data->tid = id; 44 | 45 | // UID & GID 46 | u64 userid = bpf_get_current_uid_gid(); 47 | data->uid = userid >> 32; 48 | data->gid = userid; 49 | return id; 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /ebpf/structs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | #ifndef _MAPS_H_ 17 | #define _MAPS_H_ 18 | 19 | // event_type - Defines the type of file system event 20 | enum event_type 21 | { 22 | EVENT_OPEN, 23 | EVENT_MKDIR, 24 | EVENT_LINK, 25 | EVENT_RENAME, 26 | EVENT_UNLINK, 27 | EVENT_RMDIR, 28 | EVENT_MODIFY, 29 | EVENT_SETATTR, 30 | }; 31 | 32 | // fs_event_t - File system event structure 33 | struct fs_event_t 34 | { 35 | struct process_ctx_t process_data; 36 | int flags; 37 | int mode; 38 | u32 src_path_key; 39 | u32 target_path_key; 40 | u64 src_inode; 41 | u32 src_path_length; 42 | int src_mount_id; 43 | u64 target_inode; 44 | u32 target_path_length; 45 | int target_mount_id; 46 | int retval; 47 | u32 event; 48 | }; 49 | 50 | // fs_events - Perf buffer used to send file system events back to user space 51 | struct bpf_map_def SEC("maps/fs_events") fs_events = { 52 | .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, 53 | .key_size = 0, 54 | .value_size = 0, 55 | .max_entries = 0, 56 | .pinning = PIN_NONE, 57 | .namespace = "", 58 | }; 59 | 60 | // dentry_cache_t - Dentry cache structure used to cache context between kprobes entry and return 61 | struct dentry_cache_t 62 | { 63 | struct fs_event_t fs_event; 64 | struct inode *src_dir; 65 | struct dentry *src_dentry; 66 | struct inode *target_dir; 67 | struct dentry *target_dentry; 68 | u32 cursor; 69 | }; 70 | 71 | // dentry_cache - Dentry cache map used to store dentry cache structures between 2 eBPF programs 72 | struct bpf_map_def SEC("maps/dentry_cache") dentry_cache = { 73 | .type = BPF_MAP_TYPE_LRU_HASH, 74 | .key_size = sizeof(u64), 75 | .value_size = sizeof(struct dentry_cache_t), 76 | .max_entries = 1000, 77 | .pinning = PIN_NONE, 78 | .namespace = "", 79 | }; 80 | 81 | // dentry_cache_builder - Dentry cache builder map used to reduce the amount of data on the stack 82 | struct bpf_map_def SEC("maps/dentry_cache_builder") dentry_cache_builder = { 83 | .type = BPF_MAP_TYPE_ARRAY, 84 | .key_size = sizeof(u32), 85 | .value_size = sizeof(struct dentry_cache_t), 86 | .max_entries = 16, 87 | .pinning = PIN_NONE, 88 | .namespace = "", 89 | }; 90 | 91 | // path_key_t - Structure used as the key to store path_fragment_t structures 92 | struct path_key_t { 93 | unsigned long ino; 94 | u32 mount_id; 95 | u32 padding; 96 | }; 97 | 98 | // path_fragment_t - Structure used to store path leaf during the path resolution process 99 | struct path_fragment_t 100 | { 101 | struct path_key_t parent; 102 | char name[NAME_MAX]; 103 | }; 104 | 105 | // path_fragments - Map used to store path fragments. The user space program will recover the fragments from this map. 106 | struct bpf_map_def SEC("maps/path_fragments") path_fragments = { 107 | .type = BPF_MAP_TYPE_LRU_HASH, 108 | .key_size = sizeof(struct path_key_t), 109 | .value_size = sizeof(struct path_fragment_t), 110 | .max_entries = 10000, 111 | .pinning = PIN_NONE, 112 | .namespace = "", 113 | }; 114 | 115 | // PATH_BUFFER_SIZE - Size of the eBPF buffer used to build a file system event. 116 | // Make sure that there is a n for which PATH_BUFFER = 2**n + NAME_MAX 117 | // n must be chosen so that MAX_PATH + MAX_PATH / 2 < 2**n 118 | // 2**13 + NAME_MAX = 8192 + 255 = 8447 119 | #define PATH_BUFFER_SIZE 8447 120 | 121 | struct fs_event_wrapper_t { 122 | struct fs_event_t evt; 123 | char buff[PATH_BUFFER_SIZE]; 124 | }; 125 | 126 | struct bpf_map_def SEC("maps/paths_builder") paths_builder = { 127 | .type = BPF_MAP_TYPE_ARRAY, 128 | .key_size = sizeof(u32), 129 | .value_size = sizeof(struct fs_event_wrapper_t), 130 | .max_entries = 40000, 131 | .pinning = PIN_NONE, 132 | .namespace = "", 133 | }; 134 | 135 | struct bpf_map_def SEC("maps/cached_inodes") cached_inodes = { 136 | .type = BPF_MAP_TYPE_LRU_HASH, 137 | .key_size = sizeof(u32), 138 | .value_size = sizeof(u8), 139 | .max_entries = 40000, 140 | .pinning = PIN_NONE, 141 | .namespace = "", 142 | }; 143 | 144 | struct bpf_map_def SEC("maps/inodes_filter") inodes_filter = { 145 | .type = BPF_MAP_TYPE_LRU_HASH, 146 | .key_size = sizeof(u32), 147 | .value_size = sizeof(u8), 148 | .max_entries = 120000, 149 | .pinning = PIN_NONE, 150 | .namespace = "", 151 | }; 152 | 153 | // SINGLE_FRAGMENTS_SIZE - See the comment about PATH_BUFFER_SIZE. The same condition applies, however those map values 154 | // will only hold one path at a time. Therefore we can choose: 2**12 + NAME_MAX = 4096 + 255 = 4351 155 | #define SINGLE_FRAGMENTS_SIZE 4351 156 | 157 | // single_fragment_t - Structure used to store single fragments during the path resolution process 158 | struct single_fragment_t 159 | { 160 | char name[SINGLE_FRAGMENTS_SIZE]; 161 | }; 162 | 163 | // path_fragments - Map used to store path fragments. The user space program will recover the fragments from this map. 164 | struct bpf_map_def SEC("maps/single_fragments") single_fragments = { 165 | .type = BPF_MAP_TYPE_LRU_HASH, 166 | .key_size = sizeof(u32), 167 | .value_size = sizeof(struct single_fragment_t), 168 | .max_entries = 40000, 169 | .pinning = PIN_NONE, 170 | .namespace = "", 171 | }; 172 | 173 | #endif 174 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Gui774ume/fsprobe 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 7 | github.com/Gui774ume/ebpf v0.0.0-20200411100314-4233cdb60f05 8 | github.com/hashicorp/golang-lru v0.5.1 9 | github.com/pkg/errors v0.9.1 10 | github.com/shuLhan/go-bindata v4.0.0+incompatible 11 | github.com/sirupsen/logrus v1.6.0 12 | github.com/spf13/cobra v1.0.0 13 | github.com/spf13/pflag v1.0.5 // indirect 14 | github.com/stretchr/testify v1.4.0 // indirect 15 | golang.org/x/sys v0.1.0 16 | gopkg.in/yaml.v2 v2.3.0 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 h1:OPAXA+r6yznoxWR5jQ2iTh5CvzIMrdw8AU0uFN2RwEw= 4 | github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321/go.mod h1:tGQp6XG4XpOyy67WG/YWXVxzOY6LejK35e8KcQhtRIQ= 5 | github.com/Gui774ume/ebpf v0.0.0-20200411100314-4233cdb60f05 h1:J8NeGZOZwZqLQQZYC5+CfpZeDA5nFxbpu/6Gy2AynpQ= 6 | github.com/Gui774ume/ebpf v0.0.0-20200411100314-4233cdb60f05/go.mod h1:LKA4oWjvzH4g598cq+NU3zBxkVA1lEf9bADxBxBlw9c= 7 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 8 | github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s= 9 | github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 10 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 11 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 12 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 13 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 14 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 15 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 16 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs= 17 | github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= 18 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 19 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 20 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 21 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 22 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 23 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 24 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 25 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 26 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 27 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 29 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 30 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 31 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 32 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 33 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 34 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 35 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 36 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 37 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 38 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 39 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 40 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 41 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 42 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 43 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 45 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 46 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 47 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 48 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 49 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 50 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 51 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 52 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 53 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 54 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 55 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 56 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 57 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 58 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 59 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 60 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 61 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 62 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 63 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 64 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 65 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 66 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 67 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 68 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 69 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 70 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 71 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 72 | github.com/newtools/ebpf v0.1.0/go.mod h1:H74E4gvXfcsOzgaoPSOuQVoKsWQrg2Xbx6+WybLKvyA= 73 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 74 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 75 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 76 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 77 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 78 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 79 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 80 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 81 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 82 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 83 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 84 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 85 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 86 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 87 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 88 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 89 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 90 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 91 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 92 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= 93 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= 94 | github.com/shuLhan/go-bindata v4.0.0+incompatible h1:xD8LkuVZLV5OOn/IEuFdt6EEAW7deWiqgwaaSGhjAJc= 95 | github.com/shuLhan/go-bindata v4.0.0+incompatible/go.mod h1:pkcPAATLBDD2+SpAPnX5vEM90F7fcwHCvvLCMXcmw3g= 96 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 97 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 98 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 99 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 100 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 101 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 102 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 103 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 104 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 105 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 106 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 107 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 108 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 109 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 110 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 111 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 112 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 113 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 114 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 115 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 116 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 117 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 118 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 119 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 120 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 121 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 122 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 123 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 124 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 125 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 126 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 127 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 128 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 129 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 130 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 131 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 132 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 133 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 134 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 135 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 139 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 141 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 142 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 143 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 146 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 148 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 149 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 150 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 151 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 152 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 153 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 154 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 155 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 156 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 157 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 158 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 159 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 160 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 161 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 162 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 163 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 164 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 165 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 166 | -------------------------------------------------------------------------------- /pkg/fsprobe/fsprobe.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package fsprobe 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "io/ioutil" 22 | "math" 23 | "math/rand" 24 | "os" 25 | "path" 26 | "path/filepath" 27 | "sync" 28 | "syscall" 29 | "time" 30 | 31 | "github.com/DataDog/gopsutil/host" 32 | "github.com/Gui774ume/ebpf" 33 | "github.com/pkg/errors" 34 | "github.com/sirupsen/logrus" 35 | "golang.org/x/sys/unix" 36 | 37 | "github.com/Gui774ume/fsprobe/pkg/assets" 38 | "github.com/Gui774ume/fsprobe/pkg/fsprobe/monitor" 39 | "github.com/Gui774ume/fsprobe/pkg/model" 40 | "github.com/Gui774ume/fsprobe/pkg/utils" 41 | ) 42 | 43 | // FSProbe - Main File system probe structure 44 | type FSProbe struct { 45 | options *model.FSProbeOptions 46 | paths []string 47 | wg *sync.WaitGroup 48 | collection *ebpf.Collection 49 | collectionSpec *ebpf.CollectionSpec 50 | monitors []*model.Monitor 51 | bootTime time.Time 52 | hostPidns uint64 53 | running bool 54 | runningMutex sync.RWMutex 55 | } 56 | 57 | // NewFSProbeWithOptions - Creates a new FSProbe instance with the provided options 58 | func NewFSProbeWithOptions(options model.FSProbeOptions) *FSProbe { 59 | // Extend RLIMIT_MEMLOCK (8) size 60 | err := unix.Setrlimit(8, &unix.Rlimit{ 61 | Cur: math.MaxUint64, 62 | Max: math.MaxUint64, 63 | }) 64 | if err != nil { 65 | logrus.Errorln("WARNING: Failed to adjust RLIMIT_MEMLOCK limit, loading eBPF maps might fail") 66 | } 67 | return &FSProbe{ 68 | options: &options, 69 | paths: []string{}, 70 | wg: &sync.WaitGroup{}, 71 | } 72 | } 73 | 74 | // GetWaitGroup - Returns the wait group of fsprobe 75 | func (fsp *FSProbe) GetWaitGroup() *sync.WaitGroup { 76 | return fsp.wg 77 | } 78 | 79 | // GetOptions - Returns the config of fsprobe 80 | func (fsp *FSProbe) GetOptions() *model.FSProbeOptions { 81 | return fsp.options 82 | } 83 | 84 | // GetCollection - Returns the eBPF collection of fsprobe 85 | func (fsp *FSProbe) GetCollection() *ebpf.Collection { 86 | return fsp.collection 87 | } 88 | 89 | // GetBootTime - Returns the boot time of fsprobe 90 | func (fsp *FSProbe) GetBootTime() time.Time { 91 | return fsp.bootTime 92 | } 93 | 94 | // GetHostPidns - Returns the host pidns of fsprobe 95 | func (fsp *FSProbe) GetHostPidns() uint64 { 96 | return fsp.hostPidns 97 | } 98 | 99 | // Watch - start watching the provided paths. This function is thread safe and can be called multiple times. If 100 | // already running, the new paths will be added dynamically. 101 | func (fsp *FSProbe) Watch(paths ...string) error { 102 | // 1) Check if FSProbe is already running 103 | fsp.runningMutex.RLock() 104 | if fsp.running { 105 | fsp.runningMutex.RUnlock() 106 | } else { 107 | // 1.1) setup FSProbe for the first time 108 | fsp.runningMutex.RUnlock() 109 | fsp.runningMutex.Lock() 110 | if err := fsp.start(); err != nil { 111 | return err 112 | } 113 | fsp.running = true 114 | fsp.runningMutex.Unlock() 115 | } 116 | // 2) Add watches for the provided paths 117 | if err := fsp.addWatch(paths...); err != nil { 118 | return err 119 | } 120 | return nil 121 | } 122 | 123 | // setup - runs the setup steps to start fsprobe 124 | func (fsp *FSProbe) start() error { 125 | // 1) Initialize FSProbe 126 | if err := fsp.init(); err != nil { 127 | return err 128 | } 129 | // 2) Load eBPF programs 130 | if err := fsp.loadEBPFProgram(); err != nil { 131 | return err 132 | } 133 | // 3) Start monitors 134 | if err := fsp.startMonitors(); err != nil { 135 | return err 136 | } 137 | return nil 138 | } 139 | 140 | // init - Initializes the NetworkSecurityProbe 141 | func (fsp *FSProbe) init() error { 142 | // Set a unique seed to prepare the generation of IDs 143 | rand.Seed(time.Now().UnixNano()) 144 | // Get boot time 145 | bt, err := host.BootTime() 146 | if err != nil { 147 | return err 148 | } 149 | fsp.bootTime = time.Unix(int64(bt), 0) 150 | // Get host netns 151 | fsp.hostPidns = utils.GetPidnsFromPid(1) 152 | // Register monitors 153 | fsp.monitors = monitor.RegisterMonitors() 154 | return nil 155 | } 156 | 157 | // Stop - Stop the file system probe 158 | func (fsp *FSProbe) Stop() error { 159 | // 1) Stop monitors 160 | for _, p := range fsp.monitors { 161 | if err := p.Stop(); err != nil { 162 | logrus.Errorf("couldn't stop monitor (Ctrl+C to abort): %v", p.GetName(), err) 163 | } 164 | } 165 | // 2) Close eBPF programs 166 | if fsp.collection != nil { 167 | if errs := fsp.collection.Close(); len(errs) > 0 { 168 | logrus.Errorf("couldn't close collection gracefully: %v", errs) 169 | } 170 | } 171 | // 3) Wait for all goroutine to stop 172 | fsp.wg.Wait() 173 | return nil 174 | } 175 | 176 | // compileEBPFProgram - Compile the eBPF programs of FSProbe using clang & llvm 177 | func (fsp *FSProbe) compileEBPFProgram() error { 178 | return nil 179 | } 180 | 181 | // loadEBPFProgram - Loads the compiled eBPF programs 182 | func (fsp *FSProbe) loadEBPFProgram() error { 183 | // Recover asset 184 | buf, err := assets.Asset("/probe.o") 185 | if err != nil { 186 | return errors.Wrap(err, "couldn't find asset") 187 | } 188 | reader := bytes.NewReader(buf) 189 | // Load elf CollectionSpec 190 | fsp.collectionSpec, err = ebpf.LoadCollectionSpecFromReader(reader) 191 | if err != nil { 192 | return errors.Wrap(err, "couldn't load collection spec") 193 | } 194 | // Remove unused maps based on the selected dentry resolution method 195 | fsp.removeUnusedMaps() 196 | // Edit runtime eBPF constants 197 | if err := fsp.EditEBPFConstants(fsp.collectionSpec); err != nil { 198 | return errors.Wrap(err, "couldn't edit runtime eBPF constants") 199 | } 200 | // Load eBPF program 201 | fsp.collection, err = ebpf.NewCollectionWithOptions(fsp.collectionSpec, ebpf.CollectionOptions{Programs: ebpf.ProgramOptions{LogSize: 1024 * 1024 * 3}}) 202 | if err != nil { 203 | return errors.Wrap(err, "couldn't load eBPF program") 204 | } 205 | return nil 206 | } 207 | 208 | // startMonitors - Loads and attaches the eBPF program in the kernel 209 | func (fsp *FSProbe) startMonitors() error { 210 | // Init monitors 211 | for _, p := range fsp.monitors { 212 | if err := p.Init(fsp); err != nil { 213 | logrus.Errorf("failed to init monitor %s: %v", p.GetName(), err) 214 | return err 215 | } 216 | } 217 | // Start monitors 218 | for _, p := range fsp.monitors { 219 | if err := p.Start(); err != nil { 220 | logrus.Errorf("failed to start monitor %s: %v", p.GetName(), err) 221 | return err 222 | } 223 | } 224 | return nil 225 | } 226 | 227 | // addWatch - Updates the eBPF hashmaps to look for the provided paths 228 | func (fsp *FSProbe) addWatch(paths ...string) error { 229 | // Add paths to the list of watched paths 230 | fsp.runningMutex.Lock() 231 | fsp.paths = append(fsp.paths, paths...) 232 | fsp.runningMutex.Unlock() 233 | if fsp.options.Recursive { 234 | // Watch all directories provided in paths recursively 235 | return fsp.addRecursiveWatch(paths...) 236 | } 237 | // On watch the top level directories 238 | return fsp.addTopLevelWatch(paths...) 239 | } 240 | 241 | // addTopLevelWatch - Adds watches by walking only the top level depth of directories 242 | func (fsp *FSProbe) addTopLevelWatch(paths ...string) error { 243 | for _, p := range paths { 244 | // Check if the path is a directory 245 | pathInfo, err := os.Stat(p) 246 | if err != nil { 247 | return err 248 | } 249 | if pathInfo.IsDir() { 250 | // List the top level of the directory 251 | files, err := ioutil.ReadDir(p) 252 | if err != nil { 253 | return err 254 | } 255 | for _, f := range files { 256 | fullPath := path.Join(p, f.Name()) 257 | statTmp, ok := f.Sys().(*syscall.Stat_t) 258 | if !ok && statTmp == nil { 259 | continue 260 | } 261 | // Add inode in cache 262 | fsp.watchInode(uint32(statTmp.Ino), fullPath) 263 | } 264 | } 265 | // Add the file (or directory itself) to the list of watched files 266 | pathStat, ok := pathInfo.Sys().(*syscall.Stat_t) 267 | if !ok && pathStat == nil { 268 | continue 269 | } 270 | // Add file in cache 271 | fsp.watchInode(uint32(pathStat.Ino), p) 272 | } 273 | return nil 274 | } 275 | 276 | // addRecursiveWatch - Adds watches by walking through all the provided paths recursively 277 | func (fsp *FSProbe) addRecursiveWatch(paths ...string) error { 278 | var err error 279 | for _, path := range paths { 280 | err = filepath.Walk(path, func(walkPath string, fi os.FileInfo, err error) error { 281 | if err != nil { 282 | logrus.Warnf("couldn't add watch for %s: %v", walkPath, err) 283 | return nil 284 | } 285 | if !fi.IsDir() { 286 | return fsp.addTopLevelWatch([]string{walkPath}...) 287 | } 288 | stat, ok := fi.Sys().(*syscall.Stat_t) 289 | if !ok && stat == nil { 290 | return nil 291 | } 292 | // Add inode in cache 293 | fsp.watchInode(uint32(stat.Ino), walkPath) 294 | return nil 295 | }) 296 | if err != nil { 297 | return err 298 | } 299 | } 300 | return nil 301 | } 302 | 303 | // watchInode - Adds an inode the in the resolver cache 304 | func (fsp *FSProbe) watchInode(inode uint32, path string) { 305 | for _, m := range fsp.monitors { 306 | // Add inode filter 307 | if err := m.AddInodeFilter(inode, path); err != nil { 308 | logrus.Warnf("couldn't watch inode %v of path %s: %v", inode, path, err) 309 | continue 310 | } 311 | } 312 | } 313 | 314 | // EditEBPFConstants - Edit the runtime eBPF constants 315 | func (fsp *FSProbe) EditEBPFConstants(spec *ebpf.CollectionSpec) error { 316 | // Edit the constants of all the probes declared in FSProbe 317 | for _, mon := range fsp.monitors { 318 | for _, probes := range mon.Probes { 319 | for _, probe := range probes { 320 | if len(probe.Constants) == 0 { 321 | continue 322 | } 323 | spec, ok := spec.Programs[probe.SectionName] 324 | if !ok { 325 | return fmt.Errorf("couldn't find section %s", probe.SectionName) 326 | } 327 | editor := ebpf.Edit(&spec.Instructions) 328 | // Edit constants 329 | for _, constant := range probe.Constants { 330 | var value uint64 331 | switch constant { 332 | case model.DentryResolutionModeConst: 333 | value = uint64(fsp.options.DentryResolutionMode) 334 | case model.InodeFilteringModeConst: 335 | if fsp.options.PathsFiltering { 336 | value = 1 337 | } 338 | case model.FollowModeConst: 339 | if fsp.options.FollowRenames { 340 | value = 1 341 | } 342 | case model.RecursiveModeConst: 343 | if fsp.options.Recursive { 344 | value = 1 345 | } 346 | default: 347 | return fmt.Errorf("couldn't rewrite symbol %s in program %s: unknown symbol", constant, probe.SectionName) 348 | } 349 | if err := editor.RewriteConstant(constant, value); err != nil { 350 | logrus.Warnf("couldn't rewrite symbol %s in program %s: %v", constant, probe.SectionName, err) 351 | } 352 | } 353 | } 354 | } 355 | } 356 | return nil 357 | } 358 | 359 | // removeUnusedMaps - Removes unused maps in the collectionSpec so that we use less kernel memory 360 | func (fsp *FSProbe) removeUnusedMaps() { 361 | if fsp.collectionSpec == nil { 362 | return 363 | } 364 | toRemove := []string{} 365 | for name := range fsp.collectionSpec.Maps { 366 | used := false 367 | // check if the map is used in all the monitors 368 | for _, m := range fsp.monitors { 369 | rmm, ok := m.ResolutionModeMaps[fsp.options.DentryResolutionMode] 370 | if !ok { 371 | continue 372 | } 373 | for _, eMapName := range rmm { 374 | logrus.Debugln(eMapName) 375 | if name == eMapName { 376 | used = true 377 | break 378 | } 379 | } 380 | if used { 381 | break 382 | } 383 | } 384 | if !used { 385 | toRemove = append(toRemove, name) 386 | } 387 | } 388 | for _, name := range toRemove { 389 | delete(fsp.collectionSpec.Maps, name) 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /pkg/fsprobe/monitor/fs/fs.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package fs 17 | 18 | import ( 19 | "C" 20 | "github.com/Gui774ume/ebpf" 21 | "github.com/Gui774ume/fsprobe/pkg/model" 22 | "github.com/sirupsen/logrus" 23 | ) 24 | 25 | var ( 26 | // Monitor - eBPF FIM event monitor 27 | Monitor = &model.Monitor{ 28 | Name: "FileSystem", 29 | InodeFilterSection: model.InodesFilterMap, 30 | ResolutionModeMaps: map[model.DentryResolutionMode][]string{ 31 | model.DentryResolutionFragments: []string{ 32 | model.PathFragmentsMap, 33 | model.FSEventsMap, 34 | model.DentryCacheMap, 35 | model.DentryCacheBuilderMap, 36 | model.InodesFilterMap, 37 | }, 38 | model.DentryResolutionSingleFragment: []string{ 39 | model.SingleFragmentsMap, 40 | model.CachedInodesMap, 41 | model.FSEventsMap, 42 | model.DentryCacheMap, 43 | model.DentryCacheBuilderMap, 44 | model.PathsBuilderMap, 45 | model.InodesFilterMap, 46 | }, 47 | model.DentryResolutionPerfBuffer: []string{ 48 | model.CachedInodesMap, 49 | model.FSEventsMap, 50 | model.DentryCacheMap, 51 | model.DentryCacheBuilderMap, 52 | model.PathsBuilderMap, 53 | model.InodesFilterMap, 54 | }, 55 | }, 56 | Probes: map[model.EventName][]*model.Probe{ 57 | model.Open: []*model.Probe{ 58 | &model.Probe{ 59 | Name: "open", 60 | SectionName: "kprobe/vfs_open", 61 | Enabled: false, 62 | Type: ebpf.Kprobe, 63 | Constants: []string{ 64 | model.InodeFilteringModeConst, 65 | }, 66 | }, 67 | &model.Probe{ 68 | Name: "open_ret", 69 | SectionName: "kretprobe/vfs_open", 70 | Enabled: false, 71 | Type: ebpf.Kprobe, 72 | Constants: []string{ 73 | model.DentryResolutionModeConst, 74 | }, 75 | }, 76 | }, 77 | model.Mkdir: []*model.Probe{ 78 | &model.Probe{ 79 | Name: "mkdir", 80 | SectionName: "kprobe/vfs_mkdir", 81 | Enabled: false, 82 | Type: ebpf.Kprobe, 83 | Constants: []string{ 84 | model.InodeFilteringModeConst, 85 | }, 86 | }, 87 | &model.Probe{ 88 | Name: "mkdir_ret", 89 | SectionName: "kretprobe/vfs_mkdir", 90 | Enabled: false, 91 | Type: ebpf.Kprobe, 92 | Constants: []string{ 93 | model.DentryResolutionModeConst, 94 | model.RecursiveModeConst, 95 | }, 96 | }, 97 | }, 98 | model.Unlink: []*model.Probe{ 99 | &model.Probe{ 100 | Name: "unlink", 101 | SectionName: "kprobe/vfs_unlink", 102 | Enabled: false, 103 | Type: ebpf.Kprobe, 104 | Constants: []string{ 105 | model.InodeFilteringModeConst, 106 | }, 107 | }, 108 | &model.Probe{ 109 | Name: "unlink_ret", 110 | SectionName: "kretprobe/vfs_unlink", 111 | Enabled: false, 112 | Type: ebpf.Kprobe, 113 | Constants: []string{ 114 | model.DentryResolutionModeConst, 115 | }, 116 | }, 117 | }, 118 | model.Rmdir: []*model.Probe{ 119 | &model.Probe{ 120 | Name: "rmdir", 121 | SectionName: "kprobe/vfs_rmdir", 122 | Enabled: false, 123 | Type: ebpf.Kprobe, 124 | Constants: []string{ 125 | model.InodeFilteringModeConst, 126 | }, 127 | }, 128 | &model.Probe{ 129 | Name: "rmdir_ret", 130 | SectionName: "kretprobe/vfs_rmdir", 131 | Enabled: false, 132 | Type: ebpf.Kprobe, 133 | Constants: []string{ 134 | model.DentryResolutionModeConst, 135 | }, 136 | }, 137 | }, 138 | model.Link: []*model.Probe{ 139 | &model.Probe{ 140 | Name: "link", 141 | SectionName: "kprobe/vfs_link", 142 | Enabled: false, 143 | Type: ebpf.Kprobe, 144 | Constants: []string{ 145 | model.DentryResolutionModeConst, 146 | model.InodeFilteringModeConst, 147 | }, 148 | }, 149 | &model.Probe{ 150 | Name: "link_ret", 151 | SectionName: "kretprobe/vfs_link", 152 | Enabled: false, 153 | Type: ebpf.Kprobe, 154 | Constants: []string{ 155 | model.DentryResolutionModeConst, 156 | }, 157 | }, 158 | }, 159 | model.Rename: []*model.Probe{ 160 | &model.Probe{ 161 | Name: "rename", 162 | SectionName: "kprobe/vfs_rename", 163 | Enabled: false, 164 | Type: ebpf.Kprobe, 165 | Constants: []string{ 166 | model.DentryResolutionModeConst, 167 | model.InodeFilteringModeConst, 168 | }, 169 | }, 170 | &model.Probe{ 171 | Name: "rename_ret", 172 | SectionName: "kretprobe/vfs_rename", 173 | Enabled: false, 174 | Type: ebpf.Kprobe, 175 | Constants: []string{ 176 | model.DentryResolutionModeConst, 177 | model.FollowModeConst, 178 | }, 179 | }, 180 | }, 181 | model.Modify: []*model.Probe{ 182 | &model.Probe{ 183 | Name: "modify", 184 | SectionName: "kprobe/__fsnotify_parent", 185 | Enabled: false, 186 | Type: ebpf.Kprobe, 187 | Constants: []string{ 188 | model.InodeFilteringModeConst, 189 | }, 190 | }, 191 | &model.Probe{ 192 | Name: "modify_ret", 193 | SectionName: "kretprobe/__fsnotify_parent", 194 | Enabled: false, 195 | Type: ebpf.Kprobe, 196 | Constants: []string{ 197 | model.DentryResolutionModeConst, 198 | }, 199 | }, 200 | }, 201 | model.SetAttr: []*model.Probe{ 202 | &model.Probe{ 203 | Name: "setattr", 204 | SectionName: "kprobe/security_inode_setattr", 205 | Enabled: false, 206 | Type: ebpf.Kprobe, 207 | Constants: []string{ 208 | model.InodeFilteringModeConst, 209 | }, 210 | }, 211 | &model.Probe{ 212 | Name: "setattr_ret", 213 | SectionName: "kretprobe/security_inode_setattr", 214 | Enabled: false, 215 | Type: ebpf.Kprobe, 216 | Constants: []string{ 217 | model.DentryResolutionModeConst, 218 | }, 219 | }, 220 | }, 221 | }, 222 | PerfMaps: []*model.PerfMap{ 223 | &model.PerfMap{ 224 | UserSpaceBufferLen: 1000, 225 | PerfOutputMapName: "fs_events", 226 | DataHandler: HandleFSEvent, 227 | LostHandler: LostFSEvent, 228 | }, 229 | }, 230 | } 231 | ) 232 | 233 | // LostFSEvent - Handles a LostEvent 234 | func LostFSEvent(count uint64, mapName string, monitor *model.Monitor) { 235 | // Dispatch event 236 | if monitor.Options.LostChan != nil { 237 | monitor.Options.LostChan <- &model.LostEvt{ 238 | Count: count, 239 | Map: mapName, 240 | } 241 | } 242 | } 243 | 244 | // HandleFSEvent - Handles a file system event 245 | func HandleFSEvent(data []byte, monitor *model.Monitor) { 246 | // Prepare event 247 | event, err := model.ParseFSEvent(data, monitor) 248 | if err != nil { 249 | logrus.Warnf("couldn't parse FSEvent: %v", err) 250 | return 251 | } 252 | 253 | // Take cleanup actions on the cache 254 | switch event.EventType { 255 | case model.Unlink: 256 | switch monitor.Options.DentryResolutionMode { 257 | case model.DentryResolutionSingleFragment: 258 | if err := monitor.DentryResolver.RemoveEntry(event.SrcPathnameKey); err != nil { 259 | logrus.Warnf("couldn't clear cache: %v", err) 260 | } 261 | case model.DentryResolutionPerfBuffer: 262 | if err := monitor.DentryResolver.RemoveEntry(uint32(event.SrcInode)); err != nil { 263 | logrus.Warnf("couldn't clear cache: %v", err) 264 | } 265 | case model.DentryResolutionFragments: 266 | if err := monitor.DentryResolver.RemoveInode(event.SrcMountID, event.SrcInode); err != nil { 267 | logrus.Warnf("couldn't clear cache: %v", err) 268 | } 269 | } 270 | } 271 | 272 | // Dispatch event 273 | if monitor.Options.EventChan != nil { 274 | monitor.Options.EventChan <- event 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /pkg/fsprobe/monitor/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package monitor 17 | 18 | import ( 19 | "github.com/Gui774ume/fsprobe/pkg/fsprobe/monitor/fs" 20 | "github.com/Gui774ume/fsprobe/pkg/model" 21 | ) 22 | 23 | // RegisterMonitors - Register monitors 24 | func RegisterMonitors() []*model.Monitor { 25 | return []*model.Monitor{ 26 | fs.Monitor, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/inotify/inotify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux 6 | 7 | package inotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "io" 13 | "os" 14 | "path/filepath" 15 | "strings" 16 | "sync" 17 | "unsafe" 18 | 19 | "golang.org/x/sys/unix" 20 | ) 21 | 22 | // Watcher watches a set of files, delivering events to a channel. 23 | type Watcher struct { 24 | Events chan Event 25 | Errors chan error 26 | mu sync.Mutex // Map access 27 | fd int 28 | poller *fdPoller 29 | watches map[string]*watch // Map of inotify watches (key: path) 30 | paths map[int]string // Map of watched paths (key: watch descriptor) 31 | done chan struct{} // Channel for sending a "quit message" to the reader goroutine 32 | doneResp chan struct{} // Channel to respond to Close 33 | } 34 | 35 | // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. 36 | func NewWatcher() (*Watcher, error) { 37 | // Create inotify fd 38 | fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) 39 | if fd == -1 { 40 | return nil, errno 41 | } 42 | // Create epoll 43 | poller, err := newFdPoller(fd) 44 | if err != nil { 45 | unix.Close(fd) 46 | return nil, err 47 | } 48 | w := &Watcher{ 49 | fd: fd, 50 | poller: poller, 51 | watches: make(map[string]*watch), 52 | paths: make(map[int]string), 53 | Events: make(chan Event), 54 | Errors: make(chan error), 55 | done: make(chan struct{}), 56 | doneResp: make(chan struct{}), 57 | } 58 | 59 | go w.readEvents() 60 | return w, nil 61 | } 62 | 63 | func (w *Watcher) isClosed() bool { 64 | select { 65 | case <-w.done: 66 | return true 67 | default: 68 | return false 69 | } 70 | } 71 | 72 | // Close removes all watches and closes the events channel. 73 | func (w *Watcher) Close() error { 74 | if w.isClosed() { 75 | return nil 76 | } 77 | 78 | // Send 'close' signal to goroutine, and set the Watcher to closed. 79 | close(w.done) 80 | 81 | // Wake up goroutine 82 | w.poller.wake() 83 | 84 | // Wait for goroutine to close 85 | <-w.doneResp 86 | 87 | return nil 88 | } 89 | 90 | // Add starts watching the named file or directory (non-recursively). 91 | func (w *Watcher) Add(name string) error { 92 | name = filepath.Clean(name) 93 | if w.isClosed() { 94 | return errors.New("inotify instance already closed") 95 | } 96 | 97 | const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | 98 | unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | 99 | unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF | unix.IN_OPEN 100 | 101 | var flags uint32 = agnosticEvents 102 | 103 | w.mu.Lock() 104 | defer w.mu.Unlock() 105 | watchEntry := w.watches[name] 106 | if watchEntry != nil { 107 | flags |= watchEntry.flags | unix.IN_MASK_ADD 108 | } 109 | wd, errno := unix.InotifyAddWatch(w.fd, name, flags) 110 | if wd == -1 { 111 | return errno 112 | } 113 | 114 | if watchEntry == nil { 115 | w.watches[name] = &watch{wd: uint32(wd), flags: flags} 116 | w.paths[wd] = name 117 | } else { 118 | watchEntry.wd = uint32(wd) 119 | watchEntry.flags = flags 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // Remove stops watching the named file or directory (non-recursively). 126 | func (w *Watcher) Remove(name string) error { 127 | name = filepath.Clean(name) 128 | 129 | // Fetch the watch. 130 | w.mu.Lock() 131 | defer w.mu.Unlock() 132 | watch, ok := w.watches[name] 133 | 134 | // Remove it from inotify. 135 | if !ok { 136 | return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) 137 | } 138 | 139 | // We successfully removed the watch if InotifyRmWatch doesn't return an 140 | // error, we need to clean up our internal state to ensure it matches 141 | // inotify's kernel state. 142 | delete(w.paths, int(watch.wd)) 143 | delete(w.watches, name) 144 | 145 | // inotify_rm_watch will return EINVAL if the file has been deleted; 146 | // the inotify will already have been removed. 147 | // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously 148 | // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE 149 | // so that EINVAL means that the wd is being rm_watch()ed or its file removed 150 | // by another thread and we have not received IN_IGNORE event. 151 | success, errno := unix.InotifyRmWatch(w.fd, watch.wd) 152 | if success == -1 { 153 | // TODO: Perhaps it's not helpful to return an error here in every case. 154 | // the only two possible errors are: 155 | // EBADF, which happens when w.fd is not a valid file descriptor of any kind. 156 | // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. 157 | // Watch descriptors are invalidated when they are removed explicitly or implicitly; 158 | // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. 159 | return errno 160 | } 161 | 162 | return nil 163 | } 164 | 165 | type watch struct { 166 | wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) 167 | flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) 168 | } 169 | 170 | // readEvents reads from the inotify file descriptor, converts the 171 | // received events into Event objects and sends them via the Events channel 172 | func (w *Watcher) readEvents() { 173 | var ( 174 | buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events 175 | n int // Number of bytes read with read() 176 | errno error // Syscall errno 177 | ok bool // For poller.wait 178 | ) 179 | 180 | defer close(w.doneResp) 181 | defer close(w.Errors) 182 | defer close(w.Events) 183 | defer unix.Close(w.fd) 184 | defer w.poller.close() 185 | 186 | for { 187 | // See if we have been closed. 188 | if w.isClosed() { 189 | return 190 | } 191 | 192 | ok, errno = w.poller.wait() 193 | if errno != nil { 194 | select { 195 | case w.Errors <- errno: 196 | case <-w.done: 197 | return 198 | } 199 | continue 200 | } 201 | 202 | if !ok { 203 | continue 204 | } 205 | 206 | n, errno = unix.Read(w.fd, buf[:]) 207 | // If a signal interrupted execution, see if we've been asked to close, and try again. 208 | // http://man7.org/linux/man-pages/man7/signal.7.html : 209 | // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" 210 | if errno == unix.EINTR { 211 | continue 212 | } 213 | 214 | // unix.Read might have been woken up by Close. If so, we're done. 215 | if w.isClosed() { 216 | return 217 | } 218 | 219 | if n < unix.SizeofInotifyEvent { 220 | var err error 221 | if n == 0 { 222 | // If EOF is received. This should really never happen. 223 | err = io.EOF 224 | } else if n < 0 { 225 | // If an error occurred while reading. 226 | err = errno 227 | } else { 228 | // Read was too short. 229 | err = errors.New("notify: short read in readEvents()") 230 | } 231 | select { 232 | case w.Errors <- err: 233 | case <-w.done: 234 | return 235 | } 236 | continue 237 | } 238 | 239 | var offset uint32 240 | // We don't know how many events we just read into the buffer 241 | // While the offset points to at least one whole event... 242 | for offset <= uint32(n-unix.SizeofInotifyEvent) { 243 | // Point "raw" to the event in the buffer 244 | raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) 245 | 246 | mask := uint32(raw.Mask) 247 | nameLen := uint32(raw.Len) 248 | 249 | if mask&unix.IN_Q_OVERFLOW != 0 { 250 | select { 251 | case w.Errors <- ErrEventOverflow: 252 | case <-w.done: 253 | return 254 | } 255 | } 256 | 257 | // If the event happened to the watched directory or the watched file, the kernel 258 | // doesn't append the filename to the event, but we would like to always fill the 259 | // the "Name" field with a valid filename. We retrieve the path of the watch from 260 | // the "paths" map. 261 | w.mu.Lock() 262 | name, ok := w.paths[int(raw.Wd)] 263 | // IN_DELETE_SELF occurs when the file/directory being watched is removed. 264 | // This is a sign to clean up the maps, otherwise we are no longer in sync 265 | // with the inotify kernel state which has already deleted the watch 266 | // automatically. 267 | if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { 268 | delete(w.paths, int(raw.Wd)) 269 | delete(w.watches, name) 270 | } 271 | w.mu.Unlock() 272 | 273 | if nameLen > 0 { 274 | // Point "bytes" at the first byte of the filename 275 | bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent])) 276 | // The filename is padded with NULL bytes. TrimRight() gets rid of those. 277 | name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") 278 | } 279 | 280 | event := newEvent(name, mask) 281 | 282 | // Send the events that are not ignored on the events channel 283 | if !event.ignoreLinux(mask) { 284 | select { 285 | case w.Events <- event: 286 | case <-w.done: 287 | return 288 | } 289 | } 290 | 291 | // Move to the next event in the buffer 292 | offset += unix.SizeofInotifyEvent + nameLen 293 | } 294 | } 295 | } 296 | 297 | // Certain types of events can be "ignored" and not sent over the Events 298 | // channel. Such as events marked ignore by the kernel, or MODIFY events 299 | // against files that do not exist. 300 | func (e *Event) ignoreLinux(mask uint32) bool { 301 | // Ignore anything the inotify API says to ignore 302 | if mask&unix.IN_IGNORED == unix.IN_IGNORED { 303 | return true 304 | } 305 | 306 | // If the event is not a DELETE or RENAME, the file must exist. 307 | // Otherwise the event is ignored. 308 | // *Note*: this was put in place because it was seen that a MODIFY 309 | // event was sent after the DELETE. This ignores that MODIFY and 310 | // assumes a DELETE will come or has come if the file doesn't exist. 311 | if !(e.Op&Remove == Remove || e.Op&Rename == Rename) { 312 | _, statErr := os.Lstat(e.Name) 313 | return os.IsNotExist(statErr) 314 | } 315 | return false 316 | } 317 | 318 | // newEvent returns an platform-independent Event based on an inotify mask. 319 | func newEvent(name string, mask uint32) Event { 320 | e := Event{Name: name} 321 | if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { 322 | e.Op |= Create 323 | } 324 | if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { 325 | e.Op |= Remove 326 | } 327 | if mask&unix.IN_MODIFY == unix.IN_MODIFY { 328 | e.Op |= Write 329 | } 330 | if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { 331 | e.Op |= Rename 332 | } 333 | if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { 334 | e.Op |= Chmod 335 | } 336 | if mask&unix.IN_OPEN == unix.IN_OPEN { 337 | e.Op |= Open 338 | } 339 | return e 340 | } 341 | -------------------------------------------------------------------------------- /pkg/inotify/inotify_poller.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux 6 | 7 | package inotify 8 | 9 | import ( 10 | "errors" 11 | 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | type fdPoller struct { 16 | fd int // File descriptor (as returned by the inotify_init() syscall) 17 | epfd int // Epoll file descriptor 18 | pipe [2]int // Pipe for waking up 19 | } 20 | 21 | func emptyPoller(fd int) *fdPoller { 22 | poller := new(fdPoller) 23 | poller.fd = fd 24 | poller.epfd = -1 25 | poller.pipe[0] = -1 26 | poller.pipe[1] = -1 27 | return poller 28 | } 29 | 30 | // Create a new inotify poller. 31 | // This creates an inotify handler, and an epoll handler. 32 | func newFdPoller(fd int) (*fdPoller, error) { 33 | var errno error 34 | poller := emptyPoller(fd) 35 | defer func() { 36 | if errno != nil { 37 | poller.close() 38 | } 39 | }() 40 | poller.fd = fd 41 | 42 | // Create epoll fd 43 | poller.epfd, errno = unix.EpollCreate1(0) 44 | if poller.epfd == -1 { 45 | return nil, errno 46 | } 47 | // Create pipe; pipe[0] is the read end, pipe[1] the write end. 48 | errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK) 49 | if errno != nil { 50 | return nil, errno 51 | } 52 | 53 | // Register inotify fd with epoll 54 | event := unix.EpollEvent{ 55 | Fd: int32(poller.fd), 56 | Events: unix.EPOLLIN, 57 | } 58 | errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event) 59 | if errno != nil { 60 | return nil, errno 61 | } 62 | 63 | // Register pipe fd with epoll 64 | event = unix.EpollEvent{ 65 | Fd: int32(poller.pipe[0]), 66 | Events: unix.EPOLLIN, 67 | } 68 | errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event) 69 | if errno != nil { 70 | return nil, errno 71 | } 72 | 73 | return poller, nil 74 | } 75 | 76 | // Wait using epoll. 77 | // Returns true if something is ready to be read, 78 | // false if there is not. 79 | func (poller *fdPoller) wait() (bool, error) { 80 | // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. 81 | // I don't know whether epoll_wait returns the number of events returned, 82 | // or the total number of events ready. 83 | // I decided to catch both by making the buffer one larger than the maximum. 84 | events := make([]unix.EpollEvent, 7) 85 | for { 86 | n, errno := unix.EpollWait(poller.epfd, events, -1) 87 | if n == -1 { 88 | if errno == unix.EINTR { 89 | continue 90 | } 91 | return false, errno 92 | } 93 | if n == 0 { 94 | // If there are no events, try again. 95 | continue 96 | } 97 | if n > 6 { 98 | // This should never happen. More events were returned than should be possible. 99 | return false, errors.New("epoll_wait returned more events than I know what to do with") 100 | } 101 | ready := events[:n] 102 | epollhup := false 103 | epollerr := false 104 | epollin := false 105 | for _, event := range ready { 106 | if event.Fd == int32(poller.fd) { 107 | if event.Events&unix.EPOLLHUP != 0 { 108 | // This should not happen, but if it does, treat it as a wakeup. 109 | epollhup = true 110 | } 111 | if event.Events&unix.EPOLLERR != 0 { 112 | // If an error is waiting on the file descriptor, we should pretend 113 | // something is ready to read, and let unix.Read pick up the error. 114 | epollerr = true 115 | } 116 | if event.Events&unix.EPOLLIN != 0 { 117 | // There is data to read. 118 | epollin = true 119 | } 120 | } 121 | if event.Fd == int32(poller.pipe[0]) { 122 | if event.Events&unix.EPOLLHUP != 0 { 123 | // Write pipe descriptor was closed, by us. This means we're closing down the 124 | // watcher, and we should wake up. 125 | } 126 | if event.Events&unix.EPOLLERR != 0 { 127 | // If an error is waiting on the pipe file descriptor. 128 | // This is an absolute mystery, and should never ever happen. 129 | return false, errors.New("Error on the pipe descriptor.") 130 | } 131 | if event.Events&unix.EPOLLIN != 0 { 132 | // This is a regular wakeup, so we have to clear the buffer. 133 | err := poller.clearWake() 134 | if err != nil { 135 | return false, err 136 | } 137 | } 138 | } 139 | } 140 | 141 | if epollhup || epollerr || epollin { 142 | return true, nil 143 | } 144 | return false, nil 145 | } 146 | } 147 | 148 | // Close the write end of the poller. 149 | func (poller *fdPoller) wake() error { 150 | buf := make([]byte, 1) 151 | n, errno := unix.Write(poller.pipe[1], buf) 152 | if n == -1 { 153 | if errno == unix.EAGAIN { 154 | // Buffer is full, poller will wake. 155 | return nil 156 | } 157 | return errno 158 | } 159 | return nil 160 | } 161 | 162 | func (poller *fdPoller) clearWake() error { 163 | // You have to be woken up a LOT in order to get to 100! 164 | buf := make([]byte, 100) 165 | n, errno := unix.Read(poller.pipe[0], buf) 166 | if n == -1 { 167 | if errno == unix.EAGAIN { 168 | // Buffer is empty, someone else cleared our wake. 169 | return nil 170 | } 171 | return errno 172 | } 173 | return nil 174 | } 175 | 176 | // Close all poller file descriptors, but not the one passed to it. 177 | func (poller *fdPoller) close() { 178 | if poller.pipe[1] != -1 { 179 | unix.Close(poller.pipe[1]) 180 | } 181 | if poller.pipe[0] != -1 { 182 | unix.Close(poller.pipe[0]) 183 | } 184 | if poller.epfd != -1 { 185 | unix.Close(poller.epfd) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /pkg/inotify/model.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !plan9 6 | 7 | // Package inotify provides a platform-independent interface for file system notifications. 8 | package inotify 9 | 10 | import ( 11 | "bytes" 12 | "errors" 13 | "fmt" 14 | ) 15 | 16 | // Event represents a single file system notification. 17 | type Event struct { 18 | Name string // Relative path to the file or directory. 19 | Op Op // File operation that triggered the event. 20 | } 21 | 22 | // Op describes a set of file operations. 23 | type Op uint32 24 | 25 | // These are the generalized file operations that can trigger a notification. 26 | const ( 27 | Create Op = 1 << iota 28 | Write 29 | Remove 30 | Rename 31 | Chmod 32 | Open 33 | ) 34 | 35 | func (op Op) String() string { 36 | // Use a buffer for efficient string concatenation 37 | var buffer bytes.Buffer 38 | 39 | if op&Create == Create { 40 | buffer.WriteString("|CREATE") 41 | } 42 | if op&Remove == Remove { 43 | buffer.WriteString("|REMOVE") 44 | } 45 | if op&Write == Write { 46 | buffer.WriteString("|WRITE") 47 | } 48 | if op&Rename == Rename { 49 | buffer.WriteString("|RENAME") 50 | } 51 | if op&Chmod == Chmod { 52 | buffer.WriteString("|CHMOD") 53 | } 54 | if op&Open == Open { 55 | buffer.WriteString("|OPEN") 56 | } 57 | if buffer.Len() == 0 { 58 | return "" 59 | } 60 | return buffer.String()[1:] // Strip leading pipe 61 | } 62 | 63 | // String returns a string representation of the event in the form 64 | // "file: REMOVE|WRITE|..." 65 | func (e Event) String() string { 66 | return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) 67 | } 68 | 69 | // Common errors that can be reported by a watcher 70 | var ErrEventOverflow = errors.New("inotify queue overflow") 71 | -------------------------------------------------------------------------------- /pkg/inotify/recursive_inotify.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Package recursive_inotify implements recursive folder monitoring by wrapping the excellent inotify library 17 | package inotify 18 | 19 | import ( 20 | "errors" 21 | "os" 22 | "path/filepath" 23 | ) 24 | 25 | // RWatcher wraps inotify.Watcher. When inotify adds recursive watches, you should be able to switch your code to use inotify.Watcher 26 | type RWatcher struct { 27 | Events chan Event 28 | Errors chan error 29 | 30 | done chan struct{} 31 | inotify *Watcher 32 | isClosed bool 33 | } 34 | 35 | // NewRWatcher establishes a new watcher with the underlying OS and begins waiting for events. 36 | func NewRWatcher() (*RWatcher, error) { 37 | fsWatch, err := NewWatcher() 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | m := &RWatcher{} 43 | m.inotify = fsWatch 44 | m.Events = make(chan Event) 45 | m.Errors = make(chan error) 46 | m.done = make(chan struct{}) 47 | 48 | go m.start() 49 | 50 | return m, nil 51 | } 52 | 53 | // Add starts watching the named file or directory (non-recursively). 54 | func (m *RWatcher) Add(name string) error { 55 | if m.isClosed { 56 | return errors.New("recursive_inotify instance already closed") 57 | } 58 | return m.inotify.Add(name) 59 | } 60 | 61 | // AddRecursive starts watching the named directory and all sub-directories. 62 | func (m *RWatcher) AddRecursive(name string) error { 63 | if m.isClosed { 64 | return errors.New("recursive_inotify instance already closed") 65 | } 66 | if err := m.watchRecursive(name, false); err != nil { 67 | return err 68 | } 69 | return nil 70 | } 71 | 72 | // Remove stops watching the the named file or directory (non-recursively). 73 | func (m *RWatcher) Remove(name string) error { 74 | return m.inotify.Remove(name) 75 | } 76 | 77 | // RemoveRecursive stops watching the named directory and all sub-directories. 78 | func (m *RWatcher) RemoveRecursive(name string) error { 79 | if err := m.watchRecursive(name, true); err != nil { 80 | return err 81 | } 82 | return nil 83 | } 84 | 85 | // Close removes all watches and closes the events channel. 86 | func (m *RWatcher) Close() error { 87 | if m.isClosed { 88 | return nil 89 | } 90 | close(m.done) 91 | m.isClosed = true 92 | return nil 93 | } 94 | 95 | func (m *RWatcher) start() { 96 | for { 97 | select { 98 | 99 | case e := <-m.inotify.Events: 100 | s, err := os.Stat(e.Name) 101 | if err == nil && s != nil && s.IsDir() { 102 | if e.Op&Create != 0 { 103 | m.watchRecursive(e.Name, false) 104 | } 105 | } 106 | //Can't stat a deleted directory, so just pretend that it's always a directory and 107 | //try to remove from the watch list... we really have no clue if it's a directory or not... 108 | if e.Op&Remove != 0 { 109 | m.inotify.Remove(e.Name) 110 | } 111 | m.Events <- e 112 | 113 | case e := <-m.inotify.Errors: 114 | m.Errors <- e 115 | 116 | case <-m.done: 117 | m.inotify.Close() 118 | close(m.Events) 119 | close(m.Errors) 120 | return 121 | } 122 | } 123 | } 124 | 125 | // watchRecursive adds all directories under the given one to the watch list. 126 | // this is probably a very racey process. What if a file is added to a folder before we get the watch added? 127 | func (m *RWatcher) watchRecursive(path string, unWatch bool) error { 128 | err := filepath.Walk(path, func(walkPath string, fi os.FileInfo, err error) error { 129 | if err != nil { 130 | return err 131 | } 132 | if fi.IsDir() { 133 | if unWatch { 134 | if err = m.inotify.Remove(walkPath); err != nil { 135 | return err 136 | } 137 | } else { 138 | if err = m.inotify.Add(walkPath); err != nil { 139 | return err 140 | } 141 | } 142 | } 143 | return nil 144 | }) 145 | return err 146 | } 147 | -------------------------------------------------------------------------------- /pkg/model/events.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | import ( 19 | "C" 20 | "bytes" 21 | "fmt" 22 | "strings" 23 | "time" 24 | 25 | "github.com/pkg/errors" 26 | 27 | "github.com/Gui774ume/fsprobe/pkg/utils" 28 | ) 29 | 30 | type EventName string 31 | 32 | var ( 33 | // Open - Open event 34 | Open EventName = "open" 35 | // Mkdir - Mkdir event 36 | Mkdir EventName = "mkdir" 37 | // Link - Soft link event 38 | Link EventName = "link" 39 | // Rename - Rename event 40 | Rename EventName = "rename" 41 | // SetAttr - Attribute update event 42 | SetAttr EventName = "setattr" 43 | // Unlink - File deletion event 44 | Unlink EventName = "unlink" 45 | // Rmdir - Directory deletion event 46 | Rmdir EventName = "rmdir" 47 | // Modify - File modification event 48 | Modify EventName = "modify" 49 | // Unknown - Unknown file event 50 | Unknown EventName = "unknown" 51 | ) 52 | 53 | // GetEventType - Returns the event type 54 | func GetEventType(evtType uint32) EventName { 55 | switch evtType { 56 | case 0: 57 | return Open 58 | case 1: 59 | return Mkdir 60 | case 2: 61 | return Link 62 | case 3: 63 | return Rename 64 | case 4: 65 | return Unlink 66 | case 5: 67 | return Rmdir 68 | case 6: 69 | return Modify 70 | case 7: 71 | return SetAttr 72 | default: 73 | return Unknown 74 | } 75 | } 76 | 77 | // ParseFSEvent - Parses a new FSEvent using the data provided by the kernel 78 | func ParseFSEvent(data []byte, monitor *Monitor) (*FSEvent, error) { 79 | evt := &FSEvent{} 80 | read, err := evt.UnmarshalBinary(data, monitor.FSProbe.GetBootTime()) 81 | if err != nil { 82 | return nil, err 83 | } 84 | // Resolve paths 85 | if err := resolvePaths(data, evt, monitor, read); err != nil { 86 | return nil, err 87 | } 88 | return evt, nil 89 | } 90 | 91 | // resolvePaths - Resolves the paths of the event according to the configured method 92 | func resolvePaths(data []byte, evt *FSEvent, monitor *Monitor, read int) error { 93 | var err error 94 | switch monitor.Options.DentryResolutionMode { 95 | case DentryResolutionFragments: 96 | inode := evt.SrcInode 97 | if evt.SrcPathnameKey != 0 { 98 | inode = uint64(evt.SrcPathnameKey) 99 | } 100 | evt.SrcFilename, err = monitor.DentryResolver.ResolveInode(evt.SrcMountID, inode) 101 | if err != nil { 102 | return errors.Wrap(err, "failed to resolve src dentry path") 103 | } 104 | switch evt.EventType { 105 | case Link, Rename: 106 | evt.TargetFilename, err = monitor.DentryResolver.ResolveInode(evt.TargetMountID, evt.TargetInode) 107 | if err != nil { 108 | return errors.Wrap(err, "failed to resolve target dentry path") 109 | } 110 | } 111 | if evt.EventType == Link { 112 | // Remove cache entry for link events 113 | _ = monitor.DentryResolver.RemoveInode(evt.TargetMountID, evt.TargetInode) 114 | _ = monitor.DentryResolver.RemoveInode(evt.TargetMountID, evt.TargetInode) 115 | } 116 | break 117 | case DentryResolutionSingleFragment: 118 | evt.SrcFilename, err = monitor.DentryResolver.ResolveKey(evt.SrcPathnameKey, evt.SrcPathnameLength) 119 | if err != nil { 120 | return errors.Wrap(err, "failed to resolve src dentry path") 121 | } 122 | switch evt.EventType { 123 | case Link, Rename: 124 | evt.TargetFilename, err = monitor.DentryResolver.ResolveKey(evt.TargetPathnameKey, evt.TargetPathnameLength) 125 | if err != nil { 126 | return errors.Wrap(err, "failed to resolve target dentry path") 127 | } 128 | } 129 | if evt.EventType == Link { 130 | // Remove cache entry for link events 131 | _ = monitor.DentryResolver.RemoveEntry(evt.SrcPathnameKey) 132 | _ = monitor.DentryResolver.RemoveEntry(evt.TargetPathnameKey) 133 | } 134 | break 135 | case DentryResolutionPerfBuffer: 136 | // Decode path from perf buffer when needed 137 | srcEnd := read 138 | if evt.SrcPathnameLength > 0 { 139 | srcEnd += int(evt.SrcPathnameLength) 140 | evt.SrcFilename = decodePath(data[read:srcEnd]) 141 | } 142 | // Resolve end of path from cache when needed 143 | if evt.SrcPathnameKey > 0 { 144 | prefix, err := monitor.DentryResolver.ResolveKey(evt.SrcPathnameKey, 0) 145 | if err != nil { 146 | return errors.Wrap(err, "failed to resolve src dentry path") 147 | } 148 | evt.SrcFilename = prefix + evt.SrcFilename 149 | } 150 | // Cache resolved path when needed 151 | if evt.SrcPathnameLength > 0 && evt.EventType != Link { 152 | err = monitor.DentryResolver.AddCacheEntry(uint32(evt.SrcInode), evt.SrcFilename) 153 | if err != nil { 154 | return errors.Wrap(err, "failed to add src cached inode") 155 | } 156 | } 157 | switch evt.EventType { 158 | case Link, Rename: 159 | // Decode path from perf buffer when needed 160 | if evt.TargetPathnameLength > 0 { 161 | targetEnd := srcEnd + int(evt.TargetPathnameLength) 162 | evt.TargetFilename = decodePath(data[srcEnd:targetEnd]) 163 | } 164 | // Resolve end of path from cache when needed 165 | if evt.TargetPathnameKey > 0 { 166 | prefix, err := monitor.DentryResolver.ResolveKey(evt.TargetPathnameKey, 0) 167 | if err != nil { 168 | return errors.Wrap(err, "failed to resolve target dentry path") 169 | } 170 | evt.TargetFilename = prefix + evt.TargetFilename 171 | } 172 | // Cache resolved path when needed 173 | if evt.TargetPathnameLength > 0 && evt.EventType != Link { 174 | err = monitor.DentryResolver.AddCacheEntry(uint32(evt.TargetInode), evt.TargetFilename) 175 | if err != nil { 176 | return errors.Wrap(err, "failed to add target cached inode") 177 | } 178 | } 179 | } 180 | break 181 | } 182 | return nil 183 | } 184 | 185 | // decodePath - Decode the raw path provided by the kernel 186 | func decodePath(raw []byte) string { 187 | fragments := []string{} 188 | var fragment, path string 189 | // Isolate fragments 190 | for _, b := range raw { 191 | if b == 0 { 192 | // End of fragment, append to the end of the list of fragments 193 | if len(fragment) > 0 { 194 | fragments = append(fragments, fragment) 195 | } else { 196 | // stop resolution there, the rest of the buffer could be leftover from another path 197 | break 198 | } 199 | fragment = "" 200 | } else { 201 | fragment += string(b) 202 | } 203 | } 204 | // Check last fragment 205 | lastFrag := len(fragments) - 1 206 | if lastFrag < 0 { 207 | return "" 208 | } 209 | if fragments[lastFrag] == "/" { 210 | fragments = fragments[:lastFrag] 211 | lastFrag-- 212 | } 213 | // Rebuild the entire path 214 | path = "/" 215 | for i := lastFrag; i >= 0; i-- { 216 | path += fragments[i] + "/" 217 | } 218 | return path[:len(path)-1] 219 | } 220 | 221 | // FSEvent - Raw event definition 222 | type FSEvent struct { 223 | Timestamp time.Time `json:"-"` 224 | Pid uint32 `json:"pid"` 225 | Tid uint32 `json:"tid"` 226 | UID uint32 `json:"uid"` 227 | GID uint32 `json:"gid"` 228 | Comm string `json:"comm"` 229 | Flags uint32 `json:"flags,omitempty"` 230 | Mode uint32 `json:"mode,omitempty"` 231 | SrcInode uint64 `json:"src_inode,omitempty"` 232 | SrcPathnameLength uint32 `json:"-"` 233 | SrcPathnameKey uint32 `json:"-"` 234 | SrcFilename string `json:"src_filename,omitempty"` 235 | SrcMountID uint32 `json:"src_mount_id,omitempty"` 236 | TargetInode uint64 `json:"target_inode,omitempty"` 237 | TargetPathnameLength uint32 `json:"-"` 238 | TargetPathnameKey uint32 `json:"-"` 239 | TargetFilename string `json:"target_filename,omitempty"` 240 | TargetMountID uint32 `json:"target_mount_id,omitempty"` 241 | Retval int32 `json:"retval"` 242 | EventType EventName `json:"event_type"` 243 | } 244 | 245 | func (e *FSEvent) UnmarshalBinary(data []byte, bootTime time.Time) (int, error) { 246 | if len(data) < 96 { 247 | return 0, errors.Errorf("not enough data: %d", len(data)) 248 | } 249 | // Process context data 250 | e.Timestamp = bootTime.Add(time.Duration(utils.ByteOrder.Uint64(data[0:8])) * time.Nanosecond) 251 | e.Pid = utils.ByteOrder.Uint32(data[8:12]) 252 | e.Tid = utils.ByteOrder.Uint32(data[12:16]) 253 | e.UID = utils.ByteOrder.Uint32(data[16:20]) 254 | e.GID = utils.ByteOrder.Uint32(data[20:24]) 255 | e.Comm = string(bytes.Trim(data[24:40], "\x00")) 256 | // File system event data 257 | e.Flags = utils.ByteOrder.Uint32(data[40:44]) 258 | e.Mode = utils.ByteOrder.Uint32(data[44:48]) 259 | e.SrcPathnameKey = utils.ByteOrder.Uint32(data[48:52]) 260 | e.TargetPathnameKey = utils.ByteOrder.Uint32(data[52:56]) 261 | e.SrcInode = utils.ByteOrder.Uint64(data[56:64]) 262 | e.SrcPathnameLength = utils.ByteOrder.Uint32(data[64:68]) 263 | e.SrcMountID = utils.ByteOrder.Uint32(data[68:72]) 264 | e.TargetInode = utils.ByteOrder.Uint64(data[72:80]) 265 | e.TargetPathnameLength = utils.ByteOrder.Uint32(data[80:84]) 266 | e.TargetMountID = utils.ByteOrder.Uint32(data[84:88]) 267 | e.Retval = int32(utils.ByteOrder.Uint32(data[88:92])) 268 | e.EventType = GetEventType(utils.ByteOrder.Uint32(data[92:96])) 269 | return 96, nil 270 | } 271 | 272 | // PrintFilenames - Returns a string representation of the filenames of the event 273 | func (fs *FSEvent) PrintFilenames() string { 274 | if fs.TargetFilename != "" { 275 | return fmt.Sprintf("%s -> %s", fs.SrcFilename, fs.TargetFilename) 276 | } 277 | return fs.SrcFilename 278 | } 279 | 280 | // PrintMode - Returns a string representation of the mode of the event 281 | func (fs *FSEvent) PrintMode() string { 282 | switch fs.EventType { 283 | case Open, SetAttr: 284 | return fmt.Sprintf("%o", fs.Mode) 285 | default: 286 | return fmt.Sprintf("%v", fs.Mode) 287 | } 288 | } 289 | 290 | // PrintFlags - Returns a string representation of the flags of the event 291 | func (fs *FSEvent) PrintFlags() string { 292 | switch fs.EventType { 293 | case Open: 294 | return strings.Join(OpenFlagsToStrings(fs.Flags), ",") 295 | case SetAttr: 296 | return strings.Join(SetAttrFlagsToString(fs.Flags), ",") 297 | default: 298 | return fmt.Sprintf("%v", fs.Flags) 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /pkg/model/fsprobe.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | import ( 19 | "github.com/Gui774ume/ebpf" 20 | "sync" 21 | "time" 22 | ) 23 | 24 | type FSProbe interface { 25 | GetWaitGroup() *sync.WaitGroup 26 | GetOptions() *FSProbeOptions 27 | GetCollection() *ebpf.Collection 28 | GetBootTime() time.Time 29 | } 30 | -------------------------------------------------------------------------------- /pkg/model/maps.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | const ( 19 | // FragmentsMap - This map holds the cache of resolved dentries for the path fragments method 20 | PathFragmentsMap = "path_fragments" 21 | // PathFragmentsSize - Size of the fragments used by the path fragments method 22 | PathFragmentsSize = 256 23 | // SingleFragmentSection - This map holds the cache of resolved dentries for the single fragment method 24 | SingleFragmentsMap = "single_fragments" 25 | // SingleFragmentSize - Size of the single fragment used by the single fragment method 26 | SingleFragmentSize = 4351 27 | // CachedInodesMap - This map holds the list of cached inodes so that the eBPF programs know we don't need 28 | // resolve them anymore. This is used by both the perf buffer method and the single fragment method. 29 | CachedInodesMap = "cached_inodes" 30 | // PerfBufferCachedInodesSize - Max number of cached inodes 31 | PerfBufferCachedInodesSize = 120000 32 | // FSEventsMap - Perf event buffer map used to retrieve events in userspace 33 | FSEventsMap = "fs_events" 34 | // DentryCacheMap - LRU Hashmap used to cache dentry data between kprobes 35 | DentryCacheMap = "dentry_cache" 36 | // DentryCacheBuilderMap - Array map used to reduce the amount of data on the stack 37 | DentryCacheBuilderMap = "dentry_cache_builder" 38 | // PathsBuilderMap - Array map used by the perf buffer method and the single fragment method to build paths 39 | PathsBuilderMap = "paths_builder" 40 | // InodesFilterMap - This map is used to push inode filters in kernel space. 41 | InodesFilterMap = "inodes_filter" 42 | ) 43 | -------------------------------------------------------------------------------- /pkg/model/monitor.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | import ( 19 | "fmt" 20 | "github.com/Gui774ume/fsprobe/pkg/utils" 21 | "sync" 22 | 23 | "github.com/Gui774ume/ebpf" 24 | "github.com/sirupsen/logrus" 25 | ) 26 | 27 | // Monitor - Base monitor 28 | type Monitor struct { 29 | wg *sync.WaitGroup 30 | collection *ebpf.Collection 31 | ResolutionModeMaps map[DentryResolutionMode][]string 32 | DentryResolver DentryResolver 33 | FSProbe FSProbe 34 | InodeFilterSection string 35 | Name string 36 | Options *FSProbeOptions 37 | Probes map[EventName][]*Probe 38 | PerfMaps []*PerfMap 39 | } 40 | 41 | // Configure - Configures the probes using the provided options 42 | func (m *Monitor) Configure() { 43 | if len(m.Options.Events) == 0 { 44 | // Activate everything but the modification probe 45 | for name, probes := range m.Probes { 46 | if name == Modify { 47 | continue 48 | } 49 | for _, p := range probes { 50 | p.Enabled = true 51 | } 52 | } 53 | } else { 54 | // Activate the requested events 55 | for _, name := range m.Options.Events { 56 | probes, ok := m.Probes[name] 57 | if !ok { 58 | continue 59 | } 60 | for _, p := range probes { 61 | p.Enabled = true 62 | } 63 | } 64 | } 65 | // Setup dentry resolver 66 | m.DentryResolver, _ = NewDentryResolver(m) 67 | } 68 | 69 | // GetName - Returns the name of the monitor 70 | func (m *Monitor) GetName() string { 71 | return m.Name 72 | } 73 | 74 | // GetMap - Returns the map at the provided section 75 | func (m *Monitor) GetMap(section string) *ebpf.Map { 76 | return m.collection.Maps[section] 77 | } 78 | 79 | // Init - Initializes the monitor 80 | func (m *Monitor) Init(fs FSProbe) error { 81 | m.FSProbe = fs 82 | m.wg = fs.GetWaitGroup() 83 | m.Options = fs.GetOptions() 84 | m.collection = fs.GetCollection() 85 | m.Configure() 86 | // Init probes 87 | for _, probes := range m.Probes { 88 | for _, p := range probes { 89 | if err := p.Init(m); err != nil { 90 | return err 91 | } 92 | } 93 | } 94 | // Prepare perf maps 95 | for _, pm := range m.PerfMaps { 96 | if err := pm.Init(m); err != nil { 97 | return err 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | // Start - Starts the monitor 104 | func (m *Monitor) Start() error { 105 | // start probes 106 | for _, probes := range m.Probes { 107 | for _, p := range probes { 108 | if err := p.Start(); err != nil { 109 | logrus.Errorf("couldn't start probe \"%s\": %v", p.Name, err) 110 | return err 111 | } 112 | } 113 | } 114 | // start polling perf maps 115 | for _, pm := range m.PerfMaps { 116 | if err := pm.pollStart(); err != nil { 117 | return err 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | // Stop - Stops the monitor 124 | func (m *Monitor) Stop() error { 125 | // Stop probes 126 | for _, probes := range m.Probes { 127 | for _, p := range probes { 128 | if err := p.Stop(); err != nil { 129 | logrus.Errorf("couldn't stop probe \"%s\": %v", p.Name, err) 130 | } 131 | } 132 | } 133 | // stop polling perf maps 134 | for _, pm := range m.PerfMaps { 135 | if err := pm.pollStop(); err != nil { 136 | logrus.Errorf("couldn't close perf map %v gracefully: %v", pm.PerfOutputMapName, err) 137 | } 138 | } 139 | return nil 140 | } 141 | 142 | func (m *Monitor) AddInodeFilter(inode uint32, path string) error { 143 | // Add file in caches 144 | if m.DentryResolver != nil { 145 | if err := m.DentryResolver.AddCacheEntry(inode, path); err != nil { 146 | return err 147 | } 148 | } 149 | // Add inode filter 150 | filter := m.GetMap(m.InodeFilterSection) 151 | if filter == nil { 152 | return fmt.Errorf("couldn't find %v map", m.InodeFilterSection) 153 | } 154 | keyB := make([]byte, 4) 155 | utils.ByteOrder.PutUint32(keyB, inode) 156 | var valueB byte 157 | if err := filter.Put(keyB, valueB); err != nil { 158 | return err 159 | } 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /pkg/model/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | // FSProbeOptions - Filesystem probe options 19 | type FSProbeOptions struct { 20 | Recursive bool 21 | Events []EventName 22 | PerfBufferSize int 23 | UserSpaceChanSize int 24 | DentryResolutionMode DentryResolutionMode 25 | PathsFiltering bool 26 | FollowRenames bool 27 | EventChan chan *FSEvent 28 | LostChan chan *LostEvt 29 | } 30 | -------------------------------------------------------------------------------- /pkg/model/perfmap.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | import ( 19 | "os" 20 | 21 | "github.com/Gui774ume/ebpf" 22 | "github.com/pkg/errors" 23 | ) 24 | 25 | type LostEvt struct { 26 | Count uint64 27 | Map string 28 | } 29 | 30 | // PerfMap - Definition of a perf map, used to bring data back to user space 31 | type PerfMap struct { 32 | monitor *Monitor 33 | perfReader *ebpf.PerfReader 34 | perfMap *ebpf.Map 35 | UserSpaceBufferLen int 36 | PerfOutputMapName string 37 | event chan []byte 38 | lost chan uint64 39 | stop chan struct{} 40 | DataHandler func(data []byte, m *Monitor) 41 | LostHandler func(count uint64, mapName string, m *Monitor) 42 | } 43 | 44 | // Init - Initializes perfmap 45 | func (pm *PerfMap) Init(m *Monitor) error { 46 | pm.monitor = m 47 | if pm.DataHandler == nil { 48 | return errors.New("Data handler not set") 49 | } 50 | // Default userspace buffer length 51 | if pm.UserSpaceBufferLen == 0 { 52 | pm.UserSpaceBufferLen = pm.monitor.Options.UserSpaceChanSize 53 | } 54 | // Select map 55 | var ok bool 56 | pm.perfMap, ok = pm.monitor.collection.Maps[pm.PerfOutputMapName] 57 | if !ok || pm.perfMap == nil { 58 | errors.Wrapf( 59 | errors.New("map not found"), 60 | "couldn't init map %s", 61 | pm.PerfOutputMapName, 62 | ) 63 | } 64 | // Init channels 65 | pm.stop = make(chan struct{}) 66 | return nil 67 | } 68 | 69 | func (pm *PerfMap) pollStart() error { 70 | pageSize := os.Getpagesize() 71 | // Start perf map 72 | var err error 73 | pm.perfReader, err = ebpf.NewPerfReader(ebpf.PerfReaderOptions{ 74 | Map: pm.perfMap, 75 | PerCPUBuffer: pm.monitor.Options.PerfBufferSize * pageSize, 76 | Watermark: 1, 77 | UserSpaceChanSize: pm.UserSpaceBufferLen, 78 | }) 79 | if err != nil { 80 | return errors.Wrapf(err, "couldn't start map %s", pm.PerfOutputMapName) 81 | } 82 | go pm.listen() 83 | return nil 84 | } 85 | 86 | // listen - Listen for new events from the kernel 87 | func (pm *PerfMap) listen() { 88 | pm.monitor.wg.Add(1) 89 | var sample *ebpf.PerfSample 90 | var ok bool 91 | var lostCount uint64 92 | for { 93 | select { 94 | case <-pm.stop: 95 | pm.monitor.wg.Done() 96 | return 97 | case sample, ok = <-pm.perfReader.Samples: 98 | if !ok { 99 | pm.monitor.wg.Done() 100 | return 101 | } 102 | pm.DataHandler(sample.Data, pm.monitor) 103 | case lostCount, ok = <-pm.perfReader.LostRecords: 104 | if !ok { 105 | pm.monitor.wg.Done() 106 | return 107 | } 108 | if pm.LostHandler != nil { 109 | pm.LostHandler(lostCount, pm.PerfOutputMapName, pm.monitor) 110 | } 111 | } 112 | } 113 | } 114 | 115 | // pollStop - Stop a perf map listener 116 | func (m *PerfMap) pollStop() error { 117 | err := m.perfReader.FlushAndClose() 118 | close(m.stop) 119 | return err 120 | } 121 | -------------------------------------------------------------------------------- /pkg/model/probe.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | import ( 19 | "github.com/Gui774ume/ebpf" 20 | ) 21 | 22 | // Probe - eBPF probe structure 23 | type Probe struct { 24 | Name string 25 | Enabled bool 26 | monitor *Monitor 27 | Type ebpf.ProgType 28 | SectionName string 29 | // Kprobe specific parameters 30 | KProbeMaxActive int 31 | // Constants will be edited with configuration at runtime 32 | Constants []string 33 | } 34 | 35 | // Init - Initializes the probe 36 | func (p *Probe) Init(m *Monitor) error { 37 | if !p.Enabled { 38 | return nil 39 | } 40 | p.monitor = m 41 | return nil 42 | } 43 | 44 | // Start - Starts the probe 45 | func (p *Probe) Start() error { 46 | if !p.Enabled { 47 | return nil 48 | } 49 | collection := p.monitor.collection 50 | // Enable eBPF program 51 | switch p.Type { 52 | case ebpf.TracePoint: 53 | if err := collection.EnableTracepoint(p.SectionName); err != nil { 54 | return err 55 | } 56 | case ebpf.Kprobe: 57 | maxActive := -1 58 | if p.KProbeMaxActive != 0 { 59 | maxActive = p.KProbeMaxActive 60 | } 61 | if err := collection.EnableKprobe(p.SectionName, maxActive); err != nil { 62 | return err 63 | } 64 | } 65 | return nil 66 | } 67 | 68 | // Stop - Stops the probe 69 | func (p *Probe) Stop() error { 70 | if !p.Enabled { 71 | return nil 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/model/resolver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package model 17 | 18 | import "C" 19 | import ( 20 | "bytes" 21 | "encoding/binary" 22 | "fmt" 23 | "unsafe" 24 | 25 | "github.com/Gui774ume/ebpf" 26 | "github.com/Gui774ume/fsprobe/pkg/utils" 27 | lru "github.com/hashicorp/golang-lru" 28 | "github.com/pkg/errors" 29 | "github.com/sirupsen/logrus" 30 | ) 31 | 32 | // DentryResolver - Path resolver for the path fragments and single fragment methods 33 | type DentryResolver interface { 34 | ResolveInode(mountID uint32, inode uint64) (string, error) 35 | RemoveInode(mountID uint32, inode uint64) error 36 | ResolveKey(key uint32, length uint32) (string, error) 37 | RemoveEntry(key uint32) error 38 | AddCacheEntry(key uint32, value interface{}) error 39 | } 40 | 41 | // PathFragmentsKey - Key of a dentry cache hashmap 42 | type PathFragmentsKey struct { 43 | inode uint64 44 | mountID uint32 45 | } 46 | 47 | func (pfk *PathFragmentsKey) Set(mountID uint32, inode uint64) { 48 | pfk.mountID = mountID 49 | pfk.inode = inode 50 | } 51 | 52 | func (pfk *PathFragmentsKey) Write(buffer []byte) { 53 | utils.ByteOrder.PutUint64(buffer[0:8], pfk.inode) 54 | utils.ByteOrder.PutUint32(buffer[8:12], pfk.mountID) 55 | utils.ByteOrder.PutUint32(buffer[12:16], 0) 56 | } 57 | 58 | func (pfk *PathFragmentsKey) GetKeyBytes() []byte { 59 | keyB := make([]byte, 16) 60 | pfk.Write(keyB) 61 | return keyB[:] 62 | } 63 | 64 | func (pfk *PathFragmentsKey) Read(buffer []byte) int { 65 | pfk.inode = utils.ByteOrder.Uint64(buffer[0:8]) 66 | pfk.mountID = utils.ByteOrder.Uint32(buffer[8:12]) 67 | return 16 68 | } 69 | 70 | func (pfk *PathFragmentsKey) IsNull() bool { 71 | return pfk.inode == 0 && pfk.mountID == 0 72 | } 73 | 74 | func (pfk *PathFragmentsKey) HasEmptyInode() bool { 75 | return pfk.inode == 0 76 | } 77 | 78 | func (pfk *PathFragmentsKey) String() string { 79 | return fmt.Sprintf("%x/%x", pfk.mountID, pfk.inode) 80 | } 81 | 82 | type PathFragmentsValue struct { 83 | parent PathFragmentsKey 84 | Fragment [PathFragmentsSize]byte 85 | } 86 | 87 | // Read - Reads the provided data into the buffer 88 | func (pfv *PathFragmentsValue) Read(data []byte) error { 89 | return binary.Read(bytes.NewBuffer(data), utils.ByteOrder, &pfv.Fragment) 90 | } 91 | 92 | // IsRoot - Returns true if the current fragment is the root of a mount point 93 | func (pfv *PathFragmentsValue) IsRoot() bool { 94 | if pfv.Fragment[0] == 47 { 95 | return true 96 | } 97 | return false 98 | } 99 | 100 | // GetString - Returns the path as a string 101 | func (pfv *PathFragmentsValue) GetString() string { 102 | return C.GoString((*C.char)(unsafe.Pointer(&pfv.Fragment))) 103 | } 104 | 105 | // PathFragmentsResolver - Dentry resolver of the path fragments method 106 | type PathFragmentsResolver struct { 107 | cache *ebpf.Map 108 | key *PathFragmentsKey 109 | value *PathFragmentsValue 110 | } 111 | 112 | // NewPathFragmentsResolver - Returns a new PathFragmentsResolver instance 113 | func NewPathFragmentsResolver(monitor *Monitor) (*PathFragmentsResolver, error) { 114 | cache := monitor.GetMap(PathFragmentsMap) 115 | if cache == nil { 116 | return nil, fmt.Errorf("%s eBPF map doesn't exist", PathFragmentsMap) 117 | } 118 | return &PathFragmentsResolver{ 119 | cache: cache, 120 | key: &PathFragmentsKey{}, 121 | value: &PathFragmentsValue{}, 122 | }, nil 123 | } 124 | 125 | // ResolveInode - Resolves a pathname from the provided mount id and inode 126 | func (pfr *PathFragmentsResolver) ResolveInode(mountID uint32, inode uint64) (filename string, err error) { 127 | // Don't resolve path if pathnameKey isn't valid 128 | pfr.key.Set(mountID, inode) 129 | if pfr.key.IsNull() { 130 | return "", fmt.Errorf("invalid inode/dev couple: %s", pfr.key.String()) 131 | } 132 | 133 | keyB := pfr.key.GetKeyBytes() 134 | valueB := []byte{} 135 | done := false 136 | // Fetch path recursively 137 | for !done { 138 | if valueB, err = pfr.cache.GetBytes(keyB); err != nil || len(valueB) == 0 { 139 | filename = "*ERROR*" + filename 140 | break 141 | } 142 | // Read next key from valueB 143 | read := pfr.key.Read(valueB) 144 | // Read current fragment from valueB 145 | if err = pfr.value.Read(valueB[read:]); err != nil { 146 | err = errors.Wrap(err, "failed to decode fragment") 147 | break 148 | } 149 | 150 | // Don't append dentry name if this is the root dentry (i.d. name == '/') 151 | if !pfr.value.IsRoot() { 152 | filename = "/" + pfr.value.GetString() + filename 153 | } 154 | 155 | if pfr.key.HasEmptyInode() { 156 | break 157 | } 158 | 159 | // Prepare next key 160 | pfr.key.Write(keyB) 161 | } 162 | 163 | if len(filename) == 0 { 164 | filename = "/" 165 | } 166 | 167 | return 168 | } 169 | 170 | // RemoveInode - Removes a pathname from the kernel cache using the provided mount id and inode 171 | func (pfr *PathFragmentsResolver) RemoveInode(mountID uint32, inode uint64) error { 172 | // Don't resolve path if pathnameKey isn't valid 173 | pfr.key.Set(mountID, inode) 174 | if pfr.key.IsNull() { 175 | return fmt.Errorf("invalid inode/dev couple: %s", pfr.key.String()) 176 | } 177 | keyB := pfr.key.GetKeyBytes() 178 | // Delete entry 179 | return pfr.cache.Delete(keyB) 180 | } 181 | 182 | // ResolveKey - Does nothing 183 | func (pfr *PathFragmentsResolver) ResolveKey(key uint32, length uint32) (string, error) { 184 | return "", nil 185 | } 186 | 187 | // AddCacheEntry - Adds a new entry in the user space cache 188 | func (pfr *PathFragmentsResolver) AddCacheEntry(key uint32, value interface{}) error { 189 | return nil 190 | } 191 | 192 | // RemoveEntry - Removes an entry from the cache 193 | func (pfr *PathFragmentsResolver) RemoveEntry(key uint32) error { 194 | return nil 195 | } 196 | 197 | // SingleFragmentKey - Key of a dentry cache hashmap 198 | type SingleFragmentKey struct { 199 | key uint32 200 | } 201 | 202 | func (sfk *SingleFragmentKey) Set(key uint32) { 203 | sfk.key = key 204 | } 205 | 206 | func (sfk *SingleFragmentKey) Write(buffer []byte) { 207 | utils.ByteOrder.PutUint32(buffer[0:4], sfk.key) 208 | } 209 | 210 | func (sfk *SingleFragmentKey) GetKeyBytes() []byte { 211 | keyB := make([]byte, 4) 212 | sfk.Write(keyB) 213 | return keyB 214 | } 215 | 216 | func (sfk *SingleFragmentKey) IsNull() bool { 217 | return sfk.key == 0 218 | } 219 | 220 | func (sfk *SingleFragmentKey) String() string { 221 | return fmt.Sprintf("%x", sfk.key) 222 | } 223 | 224 | type SingleFragmentValue struct { 225 | Fragment [SingleFragmentSize]byte 226 | } 227 | 228 | // Read - Reads the provided data into the buffer 229 | func (sfv *SingleFragmentValue) Read(data []byte) error { 230 | return binary.Read(bytes.NewBuffer(data), utils.ByteOrder, &sfv.Fragment) 231 | } 232 | 233 | // GetString - Returns the path as a string 234 | func (sfv *SingleFragmentValue) GetString(length uint32) string { 235 | if length > 0 { 236 | return decodePath(sfv.Fragment[:length]) 237 | } 238 | return decodePath(sfv.Fragment[:]) 239 | } 240 | 241 | type SingleFragmentResolver struct { 242 | cache *ebpf.Map 243 | key *SingleFragmentKey 244 | value *SingleFragmentValue 245 | } 246 | 247 | // NewSingleFragmentResolver - Returns a new SingleFragmentResolver instance 248 | func NewSingleFragmentResolver(monitor *Monitor) (*SingleFragmentResolver, error) { 249 | cache := monitor.GetMap(SingleFragmentsMap) 250 | if cache == nil { 251 | return nil, fmt.Errorf("%s eBPF map doesn't exist", SingleFragmentsMap) 252 | } 253 | return &SingleFragmentResolver{ 254 | cache: cache, 255 | key: &SingleFragmentKey{}, 256 | value: &SingleFragmentValue{}, 257 | }, nil 258 | } 259 | 260 | // ResolveInode - Does nothing 261 | func (sfr *SingleFragmentResolver) ResolveInode(mountID uint32, inode uint64) (filename string, err error) { 262 | return "", nil 263 | } 264 | 265 | // RemoveInode - Removes a pathname from the kernel cache using the provided mount id and inode 266 | func (sfr *SingleFragmentResolver) RemoveInode(mountID uint32, inode uint64) error { 267 | return nil 268 | } 269 | 270 | // Resolve - Resolves a pathname from the provided mount id and inode 271 | func (sfr *SingleFragmentResolver) ResolveKey(key uint32, length uint32) (filename string, err error) { 272 | // Don't resolve path if pathnameKey isn't valid 273 | sfr.key.Set(key) 274 | if sfr.key.IsNull() { 275 | return "", fmt.Errorf("invalid key: %s", sfr.key.String()) 276 | } 277 | // Generate hashmap key 278 | keyB := sfr.key.GetKeyBytes() 279 | valueB := []byte{} 280 | // Fetch hashmap value 281 | if valueB, err = sfr.cache.GetBytes(keyB); err != nil || len(valueB) == 0 { 282 | filename = "*ERROR*" 283 | err = errors.Wrap(err, "failed to query value") 284 | return 285 | } 286 | // Read fragment from valueB 287 | if err = sfr.value.Read(valueB); err != nil { 288 | filename = "*ERROR*" 289 | err = errors.Wrap(err, "failed to decode fragment") 290 | return 291 | } 292 | filename = sfr.value.GetString(length) 293 | if len(filename) == 0 { 294 | filename = "/" 295 | } 296 | return 297 | } 298 | 299 | // AddCacheEntry - Adds a new entry in the user space cache 300 | func (sfr *SingleFragmentResolver) AddCacheEntry(key uint32, value interface{}) error { 301 | return nil 302 | } 303 | 304 | // RemoveEntry - Removes an entry from the cache 305 | func (sfr *SingleFragmentResolver) RemoveEntry(key uint32) error { 306 | // Don't resolve path if pathnameKey isn't valid 307 | sfr.key.Set(key) 308 | if sfr.key.IsNull() { 309 | return fmt.Errorf("invalid key: %s", sfr.key.String()) 310 | } 311 | // Generate hashmap key 312 | keyB := sfr.key.GetKeyBytes() 313 | // Delete entry 314 | if err := sfr.cache.Delete(keyB); err != nil { 315 | return errors.Wrapf(err, "failed to delete entry at %s", sfr.key.String()) 316 | } 317 | return nil 318 | } 319 | 320 | type PerfBufferResolver struct { 321 | kernelLRU *ebpf.Map 322 | lru *lru.Cache 323 | } 324 | 325 | // NewPerfBufferResolver - Returns a new PerfBufferResolver instance 326 | func NewPerfBufferResolver(monitor *Monitor) (*PerfBufferResolver, error) { 327 | var err error 328 | pbr := PerfBufferResolver{} 329 | pbr.kernelLRU = monitor.GetMap(CachedInodesMap) 330 | if pbr.kernelLRU == nil { 331 | return nil, fmt.Errorf("%s eBPF map doesn't exist", CachedInodesMap) 332 | } 333 | pbr.lru, err = lru.NewWithEvict(PerfBufferCachedInodesSize, pbr.onCachedInodeEvicted) 334 | if err != nil { 335 | return nil, errors.Wrap(err, "couldn't create a new PerfBufferResolver LRU") 336 | } 337 | return &pbr, nil 338 | } 339 | 340 | // ResolveInode - Does nothing 341 | func (pbr *PerfBufferResolver) ResolveInode(mountID uint32, inode uint64) (filename string, err error) { 342 | return "", nil 343 | } 344 | 345 | // RemoveInode - Removes a pathname from the kernel cache using the provided mount id and inode 346 | func (pbr *PerfBufferResolver) RemoveInode(mountID uint32, inode uint64) error { 347 | return nil 348 | } 349 | 350 | // Resolve - Resolves a pathname from the provided key (length is not used) 351 | func (pbr *PerfBufferResolver) ResolveKey(key uint32, length uint32) (string, error) { 352 | // Select the inode path from the lru 353 | value, ok := pbr.lru.Get(key) 354 | if ok { 355 | return value.(string), nil 356 | } 357 | if key == 2 { 358 | return "/", nil 359 | } 360 | return "", fmt.Errorf("%v not found", key) 361 | } 362 | 363 | // AddCacheEntry - Adds a new entry in the LRU cache 364 | func (pbr *PerfBufferResolver) AddCacheEntry(key uint32, value interface{}) error { 365 | // Add entry in user space LRU 366 | pbr.lru.Add(key, value) 367 | // Add entry in the kernel space cache 368 | keyB := make([]byte, 4) 369 | utils.ByteOrder.PutUint32(keyB, key) 370 | var valueB byte 371 | if err := pbr.kernelLRU.Put(keyB, valueB); err != nil { 372 | return err 373 | } 374 | return nil 375 | } 376 | 377 | // RemoveEntry - Removes an entry from the cache 378 | func (pbr *PerfBufferResolver) RemoveEntry(key uint32) error { 379 | keyB := make([]byte, 4) 380 | utils.ByteOrder.PutUint32(keyB, key) 381 | if err := pbr.kernelLRU.Delete(keyB); err != nil { 382 | return errors.Wrap(err, "failed to delete entry from cached_inodes eBPF map") 383 | } 384 | return nil 385 | } 386 | 387 | // onCachedInodeEvicted - Removes the input inode from the kernel space cache 388 | func (pbr *PerfBufferResolver) onCachedInodeEvicted(key, value interface{}) { 389 | keyB := make([]byte, 4) 390 | keyU, ok := key.(uint32) 391 | if !ok { 392 | logrus.Warnf("failed to delete entry from cached_inodes eBPF map: key is not uint32: %v", key) 393 | } 394 | utils.ByteOrder.PutUint32(keyB, keyU) 395 | if err := pbr.kernelLRU.Delete(keyB); err != nil { 396 | logrus.Warnf("failed to delete entry from cached_inodes eBPF map: %v", err) 397 | } 398 | } 399 | 400 | // NewDentryResolver - Returns a new resolver configured for the selected resolution method 401 | func NewDentryResolver(monitor *Monitor) (DentryResolver, error) { 402 | switch monitor.Options.DentryResolutionMode { 403 | case DentryResolutionFragments: 404 | return NewPathFragmentsResolver(monitor) 405 | case DentryResolutionSingleFragment: 406 | return NewSingleFragmentResolver(monitor) 407 | case DentryResolutionPerfBuffer: 408 | return NewPerfBufferResolver(monitor) 409 | } 410 | return nil, errors.New("unknown dentry resolution mode") 411 | } 412 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package utils 17 | 18 | import ( 19 | "C" 20 | "bufio" 21 | "bytes" 22 | "encoding/binary" 23 | "fmt" 24 | "io/ioutil" 25 | "os" 26 | "reflect" 27 | "strconv" 28 | "strings" 29 | "unsafe" 30 | ) 31 | 32 | // GetPpid is a fallback to read the parent PID from /proc. 33 | // Some kernel versions, like 4.13.0 return 0 getting the parent PID 34 | // from the current task, so we need to use this fallback to have 35 | // the parent PID in any kernel. 36 | func GetPpid(pid uint32) uint32 { 37 | f, err := os.OpenFile(fmt.Sprintf("/proc/%d/status", pid), os.O_RDONLY, os.ModePerm) 38 | if err != nil { 39 | return 0 40 | } 41 | defer f.Close() 42 | 43 | sc := bufio.NewScanner(f) 44 | for sc.Scan() { 45 | text := sc.Text() 46 | if strings.Contains(text, "PPid:") { 47 | f := strings.Fields(text) 48 | i, _ := strconv.ParseUint(f[len(f)-1], 10, 64) 49 | return uint32(i) 50 | } 51 | } 52 | return 0 53 | } 54 | 55 | // getNamespaceID - Returns the namespace id in brackets 56 | func getNamespaceID(raw string) uint64 { 57 | i := strings.Index(raw, "[") 58 | if i > 0 { 59 | id, err := strconv.ParseUint(raw[i+1:len(raw)-1], 10, 64) 60 | if err != nil { 61 | return 0 62 | } 63 | return id 64 | } 65 | return 0 66 | } 67 | 68 | // GetPidnsFromPid - Returns the pid namespace of a process 69 | func GetPidnsFromPid(pid uint32) uint64 { 70 | raw, err := os.Readlink(fmt.Sprintf("/proc/%v/ns/pid_for_children", pid)) 71 | if err != nil { 72 | return 0 73 | } 74 | return getNamespaceID(raw) 75 | } 76 | 77 | // GetNetnsFromPid - Returns the network namespace of a process 78 | func GetNetnsFromPid(pid uint32) uint64 { 79 | raw, err := os.Readlink(fmt.Sprintf("/proc/%v/ns/net", pid)) 80 | if err != nil { 81 | return 0 82 | } 83 | return getNamespaceID(raw) 84 | } 85 | 86 | // GetUsernsFromPid - Returns the user namespace of a process 87 | func GetUsernsFromPid(pid uint32) uint64 { 88 | raw, err := os.Readlink(fmt.Sprintf("/proc/%v/ns/user", pid)) 89 | if err != nil { 90 | return 0 91 | } 92 | return getNamespaceID(raw) 93 | } 94 | 95 | // GetMntnsFromPid - Returns the mount namespace of a process 96 | func GetMntnsFromPid(pid uint32) uint64 { 97 | raw, err := os.Readlink(fmt.Sprintf("/proc/%v/ns/mnt", pid)) 98 | if err != nil { 99 | return 0 100 | } 101 | return getNamespaceID(raw) 102 | } 103 | 104 | // GetCgroupFromPid - Returns the cgroup of a process 105 | func GetCgroupFromPid(pid uint32) uint64 { 106 | raw, err := os.Readlink(fmt.Sprintf("/proc/%v/ns/cgroup", pid)) 107 | if err != nil { 108 | return 0 109 | } 110 | return getNamespaceID(raw) 111 | } 112 | 113 | // GetCommFromPid - Returns the comm of a process 114 | func GetCommFromPid(pid uint32) string { 115 | f, err := os.OpenFile(fmt.Sprintf("/proc/%d/comm", pid), os.O_RDONLY, os.ModePerm) 116 | if err != nil { 117 | return "" 118 | } 119 | defer f.Close() 120 | raw, err := ioutil.ReadAll(f) 121 | if err != nil { 122 | return "" 123 | } 124 | return strings.Replace(string(raw), "\n", "", -1) 125 | } 126 | 127 | // InterfaceToBytes - Tranforms an interface into a C bytes array 128 | func InterfaceToBytes(data interface{}, byteOrder binary.ByteOrder) ([]byte, error) { 129 | var buf bytes.Buffer 130 | if err := binary.Write(&buf, byteOrder, data); err != nil { 131 | return []byte{}, err 132 | } 133 | return buf.Bytes(), nil 134 | } 135 | 136 | // GetHostByteOrder - Returns the host byte order 137 | func GetHostByteOrder() binary.ByteOrder { 138 | if isBigEndian() { 139 | return binary.BigEndian 140 | } 141 | return binary.LittleEndian 142 | } 143 | 144 | func isBigEndian() (ret bool) { 145 | i := int(0x1) 146 | bs := (*[int(unsafe.Sizeof(i))]byte)(unsafe.Pointer(&i)) 147 | return bs[0] == 0 148 | } 149 | 150 | func getHostByteOrder() binary.ByteOrder { 151 | var i int32 = 0x01020304 152 | u := unsafe.Pointer(&i) 153 | pb := (*byte)(u) 154 | b := *pb 155 | if b == 0x04 { 156 | return binary.LittleEndian 157 | } 158 | 159 | return binary.BigEndian 160 | } 161 | 162 | // ByteOrder - host byte order 163 | var ByteOrder binary.ByteOrder 164 | 165 | func init() { 166 | ByteOrder = getHostByteOrder() 167 | } 168 | 169 | // String - No copy bytes to string conversion 170 | func String(bytes []byte) string { 171 | hdr := *(*reflect.SliceHeader)(unsafe.Pointer(&bytes)) 172 | return *(*string)(unsafe.Pointer(&reflect.StringHeader{ 173 | Data: hdr.Data, 174 | Len: hdr.Len, 175 | })) 176 | } 177 | 178 | // Bytes - No copy string to bytes conversion 179 | func Bytes(str string) []byte { 180 | hdr := *(*reflect.StringHeader)(unsafe.Pointer(&str)) 181 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 182 | Data: hdr.Data, 183 | Len: hdr.Len, 184 | Cap: hdr.Len, 185 | })) 186 | } 187 | -------------------------------------------------------------------------------- /tests/open_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package tests 17 | 18 | import ( 19 | "github.com/Gui774ume/fsprobe/pkg/fsprobe" 20 | "github.com/Gui774ume/fsprobe/pkg/model" 21 | "github.com/sirupsen/logrus" 22 | "os" 23 | "path" 24 | "syscall" 25 | "testing" 26 | ) 27 | 28 | func prepareTestFile(b *testing.B) string { 29 | // Prepare fake file 30 | root := "/tmp/open/open/open/open/open/" 31 | if err := os.MkdirAll(root, 0777); err != nil { 32 | b.Fatal(err) 33 | } 34 | return path.Join(root, "open-test") 35 | } 36 | 37 | // benchmarkOpen - Opens a file from the paths generator and benchmark the overhead 38 | func benchmarkOpen(b *testing.B, pg *PathsGenerator) { 39 | // Start benchmark 40 | b.ResetTimer() 41 | for i := 0; i < b.N; i++ { 42 | filepath := pg.GetRandomFile() 43 | // Select a file in the list 44 | fd, err := syscall.Open(filepath, syscall.O_CREAT, 0777) 45 | if err != nil { 46 | b.Fatal(err) 47 | } 48 | 49 | if err := syscall.Close(fd); err != nil { 50 | b.Fatal(err) 51 | } 52 | } 53 | b.StopTimer() 54 | } 55 | 56 | func benchmarkFSProbe(b *testing.B, options model.FSProbeOptions, pg *PathsGenerator) { 57 | b.Logf("restarted %d", b.N) 58 | // Initialize the paths generator 59 | if err := pg.Init(); err != nil { 60 | b.Fatal(err) 61 | } 62 | // Handle lost events 63 | lostChan := make(chan *model.LostEvt, options.UserSpaceChanSize) 64 | options.LostChan = lostChan 65 | go pollLost(lostChan) 66 | // Instantiate a new probe 67 | probe := fsprobe.NewFSProbeWithOptions(options) 68 | 69 | // Start watching file opens 70 | if err := probe.Watch(pg.GetWatchedPaths()...); err != nil { 71 | b.Fatal(err) 72 | } 73 | 74 | // Open and close files to evaluate the overhead 75 | benchmarkOpen(b, pg) 76 | 77 | // Stop probe 78 | if err := probe.Stop(); err != nil { 79 | b.Error(err) 80 | } 81 | 82 | // Clean up paths generator 83 | if err := pg.Close(); err != nil { 84 | b.Fatal(err) 85 | } 86 | 87 | // Close lost events handler 88 | close(lostChan) 89 | } 90 | 91 | func pollLost(lostChan chan *model.LostEvt) { 92 | var evt *model.LostEvt 93 | var ok bool 94 | select { 95 | case evt, ok = <-lostChan: 96 | if !ok { 97 | return 98 | } 99 | logrus.Warnf("lost %v events from %v", evt.Count, evt.Map) 100 | break 101 | } 102 | } 103 | 104 | func BenchmarkOpen(b *testing.B) { 105 | pg := &PathsGenerator{ 106 | Depth: 5, 107 | Breadth: 8000, 108 | NumOfFiles: 80000, 109 | NamesLength: 10, 110 | Root: "/tmp/fsprobe", 111 | } 112 | if err := pg.Init(); err != nil { 113 | b.Fatal(err) 114 | } 115 | // Open and close files to evaluate the overhead 116 | benchmarkOpen(b, pg) 117 | if err := pg.Close(); err != nil { 118 | b.Fatal(err) 119 | } 120 | } 121 | 122 | func BenchmarkFSProbePerfBufferOpen(b *testing.B) { 123 | benchmarkFSProbe(b, model.FSProbeOptions{ 124 | Events: []model.EventName{model.Open}, 125 | PerfBufferSize: 4096, 126 | UserSpaceChanSize: 1000, 127 | DentryResolutionMode: model.DentryResolutionPerfBuffer, 128 | PathsFiltering: true, 129 | Recursive: true, 130 | }, &PathsGenerator{ 131 | Depth: 5, 132 | Breadth: 8000, 133 | NumOfFiles: 80000, 134 | NamesLength: 10, 135 | Root: "/tmp/fsprobe", 136 | }) 137 | } 138 | 139 | func BenchmarkFSProbeFragmentsOpen(b *testing.B) { 140 | benchmarkFSProbe(b, model.FSProbeOptions{ 141 | Events: []model.EventName{model.Open}, 142 | PerfBufferSize: 4096, 143 | UserSpaceChanSize: 1000, 144 | DentryResolutionMode: model.DentryResolutionFragments, 145 | PathsFiltering: true, 146 | Recursive: true, 147 | }, &PathsGenerator{ 148 | Depth: 5, 149 | Breadth: 8000, 150 | NumOfFiles: 80000, 151 | NamesLength: 10, 152 | Root: "/tmp/fsprobe", 153 | }) 154 | } 155 | 156 | func BenchmarkFSProbeSingleFragmentOpen(b *testing.B) { 157 | benchmarkFSProbe(b, model.FSProbeOptions{ 158 | Events: []model.EventName{model.Open}, 159 | PerfBufferSize: 4096, 160 | UserSpaceChanSize: 1000, 161 | DentryResolutionMode: model.DentryResolutionSingleFragment, 162 | PathsFiltering: true, 163 | Recursive: true, 164 | }, &PathsGenerator{ 165 | Depth: 5, 166 | Breadth: 8000, 167 | NumOfFiles: 80000, 168 | NamesLength: 10, 169 | Root: "/tmp/fsprobe", 170 | }) 171 | } 172 | 173 | func BenchmarkPerfBufferSize(b *testing.B) { 174 | options := model.FSProbeOptions{ 175 | Events: []model.EventName{model.Open}, 176 | UserSpaceChanSize: 1000, 177 | PathsFiltering: true, 178 | Recursive: true, 179 | } 180 | benchmarks := []struct { 181 | name string 182 | perfBufferSize int 183 | resolutionMode model.DentryResolutionMode 184 | }{ 185 | {"Fragments8", 8, model.DentryResolutionFragments}, 186 | {"SingleFragment8", 8, model.DentryResolutionSingleFragment}, 187 | {"PerfBuffer8", 8, model.DentryResolutionPerfBuffer}, 188 | {"Fragments16", 16, model.DentryResolutionFragments}, 189 | {"SingleFragment16", 16, model.DentryResolutionSingleFragment}, 190 | {"PerfBuffer16", 16, model.DentryResolutionPerfBuffer}, 191 | {"Fragments32", 32, model.DentryResolutionFragments}, 192 | {"SingleFragment32", 32, model.DentryResolutionSingleFragment}, 193 | {"PerfBuffer32", 32, model.DentryResolutionPerfBuffer}, 194 | {"Fragments64", 64, model.DentryResolutionFragments}, 195 | {"SingleFragment64", 64, model.DentryResolutionSingleFragment}, 196 | {"PerfBuffer64", 64, model.DentryResolutionPerfBuffer}, 197 | {"Fragments128", 128, model.DentryResolutionFragments}, 198 | {"SingleFragment128", 128, model.DentryResolutionSingleFragment}, 199 | {"PerfBuffer128", 128, model.DentryResolutionPerfBuffer}, 200 | {"Fragments256", 256, model.DentryResolutionFragments}, 201 | {"SingleFragment256", 256, model.DentryResolutionSingleFragment}, 202 | {"PerfBuffer256", 256, model.DentryResolutionPerfBuffer}, 203 | {"Fragments512", 512, model.DentryResolutionFragments}, 204 | {"SingleFragment512", 512, model.DentryResolutionSingleFragment}, 205 | {"PerfBuffer512", 512, model.DentryResolutionPerfBuffer}, 206 | {"Fragments1024", 1024, model.DentryResolutionFragments}, 207 | {"SingleFragment1024", 1024, model.DentryResolutionSingleFragment}, 208 | {"PerfBuffer1024", 1024, model.DentryResolutionPerfBuffer}, 209 | {"Fragments2048", 2048, model.DentryResolutionFragments}, 210 | {"SingleFragment2048", 2048, model.DentryResolutionSingleFragment}, 211 | {"PerfBuffer2048", 2048, model.DentryResolutionPerfBuffer}, 212 | {"Fragments4096", 4096, model.DentryResolutionFragments}, 213 | {"SingleFragment4096", 4096, model.DentryResolutionSingleFragment}, 214 | {"PerfBuffer4096", 4096, model.DentryResolutionPerfBuffer}, 215 | } 216 | for _, bm := range benchmarks { 217 | options.PerfBufferSize = bm.perfBufferSize 218 | options.DentryResolutionMode = bm.resolutionMode 219 | b.Run(bm.name, func(b *testing.B) { 220 | pg := PathsGenerator{ 221 | Depth: 60, 222 | Breadth: 1000, 223 | NumOfFiles: 60000, 224 | NamesLength: 10, 225 | Root: "/tmp/fsprobe", 226 | } 227 | benchmarkFSProbe(b, options, &pg) 228 | }) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /tests/paths_generator.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "math/rand" 5 | "os" 6 | "time" 7 | ) 8 | 9 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 10 | 11 | var seededRand *rand.Rand = rand.New( 12 | rand.NewSource(time.Now().UnixNano())) 13 | 14 | func RandomStringWithCharset(length int, charset string) string { 15 | b := make([]byte, length) 16 | for i := range b { 17 | b[i] = charset[seededRand.Intn(len(charset))] 18 | } 19 | return string(b) 20 | } 21 | 22 | func RandomString(length int) string { 23 | return RandomStringWithCharset(length, charset) 24 | } 25 | 26 | // PathsGenerator - Paths generator used for the benchmark 27 | type PathsGenerator struct { 28 | WatchEntireFilesystem bool 29 | OutOfScope bool 30 | Depth int 31 | Breadth int 32 | NumOfFiles int 33 | NamesLength int 34 | Root string 35 | folders []string 36 | files []string 37 | } 38 | 39 | func (pg *PathsGenerator) Init() error { 40 | if err := pg.CreateFolders(); err != nil { 41 | return err 42 | } 43 | if err := pg.CreateFiles(); err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func (pg *PathsGenerator) GetWatchedPaths() []string { 50 | if pg.WatchEntireFilesystem { 51 | return []string{} 52 | } 53 | if pg.OutOfScope { 54 | // Return a directory. Hopefully this will not be Root, we just want to benchmark the overhead on files that 55 | // are not watched. 56 | return []string{"/boot"} 57 | } 58 | return []string{pg.Root} 59 | } 60 | 61 | func (pg *PathsGenerator) CreateFolders() error { 62 | var pathTmp string 63 | for i := 0; i < pg.Breadth; i++ { 64 | pathTmp = pg.Root 65 | for j := 0; j < pg.Depth; j++ { 66 | pathTmp += "/" + RandomString(pg.NamesLength) 67 | } 68 | // Create folder 69 | if err := os.MkdirAll(pathTmp, 0644); err != nil { 70 | return err 71 | } 72 | pg.folders = append(pg.folders, pathTmp) 73 | } 74 | return nil 75 | } 76 | 77 | func (pg *PathsGenerator) CreateFiles() error { 78 | var pathTmp string 79 | for i := 0; i < pg.NumOfFiles; i++ { 80 | pathTmp = pg.folders[i%pg.Breadth] 81 | pathTmp += "/" + RandomString(pg.NamesLength) 82 | f, err := os.OpenFile(pathTmp, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 83 | if err != nil { 84 | return err 85 | } 86 | if err := f.Close(); err != nil { 87 | return err 88 | } 89 | pg.files = append(pg.files, pathTmp) 90 | } 91 | return nil 92 | } 93 | 94 | func (pg *PathsGenerator) GetRandomFile() string { 95 | return pg.files[rand.Intn(pg.NumOfFiles)] 96 | } 97 | 98 | // Close - Delete all generated files and folders 99 | func (pg *PathsGenerator) Close() error { 100 | return os.RemoveAll(pg.Root) 101 | } 102 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 GUILLAUME FOURNIER 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // +build tools 18 | 19 | package tools 20 | 21 | // Those imports are used to track tool dependencies. 22 | // This is the currently recommended approach: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 23 | 24 | import ( 25 | _ "github.com/shuLhan/go-bindata/cmd/go-bindata" 26 | ) 27 | --------------------------------------------------------------------------------