├── .buildkite └── custom-tests.json ├── .cargo └── config ├── .github └── dependabot.yml ├── .gitignore ├── .gitmodules ├── .platform ├── CHANGELOG.md ├── CODEOWNERS ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-BSD-3-Clause ├── README.md ├── coverage_config_aarch64.json ├── coverage_config_x86_64.json ├── docs ├── img │ ├── seccomp_filter.png │ └── seccompiler_arch.png └── json_format.md ├── src ├── backend │ ├── bpf.rs │ ├── condition.rs │ ├── filter.rs │ ├── mod.rs │ └── rule.rs ├── frontend │ ├── json.rs │ └── mod.rs ├── lib.rs └── syscall_table │ ├── aarch64.rs │ ├── mod.rs │ ├── riscv64.rs │ └── x86_64.rs ├── tests ├── integration_tests.rs ├── json.rs └── multi_thread.rs └── tools └── generate_syscall_tables.sh /.buildkite/custom-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests": [ 3 | { 4 | "test_name": "build-gnu-json", 5 | "command": "RUSTFLAGS=\"-D warnings\" cargo build --release --features=json", 6 | "platform": [ 7 | "x86_64", 8 | "aarch64", 9 | "riscv64" 10 | ] 11 | }, 12 | { 13 | "test_name": "build-musl-json", 14 | "command": "RUSTFLAGS=\"-D warnings\" cargo build --release --features=json --target {target_platform}-unknown-linux-musl", 15 | "platform": [ 16 | "x86_64", 17 | "aarch64" 18 | ] 19 | }, 20 | { 21 | "test_name": "validate-syscall-tables", 22 | "command": "tools/generate_syscall_tables.sh --test", 23 | "platform": [ 24 | "x86_64" 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc" ] 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gitsubmodule 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: cargo 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | open-pull-requests-limit: 5 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.kernel 3 | Cargo.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rust-vmm-ci"] 2 | path = rust-vmm-ci 3 | url = https://github.com/rust-vmm/rust-vmm-ci.git 4 | -------------------------------------------------------------------------------- /.platform: -------------------------------------------------------------------------------- 1 | x86_64 2 | aarch64 3 | riscv64 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Upcoming Release 2 | 3 | # v0.5.0 4 | 5 | ## Added 6 | - [[#72]](https://github.com/rust-vmm/seccompiler/pull/72): Introduce RISC-V 7 | 64-bit architecture support. 8 | 9 | ## Changed 10 | - [[#78]](https://github.com/rust-vmm/seccompiler/pull/78): Update 11 | `syscall_tables` from v6.12 kernel source 12 | 13 | # v0.4.0 14 | 15 | ## Changed 16 | - Seccomp is now activated via the seccomp syscall, not prctl 17 | 18 | ## Added 19 | - A new Error::Seccomp variant is added to indictate seccomp syscall failures 20 | - Add `apply_filter_all_threads` convenience function which uses the seccomp 21 | TSYNC feature to synchronize all threads in the process to the same filter 22 | - A new Error::ThreadSync variant is added to indicate failure to sync threads 23 | 24 | # v0.3.0 25 | 26 | ## Changed 27 | - [[#40]](https://github.com/rust-vmm/seccompiler/pull/40): Update Rust 28 | to Edition 2021 29 | 30 | ## Fixed 31 | 32 | - [[#31]](https://github.com/rust-vmm/seccompiler/issues/31): Implement 33 | `From` for `Error` 34 | - [[#40]](https://github.com/rust-vmm/seccompiler/pull/40): Fix clippy 35 | complaints about missing `Eq` when `PartialEq` is implemented 36 | 37 | # v0.2.0 38 | 39 | First release 40 | 41 | ## Added 42 | 43 | - [[#7]](https://github.com/rust-vmm/seccompiler/pull/7): Add functionality for 44 | Rust-based filters and implement the BPF compilation logic. 45 | - [[#9]](https://github.com/rust-vmm/seccompiler/pull/9): Add json frontend, 46 | gated by the `json` feature. 47 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Add the list of code owners here (using their GitHub username) 2 | * @alindima @petreeftime 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seccompiler" 3 | version = "0.5.0" 4 | authors = ["Amazon Firecracker Team "] 5 | description = "Provides easy-to-use seccomp-bpf jailing." 6 | repository = "https://github.com/rust-vmm/seccompiler" 7 | readme = "README.md" 8 | keywords = ["seccomp", "jail", "sandbox"] 9 | license = "Apache-2.0 OR BSD-3-Clause" 10 | edition = "2021" 11 | 12 | [features] 13 | json = ["serde", "serde_json"] 14 | 15 | [dependencies] 16 | libc = "^0.2.153" 17 | serde = { version = "^1.0.27", features = ["derive"], optional = true} 18 | serde_json = {version = "^1.0.9", optional = true} 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /LICENSE-BSD-3-Clause: -------------------------------------------------------------------------------- 1 | Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Seccompiler 2 | 3 | Provides easy-to-use Linux [seccomp-bpf][1] jailing. 4 | 5 | Seccomp is a Linux kernel security feature which enables a tight control over 6 | what kernel-level mechanisms a process has access to. This is typically used to 7 | reduce the attack surface and exposed resources when running untrusted code. 8 | This works by allowing users to write and set a BPF (Berkeley Packet Filter) 9 | program for each process or thread, that intercepts syscalls and decides 10 | whether the syscall is safe to execute. 11 | 12 | Writing BPF programs by hand is difficult and error-prone. This crate provides 13 | high-level wrappers for working with system call filtering. 14 | 15 | ## Supported platforms 16 | 17 | Due to the fact that seccomp is a Linux-specific feature, this crate is 18 | supported only on Linux systems. 19 | 20 | Supported host architectures: 21 | - Little-endian x86_64 22 | - Little-endian aarch64 23 | - Little-endian riscv64 24 | 25 | ## Short seccomp tutorial 26 | 27 | Linux supports seccomp filters as BPF programs, that are interpreted by the 28 | kernel before each system call. 29 | 30 | They are installed in the kernel using [`prctl(PR_SET_SECCOMP)`][2] or 31 | [`seccomp(SECCOMP_SET_MODE_FILTER)`][3]. 32 | 33 | As input, the BPF program receives a C struct of the following type: 34 | 35 | ```C 36 | struct seccomp_data { 37 | int nr; // syscall number. 38 | __u32 arch; // arch-specific value for validation purposes. 39 | __u64 instruction_pointer; // as the name suggests.. 40 | __u64 args[6]; // syscall arguments. 41 | }; 42 | ``` 43 | 44 | In response, a filter returns an action, that can be either one of: 45 | 46 | ```C 47 | #define SECCOMP_RET_KILL_PROCESS 0x80000000U /* kill the process */ 48 | #define SECCOMP_RET_KILL_THREAD 0x00000000U /* kill the thread */ 49 | #define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ 50 | #define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ 51 | #define SECCOMP_RET_TRACE 0x7ff00000U /* pass to a tracer or disallow */ 52 | #define SECCOMP_RET_LOG 0x7ffc0000U /* allow after logging */ 53 | #define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ 54 | ``` 55 | 56 | ## Design 57 | 58 | The core concept of the library is the **filter**. It is an abstraction that 59 | models a collection of syscall-mapped rules, coupled with on-match and 60 | default actions, that logically describes a policy for dispatching actions 61 | (e.g. Allow, Trap, Errno) for incoming system calls. 62 | 63 | Seccompiler provides constructs for defining filters, compiling them into 64 | loadable BPF programs and installing them in the kernel. 65 | 66 | Filters are defined either with a JSON file or using Rust code, with 67 | library-defined structures. Both representations are semantically equivalent 68 | and model the rules of the filter. Choosing one or the other depends on the use 69 | case and preference. 70 | 71 | The core of the package is the module responsible for the BPF compilation. 72 | It compiles seccomp filters expressed as Rust code, into BPF filters, ready to 73 | be loaded into the kernel. This is the seccompiler **backend**. 74 | 75 | The process of translating JSON filters into BPF goes through an extra step of 76 | deserialization and validation (the **JSON frontend**), before reaching the 77 | same backend for BPF codegen. 78 | 79 | The Rust representation is therefore also an Intermediate Representation (IR) 80 | of the JSON filter. 81 | This modular implementation allows for extendability in regards to file 82 | formats. All that is needed is a compatible frontend. 83 | 84 | The diagram below illustrates the steps required for the JSON and Rust filters 85 | to be compiled into BPF. The blue boxes represent potential user input. 86 | 87 | ![seccompiler architecture](./docs/img/seccompiler_arch.png) 88 | 89 | ### Filter definition 90 | 91 | Let us take a closer look at what a filter is composed of, and how it is 92 | defined: 93 | 94 | The smallest unit of the filter is the `SeccompCondition`, which is a 95 | comparison operation applied to the current system call. It’s parametrised by 96 | the argument index, the length of the argument, the operator and the actual 97 | expected value. 98 | 99 | Going one step further, a `SeccompRule` is a vector of `SeccompCondition`s, 100 | that must all match for the rule to be considered matched. In other words, a 101 | rule is a collection of **and-bound** conditions for a system call. 102 | 103 | Finally, at the top level, there’s the `SeccompFilter`. The filter can be 104 | viewed as a collection of syscall-associated rules, with a predefined on-match 105 | action and a default action that is returned if none of the rules match. 106 | 107 | In a filter, each system call number maps to a vector of **or-bound** rules. 108 | In order for the filter to match, it is enough that one rule associated to the 109 | system call matches. A system call may also map to an empty rule vector, which 110 | means that the system call will match, regardless of the actual arguments. 111 | 112 | The following diagram models a simple filter, that only allows `accept4`, 113 | `fcntl(any, F_SETFD, FD_CLOEXEC, ..)` and `fcntl(any, F_GETFD, ...)`. 114 | For any other system calls, the process will be killed. 115 | 116 | ![filter diagram](./docs/img/seccomp_filter.png) 117 | 118 | As specified earlier, there are two ways of expressing the filters: 119 | 120 | 1. JSON (documented in [json_format.md](docs/json_format.md)); 121 | 2. Rust code (documented by the library). 122 | 123 | See below examples of both representation methods, for a filter equivalent to 124 | the diagram above: 125 | 126 | #### Example JSON filter 127 | 128 | ```json 129 | { 130 | "main_thread": { 131 | "mismatch_action": "kill_process", 132 | "match_action": "allow", 133 | "filter": [ 134 | { 135 | "syscall": "accept4" 136 | }, 137 | { 138 | "syscall": "fcntl", 139 | "args": [ 140 | { 141 | "index": 1, 142 | "type": "dword", 143 | "op": "eq", 144 | "val": 2, 145 | "comment": "F_SETFD" 146 | }, 147 | { 148 | "index": 2, 149 | "type": "dword", 150 | "op": "eq", 151 | "val": 1, 152 | "comment": "FD_CLOEXEC" 153 | } 154 | ] 155 | }, 156 | { 157 | "syscall": "fcntl", 158 | "args": [ 159 | { 160 | "index": 1, 161 | "type": "dword", 162 | "op": "eq", 163 | "val": 1, 164 | "comment": "F_GETFD" 165 | } 166 | ] 167 | } 168 | ] 169 | } 170 | } 171 | ``` 172 | 173 | Note that JSON files need to specify a name for each filter. While in the 174 | example above there is only one (`main_thread`), other programs may be using 175 | multiple filters. 176 | 177 | #### Example Rust-based filter 178 | 179 | ```rust 180 | SeccompFilter::new( 181 | // rule set - BTreeMap> 182 | vec![ 183 | (libc::SYS_accept4, vec![]), 184 | ( 185 | libc::SYS_fcntl, 186 | vec![ 187 | SeccompRule::new(vec![ 188 | Cond::new(1, 189 | SeccompCmpArgLen::Dword, 190 | SeccompCmpOp::Eq, 191 | libc::F_SETFD as u64 192 | )?, 193 | Cond::new( 194 | 2, 195 | SeccompCmpArgLen::Dword, 196 | SeccompCmpOp::Eq, 197 | libc::FD_CLOEXEC as u64, 198 | )?, 199 | ])?, 200 | SeccompRule::new(vec![ 201 | Cond::new( 202 | 1, 203 | SeccompCmpArgLen::Dword, 204 | SeccompCmpOp::Eq, 205 | libc::F_GETFD as u64, 206 | )? 207 | ])? 208 | ] 209 | ) 210 | ].into_iter().collect(), 211 | // mismatch_action 212 | SeccompAction::KillProcess, 213 | // match_action 214 | SeccompAction::Allow, 215 | // target architecture of filter 216 | TargetArch::x86_64, 217 | )? 218 | ``` 219 | 220 | ## Example usage 221 | 222 | Using seccompiler in an application is a two-step process: 223 | 224 | 1. Compiling filters (into BPF) 225 | 2. Installing filters 226 | 227 | ### Compiling filters 228 | 229 | A user application can compile the seccomp filters into loadable BPF either at 230 | **runtime** or at **build time**. 231 | 232 | At **runtime**, the process is straightforward, leveraging the seccompiler 233 | library functions on hardcoded/file-based filters. 234 | 235 | At **build-time,** an application can use a [cargo build script][4] that adds 236 | seccompiler as a build-dependency and outputs at a predefined location 237 | (e.g. using `env::var("OUT_DIR")`) the compiled filters, that have been 238 | serialized to a binary format (e.g. [bincode][5]). 239 | They can then be ingested by the application using `include_bytes!` and 240 | deserialized before getting installed. 241 | This build-time option can be used to shave off the filter compilation time 242 | from the app startup time, if using a low-overhead binary format. 243 | 244 | Regardless of the compilation moment, the process is the same: 245 | 246 | For **JSON filters**, the compilation to loadable BPF is performed using the 247 | `compile_from_json()` function: 248 | 249 | ```rust 250 | let filters: BpfMap = seccompiler::compile_from_json( 251 | File::open("/path/to/json")?, // Accepts generic Read objects. 252 | seccompiler::TargetArch::x86_64, 253 | )?; 254 | ``` 255 | 256 | `BpfMap` is another type exposed by the library, which maps thread 257 | categories to BPF programs. 258 | 259 | ```rust 260 | pub type BpfMap = HashMap; 261 | ``` 262 | 263 | Note that, in order to use the JSON functionality, you need to add the `json` 264 | feature when importing the library. 265 | 266 | For **Rust filters**, it’s enough to perform a `try_into()` cast, from a 267 | `SeccompFilter` to a `BpfProgram`: 268 | 269 | ```rust 270 | let seccomp_filter = SeccompFilter::new( 271 | rules, 272 | SeccompAction::Trap, 273 | SeccompAction::Allow, 274 | seccompiler::TargetArch::x86_64 275 | )?; 276 | 277 | let bpf_prog: BpfProgram = seccomp_filter.try_into()?; 278 | ``` 279 | 280 | ### Installing filters 281 | 282 | ```rust 283 | let bpf_prog: BpfProgram; // Assuming it was initialized with a valid filter. 284 | 285 | seccompiler::apply_filter(&bpf_prog)?; 286 | ``` 287 | 288 | It’s interesting to note that installing the filter does not take ownership or 289 | invalidate the BPF program, thanks to the kernel which performs a 290 | `copy_from_user` on the program before installing it. 291 | 292 | ### Feature documentation 293 | 294 | The documentation on [docs.rs](https://docs.rs/seccompiler/) 295 | does not include the feature-gated json functionality. 296 | 297 | In order to view the documentation including the optional json feature, you may 298 | run: 299 | `cargo doc --open --all-features` 300 | 301 | ## Seccomp best practices 302 | 303 | - Before installing a filter, make sure that the current kernel version 304 | supports the actions of the filter. This can be checked by inspecting the 305 | output of: `cat /proc/sys/kernel/seccomp/actions_avail` or by calling the 306 | `seccomp(SECCOMP_GET_ACTION_AVAIL)` syscall. 307 | 308 | - The recommendation is to use an allow-list approach for the seccomp filter, 309 | only allowing the bare minimum set of syscalls required for your application. 310 | This is safer and more robust than a deny-list, which would need updating 311 | whenever a new, dangerous system call is added to the kernel. 312 | 313 | - When determining the set of system calls needed by an application, it is 314 | recommended to exhaustively run all the code paths, while tracing with `strace` 315 | or `perf`. It is also important to note that applications rarely use the system 316 | call interface directly. They usually use libc wrappers which, depending on the 317 | implementation, use different system calls for the same functionality 318 | (e.g. `open` vs `openat`). 319 | 320 | - Linux supports installing multiple seccomp filters on a thread/process. They 321 | are all evaluated in-order and the most restrictive action is chosen. Unless 322 | your application needs to install multiple filters on a thread, it is 323 | recommended to deny the `prctl` and `seccomp` system calls, to avoid having 324 | malicious actors further restrict the installed filters. 325 | 326 | - The Linux vDSO usually causes some system calls to run entirely in userspace, 327 | bypassing the seccomp filters (for example `clock_gettime`). This can lead to 328 | failures when running on machines that don't support the same vDSO system 329 | calls, if the said syscalls are used but not allowed. It is recommended to also 330 | test the seccomp filters on a machine that doesn't have vDSO, if possible. 331 | 332 | - For minimising system call overhead, it is recommended to enable the BPF Just 333 | in Time (JIT) compiler. After the BPF program is loaded, the kernel will 334 | translate the BPF code into native CPU instructions, for maximum efficieny. 335 | It can be configured via: `/proc/sys/net/core/bpf_jit_enable`. 336 | 337 | [1]: https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html 338 | [2]: https://man7.org/linux/man-pages/man2/prctl.2.html 339 | [3]: https://man7.org/linux/man-pages/man2/seccomp.2.html 340 | [4]: https://doc.rust-lang.org/cargo/reference/build-scripts.html 341 | [5]: https://github.com/bincode-org/bincode 342 | -------------------------------------------------------------------------------- /coverage_config_aarch64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 0, 3 | "exclude_path": "tests/integration_tests.rs,tests/json.rs", 4 | "crate_features": "json" 5 | } 6 | -------------------------------------------------------------------------------- /coverage_config_x86_64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 96.7, 3 | "exclude_path": "tests/integration_tests.rs,tests/json.rs", 4 | "crate_features": "json" 5 | } 6 | -------------------------------------------------------------------------------- /docs/img/seccomp_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/seccompiler/c3cf77d65815037931ae5bc2fca010713defdc8c/docs/img/seccomp_filter.png -------------------------------------------------------------------------------- /docs/img/seccompiler_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-vmm/seccompiler/c3cf77d65815037931ae5bc2fca010713defdc8c/docs/img/seccompiler_arch.png -------------------------------------------------------------------------------- /docs/json_format.md: -------------------------------------------------------------------------------- 1 | ## JSON file format 2 | 3 | A JSON file expresses the seccomp policy for a process. It may contain multiple 4 | filters, which can be semantically mapped to thread types in multithreaded 5 | applications, and is specific to just one target platform of the application. 6 | 7 | At the top level, the file requires an object that maps unique string keys to 8 | seccomp filters: 9 | 10 | ``` 11 | { 12 | "vmm": { 13 | "mismatch_action": { 14 | "errno" : -1 15 | }, 16 | "match_action": "allow", 17 | "filter": [...] 18 | }, 19 | "api": {...}, 20 | "vcpu": {...}, 21 | } 22 | ``` 23 | 24 | The associated filter is a JSON object containing the `mismatch_action`, 25 | `match_action` and `filter` properties. 26 | 27 | The `mismatch_action` represents the action executed if none of the rules in 28 | `filter` match, and `match_action` is what gets executed if a rule in the 29 | filter matches (e.g: `"Allow"` in the case of implementing an allowlist). 30 | 31 | An **action** is the JSON representation of the following enum: 32 | 33 | ```rust 34 | pub enum SeccompAction { 35 | Allow, // Allows syscall. 36 | Errno(u32), // Returns from syscall with specified error number. 37 | KillThread, // Kills calling process. 38 | KillProcess, // Kills calling thread. 39 | Log, // Allows syscall after logging it. 40 | Trace(u32), // Notifies tracing process of the caller with respective number. 41 | Trap, // Sends `SIGSYS` to the calling process. 42 | } 43 | ``` 44 | 45 | The `filter` property specifies the set of rules that would trigger a match. 46 | This is an array containing multiple **or-bound SyscallRule** **objects** 47 | (if one of them matches, the corresponding action gets triggered). 48 | 49 | The **SyscallRule** object is used for adding a rule to a syscall. 50 | It has an optional `args` property that is used to specify a vector of 51 | and-bound conditions that the syscall arguments must satisfy in order for the 52 | rule to match. 53 | 54 | In the absence of the `args` property, the corresponding action will get 55 | triggered by any call that matches that name, irrespective of the argument 56 | values. 57 | 58 | Here is the structure of the object: 59 | 60 | ``` 61 | { 62 | "syscall": "accept4", // mandatory, the syscall name 63 | "comment": "Used by vsock & api thread", // optional, for adding comments 64 | "args": [...] // optional, vector of and-bound conditions for the arguments 65 | } 66 | ``` 67 | 68 | Note that the file format expects syscall names, not arch-specific numbers, for 69 | increased usability. This is not true, however for the syscall arguments, which 70 | are expected as base-10 integers. 71 | 72 | In order to allow a syscall with multiple alternatives for the same parameters, 73 | one can write multiple syscall rule objects at the filter-level, each with its 74 | own, distinct rules. 75 | 76 | A **condition object** is made up of the following mandatory properties: 77 | 78 | - `index` (0-based index of the syscall argument we want to check) 79 | - `type` (`dword` or `qword`, which specifies the argument size - 4 or 8 80 | bytes respectively) 81 | - `op`, which is one of `eq, ge, gt, ge, lt, masked_eq, ne` (the operator used 82 | for comparing the parameter to `val`) 83 | - `val` is the integer value being checked against 84 | 85 | As mentioned eariler, named parameters are not supported in the JSON file, but 86 | only numeric constants. One can provide meaning to each numeric value, much 87 | like when using named parameters, by using the optional `comment` property: 88 | 89 | ``` 90 | { 91 | "syscall": "accept4", 92 | "args": [ 93 | { 94 | "index": 3, 95 | "type": "dword", 96 | "op": "eq", 97 | "val": 1, 98 | "comment": "libc::AF_UNIX" 99 | } 100 | ] 101 | } 102 | ``` 103 | 104 | View an example filter in the [Readme](../README.md#example-json-filter). 105 | -------------------------------------------------------------------------------- /src/backend/bpf.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | //! Define constants, helpers and types used for BPF codegen. 5 | 6 | use super::TargetArch; 7 | 8 | /// Program made up of a sequence of BPF instructions. 9 | pub type BpfProgram = Vec; 10 | 11 | /// Reference to program made up of a sequence of BPF instructions. 12 | pub type BpfProgramRef<'a> = &'a [sock_filter]; 13 | 14 | // The maximum number of a syscall argument. 15 | // A syscall can have at most 6 arguments. 16 | // Arguments are numbered from 0 to 5. 17 | pub const ARG_NUMBER_MAX: u8 = 5; 18 | 19 | // The maximum number of BPF statements that a condition will be translated into. 20 | // This is not a linux requirement but is based on the current BPF compilation logic. 21 | // This is used in the backend code for preallocating vectors and for detecting unjumpable offsets. 22 | pub const CONDITION_MAX_LEN: u8 = 6; 23 | 24 | // The maximum seccomp-BPF program length allowed by the linux kernel. 25 | pub const BPF_MAX_LEN: usize = 4096; 26 | 27 | // `struct seccomp_data` offsets and sizes of fields in bytes: 28 | // 29 | // ```c 30 | // struct seccomp_data { 31 | // int nr; 32 | // __u32 arch; 33 | // __u64 instruction_pointer; 34 | // __u64 args[6]; 35 | // }; 36 | // ``` 37 | pub const SECCOMP_DATA_NR_OFFSET: u8 = 0; 38 | const SECCOMP_DATA_ARCH_OFFSET: u8 = 4; 39 | pub const SECCOMP_DATA_ARGS_OFFSET: u8 = 16; 40 | pub const SECCOMP_DATA_ARG_SIZE: u8 = 8; 41 | 42 | // Builds a `jump` BPF instruction. 43 | // 44 | // # Arguments 45 | // 46 | // * `code` - The operation code. 47 | // * `jt` - The jump offset in case the operation returns `true`. 48 | // * `jf` - The jump offset in case the operation returns `false`. 49 | // * `k` - The operand. 50 | #[inline(always)] 51 | pub(crate) fn bpf_jump(code: u16, k: u32, jt: u8, jf: u8) -> sock_filter { 52 | sock_filter { code, jt, jf, k } 53 | } 54 | 55 | // Builds a "statement" BPF instruction. 56 | // 57 | // # Arguments 58 | // 59 | // * `code` - The operation code. 60 | // * `k` - The operand. 61 | #[inline(always)] 62 | pub(crate) fn bpf_stmt(code: u16, k: u32) -> sock_filter { 63 | sock_filter { 64 | code, 65 | jt: 0, 66 | jf: 0, 67 | k, 68 | } 69 | } 70 | 71 | // Builds a sequence of BPF instructions that validate the underlying architecture. 72 | #[inline(always)] 73 | pub(crate) fn build_arch_validation_sequence(target_arch: TargetArch) -> Vec { 74 | let audit_arch_value = target_arch.get_audit_value(); 75 | vec![ 76 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, SECCOMP_DATA_ARCH_OFFSET as u32), 77 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, audit_arch_value, 1, 0), 78 | bpf_stmt(BPF_RET | BPF_K, libc::SECCOMP_RET_KILL_PROCESS), 79 | ] 80 | } 81 | 82 | // BPF Instruction classes. 83 | // See /usr/include/linux/bpf_common.h . 84 | // Load operation. 85 | pub const BPF_LD: u16 = 0x00; 86 | // ALU operation. 87 | pub const BPF_ALU: u16 = 0x04; 88 | // Jump operation. 89 | pub const BPF_JMP: u16 = 0x05; 90 | // Return operation. 91 | pub const BPF_RET: u16 = 0x06; 92 | 93 | // BPF ld/ldx fields. 94 | // See /usr/include/linux/bpf_common.h . 95 | // Operand size is a word. 96 | pub const BPF_W: u16 = 0x00; 97 | // Load from data area (where `seccomp_data` is). 98 | pub const BPF_ABS: u16 = 0x20; 99 | 100 | // BPF alu fields. 101 | // See /usr/include/linux/bpf_common.h . 102 | pub const BPF_AND: u16 = 0x50; 103 | 104 | // BPF jmp fields. 105 | // See /usr/include/linux/bpf_common.h . 106 | // Unconditional jump. 107 | pub const BPF_JA: u16 = 0x00; 108 | // Jump with comparisons. 109 | pub const BPF_JEQ: u16 = 0x10; 110 | pub const BPF_JGT: u16 = 0x20; 111 | pub const BPF_JGE: u16 = 0x30; 112 | // Test against the value in the K register. 113 | pub const BPF_K: u16 = 0x00; 114 | 115 | // Architecture identifier for x86_64 LE. 116 | // See /usr/include/linux/audit.h . 117 | // Defined as: 118 | // `#define AUDIT_ARCH_X86_64 (EM_X86_64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE)` 119 | pub const AUDIT_ARCH_X86_64: u32 = 62 | 0x8000_0000 | 0x4000_0000; 120 | 121 | // Architecture identifier for aarch64 LE. 122 | // Defined as: 123 | // `#define AUDIT_ARCH_AARCH64 (EM_AARCH64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE)` 124 | pub const AUDIT_ARCH_AARCH64: u32 = 183 | 0x8000_0000 | 0x4000_0000; 125 | 126 | // Architecture identifier for riscv64 LE. 127 | // Defined as: 128 | // `#define AUDIT_ARCH_RISCV64 (EM_RISCV|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE)` 129 | pub const AUDIT_ARCH_RISCV64: u32 = 243 | 0x8000_0000 | 0x4000_0000; 130 | 131 | /// BPF instruction structure definition. 132 | // See /usr/include/linux/filter.h . 133 | // We cannot use the `libc::sock_filter` definition since it doesn't implement `Debug` and 134 | // `PartialEq`. 135 | #[repr(C)] 136 | #[derive(Clone, Debug, PartialEq, Eq)] 137 | pub struct sock_filter { 138 | /// Code of the instruction. 139 | pub code: ::std::os::raw::c_ushort, 140 | /// Jump if true offset. 141 | pub jt: ::std::os::raw::c_uchar, 142 | /// Jump if false offset. 143 | pub jf: ::std::os::raw::c_uchar, 144 | /// Immediate value. 145 | pub k: ::std::os::raw::c_uint, 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::*; 151 | 152 | #[test] 153 | fn test_bpf_functions() { 154 | // Compares the output of the BPF instruction generating functions to hardcoded 155 | // instructions. 156 | assert_eq!( 157 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16), 158 | sock_filter { 159 | code: 0x20, 160 | jt: 0, 161 | jf: 0, 162 | k: 16, 163 | } 164 | ); 165 | assert_eq!( 166 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 2, 5), 167 | sock_filter { 168 | code: 0x15, 169 | jt: 2, 170 | jf: 5, 171 | k: 10, 172 | } 173 | ); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/backend/condition.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use super::{Error, Result, SeccompCmpArgLen, SeccompCmpOp}; 5 | use crate::backend::bpf::*; 6 | 7 | /// Condition that a syscall must match in order to satisfy a rule. 8 | #[derive(Clone, Debug, PartialEq, Eq)] 9 | pub struct SeccompCondition { 10 | /// Index of the argument that is to be compared. 11 | arg_index: u8, 12 | /// Length of the argument value that is to be compared. 13 | arg_len: SeccompCmpArgLen, 14 | /// Comparison operator to perform. 15 | operator: SeccompCmpOp, 16 | /// The value that will be compared with the argument value of the syscall. 17 | value: u64, 18 | } 19 | 20 | impl SeccompCondition { 21 | /// Creates a new [`SeccompCondition`]. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `arg_index` - Index of the argument that is to be compared. 26 | /// * `arg_len` - Length of the argument value that is to be compared. 27 | /// * `operator` - Comparison operator to perform. 28 | /// * `value` - The value that will be compared with the argument value of the syscall. 29 | /// 30 | /// # Example 31 | /// 32 | /// ``` 33 | /// use seccompiler::{SeccompCmpArgLen, SeccompCmpOp, SeccompCondition}; 34 | /// 35 | /// let condition = SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 1).unwrap(); 36 | /// ``` 37 | /// 38 | /// [`SeccompCondition`]: struct.SeccompCondition.html 39 | pub fn new( 40 | arg_index: u8, 41 | arg_len: SeccompCmpArgLen, 42 | operator: SeccompCmpOp, 43 | value: u64, 44 | ) -> Result { 45 | let instance = Self { 46 | arg_index, 47 | arg_len, 48 | operator, 49 | value, 50 | }; 51 | 52 | instance.validate().map(|_| Ok(instance))? 53 | } 54 | /// Validates the `SeccompCondition` data. 55 | /// 56 | /// [`SeccompCondition`]: struct.SeccompCondition.html 57 | fn validate(&self) -> Result<()> { 58 | // Checks that the given argument number is valid. 59 | if self.arg_index > ARG_NUMBER_MAX { 60 | return Err(Error::InvalidArgumentNumber); 61 | } 62 | 63 | Ok(()) 64 | } 65 | 66 | /// Computes the offsets of the syscall argument data passed to the BPF program. 67 | /// 68 | /// Returns the offsets of the most significant and least significant halves of the argument 69 | /// specified by `arg_index` relative to `struct seccomp_data` passed to the BPF program by 70 | /// the kernel. 71 | fn get_data_offsets(&self) -> (u8, u8) { 72 | // Offset to the argument specified by `arg_index`. 73 | // Cannot overflow because the value will be at most 16 + 5 * 8 = 56. 74 | let arg_offset = SECCOMP_DATA_ARGS_OFFSET + self.arg_index * SECCOMP_DATA_ARG_SIZE; 75 | 76 | // Extracts offsets of most significant and least significant halves of argument. 77 | // Addition cannot overflow because it's at most `arg_offset` + 4 = 68. 78 | (arg_offset + SECCOMP_DATA_ARG_SIZE / 2, arg_offset) 79 | } 80 | 81 | /// Splits the `value` field into 32 bit chunks 82 | /// 83 | /// Returns the most significant and least significant halves of the `value`. 84 | fn split_value(&self) -> (u32, u32) { 85 | ((self.value >> 32) as u32, self.value as u32) 86 | } 87 | 88 | /// Translates the `eq` (equal) condition into BPF statements. 89 | /// 90 | /// # Arguments 91 | /// 92 | /// * `offset` - The given jump offset to the start of the next rule. 93 | fn into_eq_bpf(self, offset: u8) -> Vec { 94 | let (msb, lsb) = self.split_value(); 95 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 96 | 97 | let mut bpf = match self.arg_len { 98 | SeccompCmpArgLen::Dword => vec![], 99 | SeccompCmpArgLen::Qword => vec![ 100 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 101 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, offset + 2), 102 | ], 103 | }; 104 | 105 | bpf.append(&mut vec![ 106 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 107 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, lsb, 0, offset), 108 | ]); 109 | bpf 110 | } 111 | 112 | /// Translates the `ne` (not equal) condition into BPF statements. 113 | /// 114 | /// # Arguments 115 | /// 116 | /// * `offset` - The given jump offset to the start of the next rule. 117 | fn into_ne_bpf(self, offset: u8) -> Vec { 118 | let (msb, lsb) = self.split_value(); 119 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 120 | 121 | let mut bpf = match self.arg_len { 122 | SeccompCmpArgLen::Dword => vec![], 123 | SeccompCmpArgLen::Qword => vec![ 124 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 125 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, 2), 126 | ], 127 | }; 128 | 129 | bpf.append(&mut vec![ 130 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 131 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, lsb, offset, 0), 132 | ]); 133 | bpf 134 | } 135 | 136 | /// Translates the `ge` (greater than or equal) condition into BPF statements. 137 | /// 138 | /// # Arguments 139 | /// 140 | /// * `offset` - The given jump offset to the start of the next rule. 141 | fn into_ge_bpf(self, offset: u8) -> Vec { 142 | let (msb, lsb) = self.split_value(); 143 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 144 | 145 | let mut bpf = match self.arg_len { 146 | SeccompCmpArgLen::Dword => vec![], 147 | SeccompCmpArgLen::Qword => vec![ 148 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 149 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, msb, 3, 0), 150 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, offset + 2), 151 | ], 152 | }; 153 | 154 | bpf.append(&mut vec![ 155 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 156 | bpf_jump(BPF_JMP | BPF_JGE | BPF_K, lsb, 0, offset), 157 | ]); 158 | bpf 159 | } 160 | 161 | /// Translates the `gt` (greater than) condition into BPF statements. 162 | /// 163 | /// # Arguments 164 | /// 165 | /// * `offset` - The given jump offset to the start of the next rule. 166 | fn into_gt_bpf(self, offset: u8) -> Vec { 167 | let (msb, lsb) = self.split_value(); 168 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 169 | 170 | let mut bpf = match self.arg_len { 171 | SeccompCmpArgLen::Dword => vec![], 172 | SeccompCmpArgLen::Qword => vec![ 173 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 174 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, msb, 3, 0), 175 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, offset + 2), 176 | ], 177 | }; 178 | 179 | bpf.append(&mut vec![ 180 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 181 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, lsb, 0, offset), 182 | ]); 183 | bpf 184 | } 185 | 186 | /// Translates the `le` (less than or equal) condition into BPF statements. 187 | /// 188 | /// # Arguments 189 | /// 190 | /// * `offset` - The given jump offset to the start of the next rule. 191 | fn into_le_bpf(self, offset: u8) -> Vec { 192 | let (msb, lsb) = self.split_value(); 193 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 194 | 195 | let mut bpf = match self.arg_len { 196 | SeccompCmpArgLen::Dword => vec![], 197 | SeccompCmpArgLen::Qword => vec![ 198 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 199 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, msb, offset + 3, 0), 200 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, 2), 201 | ], 202 | }; 203 | 204 | bpf.append(&mut vec![ 205 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 206 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, lsb, offset, 0), 207 | ]); 208 | bpf 209 | } 210 | 211 | /// Translates the `lt` (less than) condition into BPF statements. 212 | /// 213 | /// # Arguments 214 | /// 215 | /// * `offset` - The given jump offset to the start of the next rule. 216 | fn into_lt_bpf(self, offset: u8) -> Vec { 217 | let (msb, lsb) = self.split_value(); 218 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 219 | 220 | let mut bpf = match self.arg_len { 221 | SeccompCmpArgLen::Dword => vec![], 222 | SeccompCmpArgLen::Qword => vec![ 223 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 224 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, msb, offset + 3, 0), 225 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, 2), 226 | ], 227 | }; 228 | 229 | bpf.append(&mut vec![ 230 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 231 | bpf_jump(BPF_JMP | BPF_JGE | BPF_K, lsb, offset, 0), 232 | ]); 233 | bpf 234 | } 235 | 236 | /// Translates the `masked_eq` (masked equal) condition into BPF statements. 237 | /// 238 | /// The `masked_eq` condition is `true` if the result of logical `AND` between the given value 239 | /// and the mask is the value being compared against. 240 | /// 241 | /// # Arguments 242 | /// 243 | /// * `offset` - The given jump offset to the start of the next rule. 244 | fn into_masked_eq_bpf(mut self, offset: u8, mask: u64) -> Vec { 245 | // Mask the current value. 246 | self.value &= mask; 247 | 248 | let (msb_offset, lsb_offset) = self.get_data_offsets(); 249 | let (msb, lsb) = self.split_value(); 250 | let (mask_msb, mask_lsb) = ((mask >> 32) as u32, mask as u32); 251 | 252 | let mut bpf = match self.arg_len { 253 | SeccompCmpArgLen::Dword => vec![], 254 | SeccompCmpArgLen::Qword => vec![ 255 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(msb_offset)), 256 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, mask_msb), 257 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, msb, 0, offset + 3), 258 | ], 259 | }; 260 | 261 | bpf.append(&mut vec![ 262 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, u32::from(lsb_offset)), 263 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, mask_lsb), 264 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, lsb, 0, offset), 265 | ]); 266 | bpf 267 | } 268 | 269 | /// Translates the [`SeccompCondition`] into BPF statements. 270 | /// 271 | /// # Arguments 272 | /// 273 | /// * `offset` - The given jump offset to the start of the next rule. 274 | /// 275 | /// The jump is performed if the condition fails and thus the current rule does not match so 276 | /// `seccomp` tries to match the next rule by jumping out of the current rule. 277 | /// 278 | /// In case the condition is part of the last rule, the jump offset is to the default action of 279 | /// respective filter. 280 | /// 281 | /// The most significant and least significant halves of the argument value are compared 282 | /// separately since the BPF operand and accumulator are 4 bytes whereas an argument value is 8. 283 | pub(crate) fn into_bpf(self, offset: u8) -> Vec { 284 | let result = match self.operator { 285 | SeccompCmpOp::Eq => self.into_eq_bpf(offset), 286 | SeccompCmpOp::Ge => self.into_ge_bpf(offset), 287 | SeccompCmpOp::Gt => self.into_gt_bpf(offset), 288 | SeccompCmpOp::Le => self.into_le_bpf(offset), 289 | SeccompCmpOp::Lt => self.into_lt_bpf(offset), 290 | SeccompCmpOp::MaskedEq(mask) => self.into_masked_eq_bpf(offset, mask), 291 | SeccompCmpOp::Ne => self.into_ne_bpf(offset), 292 | }; 293 | 294 | // Verifies that the `CONDITION_MAX_LEN` constant was properly updated. 295 | assert!(result.len() <= CONDITION_MAX_LEN as usize); 296 | 297 | result 298 | } 299 | } 300 | 301 | #[cfg(test)] 302 | #[allow(clippy::undocumented_unsafe_blocks)] 303 | mod tests { 304 | use super::*; 305 | #[test] 306 | fn test_new_condition() { 307 | assert!(SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 60).is_ok()); 308 | assert_eq!( 309 | SeccompCondition::new(7, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 60).unwrap_err(), 310 | Error::InvalidArgumentNumber 311 | ); 312 | } 313 | 314 | #[test] 315 | fn test_get_data_offsets() { 316 | let cond = SeccompCondition::new(1, SeccompCmpArgLen::Qword, SeccompCmpOp::Eq, 60).unwrap(); 317 | let (msb_offset, lsb_offset) = cond.get_data_offsets(); 318 | assert_eq!( 319 | (msb_offset, lsb_offset), 320 | ( 321 | SECCOMP_DATA_ARGS_OFFSET + SECCOMP_DATA_ARG_SIZE + 4, 322 | SECCOMP_DATA_ARGS_OFFSET + SECCOMP_DATA_ARG_SIZE 323 | ) 324 | ); 325 | 326 | let data = libc::seccomp_data { 327 | nr: 0, 328 | arch: 0, 329 | instruction_pointer: AUDIT_ARCH_X86_64 as u64, 330 | args: [ 331 | u64::MAX, 332 | u32::MAX as u64 + 1, 333 | u64::MAX, 334 | u64::MAX, 335 | u64::MAX, 336 | u64::MAX, 337 | ], 338 | }; 339 | let data_ptr = (&data as *const libc::seccomp_data) as *const u32; 340 | 341 | assert_eq!(unsafe { *data_ptr.offset((lsb_offset / 4) as isize) }, 0); 342 | assert_eq!(unsafe { *data_ptr.offset((msb_offset / 4) as isize) }, 1); 343 | } 344 | 345 | #[test] 346 | fn test_split_value() { 347 | let cond = SeccompCondition::new( 348 | 1, 349 | SeccompCmpArgLen::Qword, 350 | SeccompCmpOp::Eq, 351 | u32::MAX as u64 + 1, 352 | ) 353 | .unwrap(); 354 | assert_eq!(cond.split_value(), (1, 0)); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/backend/filter.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::collections::BTreeMap; 5 | 6 | use crate::backend::bpf::*; 7 | use crate::backend::rule::SeccompRule; 8 | use crate::backend::{Error, Result, SeccompAction, TargetArch}; 9 | 10 | /// Filter containing rules assigned to syscall numbers. 11 | #[derive(Clone, Debug, PartialEq, Eq)] 12 | pub struct SeccompFilter { 13 | /// Map of syscall numbers and corresponding rule chains. 14 | rules: BTreeMap>, 15 | /// Default action to apply to syscalls that do not match the filter. 16 | mismatch_action: SeccompAction, 17 | /// Filter action to apply to syscalls that match the filter. 18 | match_action: SeccompAction, 19 | /// Target architecture of the generated BPF filter. 20 | target_arch: TargetArch, 21 | } 22 | 23 | impl SeccompFilter { 24 | /// Creates a new filter with a set of rules, an on-match and default action. 25 | /// 26 | /// # Arguments 27 | /// 28 | /// * `rules` - Map containing syscall numbers and their respective [`SeccompRule`]s. 29 | /// * `mismatch_action` - [`SeccompAction`] taken for all syscalls that do not match the filter. 30 | /// * `match_action` - [`SeccompAction`] taken for system calls that match the filter. 31 | /// * `target_arch` - Target architecture of the generated BPF filter. 32 | /// 33 | /// # Example 34 | /// 35 | /// ``` 36 | /// use seccompiler::{ 37 | /// SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, 38 | /// }; 39 | /// use std::convert::TryInto; 40 | /// 41 | /// let filter = SeccompFilter::new( 42 | /// vec![ 43 | /// (libc::SYS_accept4, vec![]), 44 | /// ( 45 | /// libc::SYS_fcntl, 46 | /// vec![ 47 | /// SeccompRule::new(vec![ 48 | /// SeccompCondition::new( 49 | /// 1, 50 | /// SeccompCmpArgLen::Dword, 51 | /// SeccompCmpOp::Eq, 52 | /// libc::F_SETFD as u64, 53 | /// ) 54 | /// .unwrap(), 55 | /// SeccompCondition::new( 56 | /// 2, 57 | /// SeccompCmpArgLen::Dword, 58 | /// SeccompCmpOp::Eq, 59 | /// libc::FD_CLOEXEC as u64, 60 | /// ) 61 | /// .unwrap(), 62 | /// ]) 63 | /// .unwrap(), 64 | /// SeccompRule::new(vec![SeccompCondition::new( 65 | /// 1, 66 | /// SeccompCmpArgLen::Dword, 67 | /// SeccompCmpOp::Eq, 68 | /// libc::F_GETFD as u64, 69 | /// ) 70 | /// .unwrap()]) 71 | /// .unwrap(), 72 | /// ], 73 | /// ), 74 | /// ] 75 | /// .into_iter() 76 | /// .collect(), 77 | /// SeccompAction::Trap, 78 | /// SeccompAction::Allow, 79 | /// std::env::consts::ARCH.try_into().unwrap(), 80 | /// ); 81 | /// ``` 82 | /// 83 | /// [`SeccompRule`]: struct.SeccompRule.html 84 | /// [`SeccompAction`]: enum.SeccompAction.html 85 | pub fn new( 86 | rules: BTreeMap>, 87 | mismatch_action: SeccompAction, 88 | match_action: SeccompAction, 89 | target_arch: TargetArch, 90 | ) -> Result { 91 | let instance = Self { 92 | rules, 93 | mismatch_action, 94 | match_action, 95 | target_arch, 96 | }; 97 | 98 | instance.validate()?; 99 | 100 | Ok(instance) 101 | } 102 | 103 | /// Performs semantic checks on the SeccompFilter. 104 | fn validate(&self) -> Result<()> { 105 | // Doesn't make sense to have equal default and on-match actions. 106 | if self.mismatch_action == self.match_action { 107 | return Err(Error::IdenticalActions); 108 | } 109 | 110 | Ok(()) 111 | } 112 | 113 | /// Appends a chain of rules to an accumulator, updating the length of the filter. 114 | /// 115 | /// # Arguments 116 | /// 117 | /// * `syscall_number` - The syscall to which the rules apply. 118 | /// * `chain` - The chain of rules for the specified syscall. 119 | /// * `mismatch_action` - The action to be taken in none of the rules apply. 120 | /// * `accumulator` - The expanding BPF program. 121 | fn append_syscall_chain( 122 | syscall_number: i64, 123 | chain: Vec, 124 | mismatch_action: SeccompAction, 125 | match_action: SeccompAction, 126 | accumulator: &mut Vec>, 127 | ) -> Result<()> { 128 | // The rules of the chain are translated into BPF statements. 129 | let chain: Vec<_> = chain 130 | .into_iter() 131 | .map(|rule| { 132 | let mut bpf: BpfProgram = rule.into(); 133 | // Last statement is the on-match action of the filter. 134 | bpf.push(bpf_stmt(BPF_RET | BPF_K, u32::from(match_action.clone()))); 135 | bpf 136 | }) 137 | .collect(); 138 | let chain_len: usize = chain.iter().map(Vec::len).sum(); 139 | 140 | // The chain starts with a comparison checking the loaded syscall number against the 141 | // syscall number of the chain. 142 | let mut built_syscall = Vec::with_capacity(chain_len + 2); 143 | built_syscall.push(bpf_jump( 144 | BPF_JMP | BPF_JEQ | BPF_K, 145 | // Safe because linux system call numbers are nowhere near the u32::MAX value. 146 | syscall_number.try_into().unwrap(), 147 | 0, 148 | 1, 149 | )); 150 | 151 | if chain.is_empty() { 152 | built_syscall.push(bpf_stmt(BPF_JMP | BPF_JA, 1)); 153 | built_syscall.push(bpf_stmt(BPF_JMP | BPF_JA, 2)); 154 | // If the chain is empty, we only need to append the on-match action. 155 | built_syscall.push(bpf_stmt(BPF_RET | BPF_K, u32::from(match_action))); 156 | } else { 157 | // The rules of the chain are appended. 158 | chain 159 | .into_iter() 160 | .for_each(|mut rule| built_syscall.append(&mut rule)); 161 | } 162 | 163 | // The default action is appended, if the syscall number comparison matched and then all 164 | // rules fail to match, the default action is reached. 165 | built_syscall.push(bpf_stmt(BPF_RET | BPF_K, mismatch_action.into())); 166 | 167 | accumulator.push(built_syscall); 168 | 169 | Ok(()) 170 | } 171 | } 172 | 173 | impl TryFrom for BpfProgram { 174 | type Error = Error; 175 | fn try_from(filter: SeccompFilter) -> Result { 176 | // Initialize the result with the precursory architecture check. 177 | let mut result = build_arch_validation_sequence(filter.target_arch); 178 | 179 | // If no rules are set up, the filter will always return the default action, 180 | // so let's short-circuit the function. 181 | if filter.rules.is_empty() { 182 | result.extend(vec![bpf_stmt( 183 | BPF_RET | BPF_K, 184 | u32::from(filter.mismatch_action), 185 | )]); 186 | 187 | return Ok(result); 188 | } 189 | 190 | // The called syscall number is loaded. 191 | let mut accumulator = vec![vec![bpf_stmt( 192 | BPF_LD | BPF_W | BPF_ABS, 193 | u32::from(SECCOMP_DATA_NR_OFFSET), 194 | )]]; 195 | 196 | let mut iter = filter.rules.into_iter(); 197 | 198 | // For each syscall adds its rule chain to the filter. 199 | let mismatch_action = filter.mismatch_action; 200 | let match_action = filter.match_action; 201 | 202 | iter.try_for_each(|(syscall_number, chain)| { 203 | SeccompFilter::append_syscall_chain( 204 | syscall_number, 205 | chain, 206 | mismatch_action.clone(), 207 | match_action.clone(), 208 | &mut accumulator, 209 | ) 210 | })?; 211 | 212 | // The default action is once again appended, it is reached if all syscall number 213 | // comparisons fail. 214 | accumulator.push(vec![bpf_stmt(BPF_RET | BPF_K, mismatch_action.into())]); 215 | 216 | // Finally, builds the translated filter by consuming the accumulator. 217 | accumulator 218 | .into_iter() 219 | .for_each(|mut instructions| result.append(&mut instructions)); 220 | 221 | if result.len() >= BPF_MAX_LEN { 222 | return Err(Error::FilterTooLarge(result.len())); 223 | } 224 | 225 | Ok(result) 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | mod tests { 231 | use super::SeccompFilter; 232 | use crate::backend::bpf::*; 233 | use crate::backend::condition::SeccompCondition as Cond; 234 | use crate::backend::SeccompCmpArgLen as ArgLen; 235 | use crate::backend::SeccompCmpOp::*; 236 | use crate::backend::{Error, SeccompAction, SeccompRule}; 237 | use std::collections::BTreeMap; 238 | use std::convert::TryInto; 239 | use std::env::consts::ARCH; 240 | 241 | fn create_test_bpf_filter(arg_len: ArgLen) -> SeccompFilter { 242 | SeccompFilter::new( 243 | vec![ 244 | ( 245 | 1, 246 | vec![ 247 | SeccompRule::new(vec![ 248 | Cond::new(2, arg_len.clone(), Le, 14).unwrap(), 249 | Cond::new(2, arg_len.clone(), Ne, 10).unwrap(), 250 | ]) 251 | .unwrap(), 252 | SeccompRule::new(vec![ 253 | Cond::new(2, arg_len.clone(), Gt, 20).unwrap(), 254 | Cond::new(2, arg_len.clone(), Lt, 30).unwrap(), 255 | ]) 256 | .unwrap(), 257 | SeccompRule::new(vec![Cond::new(2, arg_len.clone(), Ge, 42).unwrap()]) 258 | .unwrap(), 259 | ], 260 | ), 261 | ( 262 | 9, 263 | vec![SeccompRule::new(vec![ 264 | Cond::new(1, arg_len, MaskedEq(0b100), 36).unwrap() 265 | ]) 266 | .unwrap()], 267 | ), 268 | (10, vec![]), 269 | ] 270 | .into_iter() 271 | .collect(), 272 | SeccompAction::Trap, 273 | SeccompAction::Allow, 274 | ARCH.try_into().unwrap(), 275 | ) 276 | .unwrap() 277 | } 278 | 279 | #[test] 280 | fn test_seccomp_filter_validate() { 281 | // Filter has identical on-match and default actions. 282 | assert_eq!( 283 | SeccompFilter::new( 284 | BTreeMap::new(), 285 | SeccompAction::Allow, 286 | SeccompAction::Allow, 287 | ARCH.try_into().unwrap() 288 | ) 289 | .unwrap_err(), 290 | Error::IdenticalActions 291 | ); 292 | } 293 | 294 | #[test] 295 | fn test_seccomp_filter_too_large() { 296 | let mut rules: BTreeMap> = BTreeMap::new(); 297 | for _ in 1..1000 { 298 | rules 299 | .entry(1) 300 | .or_default() 301 | .append(&mut vec![SeccompRule::new(vec![Cond::new( 302 | 2, 303 | ArgLen::Dword, 304 | Le, 305 | 14, 306 | ) 307 | .unwrap()]) 308 | .unwrap()]); 309 | } 310 | 311 | let filter = SeccompFilter::new( 312 | rules.into_iter().collect(), 313 | SeccompAction::Allow, 314 | SeccompAction::Trap, 315 | ARCH.try_into().unwrap(), 316 | ) 317 | .unwrap(); 318 | 319 | assert_eq!( 320 | TryInto::::try_into(filter).unwrap_err(), 321 | Error::FilterTooLarge(5002) 322 | ); 323 | } 324 | 325 | #[test] 326 | fn test_empty_filter_output() { 327 | // An empty filter should just validate the architecture and return the mismatch_action. 328 | let mut expected_program = Vec::new(); 329 | expected_program.extend(build_arch_validation_sequence(ARCH.try_into().unwrap())); 330 | expected_program.extend(vec![bpf_stmt(BPF_RET, 0x7fff_0000)]); 331 | 332 | let filter = SeccompFilter::new( 333 | BTreeMap::new(), 334 | SeccompAction::Allow, 335 | SeccompAction::Trap, 336 | ARCH.try_into().unwrap(), 337 | ) 338 | .unwrap(); 339 | let prog: BpfProgram = filter.try_into().unwrap(); 340 | 341 | assert_eq!(expected_program, prog); 342 | } 343 | 344 | #[test] 345 | fn test_filter_bpf_output_dword() { 346 | // Compares translated filter with hardcoded BPF program. 347 | let filter = create_test_bpf_filter(ArgLen::Dword); 348 | 349 | let mut instructions = Vec::new(); 350 | instructions.extend(build_arch_validation_sequence(ARCH.try_into().unwrap())); 351 | instructions.extend(vec![ 352 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 0), 353 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1), 354 | bpf_stmt(BPF_JMP | BPF_JA, 1), 355 | bpf_stmt(BPF_JMP | BPF_JA, 6), 356 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 357 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 3, 0), 358 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 359 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 14, 1, 0), 360 | bpf_stmt(BPF_RET, 0x7fff_0000), 361 | bpf_stmt(BPF_JMP | BPF_JA, 1), 362 | bpf_stmt(BPF_JMP | BPF_JA, 6), 363 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 364 | bpf_jump(BPF_JMP | BPF_JGE | BPF_K, 30, 3, 0), 365 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 366 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 20, 0, 1), 367 | bpf_stmt(BPF_RET, 0x7fff_0000), 368 | bpf_stmt(BPF_JMP | BPF_JA, 1), 369 | bpf_stmt(BPF_JMP | BPF_JA, 4), 370 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 371 | bpf_jump(BPF_JMP | BPF_JGE | BPF_K, 42, 0, 1), 372 | bpf_stmt(BPF_RET, 0x7fff_0000), 373 | bpf_stmt(BPF_RET, 0x0003_0000), 374 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9, 0, 1), 375 | bpf_stmt(BPF_JMP | BPF_JA, 1), 376 | bpf_stmt(BPF_JMP | BPF_JA, 5), 377 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 24), 378 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0b100), 379 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 36 & 0b100, 0, 1), 380 | bpf_stmt(BPF_RET, 0x7fff_0000), 381 | bpf_stmt(BPF_RET, 0x0003_0000), 382 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 0, 1), 383 | bpf_stmt(BPF_JMP | BPF_JA, 1), 384 | bpf_stmt(BPF_JMP | BPF_JA, 2), 385 | bpf_stmt(BPF_RET | BPF_K, 0x7fff_0000), 386 | bpf_stmt(BPF_RET, 0x0003_0000), 387 | bpf_stmt(BPF_RET, 0x0003_0000), 388 | ]); 389 | 390 | let bpfprog: BpfProgram = filter.try_into().unwrap(); 391 | 392 | assert_eq!(bpfprog, instructions); 393 | } 394 | 395 | #[test] 396 | fn test_filter_bpf_output_qword() { 397 | let filter = create_test_bpf_filter(ArgLen::Qword); 398 | 399 | let mut instructions = Vec::new(); 400 | instructions.extend(build_arch_validation_sequence(ARCH.try_into().unwrap())); 401 | instructions.extend(vec![ 402 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 0), 403 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1), 404 | bpf_stmt(BPF_JMP | BPF_JA, 1), 405 | bpf_stmt(BPF_JMP | BPF_JA, 11), 406 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 36), 407 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 2), 408 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 409 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 6, 0), 410 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 36), 411 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 0, 4, 0), 412 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 2), 413 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 414 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 14, 1, 0), 415 | bpf_stmt(BPF_RET, 0x7fff_0000), 416 | bpf_stmt(BPF_JMP | BPF_JA, 1), 417 | bpf_stmt(BPF_JMP | BPF_JA, 12), 418 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 36), 419 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 0, 9, 0), 420 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 2), 421 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 422 | bpf_jump(BPF_JMP | BPF_JGE | BPF_K, 30, 6, 0), 423 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 36), 424 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 0, 3, 0), 425 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 3), 426 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 427 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 20, 0, 1), 428 | bpf_stmt(BPF_RET, 0x7fff_0000), 429 | bpf_stmt(BPF_JMP | BPF_JA, 1), 430 | bpf_stmt(BPF_JMP | BPF_JA, 7), 431 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 36), 432 | bpf_jump(BPF_JMP | BPF_JGT | BPF_K, 0, 3, 0), 433 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 3), 434 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32), 435 | bpf_jump(BPF_JMP | BPF_JGE | BPF_K, 42, 0, 1), 436 | bpf_stmt(BPF_RET, 0x7fff_0000), 437 | bpf_stmt(BPF_RET, 0x0003_0000), 438 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 9, 0, 1), 439 | bpf_stmt(BPF_JMP | BPF_JA, 1), 440 | bpf_stmt(BPF_JMP | BPF_JA, 8), 441 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 28), 442 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0), 443 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 4), 444 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 24), 445 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0b100), 446 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 36 & 0b100, 0, 1), 447 | bpf_stmt(BPF_RET, 0x7fff_0000), 448 | bpf_stmt(BPF_RET, 0x0003_0000), 449 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 0, 1), 450 | bpf_stmt(BPF_JMP | BPF_JA, 1), 451 | bpf_stmt(BPF_JMP | BPF_JA, 2), 452 | bpf_stmt(BPF_RET | BPF_K, 0x7fff_0000), 453 | bpf_stmt(BPF_RET, 0x0003_0000), 454 | bpf_stmt(BPF_RET, 0x0003_0000), 455 | ]); 456 | 457 | let bpfprog: BpfProgram = filter.try_into().unwrap(); 458 | assert_eq!(bpfprog, instructions); 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | //! This module defines the data structures used for the intermmediate representation (IR), 5 | //! as well as the logic for compiling the filter into BPF code, the final form of the filter. 6 | 7 | mod bpf; 8 | mod condition; 9 | mod filter; 10 | mod rule; 11 | 12 | pub use condition::SeccompCondition; 13 | pub use filter::SeccompFilter; 14 | pub use rule::SeccompRule; 15 | 16 | #[cfg(feature = "json")] 17 | use serde::Deserialize; 18 | 19 | use core::fmt::Formatter; 20 | use std::fmt::Display; 21 | 22 | // See /usr/include/linux/seccomp.h 23 | use libc::{ 24 | SECCOMP_RET_ALLOW, SECCOMP_RET_DATA, SECCOMP_RET_ERRNO, SECCOMP_RET_KILL_PROCESS, 25 | SECCOMP_RET_KILL_THREAD, SECCOMP_RET_LOG, SECCOMP_RET_TRACE, SECCOMP_RET_TRAP, 26 | }; 27 | 28 | use bpf::{ARG_NUMBER_MAX, AUDIT_ARCH_AARCH64, AUDIT_ARCH_RISCV64, AUDIT_ARCH_X86_64, BPF_MAX_LEN}; 29 | 30 | pub use bpf::{sock_filter, BpfProgram, BpfProgramRef}; 31 | 32 | /// Backend Result type. 33 | type Result = std::result::Result; 34 | 35 | /// Backend-related errors. 36 | #[derive(Debug, PartialEq, Eq)] 37 | pub enum Error { 38 | /// Attempting to associate an empty vector of conditions to a rule. 39 | EmptyRule, 40 | /// Filter exceeds the maximum number of instructions that a BPF program can have. 41 | FilterTooLarge(usize), 42 | /// Filter and default actions are equal. 43 | IdenticalActions, 44 | /// Argument index of a `SeccompCondition` exceeds the maximum linux syscall index. 45 | InvalidArgumentNumber, 46 | /// Invalid TargetArch. 47 | InvalidTargetArch(String), 48 | } 49 | 50 | impl std::error::Error for Error {} 51 | 52 | impl Display for Error { 53 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 54 | use self::Error::*; 55 | 56 | match self { 57 | EmptyRule => { 58 | write!(f, "The condition vector of a rule cannot be empty.") 59 | } 60 | FilterTooLarge(len) => write!( 61 | f, 62 | "The seccomp filter contains too many BPF instructions: {}. Max length is {}.", 63 | len, BPF_MAX_LEN 64 | ), 65 | IdenticalActions => write!(f, "`match_action` and `mismatch_action` are equal."), 66 | InvalidArgumentNumber => { 67 | write!( 68 | f, 69 | "The seccomp rule contains an invalid argument index. Maximum index value: {}", 70 | ARG_NUMBER_MAX 71 | ) 72 | } 73 | InvalidTargetArch(arch) => write!(f, "Invalid target arch: {}.", arch), 74 | } 75 | } 76 | } 77 | 78 | /// Supported target architectures. 79 | #[allow(non_camel_case_types)] 80 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 81 | pub enum TargetArch { 82 | /// x86_64 arch 83 | x86_64, 84 | /// aarch64 arch 85 | aarch64, 86 | /// riscv64 arch 87 | riscv64, 88 | } 89 | 90 | impl TargetArch { 91 | /// Get the arch audit value. Used for the runtime arch check embedded in the BPF filter. 92 | fn get_audit_value(self) -> u32 { 93 | match self { 94 | TargetArch::x86_64 => AUDIT_ARCH_X86_64, 95 | TargetArch::aarch64 => AUDIT_ARCH_AARCH64, 96 | TargetArch::riscv64 => AUDIT_ARCH_RISCV64, 97 | } 98 | } 99 | } 100 | 101 | impl TryFrom<&str> for TargetArch { 102 | type Error = Error; 103 | fn try_from(input: &str) -> Result { 104 | match input.to_lowercase().as_str() { 105 | "x86_64" => Ok(TargetArch::x86_64), 106 | "aarch64" => Ok(TargetArch::aarch64), 107 | "riscv64" => Ok(TargetArch::riscv64), 108 | _ => Err(Error::InvalidTargetArch(input.to_string())), 109 | } 110 | } 111 | } 112 | 113 | /// Comparison to perform when matching a condition. 114 | #[cfg_attr( 115 | feature = "json", 116 | derive(Deserialize), 117 | serde(rename_all = "snake_case") 118 | )] 119 | #[derive(Clone, Debug, PartialEq, Eq)] 120 | pub enum SeccompCmpOp { 121 | /// Argument value is equal to the specified value. 122 | Eq, 123 | /// Argument value is greater than or equal to the specified value. 124 | Ge, 125 | /// Argument value is greater than specified value. 126 | Gt, 127 | /// Argument value is less than or equal to the specified value. 128 | Le, 129 | /// Argument value is less than specified value. 130 | Lt, 131 | /// Masked bits of argument value are equal to masked bits of specified value. 132 | MaskedEq(u64), 133 | /// Argument value is not equal to specified value. 134 | Ne, 135 | } 136 | 137 | /// Seccomp argument value length. 138 | #[cfg_attr(feature = "json", derive(Deserialize), serde(rename_all = "lowercase"))] 139 | #[derive(Clone, Debug, PartialEq, Eq)] 140 | pub enum SeccompCmpArgLen { 141 | /// Argument value length is 4 bytes. 142 | Dword, 143 | /// Argument value length is 8 bytes. 144 | Qword, 145 | } 146 | 147 | /// Actions that a seccomp filter can return for a syscall. 148 | #[cfg_attr( 149 | feature = "json", 150 | derive(Deserialize), 151 | serde(rename_all = "snake_case") 152 | )] 153 | #[derive(Clone, Debug, PartialEq, Eq)] 154 | pub enum SeccompAction { 155 | /// Allows syscall. 156 | Allow, 157 | /// Returns from syscall with specified error number. 158 | Errno(u32), 159 | /// Kills calling thread. 160 | KillThread, 161 | /// Kills calling process. 162 | KillProcess, 163 | /// Allows syscall after logging it. 164 | Log, 165 | /// Notifies tracing process of the caller with respective number. 166 | Trace(u32), 167 | /// Sends `SIGSYS` to the calling process. 168 | Trap, 169 | } 170 | 171 | impl From for u32 { 172 | /// Return codes of the BPF program for each action. 173 | /// 174 | /// # Arguments 175 | /// 176 | /// * `action` - The [`SeccompAction`] that the kernel will take. 177 | /// 178 | /// [`SeccompAction`]: enum.SeccompAction.html 179 | fn from(action: SeccompAction) -> Self { 180 | match action { 181 | SeccompAction::Allow => SECCOMP_RET_ALLOW, 182 | SeccompAction::Errno(x) => SECCOMP_RET_ERRNO | (x & SECCOMP_RET_DATA), 183 | SeccompAction::KillThread => SECCOMP_RET_KILL_THREAD, 184 | SeccompAction::KillProcess => SECCOMP_RET_KILL_PROCESS, 185 | SeccompAction::Log => SECCOMP_RET_LOG, 186 | SeccompAction::Trace(x) => SECCOMP_RET_TRACE | (x & SECCOMP_RET_DATA), 187 | SeccompAction::Trap => SECCOMP_RET_TRAP, 188 | } 189 | } 190 | } 191 | 192 | #[cfg(test)] 193 | mod tests { 194 | use super::*; 195 | 196 | #[test] 197 | fn test_target_arch() { 198 | assert!(TargetArch::try_from("invalid").is_err()); 199 | assert!(TargetArch::try_from("x8664").is_err()); 200 | 201 | assert_eq!(TargetArch::try_from("x86_64").unwrap(), TargetArch::x86_64); 202 | assert_eq!(TargetArch::try_from("X86_64").unwrap(), TargetArch::x86_64); 203 | 204 | assert_eq!( 205 | TargetArch::try_from("aarch64").unwrap(), 206 | TargetArch::aarch64 207 | ); 208 | assert_eq!( 209 | TargetArch::try_from("aARch64").unwrap(), 210 | TargetArch::aarch64 211 | ); 212 | 213 | assert_eq!( 214 | TargetArch::try_from("riscv64").unwrap(), 215 | TargetArch::riscv64 216 | ); 217 | assert_eq!( 218 | TargetArch::try_from("RiScV64").unwrap(), 219 | TargetArch::riscv64 220 | ); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/backend/rule.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use crate::backend::{bpf::*, condition::SeccompCondition, Error, Result}; 5 | 6 | /// Rule that a filter attempts to match for a syscall. 7 | /// 8 | /// If all conditions match then rule gets matched. 9 | /// A syscall can have many rules associated. If either of them matches, the `match_action` of the 10 | /// [`SeccompFilter`] is triggered. 11 | /// 12 | /// [`SeccompFilter`]: struct.SeccompFilter.html 13 | #[derive(Clone, Debug, PartialEq, Eq)] 14 | pub struct SeccompRule { 15 | /// Conditions of rule that need to match in order for the rule to get matched. 16 | conditions: Vec, 17 | } 18 | 19 | impl SeccompRule { 20 | /// Creates a new rule. Rules with 0 conditions are not allowed; to match a syscall regardless 21 | /// of argument values, map the syscall number to an empty vector of rules when constructing 22 | /// the [`SeccompFilter`](super::SeccompFilter) instead. 23 | /// 24 | /// # Arguments 25 | /// 26 | /// * `conditions` - Vector of [`SeccompCondition`]s that the syscall must match. 27 | /// 28 | /// # Example 29 | /// 30 | /// ``` 31 | /// use seccompiler::{SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompRule}; 32 | /// 33 | /// let rule = SeccompRule::new(vec![ 34 | /// SeccompCondition::new(0, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 1).unwrap(), 35 | /// SeccompCondition::new(1, SeccompCmpArgLen::Dword, SeccompCmpOp::Eq, 1).unwrap(), 36 | /// ]) 37 | /// .unwrap(); 38 | /// ``` 39 | /// 40 | /// [`SeccompCondition`]: struct.SeccompCondition.html 41 | pub fn new(conditions: Vec) -> Result { 42 | let instance = Self { conditions }; 43 | instance.validate()?; 44 | 45 | Ok(instance) 46 | } 47 | 48 | /// Performs semantic checks on the SeccompRule. 49 | fn validate(&self) -> Result<()> { 50 | // Rules with no conditions are not allowed. Syscalls mappings to empty rule vectors are to 51 | // be used instead, for matching only on the syscall number. 52 | if self.conditions.is_empty() { 53 | return Err(Error::EmptyRule); 54 | } 55 | 56 | Ok(()) 57 | } 58 | 59 | /// Appends a condition of the rule to an accumulator. 60 | /// 61 | /// The length of the rule and offset to the next rule are updated. 62 | /// 63 | /// # Arguments 64 | /// 65 | /// * `condition` - The condition added to the rule. 66 | /// * `accumulator` - Accumulator of BPF statements that compose the BPF program. 67 | /// * `rule_len` - Number of conditions in the rule. 68 | /// * `offset` - Offset (in number of BPF statements) to the next rule. 69 | fn append_condition( 70 | condition: SeccompCondition, 71 | accumulator: &mut Vec>, 72 | offset: &mut u8, 73 | ) { 74 | // Tries to detect whether prepending the current condition will produce an unjumpable 75 | // offset (since BPF conditional jumps are a maximum of 255 instructions, which is 76 | // u8::MAX). 77 | if offset.checked_add(CONDITION_MAX_LEN + 1).is_none() { 78 | // If that is the case, three additional helper jumps are prepended and the offset 79 | // is reset to 1. 80 | // 81 | // - The first jump continues the evaluation of the condition chain by jumping to 82 | // the next condition or the action of the rule if the last condition was matched. 83 | // - The second, jumps out of the rule, to the next rule or the default action of 84 | // the filter in case of the last rule in the rule chain of a syscall. 85 | // - The third jumps out of the rule chain of the syscall, to the rule chain of the 86 | // next syscall number to be checked or the default action of the filter in the 87 | // case of the last rule chain. 88 | let helper_jumps = vec![ 89 | bpf_stmt(BPF_JMP | BPF_JA, 2), 90 | bpf_stmt(BPF_JMP | BPF_JA, u32::from(*offset) + 1), 91 | bpf_stmt(BPF_JMP | BPF_JA, u32::from(*offset) + 1), 92 | ]; 93 | accumulator.push(helper_jumps); 94 | *offset = 1; 95 | } 96 | 97 | let condition = condition.into_bpf(*offset); 98 | // Safe to unwrap since we checked that offset + `CONDITION_MAX_LEN` does not overflow. 99 | *offset += u8::try_from(condition.len()).unwrap(); 100 | accumulator.push(condition); 101 | } 102 | } 103 | 104 | impl From for BpfProgram { 105 | fn from(rule: SeccompRule) -> Self { 106 | // Each rule starts with 2 jump statements: 107 | // * The first jump enters the rule, attempting a match. 108 | // * The second one jumps out of the rule, into the next rule of the syscall or to the 109 | // default action if none of the rules were matched. 110 | 111 | // Rule is built backwards, because SeccompConditions need to know the jump offset to the 112 | // next rule, when compiled to BPF. 113 | let mut accumulator = Vec::with_capacity( 114 | rule.conditions 115 | .len() 116 | // Realistically, this overflow should never happen. 117 | // If the nr of statements ever overflows `usize`, the rust vector allocation would 118 | // anyway fail. 119 | .checked_mul(CONDITION_MAX_LEN as usize) 120 | .unwrap(), 121 | ); 122 | let mut offset = 1; 123 | 124 | // Conditions are translated into BPF statements and prepended to the rule. 125 | rule.conditions.into_iter().for_each(|condition| { 126 | SeccompRule::append_condition(condition, &mut accumulator, &mut offset) 127 | }); 128 | 129 | // The two initial jump statements are prepended to the rule. 130 | accumulator.push(vec![ 131 | bpf_stmt(BPF_JMP | BPF_JA, 1), 132 | bpf_stmt(BPF_JMP | BPF_JA, u32::from(offset) + 1), 133 | ]); 134 | 135 | // Finally, builds the translated rule by reversing and consuming the accumulator. 136 | let mut result = Vec::new(); 137 | accumulator 138 | .into_iter() 139 | .rev() 140 | .for_each(|mut instructions| result.append(&mut instructions)); 141 | 142 | result 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::SeccompRule; 149 | use crate::backend::bpf::*; 150 | use crate::backend::{ 151 | Error, SeccompCmpArgLen as ArgLen, SeccompCmpOp::*, SeccompCondition as Cond, 152 | }; 153 | 154 | #[test] 155 | fn test_validate_rule() { 156 | assert_eq!(SeccompRule::new(vec![]).unwrap_err(), Error::EmptyRule); 157 | } 158 | 159 | // Checks that rule gets translated correctly into BPF statements. 160 | #[test] 161 | fn test_rule_bpf_output() { 162 | let rule = SeccompRule::new(vec![ 163 | Cond::new(0, ArgLen::Dword, Eq, 1).unwrap(), 164 | Cond::new(2, ArgLen::Qword, MaskedEq(0b1010), 14).unwrap(), 165 | ]) 166 | .unwrap(); 167 | 168 | let (msb_offset, lsb_offset) = { (4, 0) }; 169 | 170 | // Builds hardcoded BPF instructions. 171 | let instructions = vec![ 172 | bpf_stmt(BPF_JMP | BPF_JA, 1), // Start evaluating the rule. 173 | bpf_stmt(BPF_JMP | BPF_JA, 10), // Jump to the next rule. 174 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32 + msb_offset), 175 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0), 176 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 6), 177 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 32 + lsb_offset), 178 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0b1010), 179 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 14 & 0b1010, 0, 3), 180 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + lsb_offset), 181 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, 1), 182 | ]; 183 | // In a filter, these instructions would follow: 184 | // RET match_action 185 | // OTHER RULES... 186 | // RET mismatch_action. (if the syscall number matched and then all rules fail to match) 187 | // RET default action. (if no syscall number matched) 188 | 189 | // Compares translated rule with hardcoded BPF instructions. 190 | let bpfprog: BpfProgram = rule.into(); 191 | assert_eq!(bpfprog, instructions); 192 | } 193 | 194 | // Checks that rule with too many conditions gets translated correctly into BPF statements 195 | // using three helper jumps. 196 | #[test] 197 | fn test_rule_many_conditions_bpf_output() { 198 | // Builds rule. 199 | let mut conditions = Vec::with_capacity(43); 200 | for _ in 0..42 { 201 | conditions.push(Cond::new(0, ArgLen::Qword, MaskedEq(0), 0).unwrap()); 202 | } 203 | conditions.push(Cond::new(0, ArgLen::Qword, Eq, 0).unwrap()); 204 | let rule = SeccompRule::new(conditions).unwrap(); 205 | 206 | let (msb_offset, lsb_offset) = { (4, 0) }; 207 | 208 | // Builds hardcoded BPF instructions. 209 | let mut instructions = vec![ 210 | bpf_stmt(BPF_JMP | BPF_JA, 1), // Start evaluating the rule. 211 | bpf_stmt(BPF_JMP | BPF_JA, 6), // Jump to the next rule. Actually to a helper jump. 212 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + msb_offset), 213 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 3), 214 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + lsb_offset), 215 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1), 216 | bpf_stmt(BPF_JMP | BPF_JA, 2), 217 | bpf_stmt(BPF_JMP | BPF_JA, 254), 218 | bpf_stmt(BPF_JMP | BPF_JA, 254), 219 | ]; 220 | let mut offset = 253; 221 | for _ in 0..42 { 222 | offset -= 6; 223 | // Add the rest of the `MaskedEq` conditions. 224 | instructions.append(&mut vec![ 225 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + msb_offset), 226 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0), 227 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, offset + 3), 228 | bpf_stmt(BPF_LD | BPF_W | BPF_ABS, 16 + lsb_offset), 229 | bpf_stmt(BPF_ALU | BPF_AND | BPF_K, 0), 230 | bpf_jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, offset), 231 | ]); 232 | } 233 | 234 | // Compares translated rule with hardcoded BPF instructions. 235 | let bpfprog: BpfProgram = rule.into(); 236 | assert_eq!(bpfprog, instructions); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/frontend/json.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | //! Module defining the logic for compiling the deserialized json file into 5 | //! the IR. (Intermediate Representation) 6 | //! 7 | //! It also defines some of the objects that a JSON seccomp filter is deserialized into: 8 | //! [`JsonFilter`](struct.JsonFilter.html), 9 | //! [`JsonRule`](struct.JsonRule.html), 10 | //! [`JsonCondition`](struct.JsonCondition.html). 11 | // 12 | //! The rest of objects are deserialized directly into the IR : 13 | //! [`SeccompCondition`](struct.SeccompCondition.html), 14 | //! [`SeccompAction`](enum.SeccompAction.html), 15 | //! [`SeccompCmpOp`](enum.SeccompCmpOp.html), 16 | //! [`SeccompCmpArgLen`](enum.SeccompCmpArgLen.html). 17 | 18 | use std::collections::{BTreeMap, HashMap}; 19 | use std::fmt; 20 | use std::io::Read; 21 | use std::result; 22 | 23 | use crate::backend::{ 24 | Error as BackendError, SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, 25 | SeccompFilter, SeccompRule, TargetArch, 26 | }; 27 | use crate::syscall_table::SyscallTable; 28 | use serde::de::{self, Error as _, MapAccess, Visitor}; 29 | use serde::{Deserialize, Deserializer}; 30 | 31 | type Result = result::Result; 32 | 33 | /// Error compiling JSON into IR. 34 | #[derive(Debug)] 35 | pub enum Error { 36 | /// Backend error creating the `SeccompFilter` IR. 37 | Backend(BackendError), 38 | /// Error deserializing JSON. 39 | SerdeJson(serde_json::Error), 40 | /// Invalid syscall name for the given arch. 41 | SyscallName(String, TargetArch), 42 | } 43 | 44 | impl std::error::Error for Error { 45 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 46 | use self::Error::*; 47 | 48 | match self { 49 | Backend(error) => Some(error), 50 | SerdeJson(error) => Some(error), 51 | _ => None, 52 | } 53 | } 54 | } 55 | 56 | impl fmt::Display for Error { 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | use self::Error::*; 59 | 60 | match self { 61 | Backend(error) => write!(f, "{}", error), 62 | SerdeJson(error) => { 63 | write!(f, "Error parsing Json: {}", error) 64 | } 65 | SyscallName(syscall_name, arch) => write!( 66 | f, 67 | "Invalid syscall name: {} for given arch: {:?}.", 68 | syscall_name, arch 69 | ), 70 | } 71 | } 72 | } 73 | 74 | /// Deserializable object that represents the top-level map of Json Filters. 75 | // Need the 'newtype' pattern so that we can implement a custom deserializer. 76 | pub(crate) struct JsonFilterMap(pub HashMap); 77 | 78 | // Implement a custom deserializer, that returns an error for duplicate thread keys. 79 | impl<'de> Deserialize<'de> for JsonFilterMap { 80 | fn deserialize(deserializer: D) -> result::Result 81 | where 82 | D: de::Deserializer<'de>, 83 | { 84 | struct JsonFilterMapVisitor; 85 | 86 | impl<'d> Visitor<'d> for JsonFilterMapVisitor { 87 | type Value = HashMap; 88 | 89 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> result::Result<(), fmt::Error> { 90 | f.write_str("a map of filters") 91 | } 92 | 93 | fn visit_map(self, mut access: M) -> result::Result 94 | where 95 | M: MapAccess<'d>, 96 | { 97 | let mut values = Self::Value::with_capacity(access.size_hint().unwrap_or(0)); 98 | 99 | while let Some((key, value)) = access.next_entry()? { 100 | if values.insert(key, value).is_some() { 101 | return Err(M::Error::custom("duplicate filter key")); 102 | }; 103 | } 104 | 105 | Ok(values) 106 | } 107 | } 108 | Ok(JsonFilterMap( 109 | deserializer.deserialize_map(JsonFilterMapVisitor)?, 110 | )) 111 | } 112 | } 113 | 114 | /// Dummy placeholder type for a JSON comment. Holds no value. 115 | /// Used for adding comments in the JSON file, since the standard does not allow for native 116 | /// comments. 117 | /// This type declaration is needed so that we can implement a custom deserializer for it. 118 | #[derive(PartialEq, Debug, Clone)] 119 | struct JsonComment; 120 | 121 | // Implement a custom deserializer that only validates that the comment is a string and drops the 122 | // value. 123 | impl<'de> Deserialize<'de> for JsonComment { 124 | fn deserialize(deserializer: D) -> std::result::Result 125 | where 126 | D: Deserializer<'de>, 127 | { 128 | String::deserialize(deserializer)?; 129 | 130 | Ok(JsonComment {}) 131 | } 132 | } 133 | 134 | /// Condition that a syscall must match in order to satisfy a rule. 135 | // Almost equivalent to the [`SeccompCondition`](struct.html.SeccompCondition), with the added 136 | // optional json `comment` property. 137 | #[derive(Clone, Debug, PartialEq, Deserialize)] 138 | #[serde(deny_unknown_fields)] 139 | pub(crate) struct JsonCondition { 140 | /// Index of the argument that is to be compared. 141 | #[serde(rename = "index")] 142 | arg_index: u8, 143 | /// Length of the argument value that is to be compared. 144 | #[serde(rename = "type")] 145 | arg_len: SeccompCmpArgLen, 146 | /// Comparison operator to perform. 147 | #[serde(rename = "op")] 148 | operator: SeccompCmpOp, 149 | /// The value that will be compared with the argument value of the syscall. 150 | #[serde(rename = "val")] 151 | value: u64, 152 | /// Optional empty value, represents a `comment` property in the JSON file. 153 | comment: Option, 154 | } 155 | 156 | impl TryFrom for SeccompCondition { 157 | type Error = Error; 158 | 159 | fn try_from(json_cond: JsonCondition) -> Result { 160 | SeccompCondition::new( 161 | json_cond.arg_index, 162 | json_cond.arg_len, 163 | json_cond.operator, 164 | json_cond.value, 165 | ) 166 | .map_err(Error::Backend) 167 | } 168 | } 169 | 170 | /// Deserializable object representing a rule associated to a syscall. 171 | #[derive(Debug, Deserialize, PartialEq, Clone)] 172 | #[serde(deny_unknown_fields)] 173 | pub(crate) struct JsonRule { 174 | /// Name of the syscall. 175 | syscall: String, 176 | /// Rule conditions. 177 | #[serde(rename = "args")] 178 | conditions: Option>, 179 | /// Optional empty value, represents a `comment` property in the JSON file. 180 | comment: Option, 181 | } 182 | 183 | /// Deserializable seccomp filter. 184 | #[derive(Deserialize, PartialEq, Debug, Clone)] 185 | #[serde(deny_unknown_fields)] 186 | pub(crate) struct JsonFilter { 187 | /// Default action if no rules match. e.g. `Kill` for an AllowList. 188 | #[serde(alias = "default_action")] 189 | mismatch_action: SeccompAction, 190 | /// Default action if a rule matches. e.g. `Allow` for an AllowList. 191 | #[serde(alias = "filter_action")] 192 | match_action: SeccompAction, 193 | /// The collection of `JsonRule`s. 194 | #[serde(rename = "filter")] 195 | rules: Vec, 196 | } 197 | 198 | /// Object responsible for compiling [`JsonFilter`](struct.JsonFilter.html)s into 199 | /// [`SeccompFilter`](../backend/struct.SeccompFilter.html)s, which represent the IR. 200 | pub(crate) struct JsonCompiler { 201 | /// Target architecture. Can be different from the current `target_arch`. 202 | arch: TargetArch, 203 | /// Target-specific syscall table. 204 | syscall_table: SyscallTable, 205 | } 206 | 207 | impl JsonCompiler { 208 | /// Create a new `Compiler` instance, for the given target architecture. 209 | pub fn new(arch: TargetArch) -> Self { 210 | Self { 211 | arch, 212 | syscall_table: SyscallTable::new(arch), 213 | } 214 | } 215 | 216 | /// Main compilation function. 217 | // This can easily be extracted to a Frontend trait if seccompiler will need to support 218 | // multiple frontend types (YAML, etc.) 219 | pub fn compile(&self, reader: R) -> Result> { 220 | let filters: JsonFilterMap = serde_json::from_reader(reader).map_err(Error::SerdeJson)?; 221 | let filters = filters.0; 222 | let mut bpf_map: HashMap = HashMap::with_capacity(filters.len()); 223 | 224 | for (name, filter) in filters.into_iter() { 225 | bpf_map.insert(name, self.make_seccomp_filter(filter)?); 226 | } 227 | Ok(bpf_map) 228 | } 229 | 230 | /// Transforms the deserialized `JsonFilter` into a `SeccompFilter` (IR language). 231 | fn make_seccomp_filter(&self, filter: JsonFilter) -> Result { 232 | let mut rule_map: BTreeMap> = BTreeMap::new(); 233 | 234 | for json_rule in filter.rules { 235 | let syscall_name = json_rule.syscall; 236 | let syscall_nr = self 237 | .syscall_table 238 | .get_syscall_nr(&syscall_name) 239 | .ok_or_else(|| Error::SyscallName(syscall_name.clone(), self.arch))?; 240 | let rule_accumulator = rule_map.entry(syscall_nr).or_default(); 241 | 242 | if let Some(conditions) = json_rule.conditions { 243 | let mut seccomp_conditions = Vec::with_capacity(conditions.len()); 244 | for condition in conditions { 245 | seccomp_conditions.push(condition.try_into()?); 246 | } 247 | rule_accumulator 248 | .push(SeccompRule::new(seccomp_conditions).map_err(Error::Backend)?); 249 | } 250 | } 251 | 252 | SeccompFilter::new( 253 | rule_map, 254 | filter.mismatch_action, 255 | filter.match_action, 256 | self.arch, 257 | ) 258 | .map_err(Error::Backend) 259 | } 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use super::{Error, JsonCompiler, JsonCondition, JsonFilter, JsonRule}; 265 | use crate::backend::{ 266 | Error as BackendError, SeccompAction, SeccompCmpArgLen, SeccompCmpArgLen::*, SeccompCmpOp, 267 | SeccompCmpOp::*, SeccompCondition as Cond, SeccompFilter, SeccompRule, 268 | }; 269 | use std::collections::HashMap; 270 | use std::convert::TryInto; 271 | use std::env::consts::ARCH; 272 | 273 | impl JsonFilter { 274 | pub fn new( 275 | mismatch_action: SeccompAction, 276 | match_action: SeccompAction, 277 | rules: Vec, 278 | ) -> JsonFilter { 279 | JsonFilter { 280 | mismatch_action, 281 | match_action, 282 | rules, 283 | } 284 | } 285 | } 286 | 287 | impl JsonRule { 288 | pub fn new(syscall: String, conditions: Option>) -> JsonRule { 289 | JsonRule { 290 | syscall, 291 | conditions, 292 | comment: None, 293 | } 294 | } 295 | } 296 | 297 | impl JsonCondition { 298 | pub fn new( 299 | arg_index: u8, 300 | arg_len: SeccompCmpArgLen, 301 | operator: SeccompCmpOp, 302 | value: u64, 303 | ) -> Self { 304 | Self { 305 | arg_index, 306 | arg_len, 307 | operator, 308 | value, 309 | comment: None, 310 | } 311 | } 312 | } 313 | 314 | #[test] 315 | // Test the transformation of `JsonFilter` objects into `SeccompFilter` objects. (JSON to IR) 316 | fn test_make_seccomp_filter() { 317 | let compiler = JsonCompiler::new(ARCH.try_into().unwrap()); 318 | 319 | // Test with malformed filters. 320 | let wrong_syscall_name_filter = JsonFilter::new( 321 | SeccompAction::Trap, 322 | SeccompAction::Allow, 323 | vec![JsonRule::new("wrong_syscall".to_string(), None)], 324 | ); 325 | 326 | assert!(matches!( 327 | compiler 328 | .make_seccomp_filter(wrong_syscall_name_filter) 329 | .unwrap_err(), 330 | Error::SyscallName(_, _) 331 | )); 332 | 333 | // Test that `SeccompConditions` validations are triggered and caught by the compilation. 334 | let wrong_arg_index_filter = JsonFilter::new( 335 | SeccompAction::Allow, 336 | SeccompAction::Trap, 337 | vec![JsonRule::new( 338 | "futex".to_string(), 339 | Some(vec![JsonCondition::new(8, Dword, Le, 65)]), 340 | )], 341 | ); 342 | 343 | assert!(matches!( 344 | compiler 345 | .make_seccomp_filter(wrong_arg_index_filter) 346 | .unwrap_err(), 347 | Error::Backend(BackendError::InvalidArgumentNumber) 348 | )); 349 | 350 | // Test that `SeccompRule` validations are triggered and caught by the compilation. 351 | let empty_rule_filter = JsonFilter::new( 352 | SeccompAction::Allow, 353 | SeccompAction::Trap, 354 | vec![JsonRule::new("read".to_string(), Some(vec![]))], 355 | ); 356 | 357 | assert!(matches!( 358 | compiler.make_seccomp_filter(empty_rule_filter).unwrap_err(), 359 | Error::Backend(BackendError::EmptyRule) 360 | )); 361 | 362 | // Test that `SeccompFilter` validations are triggered and caught by the compilation. 363 | let wrong_syscall_name_filter = JsonFilter::new( 364 | SeccompAction::Allow, 365 | SeccompAction::Allow, 366 | vec![JsonRule::new("read".to_string(), None)], 367 | ); 368 | 369 | assert!(matches!( 370 | compiler 371 | .make_seccomp_filter(wrong_syscall_name_filter) 372 | .unwrap_err(), 373 | Error::Backend(BackendError::IdenticalActions) 374 | )); 375 | 376 | // Test a well-formed filter. 377 | let filter = JsonFilter::new( 378 | SeccompAction::Trap, 379 | SeccompAction::Allow, 380 | vec![ 381 | JsonRule::new("read".to_string(), None), 382 | JsonRule::new( 383 | "futex".to_string(), 384 | Some(vec![ 385 | JsonCondition::new(2, Dword, Le, 65), 386 | JsonCondition::new(1, Qword, Ne, 80), 387 | ]), 388 | ), 389 | JsonRule::new( 390 | "futex".to_string(), 391 | Some(vec![ 392 | JsonCondition::new(3, Qword, Gt, 65), 393 | JsonCondition::new(1, Qword, Lt, 80), 394 | ]), 395 | ), 396 | JsonRule::new( 397 | "futex".to_string(), 398 | Some(vec![JsonCondition::new(3, Qword, Ge, 65)]), 399 | ), 400 | JsonRule::new( 401 | "ioctl".to_string(), 402 | Some(vec![JsonCondition::new(3, Dword, MaskedEq(100), 65)]), 403 | ), 404 | ], 405 | ); 406 | 407 | // The expected IR. 408 | let seccomp_filter = SeccompFilter::new( 409 | vec![ 410 | ( 411 | compiler.syscall_table.get_syscall_nr("read").unwrap(), 412 | vec![], 413 | ), 414 | ( 415 | compiler.syscall_table.get_syscall_nr("futex").unwrap(), 416 | vec![ 417 | SeccompRule::new(vec![ 418 | Cond::new(2, Dword, Le, 65).unwrap(), 419 | Cond::new(1, Qword, Ne, 80).unwrap(), 420 | ]) 421 | .unwrap(), 422 | SeccompRule::new(vec![ 423 | Cond::new(3, Qword, Gt, 65).unwrap(), 424 | Cond::new(1, Qword, Lt, 80).unwrap(), 425 | ]) 426 | .unwrap(), 427 | SeccompRule::new(vec![Cond::new(3, Qword, Ge, 65).unwrap()]).unwrap(), 428 | ], 429 | ), 430 | ( 431 | compiler.syscall_table.get_syscall_nr("ioctl").unwrap(), 432 | vec![ 433 | SeccompRule::new(vec![Cond::new(3, Dword, MaskedEq(100), 65).unwrap()]) 434 | .unwrap(), 435 | ], 436 | ), 437 | ] 438 | .into_iter() 439 | .collect(), 440 | SeccompAction::Trap, 441 | SeccompAction::Allow, 442 | ARCH.try_into().unwrap(), 443 | ) 444 | .unwrap(); 445 | 446 | assert_eq!( 447 | compiler.make_seccomp_filter(filter).unwrap(), 448 | seccomp_filter 449 | ); 450 | } 451 | 452 | #[allow(clippy::useless_asref)] 453 | #[test] 454 | fn test_compile() { 455 | let compiler = JsonCompiler::new(ARCH.try_into().unwrap()); 456 | // test with malformed JSON 457 | { 458 | // empty file 459 | let json_input = ""; 460 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 461 | 462 | // not json 463 | let json_input = "hjkln"; 464 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 465 | 466 | // top-level array 467 | let json_input = "[]"; 468 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 469 | 470 | // thread key must be a string 471 | let json_input = "{1}"; 472 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 473 | 474 | // empty Filter object 475 | let json_input = r#"{"a": {}}"#; 476 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 477 | 478 | // missing 'filter' field 479 | let json_input = r#"{"a": {"match_action": "allow", "mismatch_action":"log"}}"#; 480 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 481 | 482 | // wrong key 'filters' 483 | let json_input = 484 | r#"{"a": {"match_action": "allow", "mismatch_action":"log", "filters": []}}"#; 485 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 486 | 487 | // wrong action 'logs' 488 | let json_input = 489 | r#"{"a": {"match_action": "allow", "mismatch_action":"logs", "filter": []}}"#; 490 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 491 | 492 | // duplicate action fields using aliases 493 | let json_input = r#"{ 494 | "a": { 495 | "match_action": "allow", 496 | "mismatch_action":"log", 497 | "filter_action": "trap", 498 | "filter": [] 499 | } 500 | }"#; 501 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 502 | 503 | // action that expects a value 504 | let json_input = 505 | r#"{"a": {"match_action": "allow", "mismatch_action":"errno", "filter": []}}"#; 506 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 507 | 508 | // overflowing u64 value 509 | let json_input = r#" 510 | { 511 | "thread_2": { 512 | "mismatch_action": "trap", 513 | "match_action": "allow", 514 | "filter": [ 515 | { 516 | "syscall": "ioctl", 517 | "args": [ 518 | { 519 | "index": 3, 520 | "type": "qword", 521 | "op": "eq", 522 | "val": 18446744073709551616 523 | } 524 | ] 525 | } 526 | ] 527 | } 528 | } 529 | "#; 530 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 531 | 532 | // negative integer value 533 | let json_input = r#" 534 | { 535 | "thread_2": { 536 | "mismatch_action": "trap", 537 | "match_action": "allow", 538 | "filter": [ 539 | { 540 | "syscall": "ioctl", 541 | "args": [ 542 | { 543 | "index": 3, 544 | "type": "qword", 545 | "op": "eq", 546 | "val": -1846 547 | } 548 | ] 549 | } 550 | ] 551 | } 552 | } 553 | "#; 554 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 555 | 556 | // float value 557 | let json_input = r#" 558 | { 559 | "thread_2": { 560 | "mismatch_action": "trap", 561 | "match_action": "allow", 562 | "filter": [ 563 | { 564 | "syscall": "ioctl", 565 | "args": [ 566 | { 567 | "index": 3, 568 | "type": "qword", 569 | "op": "eq", 570 | "val": 1846.4 571 | } 572 | ] 573 | } 574 | ] 575 | } 576 | } 577 | "#; 578 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 579 | 580 | // invalid comment 581 | let json_input = r#" 582 | { 583 | "thread_2": { 584 | "mismatch_action": "trap", 585 | "match_action": "allow", 586 | "filter": [ 587 | { 588 | "syscall": "ioctl", 589 | "args": [ 590 | { 591 | "index": 3, 592 | "type": "qword", 593 | "op": "eq", 594 | "val": 14, 595 | "comment": 15 596 | } 597 | ] 598 | } 599 | ] 600 | } 601 | } 602 | "#; 603 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 604 | 605 | // duplicate filter keys 606 | let json_input = r#" 607 | { 608 | "thread_1": { 609 | "mismatch_action": "trap", 610 | "match_action": "allow", 611 | "filter": [] 612 | }, 613 | "thread_1": { 614 | "mismatch_action": "trap", 615 | "match_action": "allow", 616 | "filter": [] 617 | } 618 | } 619 | "#; 620 | assert!(compiler.compile(json_input.as_bytes()).is_err()); 621 | } 622 | 623 | // test with correctly formed JSON 624 | { 625 | // empty JSON file 626 | let json_input = "{}"; 627 | 628 | assert_eq!(compiler.compile(json_input.as_bytes()).unwrap().len(), 0); 629 | 630 | // empty Filter 631 | let json_input = 632 | r#"{"a": {"match_action": "allow", "mismatch_action":"log", "filter": []}}"#; 633 | assert!(compiler.compile(json_input.as_bytes()).is_ok()); 634 | 635 | // action fields using aliases 636 | let json_input = r#"{ 637 | "a": { 638 | "default_action":"log", 639 | "filter_action": "allow", 640 | "filter": [] 641 | } 642 | }"#; 643 | let filter_with_aliases = compiler.compile(json_input.as_bytes()).unwrap(); 644 | let json_input = r#"{ 645 | "a": { 646 | "mismatch_action":"log", 647 | "match_action": "allow", 648 | "filter": [] 649 | } 650 | }"#; 651 | let filter_without_aliases = compiler.compile(json_input.as_bytes()).unwrap(); 652 | assert_eq!( 653 | filter_with_aliases.get("a").unwrap(), 654 | filter_without_aliases.get("a").unwrap() 655 | ); 656 | 657 | // action fields using combined action fields (with and without aliases) 658 | let json_input = r#"{ 659 | "a": { 660 | "default_action":"log", 661 | "filter_action": "allow", 662 | "filter": [] 663 | } 664 | }"#; 665 | let filter_without_aliases = compiler.compile(json_input.as_bytes()).unwrap(); 666 | assert_eq!( 667 | filter_with_aliases.get("a").unwrap(), 668 | filter_without_aliases.get("a").unwrap() 669 | ); 670 | 671 | // correctly formed JSON filter 672 | let json_input = r#" 673 | { 674 | "thread_1": { 675 | "mismatch_action": { 676 | "errno": 12 677 | }, 678 | "match_action": "allow", 679 | "filter": [ 680 | { 681 | "syscall": "openat" 682 | }, 683 | { 684 | "syscall": "close" 685 | }, 686 | { 687 | "syscall": "read" 688 | }, 689 | { 690 | "syscall": "futex", 691 | "args": [ 692 | { 693 | "index": 2, 694 | "type": "dword", 695 | "op": "le", 696 | "val": 65 697 | }, 698 | { 699 | "index": 1, 700 | "type": "qword", 701 | "op": "ne", 702 | "val": 80 703 | } 704 | ] 705 | }, 706 | { 707 | "syscall": "futex", 708 | "args": [ 709 | { 710 | "index": 3, 711 | "type": "qword", 712 | "op": "gt", 713 | "val": 65 714 | }, 715 | { 716 | "index": 1, 717 | "type": "qword", 718 | "op": "lt", 719 | "val": 80 720 | } 721 | ] 722 | }, 723 | { 724 | "syscall": "futex", 725 | "args": [ 726 | { 727 | "index": 3, 728 | "type": "qword", 729 | "op": "ge", 730 | "val": 65, 731 | "comment": "dummy comment" 732 | } 733 | ] 734 | }, 735 | { 736 | "syscall": "ioctl", 737 | "args": [ 738 | { 739 | "index": 3, 740 | "type": "dword", 741 | "op": { 742 | "masked_eq": 100 743 | }, 744 | "val": 65 745 | } 746 | ] 747 | } 748 | ] 749 | }, 750 | "thread_2": { 751 | "mismatch_action": "trap", 752 | "match_action": "allow", 753 | "filter": [ 754 | { 755 | "syscall": "ioctl", 756 | "comment": "dummy comment", 757 | "args": [ 758 | { 759 | "index": 3, 760 | "type": "dword", 761 | "op": "eq", 762 | "val": 65, 763 | "comment": "dummy comment" 764 | } 765 | ] 766 | } 767 | ] 768 | } 769 | } 770 | "#; 771 | // safe because we know the string is UTF-8 772 | 773 | let mut filters = HashMap::new(); 774 | filters.insert( 775 | "thread_1".to_string(), 776 | SeccompFilter::new( 777 | vec![ 778 | (libc::SYS_openat, vec![]), 779 | (libc::SYS_close, vec![]), 780 | (libc::SYS_read, vec![]), 781 | ( 782 | libc::SYS_futex, 783 | vec![ 784 | SeccompRule::new(vec![ 785 | Cond::new(2, Dword, Le, 65).unwrap(), 786 | Cond::new(1, Qword, Ne, 80).unwrap(), 787 | ]) 788 | .unwrap(), 789 | SeccompRule::new(vec![ 790 | Cond::new(3, Qword, Gt, 65).unwrap(), 791 | Cond::new(1, Qword, Lt, 80).unwrap(), 792 | ]) 793 | .unwrap(), 794 | SeccompRule::new(vec![Cond::new(3, Qword, Ge, 65).unwrap()]) 795 | .unwrap(), 796 | ], 797 | ), 798 | ( 799 | libc::SYS_ioctl, 800 | vec![SeccompRule::new(vec![ 801 | Cond::new(3, Dword, MaskedEq(100), 65).unwrap() 802 | ]) 803 | .unwrap()], 804 | ), 805 | ] 806 | .into_iter() 807 | .collect(), 808 | SeccompAction::Errno(12), 809 | SeccompAction::Allow, 810 | ARCH.try_into().unwrap(), 811 | ) 812 | .unwrap(), 813 | ); 814 | 815 | filters.insert( 816 | "thread_2".to_string(), 817 | SeccompFilter::new( 818 | vec![( 819 | libc::SYS_ioctl, 820 | vec![SeccompRule::new(vec![Cond::new(3, Dword, Eq, 65).unwrap()]).unwrap()], 821 | )] 822 | .into_iter() 823 | .collect(), 824 | SeccompAction::Trap, 825 | SeccompAction::Allow, 826 | ARCH.try_into().unwrap(), 827 | ) 828 | .unwrap(), 829 | ); 830 | 831 | // sort the HashMaps by key and transform into vectors, to make comparison possible 832 | let mut v1: Vec<_> = filters.into_iter().collect(); 833 | v1.sort_by(|x, y| x.0.cmp(&y.0)); 834 | 835 | let mut v2: Vec<_> = compiler 836 | .compile(json_input.as_bytes()) 837 | .unwrap() 838 | .into_iter() 839 | .collect(); 840 | v2.sort_by(|x, y| x.0.cmp(&y.0)); 841 | assert_eq!(v1, v2); 842 | } 843 | } 844 | } 845 | -------------------------------------------------------------------------------- /src/frontend/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | pub mod json; 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | #![deny(missing_docs)] 4 | #![cfg(target_endian = "little")] 5 | //! Provides easy-to-use Linux seccomp-bpf jailing. 6 | //! 7 | //! Seccomp is a Linux kernel security feature which enables a tight control over what kernel-level 8 | //! mechanisms a process has access to. This is typically used to reduce the attack surface and 9 | //! exposed resources when running untrusted code. This works by allowing users to write and set a 10 | //! BPF (Berkeley Packet Filter) program for each process or thread, that intercepts syscalls and 11 | //! decides whether the syscall is safe to execute. 12 | //! 13 | //! Writing BPF programs by hand is difficult and error-prone. This crate provides high-level 14 | //! wrappers for working with system call filtering. 15 | //! 16 | //! The core concept of the library is the filter. It is an abstraction that 17 | //! models a collection of syscall-mapped rules, coupled with on-match and 18 | //! default actions, that logically describes a policy for dispatching actions 19 | //! (e.g. Allow, Trap, Errno) for incoming system calls. 20 | //! 21 | //! Seccompiler provides constructs for defining filters, compiling them into 22 | //! loadable BPF programs and installing them in the kernel. 23 | //! 24 | //! Filters are defined either with a JSON file or using Rust code, with 25 | //! library-defined structures. Both representations are semantically equivalent 26 | //! and model the rules of the filter. Choosing one or the other depends on the use 27 | //! case and preference. 28 | //! 29 | //! # Supported platforms 30 | //! 31 | //! Due to the fact that seccomp is a Linux-specific feature, this crate is 32 | //! supported only on Linux systems. 33 | //! 34 | //! Supported host architectures: 35 | //! - Little-endian x86_64 36 | //! - Little-endian aarch64 37 | //! - Little-endian riscv64 38 | //! 39 | //! # Terminology 40 | //! 41 | //! The smallest unit of the [`SeccompFilter`] is the [`SeccompCondition`], which is a 42 | //! comparison operation applied to the current system call. It’s parametrised by 43 | //! the argument index, the length of the argument, the operator and the actual 44 | //! expected value. 45 | //! 46 | //! Going one step further, a [`SeccompRule`] is a vector of [`SeccompCondition`]s, 47 | //! that must all match for the rule to be considered matched. In other words, a 48 | //! rule is a collection of **and-bound** conditions for a system call. 49 | //! 50 | //! Finally, at the top level, there’s the [`SeccompFilter`]. The filter can be 51 | //! viewed as a collection of syscall-associated rules, with a predefined on-match 52 | //! [`SeccompAction`] and a default [`SeccompAction`] that is returned if none of the rules match. 53 | //! 54 | //! In a filter, each system call number maps to a vector of **or-bound** rules. 55 | //! In order for the filter to match, it is enough that one rule associated to the 56 | //! system call matches. A system call may also map to an empty rule vector, which 57 | //! means that the system call will match, regardless of the actual arguments. 58 | //! 59 | //! # Examples 60 | //! 61 | //! The following example defines and installs a simple Rust filter, that sends SIGSYS for 62 | //! `accept4`, `fcntl(any, F_SETFD, FD_CLOEXEC, ..)` and `fcntl(any, F_GETFD, ...)`. 63 | //! It allows any other syscalls. 64 | //! 65 | //! ``` 66 | //! use seccompiler::{ 67 | //! BpfProgram, SeccompAction, SeccompCmpArgLen, SeccompCmpOp, SeccompCondition, SeccompFilter, 68 | //! SeccompRule, 69 | //! }; 70 | //! use std::convert::TryInto; 71 | //! 72 | //! let filter: BpfProgram = SeccompFilter::new( 73 | //! vec![ 74 | //! (libc::SYS_accept4, vec![]), 75 | //! ( 76 | //! libc::SYS_fcntl, 77 | //! vec![ 78 | //! SeccompRule::new(vec![ 79 | //! SeccompCondition::new( 80 | //! 1, 81 | //! SeccompCmpArgLen::Dword, 82 | //! SeccompCmpOp::Eq, 83 | //! libc::F_SETFD as u64, 84 | //! ) 85 | //! .unwrap(), 86 | //! SeccompCondition::new( 87 | //! 2, 88 | //! SeccompCmpArgLen::Dword, 89 | //! SeccompCmpOp::Eq, 90 | //! libc::FD_CLOEXEC as u64, 91 | //! ) 92 | //! .unwrap(), 93 | //! ]) 94 | //! .unwrap(), 95 | //! SeccompRule::new(vec![SeccompCondition::new( 96 | //! 1, 97 | //! SeccompCmpArgLen::Dword, 98 | //! SeccompCmpOp::Eq, 99 | //! libc::F_GETFD as u64, 100 | //! ) 101 | //! .unwrap()]) 102 | //! .unwrap(), 103 | //! ], 104 | //! ), 105 | //! ] 106 | //! .into_iter() 107 | //! .collect(), 108 | //! SeccompAction::Allow, 109 | //! SeccompAction::Trap, 110 | //! std::env::consts::ARCH.try_into().unwrap(), 111 | //! ) 112 | //! .unwrap() 113 | //! .try_into() 114 | //! .unwrap(); 115 | //! 116 | //! seccompiler::apply_filter(&filter).unwrap(); 117 | //! ``` 118 | //! 119 | //! 120 | //! This second example defines and installs an equivalent JSON filter (uses the `json` feature): 121 | //! 122 | //! ``` 123 | //! # #[cfg(feature = "json")] 124 | //! # { 125 | //! use seccompiler::BpfMap; 126 | //! use std::convert::TryInto; 127 | //! 128 | //! let json_input = r#"{ 129 | //! "main_thread": { 130 | //! "mismatch_action": "allow", 131 | //! "match_action": "trap", 132 | //! "filter": [ 133 | //! { 134 | //! "syscall": "accept4" 135 | //! }, 136 | //! { 137 | //! "syscall": "fcntl", 138 | //! "args": [ 139 | //! { 140 | //! "index": 1, 141 | //! "type": "dword", 142 | //! "op": "eq", 143 | //! "val": 2, 144 | //! "comment": "F_SETFD" 145 | //! }, 146 | //! { 147 | //! "index": 2, 148 | //! "type": "dword", 149 | //! "op": "eq", 150 | //! "val": 1, 151 | //! "comment": "FD_CLOEXEC" 152 | //! } 153 | //! ] 154 | //! }, 155 | //! { 156 | //! "syscall": "fcntl", 157 | //! "args": [ 158 | //! { 159 | //! "index": 1, 160 | //! "type": "dword", 161 | //! "op": "eq", 162 | //! "val": 1, 163 | //! "comment": "F_GETFD" 164 | //! } 165 | //! ] 166 | //! } 167 | //! ] 168 | //! } 169 | //! }"#; 170 | //! 171 | //! let filter_map: BpfMap = seccompiler::compile_from_json( 172 | //! json_input.as_bytes(), 173 | //! std::env::consts::ARCH.try_into().unwrap(), 174 | //! ) 175 | //! .unwrap(); 176 | //! let filter = filter_map.get("main_thread").unwrap(); 177 | //! 178 | //! seccompiler::apply_filter(&filter).unwrap(); 179 | //! 180 | //! # } 181 | //! ``` 182 | //! 183 | //! [`SeccompFilter`]: struct.SeccompFilter.html 184 | //! [`SeccompCondition`]: struct.SeccompCondition.html 185 | //! [`SeccompRule`]: struct.SeccompRule.html 186 | //! [`SeccompAction`]: enum.SeccompAction.html 187 | 188 | mod backend; 189 | #[cfg(feature = "json")] 190 | mod frontend; 191 | #[cfg(feature = "json")] 192 | mod syscall_table; 193 | 194 | #[cfg(feature = "json")] 195 | use std::convert::TryInto; 196 | #[cfg(feature = "json")] 197 | use std::io::Read; 198 | 199 | use std::collections::HashMap; 200 | use std::fmt::{Display, Formatter}; 201 | use std::io; 202 | 203 | #[cfg(feature = "json")] 204 | use frontend::json::{Error as JsonFrontendError, JsonCompiler}; 205 | 206 | // Re-export the IR public types. 207 | pub use backend::{ 208 | sock_filter, BpfProgram, BpfProgramRef, Error as BackendError, SeccompAction, SeccompCmpArgLen, 209 | SeccompCmpOp, SeccompCondition, SeccompFilter, SeccompRule, TargetArch, 210 | }; 211 | 212 | // BPF structure definition for filter array. 213 | // See /usr/include/linux/filter.h . 214 | #[repr(C)] 215 | struct sock_fprog { 216 | pub len: ::std::os::raw::c_ushort, 217 | pub filter: *const sock_filter, 218 | } 219 | 220 | /// Library Result type. 221 | pub type Result = std::result::Result; 222 | 223 | ///`BpfMap` is another type exposed by the library, which maps thread categories to BPF programs. 224 | pub type BpfMap = HashMap; 225 | 226 | /// Library errors. 227 | #[derive(Debug)] 228 | pub enum Error { 229 | /// Error originating in the backend compiler. 230 | Backend(BackendError), 231 | /// Attempting to install an empty filter. 232 | EmptyFilter, 233 | /// System error related to calling `prctl`. 234 | Prctl(io::Error), 235 | /// System error related to calling `seccomp` syscall. 236 | Seccomp(io::Error), 237 | /// Returned when calling `seccomp` with the thread sync flag (TSYNC) fails. Contains the pid 238 | /// of the thread that caused the failure. 239 | ThreadSync(libc::c_long), 240 | /// Json Frontend Error. 241 | #[cfg(feature = "json")] 242 | JsonFrontend(JsonFrontendError), 243 | } 244 | 245 | impl std::error::Error for Error { 246 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 247 | use self::Error::*; 248 | 249 | match self { 250 | Backend(error) => Some(error), 251 | Prctl(error) => Some(error), 252 | Seccomp(error) => Some(error), 253 | ThreadSync(_) => None, 254 | #[cfg(feature = "json")] 255 | JsonFrontend(error) => Some(error), 256 | _ => None, 257 | } 258 | } 259 | } 260 | 261 | impl Display for Error { 262 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 263 | use self::Error::*; 264 | 265 | match self { 266 | Backend(error) => { 267 | write!(f, "Backend error: {}", error) 268 | } 269 | EmptyFilter => { 270 | write!(f, "Cannot install empty filter.") 271 | } 272 | Prctl(errno) => { 273 | write!(f, "Error calling `prctl`: {}", errno) 274 | } 275 | Seccomp(errno) => { 276 | write!(f, "Error calling `seccomp`: {}", errno) 277 | } 278 | ThreadSync(pid) => { 279 | write!( 280 | f, 281 | "Seccomp filter synchronization failed in thread `{}`", 282 | pid 283 | ) 284 | } 285 | #[cfg(feature = "json")] 286 | JsonFrontend(error) => { 287 | write!(f, "Json Frontend error: {}", error) 288 | } 289 | } 290 | } 291 | } 292 | 293 | impl From for Error { 294 | fn from(value: BackendError) -> Self { 295 | Self::Backend(value) 296 | } 297 | } 298 | #[cfg(feature = "json")] 299 | impl From for Error { 300 | fn from(value: JsonFrontendError) -> Self { 301 | Self::JsonFrontend(value) 302 | } 303 | } 304 | 305 | /// Apply a BPF filter to the calling thread. 306 | /// 307 | /// # Arguments 308 | /// 309 | /// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed. 310 | /// 311 | /// [`BpfProgram`]: type.BpfProgram.html 312 | pub fn apply_filter(bpf_filter: BpfProgramRef) -> Result<()> { 313 | apply_filter_with_flags(bpf_filter, 0) 314 | } 315 | 316 | /// Apply a BPF filter to the all threads in the process via the TSYNC feature. Please read the 317 | /// man page for seccomp (`man 2 seccomp`) for more information. 318 | /// 319 | /// # Arguments 320 | /// 321 | /// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed. 322 | /// 323 | /// [`BpfProgram`]: type.BpfProgram.html 324 | pub fn apply_filter_all_threads(bpf_filter: BpfProgramRef) -> Result<()> { 325 | apply_filter_with_flags(bpf_filter, libc::SECCOMP_FILTER_FLAG_TSYNC) 326 | } 327 | 328 | /// Apply a BPF filter to the calling thread. 329 | /// 330 | /// # Arguments 331 | /// 332 | /// * `bpf_filter` - A reference to the [`BpfProgram`] to be installed. 333 | /// * `flags` - A u64 representing a bitset of seccomp's flags parameter. 334 | /// 335 | /// [`BpfProgram`]: type.BpfProgram.html 336 | fn apply_filter_with_flags(bpf_filter: BpfProgramRef, flags: libc::c_ulong) -> Result<()> { 337 | // If the program is empty, don't install the filter. 338 | if bpf_filter.is_empty() { 339 | return Err(Error::EmptyFilter); 340 | } 341 | 342 | // SAFETY: 343 | // Safe because syscall arguments are valid. 344 | let rc = unsafe { libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) }; 345 | if rc != 0 { 346 | return Err(Error::Prctl(io::Error::last_os_error())); 347 | } 348 | 349 | let bpf_prog = sock_fprog { 350 | len: bpf_filter.len() as u16, 351 | filter: bpf_filter.as_ptr(), 352 | }; 353 | let bpf_prog_ptr = &bpf_prog as *const sock_fprog; 354 | 355 | // SAFETY: 356 | // Safe because the kernel performs a `copy_from_user` on the filter and leaves the memory 357 | // untouched. We can therefore use a reference to the BpfProgram, without needing ownership. 358 | let rc = unsafe { 359 | libc::syscall( 360 | libc::SYS_seccomp, 361 | libc::SECCOMP_SET_MODE_FILTER, 362 | flags, 363 | bpf_prog_ptr, 364 | ) 365 | }; 366 | 367 | #[allow(clippy::comparison_chain)] 368 | // Per manpage, if TSYNC fails, retcode is >0 and equals the pid of the thread that caused the 369 | // failure. Otherwise, error code is -1 and errno is set. 370 | if rc < 0 { 371 | return Err(Error::Seccomp(io::Error::last_os_error())); 372 | } else if rc > 0 { 373 | return Err(Error::ThreadSync(rc)); 374 | } 375 | 376 | Ok(()) 377 | } 378 | 379 | /// Compile [`BpfProgram`]s from JSON. 380 | /// 381 | /// # Arguments 382 | /// 383 | /// * `reader` - [`std::io::Read`] object containing the JSON data conforming to the 384 | /// [JSON file format](https://github.com/rust-vmm/seccompiler/blob/master/docs/json_format.md). 385 | /// * `arch` - target architecture of the filter. 386 | /// 387 | /// [`BpfProgram`]: type.BpfProgram.html 388 | #[cfg(feature = "json")] 389 | pub fn compile_from_json(reader: R, arch: TargetArch) -> Result { 390 | // Run the frontend. 391 | let seccomp_filters: HashMap = 392 | JsonCompiler::new(arch).compile(reader)?; 393 | 394 | // Run the backend. 395 | let mut bpf_data: BpfMap = BpfMap::with_capacity(seccomp_filters.len()); 396 | for (name, seccomp_filter) in seccomp_filters { 397 | bpf_data.insert(name, seccomp_filter.try_into()?); 398 | } 399 | 400 | Ok(bpf_data) 401 | } 402 | -------------------------------------------------------------------------------- /src/syscall_table/aarch64.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | // This file is auto-generated by `tools/generate_syscall_tables`. 5 | // Do NOT manually edit! 6 | // Generated on: Sat Dec 14 01:47:02 PM CST 2024 7 | // Kernel version: 6.12 8 | 9 | use std::collections::HashMap; 10 | 11 | pub(crate) fn make_syscall_table() -> HashMap<&'static str, i64> { 12 | vec![ 13 | ("accept", 202), 14 | ("accept4", 242), 15 | ("acct", 89), 16 | ("add_key", 217), 17 | ("adjtimex", 171), 18 | ("bind", 200), 19 | ("bpf", 280), 20 | ("brk", 214), 21 | ("cachestat", 451), 22 | ("capget", 90), 23 | ("capset", 91), 24 | ("chdir", 49), 25 | ("chroot", 51), 26 | ("clock_adjtime", 266), 27 | ("clock_getres", 114), 28 | ("clock_gettime", 113), 29 | ("clock_nanosleep", 115), 30 | ("clock_settime", 112), 31 | ("clone", 220), 32 | ("clone3", 435), 33 | ("close", 57), 34 | ("close_range", 436), 35 | ("connect", 203), 36 | ("copy_file_range", 285), 37 | ("delete_module", 106), 38 | ("dup", 23), 39 | ("dup3", 24), 40 | ("epoll_create1", 20), 41 | ("epoll_ctl", 21), 42 | ("epoll_pwait", 22), 43 | ("epoll_pwait2", 441), 44 | ("eventfd2", 19), 45 | ("execve", 221), 46 | ("execveat", 281), 47 | ("exit", 93), 48 | ("exit_group", 94), 49 | ("faccessat", 48), 50 | ("faccessat2", 439), 51 | ("fadvise64", 223), 52 | ("fallocate", 47), 53 | ("fanotify_init", 262), 54 | ("fanotify_mark", 263), 55 | ("fchdir", 50), 56 | ("fchmod", 52), 57 | ("fchmodat", 53), 58 | ("fchmodat2", 452), 59 | ("fchown", 55), 60 | ("fchownat", 54), 61 | ("fcntl", 25), 62 | ("fdatasync", 83), 63 | ("fgetxattr", 10), 64 | ("finit_module", 273), 65 | ("flistxattr", 13), 66 | ("flock", 32), 67 | ("fremovexattr", 16), 68 | ("fsconfig", 431), 69 | ("fsetxattr", 7), 70 | ("fsmount", 432), 71 | ("fsopen", 430), 72 | ("fspick", 433), 73 | ("fstat", 80), 74 | ("fstatfs", 44), 75 | ("fsync", 82), 76 | ("ftruncate", 46), 77 | ("futex", 98), 78 | ("futex_requeue", 456), 79 | ("futex_wait", 455), 80 | ("futex_waitv", 449), 81 | ("futex_wake", 454), 82 | ("getcpu", 168), 83 | ("getcwd", 17), 84 | ("getdents64", 61), 85 | ("getegid", 177), 86 | ("geteuid", 175), 87 | ("getgid", 176), 88 | ("getgroups", 158), 89 | ("getitimer", 102), 90 | ("get_mempolicy", 236), 91 | ("getpeername", 205), 92 | ("getpgid", 155), 93 | ("getpid", 172), 94 | ("getppid", 173), 95 | ("getpriority", 141), 96 | ("getrandom", 278), 97 | ("getresgid", 150), 98 | ("getresuid", 148), 99 | ("getrlimit", 163), 100 | ("get_robust_list", 100), 101 | ("getrusage", 165), 102 | ("getsid", 156), 103 | ("getsockname", 204), 104 | ("getsockopt", 209), 105 | ("gettid", 178), 106 | ("gettimeofday", 169), 107 | ("getuid", 174), 108 | ("getxattr", 8), 109 | ("init_module", 105), 110 | ("inotify_add_watch", 27), 111 | ("inotify_init1", 26), 112 | ("inotify_rm_watch", 28), 113 | ("io_cancel", 3), 114 | ("ioctl", 29), 115 | ("io_destroy", 1), 116 | ("io_getevents", 4), 117 | ("io_pgetevents", 292), 118 | ("ioprio_get", 31), 119 | ("ioprio_set", 30), 120 | ("io_setup", 0), 121 | ("io_submit", 2), 122 | ("io_uring_enter", 426), 123 | ("io_uring_register", 427), 124 | ("io_uring_setup", 425), 125 | ("kcmp", 272), 126 | ("kexec_file_load", 294), 127 | ("kexec_load", 104), 128 | ("keyctl", 219), 129 | ("kill", 129), 130 | ("landlock_add_rule", 445), 131 | ("landlock_create_ruleset", 444), 132 | ("landlock_restrict_self", 446), 133 | ("lgetxattr", 9), 134 | ("linkat", 37), 135 | ("listen", 201), 136 | ("listmount", 458), 137 | ("listxattr", 11), 138 | ("llistxattr", 12), 139 | ("lookup_dcookie", 18), 140 | ("lremovexattr", 15), 141 | ("lseek", 62), 142 | ("lsetxattr", 6), 143 | ("lsm_get_self_attr", 459), 144 | ("lsm_list_modules", 461), 145 | ("lsm_set_self_attr", 460), 146 | ("madvise", 233), 147 | ("map_shadow_stack", 453), 148 | ("mbind", 235), 149 | ("membarrier", 283), 150 | ("memfd_create", 279), 151 | ("memfd_secret", 447), 152 | ("migrate_pages", 238), 153 | ("mincore", 232), 154 | ("mkdirat", 34), 155 | ("mknodat", 33), 156 | ("mlock", 228), 157 | ("mlock2", 284), 158 | ("mlockall", 230), 159 | ("mmap", 222), 160 | ("mount", 40), 161 | ("mount_setattr", 442), 162 | ("move_mount", 429), 163 | ("move_pages", 239), 164 | ("mprotect", 226), 165 | ("mq_getsetattr", 185), 166 | ("mq_notify", 184), 167 | ("mq_open", 180), 168 | ("mq_timedreceive", 183), 169 | ("mq_timedsend", 182), 170 | ("mq_unlink", 181), 171 | ("mremap", 216), 172 | ("mseal", 462), 173 | ("msgctl", 187), 174 | ("msgget", 186), 175 | ("msgrcv", 188), 176 | ("msgsnd", 189), 177 | ("msync", 227), 178 | ("munlock", 229), 179 | ("munlockall", 231), 180 | ("munmap", 215), 181 | ("name_to_handle_at", 264), 182 | ("nanosleep", 101), 183 | ("newfstatat", 79), 184 | ("nfsservctl", 42), 185 | ("openat", 56), 186 | ("openat2", 437), 187 | ("open_by_handle_at", 265), 188 | ("open_tree", 428), 189 | ("perf_event_open", 241), 190 | ("personality", 92), 191 | ("pidfd_getfd", 438), 192 | ("pidfd_open", 434), 193 | ("pidfd_send_signal", 424), 194 | ("pipe2", 59), 195 | ("pivot_root", 41), 196 | ("pkey_alloc", 289), 197 | ("pkey_free", 290), 198 | ("pkey_mprotect", 288), 199 | ("ppoll", 73), 200 | ("prctl", 167), 201 | ("pread64", 67), 202 | ("preadv", 69), 203 | ("preadv2", 286), 204 | ("prlimit64", 261), 205 | ("process_madvise", 440), 206 | ("process_mrelease", 448), 207 | ("process_vm_readv", 270), 208 | ("process_vm_writev", 271), 209 | ("pselect6", 72), 210 | ("ptrace", 117), 211 | ("pwrite64", 68), 212 | ("pwritev", 70), 213 | ("pwritev2", 287), 214 | ("quotactl", 60), 215 | ("quotactl_fd", 443), 216 | ("read", 63), 217 | ("readahead", 213), 218 | ("readlinkat", 78), 219 | ("readv", 65), 220 | ("reboot", 142), 221 | ("recvfrom", 207), 222 | ("recvmmsg", 243), 223 | ("recvmsg", 212), 224 | ("remap_file_pages", 234), 225 | ("removexattr", 14), 226 | ("renameat", 38), 227 | ("renameat2", 276), 228 | ("request_key", 218), 229 | ("restart_syscall", 128), 230 | ("rseq", 293), 231 | ("rt_sigaction", 134), 232 | ("rt_sigpending", 136), 233 | ("rt_sigprocmask", 135), 234 | ("rt_sigqueueinfo", 138), 235 | ("rt_sigreturn", 139), 236 | ("rt_sigsuspend", 133), 237 | ("rt_sigtimedwait", 137), 238 | ("rt_tgsigqueueinfo", 240), 239 | ("sched_getaffinity", 123), 240 | ("sched_getattr", 275), 241 | ("sched_getparam", 121), 242 | ("sched_get_priority_max", 125), 243 | ("sched_get_priority_min", 126), 244 | ("sched_getscheduler", 120), 245 | ("sched_rr_get_interval", 127), 246 | ("sched_setaffinity", 122), 247 | ("sched_setattr", 274), 248 | ("sched_setparam", 118), 249 | ("sched_setscheduler", 119), 250 | ("sched_yield", 124), 251 | ("seccomp", 277), 252 | ("semctl", 191), 253 | ("semget", 190), 254 | ("semop", 193), 255 | ("semtimedop", 192), 256 | ("sendfile", 71), 257 | ("sendmmsg", 269), 258 | ("sendmsg", 211), 259 | ("sendto", 206), 260 | ("setdomainname", 162), 261 | ("setfsgid", 152), 262 | ("setfsuid", 151), 263 | ("setgid", 144), 264 | ("setgroups", 159), 265 | ("sethostname", 161), 266 | ("setitimer", 103), 267 | ("set_mempolicy", 237), 268 | ("set_mempolicy_home_node", 450), 269 | ("setns", 268), 270 | ("setpgid", 154), 271 | ("setpriority", 140), 272 | ("setregid", 143), 273 | ("setresgid", 149), 274 | ("setresuid", 147), 275 | ("setreuid", 145), 276 | ("setrlimit", 164), 277 | ("set_robust_list", 99), 278 | ("setsid", 157), 279 | ("setsockopt", 208), 280 | ("set_tid_address", 96), 281 | ("settimeofday", 170), 282 | ("setuid", 146), 283 | ("setxattr", 5), 284 | ("shmat", 196), 285 | ("shmctl", 195), 286 | ("shmdt", 197), 287 | ("shmget", 194), 288 | ("shutdown", 210), 289 | ("sigaltstack", 132), 290 | ("signalfd4", 74), 291 | ("socket", 198), 292 | ("socketpair", 199), 293 | ("splice", 76), 294 | ("statfs", 43), 295 | ("statmount", 457), 296 | ("statx", 291), 297 | ("swapoff", 225), 298 | ("swapon", 224), 299 | ("symlinkat", 36), 300 | ("sync", 81), 301 | ("sync_file_range", 84), 302 | ("syncfs", 267), 303 | ("sysinfo", 179), 304 | ("syslog", 116), 305 | ("tee", 77), 306 | ("tgkill", 131), 307 | ("timer_create", 107), 308 | ("timer_delete", 111), 309 | ("timerfd_create", 85), 310 | ("timerfd_gettime", 87), 311 | ("timerfd_settime", 86), 312 | ("timer_getoverrun", 109), 313 | ("timer_gettime", 108), 314 | ("timer_settime", 110), 315 | ("times", 153), 316 | ("tkill", 130), 317 | ("truncate", 45), 318 | ("umask", 166), 319 | ("umount2", 39), 320 | ("uname", 160), 321 | ("unlinkat", 35), 322 | ("unshare", 97), 323 | ("userfaultfd", 282), 324 | ("utimensat", 88), 325 | ("vhangup", 58), 326 | ("vmsplice", 75), 327 | ("wait4", 260), 328 | ("waitid", 95), 329 | ("write", 64), 330 | ("writev", 66), 331 | ] 332 | .into_iter() 333 | .collect() 334 | } 335 | -------------------------------------------------------------------------------- /src/syscall_table/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | mod aarch64; 5 | mod riscv64; 6 | mod x86_64; 7 | 8 | use crate::backend::TargetArch; 9 | use std::collections::HashMap; 10 | 11 | /// Creates and owns a mapping from the arch-specific syscall name to the right number. 12 | #[derive(Debug)] 13 | pub(crate) struct SyscallTable { 14 | map: HashMap<&'static str, i64>, 15 | } 16 | 17 | impl SyscallTable { 18 | pub fn new(arch: TargetArch) -> Self { 19 | Self { 20 | map: match arch { 21 | TargetArch::aarch64 => aarch64::make_syscall_table(), 22 | TargetArch::x86_64 => x86_64::make_syscall_table(), 23 | TargetArch::riscv64 => riscv64::make_syscall_table(), 24 | }, 25 | } 26 | } 27 | 28 | /// Returns the arch-specific syscall number based on the given name. 29 | pub fn get_syscall_nr(&self, sys_name: &str) -> Option { 30 | self.map.get(sys_name).copied() 31 | } 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::SyscallTable; 37 | use crate::backend::TargetArch; 38 | 39 | #[test] 40 | fn test_get_syscall_nr() { 41 | // get number for a valid syscall 42 | let instance_x86_64 = SyscallTable::new(TargetArch::x86_64); 43 | let instance_aarch64 = SyscallTable::new(TargetArch::aarch64); 44 | let instance_riscv64 = SyscallTable::new(TargetArch::riscv64); 45 | 46 | assert_eq!(instance_x86_64.get_syscall_nr("close").unwrap(), 3); 47 | assert_eq!(instance_aarch64.get_syscall_nr("close").unwrap(), 57); 48 | assert_eq!(instance_riscv64.get_syscall_nr("close").unwrap(), 57); 49 | 50 | // invalid syscall name 51 | assert!(instance_x86_64.get_syscall_nr("nosyscall").is_none()); 52 | assert!(instance_aarch64.get_syscall_nr("nosyscall").is_none()); 53 | assert!(instance_riscv64.get_syscall_nr("nosyscall").is_none()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/syscall_table/riscv64.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | // This file is auto-generated by `tools/generate_syscall_tables`. 5 | // Do NOT manually edit! 6 | // Generated on: Sat Dec 14 01:47:02 PM CST 2024 7 | // Kernel version: 6.12 8 | 9 | use std::collections::HashMap; 10 | 11 | pub(crate) fn make_syscall_table() -> HashMap<&'static str, i64> { 12 | vec![ 13 | ("accept", 202), 14 | ("accept4", 242), 15 | ("acct", 89), 16 | ("add_key", 217), 17 | ("adjtimex", 171), 18 | ("bind", 200), 19 | ("bpf", 280), 20 | ("brk", 214), 21 | ("cachestat", 451), 22 | ("capget", 90), 23 | ("capset", 91), 24 | ("chdir", 49), 25 | ("chroot", 51), 26 | ("clock_adjtime", 266), 27 | ("clock_getres", 114), 28 | ("clock_gettime", 113), 29 | ("clock_nanosleep", 115), 30 | ("clock_settime", 112), 31 | ("clone", 220), 32 | ("clone3", 435), 33 | ("close", 57), 34 | ("close_range", 436), 35 | ("connect", 203), 36 | ("copy_file_range", 285), 37 | ("delete_module", 106), 38 | ("dup", 23), 39 | ("dup3", 24), 40 | ("epoll_create1", 20), 41 | ("epoll_ctl", 21), 42 | ("epoll_pwait", 22), 43 | ("epoll_pwait2", 441), 44 | ("eventfd2", 19), 45 | ("execve", 221), 46 | ("execveat", 281), 47 | ("exit", 93), 48 | ("exit_group", 94), 49 | ("faccessat", 48), 50 | ("faccessat2", 439), 51 | ("fadvise64", 223), 52 | ("fallocate", 47), 53 | ("fanotify_init", 262), 54 | ("fanotify_mark", 263), 55 | ("fchdir", 50), 56 | ("fchmod", 52), 57 | ("fchmodat", 53), 58 | ("fchmodat2", 452), 59 | ("fchown", 55), 60 | ("fchownat", 54), 61 | ("fcntl", 25), 62 | ("fdatasync", 83), 63 | ("fgetxattr", 10), 64 | ("finit_module", 273), 65 | ("flistxattr", 13), 66 | ("flock", 32), 67 | ("fremovexattr", 16), 68 | ("fsconfig", 431), 69 | ("fsetxattr", 7), 70 | ("fsmount", 432), 71 | ("fsopen", 430), 72 | ("fspick", 433), 73 | ("fstat", 80), 74 | ("fstatfs", 44), 75 | ("fsync", 82), 76 | ("ftruncate", 46), 77 | ("futex", 98), 78 | ("futex_requeue", 456), 79 | ("futex_wait", 455), 80 | ("futex_waitv", 449), 81 | ("futex_wake", 454), 82 | ("getcpu", 168), 83 | ("getcwd", 17), 84 | ("getdents64", 61), 85 | ("getegid", 177), 86 | ("geteuid", 175), 87 | ("getgid", 176), 88 | ("getgroups", 158), 89 | ("getitimer", 102), 90 | ("get_mempolicy", 236), 91 | ("getpeername", 205), 92 | ("getpgid", 155), 93 | ("getpid", 172), 94 | ("getppid", 173), 95 | ("getpriority", 141), 96 | ("getrandom", 278), 97 | ("getresgid", 150), 98 | ("getresuid", 148), 99 | ("getrlimit", 163), 100 | ("get_robust_list", 100), 101 | ("getrusage", 165), 102 | ("getsid", 156), 103 | ("getsockname", 204), 104 | ("getsockopt", 209), 105 | ("gettid", 178), 106 | ("gettimeofday", 169), 107 | ("getuid", 174), 108 | ("getxattr", 8), 109 | ("init_module", 105), 110 | ("inotify_add_watch", 27), 111 | ("inotify_init1", 26), 112 | ("inotify_rm_watch", 28), 113 | ("io_cancel", 3), 114 | ("ioctl", 29), 115 | ("io_destroy", 1), 116 | ("io_getevents", 4), 117 | ("io_pgetevents", 292), 118 | ("ioprio_get", 31), 119 | ("ioprio_set", 30), 120 | ("io_setup", 0), 121 | ("io_submit", 2), 122 | ("io_uring_enter", 426), 123 | ("io_uring_register", 427), 124 | ("io_uring_setup", 425), 125 | ("kcmp", 272), 126 | ("kexec_file_load", 294), 127 | ("kexec_load", 104), 128 | ("keyctl", 219), 129 | ("kill", 129), 130 | ("landlock_add_rule", 445), 131 | ("landlock_create_ruleset", 444), 132 | ("landlock_restrict_self", 446), 133 | ("lgetxattr", 9), 134 | ("linkat", 37), 135 | ("listen", 201), 136 | ("listmount", 458), 137 | ("listxattr", 11), 138 | ("llistxattr", 12), 139 | ("lookup_dcookie", 18), 140 | ("lremovexattr", 15), 141 | ("lseek", 62), 142 | ("lsetxattr", 6), 143 | ("lsm_get_self_attr", 459), 144 | ("lsm_list_modules", 461), 145 | ("lsm_set_self_attr", 460), 146 | ("madvise", 233), 147 | ("map_shadow_stack", 453), 148 | ("mbind", 235), 149 | ("membarrier", 283), 150 | ("memfd_create", 279), 151 | ("memfd_secret", 447), 152 | ("migrate_pages", 238), 153 | ("mincore", 232), 154 | ("mkdirat", 34), 155 | ("mknodat", 33), 156 | ("mlock", 228), 157 | ("mlock2", 284), 158 | ("mlockall", 230), 159 | ("mmap", 222), 160 | ("mount", 40), 161 | ("mount_setattr", 442), 162 | ("move_mount", 429), 163 | ("move_pages", 239), 164 | ("mprotect", 226), 165 | ("mq_getsetattr", 185), 166 | ("mq_notify", 184), 167 | ("mq_open", 180), 168 | ("mq_timedreceive", 183), 169 | ("mq_timedsend", 182), 170 | ("mq_unlink", 181), 171 | ("mremap", 216), 172 | ("mseal", 462), 173 | ("msgctl", 187), 174 | ("msgget", 186), 175 | ("msgrcv", 188), 176 | ("msgsnd", 189), 177 | ("msync", 227), 178 | ("munlock", 229), 179 | ("munlockall", 231), 180 | ("munmap", 215), 181 | ("name_to_handle_at", 264), 182 | ("nanosleep", 101), 183 | ("newfstatat", 79), 184 | ("nfsservctl", 42), 185 | ("openat", 56), 186 | ("openat2", 437), 187 | ("open_by_handle_at", 265), 188 | ("open_tree", 428), 189 | ("perf_event_open", 241), 190 | ("personality", 92), 191 | ("pidfd_getfd", 438), 192 | ("pidfd_open", 434), 193 | ("pidfd_send_signal", 424), 194 | ("pipe2", 59), 195 | ("pivot_root", 41), 196 | ("pkey_alloc", 289), 197 | ("pkey_free", 290), 198 | ("pkey_mprotect", 288), 199 | ("ppoll", 73), 200 | ("prctl", 167), 201 | ("pread64", 67), 202 | ("preadv", 69), 203 | ("preadv2", 286), 204 | ("prlimit64", 261), 205 | ("process_madvise", 440), 206 | ("process_mrelease", 448), 207 | ("process_vm_readv", 270), 208 | ("process_vm_writev", 271), 209 | ("pselect6", 72), 210 | ("ptrace", 117), 211 | ("pwrite64", 68), 212 | ("pwritev", 70), 213 | ("pwritev2", 287), 214 | ("quotactl", 60), 215 | ("quotactl_fd", 443), 216 | ("read", 63), 217 | ("readahead", 213), 218 | ("readlinkat", 78), 219 | ("readv", 65), 220 | ("reboot", 142), 221 | ("recvfrom", 207), 222 | ("recvmmsg", 243), 223 | ("recvmsg", 212), 224 | ("remap_file_pages", 234), 225 | ("removexattr", 14), 226 | ("renameat2", 276), 227 | ("request_key", 218), 228 | ("restart_syscall", 128), 229 | ("riscv_flush_icache", 259), 230 | ("riscv_hwprobe", 258), 231 | ("rseq", 293), 232 | ("rt_sigaction", 134), 233 | ("rt_sigpending", 136), 234 | ("rt_sigprocmask", 135), 235 | ("rt_sigqueueinfo", 138), 236 | ("rt_sigreturn", 139), 237 | ("rt_sigsuspend", 133), 238 | ("rt_sigtimedwait", 137), 239 | ("rt_tgsigqueueinfo", 240), 240 | ("sched_getaffinity", 123), 241 | ("sched_getattr", 275), 242 | ("sched_getparam", 121), 243 | ("sched_get_priority_max", 125), 244 | ("sched_get_priority_min", 126), 245 | ("sched_getscheduler", 120), 246 | ("sched_rr_get_interval", 127), 247 | ("sched_setaffinity", 122), 248 | ("sched_setattr", 274), 249 | ("sched_setparam", 118), 250 | ("sched_setscheduler", 119), 251 | ("sched_yield", 124), 252 | ("seccomp", 277), 253 | ("semctl", 191), 254 | ("semget", 190), 255 | ("semop", 193), 256 | ("semtimedop", 192), 257 | ("sendfile", 71), 258 | ("sendmmsg", 269), 259 | ("sendmsg", 211), 260 | ("sendto", 206), 261 | ("setdomainname", 162), 262 | ("setfsgid", 152), 263 | ("setfsuid", 151), 264 | ("setgid", 144), 265 | ("setgroups", 159), 266 | ("sethostname", 161), 267 | ("setitimer", 103), 268 | ("set_mempolicy", 237), 269 | ("set_mempolicy_home_node", 450), 270 | ("setns", 268), 271 | ("setpgid", 154), 272 | ("setpriority", 140), 273 | ("setregid", 143), 274 | ("setresgid", 149), 275 | ("setresuid", 147), 276 | ("setreuid", 145), 277 | ("setrlimit", 164), 278 | ("set_robust_list", 99), 279 | ("setsid", 157), 280 | ("setsockopt", 208), 281 | ("set_tid_address", 96), 282 | ("settimeofday", 170), 283 | ("setuid", 146), 284 | ("setxattr", 5), 285 | ("shmat", 196), 286 | ("shmctl", 195), 287 | ("shmdt", 197), 288 | ("shmget", 194), 289 | ("shutdown", 210), 290 | ("sigaltstack", 132), 291 | ("signalfd4", 74), 292 | ("socket", 198), 293 | ("socketpair", 199), 294 | ("splice", 76), 295 | ("statfs", 43), 296 | ("statmount", 457), 297 | ("statx", 291), 298 | ("swapoff", 225), 299 | ("swapon", 224), 300 | ("symlinkat", 36), 301 | ("sync", 81), 302 | ("sync_file_range", 84), 303 | ("syncfs", 267), 304 | ("sysinfo", 179), 305 | ("syslog", 116), 306 | ("tee", 77), 307 | ("tgkill", 131), 308 | ("timer_create", 107), 309 | ("timer_delete", 111), 310 | ("timerfd_create", 85), 311 | ("timerfd_gettime", 87), 312 | ("timerfd_settime", 86), 313 | ("timer_getoverrun", 109), 314 | ("timer_gettime", 108), 315 | ("timer_settime", 110), 316 | ("times", 153), 317 | ("tkill", 130), 318 | ("truncate", 45), 319 | ("umask", 166), 320 | ("umount2", 39), 321 | ("uname", 160), 322 | ("unlinkat", 35), 323 | ("unshare", 97), 324 | ("userfaultfd", 282), 325 | ("utimensat", 88), 326 | ("vhangup", 58), 327 | ("vmsplice", 75), 328 | ("wait4", 260), 329 | ("waitid", 95), 330 | ("write", 64), 331 | ("writev", 66), 332 | ] 333 | .into_iter() 334 | .collect() 335 | } 336 | -------------------------------------------------------------------------------- /src/syscall_table/x86_64.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | // This file is auto-generated by `tools/generate_syscall_tables`. 5 | // Do NOT manually edit! 6 | // Generated on: Sat Dec 14 01:47:02 PM CST 2024 7 | // Kernel version: 6.12 8 | 9 | use std::collections::HashMap; 10 | 11 | pub(crate) fn make_syscall_table() -> HashMap<&'static str, i64> { 12 | vec![ 13 | ("accept", 43), 14 | ("accept4", 288), 15 | ("access", 21), 16 | ("acct", 163), 17 | ("add_key", 248), 18 | ("adjtimex", 159), 19 | ("afs_syscall", 183), 20 | ("alarm", 37), 21 | ("arch_prctl", 158), 22 | ("bind", 49), 23 | ("bpf", 321), 24 | ("brk", 12), 25 | ("cachestat", 451), 26 | ("capget", 125), 27 | ("capset", 126), 28 | ("chdir", 80), 29 | ("chmod", 90), 30 | ("chown", 92), 31 | ("chroot", 161), 32 | ("clock_adjtime", 305), 33 | ("clock_getres", 229), 34 | ("clock_gettime", 228), 35 | ("clock_nanosleep", 230), 36 | ("clock_settime", 227), 37 | ("clone", 56), 38 | ("clone3", 435), 39 | ("close", 3), 40 | ("close_range", 436), 41 | ("connect", 42), 42 | ("copy_file_range", 326), 43 | ("creat", 85), 44 | ("create_module", 174), 45 | ("delete_module", 176), 46 | ("dup", 32), 47 | ("dup2", 33), 48 | ("dup3", 292), 49 | ("epoll_create", 213), 50 | ("epoll_create1", 291), 51 | ("epoll_ctl", 233), 52 | ("epoll_ctl_old", 214), 53 | ("epoll_pwait", 281), 54 | ("epoll_pwait2", 441), 55 | ("epoll_wait", 232), 56 | ("epoll_wait_old", 215), 57 | ("eventfd", 284), 58 | ("eventfd2", 290), 59 | ("execve", 59), 60 | ("execveat", 322), 61 | ("exit", 60), 62 | ("exit_group", 231), 63 | ("faccessat", 269), 64 | ("faccessat2", 439), 65 | ("fadvise64", 221), 66 | ("fallocate", 285), 67 | ("fanotify_init", 300), 68 | ("fanotify_mark", 301), 69 | ("fchdir", 81), 70 | ("fchmod", 91), 71 | ("fchmodat", 268), 72 | ("fchmodat2", 452), 73 | ("fchown", 93), 74 | ("fchownat", 260), 75 | ("fcntl", 72), 76 | ("fdatasync", 75), 77 | ("fgetxattr", 193), 78 | ("finit_module", 313), 79 | ("flistxattr", 196), 80 | ("flock", 73), 81 | ("fork", 57), 82 | ("fremovexattr", 199), 83 | ("fsconfig", 431), 84 | ("fsetxattr", 190), 85 | ("fsmount", 432), 86 | ("fsopen", 430), 87 | ("fspick", 433), 88 | ("fstat", 5), 89 | ("fstatfs", 138), 90 | ("fsync", 74), 91 | ("ftruncate", 77), 92 | ("futex", 202), 93 | ("futex_requeue", 456), 94 | ("futex_wait", 455), 95 | ("futex_waitv", 449), 96 | ("futex_wake", 454), 97 | ("futimesat", 261), 98 | ("getcpu", 309), 99 | ("getcwd", 79), 100 | ("getdents", 78), 101 | ("getdents64", 217), 102 | ("getegid", 108), 103 | ("geteuid", 107), 104 | ("getgid", 104), 105 | ("getgroups", 115), 106 | ("getitimer", 36), 107 | ("get_kernel_syms", 177), 108 | ("get_mempolicy", 239), 109 | ("getpeername", 52), 110 | ("getpgid", 121), 111 | ("getpgrp", 111), 112 | ("getpid", 39), 113 | ("getpmsg", 181), 114 | ("getppid", 110), 115 | ("getpriority", 140), 116 | ("getrandom", 318), 117 | ("getresgid", 120), 118 | ("getresuid", 118), 119 | ("getrlimit", 97), 120 | ("get_robust_list", 274), 121 | ("getrusage", 98), 122 | ("getsid", 124), 123 | ("getsockname", 51), 124 | ("getsockopt", 55), 125 | ("get_thread_area", 211), 126 | ("gettid", 186), 127 | ("gettimeofday", 96), 128 | ("getuid", 102), 129 | ("getxattr", 191), 130 | ("init_module", 175), 131 | ("inotify_add_watch", 254), 132 | ("inotify_init", 253), 133 | ("inotify_init1", 294), 134 | ("inotify_rm_watch", 255), 135 | ("io_cancel", 210), 136 | ("ioctl", 16), 137 | ("io_destroy", 207), 138 | ("io_getevents", 208), 139 | ("ioperm", 173), 140 | ("io_pgetevents", 333), 141 | ("iopl", 172), 142 | ("ioprio_get", 252), 143 | ("ioprio_set", 251), 144 | ("io_setup", 206), 145 | ("io_submit", 209), 146 | ("io_uring_enter", 426), 147 | ("io_uring_register", 427), 148 | ("io_uring_setup", 425), 149 | ("kcmp", 312), 150 | ("kexec_file_load", 320), 151 | ("kexec_load", 246), 152 | ("keyctl", 250), 153 | ("kill", 62), 154 | ("landlock_add_rule", 445), 155 | ("landlock_create_ruleset", 444), 156 | ("landlock_restrict_self", 446), 157 | ("lchown", 94), 158 | ("lgetxattr", 192), 159 | ("link", 86), 160 | ("linkat", 265), 161 | ("listen", 50), 162 | ("listmount", 458), 163 | ("listxattr", 194), 164 | ("llistxattr", 195), 165 | ("lookup_dcookie", 212), 166 | ("lremovexattr", 198), 167 | ("lseek", 8), 168 | ("lsetxattr", 189), 169 | ("lsm_get_self_attr", 459), 170 | ("lsm_list_modules", 461), 171 | ("lsm_set_self_attr", 460), 172 | ("lstat", 6), 173 | ("madvise", 28), 174 | ("map_shadow_stack", 453), 175 | ("mbind", 237), 176 | ("membarrier", 324), 177 | ("memfd_create", 319), 178 | ("memfd_secret", 447), 179 | ("migrate_pages", 256), 180 | ("mincore", 27), 181 | ("mkdir", 83), 182 | ("mkdirat", 258), 183 | ("mknod", 133), 184 | ("mknodat", 259), 185 | ("mlock", 149), 186 | ("mlock2", 325), 187 | ("mlockall", 151), 188 | ("mmap", 9), 189 | ("modify_ldt", 154), 190 | ("mount", 165), 191 | ("mount_setattr", 442), 192 | ("move_mount", 429), 193 | ("move_pages", 279), 194 | ("mprotect", 10), 195 | ("mq_getsetattr", 245), 196 | ("mq_notify", 244), 197 | ("mq_open", 240), 198 | ("mq_timedreceive", 243), 199 | ("mq_timedsend", 242), 200 | ("mq_unlink", 241), 201 | ("mremap", 25), 202 | ("mseal", 462), 203 | ("msgctl", 71), 204 | ("msgget", 68), 205 | ("msgrcv", 70), 206 | ("msgsnd", 69), 207 | ("msync", 26), 208 | ("munlock", 150), 209 | ("munlockall", 152), 210 | ("munmap", 11), 211 | ("name_to_handle_at", 303), 212 | ("nanosleep", 35), 213 | ("newfstatat", 262), 214 | ("nfsservctl", 180), 215 | ("open", 2), 216 | ("openat", 257), 217 | ("openat2", 437), 218 | ("open_by_handle_at", 304), 219 | ("open_tree", 428), 220 | ("pause", 34), 221 | ("perf_event_open", 298), 222 | ("personality", 135), 223 | ("pidfd_getfd", 438), 224 | ("pidfd_open", 434), 225 | ("pidfd_send_signal", 424), 226 | ("pipe", 22), 227 | ("pipe2", 293), 228 | ("pivot_root", 155), 229 | ("pkey_alloc", 330), 230 | ("pkey_free", 331), 231 | ("pkey_mprotect", 329), 232 | ("poll", 7), 233 | ("ppoll", 271), 234 | ("prctl", 157), 235 | ("pread64", 17), 236 | ("preadv", 295), 237 | ("preadv2", 327), 238 | ("prlimit64", 302), 239 | ("process_madvise", 440), 240 | ("process_mrelease", 448), 241 | ("process_vm_readv", 310), 242 | ("process_vm_writev", 311), 243 | ("pselect6", 270), 244 | ("ptrace", 101), 245 | ("putpmsg", 182), 246 | ("pwrite64", 18), 247 | ("pwritev", 296), 248 | ("pwritev2", 328), 249 | ("query_module", 178), 250 | ("quotactl", 179), 251 | ("quotactl_fd", 443), 252 | ("read", 0), 253 | ("readahead", 187), 254 | ("readlink", 89), 255 | ("readlinkat", 267), 256 | ("readv", 19), 257 | ("reboot", 169), 258 | ("recvfrom", 45), 259 | ("recvmmsg", 299), 260 | ("recvmsg", 47), 261 | ("remap_file_pages", 216), 262 | ("removexattr", 197), 263 | ("rename", 82), 264 | ("renameat", 264), 265 | ("renameat2", 316), 266 | ("request_key", 249), 267 | ("restart_syscall", 219), 268 | ("rmdir", 84), 269 | ("rseq", 334), 270 | ("rt_sigaction", 13), 271 | ("rt_sigpending", 127), 272 | ("rt_sigprocmask", 14), 273 | ("rt_sigqueueinfo", 129), 274 | ("rt_sigreturn", 15), 275 | ("rt_sigsuspend", 130), 276 | ("rt_sigtimedwait", 128), 277 | ("rt_tgsigqueueinfo", 297), 278 | ("sched_getaffinity", 204), 279 | ("sched_getattr", 315), 280 | ("sched_getparam", 143), 281 | ("sched_get_priority_max", 146), 282 | ("sched_get_priority_min", 147), 283 | ("sched_getscheduler", 145), 284 | ("sched_rr_get_interval", 148), 285 | ("sched_setaffinity", 203), 286 | ("sched_setattr", 314), 287 | ("sched_setparam", 142), 288 | ("sched_setscheduler", 144), 289 | ("sched_yield", 24), 290 | ("seccomp", 317), 291 | ("security", 185), 292 | ("select", 23), 293 | ("semctl", 66), 294 | ("semget", 64), 295 | ("semop", 65), 296 | ("semtimedop", 220), 297 | ("sendfile", 40), 298 | ("sendmmsg", 307), 299 | ("sendmsg", 46), 300 | ("sendto", 44), 301 | ("setdomainname", 171), 302 | ("setfsgid", 123), 303 | ("setfsuid", 122), 304 | ("setgid", 106), 305 | ("setgroups", 116), 306 | ("sethostname", 170), 307 | ("setitimer", 38), 308 | ("set_mempolicy", 238), 309 | ("set_mempolicy_home_node", 450), 310 | ("setns", 308), 311 | ("setpgid", 109), 312 | ("setpriority", 141), 313 | ("setregid", 114), 314 | ("setresgid", 119), 315 | ("setresuid", 117), 316 | ("setreuid", 113), 317 | ("setrlimit", 160), 318 | ("set_robust_list", 273), 319 | ("setsid", 112), 320 | ("setsockopt", 54), 321 | ("set_thread_area", 205), 322 | ("set_tid_address", 218), 323 | ("settimeofday", 164), 324 | ("setuid", 105), 325 | ("setxattr", 188), 326 | ("shmat", 30), 327 | ("shmctl", 31), 328 | ("shmdt", 67), 329 | ("shmget", 29), 330 | ("shutdown", 48), 331 | ("sigaltstack", 131), 332 | ("signalfd", 282), 333 | ("signalfd4", 289), 334 | ("socket", 41), 335 | ("socketpair", 53), 336 | ("splice", 275), 337 | ("stat", 4), 338 | ("statfs", 137), 339 | ("statmount", 457), 340 | ("statx", 332), 341 | ("swapoff", 168), 342 | ("swapon", 167), 343 | ("symlink", 88), 344 | ("symlinkat", 266), 345 | ("sync", 162), 346 | ("sync_file_range", 277), 347 | ("syncfs", 306), 348 | ("_sysctl", 156), 349 | ("sysfs", 139), 350 | ("sysinfo", 99), 351 | ("syslog", 103), 352 | ("tee", 276), 353 | ("tgkill", 234), 354 | ("time", 201), 355 | ("timer_create", 222), 356 | ("timer_delete", 226), 357 | ("timerfd_create", 283), 358 | ("timerfd_gettime", 287), 359 | ("timerfd_settime", 286), 360 | ("timer_getoverrun", 225), 361 | ("timer_gettime", 224), 362 | ("timer_settime", 223), 363 | ("times", 100), 364 | ("tkill", 200), 365 | ("truncate", 76), 366 | ("tuxcall", 184), 367 | ("umask", 95), 368 | ("umount2", 166), 369 | ("uname", 63), 370 | ("unlink", 87), 371 | ("unlinkat", 263), 372 | ("unshare", 272), 373 | ("uretprobe", 335), 374 | ("uselib", 134), 375 | ("userfaultfd", 323), 376 | ("ustat", 136), 377 | ("utime", 132), 378 | ("utimensat", 280), 379 | ("utimes", 235), 380 | ("vfork", 58), 381 | ("vhangup", 153), 382 | ("vmsplice", 278), 383 | ("vserver", 236), 384 | ("wait4", 61), 385 | ("waitid", 247), 386 | ("write", 1), 387 | ("writev", 20), 388 | ] 389 | .into_iter() 390 | .collect() 391 | } 392 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | #![allow(clippy::undocumented_unsafe_blocks)] 5 | 6 | use std::collections::BTreeMap; 7 | 8 | use seccompiler::SeccompCmpArgLen::*; 9 | use seccompiler::SeccompCmpOp::*; 10 | use seccompiler::{ 11 | apply_filter, sock_filter, BpfProgram, Error, SeccompAction, SeccompCondition as Cond, 12 | SeccompFilter, SeccompRule, 13 | }; 14 | use std::convert::TryInto; 15 | use std::env::consts::ARCH; 16 | use std::thread; 17 | 18 | // The type of the `req` parameter is different for the `musl` library. This will enable 19 | // successful build for other non-musl libraries. 20 | #[cfg(target_env = "musl")] 21 | type IoctlRequest = i32; 22 | #[cfg(not(target_env = "musl"))] 23 | type IoctlRequest = u64; 24 | 25 | // We use KVM_GET_PIT2 as the second parameter for ioctl syscalls in some unit tests 26 | // because has non-0 MSB and LSB. 27 | const KVM_GET_PIT2: u64 = 0x8070_ae9f; 28 | const KVM_GET_PIT2_MSB: u64 = 0x0000_ae9f; 29 | const KVM_GET_PIT2_LSB: u64 = 0x8070_0000; 30 | 31 | const FAILURE_CODE: i32 = 1000; 32 | 33 | const EXTRA_SYSCALLS: [i64; 6] = [ 34 | libc::SYS_rt_sigprocmask, 35 | libc::SYS_sigaltstack, 36 | libc::SYS_munmap, 37 | libc::SYS_exit, 38 | libc::SYS_rt_sigreturn, 39 | libc::SYS_futex, 40 | ]; 41 | 42 | enum Errno { 43 | Equals(i32), 44 | NotEquals(i32), 45 | None, 46 | } 47 | 48 | fn validate_seccomp_filter(rules: Vec<(i64, Vec)>, validation_fn: fn(), errno: Errno) { 49 | let mut rule_map: BTreeMap> = rules.into_iter().collect(); 50 | 51 | // Make sure the extra needed syscalls are allowed 52 | for syscall in EXTRA_SYSCALLS.iter() { 53 | rule_map.entry(*syscall).or_default(); 54 | } 55 | 56 | // Build seccomp filter. 57 | let filter = SeccompFilter::new( 58 | rule_map, 59 | SeccompAction::Errno(FAILURE_CODE as u32), 60 | SeccompAction::Allow, 61 | ARCH.try_into().unwrap(), 62 | ) 63 | .unwrap(); 64 | 65 | let filter: BpfProgram = filter.try_into().unwrap(); 66 | 67 | // We need to run the validation inside another thread in order to avoid setting 68 | // the seccomp filter for the entire unit tests process. 69 | let returned_errno = thread::spawn(move || { 70 | // Install the filter. 71 | apply_filter(&filter).unwrap(); 72 | 73 | // Call the validation fn. 74 | validation_fn(); 75 | 76 | // Return errno. 77 | std::io::Error::last_os_error().raw_os_error().unwrap() 78 | }) 79 | .join() 80 | .unwrap(); 81 | 82 | match errno { 83 | Errno::Equals(no) => assert_eq!(returned_errno, no), 84 | Errno::NotEquals(no) => assert_ne!(returned_errno, no), 85 | Errno::None => {} 86 | }; 87 | } 88 | 89 | #[test] 90 | fn test_empty_filter() { 91 | // An empty filter should always return the default action. 92 | // For example, for an empty allowlist, it should always trap/kill, 93 | // for an empty denylist, it should allow allow all system calls. 94 | 95 | let filter = SeccompFilter::new( 96 | BTreeMap::new(), 97 | SeccompAction::Allow, 98 | SeccompAction::Trap, 99 | ARCH.try_into().unwrap(), 100 | ) 101 | .unwrap(); 102 | let prog: BpfProgram = filter.try_into().unwrap(); 103 | 104 | // This should allow any system calls. 105 | let pid = thread::spawn(move || { 106 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 107 | assert_eq!(seccomp_level, 0); 108 | // Install the filter. 109 | apply_filter(&prog).unwrap(); 110 | 111 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 112 | assert_eq!(seccomp_level, 2); 113 | 114 | unsafe { libc::getpid() } 115 | }) 116 | .join() 117 | .unwrap(); 118 | 119 | // Check that the getpid syscall returned successfully. 120 | assert!(pid > 0); 121 | } 122 | 123 | #[test] 124 | fn test_invalid_architecture() { 125 | // A filter compiled for another architecture should kill the process upon evaluation. 126 | // The process will appear as if it received a SIGSYS. 127 | let mut arch = "aarch64"; 128 | 129 | if ARCH == "aarch64" { 130 | arch = "x86_64"; 131 | } 132 | 133 | let filter = SeccompFilter::new( 134 | BTreeMap::new(), 135 | SeccompAction::Allow, 136 | SeccompAction::Trap, 137 | arch.try_into().unwrap(), 138 | ) 139 | .unwrap(); 140 | let prog: BpfProgram = filter.try_into().unwrap(); 141 | 142 | let pid = unsafe { libc::fork() }; 143 | match pid { 144 | 0 => { 145 | apply_filter(&prog).unwrap(); 146 | 147 | unsafe { 148 | libc::getpid(); 149 | } 150 | } 151 | child_pid => { 152 | let mut child_status: i32 = -1; 153 | let pid_done = unsafe { libc::waitpid(child_pid, &mut child_status, 0) }; 154 | assert_eq!(pid_done, child_pid); 155 | 156 | assert!(libc::WIFSIGNALED(child_status)); 157 | assert_eq!(libc::WTERMSIG(child_status), libc::SIGSYS); 158 | } 159 | }; 160 | } 161 | 162 | #[test] 163 | fn test_eq_operator() { 164 | // check use cases for DWORD 165 | let rules = vec![( 166 | libc::SYS_ioctl, 167 | vec![SeccompRule::new(vec![Cond::new(1, Dword, Eq, KVM_GET_PIT2).unwrap()]).unwrap()], 168 | )]; 169 | // check syscalls that are supposed to work 170 | validate_seccomp_filter( 171 | rules.clone(), 172 | || unsafe { 173 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 174 | }, 175 | Errno::NotEquals(FAILURE_CODE), 176 | ); 177 | // check syscalls that are not supposed to work 178 | validate_seccomp_filter( 179 | rules, 180 | || unsafe { 181 | libc::ioctl(0, 0); 182 | }, 183 | Errno::Equals(FAILURE_CODE), 184 | ); 185 | 186 | // check use cases for QWORD 187 | let rules = vec![( 188 | libc::SYS_ioctl, 189 | vec![SeccompRule::new(vec![Cond::new(2, Qword, Eq, u64::MAX).unwrap()]).unwrap()], 190 | )]; 191 | // check syscalls that are supposed to work 192 | validate_seccomp_filter( 193 | rules.clone(), 194 | || unsafe { 195 | libc::ioctl(0, 0, u64::MAX); 196 | }, 197 | Errno::NotEquals(FAILURE_CODE), 198 | ); 199 | // check syscalls that are not supposed to work 200 | validate_seccomp_filter( 201 | rules, 202 | || unsafe { 203 | libc::ioctl(0, 0, 0); 204 | }, 205 | Errno::Equals(FAILURE_CODE), 206 | ); 207 | } 208 | 209 | #[test] 210 | fn test_ge_operator() { 211 | // check use case for DWORD 212 | let rules = vec![( 213 | libc::SYS_ioctl, 214 | vec![SeccompRule::new(vec![Cond::new(1, Dword, Ge, KVM_GET_PIT2).unwrap()]).unwrap()], 215 | )]; 216 | // check syscalls that are supposed to work 217 | validate_seccomp_filter( 218 | rules.clone(), 219 | || unsafe { 220 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 221 | libc::ioctl(0, (KVM_GET_PIT2 + 1) as IoctlRequest); 222 | }, 223 | Errno::NotEquals(FAILURE_CODE), 224 | ); 225 | // check syscalls that are not supposed to work 226 | validate_seccomp_filter( 227 | rules, 228 | || unsafe { 229 | libc::ioctl(0, (KVM_GET_PIT2 - 1) as IoctlRequest); 230 | }, 231 | Errno::Equals(FAILURE_CODE), 232 | ); 233 | 234 | // check use case for QWORD 235 | let rules = vec![( 236 | libc::SYS_ioctl, 237 | vec![ 238 | SeccompRule::new(vec![Cond::new(2, Qword, Ge, u64::from(u32::MAX)).unwrap()]).unwrap(), 239 | ], 240 | )]; 241 | // check syscalls that are supposed to work 242 | validate_seccomp_filter( 243 | rules.clone(), 244 | || unsafe { 245 | libc::ioctl(0, 0, u64::from(u32::MAX)); 246 | libc::ioctl(0, 0, u64::from(u32::MAX) + 1); 247 | }, 248 | Errno::NotEquals(FAILURE_CODE), 249 | ); 250 | // check syscalls that are not supposed to work 251 | validate_seccomp_filter( 252 | rules, 253 | || unsafe { 254 | libc::ioctl(0, 0, 1); 255 | }, 256 | Errno::Equals(FAILURE_CODE), 257 | ); 258 | } 259 | 260 | #[test] 261 | fn test_gt_operator() { 262 | // check use case for DWORD 263 | let rules = vec![( 264 | libc::SYS_ioctl, 265 | vec![SeccompRule::new(vec![Cond::new(1, Dword, Gt, KVM_GET_PIT2).unwrap()]).unwrap()], 266 | )]; 267 | // check syscalls that are supposed to work 268 | validate_seccomp_filter( 269 | rules.clone(), 270 | || unsafe { 271 | libc::ioctl(0, (KVM_GET_PIT2 + 1) as IoctlRequest); 272 | }, 273 | Errno::NotEquals(FAILURE_CODE), 274 | ); 275 | // check syscalls that are not supposed to work 276 | validate_seccomp_filter( 277 | rules, 278 | || unsafe { 279 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 280 | }, 281 | Errno::Equals(FAILURE_CODE), 282 | ); 283 | 284 | // check use case for QWORD 285 | let rules = vec![( 286 | libc::SYS_ioctl, 287 | vec![SeccompRule::new(vec![ 288 | Cond::new(2, Qword, Gt, u64::from(u32::MAX) + 10).unwrap() 289 | ]) 290 | .unwrap()], 291 | )]; 292 | // check syscalls that are supposed to work 293 | validate_seccomp_filter( 294 | rules.clone(), 295 | || unsafe { 296 | libc::ioctl(0, 0, u64::from(u32::MAX) + 11); 297 | }, 298 | Errno::NotEquals(FAILURE_CODE), 299 | ); 300 | // check syscalls that are not supposed to work 301 | validate_seccomp_filter( 302 | rules, 303 | || unsafe { 304 | libc::ioctl(0, 0, u64::from(u32::MAX) + 10); 305 | }, 306 | Errno::Equals(FAILURE_CODE), 307 | ); 308 | } 309 | 310 | #[test] 311 | fn test_le_operator() { 312 | // check use case for DWORD 313 | let rules = vec![( 314 | libc::SYS_ioctl, 315 | vec![SeccompRule::new(vec![Cond::new(1, Dword, Le, KVM_GET_PIT2).unwrap()]).unwrap()], 316 | )]; 317 | // check syscalls that are supposed to work 318 | validate_seccomp_filter( 319 | rules.clone(), 320 | || unsafe { 321 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 322 | libc::ioctl(0, (KVM_GET_PIT2 - 1) as IoctlRequest); 323 | }, 324 | Errno::NotEquals(FAILURE_CODE), 325 | ); 326 | // check syscalls that are not supposed to work 327 | validate_seccomp_filter( 328 | rules, 329 | || unsafe { 330 | libc::ioctl(0, (KVM_GET_PIT2 + 1) as IoctlRequest); 331 | }, 332 | Errno::Equals(FAILURE_CODE), 333 | ); 334 | 335 | // check use case for QWORD 336 | let rules = vec![( 337 | libc::SYS_ioctl, 338 | vec![SeccompRule::new(vec![ 339 | Cond::new(2, Qword, Le, u64::from(u32::MAX) + 10).unwrap() 340 | ]) 341 | .unwrap()], 342 | )]; 343 | // check syscalls that are supposed to work 344 | validate_seccomp_filter( 345 | rules.clone(), 346 | || unsafe { 347 | libc::ioctl(0, 0, u64::from(u32::MAX) + 10); 348 | libc::ioctl(0, 0, u64::from(u32::MAX) + 9); 349 | }, 350 | Errno::NotEquals(FAILURE_CODE), 351 | ); 352 | // check syscalls that are not supposed to work 353 | validate_seccomp_filter( 354 | rules, 355 | || unsafe { 356 | libc::ioctl(0, 0, u64::from(u32::MAX) + 11); 357 | }, 358 | Errno::Equals(FAILURE_CODE), 359 | ); 360 | } 361 | 362 | #[test] 363 | fn test_lt_operator() { 364 | // check use case for DWORD 365 | let rules = vec![( 366 | libc::SYS_ioctl, 367 | vec![SeccompRule::new(vec![Cond::new(1, Dword, Lt, KVM_GET_PIT2).unwrap()]).unwrap()], 368 | )]; 369 | // check syscalls that are supposed to work 370 | validate_seccomp_filter( 371 | rules.clone(), 372 | || unsafe { 373 | libc::ioctl(0, (KVM_GET_PIT2 - 1) as IoctlRequest); 374 | }, 375 | Errno::NotEquals(FAILURE_CODE), 376 | ); 377 | // check syscalls that are not supposed to work 378 | validate_seccomp_filter( 379 | rules, 380 | || unsafe { 381 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 382 | }, 383 | Errno::Equals(FAILURE_CODE), 384 | ); 385 | 386 | // check use case for QWORD 387 | let rules = vec![( 388 | libc::SYS_ioctl, 389 | vec![SeccompRule::new(vec![ 390 | Cond::new(2, Qword, Lt, u64::from(u32::MAX) + 10).unwrap() 391 | ]) 392 | .unwrap()], 393 | )]; 394 | // check syscalls that are supposed to work 395 | validate_seccomp_filter( 396 | rules.clone(), 397 | || unsafe { 398 | libc::ioctl(0, 0, u64::from(u32::MAX) + 9); 399 | }, 400 | Errno::NotEquals(FAILURE_CODE), 401 | ); 402 | // check syscalls that are not supposed to work 403 | validate_seccomp_filter( 404 | rules, 405 | || unsafe { 406 | libc::ioctl(0, 0, u64::from(u32::MAX) + 10); 407 | }, 408 | Errno::Equals(FAILURE_CODE), 409 | ); 410 | } 411 | 412 | #[test] 413 | fn test_masked_eq_operator() { 414 | // check use case for DWORD 415 | let rules = vec![( 416 | libc::SYS_ioctl, 417 | vec![SeccompRule::new(vec![Cond::new( 418 | 1, 419 | Dword, 420 | MaskedEq(KVM_GET_PIT2_MSB), 421 | KVM_GET_PIT2, 422 | ) 423 | .unwrap()]) 424 | .unwrap()], 425 | )]; 426 | // check syscalls that are supposed to work 427 | validate_seccomp_filter( 428 | rules.clone(), 429 | || unsafe { 430 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 431 | libc::ioctl(0, KVM_GET_PIT2_MSB as IoctlRequest); 432 | }, 433 | Errno::NotEquals(FAILURE_CODE), 434 | ); 435 | // check syscalls that are not supposed to work 436 | validate_seccomp_filter( 437 | rules, 438 | || unsafe { 439 | libc::ioctl(0, KVM_GET_PIT2_LSB as IoctlRequest); 440 | }, 441 | Errno::Equals(FAILURE_CODE), 442 | ); 443 | 444 | // check use case for QWORD 445 | let rules = vec![( 446 | libc::SYS_ioctl, 447 | vec![SeccompRule::new(vec![Cond::new( 448 | 2, 449 | Qword, 450 | MaskedEq(u64::from(u32::MAX)), 451 | u64::MAX, 452 | ) 453 | .unwrap()]) 454 | .unwrap()], 455 | )]; 456 | // check syscalls that are supposed to work 457 | validate_seccomp_filter( 458 | rules.clone(), 459 | || unsafe { 460 | libc::ioctl(0, 0, u64::from(u32::MAX)); 461 | libc::ioctl(0, 0, u64::MAX); 462 | }, 463 | Errno::NotEquals(FAILURE_CODE), 464 | ); 465 | // check syscalls that are not supposed to work 466 | validate_seccomp_filter( 467 | rules, 468 | || unsafe { 469 | libc::ioctl(0, 0, 0); 470 | }, 471 | Errno::Equals(FAILURE_CODE), 472 | ); 473 | } 474 | 475 | #[test] 476 | fn test_ne_operator() { 477 | // check use case for DWORD 478 | let rules = vec![( 479 | libc::SYS_ioctl, 480 | vec![SeccompRule::new(vec![Cond::new(1, Dword, Ne, KVM_GET_PIT2).unwrap()]).unwrap()], 481 | )]; 482 | // check syscalls that are supposed to work 483 | validate_seccomp_filter( 484 | rules.clone(), 485 | || unsafe { 486 | libc::ioctl(0, 0); 487 | }, 488 | Errno::NotEquals(FAILURE_CODE), 489 | ); 490 | // check syscalls that are not supposed to work 491 | validate_seccomp_filter( 492 | rules, 493 | || unsafe { 494 | libc::ioctl(0, KVM_GET_PIT2 as IoctlRequest); 495 | }, 496 | Errno::Equals(FAILURE_CODE), 497 | ); 498 | 499 | // check use case for QWORD 500 | let rules = vec![( 501 | libc::SYS_ioctl, 502 | vec![SeccompRule::new(vec![Cond::new(2, Qword, Ne, u64::MAX).unwrap()]).unwrap()], 503 | )]; 504 | // check syscalls that are supposed to work 505 | validate_seccomp_filter( 506 | rules.clone(), 507 | || unsafe { 508 | libc::ioctl(0, 0, 0); 509 | }, 510 | Errno::NotEquals(FAILURE_CODE), 511 | ); 512 | // check syscalls that are not supposed to work 513 | validate_seccomp_filter( 514 | rules, 515 | || unsafe { 516 | libc::ioctl(0, 0, u64::MAX); 517 | }, 518 | Errno::Equals(FAILURE_CODE), 519 | ); 520 | } 521 | 522 | #[test] 523 | fn test_complex_filter() { 524 | let rules = vec![ 525 | ( 526 | libc::SYS_ioctl, 527 | vec![ 528 | SeccompRule::new(vec![ 529 | Cond::new(2, Dword, Le, 14).unwrap(), 530 | Cond::new(2, Dword, Ne, 13).unwrap(), 531 | ]) 532 | .unwrap(), 533 | SeccompRule::new(vec![ 534 | Cond::new(2, Dword, Gt, 20).unwrap(), 535 | Cond::new(2, Dword, Lt, 40).unwrap(), 536 | ]) 537 | .unwrap(), 538 | SeccompRule::new(vec![ 539 | Cond::new(0, Dword, Eq, 1).unwrap(), 540 | Cond::new(2, Dword, Eq, 15).unwrap(), 541 | ]) 542 | .unwrap(), 543 | SeccompRule::new(vec![Cond::new(2, Qword, Eq, u32::MAX as u64 + 41).unwrap()]) 544 | .unwrap(), 545 | ], 546 | ), 547 | ( 548 | libc::SYS_madvise, 549 | vec![SeccompRule::new(vec![ 550 | Cond::new(0, Dword, Eq, 0).unwrap(), 551 | Cond::new(1, Dword, Eq, 0).unwrap(), 552 | ]) 553 | .unwrap()], 554 | ), 555 | (libc::SYS_getpid, vec![]), 556 | ]; 557 | // check syscalls that are supposed to work 558 | { 559 | validate_seccomp_filter( 560 | rules.clone(), 561 | || unsafe { 562 | libc::ioctl(0, 0, 12); 563 | }, 564 | Errno::NotEquals(FAILURE_CODE), 565 | ); 566 | 567 | validate_seccomp_filter( 568 | rules.clone(), 569 | || unsafe { 570 | libc::ioctl(0, 0, 14); 571 | }, 572 | Errno::NotEquals(FAILURE_CODE), 573 | ); 574 | 575 | validate_seccomp_filter( 576 | rules.clone(), 577 | || unsafe { 578 | libc::ioctl(0, 0, 21); 579 | }, 580 | Errno::NotEquals(FAILURE_CODE), 581 | ); 582 | 583 | validate_seccomp_filter( 584 | rules.clone(), 585 | || unsafe { 586 | libc::ioctl(0, 0, 39); 587 | }, 588 | Errno::NotEquals(FAILURE_CODE), 589 | ); 590 | 591 | validate_seccomp_filter( 592 | rules.clone(), 593 | || unsafe { 594 | libc::ioctl(1, 0, 15); 595 | }, 596 | Errno::NotEquals(FAILURE_CODE), 597 | ); 598 | 599 | validate_seccomp_filter( 600 | rules.clone(), 601 | || unsafe { 602 | libc::ioctl(0, 0, u32::MAX as u64 + 41); 603 | }, 604 | Errno::NotEquals(FAILURE_CODE), 605 | ); 606 | 607 | validate_seccomp_filter( 608 | rules.clone(), 609 | || unsafe { 610 | libc::madvise(std::ptr::null_mut(), 0, 0); 611 | }, 612 | Errno::NotEquals(FAILURE_CODE), 613 | ); 614 | 615 | validate_seccomp_filter( 616 | rules.clone(), 617 | || unsafe { 618 | assert!(libc::getpid() > 0); 619 | }, 620 | Errno::None, 621 | ); 622 | } 623 | 624 | // check syscalls that are not supposed to work 625 | { 626 | validate_seccomp_filter( 627 | rules.clone(), 628 | || unsafe { 629 | libc::ioctl(0, 0, 13); 630 | }, 631 | Errno::Equals(FAILURE_CODE), 632 | ); 633 | 634 | validate_seccomp_filter( 635 | rules.clone(), 636 | || unsafe { 637 | libc::ioctl(0, 0, 16); 638 | }, 639 | Errno::Equals(FAILURE_CODE), 640 | ); 641 | 642 | validate_seccomp_filter( 643 | rules.clone(), 644 | || unsafe { 645 | libc::ioctl(0, 0, 17); 646 | }, 647 | Errno::Equals(FAILURE_CODE), 648 | ); 649 | 650 | validate_seccomp_filter( 651 | rules.clone(), 652 | || unsafe { 653 | libc::ioctl(0, 0, 18); 654 | }, 655 | Errno::Equals(FAILURE_CODE), 656 | ); 657 | 658 | validate_seccomp_filter( 659 | rules.clone(), 660 | || unsafe { 661 | libc::ioctl(0, 0, 19); 662 | }, 663 | Errno::Equals(FAILURE_CODE), 664 | ); 665 | 666 | validate_seccomp_filter( 667 | rules.clone(), 668 | || unsafe { 669 | libc::ioctl(0, 0, 20); 670 | }, 671 | Errno::Equals(FAILURE_CODE), 672 | ); 673 | 674 | validate_seccomp_filter( 675 | rules.clone(), 676 | || unsafe { 677 | libc::ioctl(0, 0, u32::MAX as u64 + 42); 678 | }, 679 | Errno::Equals(FAILURE_CODE), 680 | ); 681 | 682 | validate_seccomp_filter( 683 | rules.clone(), 684 | || unsafe { 685 | libc::madvise(std::ptr::null_mut(), 1, 0); 686 | }, 687 | Errno::Equals(FAILURE_CODE), 688 | ); 689 | 690 | validate_seccomp_filter( 691 | rules, 692 | || unsafe { 693 | assert_eq!(libc::getuid() as i32, -FAILURE_CODE); 694 | }, 695 | Errno::None, 696 | ); 697 | } 698 | } 699 | 700 | #[test] 701 | fn test_filter_apply() { 702 | // Test filter too large. 703 | thread::spawn(|| { 704 | let filter: BpfProgram = vec![ 705 | sock_filter { 706 | code: 6, 707 | jt: 0, 708 | jf: 0, 709 | k: 0, 710 | }; 711 | 5000 // Limit is 4096 712 | ]; 713 | 714 | // Apply seccomp filter. 715 | assert!(matches!( 716 | apply_filter(&filter).unwrap_err(), 717 | Error::Seccomp(_) 718 | )); 719 | }) 720 | .join() 721 | .unwrap(); 722 | 723 | // Test empty filter. 724 | thread::spawn(|| { 725 | let filter: BpfProgram = vec![]; 726 | 727 | assert_eq!(filter.len(), 0); 728 | 729 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 730 | assert_eq!(seccomp_level, 0); 731 | 732 | assert!(matches!( 733 | apply_filter(&filter).unwrap_err(), 734 | Error::EmptyFilter 735 | )); 736 | 737 | // test that seccomp level remains 0 on failure. 738 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 739 | assert_eq!(seccomp_level, 0); 740 | }) 741 | .join() 742 | .unwrap(); 743 | 744 | // Test invalid BPF code. 745 | thread::spawn(|| { 746 | let filter = vec![sock_filter { 747 | // invalid opcode 748 | code: 9999, 749 | jt: 0, 750 | jf: 0, 751 | k: 0, 752 | }]; 753 | 754 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 755 | assert_eq!(seccomp_level, 0); 756 | 757 | assert!(matches!( 758 | apply_filter(&filter).unwrap_err(), 759 | Error::Seccomp(_) 760 | )); 761 | 762 | // test that seccomp level remains 0 on failure. 763 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 764 | assert_eq!(seccomp_level, 0); 765 | }) 766 | .join() 767 | .unwrap(); 768 | 769 | // Test valid filter and assert seccomp level. 770 | thread::spawn(|| { 771 | let filter = SeccompFilter::new( 772 | BTreeMap::new(), 773 | SeccompAction::Allow, 774 | SeccompAction::Trap, 775 | ARCH.try_into().unwrap(), 776 | ) 777 | .unwrap(); 778 | let prog: BpfProgram = filter.try_into().unwrap(); 779 | 780 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 781 | assert_eq!(seccomp_level, 0); 782 | 783 | apply_filter(&prog).unwrap(); 784 | 785 | // test that seccomp level is 2 (SECCOMP_MODE_FILTER). 786 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 787 | assert_eq!(seccomp_level, 2); 788 | }) 789 | .join() 790 | .unwrap(); 791 | } 792 | -------------------------------------------------------------------------------- /tests/json.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | #![cfg(feature = "json")] 5 | #![allow(clippy::undocumented_unsafe_blocks)] 6 | 7 | use seccompiler::{apply_filter, compile_from_json, BpfProgram}; 8 | use std::convert::TryInto; 9 | use std::env::consts::ARCH; 10 | use std::io::Read; 11 | use std::thread; 12 | 13 | const FAILURE_CODE: i32 = 1000; 14 | 15 | enum Errno { 16 | Equals(i32), 17 | NotEquals(i32), 18 | None, 19 | } 20 | 21 | fn validate_json_filter(reader: R, validation_fn: fn(), errno: Errno) { 22 | let mut filters = compile_from_json(reader, ARCH.try_into().unwrap()).unwrap(); 23 | let filter: BpfProgram = filters.remove("main_thread").unwrap(); 24 | 25 | // We need to run the validation inside another thread in order to avoid setting 26 | // the seccomp filter for the entire unit tests process. 27 | let returned_errno = thread::spawn(move || { 28 | // Install the filter. 29 | apply_filter(&filter).unwrap(); 30 | 31 | // Call the validation fn. 32 | validation_fn(); 33 | 34 | // Return errno. 35 | std::io::Error::last_os_error().raw_os_error().unwrap() 36 | }) 37 | .join() 38 | .unwrap(); 39 | 40 | match errno { 41 | Errno::Equals(no) => assert_eq!(returned_errno, no), 42 | Errno::NotEquals(no) => assert_ne!(returned_errno, no), 43 | Errno::None => {} 44 | }; 45 | } 46 | 47 | #[test] 48 | fn test_empty_filter_allow_all() { 49 | // An empty filter should always return the default action. 50 | // For example, for an empty allowlist, it should always trap/kill, 51 | // for an empty denylist, it should allow all system calls. 52 | 53 | let json_input = r#"{ 54 | "main_thread": { 55 | "mismatch_action": "allow", 56 | "match_action": "trap", 57 | "filter": [] 58 | } 59 | }"#; 60 | 61 | let mut filters = compile_from_json(json_input.as_bytes(), ARCH.try_into().unwrap()).unwrap(); 62 | let filter = filters.remove("main_thread").unwrap(); 63 | // This should allow any system calls. 64 | let pid = thread::spawn(move || { 65 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 66 | assert_eq!(seccomp_level, 0); 67 | // Install the filter. 68 | apply_filter(&filter).unwrap(); 69 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 70 | assert_eq!(seccomp_level, 2); 71 | unsafe { libc::getpid() } 72 | }) 73 | .join() 74 | .unwrap(); 75 | // Check that the getpid syscall returned successfully. 76 | assert!(pid > 0); 77 | } 78 | 79 | #[test] 80 | fn test_empty_filter_deny_all() { 81 | let json_input = r#"{ 82 | "main_thread": { 83 | "mismatch_action": "kill_process", 84 | "match_action": "allow", 85 | "filter": [] 86 | } 87 | }"#; 88 | 89 | let mut filters = compile_from_json(json_input.as_bytes(), ARCH.try_into().unwrap()).unwrap(); 90 | let filter = filters.remove("main_thread").unwrap(); 91 | 92 | // We need to use `fork` instead of `thread::spawn` to prohibit cargo from failing the test 93 | // due to the SIGSYS exit code. 94 | let pid = unsafe { libc::fork() }; 95 | 96 | match pid { 97 | 0 => { 98 | let seccomp_level = unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 99 | assert_eq!(seccomp_level, 0); 100 | // Install the filter. 101 | apply_filter(&filter).unwrap(); 102 | // this syscall will fail 103 | unsafe { libc::prctl(libc::PR_GET_SECCOMP) }; 104 | } 105 | child_pid => { 106 | let mut child_status: i32 = -1; 107 | let pid_done = unsafe { libc::waitpid(child_pid, &mut child_status, 0) }; 108 | assert_eq!(pid_done, child_pid); 109 | 110 | assert!(libc::WIFSIGNALED(child_status)); 111 | assert_eq!(libc::WTERMSIG(child_status), libc::SIGSYS); 112 | } 113 | } 114 | } 115 | 116 | #[test] 117 | fn test_invalid_architecture() { 118 | // A filter compiled for another architecture should kill the process upon evaluation. 119 | // The process will appear as if it received a SIGSYS. 120 | let mut arch = "aarch64"; 121 | 122 | if ARCH == "aarch64" { 123 | arch = "x86_64"; 124 | } 125 | 126 | let json_input = r#"{ 127 | "main_thread": { 128 | "mismatch_action": "allow", 129 | "match_action": "trap", 130 | "filter": [] 131 | } 132 | }"#; 133 | 134 | let mut filters = compile_from_json(json_input.as_bytes(), arch.try_into().unwrap()).unwrap(); 135 | let filter = filters.remove("main_thread").unwrap(); 136 | 137 | let pid = unsafe { libc::fork() }; 138 | match pid { 139 | 0 => { 140 | apply_filter(&filter).unwrap(); 141 | 142 | unsafe { 143 | libc::getpid(); 144 | } 145 | } 146 | child_pid => { 147 | let mut child_status: i32 = -1; 148 | let pid_done = unsafe { libc::waitpid(child_pid, &mut child_status, 0) }; 149 | assert_eq!(pid_done, child_pid); 150 | 151 | assert!(libc::WIFSIGNALED(child_status)); 152 | assert_eq!(libc::WTERMSIG(child_status), libc::SIGSYS); 153 | } 154 | }; 155 | } 156 | 157 | #[test] 158 | fn test_complex_filter() { 159 | let json_input = r#"{ 160 | "main_thread": { 161 | "mismatch_action": {"errno" : 1000}, 162 | "match_action": "allow", 163 | "filter": [ 164 | { 165 | "syscall": "rt_sigprocmask", 166 | "comment": "extra syscalls needed by the test runtime" 167 | }, 168 | { 169 | "syscall": "sigaltstack" 170 | }, 171 | { 172 | "syscall": "munmap" 173 | }, 174 | { 175 | "syscall": "exit" 176 | }, 177 | { 178 | "syscall": "rt_sigreturn" 179 | }, 180 | { 181 | "syscall": "futex" 182 | }, 183 | { 184 | "syscall": "getpid", 185 | "comment": "start of the actual filter we want to test." 186 | }, 187 | { 188 | "syscall": "ioctl", 189 | "args": [ 190 | { 191 | "index": 2, 192 | "type": "dword", 193 | "op": "le", 194 | "val": 14 195 | }, 196 | { 197 | "index": 2, 198 | "type": "dword", 199 | "op": "ne", 200 | "val": 13 201 | } 202 | ] 203 | }, 204 | { 205 | "syscall": "ioctl", 206 | "args": [ 207 | { 208 | "index": 2, 209 | "type": "dword", 210 | "op": "gt", 211 | "val": 20 212 | }, 213 | { 214 | "index": 2, 215 | "type": "dword", 216 | "op": "lt", 217 | "val": 40 218 | } 219 | ] 220 | }, 221 | { 222 | "syscall": "ioctl", 223 | "args": [ 224 | { 225 | "index": 0, 226 | "type": "dword", 227 | "op": "eq", 228 | "val": 1 229 | }, 230 | { 231 | "index": 2, 232 | "type": "dword", 233 | "op": "eq", 234 | "val": 15 235 | } 236 | ] 237 | }, 238 | { 239 | "syscall": "ioctl", 240 | "args": [ 241 | { 242 | "index": 2, 243 | "type": "qword", 244 | "op": "eq", 245 | "val": 4294967336, 246 | "comment": "u32::MAX as u64 + 41" 247 | } 248 | ] 249 | }, 250 | { 251 | "syscall": "madvise", 252 | "args": [ 253 | { 254 | "index": 0, 255 | "type": "dword", 256 | "op": "eq", 257 | "val": 0 258 | }, 259 | { 260 | "index": 1, 261 | "type": "dword", 262 | "op": "eq", 263 | "val": 0 264 | } 265 | ] 266 | } 267 | ] 268 | } 269 | }"#; 270 | 271 | // check syscalls that are supposed to work 272 | { 273 | validate_json_filter( 274 | json_input.as_bytes(), 275 | || unsafe { 276 | libc::ioctl(0, 0, 12); 277 | }, 278 | Errno::NotEquals(FAILURE_CODE), 279 | ); 280 | 281 | validate_json_filter( 282 | json_input.as_bytes(), 283 | || unsafe { 284 | libc::ioctl(0, 0, 14); 285 | }, 286 | Errno::NotEquals(FAILURE_CODE), 287 | ); 288 | 289 | validate_json_filter( 290 | json_input.as_bytes(), 291 | || unsafe { 292 | libc::ioctl(0, 0, 21); 293 | }, 294 | Errno::NotEquals(FAILURE_CODE), 295 | ); 296 | 297 | validate_json_filter( 298 | json_input.as_bytes(), 299 | || unsafe { 300 | libc::ioctl(0, 0, 39); 301 | }, 302 | Errno::NotEquals(FAILURE_CODE), 303 | ); 304 | 305 | validate_json_filter( 306 | json_input.as_bytes(), 307 | || unsafe { 308 | libc::ioctl(1, 0, 15); 309 | }, 310 | Errno::NotEquals(FAILURE_CODE), 311 | ); 312 | 313 | validate_json_filter( 314 | json_input.as_bytes(), 315 | || unsafe { 316 | libc::ioctl(0, 0, u32::MAX as u64 + 41); 317 | }, 318 | Errno::NotEquals(FAILURE_CODE), 319 | ); 320 | 321 | validate_json_filter( 322 | json_input.as_bytes(), 323 | || unsafe { 324 | libc::madvise(std::ptr::null_mut(), 0, 0); 325 | }, 326 | Errno::NotEquals(FAILURE_CODE), 327 | ); 328 | 329 | validate_json_filter( 330 | json_input.as_bytes(), 331 | || unsafe { 332 | assert!(libc::getpid() > 0); 333 | }, 334 | Errno::None, 335 | ); 336 | } 337 | 338 | // check syscalls that are not supposed to work 339 | { 340 | validate_json_filter( 341 | json_input.as_bytes(), 342 | || unsafe { 343 | libc::ioctl(0, 0, 13); 344 | }, 345 | Errno::Equals(FAILURE_CODE), 346 | ); 347 | 348 | validate_json_filter( 349 | json_input.as_bytes(), 350 | || unsafe { 351 | libc::ioctl(0, 0, 16); 352 | }, 353 | Errno::Equals(FAILURE_CODE), 354 | ); 355 | 356 | validate_json_filter( 357 | json_input.as_bytes(), 358 | || unsafe { 359 | libc::ioctl(0, 0, 17); 360 | }, 361 | Errno::Equals(FAILURE_CODE), 362 | ); 363 | 364 | validate_json_filter( 365 | json_input.as_bytes(), 366 | || unsafe { 367 | libc::ioctl(0, 0, 18); 368 | }, 369 | Errno::Equals(FAILURE_CODE), 370 | ); 371 | 372 | validate_json_filter( 373 | json_input.as_bytes(), 374 | || unsafe { 375 | libc::ioctl(0, 0, 19); 376 | }, 377 | Errno::Equals(FAILURE_CODE), 378 | ); 379 | 380 | validate_json_filter( 381 | json_input.as_bytes(), 382 | || unsafe { 383 | libc::ioctl(0, 0, 20); 384 | }, 385 | Errno::Equals(FAILURE_CODE), 386 | ); 387 | 388 | validate_json_filter( 389 | json_input.as_bytes(), 390 | || unsafe { 391 | libc::ioctl(0, 0, u32::MAX as u64 + 42); 392 | }, 393 | Errno::Equals(FAILURE_CODE), 394 | ); 395 | 396 | validate_json_filter( 397 | json_input.as_bytes(), 398 | || unsafe { 399 | libc::madvise(std::ptr::null_mut(), 1, 0); 400 | }, 401 | Errno::Equals(FAILURE_CODE), 402 | ); 403 | 404 | validate_json_filter( 405 | json_input.as_bytes(), 406 | || unsafe { 407 | assert_eq!(libc::getuid() as i32, -FAILURE_CODE); 408 | }, 409 | Errno::None, 410 | ); 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /tests/multi_thread.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::undocumented_unsafe_blocks)] 2 | 3 | /// This test is in a separate top-level test file so that it is isolated from the other tests - 4 | /// each file in the tests/ directory gets compiled to a separate binary and is run as a separate 5 | /// process. 6 | use std::collections::BTreeMap; 7 | 8 | use std::sync::mpsc::sync_channel; 9 | use std::thread; 10 | 11 | use seccompiler::{ 12 | apply_filter_all_threads, BpfProgram, SeccompAction, SeccompFilter, SeccompRule, 13 | }; 14 | use std::env::consts::ARCH; 15 | 16 | fn check_getpid_fails() { 17 | let pid = unsafe { libc::getpid() }; 18 | let errno = std::io::Error::last_os_error().raw_os_error().unwrap(); 19 | 20 | assert_eq!(pid, -1, "getpid should return -1 as set in SeccompFilter"); 21 | assert_eq!(errno, 0, "there should be no errors"); 22 | } 23 | 24 | #[test] 25 | /// Test seccomp's TSYNC functionality, which syncs the current filter to all threads in the 26 | /// process. 27 | fn test_tsync() { 28 | // These channels will block on send until the receiver has called recv. 29 | let (setup_tx, setup_rx) = sync_channel::<()>(0); 30 | let (finish_tx, finish_rx) = sync_channel::<()>(0); 31 | 32 | // first check getpid is working 33 | let pid = unsafe { libc::getpid() }; 34 | let errno = std::io::Error::last_os_error().raw_os_error().unwrap(); 35 | 36 | assert!(pid > 0, "getpid should return the actual pid"); 37 | assert_eq!(errno, 0, "there should be no errors"); 38 | 39 | // create two threads, one which applies the filter to all threads and another which tries 40 | // to call getpid. 41 | let seccomp_thread = thread::spawn(move || { 42 | let rules = vec![(libc::SYS_getpid, vec![])]; 43 | 44 | let rule_map: BTreeMap> = rules.into_iter().collect(); 45 | 46 | // Build seccomp filter only disallowing getpid 47 | let filter = SeccompFilter::new( 48 | rule_map, 49 | SeccompAction::Allow, 50 | SeccompAction::Errno(1u32), 51 | ARCH.try_into().unwrap(), 52 | ) 53 | .unwrap(); 54 | 55 | let filter: BpfProgram = filter.try_into().unwrap(); 56 | apply_filter_all_threads(&filter).unwrap(); 57 | 58 | // Verify seccomp is working in this thread 59 | check_getpid_fails(); 60 | 61 | // seccomp setup done, let the other thread start 62 | setup_tx.send(()).unwrap(); 63 | 64 | // don't close this thread until the other thread is done asserting. This way we can be 65 | // sure the thread that loaded the filter is definitely active when the other thread runs. 66 | finish_rx.recv().unwrap(); 67 | println!("exit seccomp thread"); 68 | }); 69 | 70 | let test_thread = thread::spawn(move || { 71 | // wait until seccomp setup is done 72 | setup_rx.recv().unwrap(); 73 | 74 | // Verify seccomp is working in this thread after disallowing it in other thread 75 | check_getpid_fails(); 76 | 77 | // let other thread know we've passed 78 | finish_tx.send(()).unwrap(); 79 | println!("exit io thread"); 80 | }); 81 | 82 | let seccomp_res = seccomp_thread.join(); 83 | assert!( 84 | seccomp_res.is_ok(), 85 | "seccomp thread failed: {:?}", 86 | seccomp_res.unwrap_err() 87 | ); 88 | let test_res = test_thread.join(); 89 | assert!( 90 | test_res.is_ok(), 91 | "test thread failed: {:?}", 92 | test_res.unwrap_err() 93 | ); 94 | 95 | // Verify seccomp is working in the parent thread as well 96 | check_getpid_fails(); 97 | } 98 | -------------------------------------------------------------------------------- /tools/generate_syscall_tables.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 4 | 5 | # This script generates the syscall tables for seccompiler. 6 | 7 | set -e 8 | 9 | # Full path to the seccompiler tools dir. 10 | TOOLS_DIR=$(cd "$(dirname "$0")" && pwd) 11 | 12 | # Full path to the seccompiler sources dir. 13 | ROOT_DIR=$(cd "${TOOLS_DIR}/.." && pwd) 14 | 15 | # Path to the temporary linux kernel directory. 16 | KERNEL_DIR="${ROOT_DIR}/.kernel" 17 | 18 | test_mode=0 19 | 20 | PATH_TO_X86_TABLE="$ROOT_DIR/src/syscall_table/x86_64.rs" 21 | PATH_TO_AARCH64_TABLE="$ROOT_DIR/src/syscall_table/aarch64.rs" 22 | PATH_TO_RISCV64_TABLE="$ROOT_DIR/src/syscall_table/riscv64.rs" 23 | 24 | PATH_TO_X86_TEST_TABLE="$ROOT_DIR/src/syscall_table/test_x86_64.rs" 25 | PATH_TO_AARCH64_TEST_TABLE="$ROOT_DIR/src/syscall_table/test_aarch64.rs" 26 | PATH_TO_RISCV64_TEST_TABLE="$ROOT_DIR/src/syscall_table/test_riscv64.rs" 27 | 28 | install_header() { 29 | make -C "$KERNEL_DIR/linux" ARCH="$1" INSTALL_HDR_PATH="$1-headers" headers_install &>/dev/null 30 | echo $KERNEL_DIR/linux/$1-headers/include/asm/unistd_64.h 31 | } 32 | 33 | generate_syscall_list() { 34 | syscall_header=$(install_header $1) 35 | echo $(cat ${syscall_header} | grep "#define __NR_" |\ 36 | grep -v "__NR_syscalls" | grep -v "__NR_arch_specific_syscall" |\ 37 | awk -F '__NR_' '{print $2}' | awk '{ print "(\""$1"\", "$2")," }' | sort -d) 38 | } 39 | 40 | write_rust_syscall_table() { 41 | kernel_version=$1 42 | platform=$2 43 | path_to_rust_file=$3 44 | 45 | if [ "$platform" == "x86_64" ]; then 46 | syscall_list=$(generate_syscall_list x86_64) 47 | elif [ "$platform" == "aarch64" ]; then 48 | syscall_list=$(generate_syscall_list arm64) 49 | elif [ "$platform" == "riscv64" ]; then 50 | syscall_list=$(generate_syscall_list riscv) 51 | else 52 | die "Invalid platform" 53 | fi 54 | 55 | echo "$(get_rust_file_header "$kernel_version")" > $path_to_rust_file 56 | 57 | printf " 58 | use std::collections::HashMap; 59 | 60 | pub(crate) fn make_syscall_table() -> HashMap<&'static str, i64> { 61 | vec![%s].into_iter().collect() }" "${syscall_list}" >> $path_to_rust_file 62 | 63 | rustfmt $path_to_rust_file 64 | 65 | echo "Generated at: $path_to_rust_file" 66 | } 67 | 68 | # Validate the user supplied kernel version number. 69 | # It must be composed of 2 groups of integers separated by dot, with an 70 | # optional third group. 71 | validate_kernel_version() { 72 | local version_regex="^([0-9]+.)[0-9]+(.[0-9]+)?$" 73 | version="$1" 74 | 75 | if [ -z "$version" ]; then 76 | die "Version cannot be empty." 77 | elif [[ ! "$version" =~ $version_regex ]]; then 78 | die "Invalid version number: $version (expected: \$Major.\$Minor.\$Patch(optional))." 79 | fi 80 | 81 | } 82 | 83 | download_kernel() { 84 | kernel_version=$1 85 | kernel_major=v$(echo ${kernel_version} | cut -d . -f 1).x 86 | kernel_baseurl=https://www.kernel.org/pub/linux/kernel/${kernel_major} 87 | kernel_archive=linux-${kernel_version}.tar.xz 88 | 89 | # Create the kernel clone directory 90 | rm -rf "$KERNEL_DIR" 91 | mkdir -p "$KERNEL_DIR" || die "Error: cannot create dir $dir" 92 | [ -x "$KERNEL_DIR" ] && [ -w "$dir" ] || \ 93 | { 94 | chmod +x+w "$KERNEL_DIR" 95 | } || \ 96 | die "Error: wrong permissions for $KERNEL_DIR. Should be +x+w" 97 | 98 | cd "$KERNEL_DIR" 99 | 100 | echo "Fetching linux kernel..." 101 | 102 | # Get sha256 checksum. 103 | curl -fsSLO ${kernel_baseurl}/sha256sums.asc 104 | kernel_sha256=$(grep ${kernel_archive} sha256sums.asc | cut -d ' ' -f 1) 105 | # Get kernel archive. 106 | curl -fsSLO "$kernel_baseurl/$kernel_archive" 107 | # Verify checksum. 108 | echo "${kernel_sha256} ${kernel_archive}" | sha256sum -c - 109 | # Decompress the kernel source. 110 | xz -d "${kernel_archive}" 111 | cat linux-${kernel_version}.tar | tar -x && \ 112 | mv linux-${kernel_version} linux 113 | } 114 | 115 | run_validation() { 116 | # We want to regenerate the tables and compare them with the existing ones. 117 | # This is to validate that the tables are actually correct and were not 118 | # mistakenly or maliciously modified. 119 | arch=$1 120 | kernel_version=$2 121 | 122 | if [[ $arch == "x86_64" ]]; then 123 | path_to_table=$PATH_TO_X86_TABLE 124 | path_to_test_table=$PATH_TO_X86_TEST_TABLE 125 | elif [[ $arch == "aarch64" ]]; then 126 | path_to_table=$PATH_TO_AARCH64_TABLE 127 | path_to_test_table=$PATH_TO_AARCH64_TEST_TABLE 128 | elif [[ $arch == "riscv64" ]]; then 129 | path_to_table=$PATH_TO_RISCV64_TABLE 130 | path_to_test_table=$PATH_TO_RISCV64_TEST_TABLE 131 | else 132 | die "Invalid platform" 133 | fi 134 | 135 | download_kernel "$kernel_version" 136 | 137 | # Generate new tables to validate against. 138 | write_rust_syscall_table \ 139 | "$kernel_version" "$arch" "$path_to_test_table" 140 | 141 | # Perform comparison. Tables should be identical, except for the timestamp 142 | # comment line. 143 | diff -I "\/\/ Generated on:.*" -I "\/\/ Copyright .*" $path_to_table $path_to_test_table || { 144 | echo "" 145 | echo "Syscall table validation failed." 146 | echo "Make sure they haven't been mistakenly altered." 147 | echo "" 148 | 149 | exit 1 150 | } 151 | 152 | echo "Validation successful." 153 | } 154 | 155 | get_rust_file_header() { 156 | echo "$(cat <<-END 157 | // Copyright $(date +"%Y") Amazon.com, Inc. or its affiliates. All Rights Reserved. 158 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 159 | 160 | // This file is auto-generated by \`tools/generate_syscall_tables\`. 161 | // Do NOT manually edit! 162 | // Generated on: $(date) 163 | // Kernel version: $1 164 | END 165 | )" 166 | } 167 | 168 | # Exit with an error message 169 | die() { 170 | echo -e "$1" 171 | exit 1 172 | } 173 | 174 | help() { 175 | echo "" 176 | echo "Generates the syscall tables for seccompiler, according to a given kernel version." 177 | echo "Release candidate (rc) linux versions are not allowed." 178 | echo "Outputs a rust file for each supported arch: src/seccompiler/src/syscall_table/{arch}.rs" 179 | echo "Supported architectures: x86_64 and aarch64." 180 | echo "" 181 | echo "If passed the --test flag, it will validate that the generated syscall tables" 182 | echo "are correct by regenerating them and comparing the results." 183 | echo "" 184 | } 185 | 186 | cleanup () { 187 | rm -rf $KERNEL_DIR 188 | 189 | if [[ $test_mode -eq 1 ]]; then 190 | rm -rf $PATH_TO_X86_TEST_TABLE 191 | rm -rf $PATH_TO_AARCH64_TEST_TABLE 192 | rm -rf $PATH_TO_RISCV64_TEST_TABLE 193 | fi 194 | } 195 | 196 | parse_cmdline() { 197 | # Parse command line args. 198 | while [ $# -gt 0 ]; do 199 | case "$1" in 200 | "-h"|"--help") { help; exit 1; } ;; 201 | "--test") { test_mode=1; break; } ;; 202 | *) { kernel_version="$1"; } ;; 203 | esac 204 | shift 205 | done 206 | } 207 | 208 | test() { 209 | # Run the validation for x86_64. 210 | echo "Validating table for x86_64..." 211 | 212 | kernel_version_x86_64=$(cat $PATH_TO_X86_TABLE | \ 213 | awk -F '// Kernel version:' '{print $2}' | xargs) 214 | 215 | validate_kernel_version "$kernel_version_x86_64" 216 | 217 | run_validation "x86_64" "$kernel_version_x86_64" 218 | 219 | # Run the validation for aarch64. 220 | echo "Validating table for aarch64..." 221 | 222 | kernel_version_aarch64=$(cat $PATH_TO_AARCH64_TABLE | \ 223 | awk -F '// Kernel version:' '{print $2}' | xargs) 224 | 225 | validate_kernel_version "$kernel_version_aarch64" 226 | 227 | run_validation "aarch64" "$kernel_version_aarch64" 228 | 229 | # Run the validation for riscv64. 230 | echo "Validating table for riscv64..." 231 | 232 | kernel_version_riscv64=$(cat $PATH_TO_RISCV64_TABLE | \ 233 | awk -F '// Kernel version:' '{print $2}' | xargs) 234 | 235 | validate_kernel_version "$kernel_version_riscv64" 236 | 237 | run_validation "riscv64" "$kernel_version_riscv64" 238 | } 239 | 240 | main() { 241 | if [[ $test_mode -eq 1 ]]; then 242 | # When in test mode, re-generate the tables according to the version 243 | # from the rust files and validate that they are identical. 244 | test 245 | else 246 | # When not in test mode, we only want to re-generate the tables. 247 | 248 | validate_kernel_version "$kernel_version" 249 | download_kernel "$kernel_version" 250 | 251 | # generate syscall table for x86_64 252 | echo "Generating table for x86_64..." 253 | write_rust_syscall_table \ 254 | "$kernel_version" "x86_64" "$PATH_TO_X86_TABLE" 255 | 256 | # generate syscall table for aarch64 257 | echo "Generating table for aarch64..." 258 | write_rust_syscall_table \ 259 | "$kernel_version" "aarch64" "$PATH_TO_AARCH64_TABLE" 260 | 261 | # generate syscall table for riscv64 262 | echo "Generating table for riscv64..." 263 | write_rust_syscall_table \ 264 | "$kernel_version" "riscv64" "$PATH_TO_RISCV64_TABLE" 265 | fi 266 | } 267 | 268 | # Setup a cleanup trap on exit. 269 | trap cleanup EXIT 270 | 271 | parse_cmdline $@ 272 | 273 | main 274 | --------------------------------------------------------------------------------