├── .clang-format ├── .gitattributes ├── .github └── workflows │ └── linux.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── static_key.c ├── static_key.cpp ├── static_key.h ├── static_key.hpp └── tests ├── arm.cmake ├── host.cmake ├── rv64.cmake ├── test.sh └── test_cross.sh /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | platform: [ host, arm, rv64 ] 14 | build_type: [ Debug, Release ] 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: dtcxzyw/checkout@main 20 | 21 | - name: Install dependencies 22 | run: | 23 | sudo apt-get update 24 | sudo apt-get install -y qemu-user gcc-riscv64-linux-gnu g++-riscv64-linux-gnu gcc-arm-linux-gnueabi g++-arm-linux-gnueabi cmake 25 | 26 | - name: Build and test 27 | run: | 28 | ./tests/test_cross.sh ${{matrix.build_type}} ${{matrix.platform}} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | .cache 4 | __pycache__ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | enable_testing() 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | 6 | set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL) 7 | set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC) 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | set(CMAKE_CXX_EXTENSIONS OFF) 12 | 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_C_STANDARD_REQUIRED ON) 15 | set(CMAKE_C_EXTENSIONS OFF) 16 | 17 | project(static_key) 18 | 19 | add_executable(test_static_key_c static_key.c) 20 | add_executable(test_static_key_cpp static_key.cpp) 21 | 22 | add_test(NAME test_static_key_c COMMAND ${CMAKE_CURRENT_LIST_DIR}/tests/test.sh ${STATIC_KEY_RUNNER} ./test_static_key_c) 23 | add_test(NAME test_static_key_cpp COMMAND ${CMAKE_CURRENT_LIST_DIR}/tests/test.sh ${STATIC_KEY_RUNNER} ./test_static_key_cpp) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Yingwei Zheng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static-key 2 | A header-only, standalone user-space implementation of the Linux's static key mechanism. 3 | 4 | ## Usage 5 | 6 | C API: 7 | ``` 8 | #include 9 | 10 | struct static_key key; // Declare a static key 11 | 12 | static_key_fuse(&key, true/false); // Set the key to true/false. Don't call it twice with the same key!!! 13 | 14 | // Get the value of the key. Don't call it before static_key_fuse() is called. 15 | if (static_key_branch(&key)) { 16 | // The key is true 17 | } else { 18 | // The key is false 19 | } 20 | 21 | ``` 22 | 23 | C++ wrapper: 24 | 25 | ``` 26 | #include 27 | 28 | static_key_cpp::flag key; 29 | 30 | key.fuse(true/false); 31 | 32 | if (key) { 33 | // The key is true 34 | } else { 35 | // The key is false 36 | } 37 | 38 | ``` 39 | 40 | ## Reference 41 | https://docs.kernel.org/staging/static-keys.html 42 | -------------------------------------------------------------------------------- /static_key.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // MIT License 4 | // 5 | // Copyright (c) 2024 Yingwei Zheng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #include "static_key.h" 26 | #include 27 | #include 28 | 29 | struct static_key key1; 30 | struct static_key key2; 31 | 32 | int main() { 33 | static_key_fuse(&key1, getenv("KEY1")); 34 | static_key_fuse(&key2, getenv("KEY2")); 35 | 36 | if (static_key_branch(&key1)) 37 | printf("key1 enabled.\n"); 38 | else 39 | printf("key1 disabled.\n"); 40 | 41 | if (static_key_branch(&key2)) 42 | printf("key2 enabled.\n"); 43 | else 44 | printf("key2 disabled.\n"); 45 | 46 | return EXIT_SUCCESS; 47 | } 48 | -------------------------------------------------------------------------------- /static_key.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // MIT License 4 | // 5 | // Copyright (c) 2024 Yingwei Zheng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #include "static_key.hpp" 26 | #include 27 | #include 28 | 29 | static_key_cpp::flag key1; 30 | static_key_cpp::flag key2; 31 | 32 | int main() { 33 | key1.fuse(getenv("KEY1")); 34 | key2.fuse(getenv("KEY2")); 35 | 36 | if (key1) 37 | printf("key1 enabled.\n"); 38 | else 39 | printf("key1 disabled.\n"); 40 | 41 | if (key2) 42 | printf("key2 enabled.\n"); 43 | else 44 | printf("key2 disabled.\n"); 45 | 46 | return EXIT_SUCCESS; 47 | } 48 | -------------------------------------------------------------------------------- /static_key.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // MIT License 4 | // 5 | // Copyright (c) 2024 Yingwei Zheng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #pragma once 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #ifdef __cplusplus 33 | #include 34 | #else 35 | #include 36 | #endif 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | #if !defined(__OPTIMIZE__) || !defined(__unix__) 43 | #define STATIC_KEY_FORCE_FALLBACK 44 | #endif 45 | 46 | #ifdef STATIC_KEY_FORCE_FALLBACK 47 | #define STATIC_KEY_FALLBACK 48 | #else 49 | #if defined(__i386__) || defined(__x86_64__) 50 | #define STATIC_KEY_X86 51 | #define STATIC_KEY_WORDSIZE 2 52 | #define STATIC_KEY_JUMP_BEGIN "" 53 | #define STATIC_KEY_JUMP " jmp " 54 | #define STATIC_KEY_JUMP_END "" 55 | #define STATIC_KEY_C0 " %c0 " 56 | #elif defined(__arm__) || defined(__thumb__) 57 | #define STATIC_KEY_ARM 58 | #define STATIC_KEY_WORDSIZE 4 59 | #define STATIC_KEY_JUMP_BEGIN "" 60 | #define STATIC_KEY_JUMP " b " 61 | #define STATIC_KEY_JUMP_END "" 62 | #define STATIC_KEY_C0 " %c0 " 63 | #elif defined(__riscv) 64 | #define STATIC_KEY_RISCV 65 | #define STATIC_KEY_WORDSIZE 4 66 | #define STATIC_KEY_JUMP_BEGIN \ 67 | ".option push\n" \ 68 | ".option norelax\n" \ 69 | ".option norvc\n" 70 | #define STATIC_KEY_JUMP " j " 71 | #define STATIC_KEY_JUMP_END ".option pop\n" 72 | #define STATIC_KEY_C0 " %0 " 73 | #else 74 | #ifdef STATIC_KEY_ALLOW_FALLBACK 75 | #define STATIC_KEY_FALLBACK 76 | #define STATIC_KEY_WORDSIZE __SIZEOF_POINTER__ 77 | #else 78 | #error "Unsupported architecture" 79 | #endif 80 | #endif 81 | 82 | #if __SIZEOF_POINTER__ / STATIC_KEY_WORDSIZE == 4 83 | #define STATIC_KEY_ASM_PTR ".quad " 84 | #elif __SIZEOF_POINTER__ / STATIC_KEY_WORDSIZE == 2 85 | #define STATIC_KEY_ASM_PTR ".dword " 86 | #elif __SIZEOF_POINTER__ / STATIC_KEY_WORDSIZE == 1 87 | #define STATIC_KEY_ASM_PTR ".word " 88 | #else 89 | #error "Unknown pointer size" 90 | #endif 91 | 92 | #endif 93 | 94 | #ifndef NDEBUG 95 | #define STATIC_KEY_DEBUG 96 | #endif 97 | 98 | #ifdef __cplusplus 99 | #define STATIC_KEY_ATOMIC_FLAG std::atomic_flag 100 | #define STATIC_KEY_ATOMIC_BOOL std::atomic_bool 101 | #else 102 | #define STATIC_KEY_ATOMIC_FLAG atomic_flag 103 | #define STATIC_KEY_ATOMIC_BOOL atomic_bool 104 | #endif 105 | 106 | struct static_key { 107 | #ifdef STATIC_KEY_FALLBACK 108 | STATIC_KEY_ATOMIC_BOOL enabled; 109 | #endif 110 | #ifdef STATIC_KEY_DEBUG 111 | STATIC_KEY_ATOMIC_FLAG inited; 112 | #endif 113 | char padding[2]; 114 | }; 115 | 116 | struct static_key_use { 117 | int32_t beg; 118 | int32_t end; 119 | ptrdiff_t key; 120 | }; 121 | 122 | __always_inline bool static_key_branch(struct static_key *key) { 123 | #ifdef STATIC_KEY_DEBUG 124 | assert(atomic_flag_test_and_set(&key->inited) && "Use before init"); 125 | #endif 126 | #ifdef STATIC_KEY_FALLBACK 127 | return atomic_load(&key->enabled); 128 | #else 129 | __asm__ goto(".p2align 2\n" 130 | "1:" STATIC_KEY_JUMP_BEGIN STATIC_KEY_JUMP 131 | " %l[ret_false]\n" STATIC_KEY_JUMP_END "2:\n" 132 | ".pushsection __static_key_jump_table, \"a\"\n" 133 | ".p2align 2\n" 134 | ".long 1b - .\n" 135 | ".long 2b - . + 4\n" STATIC_KEY_ASM_PTR STATIC_KEY_C0 136 | " - . + 8\n" 137 | ".popsection\n" 138 | : 139 | : "i"(key) 140 | : 141 | : ret_false); 142 | return true; 143 | ret_false: 144 | return false; 145 | #endif 146 | } 147 | 148 | #ifndef STATIC_KEY_FALLBACK 149 | static inline void static_key_patch_nop(char *begin, char *end) { 150 | size_t page_size = sysconf(_SC_PAGE_SIZE); 151 | intptr_t mask = page_size - 1; 152 | char *page_begin = (char *)(((intptr_t)begin) & ~mask); 153 | char *page_end = (char *)((((intptr_t)end) + mask) & ~mask); 154 | ptrdiff_t size = page_end - page_begin; 155 | mprotect(page_begin, size, PROT_READ | PROT_WRITE | PROT_EXEC); 156 | #if defined(STATIC_KEY_X86) 157 | for (; begin != end; ++begin) 158 | *begin = 0x90; 159 | #elif defined(STATIC_KEY_RISCV) 160 | assert((end - begin) % 4 == 0); 161 | for (; begin != end; begin += 4) 162 | *(int32_t *)begin = 0x00000013; 163 | #elif defined(STATIC_KEY_ARM) 164 | for (; begin != end; ++begin) 165 | *begin = 0; 166 | #else 167 | #error "Unsupported architecture" 168 | #endif 169 | mprotect(page_begin, size, PROT_READ | PROT_EXEC); 170 | } 171 | #endif 172 | 173 | extern const struct static_key_use __start___static_key_jump_table[]; 174 | extern const struct static_key_use __stop___static_key_jump_table[]; 175 | 176 | static inline void static_key_fuse(struct static_key *entry, bool enabled) { 177 | #ifdef STATIC_KEY_DEBUG 178 | assert(!atomic_flag_test_and_set(&entry->inited) && "Cannot init twice"); 179 | #endif 180 | #ifdef STATIC_KEY_FALLBACK 181 | atomic_store(&entry->enabled, enabled); 182 | #else 183 | if (!enabled) 184 | return; 185 | const struct static_key_use *iter = __start___static_key_jump_table; 186 | const struct static_key_use *end = __stop___static_key_jump_table; 187 | for (; iter != end; ++iter) { 188 | if (((char *)iter) + iter->key == (char *)entry) { 189 | static_key_patch_nop(((char *)(iter)) + iter->beg, 190 | ((char *)(iter)) + iter->end); 191 | #ifndef STATIC_KEY_X86 192 | __builtin___clear_cache(((char *)(iter)) + iter->beg, 193 | ((char *)(iter)) + iter->end); 194 | #endif 195 | } 196 | } 197 | #endif 198 | } 199 | 200 | #ifdef __cplusplus 201 | } 202 | #endif 203 | -------------------------------------------------------------------------------- /static_key.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // MIT License 4 | // 5 | // Copyright (c) 2024 Yingwei Zheng 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | 25 | #pragma once 26 | #include "static_key.h" 27 | 28 | namespace static_key_cpp { 29 | 30 | class flag { 31 | static_key m_key; 32 | 33 | public: 34 | flag() = default; 35 | ~flag() = default; 36 | flag(const flag &) = delete; 37 | flag(flag &&) = delete; 38 | flag &operator=(const flag &) = delete; 39 | flag &operator=(flag &&) = delete; 40 | 41 | void fuse(bool enabled) { static_key_fuse(&m_key, enabled); } 42 | operator bool() const { 43 | return static_key_branch(const_cast(&m_key)); 44 | } 45 | }; 46 | 47 | } // namespace static_key_cpp 48 | -------------------------------------------------------------------------------- /tests/arm.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_C_COMPILER arm-linux-gnueabi-gcc) 3 | set(CMAKE_CXX_COMPILER arm-linux-gnueabi-g++) 4 | set(STATIC_KEY_RUNNER qemu-arm -L /usr/arm-linux-gnueabi) 5 | -------------------------------------------------------------------------------- /tests/host.cmake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtcxzyw/static-key/e9df81b127a1ee2d764f270e8aed605d67bcf05b/tests/host.cmake -------------------------------------------------------------------------------- /tests/rv64.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_C_COMPILER riscv64-linux-gnu-gcc) 3 | set(CMAKE_CXX_COMPILER riscv64-linux-gnu-g++) 4 | set(STATIC_KEY_RUNNER qemu-riscv64 -L /usr/riscv64-linux-gnu) -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | set -euo pipefail 3 | shopt -s inherit_errexit 4 | 5 | $@ | grep -Pz "key1 disabled.\nkey2 disabled.\n" > /dev/null 6 | KEY1=1 $@ | grep -Pz "key1 enabled.\nkey2 disabled.\n" > /dev/null 7 | KEY2=1 $@ | grep -Pz "key1 disabled.\nkey2 enabled.\n" > /dev/null 8 | KEY1=1 KEY2=1 $@ | grep -Pz "key1 enabled.\nkey2 enabled.\n" > /dev/null 9 | -------------------------------------------------------------------------------- /tests/test_cross.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | set -euo pipefail 3 | shopt -s inherit_errexit 4 | 5 | rm -rf build 6 | mkdir -p build 7 | cd build 8 | cmake .. -DCMAKE_TOOLCHAIN_FILE=../tests/$2.cmake -DCMAKE_BUILD_TYPE=$1 9 | cmake --build . -j 10 | ctest 11 | --------------------------------------------------------------------------------