├── .clang-format ├── .github └── workflows │ ├── build.yml │ └── linux.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CODEOWNERS ├── LICENSE ├── README.md ├── cmake ├── options.cmake ├── settings.cmake ├── toolchain.cmake └── version.cmake ├── libraries ├── CLI11 │ └── CMakeLists.txt ├── CMakeLists.txt ├── ebpf-common │ └── CMakeLists.txt └── rapidjson │ └── CMakeLists.txt ├── package_generator ├── CMakeLists.txt └── description.txt └── src ├── configuration.cpp ├── configuration.h ├── faultinjector.cpp ├── faultinjector.h ├── main.cpp ├── utils.cpp └── utils.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | 4 | ... 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | name: Build 10 | 11 | on: 12 | schedule: 13 | - cron: '00 21 * * *' 14 | 15 | pull_request: 16 | branches: 17 | - '*' 18 | 19 | push: 20 | branches: 21 | - 'main' 22 | 23 | concurrency: 24 | group: main-${{ github.event.pull_request.number || github.sha }} 25 | cancel-in-progress: true 26 | 27 | jobs: 28 | Linux_RelWithDebInfo: 29 | uses: ./.github/workflows/linux.yml 30 | with: 31 | platform: ubuntu-22.04 32 | build_type: RelWithDebInfo 33 | osquery_toolchain_version: 1.1.0 34 | 35 | Linux_Debug: 36 | uses: ./.github/workflows/linux.yml 37 | with: 38 | platform: ubuntu-22.04 39 | build_type: Debug 40 | osquery_toolchain_version: 1.1.0 41 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | name: Linux 10 | 11 | on: 12 | workflow_call: 13 | inputs: 14 | platform: 15 | required: true 16 | type: string 17 | 18 | build_type: 19 | required: true 20 | type: string 21 | 22 | osquery_toolchain_version: 23 | required: true 24 | type: string 25 | 26 | jobs: 27 | build: 28 | runs-on: ${{ inputs.platform }} 29 | 30 | env: 31 | CACHE_KEY: platform-${{ inputs.platform }}_type-${{ inputs.build_type }} 32 | 33 | steps: 34 | - name: Update the environment variables 35 | run: | 36 | echo "CCACHE_DIR=${{ github.workspace }}/ccache" >> $GITHUB_ENV 37 | echo "DOWNLOADS_PATH=${{ github.workspace }}/downloads" >> $GITHUB_ENV 38 | echo "TOOLCHAIN_PATH=${{ github.workspace }}/osquery-toolchain-${{ inputs.osquery_toolchain_version }}" >> $GITHUB_ENV 39 | echo "DESTDIR=${{ github.workspace }}/install" >> $GITHUB_ENV 40 | 41 | - name: Update the ccache cache 42 | uses: actions/cache@v3 43 | with: 44 | path: ${{ env.CCACHE_DIR }} 45 | key: ccache_${{ env.CACHE_KEY }}-${{ github.sha }} 46 | restore-keys: ccache_${{ env.CACHE_KEY }} 47 | 48 | - name: Update the downloads cache 49 | uses: actions/cache@v3 50 | with: 51 | path: ${{ env.DOWNLOADS_PATH }} 52 | key: downloads_${{ env.CACHE_KEY }}-${{ github.sha }} 53 | restore-keys: downloads_${{ env.CACHE_KEY }} 54 | 55 | - name: Create the build folders 56 | run: | 57 | mkdir -p \ 58 | ${{ env.CCACHE_DIR }} \ 59 | ${{ env.DOWNLOADS_PATH }} \ 60 | ${{ env.DESTDIR }} \ 61 | 62 | - name: Install system dependencies 63 | run: | 64 | sudo apt-get install -y \ 65 | ccache \ 66 | ninja-build \ 67 | cmake 68 | 69 | - name: Install the osquery-toolchain 70 | id: osquery_toolchain_installer 71 | run: | 72 | if [ ! -f "${{ env.DOWNLOADS_PATH }}/osquery-toolchain-${{ inputs.osquery_toolchain_version }}.tar.xz" ] ; then 73 | curl \ 74 | -L \ 75 | "https://github.com/osquery/osquery-toolchain/releases/download/${{ inputs.osquery_toolchain_version }}/osquery-toolchain-${{ inputs.osquery_toolchain_version }}-x86_64.tar.xz" \ 76 | -o \ 77 | "${{ env.DOWNLOADS_PATH }}/osquery-toolchain-${{ inputs.osquery_toolchain_version }}.tar.xz" 78 | fi 79 | 80 | tar \ 81 | xf \ 82 | "${{ env.DOWNLOADS_PATH }}/osquery-toolchain-${{ inputs.osquery_toolchain_version }}.tar.xz" 83 | 84 | mv \ 85 | osquery-toolchain \ 86 | ${{ env.TOOLCHAIN_PATH }} 87 | 88 | - name: Clone the ebpfault source code 89 | uses: actions/checkout@v3 90 | with: 91 | repository: trailofbits/ebpfault 92 | path: ebpfault 93 | fetch-depth: 0 94 | 95 | - name: Initialize the submodules 96 | working-directory: ${{ github.workspace }}/ebpfault 97 | run: | 98 | git submodule update --init --recursive 99 | 100 | - name: Configure the project 101 | run: | 102 | cmake \ 103 | -G Ninja \ 104 | -S ebpfault \ 105 | -B build-ebpfault \ 106 | -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \ 107 | -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} \ 108 | -DEBPFAULT_ENABLE_INSTALL=true 109 | 110 | - name: Build the project 111 | run: | 112 | cmake \ 113 | --build build-ebpfault \ 114 | -v 115 | 116 | - name: Install the project 117 | run: | 118 | cmake \ 119 | --build build-ebpfault \ 120 | --target install 121 | 122 | - name: Configure the packaging project 123 | run: | 124 | cmake \ 125 | -G Ninja \ 126 | -S ebpfault/package_generator \ 127 | -B build-ebpfault/package \ 128 | -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} \ 129 | -DEBPFAULT_INSTALL_PATH=${{ env.DESTDIR }} 130 | 131 | - name: Build the package 132 | run: | 133 | cmake \ 134 | --build build-ebpfault/package \ 135 | --target package \ 136 | -v 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .cache 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libraries/ebpf-common/src"] 2 | path = libraries/ebpf-common/src 3 | url = https://github.com/trailofbits/ebpf-common 4 | [submodule "libraries/rapidjson/src"] 5 | path = libraries/rapidjson/src 6 | url = https://github.com/Tencent/rapidjson 7 | [submodule "libraries/CLI11/src"] 8 | path = libraries/CLI11/src 9 | url = https://github.com/CLIUtils/CLI11 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | cmake_minimum_required(VERSION 3.21.4) 10 | 11 | project("ebpfault") 12 | 13 | include("cmake/options.cmake") 14 | include("cmake/settings.cmake") 15 | 16 | add_executable(ebpfault 17 | src/main.cpp 18 | 19 | src/faultinjector.h 20 | src/faultinjector.cpp 21 | 22 | src/utils.h 23 | src/utils.cpp 24 | 25 | src/configuration.h 26 | src/configuration.cpp 27 | ) 28 | 29 | add_subdirectory("libraries") 30 | 31 | target_link_libraries(ebpfault PRIVATE 32 | ebpfault_cxx_settings 33 | 34 | thirdparty_rapidjson 35 | thirdparty_cli11 36 | 37 | ebpf 38 | error 39 | utils 40 | ) 41 | 42 | if(EBPFAULT_ENABLE_INSTALL) 43 | install(TARGETS ebpfault DESTINATION "bin") 44 | endif() 45 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @alessandrogario 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ebpfault 2 | 3 | This tool is a syscall fault injector built on top of eBPF that has no requirements on the target machine other than a kernel version good enough to support the required features. 4 | 5 | ## Usage 6 | 7 | ### Sample configuration 8 | 9 | The configuration supports both integers and errno value names. 10 | 11 | ```json 12 | { 13 | "fault_injectors": [ 14 | { 15 | "syscall_name": "fchmodat", 16 | 17 | "error_list": [ 18 | { 19 | "exit_code": "-ENOENT", 20 | "probability": 50 21 | }, 22 | 23 | { 24 | "exit_code": -100, 25 | "probability": 30 26 | } 27 | ] 28 | }, 29 | 30 | { 31 | "syscall_name": "openat", 32 | 33 | "error_list": [ 34 | { 35 | "exit_code": "-ENOENT", 36 | "probability": 50 37 | } 38 | ] 39 | } 40 | ] 41 | } 42 | ``` 43 | 44 | ### Against a new process 45 | 46 | ``` 47 | ebpfault --config /path/to/config.json --exec /path/to/program arg1 arg2 48 | ``` 49 | 50 | ### Against one or more running processes 51 | 52 | ``` 53 | ebpfault --config /path/to/config.json --pid_list pid1,pid2,pid3,... 54 | ``` 55 | 56 | ### System wide, excluding one or more running processes 57 | 58 | ``` 59 | ebpfault --config /path/to/config.json --except-pid-list --pid_list pid1,pid2,pid3,... 60 | ``` 61 | 62 | ## Building 63 | 64 | ### Prerequisites 65 | * A recent Clang/LLVM installation (9.0 or better), compiled with BPF support 66 | * A recent libc++ or stdc++ library, supporting C++17 67 | * CMake >= 3.21.4. A pre-built binary can be downloaded from the [CMake's download page](https://cmake.org/download/). 68 | * :warning: Linux kernel >= 5.x (tested on Ubuntu 19.10) with the `CONFIG_BPF_KPROBE_OVERRIDE` option enabled 69 | 70 | ### Building 71 | 72 | 1. Download the osquery-toolchain from the following page: https://github.com/osquery/osquery-toolchain 73 | 2. Extract the osquery-toolchain and set the `TOOLCHAIN_PATH` environment variable to its location 74 | 3. Obtain the source code: `git clone --recursive https://github.com/trailofbits/ebpfault` 75 | 4. In case the `--recursive` flag was not provided, run `git submodule update --init --recursive` 76 | 7. Configure the project: `cmake -S ebpfault -B build-ebpfault -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DEBPFAULT_ENABLE_INSTALL=true` 77 | 8. Build the project: `cmake --build build-ebpfault` 78 | -------------------------------------------------------------------------------- /cmake/options.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | option(EBPFAULT_ENABLE_INSTALL "Set to ON to enable the install target") 10 | -------------------------------------------------------------------------------- /cmake/settings.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | add_library("ebpfault_common_settings" INTERFACE) 10 | target_compile_options("ebpfault_common_settings" INTERFACE 11 | -Wall 12 | -pedantic 13 | -Wconversion 14 | -Wunused 15 | -Wshadow 16 | -fvisibility=hidden 17 | -Werror 18 | -Wno-deprecated-declarations 19 | ) 20 | 21 | set_target_properties("ebpfault_common_settings" PROPERTIES 22 | INTERFACE_POSITION_INDEPENDENT_CODE 23 | true 24 | ) 25 | 26 | if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") 27 | target_compile_options("ebpfault_common_settings" INTERFACE 28 | -O0 29 | ) 30 | 31 | target_compile_definitions("ebpfault_common_settings" INTERFACE 32 | DEBUG 33 | ) 34 | 35 | else() 36 | target_compile_options("ebpfault_common_settings" INTERFACE 37 | -O2 38 | ) 39 | 40 | target_compile_definitions("ebpfault_common_settings" INTERFACE 41 | NDEBUG 42 | ) 43 | endif() 44 | 45 | if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") 46 | target_compile_options("ebpfault_common_settings" INTERFACE 47 | -g3 48 | ) 49 | else() 50 | target_compile_options("ebpfault_common_settings" INTERFACE 51 | -g0 52 | ) 53 | endif() 54 | 55 | add_library("ebpfault_cxx_settings" INTERFACE) 56 | target_compile_features("ebpfault_cxx_settings" INTERFACE 57 | cxx_std_17 58 | ) 59 | 60 | target_link_libraries("ebpfault_cxx_settings" INTERFACE 61 | "ebpfault_common_settings" 62 | ) 63 | -------------------------------------------------------------------------------- /cmake/toolchain.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | if(NOT DEFINED ENV{TOOLCHAIN_PATH}) 10 | message(FATAL_ERROR "The toolchain path is not set. Please set the 'TOOLCHAIN_PATH' environment variable and try again") 11 | endif() 12 | 13 | set(TOOLCHAIN_PATH "$ENV{TOOLCHAIN_PATH}") 14 | set(CMAKE_SYSROOT "${TOOLCHAIN_PATH}") 15 | 16 | set(CMAKE_C_COMPILER "${TOOLCHAIN_PATH}/usr/bin/clang") 17 | set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "${TOOLCHAIN_PATH}/usr/lib ${TOOLCHAIN_PATH}/lib") 18 | 19 | set(CMAKE_CXX_COMPILER "${TOOLCHAIN_PATH}/usr/bin/clang++") 20 | set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++") 21 | 22 | set(CMAKE_EXE_LINKER_FLAGS_INIT "${TOOLCHAIN_PATH}/usr/lib/libc++abi.a ${TOOLCHAIN_PATH}/usr/lib/librt.a") 23 | 24 | set(CMAKE_LINK_SEARCH_START_STATIC true) 25 | set(CMAKE_LINK_SEARCH_END_STATIC true) 26 | 27 | set(CMAKE_CXX_LINK_NO_PIE_SUPPORTED true) 28 | set(CMAKE_CXX_LINK_PIE_SUPPORTED true) 29 | 30 | set(CMAKE_C_LINK_NO_PIE_SUPPORTED true) 31 | set(CMAKE_C_LINK_PIE_SUPPORTED true) 32 | -------------------------------------------------------------------------------- /cmake/version.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | set(EBPFAULT_VERSION "1.1.2") 10 | 11 | -------------------------------------------------------------------------------- /libraries/CLI11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | set(library_root "${CMAKE_CURRENT_SOURCE_DIR}/src") 10 | 11 | add_library(thirdparty_cli11 INTERFACE) 12 | target_include_directories(thirdparty_cli11 SYSTEM INTERFACE 13 | "${library_root}/include" 14 | ) 15 | 16 | target_link_libraries(thirdparty_cli11 INTERFACE 17 | ebpfault_cxx_settings 18 | ) 19 | -------------------------------------------------------------------------------- /libraries/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | add_subdirectory("ebpf-common") 10 | add_subdirectory("rapidjson") 11 | add_subdirectory("CLI11") 12 | -------------------------------------------------------------------------------- /libraries/ebpf-common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | add_subdirectory("src") 10 | -------------------------------------------------------------------------------- /libraries/rapidjson/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | set(library_root "${CMAKE_CURRENT_SOURCE_DIR}/src") 10 | 11 | add_library(thirdparty_rapidjson INTERFACE) 12 | target_include_directories(thirdparty_rapidjson SYSTEM INTERFACE 13 | "${library_root}/include" 14 | ) 15 | 16 | target_link_libraries(thirdparty_rapidjson INTERFACE 17 | ebpfault_cxx_settings 18 | ) 19 | 20 | target_compile_definitions(thirdparty_rapidjson INTERFACE 21 | RAPIDJSON_HAS_STDSTRING=1 22 | ) 23 | -------------------------------------------------------------------------------- /package_generator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | cmake_minimum_required(VERSION 3.14) 10 | project("ebpfault") 11 | 12 | include("../cmake/version.cmake") 13 | 14 | set(EBPFAULT_INSTALL_PATH "" CACHE STRING "This is the path where ebpfault has been installed") 15 | set(PACKAGE_VERSION 1) 16 | 17 | string(REPLACE "." ";" EBPFAULT_VERSION_COMPONENTS "${EBPFAULT_VERSION}") 18 | list(GET EBPFAULT_VERSION_COMPONENTS 0 CPACK_PACKAGE_VERSION_MAJOR) 19 | list(GET EBPFAULT_VERSION_COMPONENTS 1 CPACK_PACKAGE_VERSION_MINOR) 20 | list(GET EBPFAULT_VERSION_COMPONENTS 2 CPACK_PACKAGE_VERSION_PATCH) 21 | 22 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "ebpfault is an eBPF-based syscall fault injection utility.") 23 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/description.txt") 24 | set(CPACK_PACKAGE_NAME "ebpfault") 25 | set(CPACK_PACKAGE_VENDOR "Trail of Bits") 26 | set(CPACK_PACKAGE_CONTACT "info@trailofbits.com") 27 | set(CPACK_PACKAGE_HOMEPAGE_URL "https://www.trailofbits.com") 28 | set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${EBPFAULT_VERSION}-${PACKAGE_VERSION}.${CMAKE_SYSTEM_PROCESSOR}") 29 | set(CPACK_PACKAGE_RELOCATABLE ON) 30 | 31 | set(CPACK_DEBIAN_PACKAGE_RELEASE "${PACKAGE_VERSION}") 32 | set(CPACK_DEBIAN_PACKAGE_PRIORITY "extra") 33 | set(CPACK_DEBIAN_PACKAGE_SECTION "default") 34 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>=2.12)") 35 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "${CPACK_PACKAGE_HOMEPAGE_URL}") 36 | 37 | set(CPACK_RPM_PACKAGE_RELEASE_DIST "${PACKAGE_VERSION}") 38 | set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") 39 | set(CPACK_RPM_PACKAGE_GROUP "default") 40 | set(CPACK_RPM_PACKAGE_REQUIRES "glibc >= 2.12") 41 | 42 | function(ebpfaultPackageGenerator) 43 | if("${EBPFAULT_INSTALL_PATH}" STREQUAL "") 44 | message(FATAL_ERROR "The EBPFAULT_INSTALL_PATH parameter is mandatory") 45 | endif() 46 | 47 | set(executable_path "${EBPFAULT_INSTALL_PATH}/usr/local/bin/ebpfault") 48 | 49 | if(NOT EXISTS "${executable_path}") 50 | message(FATAL_ERROR "The provided EBPFAULT_INSTALL_PATH parameter is not valid") 51 | endif() 52 | 53 | install( 54 | FILES 55 | "${executable_path}" 56 | 57 | DESTINATION 58 | "bin" 59 | 60 | PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE 61 | GROUP_READ GROUP_WRITE GROUP_EXECUTE 62 | WORLD_READ WORLD_EXECUTE 63 | ) 64 | endfunction() 65 | 66 | set(CPACK_GENERATOR "TGZ") 67 | 68 | unset(rpm_executable_path CACHE) 69 | find_program(rpm_executable_path "rpm") 70 | if("${rpm_executable_path}" STREQUAL "rpm_executable_path-NOTFOUND") 71 | message(WARNING "ebpfault - The RPM package generator requires the 'rpm' tool") 72 | else() 73 | list(APPEND CPACK_GENERATOR "RPM") 74 | message(STATUS "ebpfault - The RPM generator has been enabled") 75 | endif() 76 | 77 | unset(dpkg_executable_path CACHE) 78 | find_program(dpkg_executable_path "dpkg") 79 | if("${dpkg_executable_path}" STREQUAL "dpkg_executable_path-NOTFOUND") 80 | message(WARNING "ebpfault - The DEB package generator requires the 'dpkg' tool") 81 | else() 82 | list(APPEND CPACK_GENERATOR "DEB") 83 | message(STATUS "ebpfault - The DEB generator has been enabled") 84 | endif() 85 | 86 | include(CPack) 87 | 88 | ebpfaultPackageGenerator() 89 | -------------------------------------------------------------------------------- /package_generator/description.txt: -------------------------------------------------------------------------------- 1 | ebpfault is a software testing utility that is capable of injecting faults in syscalls by using eBPF. It supports both system-wide and per-process faults, and can either attach to existing processes by using a PID list or start the program directly. -------------------------------------------------------------------------------- /src/configuration.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include "configuration.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | 20 | namespace tob::ebpfault { 21 | namespace { 22 | extern const std::unordered_map kErrnoToValue; 23 | 24 | StringErrorOr parseExitCodeValue(const std::string &buffer) { 25 | if (buffer.empty()) { 26 | return StringError::create("Error name is empty"); 27 | } 28 | 29 | const char *buffer_ptr{nullptr}; 30 | bool negative_value{false}; 31 | 32 | if (buffer.at(0) == '-') { 33 | buffer_ptr = buffer.c_str() + 1U; 34 | negative_value = true; 35 | 36 | } else { 37 | buffer_ptr = buffer.c_str(); 38 | } 39 | 40 | auto it = kErrnoToValue.find(buffer_ptr); 41 | if (it == kErrnoToValue.end()) { 42 | return StringError::create("Invalid errno value provided: " + buffer); 43 | } 44 | 45 | auto value = it->second; 46 | if (negative_value) { 47 | value *= -1UL; 48 | } 49 | 50 | return value; 51 | } 52 | } // namespace 53 | 54 | struct Configuration::PrivateData final { 55 | std::vector syscall_fault_list; 56 | }; 57 | 58 | StringErrorOr 59 | Configuration::create(const std::string &path) { 60 | try { 61 | return Ref(new Configuration(path)); 62 | 63 | } catch (const std::bad_alloc &) { 64 | return StringError::create("Memory allocation failure"); 65 | 66 | } catch (const StringError &error) { 67 | return error; 68 | } 69 | } 70 | 71 | Configuration::~Configuration() {} 72 | 73 | Configuration::IteratorType Configuration::begin() noexcept { 74 | return d->syscall_fault_list.begin(); 75 | } 76 | 77 | Configuration::IteratorType Configuration::end() noexcept { 78 | return d->syscall_fault_list.end(); 79 | } 80 | 81 | Configuration::ConstIteratorType Configuration::begin() const noexcept { 82 | return d->syscall_fault_list.begin(); 83 | } 84 | 85 | Configuration::ConstIteratorType Configuration::end() const noexcept { 86 | return d->syscall_fault_list.end(); 87 | } 88 | 89 | Configuration::ConstIteratorType Configuration::cbegin() const noexcept { 90 | return d->syscall_fault_list.cbegin(); 91 | } 92 | 93 | Configuration::ConstIteratorType Configuration::cend() const noexcept { 94 | return d->syscall_fault_list.cend(); 95 | } 96 | 97 | Configuration::Configuration(const std::string &path) : d(new PrivateData) { 98 | std::string configuration; 99 | 100 | { 101 | std::fstream input_file(path); 102 | if (!input_file) { 103 | throw StringError::create( 104 | "Failed to open the following configuration file: " + path); 105 | } 106 | 107 | std::stringstream buffer; 108 | buffer << input_file.rdbuf(); 109 | if (!input_file) { 110 | throw StringError::create( 111 | "Failed to read the following configuration file: " + path); 112 | } 113 | 114 | configuration = buffer.str(); 115 | } 116 | 117 | rapidjson::Document document; 118 | document.Parse(configuration); 119 | 120 | if (document.HasParseError() || !document.IsObject()) { 121 | throw StringError::create("Invalid configuration format"); 122 | } 123 | 124 | if (!document.HasMember("fault_injectors") || 125 | !document["fault_injectors"].IsArray()) { 126 | throw StringError::create(""); 127 | } 128 | 129 | const auto &fault_injector_list = document["fault_injectors"].GetArray(); 130 | 131 | std::vector syscall_fault_list; 132 | 133 | for (const auto &fault_injector : fault_injector_list) { 134 | if (!fault_injector.HasMember("syscall_name") || 135 | !fault_injector["syscall_name"].IsString()) { 136 | throw StringError::create(""); 137 | } 138 | 139 | const auto &syscall_name = fault_injector["syscall_name"].GetString(); 140 | 141 | if (!fault_injector.HasMember("error_list") || 142 | !fault_injector["error_list"].IsArray()) { 143 | throw StringError::create(""); 144 | } 145 | 146 | const auto &error_list = fault_injector["error_list"].GetArray(); 147 | 148 | SyscallFault syscall_fault = {}; 149 | syscall_fault.name = syscall_name; 150 | 151 | std::size_t probability_sum = 0U; 152 | 153 | for (const auto &error : error_list) { 154 | if (!error.IsObject()) { 155 | throw StringError::create(""); 156 | } 157 | 158 | if (!error.HasMember("exit_code")) { 159 | throw StringError::create(""); 160 | } 161 | 162 | const auto &exit_code = error["exit_code"]; 163 | std::uint64_t exit_code_value = {}; 164 | 165 | if (exit_code.IsString()) { 166 | const auto &exit_code_string = exit_code.GetString(); 167 | 168 | auto integer_value_exp = parseExitCodeValue(exit_code_string); 169 | if (!integer_value_exp.succeeded()) { 170 | throw integer_value_exp.error(); 171 | } 172 | 173 | exit_code_value = integer_value_exp.takeValue(); 174 | 175 | } else if (exit_code.IsNumber()) { 176 | exit_code_value = static_cast(exit_code.GetInt()); 177 | 178 | } else { 179 | throw StringError::create(""); 180 | } 181 | 182 | if (!error.HasMember("probability") || !error["probability"].IsNumber()) { 183 | throw StringError::create(""); 184 | } 185 | 186 | auto probability = 187 | static_cast(error["probability"].GetInt()); 188 | 189 | if (probability <= 0 || probability > 100) { 190 | throw StringError::create(""); 191 | } 192 | 193 | probability_sum += probability; 194 | 195 | if (probability_sum > 100) { 196 | throw StringError::create(""); 197 | } 198 | 199 | SyscallFault::Error syscall_error = {}; 200 | syscall_error.probability = static_cast(probability); 201 | syscall_error.exit_code = exit_code_value; 202 | 203 | syscall_fault.error_list.push_back(std::move(syscall_error)); 204 | } 205 | 206 | syscall_fault_list.push_back(std::move(syscall_fault)); 207 | } 208 | 209 | d->syscall_fault_list = std::move(syscall_fault_list); 210 | } 211 | 212 | namespace { 213 | const std::unordered_map kErrnoToValue = { 214 | {"EPERM", 1}, 215 | {"ENOENT", 2}, 216 | {"ESRCH", 3}, 217 | {"EINTR", 4}, 218 | {"EIO", 5}, 219 | {"ENXIO", 6}, 220 | {"E2BIG", 7}, 221 | {"ENOEXEC", 8}, 222 | {"EBADF", 9}, 223 | {"ECHILD", 10}, 224 | {"EAGAIN", 11}, 225 | {"ENOMEM", 12}, 226 | {"EACCES", 13}, 227 | {"EFAULT", 14}, 228 | {"ENOTBLK", 15}, 229 | {"EBUSY", 16}, 230 | {"EEXIST", 17}, 231 | {"EXDEV", 18}, 232 | {"ENODEV", 19}, 233 | {"ENOTDIR", 20}, 234 | {"EISDIR", 21}, 235 | {"EINVAL", 22}, 236 | {"ENFILE", 23}, 237 | {"EMFILE", 24}, 238 | {"ENOTTY", 25}, 239 | {"ETXTBSY", 26}, 240 | {"EFBIG", 27}, 241 | {"ENOSPC", 28}, 242 | {"ESPIPE", 29}, 243 | {"EROFS", 30}, 244 | {"EMLINK", 31}, 245 | {"EPIPE", 32}, 246 | {"EDOM", 33}, 247 | {"ERANGE", 34}, 248 | {"EDEADLK", 35}, 249 | {"ENAMETOOLONG", 36}, 250 | {"ENOLCK", 37}, 251 | {"ENOSYS", 38}, 252 | {"ENOTEMPTY", 39}, 253 | {"ELOOP", 40}, 254 | {"ENOMSG", 42}, 255 | {"EIDRM", 43}, 256 | {"ECHRNG", 44}, 257 | {"EL2NSYNC", 45}, 258 | {"EL3HLT", 46}, 259 | {"EL3RST", 47}, 260 | {"ELNRNG", 48}, 261 | {"EUNATCH", 49}, 262 | {"ENOCSI", 50}, 263 | {"EL2HLT", 51}, 264 | {"EBADE", 52}, 265 | {"EBADR", 53}, 266 | {"EXFULL", 54}, 267 | {"ENOANO", 55}, 268 | {"EBADRQC", 56}, 269 | {"EBADSLT", 57}, 270 | {"EBFONT", 59}, 271 | {"ENOSTR", 60}, 272 | {"ENODATA", 61}, 273 | {"ETIME", 62}, 274 | {"ENOSR", 63}, 275 | {"ENONET", 64}, 276 | {"ENOPKG", 65}, 277 | {"EREMOTE", 66}, 278 | {"ENOLINK", 67}, 279 | {"EADV", 68}, 280 | {"ESRMNT", 69}, 281 | {"ECOMM", 70}, 282 | {"EPROTO", 71}, 283 | {"EMULTIHOP", 72}, 284 | {"EDOTDOT", 73}, 285 | {"EBADMSG", 74}, 286 | {"EOVERFLOW", 75}, 287 | {"ENOTUNIQ", 76}, 288 | {"EBADFD", 77}, 289 | {"EREMCHG", 78}, 290 | {"ELIBACC", 79}, 291 | {"ELIBBAD", 80}, 292 | {"ELIBSCN", 81}, 293 | {"ELIBMAX", 82}, 294 | {"ELIBEXEC", 83}, 295 | {"EILSEQ", 84}, 296 | {"ERESTART", 85}, 297 | {"ESTRPIPE", 86}, 298 | {"EUSERS", 87}, 299 | {"ENOTSOCK", 88}, 300 | {"EDESTADDRREQ", 89}, 301 | {"EMSGSIZE", 90}, 302 | {"EPROTOTYPE", 91}, 303 | {"ENOPROTOOPT", 92}, 304 | {"EPROTONOSUPPORT", 93}, 305 | {"ESOCKTNOSUPPORT", 94}, 306 | {"EOPNOTSUPP", 95}, 307 | {"EPFNOSUPPORT", 96}, 308 | {"EAFNOSUPPORT", 97}, 309 | {"EADDRINUSE", 98}, 310 | {"EADDRNOTAVAIL", 99}, 311 | {"ENETDOWN", 100}, 312 | {"ENETUNREACH", 101}, 313 | {"ENETRESET", 102}, 314 | {"ECONNABORTED", 103}, 315 | {"ECONNRESET", 104}, 316 | {"ENOBUFS", 105}, 317 | {"EISCONN", 106}, 318 | {"ENOTCONN", 107}, 319 | {"ESHUTDOWN", 108}, 320 | {"ETOOMANYREFS", 109}, 321 | {"ETIMEDOUT", 110}, 322 | {"ECONNREFUSED", 111}, 323 | {"EHOSTDOWN", 112}, 324 | {"EHOSTUNREACH", 113}, 325 | {"EALREADY", 114}, 326 | {"EINPROGRESS", 115}, 327 | {"ESTALE", 116}, 328 | {"EUCLEAN", 117}, 329 | {"ENOTNAM", 118}, 330 | {"ENAVAIL", 119}, 331 | {"EISNAM", 120}, 332 | {"EREMOTEIO", 121}, 333 | {"EDQUOT", 122}, 334 | {"ENOMEDIUM", 123}, 335 | {"EMEDIUMTYPE", 124}, 336 | {"ECANCELED", 125}, 337 | {"ENOKEY", 126}, 338 | {"EKEYEXPIRED", 127}, 339 | {"EKEYREVOKED", 128}, 340 | {"EKEYREJECTED", 129}, 341 | {"EOWNERDEAD", 130}, 342 | {"ENOTRECOVERABLE", 131}, 343 | {"ERFKILL", 132}, 344 | {"EHWPOISON", 133}}; 345 | } 346 | } // namespace tob::ebpfault 347 | -------------------------------------------------------------------------------- /src/configuration.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace tob::ebpfault { 18 | class Configuration final { 19 | public: 20 | struct SyscallFault final { 21 | struct Error final { 22 | std::uint64_t exit_code; 23 | std::uint8_t probability; 24 | }; 25 | 26 | std::string name; 27 | std::vector error_list; 28 | }; 29 | 30 | using IteratorType = std::vector::iterator; 31 | using ConstIteratorType = std::vector::const_iterator; 32 | 33 | using Ref = std::unique_ptr; 34 | static StringErrorOr create(const std::string &path); 35 | 36 | ~Configuration(); 37 | 38 | IteratorType begin() noexcept; 39 | IteratorType end() noexcept; 40 | 41 | ConstIteratorType begin() const noexcept; 42 | ConstIteratorType end() const noexcept; 43 | 44 | ConstIteratorType cbegin() const noexcept; 45 | ConstIteratorType cend() const noexcept; 46 | 47 | Configuration(const Configuration &) = delete; 48 | Configuration &operator=(const Configuration &) = delete; 49 | 50 | private: 51 | struct PrivateData; 52 | std::unique_ptr d; 53 | 54 | Configuration(const std::string &path); 55 | }; 56 | } // namespace tob::ebpfault 57 | -------------------------------------------------------------------------------- /src/faultinjector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include "faultinjector.h" 10 | #include "utils.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace tob::ebpfault { 22 | 23 | namespace { 24 | 25 | // 26 | // Register list. Make sure that the order is the same as the 27 | // struct pt_regs definition! 28 | // 29 | 30 | const std::vector kRegisterList { 31 | #if __x86_64__ 32 | "r15", "r14", "r13", "r12", "rbp", "rbx", "r11", "r10", "r9", "r8", "rax", 33 | "rcx", "rdx", "rsi", "rdi", "orig_rax", "rip", "cs", "eflags", "rsp", "ss" 34 | 35 | #elif __arm__ || __aarch64__ 36 | "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", 37 | "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", 38 | "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "sp", "pc", 39 | "pstate" 40 | 41 | #else 42 | #error Unsupported architecture 43 | #endif 44 | }; 45 | 46 | } // namespace 47 | 48 | struct FaultInjector::PrivateData final { 49 | PrivateData(ebpf::PerfEventArray &perf_event_array_) 50 | : perf_event_array(perf_event_array_) {} 51 | 52 | ebpf::PerfEventArray &perf_event_array; 53 | std::uint32_t event_data_size{0U}; 54 | 55 | ProcessIDFilter filter; 56 | Configuration::SyscallFault config; 57 | 58 | llvm::LLVMContext context; 59 | std::unique_ptr module; 60 | 61 | ebpf::IEvent::Ref kprobe_event; 62 | utils::UniqueFd program_fd; 63 | }; 64 | 65 | StringErrorOr 66 | FaultInjector::create(ebpf::PerfEventArray &perf_event_array, 67 | const Configuration::SyscallFault &config, 68 | const ProcessIDFilter &filter) { 69 | 70 | try { 71 | return Ref(new FaultInjector(perf_event_array, config, filter)); 72 | 73 | } catch (const std::bad_alloc &) { 74 | return StringError::create("Memory allocation failure"); 75 | 76 | } catch (const StringError &error) { 77 | return error; 78 | } 79 | } 80 | 81 | FaultInjector::~FaultInjector() {} 82 | 83 | std::uint64_t FaultInjector::eventIdentifier() const { 84 | return static_cast(d->kprobe_event->fd()); 85 | } 86 | 87 | std::vector 88 | FaultInjector::parseEventData(utils::BufferReader &buffer_reader, 89 | ebpf::PerfEventArray::BufferList &&buffer_list) { 90 | 91 | std::vector event_data_list; 92 | 93 | for (const auto &buffer : buffer_list) { 94 | buffer_reader.reset(buffer); 95 | buffer_reader.skipBytes(tob::ebpf::kPerfEventHeaderSize + 96 | sizeof(std::uint32_t)); 97 | 98 | EventData event_data{}; 99 | 100 | try { 101 | event_data.timestamp = buffer_reader.u64(); 102 | event_data.event_id = buffer_reader.u64(); 103 | event_data.process_id = buffer_reader.u32(); 104 | event_data.thread_id = buffer_reader.u32(); 105 | event_data.injected_error = buffer_reader.u64(); 106 | 107 | for (const auto ®ister_name : kRegisterList) { 108 | auto register_value = buffer_reader.u64(); 109 | event_data.register_map.insert({register_name, register_value}); 110 | } 111 | 112 | event_data_list.push_back(std::move(event_data)); 113 | 114 | } catch (...) { 115 | std::cerr 116 | << "Failed to read the current event. Skipping to the next one...\n"; 117 | } 118 | 119 | event_data = {}; 120 | } 121 | 122 | return event_data_list; 123 | } 124 | 125 | FaultInjector::FaultInjector(ebpf::PerfEventArray &perf_event_array, 126 | const Configuration::SyscallFault &config, 127 | const ProcessIDFilter &filter) 128 | : d(new PrivateData(perf_event_array)) { 129 | 130 | d->config = config; 131 | d->filter = filter; 132 | 133 | // Make sure the fault settings do not go above 100% probability 134 | std::uint8_t sum{0U}; 135 | 136 | for (const auto &fault : d->config.error_list) { 137 | sum += fault.probability; 138 | } 139 | 140 | if (sum == 0U || sum > 100U) { 141 | throw StringError::create("Fault configuration exceeds 100% probability"); 142 | } 143 | 144 | std::string arch; 145 | #if __x86_64__ 146 | arch = "x64"; 147 | #elif __arm__ || __aarch64__ 148 | arch = "arm64"; 149 | #else 150 | throw StringError::create( 151 | "The current architecture is not valid. Supported: arm or x86_64."); 152 | #endif 153 | 154 | // Create the event first, so we know whether the given system call exists or 155 | // not 156 | auto syscall_name = "__" + arch + "_sys_" + d->config.name; 157 | 158 | auto kprobe_event_exp = 159 | ebpf::IEvent::createKprobe(syscall_name, false, false); 160 | 161 | if (!kprobe_event_exp.succeeded()) { 162 | throw kprobe_event_exp.error(); 163 | } 164 | 165 | d->kprobe_event = kprobe_event_exp.takeValue(); 166 | 167 | // Generate the program, compile it, then load it 168 | auto program_status = generateBPFProgram(); 169 | if (program_status.failed()) { 170 | throw program_status.error(); 171 | } 172 | 173 | program_status = loadBPFProgram(); 174 | if (program_status.failed()) { 175 | throw program_status.error(); 176 | } 177 | } 178 | 179 | SuccessOrStringError FaultInjector::generateBPFProgram() { 180 | d->module = 181 | ebpf::createLLVMModule(d->context, d->config.name + "_FaultInjector"); 182 | 183 | // Get the pt_regs structure for our processor 184 | auto pt_regs_struct_exp = 185 | ebpf::getPtRegsStructure(*d->module.get(), "pt_regs"); 186 | 187 | if (!pt_regs_struct_exp.succeeded()) { 188 | return pt_regs_struct_exp.error(); 189 | } 190 | 191 | auto pt_regs_struct = pt_regs_struct_exp.takeValue(); 192 | 193 | // Generate the event data structure (timestamp + event_id + (pid/tgid) + 194 | // injected error + pt_regs) 195 | std::vector event_data_type_list( 196 | 4U, llvm::Type::getInt64Ty(d->context)); 197 | 198 | event_data_type_list.push_back(pt_regs_struct); 199 | 200 | auto event_data_struct = 201 | llvm::StructType::create(event_data_type_list, "EventData", true); 202 | 203 | if (event_data_struct == nullptr) { 204 | return StringError::create( 205 | "Failed to create the event data structure type"); 206 | } 207 | 208 | d->event_data_size = static_cast( 209 | ebpf::getTypeSize(*d->module.get(), event_data_struct)); 210 | 211 | // Create the entry point function 212 | auto function_type = 213 | llvm::FunctionType::get(llvm::Type::getInt64Ty(d->context), 214 | {pt_regs_struct->getPointerTo()}, false); 215 | 216 | auto function = 217 | llvm::Function::Create(function_type, llvm::Function::ExternalLinkage, 218 | "on_" + d->config.name, d->module.get()); 219 | 220 | if (function == nullptr) { 221 | return StringError::create("Failed to create the syscall event function"); 222 | } 223 | 224 | auto section_name = d->config.name + "_section"; 225 | 226 | function->setSection(section_name); 227 | 228 | auto kprobe_context = function->arg_begin(); 229 | kprobe_context->setName("ctx"); 230 | 231 | auto entry_bb = llvm::BasicBlock::Create(d->context, "entry", function); 232 | 233 | // Allocate space for the event data 234 | llvm::IRBuilder<> builder(entry_bb); 235 | 236 | auto event_data = builder.CreateAlloca(event_data_struct); 237 | 238 | // Generate the PID filtering logic 239 | auto bpf_syscall_interface_exp = 240 | tob::ebpf::BPFSyscallInterface::create(builder); 241 | 242 | if (!bpf_syscall_interface_exp.succeeded()) { 243 | return bpf_syscall_interface_exp.error(); 244 | } 245 | 246 | auto bpf_syscall_interface = bpf_syscall_interface_exp.takeValue(); 247 | 248 | auto current_pid_tgid = bpf_syscall_interface->getCurrentPidTgid(); 249 | 250 | auto current_tgid = 251 | builder.CreateBinOp(llvm::Instruction::And, current_pid_tgid, 252 | builder.getInt64(0x00000000FFFFFFFFU)); 253 | 254 | for (const auto &process_id : d->filter.process_id_list) { 255 | auto process_id_value = 256 | builder.getInt64(static_cast(process_id)); 257 | 258 | auto check_pid_condition = 259 | builder.CreateICmpEQ(process_id_value, current_tgid); 260 | 261 | auto basic_block_name = "pid_condition_" + std::to_string(process_id); 262 | 263 | auto ignore_syscall_bb = llvm::BasicBlock::Create( 264 | d->context, basic_block_name + "ignore", function); 265 | 266 | basic_block_name = "pid_differs_" + std::to_string(process_id); 267 | 268 | auto fail_syscall_bb = llvm::BasicBlock::Create( 269 | d->context, basic_block_name + "fail", function); 270 | 271 | if (d->filter.type == ProcessIDFilter::Type::Except) { 272 | builder.CreateCondBr(check_pid_condition, ignore_syscall_bb, 273 | fail_syscall_bb); 274 | } else { 275 | builder.CreateCondBr(check_pid_condition, fail_syscall_bb, 276 | ignore_syscall_bb); 277 | } 278 | 279 | builder.SetInsertPoint(fail_syscall_bb); 280 | auto gen_status = generateFaultSelector( 281 | builder, *bpf_syscall_interface.get(), event_data, kprobe_context); 282 | 283 | if (gen_status.failed()) { 284 | return gen_status.error(); 285 | } 286 | 287 | builder.SetInsertPoint(ignore_syscall_bb); 288 | } 289 | 290 | // Terminate the function 291 | builder.CreateRet(builder.getInt64(0)); 292 | 293 | return {}; 294 | } 295 | 296 | SuccessOrStringError FaultInjector::generateFaultSelector( 297 | llvm::IRBuilder<> &builder, 298 | ebpf::BPFSyscallInterface &bpf_syscall_interface, llvm::Value *event_data, 299 | llvm::Value *pt_regs) { 300 | 301 | struct FaultRange final { 302 | std::uint8_t start{0U}; 303 | std::uint8_t end{0U}; 304 | std::uint64_t exit_code{0U}; 305 | bool succeed{false}; 306 | }; 307 | 308 | std::vector fault_range; 309 | std::uint8_t base{0U}; 310 | 311 | for (const auto &fault : d->config.error_list) { 312 | FaultRange range = {}; 313 | range.start = base; 314 | range.end = base + fault.probability; 315 | range.exit_code = fault.exit_code; 316 | range.succeed = false; 317 | 318 | fault_range.push_back(std::move(range)); 319 | base += fault.probability; 320 | } 321 | 322 | auto current_basic_block = builder.GetInsertBlock(); 323 | auto current_function = current_basic_block->getParent(); 324 | 325 | auto random_u32_value = bpf_syscall_interface.getPrandomU32(); 326 | 327 | random_u32_value = builder.CreateBinOp( 328 | llvm::Instruction::URem, random_u32_value, builder.getInt32(100)); 329 | 330 | std::size_t counter{0U}; 331 | 332 | for (const auto &fault : fault_range) { 333 | auto greater_or_equal_cond = 334 | builder.CreateICmpUGE(random_u32_value, builder.getInt32(fault.start)); 335 | 336 | greater_or_equal_cond = builder.CreateIntCast(greater_or_equal_cond, 337 | builder.getInt32Ty(), false); 338 | 339 | auto less_than_cond = builder.CreateICmpSLT( 340 | random_u32_value, builder.getInt32(fault.start + fault.end)); 341 | 342 | less_than_cond = 343 | builder.CreateIntCast(less_than_cond, builder.getInt32Ty(), false); 344 | 345 | auto condition_sum = builder.CreateBinOp( 346 | llvm::Instruction::Add, greater_or_equal_cond, less_than_cond); 347 | 348 | auto inside_range_cond = 349 | builder.CreateICmpEQ(condition_sum, builder.getInt32(2U)); 350 | 351 | auto fail_syscall_bb = llvm::BasicBlock::Create( 352 | d->context, "fail_syscall_with_" + std::to_string(fault.exit_code), 353 | current_function); 354 | 355 | auto continue_bb = llvm::BasicBlock::Create( 356 | d->context, "continue_" + std::to_string(++counter), current_function); 357 | 358 | builder.CreateCondBr(inside_range_cond, fail_syscall_bb, continue_bb); 359 | 360 | builder.SetInsertPoint(fail_syscall_bb); 361 | 362 | // Populate the event data structure (timestamp + event_id + (pid/tgid) + 363 | // injected error + pt_regs) 364 | 365 | // Timestamp 366 | auto timestamp = bpf_syscall_interface.ktimeGetNs(); 367 | 368 | auto event_data_field_ptr = builder.CreateGEP( 369 | event_data, {builder.getInt32(0), builder.getInt32(0U)}); 370 | 371 | builder.CreateStore(timestamp, event_data_field_ptr); 372 | 373 | // Event identifier 374 | event_data_field_ptr = builder.CreateGEP( 375 | event_data, {builder.getInt32(0), builder.getInt32(1U)}); 376 | 377 | builder.CreateStore(builder.getInt64(eventIdentifier()), 378 | event_data_field_ptr); 379 | 380 | // Thread id + process id 381 | auto pid_tgid = bpf_syscall_interface.getCurrentPidTgid(); 382 | 383 | event_data_field_ptr = builder.CreateGEP( 384 | event_data, {builder.getInt32(0), builder.getInt32(2U)}); 385 | 386 | builder.CreateStore(pid_tgid, event_data_field_ptr); 387 | 388 | // Injected error code 389 | event_data_field_ptr = builder.CreateGEP( 390 | event_data, {builder.getInt32(0), builder.getInt32(3U)}); 391 | 392 | builder.CreateStore(builder.getInt64(fault.exit_code), 393 | event_data_field_ptr); 394 | 395 | // pt_regs structure 396 | auto &module = *fail_syscall_bb->getModule(); 397 | 398 | auto pt_regs_type = module.getTypeByName("pt_regs"); 399 | auto pt_regs_member_count = 400 | static_cast(pt_regs_type->getNumElements()); 401 | 402 | for (std::uint32_t i = 0U; i < pt_regs_member_count; ++i) { 403 | auto reg_value_ptr = builder.CreateGEP( 404 | pt_regs, {builder.getInt32(0), builder.getInt32(i)}); 405 | 406 | auto reg_value = builder.CreateLoad(reg_value_ptr); 407 | 408 | auto reg_dest_ptr = builder.CreateGEP( 409 | event_data, 410 | {builder.getInt32(0), builder.getInt32(4U), builder.getInt32(i)}); 411 | 412 | builder.CreateStore(reg_value, reg_dest_ptr); 413 | } 414 | 415 | bpf_syscall_interface.perfEventOutput(pt_regs, d->perf_event_array.fd(), 416 | event_data, d->event_data_size); 417 | 418 | bpf_syscall_interface.overrideReturn(current_function->arg_begin(), 419 | fault.exit_code); 420 | 421 | builder.CreateRet(builder.getInt64(0)); 422 | 423 | builder.SetInsertPoint(continue_bb); 424 | } 425 | 426 | builder.CreateRet(builder.getInt64(0)); 427 | 428 | return {}; 429 | } 430 | 431 | SuccessOrStringError FaultInjector::loadBPFProgram() { 432 | // Compile the program 433 | auto &module = *d->module.get(); 434 | 435 | auto bpf_program_map_exp = ebpf::compileModule(module); 436 | if (!bpf_program_map_exp.succeeded()) { 437 | return bpf_program_map_exp.error(); 438 | } 439 | 440 | auto bpf_program_map = bpf_program_map_exp.takeValue(); 441 | 442 | auto section_name = d->config.name + "_section"; 443 | auto bpf_program_it = bpf_program_map.find(section_name); 444 | if (bpf_program_it == bpf_program_map.end()) { 445 | return StringError::create("Failed to compile the BPF function"); 446 | } 447 | 448 | auto &bpf_program = bpf_program_it->second; 449 | 450 | // Load the program 451 | auto program_fd_exp = ebpf::loadProgram(bpf_program, *d->kprobe_event.get()); 452 | if (!program_fd_exp.succeeded()) { 453 | throw program_fd_exp.error(); 454 | } 455 | 456 | d->program_fd = program_fd_exp.takeValue(); 457 | return {}; 458 | } 459 | } // namespace tob::ebpfault 460 | -------------------------------------------------------------------------------- /src/faultinjector.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "configuration.h" 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | #include 24 | 25 | namespace tob::ebpfault { 26 | class FaultInjector final { 27 | public: 28 | struct ProcessIDFilter final { 29 | enum class Type { Matching, Except }; 30 | 31 | Type type{Type::Matching}; 32 | std::vector process_id_list; 33 | }; 34 | 35 | struct EventData final { 36 | std::uint64_t timestamp; 37 | std::uint64_t event_id; 38 | std::uint32_t process_id; 39 | std::uint32_t thread_id; 40 | std::uint64_t injected_error; 41 | std::unordered_map register_map; 42 | }; 43 | 44 | using Ref = std::unique_ptr; 45 | static StringErrorOr create(ebpf::PerfEventArray &perf_event_array, 46 | const Configuration::SyscallFault &config, 47 | const ProcessIDFilter &filter); 48 | 49 | ~FaultInjector(); 50 | 51 | std::uint64_t eventIdentifier() const; 52 | 53 | FaultInjector(const FaultInjector &) = delete; 54 | FaultInjector &operator=(const FaultInjector &) = delete; 55 | 56 | static std::vector 57 | parseEventData(utils::BufferReader &buffer_reader, 58 | ebpf::PerfEventArray::BufferList &&buffer_list); 59 | 60 | private: 61 | struct PrivateData; 62 | std::unique_ptr d; 63 | 64 | FaultInjector(ebpf::PerfEventArray &perf_event_array, 65 | const Configuration::SyscallFault &config, 66 | const ProcessIDFilter &filter); 67 | 68 | SuccessOrStringError generateBPFProgram(); 69 | 70 | SuccessOrStringError 71 | generateFaultSelector(llvm::IRBuilder<> &builder, 72 | ebpf::BPFSyscallInterface &bpf_syscall_interface, 73 | llvm::Value *event_data, llvm::Value *pt_regs); 74 | 75 | SuccessOrStringError loadBPFProgram(); 76 | }; 77 | } // namespace tob::ebpfault 78 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include "configuration.h" 10 | #include "faultinjector.h" 11 | #include "utils.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #define EBPFAULT_DUMP_REGISTER(register_name) \ 28 | do { \ 29 | std::cout << std::setfill(' ') << std::setw(10) << #register_name << " " \ 30 | << std::hex << std::setfill('0') << std::setw(16) \ 31 | << event_data.register_name << " "; \ 32 | } while (false) 33 | 34 | std::unordered_map event_name_map; 35 | 36 | void printEventData(tob::utils::BufferReader &buffer_reader, 37 | tob::ebpf::PerfEventArray::BufferList buffer_list) { 38 | 39 | auto event_data_list = tob::ebpfault::FaultInjector::parseEventData( 40 | buffer_reader, std::move(buffer_list)); 41 | 42 | for (const auto &event_data : event_data_list) { 43 | 44 | std::string event_name; 45 | auto event_name_it = event_name_map.find(event_data.event_id); 46 | if (event_name_it == event_name_map.end()) { 47 | event_name = std::to_string(event_data.event_id); 48 | } else { 49 | event_name = event_name_it->second; 50 | } 51 | 52 | std::cout << "timestamp: " << std::dec << event_data.timestamp 53 | << " syscall: " << event_name 54 | << " process_id: " << event_data.process_id 55 | << " thread_id: " << event_data.thread_id << " injected_error: " 56 | << tob::ebpfault::describeFaultValue(event_data.injected_error) 57 | << "\n"; 58 | 59 | std::size_t index{1}; 60 | for (const auto ®ister_map_p : event_data.register_map) { 61 | const auto ®ister_name = register_map_p.first; 62 | const auto ®ister_value = register_map_p.second; 63 | 64 | std::cout << std::setfill(' ') << std::setw(10) << register_name << " " 65 | << std::hex << std::setfill('0') << std::setw(16) 66 | << register_value << " "; 67 | 68 | if ((index % 3) == 0) { 69 | std::cout << "\n"; 70 | } 71 | 72 | ++index; 73 | } 74 | } 75 | } 76 | 77 | int main(int argc, char *argv[], char *envp[]) { 78 | auto command_line_params_exp = tob::ebpfault::parseCommandLine(argc, argv); 79 | if (!command_line_params_exp.succeeded()) { 80 | std::cerr << command_line_params_exp.error().message() << "\n"; 81 | return 1; 82 | } 83 | 84 | auto command_line_params = command_line_params_exp.takeValue(); 85 | static_cast(command_line_params); 86 | 87 | if (!tob::ebpfault::configureRlimit()) { 88 | std::cerr << "Failed to set RLIMIT_MEMLOCK\n"; 89 | return 1; 90 | } 91 | 92 | auto configuration_exp = tob::ebpfault::Configuration::create( 93 | command_line_params.configuration_path); 94 | 95 | if (!configuration_exp.succeeded()) { 96 | std::cerr << configuration_exp.error().message() << "\n"; 97 | return 1; 98 | } 99 | 100 | auto configuration = configuration_exp.takeValue(); 101 | 102 | auto perf_event_array_exp = tob::ebpf::PerfEventArray::create(10); 103 | 104 | if (!perf_event_array_exp.succeeded()) { 105 | std::cerr << perf_event_array_exp.error().message() << "\n"; 106 | return 1; 107 | } 108 | 109 | auto perf_event_array = perf_event_array_exp.takeValue(); 110 | 111 | tob::ebpfault::FaultInjector::ProcessIDFilter pid_filter; 112 | sem_t *execve_semaphore{nullptr}; 113 | 114 | if (command_line_params.opt_pid_list.has_value()) { 115 | pid_filter.process_id_list = command_line_params.opt_pid_list.value(); 116 | 117 | if (command_line_params.except_pid_list) { 118 | pid_filter.type = 119 | tob::ebpfault::FaultInjector::ProcessIDFilter::Type::Except; 120 | } else { 121 | pid_filter.type = 122 | tob::ebpfault::FaultInjector::ProcessIDFilter::Type::Matching; 123 | } 124 | 125 | } else { 126 | auto semaphore_name = "ebpfault_" + std::to_string(getpid()); 127 | execve_semaphore = 128 | sem_open(semaphore_name.c_str(), O_CREAT | O_EXCL, 0600, 0); 129 | 130 | if (execve_semaphore == SEM_FAILED) { 131 | std::cerr << "Failed to create the semaphore\n"; 132 | return 1; 133 | } 134 | 135 | auto child_pid = fork(); 136 | 137 | if (child_pid == 0) { 138 | auto exec_command_line = 139 | command_line_params.opt_exec_command_line.value(); 140 | 141 | auto path = exec_command_line.at(0); 142 | 143 | std::vector exec_argv; 144 | for (auto ¶m : exec_command_line) { 145 | exec_argv.push_back(¶m[0]); 146 | } 147 | 148 | exec_argv.push_back(nullptr); 149 | 150 | execve_semaphore = sem_open(semaphore_name.c_str(), O_CREAT, 0600, 0); 151 | 152 | if (execve_semaphore == SEM_FAILED) { 153 | std::cerr << "Failed to create the semaphore\n"; 154 | return 1; 155 | } 156 | 157 | if (sem_wait(execve_semaphore) < 0) { 158 | std::cerr << "Semaphore wait has failed\n"; 159 | return 1; 160 | } 161 | 162 | execve(path.c_str(), exec_argv.data(), envp); 163 | throw std::runtime_error("exec has failed"); 164 | } 165 | 166 | pid_filter.process_id_list = {child_pid}; 167 | pid_filter.type = 168 | tob::ebpfault::FaultInjector::ProcessIDFilter::Type::Matching; 169 | } 170 | 171 | std::vector fault_injector_list; 172 | 173 | std::cout << "Generating fault injectors...\n\n"; 174 | 175 | for (const auto &config : *configuration) { 176 | std::cout << " > " << config.name << "\n"; 177 | std::cout << " Error list:\n"; 178 | 179 | for (const auto &error : config.error_list) { 180 | std::cout << " - " << std::setw(3) << std::setfill(' ') 181 | << static_cast(error.probability) << "% => " 182 | << tob::ebpfault::describeFaultValue(error.exit_code) << "\n"; 183 | } 184 | 185 | auto fault_injector_exp = tob::ebpfault::FaultInjector::create( 186 | *perf_event_array.get(), config, pid_filter); 187 | 188 | if (!fault_injector_exp.succeeded()) { 189 | std::cerr << fault_injector_exp.error().message() << "\n"; 190 | return 1; 191 | } 192 | 193 | auto fault_injector = fault_injector_exp.takeValue(); 194 | 195 | event_name_map.insert({fault_injector->eventIdentifier(), config.name}); 196 | 197 | fault_injector_list.push_back(std::move(fault_injector)); 198 | 199 | std::cout << "\n"; 200 | } 201 | 202 | if (execve_semaphore != nullptr) { 203 | if (sem_post(execve_semaphore) < 0) { 204 | std::cerr << "Failed to post to the semaphore\n"; 205 | return 1; 206 | } 207 | } 208 | 209 | auto buffer_reader_exp = tob::utils::BufferReader::create(); 210 | if (!buffer_reader_exp.succeeded()) { 211 | std::cerr << "Failed to create the buffer reader: " 212 | << buffer_reader_exp.error().message() << "\n"; 213 | return 1; 214 | } 215 | 216 | auto buffer_reader = buffer_reader_exp.takeValue(); 217 | 218 | for (;;) { 219 | std::size_t running_process_count = 0U; 220 | 221 | for (auto pid : pid_filter.process_id_list) { 222 | if (kill(pid, 0) == 0) { 223 | ++running_process_count; 224 | } 225 | } 226 | 227 | if (running_process_count == 0U) { 228 | std::cout << "All processes have been terminated\n"; 229 | break; 230 | } 231 | 232 | tob::ebpf::PerfEventArray::BufferList buffer_list; 233 | std::size_t read_error_count{}; 234 | std::size_t lost_event_count{}; 235 | 236 | if (!perf_event_array->read(buffer_list, read_error_count, 237 | lost_event_count)) { 238 | continue; 239 | } 240 | 241 | printEventData(*buffer_reader.get(), std::move(buffer_list)); 242 | } 243 | 244 | if (execve_semaphore != nullptr) { 245 | sem_close(execve_semaphore); 246 | } 247 | 248 | std::cout << "Exiting...\n"; 249 | return 0; 250 | } 251 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include "utils.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | namespace tob::ebpfault { 22 | namespace { 23 | extern const std::unordered_map kValueToErrorMap; 24 | } 25 | 26 | tob::StringErrorOr parseCommandLine(int argc, 27 | char *argv[]) { 28 | CLI::App application{"ebpfault - eBPF-based syscall fault injector"}; 29 | 30 | CommandLineParameters command_line_params; 31 | auto except_option = application.add_flag( 32 | "-x,--except-pid-list", command_line_params.except_pid_list, 33 | "Affect all processes except the ones in the PID filter"); 34 | 35 | application 36 | .add_option("-c,--config", command_line_params.configuration_path, 37 | "Fault configuration") 38 | ->required() 39 | ->check(CLI::ExistingFile); 40 | 41 | std::vector pid_list; 42 | application.add_option("-p,--pid_list", pid_list, "Process ID filter") 43 | ->delimiter(','); 44 | 45 | std::vector exec_command_line; 46 | auto exec_option = 47 | application 48 | .add_option("-e,--exec", exec_command_line, "Program to execute") 49 | ->delimiter(' '); 50 | 51 | exec_option->excludes(except_option); 52 | 53 | try { 54 | application.parse(argc, argv); 55 | 56 | if (pid_list.empty() == exec_command_line.empty()) { 57 | return StringError::create( 58 | "Missing required option: either -p/--pid_list or -e/--exec"); 59 | } 60 | 61 | if (!pid_list.empty()) { 62 | command_line_params.opt_pid_list = pid_list; 63 | } else { 64 | command_line_params.opt_exec_command_line = exec_command_line; 65 | } 66 | 67 | return command_line_params; 68 | 69 | } catch (const CLI::RuntimeError &e) { 70 | std::string message{"A runtime error has occurred"}; 71 | message += e.what(); 72 | 73 | return StringError::create(message); 74 | 75 | } catch (const CLI::ParseError &e) { 76 | std::stringstream message; 77 | application.exit(e, message, message); 78 | 79 | return StringError::create(message.str()); 80 | } 81 | } 82 | 83 | std::string describeFaultValue(std::uint64_t fault_value) { 84 | auto it = kValueToErrorMap.find(fault_value); 85 | if (it != kValueToErrorMap.end()) { 86 | return it->second; 87 | } 88 | 89 | auto modified_fault_value = fault_value * (-1ULL); 90 | 91 | it = kValueToErrorMap.find(modified_fault_value); 92 | if (it != kValueToErrorMap.end()) { 93 | return "-" + it->second; 94 | } 95 | 96 | std::stringstream stream; 97 | stream << fault_value << "(0x" << std::hex << fault_value << ")"; 98 | 99 | return stream.str(); 100 | } 101 | 102 | bool configureRlimit() { 103 | struct rlimit rl = {}; 104 | rl.rlim_max = RLIM_INFINITY; 105 | rl.rlim_cur = rl.rlim_max; 106 | 107 | auto error = setrlimit(RLIMIT_MEMLOCK, &rl); 108 | if (error != 0) { 109 | return false; 110 | } 111 | 112 | return true; 113 | } 114 | 115 | namespace { 116 | // clang-format off 117 | const std::unordered_map kValueToErrorMap = { 118 | { 1, "EPERM" }, 119 | { 2, "ENOENT" }, 120 | { 3, "ESRCH" }, 121 | { 4, "EINTR" }, 122 | { 5, "EIO" }, 123 | { 6, "ENXIO" }, 124 | { 7, "E2BIG" }, 125 | { 8, "ENOEXEC" }, 126 | { 9, "EBADF" }, 127 | { 10, "ECHILD" }, 128 | { 11, "EAGAIN" }, 129 | { 12, "ENOMEM" }, 130 | { 13, "EACCES" }, 131 | { 14, "EFAULT" }, 132 | { 15, "ENOTBLK" }, 133 | { 16, "EBUSY" }, 134 | { 17, "EEXIST" }, 135 | { 18, "EXDEV" }, 136 | { 19, "ENODEV" }, 137 | { 20, "ENOTDIR" }, 138 | { 21, "EISDIR" }, 139 | { 22, "EINVAL" }, 140 | { 23, "ENFILE" }, 141 | { 24, "EMFILE" }, 142 | { 25, "ENOTTY" }, 143 | { 26, "ETXTBSY" }, 144 | { 27, "EFBIG" }, 145 | { 28, "ENOSPC" }, 146 | { 29, "ESPIPE" }, 147 | { 30, "EROFS" }, 148 | { 31, "EMLINK" }, 149 | { 32, "EPIPE" }, 150 | { 33, "EDOM" }, 151 | { 34, "ERANGE" }, 152 | { 35, "EDEADLK" }, 153 | { 36, "ENAMETOOLONG" }, 154 | { 37, "ENOLCK" }, 155 | { 38, "ENOSYS" }, 156 | { 39, "ENOTEMPTY" }, 157 | { 40, "ELOOP" }, 158 | { 42, "ENOMSG" }, 159 | { 43, "EIDRM" }, 160 | { 44, "ECHRNG" }, 161 | { 45, "EL2NSYNC" }, 162 | { 46, "EL3HLT" }, 163 | { 47, "EL3RST" }, 164 | { 48, "ELNRNG" }, 165 | { 49, "EUNATCH" }, 166 | { 50, "ENOCSI" }, 167 | { 51, "EL2HLT" }, 168 | { 52, "EBADE" }, 169 | { 53, "EBADR" }, 170 | { 54, "EXFULL" }, 171 | { 55, "ENOANO" }, 172 | { 56, "EBADRQC" }, 173 | { 57, "EBADSLT" }, 174 | { 59, "EBFONT" }, 175 | { 60, "ENOSTR" }, 176 | { 61, "ENODATA" }, 177 | { 62, "ETIME" }, 178 | { 63, "ENOSR" }, 179 | { 64, "ENONET" }, 180 | { 65, "ENOPKG" }, 181 | { 66, "EREMOTE" }, 182 | { 67, "ENOLINK" }, 183 | { 68, "EADV" }, 184 | { 69, "ESRMNT" }, 185 | { 70, "ECOMM" }, 186 | { 71, "EPROTO" }, 187 | { 72, "EMULTIHOP" }, 188 | { 73, "EDOTDOT" }, 189 | { 74, "EBADMSG" }, 190 | { 75, "EOVERFLOW" }, 191 | { 76, "ENOTUNIQ" }, 192 | { 77, "EBADFD" }, 193 | { 78, "EREMCHG" }, 194 | { 79, "ELIBACC" }, 195 | { 80, "ELIBBAD" }, 196 | { 81, "ELIBSCN" }, 197 | { 82, "ELIBMAX" }, 198 | { 83, "ELIBEXEC" }, 199 | { 84, "EILSEQ" }, 200 | { 85, "ERESTART" }, 201 | { 86, "ESTRPIPE" }, 202 | { 87, "EUSERS" }, 203 | { 88, "ENOTSOCK" }, 204 | { 89, "EDESTADDRREQ" }, 205 | { 90, "EMSGSIZE" }, 206 | { 91, "EPROTOTYPE" }, 207 | { 92, "ENOPROTOOPT" }, 208 | { 93, "EPROTONOSUPPORT" }, 209 | { 94, "ESOCKTNOSUPPORT" }, 210 | { 95, "EOPNOTSUPP" }, 211 | { 96, "EPFNOSUPPORT" }, 212 | { 97, "EAFNOSUPPORT" }, 213 | { 98, "EADDRINUSE" }, 214 | { 99, "EADDRNOTAVAIL" }, 215 | { 100, "ENETDOWN" }, 216 | { 101, "ENETUNREACH" }, 217 | { 102, "ENETRESET" }, 218 | { 103, "ECONNABORTED" }, 219 | { 104, "ECONNRESET" }, 220 | { 105, "ENOBUFS" }, 221 | { 106, "EISCONN" }, 222 | { 107, "ENOTCONN" }, 223 | { 108, "ESHUTDOWN" }, 224 | { 109, "ETOOMANYREFS" }, 225 | { 110, "ETIMEDOUT" }, 226 | { 111, "ECONNREFUSED" }, 227 | { 112, "EHOSTDOWN" }, 228 | { 113, "EHOSTUNREACH" }, 229 | { 114, "EALREADY" }, 230 | { 115, "EINPROGRESS" }, 231 | { 116, "ESTALE" }, 232 | { 117, "EUCLEAN" }, 233 | { 118, "ENOTNAM" }, 234 | { 119, "ENAVAIL" }, 235 | { 120, "EISNAM" }, 236 | { 121, "EREMOTEIO" }, 237 | { 122, "EDQUOT" }, 238 | { 123, "ENOMEDIUM" }, 239 | { 124, "EMEDIUMTYPE" }, 240 | { 125, "ECANCELED" }, 241 | { 126, "ENOKEY" }, 242 | { 127, "EKEYEXPIRED" }, 243 | { 128, "EKEYREVOKED" }, 244 | { 129, "EKEYREJECTED" }, 245 | { 130, "EOWNERDEAD" }, 246 | { 131, "ENOTRECOVERABLE" }, 247 | { 132, "ERFKILL" }, 248 | { 133, "EHWPOISON" } 249 | }; 250 | // clang-format on 251 | } // namespace 252 | } // namespace tob::ebpfault 253 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2019-present, Trail of Bits, Inc. 3 | All rights reserved. 4 | 5 | This source code is licensed in accordance with the terms specified in 6 | the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace tob::ebpfault { 18 | struct CommandLineParameters final { 19 | std::string configuration_path; 20 | 21 | bool except_pid_list{false}; 22 | std::optional> opt_pid_list; 23 | 24 | std::optional> opt_exec_command_line; 25 | }; 26 | 27 | tob::StringErrorOr parseCommandLine(int argc, 28 | char *argv[]); 29 | 30 | std::string describeFaultValue(std::uint64_t fault_value); 31 | bool configureRlimit(); 32 | } // namespace tob::ebpfault 33 | --------------------------------------------------------------------------------