├── .clang-format ├── .clang-tidy ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── SECURITY.md ├── cmake ├── FindSphinx.cmake └── rlbox-sandboxing-apiConfig.cmake.in ├── code ├── include │ ├── rlbox.hpp │ ├── rlbox_app_pointer.hpp │ ├── rlbox_conversion.hpp │ ├── rlbox_dylib_sandbox.hpp │ ├── rlbox_helpers.hpp │ ├── rlbox_noop_sandbox.hpp │ ├── rlbox_policy_types.hpp │ ├── rlbox_range.hpp │ ├── rlbox_sandbox.hpp │ ├── rlbox_stdlib.hpp │ ├── rlbox_stdlib_polyfill.hpp │ ├── rlbox_struct_support.hpp │ ├── rlbox_type_traits.hpp │ ├── rlbox_types.hpp │ ├── rlbox_unwrap.hpp │ └── rlbox_wrapper_traits.hpp └── tests │ ├── rlbox │ ├── test_app_pointers.cpp │ ├── test_bool_operators.cpp │ ├── test_callback.cpp │ ├── test_comparison.cpp │ ├── test_conversion.cpp │ ├── test_include.hpp │ ├── test_operators.cpp │ ├── test_sandbox_function_assignment.cpp │ ├── test_sandbox_noop_sandbox.cpp │ ├── test_sandbox_noop_sandbox_invoke_fail.cpp │ ├── test_sandbox_ptr_conversion.cpp │ ├── test_sandbox_transition_customization.cpp │ ├── test_sandbox_transition_timings.cpp │ ├── test_sandbox_types.cpp │ ├── test_stdlib.cpp │ ├── test_tainted_assignment.cpp │ ├── test_tainted_opaque.cpp │ ├── test_tainted_sizes.cpp │ ├── test_tainted_structs.cpp │ ├── test_tainted_structs.hpp │ ├── test_type_traits.cpp │ ├── test_verification.cpp │ ├── test_verify_arrays.cpp │ ├── test_wrapper_traits.cpp │ └── testing_structs_for_cpp_api.h │ ├── rlbox_glue │ ├── lib │ │ ├── libtest.c │ │ ├── libtest.h │ │ └── libtest_structs_for_cpp_api.h │ ├── test_dylib_sandbox_glue.cpp │ ├── test_noop_sandbox_glue.cpp │ ├── test_noop_sandbox_glue_configs.cpp │ └── test_sandbox_glue.inc.cpp │ └── test_main.cpp ├── iwyu.imp ├── leak_suppressions.txt └── ub_suppressions.txt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Mozilla 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: Yes 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: false 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: true 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: false 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Mozilla 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeComma 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeComma 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 80 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 2 53 | ContinuationIndentWidth: 2 54 | Cpp11BracedListStyle: false 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: false 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"' 66 | Priority: 3 67 | - Regex: '^<' 68 | Priority: 2 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: true 73 | IndentPPDirectives: AfterHash 74 | IndentWidth: 2 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: Inner 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: true 86 | ObjCSpaceBeforeProtocolList: false 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 200 95 | PointerAlignment: Left 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: false 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: false 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 8 119 | UseTab: Never 120 | ... 121 | 122 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '*,-modernize-use-trailing-return-type' 2 | HeaderFilterRegex: '*' 3 | WarningsAsErrors: '*' -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master, ci_debug ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Configure CMake 25 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 26 | 27 | - name: Build 28 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 29 | 30 | - name: Test 31 | working-directory: ${{github.workspace}}/build 32 | run: ctest -V -C ${{env.BUILD_TYPE}} --rerun-failed --output-on-failure 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | /.vscode/ 35 | /build*/ 36 | *~ 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(rlbox-sandboxing-api 4 | VERSION 0.1 5 | DESCRIPTION "RLBox safe sandboxing API in C++17" 6 | HOMEPAGE_URL "https://github.com/PLSysSec/rlbox_sandboxing_api") 7 | 8 | set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 9 | 10 | # Project Settings ################### 11 | 12 | # set(CMAKE_VERBOSE_MAKEFILE ON) 13 | set(CMAKE_CXX_STANDARD 17) 14 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | 17 | if(MSVC) 18 | # ignore warnings about macro invocations with no parameters 19 | add_compile_options(/wd4002) 20 | # ignore warnings about using unsafe strcpy vs strcpy_s define _CRT_SECURE_NO_WARNINGS. 21 | add_compile_options(/D _CRT_SECURE_NO_WARNINGS) 22 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 23 | endif() 24 | 25 | option(DEV "Use settings suitable for dev contributions to rlbox" OFF) 26 | 27 | file(GLOB_RECURSE 28 | ALL_CXX_SOURCE_FILES 29 | code/*.[chi]pp 30 | code/*.[chi]xx 31 | code/*.cc 32 | code/*.hh 33 | code/*.ii 34 | code/*.[CHI]) 35 | 36 | # Dev Tools ################### 37 | 38 | if(DEV) 39 | if(MSVC) 40 | add_compile_options(/W4) # warnings 41 | add_compile_options(/WX) # warnings as errors 42 | else() 43 | add_compile_options(-Wall -Wextra -pedantic) # warnings 44 | add_compile_options(-Werror) # warnings as errors 45 | add_compile_options(-fsanitize=address) 46 | add_link_options(-fsanitize=address) 47 | add_compile_options(-fsanitize=undefined) 48 | add_link_options(-fsanitize=undefined) 49 | endif() 50 | 51 | find_program(CLANG_TIDY "clang-tidy") 52 | if(CLANG_TIDY) 53 | # Config in .clang-tidy 54 | set(CMAKE_CXX_CLANG_TIDY clang-tidy) 55 | endif() 56 | 57 | find_program(IWYU "iwyu") 58 | if("")#IWYU)# TODO: Still have to ensure this checks .hpp files, which it 59 | # doesn't right now. See bug below about "check_also"... which doesn't 60 | # seem to work https://github.com/include-what-you-use/include-what- 61 | # you-use/issues/633 62 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE 63 | iwyu 64 | -Xiwyu 65 | --transitive_includes_only 66 | -Xiwyu 67 | --check_also="*.hpp" 68 | # -Xiwyu 69 | # --verbose=4 70 | -Xiwyu --mapping_file=${CMAKE_SOURCE_DIR}/iwyu.imp) 71 | endif() 72 | 73 | endif() 74 | 75 | # Clang format ################### 76 | 77 | find_program(CLANG_FORMAT "clang-format") 78 | if(CLANG_FORMAT) 79 | # Config in .clang-format 80 | add_custom_target(format-source 81 | COMMAND clang-format 82 | -i 83 | -style=file 84 | ${ALL_CXX_SOURCE_FILES}) 85 | endif() 86 | 87 | 88 | # Dependencies ################### 89 | 90 | include(CTest) 91 | 92 | if(BUILD_TESTING) 93 | find_package(Catch2 2.13.7 QUIET) 94 | if(NOT Catch2_FOUND) 95 | include(FetchContent) 96 | FetchContent_Declare(catch2 97 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 98 | GIT_TAG v2.13.7) 99 | 100 | FetchContent_GetProperties(catch2) 101 | if(NOT catch2_POPULATED) 102 | FetchContent_Populate(catch2) 103 | endif() 104 | 105 | add_subdirectory("${catch2_SOURCE_DIR}") 106 | list(APPEND CMAKE_MODULE_PATH "${catch2_SOURCE_DIR}/contrib") 107 | endif() 108 | endif() 109 | 110 | file(GLOB_RECURSE 111 | RLBOX_SOURCE_FILES 112 | code/include/*.[chi]pp 113 | code/include/*.[chi]xx 114 | code/include/*.cc 115 | code/include/*.hh 116 | code/include/*.ii 117 | code/include/*.[CHI]) 118 | 119 | # Targets ################### 120 | 121 | include(GNUInstallDirs) 122 | 123 | find_package(Threads REQUIRED) 124 | add_library(${PROJECT_NAME} INTERFACE) 125 | target_include_directories(${PROJECT_NAME} INTERFACE 126 | $ 127 | $) 128 | target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) 129 | target_link_libraries(${PROJECT_NAME} INTERFACE ${CMAKE_THREAD_LIBS_INIT}) 130 | set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER "${RLBOX_SOURCE_FILES}") 131 | 132 | # Install ################### 133 | 134 | include(CMakePackageConfigHelpers) 135 | 136 | write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" 137 | VERSION ${CMAKE_PROJECT_VERSION} 138 | COMPATIBILITY SameMajorVersion) 139 | 140 | set(DATAROOT_CONFIG_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}") 141 | configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in 142 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 143 | INSTALL_DESTINATION ${DATAROOT_CONFIG_DESTINATION} 144 | PATH_VARS CMAKE_INSTALL_INCLUDEDIR) 145 | install(TARGETS ${PROJECT_NAME} 146 | EXPORT ${PROJECT_NAME}-targets 147 | PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rlbox" 148 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 149 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 150 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 151 | install(EXPORT ${PROJECT_NAME}-targets 152 | FILE ${PROJECT_NAME}Targets.cmake 153 | DESTINATION ${DATAROOT_CONFIG_DESTINATION}) 154 | install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 155 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 156 | DESTINATION ${DATAROOT_CONFIG_DESTINATION}) 157 | 158 | # Tests ################### 159 | 160 | if(BUILD_TESTING) 161 | 162 | include(Catch) 163 | 164 | # Test rlbox features 165 | 166 | add_executable(test_rlbox 167 | code/tests/test_main.cpp 168 | code/tests/rlbox/test_app_pointers.cpp 169 | code/tests/rlbox/test_bool_operators.cpp 170 | code/tests/rlbox/test_callback.cpp 171 | code/tests/rlbox/test_comparison.cpp 172 | code/tests/rlbox/test_conversion.cpp 173 | code/tests/rlbox/test_operators.cpp 174 | code/tests/rlbox/test_sandbox_function_assignment.cpp 175 | code/tests/rlbox/test_sandbox_noop_sandbox.cpp 176 | code/tests/rlbox/test_sandbox_noop_sandbox_invoke_fail.cpp 177 | code/tests/rlbox/test_sandbox_ptr_conversion.cpp 178 | code/tests/rlbox/test_sandbox_types.cpp 179 | code/tests/rlbox/test_stdlib.cpp 180 | code/tests/rlbox/test_tainted_assignment.cpp 181 | code/tests/rlbox/test_tainted_opaque.cpp 182 | code/tests/rlbox/test_tainted_sizes.cpp 183 | code/tests/rlbox/test_tainted_structs.cpp 184 | code/tests/rlbox/test_type_traits.cpp 185 | code/tests/rlbox/test_verification.cpp 186 | code/tests/rlbox/test_verify_arrays.cpp 187 | code/tests/rlbox/test_wrapper_traits.cpp) 188 | 189 | target_include_directories(test_rlbox PRIVATE code/tests/rlbox) 190 | 191 | target_link_libraries(test_rlbox Catch2::Catch2 ${PROJECT_NAME}) 192 | 193 | catch_discover_tests(test_rlbox) 194 | 195 | # Test rlbox overheads 196 | 197 | add_executable(test_rlbox_transition_timers 198 | code/tests/test_main.cpp 199 | code/tests/rlbox/test_sandbox_transition_timings.cpp) 200 | 201 | target_include_directories(test_rlbox_transition_timers PRIVATE code/tests/rlbox) 202 | 203 | target_link_libraries(test_rlbox_transition_timers Catch2::Catch2 ${PROJECT_NAME}) 204 | 205 | catch_discover_tests(test_rlbox_transition_timers) 206 | 207 | # Test rlbox transition customization 208 | 209 | add_executable(test_rlbox_transition_customization 210 | code/tests/test_main.cpp 211 | code/tests/rlbox/test_sandbox_transition_customization.cpp) 212 | 213 | target_include_directories(test_rlbox_transition_customization PRIVATE code/tests/rlbox) 214 | 215 | target_link_libraries(test_rlbox_transition_customization Catch2::Catch2 ${PROJECT_NAME}) 216 | 217 | catch_discover_tests(test_rlbox_transition_customization) 218 | 219 | # Test rlbox glue 220 | 221 | add_library(rlbox_glue_lib_static STATIC code/tests/rlbox_glue/lib/libtest.c) 222 | target_include_directories(rlbox_glue_lib_static 223 | PUBLIC code/tests/rlbox_glue/lib) 224 | 225 | add_library(rlbox_glue_lib_shared SHARED code/tests/rlbox_glue/lib/libtest.c) 226 | target_include_directories(rlbox_glue_lib_shared 227 | PUBLIC code/tests/rlbox_glue/lib) 228 | 229 | add_executable(test_rlbox_glue 230 | code/tests/test_main.cpp 231 | code/tests/rlbox_glue/test_noop_sandbox_glue.cpp) 232 | target_include_directories(test_rlbox_glue PUBLIC code/tests/rlbox_glue) 233 | 234 | target_link_libraries(test_rlbox_glue 235 | Catch2::Catch2 236 | ${PROJECT_NAME} 237 | rlbox_glue_lib_static) 238 | 239 | catch_discover_tests(test_rlbox_glue) 240 | 241 | add_executable(test_rlbox_glue_configs 242 | code/tests/test_main.cpp 243 | code/tests/rlbox_glue/test_noop_sandbox_glue_configs.cpp) 244 | target_include_directories(test_rlbox_glue_configs PUBLIC code/tests/rlbox_glue) 245 | 246 | target_link_libraries(test_rlbox_glue_configs 247 | Catch2::Catch2 248 | ${PROJECT_NAME} 249 | rlbox_glue_lib_static) 250 | 251 | catch_discover_tests(test_rlbox_glue_configs) 252 | 253 | add_executable(test_rlbox_glue_dylib 254 | code/tests/test_main.cpp 255 | code/tests/rlbox_glue/test_dylib_sandbox_glue.cpp) 256 | target_include_directories(test_rlbox_glue_dylib PUBLIC code/tests/rlbox_glue 257 | code/tests/rlbox_glue/lib) 258 | target_compile_definitions(test_rlbox_glue_dylib PUBLIC GLUE_LIB_PATH="$") 259 | 260 | target_link_libraries(test_rlbox_glue_dylib 261 | Catch2::Catch2 262 | ${PROJECT_NAME} 263 | ${CMAKE_DL_LIBS}) 264 | 265 | add_dependencies(test_rlbox_glue_dylib rlbox_glue_lib_shared) 266 | 267 | catch_discover_tests(test_rlbox_glue_dylib) 268 | 269 | # make check 270 | 271 | add_custom_target( 272 | check 273 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 274 | COMMAND 275 | ${CMAKE_COMMAND} -E env 276 | LSAN_OPTIONS=suppressions=${CMAKE_SOURCE_DIR}/leak_suppressions.txt 277 | UBSAN_OPTIONS=suppressions=${CMAKE_SOURCE_DIR}/ub_suppressions.txt 278 | ${CMAKE_CTEST_COMMAND} -V 279 | DEPENDS test_rlbox 280 | test_rlbox_transition_timers 281 | test_rlbox_transition_customization 282 | test_rlbox_glue 283 | test_rlbox_glue_configs 284 | test_rlbox_glue_dylib 285 | COMMENT "Running tests" 286 | ) 287 | 288 | endif() 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 UCSD PLSysSec 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 | # RLBox 2 | 3 | [![Tests](https://github.com/PLSysSec/rlbox_sandboxing_api/actions/workflows/cmake.yml/badge.svg)](https://github.com/PLSysSec/rlbox_sandboxing_api/actions/workflows/cmake.yml) 4 | 5 | RLBox sandboxing framework. This code has been tested on 64-bit versions of Linux, Mac OSX, and Windows. 6 | 7 | ## Reporting security bugs 8 | 9 | If you find a security bug, please do not create a public issue. Instead, file a security bug on bugzilla using the [following template link](https://bugzilla.mozilla.org/enter_bug.cgi?component=Security%3A%20RLBox&defined_groups=1&groups=core-security&product=Core&bug_type=defect). 10 | 11 | ## Using this library 12 | 13 | RLBox is a general purpose sandboxing API that can be used to interact with 14 | library sandboxed with different backends --- WebAssembly, Native Client, OS 15 | processess, etc. Support for each backend is provided by a separate plugin that 16 | must also be downloaded separately. 17 | 18 | See the [online docs](https://rlbox.dev) for more details. 19 | 20 | The RLBox library is a header only library, so you can directly download this repo and use include the contents of `code/include/` in your application. On Linux/Mac machines, you can optionally install the headers as well with `make install`. 21 | 22 | Support for cmake's `find_package` API is also included (see [this example](https://github.com/PLSysSec/rlbox-book/blob/main/src/chapters/examples/noop-hello-example/CMakeLists.txt)). 23 | 24 | ## Running the tests 25 | 26 | 1. Setup a build folder and then build. 27 | 28 | ```bash 29 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release 30 | ``` 31 | 32 | You can build with cmake: 33 | 34 | ```bash 35 | cmake --build ./build --config Release --parallel 36 | ``` 37 | 38 | or make: 39 | 40 | ```bash 41 | cd build && make -j 42 | ``` 43 | 44 | 2. To test: 45 | 46 | With cmake: 47 | 48 | ```bash 49 | cd build && ctest -V 50 | ``` 51 | 52 | or with make (on Linux/Mac): 53 | 54 | ```bash 55 | cd build && make test 56 | ``` 57 | 58 | When running with ASAN and UBSAN: 59 | 60 | ```bash 61 | cd build && cmake -E env LSAN_OPTIONS=suppressions=../leak_suppressions.txt UBSAN_OPTIONS=suppressions=../ub_suppressions.txt ctest -V 62 | ``` 63 | 64 | Currently RLBox has been tested and should work with gcc-7 or later and 65 | clang-5, Visual Studio 2019 (possibly previous versions as well) or later. If 66 | you are using other compilers/compiler versions (like mingw), these may also be 67 | supported. Simply run the test suite and check that everything passes. 68 | 69 | If you want to disable building tests, you can add `-DBUILD_TESTING=OFF` when invoking cmake the first time. This will also remove the Catch2 dependency. 70 | 71 | ```bash 72 | cmake -S . -B ./build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF 73 | ``` 74 | 75 | ## Install the library (Linux/Mac only) 76 | 77 | The recommended use of RLBox is to make a copy of this library in your source tree. 78 | However, if you want to install the library, you can do so by following the below steps. 79 | 80 | Configure the build with cmake in the same way that previous paragraph. Then simply run: 81 | 82 | ```bash 83 | cd build 84 | make install 85 | ``` 86 | 87 | ## Contributing Code 88 | 89 | 1. To contribute code, it is recommended you install clang-tidy which the build 90 | uses if available. Install using: 91 | 92 | On Ubuntu: 93 | 94 | ```bash 95 | sudo apt install clang-tidy 96 | ``` 97 | 98 | On Arch Linux: 99 | 100 | ```bash 101 | sudo pacman -S clang-tidy 102 | ``` 103 | 104 | 2. It is recommended you use the dev mode for building during development. This 105 | treat warnings as errors, enables clang-tidy checks, runs address sanitizer etc. 106 | Also, you probably want to use the debug build. To do this, adjust your build 107 | settings as shown below 108 | 109 | ```bash 110 | cmake -DCMAKE_BUILD_TYPE=Debug -DDEV=ON -S . -B ./build 111 | cd build 112 | make 113 | ``` 114 | 115 | 3. After making changes to the source, add any new required tests and run all 116 | tests (as described earlier). 117 | 118 | 4. If you don't already have clang-format installed, install it: 119 | 120 | On Ubuntu: 121 | 122 | ```bash 123 | sudo apt install clang-format 124 | ``` 125 | 126 | On Arch Linux: 127 | 128 | ```bash 129 | sudo pacman -S clang-format 130 | ``` 131 | 132 | 6. Format code with the format-source target: 133 | 134 | ```bash 135 | cmake --build ./build --target format-source 136 | ``` 137 | 138 | 7. Submit the pull request! 139 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Reporting a Vulnerability 4 | 5 | RLBox is used in production Firefox. Please do not file any security-sensitive bugs on GitHub. Instead, file a security bug on bugzilla using the following. Instead, file a security bug on bugzilla using the [following template link](https://bugzilla.mozilla.org/enter_bug.cgi?component=Security%3A%20RLBox&defined_groups=1&groups=core-security&product=Core&bug_type=defect). 6 | -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | #Look for an executable called sphinx-build 2 | find_program(SPHINX_EXECUTABLE 3 | NAMES sphinx-build 4 | DOC "Path to sphinx-build executable") 5 | 6 | include(FindPackageHandleStandardArgs) 7 | 8 | #Handle standard arguments to find_package like REQUIRED and QUIET 9 | find_package_handle_standard_args(Sphinx 10 | "Failed to find sphinx-build executable" 11 | SPHINX_EXECUTABLE) -------------------------------------------------------------------------------- /cmake/rlbox-sandboxing-apiConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 4 | 5 | check_required_components("@PROJECT_NAME@") 6 | -------------------------------------------------------------------------------- /code/include/rlbox_app_pointer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | #ifndef RLBOX_USE_CUSTOM_SHARED_LOCK 7 | # include 8 | #endif 9 | #include 10 | 11 | #include "rlbox_helpers.hpp" 12 | #include "rlbox_type_traits.hpp" 13 | #include "rlbox_types.hpp" 14 | 15 | namespace rlbox { 16 | 17 | template 18 | class app_pointer_map 19 | { 20 | 21 | private: 22 | using T_PointerTypeUnsigned = detail::unsigned_int_of_size_t; 23 | 24 | std::map pointer_map; 25 | T_PointerTypeUnsigned counter = 1; 26 | #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS 27 | RLBOX_SHARED_LOCK(map_mutex); 28 | #endif 29 | 30 | T_PointerType get_unused_index(T_PointerType max_ptr_val) 31 | { 32 | const auto max_val = (T_PointerTypeUnsigned)max_ptr_val; 33 | for (T_PointerTypeUnsigned i = counter; i <= max_val; i++) { 34 | if (pointer_map.find(i) == pointer_map.end()) { 35 | counter = i + 1; 36 | return (T_PointerType)i; 37 | } 38 | } 39 | for (T_PointerTypeUnsigned i = 1; i < counter; i++) { 40 | if (pointer_map.find(i) == pointer_map.end()) { 41 | counter = i + 1; 42 | return (T_PointerType)i; 43 | } 44 | } 45 | detail::dynamic_check(false, "Could not find free app pointer slot"); 46 | return 0; 47 | } 48 | 49 | public: 50 | app_pointer_map() 51 | { 52 | // ensure we can't use app pointer 0 as this is sometimes confused as null 53 | // by the sandbox 54 | pointer_map[0] = nullptr; 55 | } 56 | 57 | T_PointerType get_app_pointer_idx(void* ptr, T_PointerType max_ptr_val) 58 | { 59 | #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS 60 | RLBOX_ACQUIRE_UNIQUE_GUARD(lock, map_mutex); 61 | #endif 62 | T_PointerType idx = get_unused_index(max_ptr_val); 63 | T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx; 64 | pointer_map[idx_int] = ptr; 65 | return idx; 66 | } 67 | 68 | void remove_app_ptr(T_PointerType idx) 69 | { 70 | #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS 71 | RLBOX_ACQUIRE_UNIQUE_GUARD(lock, map_mutex); 72 | #endif 73 | T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx; 74 | auto it = pointer_map.find(idx_int); 75 | detail::dynamic_check(it != pointer_map.end(), 76 | "Error: removing a non-existing app pointer"); 77 | pointer_map.erase(it); 78 | } 79 | 80 | void* lookup_index(T_PointerType idx) 81 | { 82 | #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS 83 | RLBOX_ACQUIRE_SHARED_GUARD(lock, map_mutex); 84 | #endif 85 | T_PointerTypeUnsigned idx_int = (T_PointerTypeUnsigned)idx; 86 | auto it = pointer_map.find(idx_int); 87 | detail::dynamic_check(it != pointer_map.end(), 88 | "Error: looking up a non-existing app pointer"); 89 | return it->second; 90 | } 91 | }; 92 | 93 | } -------------------------------------------------------------------------------- /code/include/rlbox_conversion.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "rlbox_helpers.hpp" 11 | #include "rlbox_type_traits.hpp" 12 | #include "rlbox_types.hpp" 13 | 14 | namespace rlbox::detail { 15 | 16 | template 17 | inline constexpr void convert_type_fundamental(T_To& to, 18 | const volatile T_From& from) 19 | { 20 | using namespace std; 21 | 22 | if_constexpr_named(cond1, !is_fundamental_or_enum_v) 23 | { 24 | rlbox_detail_static_fail_because( 25 | cond1, "Conversion target should be fundamental or enum type"); 26 | } 27 | else if_constexpr_named(cond2, !is_fundamental_or_enum_v) 28 | { 29 | rlbox_detail_static_fail_because( 30 | cond2, "Conversion source should be fundamental or enum type"); 31 | } 32 | else if_constexpr_named(cond3, is_enum_v || is_enum_v) 33 | { 34 | static_assert(std::is_same_v, 35 | detail::remove_cv_ref_t>); 36 | to = from; 37 | } 38 | else if_constexpr_named( 39 | cond4, is_floating_point_v || is_floating_point_v) 40 | { 41 | static_assert(is_floating_point_v && is_floating_point_v); 42 | // language coerces different float types 43 | to = from; 44 | } 45 | else if_constexpr_named(cond5, is_integral_v || is_integral_v) 46 | { 47 | static_assert(is_integral_v && is_integral_v); 48 | 49 | const char* err_msg = 50 | "Over/Underflow when converting between integer types"; 51 | 52 | // Some branches don't use the param 53 | RLBOX_UNUSED(err_msg); 54 | 55 | if constexpr (is_signed_v == is_signed_v && 56 | sizeof(T_To) >= sizeof(T_From)) { 57 | // Eg: int64_t from int32_t, uint64_t from uint32_t 58 | } else if constexpr (is_unsigned_v && is_unsigned_v) { 59 | // Eg: uint32_t from uint64_t 60 | dynamic_check(from <= numeric_limits::max(), err_msg); 61 | } else if constexpr (is_signed_v && is_signed_v) { 62 | // Eg: int32_t from int64_t 63 | dynamic_check(from >= numeric_limits::min(), err_msg); 64 | dynamic_check(from <= numeric_limits::max(), err_msg); 65 | } else if constexpr (is_unsigned_v && is_signed_v) { 66 | if constexpr (sizeof(T_To) < sizeof(T_From)) { 67 | // Eg: uint32_t from int64_t 68 | dynamic_check(from >= 0, err_msg); 69 | auto to_max = numeric_limits::max(); 70 | dynamic_check(from <= static_cast(to_max), err_msg); 71 | } else { 72 | // Eg: uint32_t from int32_t, uint64_t from int32_t 73 | dynamic_check(from >= 0, err_msg); 74 | } 75 | } else if constexpr (is_signed_v && is_unsigned_v) { 76 | if constexpr (sizeof(T_To) <= sizeof(T_From)) { 77 | // Eg: int32_t from uint32_t, int32_t from uint64_t 78 | auto to_max = numeric_limits::max(); 79 | dynamic_check(from <= static_cast(to_max), err_msg); 80 | } else { 81 | // Eg: int64_t from uint32_t 82 | } 83 | } 84 | to = static_cast(from); 85 | } 86 | else 87 | { 88 | constexpr auto unknownCase = !(cond1 || cond2 || cond3 || cond4 || cond5); 89 | rlbox_detail_static_fail_because( 90 | unknownCase, "Unexpected case for convert_type_fundamental"); 91 | } 92 | } 93 | 94 | template 95 | inline constexpr void convert_type_fundamental_or_array(T_To& to, 96 | const T_From& from) 97 | { 98 | using namespace std; 99 | 100 | using T_To_C = std_array_to_c_arr_t; 101 | using T_From_C = std_array_to_c_arr_t; 102 | using T_To_El = remove_all_extents_t; 103 | using T_From_El = remove_all_extents_t; 104 | 105 | if_constexpr_named(cond1, is_array_v != is_array_v) 106 | { 107 | rlbox_detail_static_fail_because( 108 | cond1, "Conversion should not go between array and non array types"); 109 | } 110 | else if constexpr (!is_array_v) 111 | { 112 | convert_type_fundamental(to, from); 113 | } 114 | else if_constexpr_named(cond2, !all_extents_same) 115 | { 116 | rlbox_detail_static_fail_because( 117 | cond2, "Conversion between arrays should have same dimensions"); 118 | } 119 | else if_constexpr_named(cond3, 120 | is_pointer_v || is_pointer_v) 121 | { 122 | rlbox_detail_static_fail_because(cond3, 123 | "convert_type_fundamental_or_array " 124 | "does not allow arrays of pointers"); 125 | } 126 | else 127 | { 128 | // Explicitly using size to check for element type as we may be going across 129 | // different types of the same width such as void* and uintptr_t 130 | if constexpr (sizeof(T_To_El) == sizeof(T_From_El) && 131 | is_signed_v == is_signed_v) { 132 | // Sanity check - this should definitely be true 133 | static_assert(sizeof(T_From_C) == sizeof(T_To_C)); 134 | std::memcpy(&to, &from, sizeof(T_To_C)); 135 | } else { 136 | for (size_t i = 0; i < std::extent_v; i++) { 137 | convert_type_fundamental_or_array(to[i], from[i]); 138 | } 139 | } 140 | } 141 | } 142 | 143 | enum class adjust_type_direction 144 | { 145 | TO_SANDBOX, 146 | TO_APPLICATION, 147 | NO_CHANGE 148 | }; 149 | 150 | enum class adjust_type_context 151 | { 152 | EXAMPLE, 153 | SANDBOX 154 | }; 155 | 156 | template 161 | inline constexpr void convert_type_non_class( 162 | T_To& to, 163 | const T_From& from, 164 | const void* example_unsandboxed_ptr, 165 | rlbox_sandbox* sandbox_ptr) 166 | { 167 | using namespace std; 168 | 169 | // Some branches don't use the param 170 | RLBOX_UNUSED(example_unsandboxed_ptr); 171 | RLBOX_UNUSED(sandbox_ptr); 172 | 173 | using T_To_C = std_array_to_c_arr_t; 174 | using T_From_C = std_array_to_c_arr_t; 175 | using T_To_El = remove_all_extents_t; 176 | using T_From_El = remove_all_extents_t; 177 | 178 | if constexpr (is_pointer_v || is_pointer_v) { 179 | 180 | if constexpr (Direction == adjust_type_direction::NO_CHANGE) { 181 | 182 | static_assert(is_pointer_v && is_pointer_v && 183 | sizeof(T_To_C) == sizeof(T_From_C)); 184 | to = from; 185 | 186 | } else if constexpr (Direction == adjust_type_direction::TO_SANDBOX) { 187 | 188 | static_assert(is_pointer_v); 189 | // Maybe a function pointer, so convert 190 | auto from_c = reinterpret_cast(from); 191 | if constexpr (Context == adjust_type_context::SANDBOX) { 192 | RLBOX_DEBUG_ASSERT(sandbox_ptr != nullptr); 193 | to = sandbox_ptr->template get_sandboxed_pointer(from_c); 194 | } else { 195 | RLBOX_DEBUG_ASSERT(from_c == nullptr || 196 | example_unsandboxed_ptr != nullptr); 197 | to = 198 | rlbox_sandbox::template get_sandboxed_pointer_no_ctx( 199 | from_c, example_unsandboxed_ptr); 200 | } 201 | 202 | } else if constexpr (Direction == adjust_type_direction::TO_APPLICATION) { 203 | 204 | static_assert(is_pointer_v); 205 | if constexpr (Context == adjust_type_context::SANDBOX) { 206 | RLBOX_DEBUG_ASSERT(sandbox_ptr != nullptr); 207 | to = sandbox_ptr->template get_unsandboxed_pointer(from); 208 | } else { 209 | RLBOX_DEBUG_ASSERT(from == 0 || example_unsandboxed_ptr != nullptr); 210 | to = 211 | rlbox_sandbox::template get_unsandboxed_pointer_no_ctx( 212 | from, example_unsandboxed_ptr); 213 | } 214 | } 215 | 216 | } else if constexpr (is_pointer_v || is_pointer_v) { 217 | 218 | if constexpr (Direction == adjust_type_direction::NO_CHANGE) { 219 | // Sanity check - this should definitely be true 220 | static_assert(sizeof(T_To_El) == sizeof(T_From_El) && 221 | sizeof(T_From_C) == sizeof(T_To_C)); 222 | memcpy(&to, &from, sizeof(T_To_C)); 223 | } else { 224 | for (size_t i = 0; i < std::extent_v; i++) { 225 | convert_type_non_class( 226 | to[i], from[i], example_unsandboxed_ptr, sandbox_ptr); 227 | } 228 | } 229 | 230 | } else { 231 | convert_type_fundamental_or_array(to, from); 232 | } 233 | } 234 | 235 | // Structs implement their own convert_type by specializing this class 236 | // Have to do this via a class, as functions can't be partially specialized 237 | template 242 | class convert_type_class; 243 | // The specialization implements the following 244 | // { 245 | // static inline void run(T_To& to, 246 | // const T_From& from, 247 | // const void* example_unsandboxed_ptr); 248 | // } 249 | 250 | template 255 | inline void convert_type(T_To& to, 256 | const T_From& from, 257 | const void* example_unsandboxed_ptr, 258 | rlbox_sandbox* sandbox_ptr) 259 | { 260 | if constexpr ((std::is_class_v || 261 | std::is_class_v)&&!detail::is_std_array_v && 262 | !detail::is_std_array_v) { 263 | // Sanity check 264 | static_assert(std::is_class_v && std::is_class_v); 265 | convert_type_class::run( 266 | to, from, example_unsandboxed_ptr, sandbox_ptr); 267 | } else { 268 | convert_type_non_class( 269 | to, from, example_unsandboxed_ptr, sandbox_ptr); 270 | } 271 | } 272 | 273 | } -------------------------------------------------------------------------------- /code/include/rlbox_dylib_sandbox.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #ifndef RLBOX_USE_CUSTOM_SHARED_LOCK 7 | # include 8 | #endif 9 | #include 10 | 11 | #if defined(_WIN32) 12 | // Ensure the min/max macro in the header doesn't collide with functions in 13 | // std:: 14 | # ifndef NOMINMAX 15 | # define NOMINMAX 16 | # endif 17 | # include 18 | #else 19 | # include 20 | #endif 21 | 22 | #include "rlbox_helpers.hpp" 23 | 24 | namespace rlbox { 25 | 26 | class rlbox_dylib_sandbox; 27 | 28 | struct rlbox_dylib_sandbox_thread_data 29 | { 30 | rlbox_dylib_sandbox* sandbox; 31 | uint32_t last_callback_invoked; 32 | }; 33 | 34 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 35 | 36 | rlbox_dylib_sandbox_thread_data* get_rlbox_dylib_sandbox_thread_data(); 37 | # define RLBOX_DYLIB_SANDBOX_STATIC_VARIABLES() \ 38 | thread_local rlbox::rlbox_dylib_sandbox_thread_data \ 39 | rlbox_dylib_sandbox_thread_info{ 0, 0 }; \ 40 | namespace rlbox { \ 41 | rlbox_dylib_sandbox_thread_data* get_rlbox_dylib_sandbox_thread_data() \ 42 | { \ 43 | return &rlbox_dylib_sandbox_thread_info; \ 44 | } \ 45 | } \ 46 | static_assert(true, "Enforce semi-colon") 47 | 48 | #endif 49 | 50 | /** 51 | * @brief Class that implements the null sandbox. This sandbox doesn't actually 52 | * provide any isolation and only serves as a stepping stone towards migrating 53 | * an application to use the RLBox API. 54 | */ 55 | class rlbox_dylib_sandbox 56 | { 57 | public: 58 | // Stick with the system defaults 59 | using T_LongLongType = long long; 60 | using T_LongType = long; 61 | using T_IntType = int; 62 | using T_PointerType = void*; 63 | using T_ShortType = short; 64 | // no-op sandbox can transfer buffers as there is no sandboxings 65 | // Thus transfer is a noop 66 | using can_grant_deny_access = void; 67 | // if this plugin uses a separate function to lookup internal callbacks 68 | using needs_internal_lookup_symbol = void; 69 | 70 | private: 71 | void* sandbox = nullptr; 72 | 73 | RLBOX_SHARED_LOCK(callback_mutex); 74 | static inline const uint32_t MAX_CALLBACKS = 64; 75 | void* callback_unique_keys[MAX_CALLBACKS]{ 0 }; 76 | void* callbacks[MAX_CALLBACKS]{ 0 }; 77 | 78 | #ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 79 | thread_local static inline rlbox_dylib_sandbox_thread_data thread_data{ 0, 80 | 0 }; 81 | #endif 82 | 83 | template 84 | static T_Ret callback_trampoline(T_Args... params) 85 | { 86 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 87 | auto& thread_data = *get_rlbox_dylib_sandbox_thread_data(); 88 | #endif 89 | thread_data.last_callback_invoked = N; 90 | using T_Func = T_Ret (*)(T_Args...); 91 | T_Func func; 92 | { 93 | #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS 94 | RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex); 95 | #endif 96 | func = reinterpret_cast(thread_data.sandbox->callbacks[N]); 97 | } 98 | // Callbacks are invoked through function pointers, cannot use std::forward 99 | // as we don't have caller context for T_Args, which means they are all 100 | // effectively passed by value 101 | return func(params...); 102 | } 103 | 104 | protected: 105 | #if defined(_WIN32) 106 | using path_buf = const LPCWSTR; 107 | #else 108 | using path_buf = const char*; 109 | #endif 110 | 111 | inline void impl_create_sandbox(path_buf path) 112 | { 113 | #if defined(_WIN32) 114 | sandbox = (void*)LoadLibraryW(path); 115 | #else 116 | sandbox = dlopen(path, RTLD_LAZY | RTLD_LOCAL); 117 | #endif 118 | 119 | if (!sandbox) { 120 | std::string error_msg = "Could not load dynamic library: "; 121 | #if defined(_WIN32) 122 | DWORD errorMessageID = GetLastError(); 123 | if (errorMessageID != 0) { 124 | LPSTR messageBuffer = nullptr; 125 | // The api creates the buffer that holds the message 126 | size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | 127 | FORMAT_MESSAGE_FROM_SYSTEM | 128 | FORMAT_MESSAGE_IGNORE_INSERTS, 129 | NULL, 130 | errorMessageID, 131 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 132 | (LPSTR)&messageBuffer, 133 | 0, 134 | NULL); 135 | // Copy the error message into a std::string. 136 | std::string message(messageBuffer, size); 137 | error_msg += message; 138 | LocalFree(messageBuffer); 139 | } 140 | #else 141 | error_msg += dlerror(); 142 | #endif 143 | detail::dynamic_check(false, error_msg.c_str()); 144 | } 145 | } 146 | 147 | inline void impl_destroy_sandbox() 148 | { 149 | #if defined(_WIN32) 150 | FreeLibrary((HMODULE)sandbox); 151 | #else 152 | dlclose(sandbox); 153 | #endif 154 | sandbox = nullptr; 155 | } 156 | 157 | template 158 | inline void* impl_get_unsandboxed_pointer(T_PointerType p) const 159 | { 160 | return p; 161 | } 162 | 163 | template 164 | inline T_PointerType impl_get_sandboxed_pointer(const void* p) const 165 | { 166 | return const_cast(p); 167 | } 168 | 169 | template 170 | static inline void* impl_get_unsandboxed_pointer_no_ctx( 171 | T_PointerType p, 172 | const void* /* example_unsandboxed_ptr */, 173 | rlbox_dylib_sandbox* (* // Func ptr 174 | /* param: expensive_sandbox_finder */)( 175 | const void* example_unsandboxed_ptr)) 176 | { 177 | return p; 178 | } 179 | 180 | template 181 | static inline T_PointerType impl_get_sandboxed_pointer_no_ctx( 182 | const void* p, 183 | const void* /* example_unsandboxed_ptr */, 184 | rlbox_dylib_sandbox* (* // Func ptr 185 | /* param: expensive_sandbox_finder */)( 186 | const void* example_unsandboxed_ptr)) 187 | { 188 | return const_cast(p); 189 | } 190 | 191 | inline T_PointerType impl_malloc_in_sandbox(size_t size) 192 | { 193 | void* p = malloc(size); 194 | return p; 195 | } 196 | 197 | inline void impl_free_in_sandbox(T_PointerType p) { free(p); } 198 | 199 | static inline bool impl_is_in_same_sandbox(const void*, const void*) 200 | { 201 | return true; 202 | } 203 | 204 | inline bool impl_is_pointer_in_sandbox_memory(const void*) { return true; } 205 | inline bool impl_is_pointer_in_app_memory(const void*) { return true; } 206 | 207 | inline size_t impl_get_total_memory() 208 | { 209 | return std::numeric_limits::max(); 210 | } 211 | 212 | inline void* impl_get_memory_location() 213 | { 214 | // There isn't any sandbox memory for the dylib_sandbox as we just redirect 215 | // to the app. Also, this is mostly used for pointer swizzling or sandbox 216 | // bounds checks which is also not present/not required. So we can just 217 | // return null 218 | return nullptr; 219 | } 220 | 221 | void* impl_lookup_symbol(const char* func_name) 222 | { 223 | #if defined(_WIN32) 224 | void* ret = GetProcAddress((HMODULE)sandbox, func_name); 225 | #else 226 | void* ret = dlsym(sandbox, func_name); 227 | #endif 228 | detail::dynamic_check(ret != nullptr, "Symbol not found"); 229 | return ret; 230 | } 231 | 232 | void* impl_internal_lookup_symbol(const char* func_name) 233 | { 234 | return impl_lookup_symbol(func_name); 235 | } 236 | 237 | template 238 | auto impl_invoke_with_func_ptr(T_Converted* func_ptr, T_Args&&... params) 239 | { 240 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 241 | auto& thread_data = *get_rlbox_dylib_sandbox_thread_data(); 242 | #endif 243 | auto old_sandbox = thread_data.sandbox; 244 | thread_data.sandbox = this; 245 | auto on_exit = 246 | detail::make_scope_exit([&] { thread_data.sandbox = old_sandbox; }); 247 | return (*func_ptr)(params...); 248 | } 249 | 250 | template 251 | inline T_PointerType impl_register_callback(void* key, void* callback) 252 | { 253 | RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); 254 | 255 | void* chosen_trampoline = nullptr; 256 | 257 | // need a compile time for loop as we we need I to be a compile time value 258 | // this is because we are returning the I'th callback trampoline 259 | detail::compile_time_for([&](auto I) { 260 | if (!chosen_trampoline && callback_unique_keys[I.value] == nullptr) { 261 | callback_unique_keys[I.value] = key; 262 | callbacks[I.value] = callback; 263 | chosen_trampoline = reinterpret_cast( 264 | callback_trampoline); 265 | } 266 | }); 267 | 268 | return reinterpret_cast(chosen_trampoline); 269 | } 270 | 271 | static inline std::pair 272 | impl_get_executed_callback_sandbox_and_key() 273 | { 274 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 275 | auto& thread_data = *get_rlbox_dylib_sandbox_thread_data(); 276 | #endif 277 | auto sandbox = thread_data.sandbox; 278 | auto callback_num = thread_data.last_callback_invoked; 279 | void* key = sandbox->callback_unique_keys[callback_num]; 280 | return std::make_pair(sandbox, key); 281 | } 282 | 283 | template 284 | inline void impl_unregister_callback(void* key) 285 | { 286 | RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); 287 | for (uint32_t i = 0; i < MAX_CALLBACKS; i++) { 288 | if (callback_unique_keys[i] == key) { 289 | callback_unique_keys[i] = nullptr; 290 | callbacks[i] = nullptr; 291 | break; 292 | } 293 | } 294 | } 295 | 296 | template 297 | inline T* impl_grant_access(T* src, size_t num, bool& success) 298 | { 299 | RLBOX_UNUSED(num); 300 | success = true; 301 | return src; 302 | } 303 | 304 | template 305 | inline T* impl_deny_access(T* src, size_t num, bool& success) 306 | { 307 | RLBOX_UNUSED(num); 308 | success = true; 309 | return src; 310 | } 311 | }; 312 | 313 | } 314 | -------------------------------------------------------------------------------- /code/include/rlbox_helpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifndef RLBOX_USE_CUSTOM_SHARED_LOCK 11 | # include 12 | #endif 13 | 14 | #include "rlbox_stdlib_polyfill.hpp" 15 | 16 | namespace rlbox { 17 | namespace detail { 18 | const int CompileErrorCode = 42; 19 | 20 | inline void dynamic_check(bool check, const char* const msg) 21 | { 22 | // clang-format off 23 | if (!check) { 24 | #if __cpp_exceptions && defined(RLBOX_USE_EXCEPTIONS) 25 | throw std::runtime_error(msg); 26 | #else 27 | #ifdef RLBOX_CUSTOM_ABORT 28 | RLBOX_CUSTOM_ABORT(msg); 29 | #else 30 | fprintf(stderr, "%s\n", msg); 31 | std::abort(); 32 | #endif 33 | #endif 34 | } 35 | // clang-format on 36 | } 37 | 38 | #ifdef RLBOX_NO_COMPILE_CHECKS 39 | # if __cpp_exceptions && defined(RLBOX_USE_EXCEPTIONS) 40 | # define rlbox_detail_static_fail_because(CondExpr, Message) \ 41 | ((void)(CondExpr)), throw std::runtime_error(Message) 42 | # else 43 | # define rlbox_detail_static_fail_because(CondExpr, Message) abort() 44 | # endif 45 | #else 46 | # define rlbox_detail_static_fail_because(CondExpr, Message) \ 47 | static_assert(!(CondExpr), Message) 48 | #endif 49 | 50 | #ifdef RLBOX_ENABLE_DEBUG_ASSERTIONS 51 | # define RLBOX_DEBUG_ASSERT(...) \ 52 | ::rlbox::detail::dynamic_check(__VA_ARGS__, "Debug assertion failed") 53 | #else 54 | # define RLBOX_DEBUG_ASSERT(...) (void)0 55 | #endif 56 | 57 | #define RLBOX_UNUSED(...) (void)__VA_ARGS__ 58 | 59 | #define RLBOX_REQUIRE_SEMI_COLON static_assert(true) 60 | 61 | #define if_constexpr_named(varName, ...) \ 62 | if constexpr (constexpr auto varName = __VA_ARGS__; varName) 63 | 64 | template 65 | void printTypes() 66 | { 67 | #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) 68 | puts(__PRETTY_FUNCTION__); // NOLINT 69 | #elif defined(_MSC_VER) 70 | puts(__FUNCSIG__); // NOLINT 71 | #else 72 | puts("Unsupported"); 73 | #endif 74 | } 75 | 76 | // Create an extension point so applications can provide their own shared lock 77 | // implementation 78 | #ifndef RLBOX_USE_CUSTOM_SHARED_LOCK 79 | # define RLBOX_SHARED_LOCK(name) std::shared_timed_mutex name 80 | # define RLBOX_ACQUIRE_SHARED_GUARD(name, ...) \ 81 | std::shared_lock name(__VA_ARGS__) 82 | # define RLBOX_ACQUIRE_UNIQUE_GUARD(name, ...) \ 83 | std::unique_lock name(__VA_ARGS__) 84 | #else 85 | # if !defined(RLBOX_SHARED_LOCK) || !defined(RLBOX_ACQUIRE_SHARED_GUARD) || \ 86 | !defined(RLBOX_ACQUIRE_UNIQUE_GUARD) 87 | # error \ 88 | "RLBOX_USE_CUSTOM_SHARED_LOCK defined but missing definitions for RLBOX_SHARED_LOCK, RLBOX_ACQUIRE_SHARED_GUARD, RLBOX_ACQUIRE_UNIQUE_GUARD" 89 | # endif 90 | #endif 91 | 92 | #define rlbox_detail_forward_binop_to_base(opSymbol, ...) \ 93 | template \ 94 | inline auto operator opSymbol(T_Rhs rhs) \ 95 | { \ 96 | auto b = static_cast<__VA_ARGS__*>(this); \ 97 | return (*b)opSymbol rhs; \ 98 | } \ 99 | RLBOX_REQUIRE_SEMI_COLON 100 | 101 | template 102 | inline auto remove_volatile_from_ptr_cast(T* ptr) 103 | { 104 | using T_Result = std::add_pointer_t>; 105 | return const_cast(ptr); 106 | } 107 | 108 | // https://stackoverflow.com/questions/37602057/why-isnt-a-for-loop-a-compile-time-expression 109 | namespace compile_time_for_detail { 110 | template 111 | struct num 112 | { 113 | static const constexpr auto value = N; 114 | }; 115 | 116 | template 117 | inline void compile_time_for_helper(F func, std::index_sequence) 118 | { 119 | (func(num{}), ...); 120 | } 121 | } 122 | 123 | template 124 | inline void compile_time_for(F func) 125 | { 126 | compile_time_for_detail::compile_time_for_helper( 127 | func, std::make_index_sequence()); 128 | } 129 | 130 | template 131 | [[nodiscard]] inline auto return_first_result(T first_task, T2 second_task) 132 | { 133 | using T_Result = rlbox::detail::polyfill::invoke_result_t; 134 | 135 | if constexpr (std::is_void_v) { 136 | first_task(); 137 | second_task(); 138 | } else { 139 | auto val = first_task(); 140 | second_task(); 141 | return val; 142 | } 143 | } 144 | 145 | // Scope Exit guards 146 | template 147 | class scope_exit 148 | { 149 | T_ExitFunc exit_func; 150 | bool released; 151 | 152 | public: 153 | explicit scope_exit(T_ExitFunc&& cleanup) 154 | : exit_func(cleanup) 155 | , released(true) 156 | {} 157 | 158 | scope_exit(scope_exit&& rhs) 159 | : exit_func(std::move(rhs.exit_func)) 160 | , released(rhs.released) 161 | { 162 | rhs.release(); 163 | } 164 | 165 | ~scope_exit() 166 | { 167 | if (released) { 168 | exit_func(); 169 | } 170 | } 171 | 172 | void release() { released = false; } 173 | 174 | private: 175 | explicit scope_exit(const scope_exit&) = delete; 176 | scope_exit& operator=(const scope_exit&) = delete; 177 | scope_exit& operator=(scope_exit&&) = delete; 178 | }; 179 | 180 | template 181 | [[nodiscard]] scope_exit make_scope_exit( 182 | T_ExitFunc&& exitFunction) 183 | { 184 | return scope_exit(std::move(exitFunction)); 185 | } 186 | 187 | /* 188 | Make sure classes can access the private memmbers of tainted and 189 | tainted_volatile. Ideally, this should be 190 | 191 | template 192 | friend class tainted; 193 | 194 | But C++ doesn't seem to allow the above 195 | */ 196 | #define KEEP_CLASSES_FRIENDLY \ 197 | template typename U1, typename U2, typename U3> \ 198 | friend class tainted_base_impl; \ 199 | \ 200 | template \ 201 | friend class tainted; \ 202 | \ 203 | template \ 204 | friend class tainted_volatile; \ 205 | \ 206 | template \ 207 | friend class rlbox_sandbox; \ 208 | \ 209 | template \ 210 | friend class sandbox_callback; \ 211 | \ 212 | template \ 213 | friend class app_pointer; 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /code/include/rlbox_noop_sandbox.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #ifndef RLBOX_USE_CUSTOM_SHARED_LOCK 7 | # include 8 | #endif 9 | #include 10 | 11 | #include "rlbox_helpers.hpp" 12 | 13 | namespace rlbox { 14 | 15 | class rlbox_noop_sandbox; 16 | 17 | struct rlbox_noop_sandbox_thread_data 18 | { 19 | rlbox_noop_sandbox* sandbox; 20 | uint32_t last_callback_invoked; 21 | }; 22 | 23 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 24 | 25 | rlbox_noop_sandbox_thread_data* get_rlbox_noop_sandbox_thread_data(); 26 | # define RLBOX_NOOP_SANDBOX_STATIC_VARIABLES() \ 27 | thread_local rlbox::rlbox_noop_sandbox_thread_data \ 28 | rlbox_noop_sandbox_thread_info{ 0, 0 }; \ 29 | namespace rlbox { \ 30 | rlbox_noop_sandbox_thread_data* get_rlbox_noop_sandbox_thread_data() \ 31 | { \ 32 | return &rlbox_noop_sandbox_thread_info; \ 33 | } \ 34 | } \ 35 | static_assert(true, "Enforce semi-colon") 36 | 37 | #endif 38 | 39 | /** 40 | * @brief Class that implements the null sandbox. This sandbox doesn't actually 41 | * provide any isolation and only serves as a stepping stone towards migrating 42 | * an application to use the RLBox API. 43 | */ 44 | class rlbox_noop_sandbox 45 | { 46 | public: 47 | // Stick with the system defaults 48 | using T_LongLongType = long long; 49 | using T_LongType = long; 50 | using T_IntType = int; 51 | using T_PointerType = void*; 52 | using T_ShortType = short; 53 | // no-op sandbox can transfer buffers as there is no sandboxings 54 | // Thus transfer is a noop 55 | using can_grant_deny_access = void; 56 | 57 | private: 58 | RLBOX_SHARED_LOCK(callback_mutex); 59 | static inline const uint32_t MAX_CALLBACKS = 64; 60 | void* callback_unique_keys[MAX_CALLBACKS]{ 0 }; 61 | void* callbacks[MAX_CALLBACKS]{ 0 }; 62 | 63 | #ifndef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 64 | thread_local static inline rlbox_noop_sandbox_thread_data thread_data{ 0, 0 }; 65 | #endif 66 | 67 | template 68 | static T_Ret callback_trampoline(T_Args... params) 69 | { 70 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 71 | auto& thread_data = *get_rlbox_noop_sandbox_thread_data(); 72 | #endif 73 | thread_data.last_callback_invoked = N; 74 | using T_Func = T_Ret (*)(T_Args...); 75 | T_Func func; 76 | { 77 | #ifndef RLBOX_SINGLE_THREADED_INVOCATIONS 78 | RLBOX_ACQUIRE_SHARED_GUARD(lock, thread_data.sandbox->callback_mutex); 79 | #endif 80 | func = reinterpret_cast(thread_data.sandbox->callbacks[N]); 81 | } 82 | // Callbacks are invoked through function pointers, cannot use std::forward 83 | // as we don't have caller context for T_Args, which means they are all 84 | // effectively passed by value 85 | return func(params...); 86 | } 87 | 88 | protected: 89 | inline void impl_create_sandbox() {} 90 | 91 | inline void impl_destroy_sandbox() {} 92 | 93 | template 94 | inline void* impl_get_unsandboxed_pointer(T_PointerType p) const 95 | { 96 | return p; 97 | } 98 | 99 | template 100 | inline T_PointerType impl_get_sandboxed_pointer(const void* p) const 101 | { 102 | return const_cast(p); 103 | } 104 | 105 | template 106 | static inline void* impl_get_unsandboxed_pointer_no_ctx( 107 | T_PointerType p, 108 | const void* /* example_unsandboxed_ptr */, 109 | rlbox_noop_sandbox* (* // Func ptr 110 | /* param: expensive_sandbox_finder */)( 111 | const void* example_unsandboxed_ptr)) 112 | { 113 | return p; 114 | } 115 | 116 | template 117 | static inline T_PointerType impl_get_sandboxed_pointer_no_ctx( 118 | const void* p, 119 | const void* /* example_unsandboxed_ptr */, 120 | rlbox_noop_sandbox* (* // Func ptr 121 | /* param: expensive_sandbox_finder */)( 122 | const void* example_unsandboxed_ptr)) 123 | { 124 | return const_cast(p); 125 | } 126 | 127 | inline T_PointerType impl_malloc_in_sandbox(size_t size) 128 | { 129 | void* p = malloc(size); 130 | return p; 131 | } 132 | 133 | inline void impl_free_in_sandbox(T_PointerType p) { free(p); } 134 | 135 | static inline bool impl_is_in_same_sandbox(const void*, const void*) 136 | { 137 | return true; 138 | } 139 | 140 | inline bool impl_is_pointer_in_sandbox_memory(const void*) { return true; } 141 | inline bool impl_is_pointer_in_app_memory(const void*) { return true; } 142 | 143 | inline size_t impl_get_total_memory() 144 | { 145 | return std::numeric_limits::max(); 146 | } 147 | 148 | inline void* impl_get_memory_location() 149 | { 150 | // There isn't any sandbox memory for the noop_sandbox as we just redirect 151 | // to the app. Also, this is mostly used for pointer swizzling or sandbox 152 | // bounds checks which is also not present/not required. So we can just 153 | // return null 154 | return nullptr; 155 | } 156 | 157 | // adding a template so that we can use static_assert to fire only if this 158 | // function is invoked 159 | template 160 | void* impl_lookup_symbol(const char* /* func_name */) 161 | { 162 | // Will fire if this impl_lookup_symbol is ever called for the static 163 | // sandbox 164 | constexpr bool fail = std::is_same_v; 165 | rlbox_detail_static_fail_because( 166 | fail, 167 | "The no_op_sandbox uses static calls and thus developers should add\n\n" 168 | "#define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol\n\n" 169 | "to their code, to ensure that static calls are handled correctly."); 170 | 171 | return nullptr; 172 | } 173 | 174 | #define rlbox_noop_sandbox_lookup_symbol(func_name) \ 175 | reinterpret_cast(&func_name) /* NOLINT */ 176 | 177 | template 178 | auto impl_invoke_with_func_ptr(T_Converted* func_ptr, T_Args&&... params) 179 | { 180 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 181 | auto& thread_data = *get_rlbox_noop_sandbox_thread_data(); 182 | #endif 183 | auto old_sandbox = thread_data.sandbox; 184 | thread_data.sandbox = this; 185 | auto on_exit = 186 | detail::make_scope_exit([&] { thread_data.sandbox = old_sandbox; }); 187 | return (*func_ptr)(params...); 188 | } 189 | 190 | template 191 | inline T_PointerType impl_register_callback(void* key, void* callback) 192 | { 193 | RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); 194 | 195 | void* chosen_trampoline = nullptr; 196 | 197 | // need a compile time for loop as we we need I to be a compile time value 198 | // this is because we are returning the I'th callback trampoline 199 | detail::compile_time_for([&](auto I) { 200 | if (!chosen_trampoline && callback_unique_keys[I.value] == nullptr) { 201 | callback_unique_keys[I.value] = key; 202 | callbacks[I.value] = callback; 203 | chosen_trampoline = reinterpret_cast( 204 | callback_trampoline); 205 | } 206 | }); 207 | 208 | return reinterpret_cast(chosen_trampoline); 209 | } 210 | 211 | static inline std::pair 212 | impl_get_executed_callback_sandbox_and_key() 213 | { 214 | #ifdef RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 215 | auto& thread_data = *get_rlbox_noop_sandbox_thread_data(); 216 | #endif 217 | auto sandbox = thread_data.sandbox; 218 | auto callback_num = thread_data.last_callback_invoked; 219 | void* key = sandbox->callback_unique_keys[callback_num]; 220 | return std::make_pair(sandbox, key); 221 | } 222 | 223 | template 224 | inline void impl_unregister_callback(void* key) 225 | { 226 | RLBOX_ACQUIRE_UNIQUE_GUARD(lock, callback_mutex); 227 | for (uint32_t i = 0; i < MAX_CALLBACKS; i++) { 228 | if (callback_unique_keys[i] == key) { 229 | callback_unique_keys[i] = nullptr; 230 | callbacks[i] = nullptr; 231 | break; 232 | } 233 | } 234 | } 235 | 236 | template 237 | inline T* impl_grant_access(T* src, size_t num, bool& success) 238 | { 239 | RLBOX_UNUSED(num); 240 | success = true; 241 | return src; 242 | } 243 | 244 | template 245 | inline T* impl_deny_access(T* src, size_t num, bool& success) 246 | { 247 | RLBOX_UNUSED(num); 248 | success = true; 249 | return src; 250 | } 251 | }; 252 | 253 | } 254 | -------------------------------------------------------------------------------- /code/include/rlbox_policy_types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "rlbox_helpers.hpp" 9 | #include "rlbox_struct_support.hpp" 10 | #include "rlbox_types.hpp" 11 | 12 | namespace rlbox { 13 | 14 | namespace callback_detail { 15 | 16 | // Compute the expected type of the callback 17 | template 18 | using T_Cb = 19 | std::conditional_t, void, tainted> (*)( 20 | rlbox_sandbox&, 21 | tainted...); 22 | 23 | template 24 | T_Cb callback_type_helper(T_Ret (*)(T_Args...)); 25 | 26 | // Compute the expected type of the interceptor 27 | template 28 | using T_I = detail::convert_to_sandbox_equivalent_t (*)( 29 | detail::convert_to_sandbox_equivalent_t...); 30 | 31 | template 32 | T_I interceptor_type_helper(T_Ret (*)(T_Args...)); 33 | } 34 | 35 | template 36 | class sandbox_callback 37 | { 38 | KEEP_CLASSES_FRIENDLY 39 | 40 | private: 41 | rlbox_sandbox* sandbox; 42 | 43 | using T_Callback = 44 | decltype(callback_detail::callback_type_helper(std::declval())); 45 | T_Callback callback; 46 | 47 | // The interceptor is the function that runs between the sandbox invoking the 48 | // callback and the actual callback running. The interceptor is responsible 49 | // for wrapping and converting callback arguments, returns etc. to their 50 | // appropriate representations 51 | using T_Interceptor = decltype( 52 | callback_detail::interceptor_type_helper(std::declval())); 53 | T_Interceptor callback_interceptor; 54 | 55 | // The trampoline is the internal sandbox representation of the callback. 56 | // Depending on the sandbox type, this could be the callback pointer directly 57 | // or a trampoline function that gates exits from the sandbox. 58 | using T_Trampoline = detail::convert_to_sandbox_equivalent_t; 59 | T_Trampoline callback_trampoline; 60 | 61 | // The unique key representing the callback to pass to unregister_callback on 62 | // destruction 63 | void* key; 64 | 65 | inline void move_obj(sandbox_callback&& other) 66 | { 67 | sandbox = other.sandbox; 68 | callback = other.callback; 69 | callback_interceptor = other.callback_interceptor; 70 | callback_trampoline = other.callback_trampoline; 71 | key = other.key; 72 | other.sandbox = nullptr; 73 | other.callback = nullptr; 74 | other.callback_interceptor = nullptr; 75 | other.callback_trampoline = 0; 76 | other.key = nullptr; 77 | } 78 | 79 | template 80 | inline void unregister_helper(T_Ret (*)(T_Args...)) 81 | { 82 | if (callback != nullptr) { 83 | // Don't need to worry about race between unregister and move as 84 | // 1) this will not happen in a correctly written program 85 | // 2) if this does happen, the worst that can happen is an invocation of a 86 | // null function pointer, which causes a crash that cannot be exploited 87 | // for RCE 88 | sandbox->template unregister_callback(key); 89 | sandbox = nullptr; 90 | callback = nullptr; 91 | callback_interceptor = nullptr; 92 | callback_trampoline = 0; 93 | key = nullptr; 94 | } 95 | } 96 | 97 | inline T_Callback get_raw_value() const noexcept { return callback; } 98 | inline T_Trampoline get_raw_sandbox_value() const noexcept 99 | { 100 | return callback_trampoline; 101 | } 102 | 103 | // Keep constructor private as only rlbox_sandbox should be able to create 104 | // this object 105 | sandbox_callback(rlbox_sandbox* p_sandbox, 106 | T_Callback p_callback, 107 | T_Interceptor p_callback_interceptor, 108 | T_Trampoline p_callback_trampoline, 109 | void* p_key) 110 | : sandbox(p_sandbox) 111 | , callback(p_callback) 112 | , callback_interceptor(p_callback_interceptor) 113 | , callback_trampoline(p_callback_trampoline) 114 | , key(p_key) 115 | { 116 | detail::dynamic_check(sandbox != nullptr, 117 | "Unexpected null sandbox when creating a callback"); 118 | } 119 | 120 | public: 121 | sandbox_callback() 122 | : sandbox(nullptr) 123 | , callback(nullptr) 124 | , callback_interceptor(nullptr) 125 | , callback_trampoline(0) 126 | , key(nullptr) 127 | {} 128 | 129 | sandbox_callback(sandbox_callback&& other) 130 | { 131 | move_obj(std::forward(other)); 132 | } 133 | 134 | inline sandbox_callback& operator=(sandbox_callback&& other) 135 | { 136 | if (this != &other) { 137 | move_obj(std::forward(other)); 138 | } 139 | return *this; 140 | } 141 | 142 | void unregister() 143 | { 144 | T dummy = nullptr; 145 | unregister_helper(dummy); 146 | } 147 | 148 | ~sandbox_callback() { unregister(); } 149 | 150 | /** 151 | * @brief Check if callback is _not_ registered. 152 | */ 153 | inline bool is_unregistered() const noexcept 154 | { 155 | return get_raw_value() == nullptr; 156 | } 157 | 158 | /** 159 | * @brief Unwrap a callback without verification. This is an unsafe operation 160 | * and should be used with care. 161 | */ 162 | inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); } 163 | /** 164 | * @brief Like UNSAFE_unverified, but get the underlying sandbox 165 | * representation. 166 | * 167 | * @param sandbox Reference to sandbox. 168 | */ 169 | inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const noexcept 170 | { 171 | RLBOX_UNUSED(sandbox); 172 | return get_raw_sandbox_value(); 173 | } 174 | }; 175 | 176 | template 177 | class app_pointer 178 | { 179 | KEEP_CLASSES_FRIENDLY 180 | 181 | private: 182 | app_pointer_map* map; 183 | typename T_Sbx::T_PointerType idx; 184 | T idx_unsandboxed; 185 | 186 | inline void move_obj(app_pointer&& other) 187 | { 188 | map = other.map; 189 | idx = other.idx; 190 | idx_unsandboxed = other.idx_unsandboxed; 191 | other.map = nullptr; 192 | other.idx = 0; 193 | other.idx_unsandboxed = nullptr; 194 | } 195 | 196 | inline T get_raw_value() const noexcept 197 | { 198 | return to_tainted().get_raw_value(); 199 | } 200 | inline typename T_Sbx::T_PointerType get_raw_sandbox_value() const noexcept 201 | { 202 | return idx; 203 | } 204 | 205 | app_pointer(app_pointer_map* a_map, 206 | typename T_Sbx::T_PointerType a_idx, 207 | T a_idx_unsandboxed) 208 | : map(a_map) 209 | , idx(a_idx) 210 | , idx_unsandboxed(a_idx_unsandboxed) 211 | {} 212 | 213 | public: 214 | app_pointer() 215 | : map(nullptr) 216 | , idx(0) 217 | , idx_unsandboxed(0) 218 | {} 219 | 220 | ~app_pointer() { unregister(); } 221 | 222 | app_pointer(app_pointer&& other) 223 | { 224 | move_obj(std::forward(other)); 225 | } 226 | 227 | inline app_pointer& operator=(app_pointer&& other) 228 | { 229 | if (this != &other) { 230 | move_obj(std::forward(other)); 231 | } 232 | return *this; 233 | } 234 | 235 | void unregister() 236 | { 237 | if (idx != 0) { 238 | map->remove_app_ptr(idx); 239 | map = nullptr; 240 | idx = 0; 241 | idx_unsandboxed = nullptr; 242 | } 243 | } 244 | 245 | tainted to_tainted() 246 | { 247 | return tainted::internal_factory( 248 | reinterpret_cast(idx_unsandboxed)); 249 | } 250 | 251 | /** 252 | * @brief Check if app pointer is _not_ registered. 253 | */ 254 | inline bool is_unregistered() const noexcept { return idx == 0; } 255 | 256 | /** 257 | * @brief Unwrap app_pointer without verification. This is an unsafe operation 258 | * and should be used with care. 259 | */ 260 | inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); } 261 | /** 262 | * @brief Like UNSAFE_unverified, but get the underlying sandbox 263 | * representation. 264 | * 265 | * @param sandbox Reference to sandbox. 266 | */ 267 | inline auto UNSAFE_sandboxed(rlbox_sandbox& sandbox) const noexcept 268 | { 269 | RLBOX_UNUSED(sandbox); 270 | return get_raw_sandbox_value(); 271 | } 272 | }; 273 | 274 | /** 275 | * @brief Tainted boolean value that serves as a "hint" and not a definite 276 | * answer. Comparisons with a tainted_volatile return such hints. They are 277 | * not `tainted` values because a compromised sandbox can modify 278 | * tainted_volatile data at any time. 279 | */ 280 | class tainted_boolean_hint 281 | { 282 | private: 283 | bool val; 284 | 285 | public: 286 | tainted_boolean_hint(bool init) 287 | : val(init) 288 | {} 289 | tainted_boolean_hint(const tainted_boolean_hint&) = default; 290 | inline tainted_boolean_hint& operator=(bool rhs) 291 | { 292 | val = rhs; 293 | return *this; 294 | } 295 | inline tainted_boolean_hint operator!() const 296 | { 297 | return tainted_boolean_hint(!val); 298 | } 299 | template 300 | inline bool unverified_safe_because(const char (&reason)[N]) const 301 | { 302 | (void)reason; /* unused */ 303 | return val; 304 | } 305 | inline bool UNSAFE_unverified() const { return val; } 306 | inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } 307 | 308 | // Add a template parameter to make sure the assert only fires when called 309 | template 310 | inline bool copy_and_verify(...) const 311 | { 312 | rlbox_detail_static_fail_because( 313 | detail::true_v, 314 | "You can't call copy_and_verify on this value, as this is a result of a " 315 | "comparison with memory accessible by the sandbox. \n" 316 | "The sandbox could unexpectedly change the value leading to " 317 | "time-of-check-time-of-use attacks. \n" 318 | "You can avoid this by making a local copy of the data." 319 | "For example, if your original code, looked like \n" 320 | "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n" 321 | "Change this to \n\n" 322 | "tainted val = tainted_ptr->member\n" 323 | "if ((val == 5).copy_and_verify(...)) { ... } \n\n" 324 | "tainted foo(rlbox_sandbox& sandbox) {...} \n\n" 325 | "Alternately, if you are sure your code is safe you can use the " 326 | "unverified_safe_because API to remove tainting\n"); 327 | 328 | // this is never executed, but we need it for the function to type-check 329 | return false; 330 | } 331 | }; 332 | 333 | /** 334 | * @brief Tainted integer value that serves as a "hint" and not a definite 335 | * answer. Comparisons with a tainted_volatile return such hints. They are 336 | * not `tainted` values because a compromised sandbox can modify 337 | * tainted_volatile data at any time. 338 | */ 339 | class tainted_int_hint 340 | { 341 | private: 342 | int val; 343 | 344 | public: 345 | tainted_int_hint(int init) 346 | : val(init) 347 | {} 348 | tainted_int_hint(const tainted_int_hint&) = default; 349 | inline tainted_int_hint& operator=(int rhs) 350 | { 351 | val = rhs; 352 | return *this; 353 | } 354 | inline tainted_boolean_hint operator!() const 355 | { 356 | return tainted_boolean_hint(!val); 357 | } 358 | template 359 | inline int unverified_safe_because(const char (&reason)[N]) const 360 | { 361 | (void)reason; /* unused */ 362 | return val; 363 | } 364 | inline int UNSAFE_unverified() const { return val; } 365 | inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } 366 | 367 | // Add a template parameter to make sure the assert only fires when called 368 | template 369 | inline int copy_and_verify(...) const 370 | { 371 | rlbox_detail_static_fail_because( 372 | detail::true_v, 373 | "You can't call copy_and_verify on this value, as this is a result of a " 374 | "comparison with memory accessible by the sandbox. \n" 375 | "The sandbox could unexpectedly change the value leading to " 376 | "time-of-check-time-of-use attacks. \n" 377 | "You can avoid this by making a local copy of the data." 378 | "For example, if your original code, looked like \n" 379 | "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n" 380 | "Change this to \n\n" 381 | "tainted val = tainted_ptr->member\n" 382 | "if ((val == 5).copy_and_verify(...)) { ... } \n\n" 383 | "tainted foo(rlbox_sandbox& sandbox) {...} \n\n" 384 | "Alternately, if you are sure your code is safe you can use the " 385 | "unverified_safe_because API to remove tainting\n"); 386 | 387 | // this is never executed, but we need it for the function to type-check 388 | return 0; 389 | } 390 | }; 391 | 392 | } 393 | -------------------------------------------------------------------------------- /code/include/rlbox_range.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | // IWYU pragma: private, include "rlbox.hpp" 4 | // IWYU pragma: friend "rlbox_.*\.hpp" 5 | 6 | #include 7 | 8 | #include "rlbox_types.hpp" 9 | 10 | namespace rlbox::detail { 11 | 12 | // Checks that a given range is either entirely in a sandbox or entirely 13 | // outside 14 | template 15 | inline void check_range_doesnt_cross_app_sbx_boundary(const void* ptr, 16 | size_t size) 17 | { 18 | auto ptr_start_val = reinterpret_cast(ptr); 19 | detail::dynamic_check( 20 | ptr_start_val, 21 | "Performing memory operation memset/memcpy on a null pointer"); 22 | auto ptr_end_val = ptr_start_val + size - 1; 23 | 24 | auto ptr_start = reinterpret_cast(ptr_start_val); 25 | auto ptr_end = reinterpret_cast(ptr_end_val); 26 | 27 | detail::dynamic_check( 28 | rlbox_sandbox::is_in_same_sandbox(ptr_start, ptr_end), 29 | "range has overflowed sandbox bounds"); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /code/include/rlbox_stdlib.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include "rlbox_helpers.hpp" 9 | #include "rlbox_types.hpp" 10 | #include "rlbox_unwrap.hpp" 11 | #include "rlbox_wrapper_traits.hpp" 12 | 13 | namespace rlbox { 14 | #define KEEP_CAST_FRIENDLY \ 15 | template \ 19 | typename T_C_Wrap> \ 20 | friend inline tainted sandbox_reinterpret_cast( \ 21 | const T_C_Wrap& rhs) noexcept; \ 22 | \ 23 | template \ 27 | typename T_C_Wrap> \ 28 | friend inline tainted sandbox_const_cast( \ 29 | const T_C_Wrap& rhs) noexcept; \ 30 | \ 31 | template \ 35 | typename T_C_Wrap> \ 36 | friend inline tainted sandbox_static_cast( \ 37 | const T_C_Wrap& rhs) noexcept; 38 | 39 | /** 40 | * @brief The equivalent of a reinterpret_cast but operates on sandboxed values. 41 | */ 42 | template 46 | typename T_Wrap> 47 | inline tainted sandbox_reinterpret_cast( 48 | const T_Wrap& rhs) noexcept 49 | { 50 | static_assert(detail::rlbox_is_wrapper_v> && 51 | std::is_pointer_v && std::is_pointer_v, 52 | "sandbox_reinterpret_cast on incompatible types"); 53 | 54 | tainted taintedVal = rhs; 55 | auto raw = reinterpret_cast(taintedVal.INTERNAL_unverified_safe()); 56 | auto ret = tainted::internal_factory(raw); 57 | return ret; 58 | } 59 | 60 | /** 61 | * @brief The equivalent of a const_cast but operates on sandboxed values. 62 | */ 63 | template 67 | typename T_Wrap> 68 | inline tainted sandbox_const_cast( 69 | const T_Wrap& rhs) noexcept 70 | { 71 | static_assert(detail::rlbox_is_wrapper_v>, 72 | "sandbox_const_cast on incompatible types"); 73 | 74 | tainted taintedVal = rhs; 75 | auto raw = const_cast(taintedVal.INTERNAL_unverified_safe()); 76 | auto ret = tainted::internal_factory(raw); 77 | return ret; 78 | } 79 | 80 | /** 81 | * @brief The equivalent of a static_cast but operates on sandboxed values. 82 | */ 83 | template 87 | typename T_Wrap> 88 | inline tainted sandbox_static_cast( 89 | const T_Wrap& rhs) noexcept 90 | { 91 | static_assert(detail::rlbox_is_wrapper_v>, 92 | "sandbox_static_cast on incompatible types"); 93 | 94 | tainted taintedVal = rhs; 95 | auto raw = static_cast(taintedVal.INTERNAL_unverified_safe()); 96 | auto ret = tainted::internal_factory(raw); 97 | return ret; 98 | } 99 | 100 | /** 101 | * @brief Fill sandbox memory with a constant byte. 102 | */ 103 | template 108 | typename T_Wrap> 109 | inline T_Wrap memset(rlbox_sandbox& sandbox, 110 | T_Wrap ptr, 111 | T_Val value, 112 | T_Num num) 113 | { 114 | 115 | static_assert(detail::rlbox_is_tainted_or_vol_v>, 116 | "memset called on non wrapped type"); 117 | 118 | static_assert(!std::is_const_v, "Destination is const"); 119 | 120 | auto num_val = detail::unwrap_value(num); 121 | detail::dynamic_check(num_val <= sandbox.get_total_memory(), 122 | "Called memset for memory larger than the sandbox"); 123 | 124 | tainted ptr_tainted = ptr; 125 | void* dest_start = ptr_tainted.INTERNAL_unverified_safe(); 126 | detail::check_range_doesnt_cross_app_sbx_boundary(dest_start, num_val); 127 | 128 | std::memset(dest_start, detail::unwrap_value(value), num_val); 129 | return ptr; 130 | } 131 | 132 | /** 133 | * @brief types that do not need to be adjusted to fix ABI differences. 134 | * Currently these are only char, wchar, float, and double 135 | */ 136 | 137 | template 138 | static constexpr bool can_type_be_memcopied = 139 | std::is_same_v> || std::is_same_v> || 140 | std::is_same_v> || std::is_same_v> || 141 | std::is_same_v> || std::is_same_v>; 142 | 143 | /** 144 | * @brief Copy to sandbox memory area. Note that memcpy is meant to be called on 145 | * byte arrays does not adjust data according to ABI differences. If the 146 | * programmer does accidentally call memcpy on buffers that needs ABI 147 | * adjustment, this may cause compatibility issues, but will not cause a 148 | * security issue as the destination is always a tainted or tainted_volatile 149 | * pointer 150 | */ 151 | template 156 | typename T_Wrap> 157 | inline T_Wrap memcpy(rlbox_sandbox& sandbox, 158 | T_Wrap dest, 159 | T_Lhs src, 160 | T_Num num) 161 | { 162 | 163 | static_assert(detail::rlbox_is_tainted_or_vol_v>, 164 | "memcpy called on non wrapped type"); 165 | 166 | static_assert(!std::is_const_v, "Destination is const"); 167 | 168 | auto num_val = detail::unwrap_value(num); 169 | detail::dynamic_check(num_val <= sandbox.get_total_memory(), 170 | "Called memcpy for memory larger than the sandbox"); 171 | 172 | tainted dest_tainted = dest; 173 | void* dest_start = dest_tainted.INTERNAL_unverified_safe(); 174 | detail::check_range_doesnt_cross_app_sbx_boundary(dest_start, num_val); 175 | 176 | // src also needs to be checked, as we don't want to allow a src rand to start 177 | // inside the sandbox and end outside, and vice versa 178 | // src may or may not be a wrapper, so use unwrap_value 179 | const void* src_start = detail::unwrap_value(src); 180 | detail::check_range_doesnt_cross_app_sbx_boundary(src_start, num_val); 181 | 182 | std::memcpy(dest_start, src_start, num_val); 183 | 184 | return dest; 185 | } 186 | 187 | /** 188 | * @brief Compare data in sandbox memory area. 189 | */ 190 | template 191 | inline tainted_int_hint memcmp(rlbox_sandbox& sandbox, 192 | T_Rhs&& dest, 193 | T_Lhs&& src, 194 | T_Num&& num) 195 | { 196 | static_assert( 197 | detail::rlbox_is_tainted_or_vol_v> || 198 | detail::rlbox_is_tainted_or_vol_v>, 199 | "memcmp called on non wrapped type"); 200 | 201 | auto num_val = detail::unwrap_value(num); 202 | detail::dynamic_check(num_val <= sandbox.get_total_memory(), 203 | "Called memcmp for memory larger than the sandbox"); 204 | 205 | void* dest_start = dest.INTERNAL_unverified_safe(); 206 | detail::check_range_doesnt_cross_app_sbx_boundary(dest_start, num_val); 207 | 208 | // src also needs to be checked, as we don't want to allow a src rand to start 209 | // inside the sandbox and end outside, and vice versa 210 | // src may or may not be a wrapper, so use unwrap_value 211 | const void* src_start = detail::unwrap_value(src); 212 | detail::check_range_doesnt_cross_app_sbx_boundary(src_start, num_val); 213 | 214 | int ret = std::memcmp(dest_start, src_start, num_val); 215 | tainted_int_hint converted_ret(ret); 216 | return converted_ret; 217 | } 218 | 219 | /** 220 | * @brief This function either 221 | * - copies the given buffer into the sandbox calling delete on the src 222 | * OR 223 | * - if the sandbox allows, adds the buffer to the existing sandbox memory 224 | * @param sandbox Target sandbox 225 | * @param src Raw pointer to the buffer 226 | * @param num Number of T-sized elements in the buffer 227 | * @param free_source_on_copy If the source buffer was copied, this variable 228 | * controls whether copy_memory_or_grant_access should call delete on the src. 229 | * This calls delete[] if num > 1. 230 | * @param copied out parameter indicating if the source was copied or transfered 231 | */ 232 | template 233 | tainted copy_memory_or_grant_access(rlbox_sandbox& sandbox, 234 | T* src, 235 | size_t num, 236 | bool free_source_on_copy, 237 | bool& copied) 238 | { 239 | copied = false; 240 | 241 | // This function is meant for byte buffers only 242 | static_assert(can_type_be_memcopied>, 243 | "copy_memory_or_grant_access not supported on this type as " 244 | "there may be ABI differences"); 245 | 246 | // overflow ok 247 | size_t source_size = num * sizeof(T); 248 | 249 | // sandbox can grant access if it includes the following line 250 | // using can_grant_deny_access = void; 251 | if constexpr (detail::has_member_using_can_grant_deny_access_v) { 252 | detail::check_range_doesnt_cross_app_sbx_boundary(src, source_size); 253 | 254 | bool success; 255 | auto ret = sandbox.INTERNAL_grant_access(src, num, success); 256 | if (success) { 257 | return ret; 258 | } 259 | } 260 | 261 | // Malloc in sandbox takes a uint32_t as the parameter, need a bounds check 262 | detail::dynamic_check(num <= std::numeric_limits::max(), 263 | "Granting access too large a region"); 264 | using T_nocv = std::remove_cv_t; 265 | tainted copy = 266 | sandbox.template malloc_in_sandbox(static_cast(num)); 267 | if (!copy) { 268 | return nullptr; 269 | } 270 | 271 | rlbox::memcpy(sandbox, copy, src, source_size); 272 | if (free_source_on_copy) { 273 | free(const_cast(reinterpret_cast(src))); 274 | } 275 | 276 | copied = true; 277 | return sandbox_const_cast(copy); 278 | } 279 | 280 | /** 281 | * @brief This function either 282 | * - copies the given buffer out of the sandbox calling free_in_sandbox on the 283 | * src 284 | * OR 285 | * - if the sandbox allows, moves the buffer out of existing sandbox memory 286 | * @param sandbox Target sandbox 287 | * @param src Raw pointer to the buffer 288 | * @param num Number of T-sized elements in the buffer 289 | * @param free_source_on_copy If the source buffer was copied, this variable 290 | * controls whether copy_memory_or_deny_access should call delete on the src. 291 | * This calls delete[] if num > 1. 292 | * @param copied out parameter indicating if the source was copied or transfered 293 | */ 294 | template 297 | typename T_Wrap> 298 | T* copy_memory_or_deny_access(rlbox_sandbox& sandbox, 299 | T_Wrap src, 300 | size_t num, 301 | bool free_source_on_copy, 302 | bool& copied) 303 | { 304 | copied = false; 305 | 306 | // This function is meant for byte buffers only - so char and char16 307 | static_assert(can_type_be_memcopied>, 308 | "copy_memory_or_deny_access not supported on this type as " 309 | "there may be ABI differences"); 310 | 311 | // overflow ok 312 | size_t source_size = num * sizeof(T); 313 | 314 | // sandbox can grant access if it includes the following line 315 | // using can_grant_deny_access = void; 316 | if constexpr (detail::has_member_using_can_grant_deny_access_v) { 317 | detail::check_range_doesnt_cross_app_sbx_boundary( 318 | src.INTERNAL_unverified_safe(), source_size); 319 | 320 | bool success; 321 | auto ret = sandbox.INTERNAL_deny_access(src, num, success); 322 | if (success) { 323 | return ret; 324 | } 325 | } 326 | 327 | auto copy = static_cast(malloc(source_size)); 328 | if (!copy) { 329 | return nullptr; 330 | } 331 | 332 | tainted src_tainted = src; 333 | char* src_raw = src_tainted.copy_and_verify_buffer_address( 334 | [](uintptr_t val) { return reinterpret_cast(val); }, num); 335 | std::memcpy(copy, src_raw, source_size); 336 | if (free_source_on_copy) { 337 | sandbox.free_in_sandbox(src); 338 | } 339 | 340 | copied = true; 341 | return copy; 342 | } 343 | 344 | } 345 | -------------------------------------------------------------------------------- /code/include/rlbox_stdlib_polyfill.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This file is a polyfill for parts of the C++ standard library available only 4 | // in newer compilers. Since these are only compile time requirements, we can 5 | // just include these as part of the rlbox library in case the target compiler 6 | // doesn't support these features. For instance clang-5 which rlbox supports 7 | // does not support std::invocable and related functionality in 8 | // and is polyfilled here. 9 | // 10 | // This code was borrowed from clang's standard library - libc++ 11 | // 12 | // Link: 13 | // https://github.com/llvm-mirror/libcxx/blob/master/include/type_traits 14 | // 15 | // libc++ is dual licensed under the MIT license and the UIUC License (a 16 | // BSD-like license) and is therefore compatible with our code base 17 | 18 | // std::invocable and friends 19 | 20 | namespace rlbox::detail::polyfill { 21 | 22 | struct __nat 23 | { 24 | __nat() = delete; 25 | __nat(const __nat&) = delete; 26 | __nat& operator=(const __nat&) = delete; 27 | ~__nat() = delete; 28 | }; 29 | 30 | template 31 | using _BoolConstant = std::integral_constant; 32 | 33 | template 34 | using _IsNotSame = _BoolConstant::value>; 35 | 36 | #define INVOKE_RETURN(...) \ 37 | noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; } 38 | 39 | template 40 | inline auto helper__invoke(_Fp&& __f, _Args&&... __args) 41 | INVOKE_RETURN(std::forward<_Fp>(__f)(std::forward<_Args>(__args)...)) 42 | 43 | template 44 | inline constexpr auto helper__invoke_constexpr(_Fp&& __f, _Args&&... __args) 45 | INVOKE_RETURN(std::forward<_Fp>(__f)(std::forward<_Args>(__args)...)) 46 | 47 | #undef INVOKE_RETURN 48 | 49 | // __invokable 50 | template 51 | struct __invokable_r 52 | { 53 | template 54 | static auto __try_call(int) 55 | -> decltype(helper__invoke(std::declval<_XFp>(), 56 | std::declval<_XArgs>()...)); 57 | template 58 | static __nat __try_call(...); 59 | 60 | // FIXME: Check that _Ret, _Fp, and _Args... are all complete types, cv void, 61 | // or incomplete array types as required by the standard. 62 | using _Result = decltype(__try_call<_Fp, _Args...>(0)); 63 | 64 | using type = typename std::conditional< 65 | _IsNotSame<_Result, __nat>::value, 66 | typename std::conditional::value, 67 | std::true_type, 68 | std::is_convertible<_Result, _Ret>>::type, 69 | std::false_type>::type; 70 | static const bool value = type::value; 71 | }; 72 | template 73 | using __invokable = __invokable_r; 74 | 75 | template 80 | struct __nothrow_invokable_r_imp 81 | { 82 | static const bool value = false; 83 | }; 84 | 85 | template 86 | struct __nothrow_invokable_r_imp 87 | { 88 | typedef __nothrow_invokable_r_imp _ThisT; 89 | 90 | template 91 | static void __test_noexcept(_Tp) noexcept; 92 | 93 | static const bool value = noexcept(_ThisT::__test_noexcept<_Ret>( 94 | helper__invoke(std::declval<_Fp>(), std::declval<_Args>()...))); 95 | }; 96 | 97 | template 98 | struct __nothrow_invokable_r_imp 99 | { 100 | static const bool value = 101 | noexcept(helper__invoke(std::declval<_Fp>(), std::declval<_Args>()...)); 102 | }; 103 | 104 | template 105 | using __nothrow_invokable_r = 106 | __nothrow_invokable_r_imp<__invokable_r<_Ret, _Fp, _Args...>::value, 107 | std::is_void<_Ret>::value, 108 | _Ret, 109 | _Fp, 110 | _Args...>; 111 | 112 | template 113 | using __nothrow_invokable = 114 | __nothrow_invokable_r_imp<__invokable<_Fp, _Args...>::value, 115 | true, 116 | void, 117 | _Fp, 118 | _Args...>; 119 | 120 | template 121 | struct helper__invoke_of 122 | : public std::enable_if<__invokable<_Fp, _Args...>::value, 123 | typename __invokable_r::_Result> 124 | {}; 125 | 126 | // invoke_result 127 | 128 | template 129 | struct invoke_result : helper__invoke_of<_Fn, _Args...> 130 | {}; 131 | 132 | template 133 | using invoke_result_t = typename invoke_result<_Fn, _Args...>::type; 134 | 135 | // is_invocable 136 | 137 | template 138 | struct is_invocable 139 | : std::integral_constant::value> 140 | {}; 141 | 142 | template 143 | struct is_invocable_r 144 | : std::integral_constant::value> 145 | {}; 146 | 147 | template 148 | inline constexpr bool is_invocable_v = is_invocable<_Fn, _Args...>::value; 149 | 150 | template 151 | inline constexpr bool is_invocable_r_v = 152 | is_invocable_r<_Ret, _Fn, _Args...>::value; 153 | 154 | // is_nothrow_invocable 155 | 156 | template 157 | struct is_nothrow_invocable 158 | : std::integral_constant::value> 159 | {}; 160 | 161 | template 162 | struct is_nothrow_invocable_r 163 | : std::integral_constant::value> 165 | {}; 166 | 167 | template 168 | inline constexpr bool is_nothrow_invocable_v = 169 | is_nothrow_invocable<_Fn, _Args...>::value; 170 | 171 | template 172 | inline constexpr bool is_nothrow_invocable_r_v = 173 | is_nothrow_invocable_r<_Ret, _Fn, _Args...>::value; 174 | 175 | } 176 | -------------------------------------------------------------------------------- /code/include/rlbox_types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | namespace rlbox { 6 | 7 | template 8 | class tainted_opaque 9 | { 10 | private: 11 | T data{ 0 }; 12 | 13 | public: 14 | template 15 | void set_zero() 16 | { 17 | data = 0; 18 | } 19 | }; 20 | 21 | template 22 | class tainted; 23 | 24 | template 25 | class tainted_volatile; 26 | 27 | class tainted_boolean_hint; 28 | 29 | class tainted_int_hint; 30 | 31 | template 32 | class rlbox_sandbox; 33 | 34 | template 35 | class sandbox_callback; 36 | 37 | template 38 | class app_pointer; 39 | 40 | class rlbox_noop_sandbox; 41 | 42 | class rlbox_dylib_sandbox; 43 | } 44 | 45 | #define RLBOX_DEFINE_BASE_TYPES_FOR(SBXNAME, SBXTYPE) \ 46 | namespace rlbox { \ 47 | class rlbox_##SBXTYPE##_sandbox; \ 48 | } \ 49 | using rlbox_##SBXNAME##_sandbox_type = rlbox::rlbox_##SBXTYPE##_sandbox; \ 50 | using rlbox_sandbox_##SBXNAME = \ 51 | rlbox::rlbox_sandbox; \ 52 | template \ 53 | using sandbox_callback_##SBXNAME = \ 54 | rlbox::sandbox_callback; \ 55 | template \ 56 | using tainted_##SBXNAME = rlbox::tainted; \ 57 | template \ 58 | using tainted_opaque_##SBXNAME = \ 59 | rlbox::tainted_opaque; \ 60 | template \ 61 | using tainted_volatile_##SBXNAME = \ 62 | rlbox::tainted_volatile; \ 63 | using rlbox::tainted_boolean_hint; \ 64 | template \ 65 | using app_pointer_##SBXNAME = \ 66 | rlbox::app_pointer; 67 | 68 | // This is like RLBOX_DEFINE_BASE_TYPES_FOR but with an explicit sandbox type 69 | #define RLBOX_DEFINE_BASE_TYPES_FOR_TYPE(SBXNAME, SBXTYPE) \ 70 | using rlbox_##SBXNAME##_sandbox_type = SBXTYPE; \ 71 | using rlbox_sandbox_##SBXNAME = \ 72 | rlbox::rlbox_sandbox; \ 73 | template \ 74 | using sandbox_callback_##SBXNAME = \ 75 | rlbox::sandbox_callback; \ 76 | template \ 77 | using tainted_##SBXNAME = rlbox::tainted; \ 78 | template \ 79 | using tainted_opaque_##SBXNAME = \ 80 | rlbox::tainted_opaque; \ 81 | template \ 82 | using tainted_volatile_##SBXNAME = \ 83 | rlbox::tainted_volatile; \ 84 | using rlbox::tainted_boolean_hint; \ 85 | template \ 86 | using app_pointer_##SBXNAME = \ 87 | rlbox::app_pointer; 88 | -------------------------------------------------------------------------------- /code/include/rlbox_unwrap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | 7 | #include "rlbox_type_traits.hpp" 8 | #include "rlbox_types.hpp" 9 | 10 | namespace rlbox::detail { 11 | 12 | template 13 | inline auto unwrap_value(T_Rhs&& rhs) noexcept 14 | { 15 | using T_RhsNoQ = detail::remove_cv_ref_t; 16 | if constexpr (detail::rlbox_is_wrapper_v) { 17 | return rhs.INTERNAL_unverified_safe(); 18 | } else if constexpr (detail::rlbox_is_tainted_boolean_hint_v) { 19 | return rhs.INTERNAL_unverified_safe(); 20 | } else { 21 | return rhs; 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /code/include/rlbox_wrapper_traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // IWYU pragma: private, include "rlbox.hpp" 3 | // IWYU pragma: friend "rlbox_.*\.hpp" 4 | 5 | #include 6 | 7 | #include "rlbox_types.hpp" 8 | 9 | namespace rlbox::detail { 10 | 11 | #define rlbox_generate_wrapper_check(name) \ 12 | namespace detail_rlbox_is_##name \ 13 | { \ 14 | template \ 15 | struct unwrapper : std::false_type \ 16 | {}; \ 17 | \ 18 | template \ 19 | struct unwrapper> : std::true_type \ 20 | {}; \ 21 | } \ 22 | \ 23 | template \ 24 | constexpr bool rlbox_is_##name##_v = \ 25 | detail_rlbox_is_##name::unwrapper::value; \ 26 | RLBOX_REQUIRE_SEMI_COLON 27 | 28 | rlbox_generate_wrapper_check(tainted); 29 | rlbox_generate_wrapper_check(tainted_volatile); 30 | rlbox_generate_wrapper_check(tainted_opaque); 31 | rlbox_generate_wrapper_check(sandbox_callback); 32 | 33 | #undef rlbox_generate_wrapper_check 34 | 35 | namespace detail_rlbox_is_tainted_boolean_hint { 36 | template 37 | struct unwrapper : std::false_type 38 | {}; 39 | 40 | template<> 41 | struct unwrapper : std::true_type 42 | {}; 43 | } 44 | 45 | template 46 | constexpr bool rlbox_is_tainted_boolean_hint_v = 47 | detail_rlbox_is_tainted_boolean_hint::unwrapper::value; 48 | 49 | template 50 | constexpr bool rlbox_is_tainted_or_vol_v = 51 | rlbox_is_tainted_v || rlbox_is_tainted_volatile_v; 52 | 53 | template 54 | constexpr bool rlbox_is_tainted_or_opaque_v = 55 | rlbox_is_tainted_v || rlbox_is_tainted_opaque_v; 56 | 57 | // tainted_hint is NOT considered a wrapper type... This carries no particular 58 | // significant and is just a convention choice 59 | template 60 | constexpr bool rlbox_is_wrapper_v = 61 | rlbox_is_tainted_v || rlbox_is_tainted_volatile_v || 62 | rlbox_is_tainted_opaque_v || rlbox_is_sandbox_callback_v; 63 | 64 | namespace detail_rlbox_remove_wrapper { 65 | template 66 | struct unwrapper 67 | { 68 | using type = T; 69 | using type_sbx = void; 70 | }; 71 | 72 | template 73 | struct unwrapper> 74 | { 75 | using type = T; 76 | using type_sbx = T_Sbx; 77 | }; 78 | 79 | template 80 | struct unwrapper> 81 | { 82 | using type = T; 83 | using type_sbx = T_Sbx; 84 | }; 85 | 86 | template 87 | struct unwrapper> 88 | { 89 | using type = T; 90 | using type_sbx = T_Sbx; 91 | }; 92 | 93 | template 94 | struct unwrapper> 95 | { 96 | using type = T; 97 | using type_sbx = T_Sbx; 98 | }; 99 | } 100 | 101 | template 102 | using rlbox_remove_wrapper_t = 103 | typename detail_rlbox_remove_wrapper::unwrapper::type; 104 | 105 | template 106 | using rlbox_get_wrapper_sandbox_t = 107 | typename detail_rlbox_remove_wrapper::unwrapper::type_sbx; 108 | 109 | template 110 | using rlbox_tainted_opaque_to_tainted_t = 111 | std::conditional_t, 112 | tainted, T_Sbx>, 113 | T>; 114 | 115 | // https://stackoverflow.com/questions/34974844/check-if-a-type-is-from-a-particular-namespace 116 | namespace detail_is_member_of_rlbox_detail { 117 | template 118 | struct is_member_of_rlbox_detail_helper : std::false_type 119 | {}; 120 | 121 | template 122 | struct is_member_of_rlbox_detail_helper< 123 | T, 124 | decltype(struct_is_member_of_rlbox_detail(std::declval()))> 125 | : std::true_type 126 | {}; 127 | } 128 | 129 | template 130 | void struct_is_member_of_rlbox_detail(T&&); 131 | 132 | template 133 | constexpr auto is_member_of_rlbox_detail = 134 | detail_is_member_of_rlbox_detail::is_member_of_rlbox_detail_helper::value; 135 | 136 | // https://stackoverflow.com/questions/9644477/how-to-check-whether-a-class-has-specified-nested-class-definition-or-typedef-in 137 | namespace detail_has_member_using_can_grant_deny_access { 138 | template 139 | struct has_member_using_can_grant_deny_access : std::false_type 140 | {}; 141 | 142 | template 143 | struct has_member_using_can_grant_deny_access< 144 | T, 145 | std::void_t> : std::true_type 146 | {}; 147 | } 148 | 149 | template 150 | constexpr bool has_member_using_can_grant_deny_access_v = 151 | detail_has_member_using_can_grant_deny_access:: 152 | has_member_using_can_grant_deny_access::value; 153 | 154 | namespace detail_has_member_using_needs_internal_lookup_symbol { 155 | template 156 | struct has_member_using_needs_internal_lookup_symbol : std::false_type 157 | {}; 158 | 159 | template 160 | struct has_member_using_needs_internal_lookup_symbol< 161 | T, 162 | std::void_t> : std::true_type 163 | {}; 164 | } 165 | 166 | template 167 | constexpr bool has_member_using_needs_internal_lookup_symbol_v = 168 | detail_has_member_using_needs_internal_lookup_symbol:: 169 | has_member_using_needs_internal_lookup_symbol::value; 170 | 171 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_app_pointers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "test_include.hpp" 5 | 6 | using rlbox::app_pointer; 7 | using rlbox::tainted; 8 | 9 | using RL = rlbox::rlbox_sandbox; 10 | 11 | // NOLINTNEXTLINE 12 | TEST_CASE("Test app pointers", "[app pointer]") 13 | { 14 | RL sandbox; 15 | sandbox.create_sandbox(); 16 | 17 | auto sandbox_mem_loc = sandbox.malloc_in_sandbox(); 18 | 19 | unsigned int* ptr = (unsigned int*)malloc(sizeof(unsigned int)); 20 | app_pointer app_ptr = 21 | sandbox.get_app_pointer(ptr); 22 | tainted app_ptr_tainted = app_ptr.to_tainted(); 23 | 24 | REQUIRE(!app_ptr.is_unregistered()); 25 | 26 | // Force the conversion to volatile and back 27 | *sandbox_mem_loc = app_ptr_tainted; 28 | tainted app_ptr_tainted_reread = *sandbox_mem_loc; 29 | unsigned int* original_ptr = sandbox.lookup_app_ptr(app_ptr_tainted_reread); 30 | 31 | REQUIRE(ptr == original_ptr); 32 | 33 | free(ptr); 34 | 35 | app_ptr.unregister(); 36 | REQUIRE(app_ptr.is_unregistered()); 37 | 38 | sandbox.destroy_sandbox(); 39 | } 40 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_bool_operators.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "test_include.hpp" 4 | 5 | using rlbox::tainted; 6 | 7 | // NOLINTNEXTLINE 8 | TEST_CASE("Test bool operators", "[operator]") 9 | { 10 | tainted t_T = true; 11 | tainted t_F = false; 12 | const bool T = true; 13 | 14 | SECTION("tainted left") // NOLINT 15 | { 16 | REQUIRE(t_T.UNSAFE_unverified()); 17 | 18 | REQUIRE_FALSE(t_F.UNSAFE_unverified()); 19 | 20 | REQUIRE((!t_F).UNSAFE_unverified()); 21 | 22 | tainted and_false = t_F && T; 23 | REQUIRE_FALSE(and_false.UNSAFE_unverified()); 24 | 25 | tainted and_true = t_T && T; 26 | REQUIRE(and_true.UNSAFE_unverified()); 27 | 28 | REQUIRE_FALSE((t_T && t_F).UNSAFE_unverified()); 29 | REQUIRE((t_T || t_F).UNSAFE_unverified()); 30 | 31 | // No r-value expressions for boolean operators as short circuiting behavior 32 | // changes 33 | REQUIRE_COMPILE_ERR(t_T && true); 34 | REQUIRE_COMPILE_ERR(t_T && tainted(true)); 35 | } 36 | 37 | SECTION("tainted right") // NOLINT 38 | { 39 | tainted and_false = T && t_F; 40 | REQUIRE_FALSE(and_false.UNSAFE_unverified()); 41 | 42 | tainted and_true = T && t_T; 43 | REQUIRE(and_true.UNSAFE_unverified()); 44 | 45 | // No r-value expressions for boolean operators as short circuiting behavior 46 | // changes 47 | REQUIRE_COMPILE_ERR(true && tainted(true)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_callback.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | #include 4 | 5 | using rlbox::tainted; 6 | using rlbox::tainted_opaque; 7 | using RL = rlbox::rlbox_sandbox; 8 | 9 | // NOLINTNEXTLINE 10 | tainted test_cb(RL& /*unused*/, tainted a) 11 | { 12 | return a; 13 | } 14 | 15 | // NOLINTNEXTLINE 16 | TEST_CASE("Test sandbox_callback assignment", "[sandbox_callback]") 17 | { 18 | RL sandbox; 19 | sandbox.create_sandbox(); 20 | 21 | using T_F = int (*)(int); 22 | 23 | auto ptr = sandbox.malloc_in_sandbox(); 24 | auto cb = sandbox.register_callback(test_cb); 25 | 26 | tainted val = nullptr; 27 | 28 | // Assignment to tainted is not ok 29 | REQUIRE_COMPILE_ERR(val = cb); 30 | 31 | // Assignment to tainted_volatile is fine 32 | REQUIRE_NO_COMPILE_ERR(*ptr = cb); 33 | 34 | sandbox.destroy_sandbox(); 35 | } 36 | 37 | // No sandbox as the first param 38 | static void bad_callback_1() {} 39 | 40 | // No sandbox as the first param 41 | static void bad_callback_2(int) {} // NOLINT 42 | 43 | // No tainted param 44 | static void bad_callback_3(RL&, int) {} // NOLINT 45 | 46 | // No tainted param 47 | static tainted bad_callback_4(RL&, int) // NOLINT 48 | { 49 | const int test_val = 5; 50 | return test_val; 51 | } 52 | 53 | // No tainted return 54 | static int bad_callback_5(RL&) // NOLINT 55 | { 56 | const int test_val = 5; 57 | return test_val; 58 | } 59 | 60 | // No tainted return 61 | static int bad_callback_6(RL&, tainted) // NOLINT 62 | { 63 | const int test_val = 5; 64 | return test_val; 65 | } 66 | 67 | // Using static array parameter 68 | static void bad_callback_7(RL&, tainted) {} // NOLINT 69 | 70 | static void good_callback_1(RL&) {} // NOLINT 71 | 72 | static void good_callback_2(RL&, tainted) {} // NOLINT 73 | 74 | static tainted good_callback_3(RL&) // NOLINT 75 | { 76 | const int test_val = 5; 77 | return test_val; 78 | } 79 | 80 | static tainted good_callback_4( 81 | RL&, // NOLINT 82 | tainted) // NOLINT 83 | { 84 | const int test_val = 5; 85 | return test_val; 86 | } 87 | 88 | static tainted_opaque good_callback_5( 89 | RL&, // NOLINT 90 | tainted_opaque) // NOLINT 91 | { 92 | const int test_val = 5; 93 | return tainted(test_val).to_opaque(); 94 | } 95 | 96 | // NOLINTNEXTLINE 97 | TEST_CASE("callback sig checks", "[sandbox_callback]") 98 | { 99 | RL sandbox; 100 | sandbox.create_sandbox(); 101 | 102 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_1)); 103 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_2)); 104 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_3)); 105 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_4)); 106 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_5)); 107 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_6)); 108 | REQUIRE_COMPILE_ERR(sandbox.register_callback(bad_callback_7)); 109 | REQUIRE_NO_COMPILE_ERR(sandbox.register_callback(good_callback_1)); 110 | REQUIRE_NO_COMPILE_ERR(sandbox.register_callback(good_callback_2)); 111 | REQUIRE_NO_COMPILE_ERR(sandbox.register_callback(good_callback_3)); 112 | REQUIRE_NO_COMPILE_ERR(sandbox.register_callback(good_callback_4)); 113 | REQUIRE_NO_COMPILE_ERR(sandbox.register_callback(good_callback_5)); 114 | 115 | sandbox.destroy_sandbox(); 116 | } 117 | 118 | // NOLINTNEXTLINE 119 | TEST_CASE("callback assignment check", "[sandbox_callback]") 120 | { 121 | RL sandbox; 122 | sandbox.create_sandbox(); 123 | 124 | auto p_fnPtr = sandbox.malloc_in_sandbox(); 125 | REQUIRE_NO_COMPILE_ERR(p_fnPtr = nullptr); 126 | 127 | sandbox.destroy_sandbox(); 128 | } 129 | 130 | // NOLINTNEXTLINE 131 | TEST_CASE("callback re-register", "[sandbox_callback]") 132 | { 133 | RL sandbox; 134 | sandbox.create_sandbox(); 135 | 136 | using T_F = int (*)(int); 137 | { 138 | auto cb = std::make_unique>( 139 | sandbox.register_callback(test_cb)); 140 | } 141 | { 142 | auto cb2 = sandbox.register_callback(test_cb); 143 | } 144 | { 145 | auto cb3 = sandbox.register_callback(test_cb); 146 | } 147 | 148 | sandbox.destroy_sandbox(); 149 | } 150 | 151 | // NOLINTNEXTLINE 152 | TEST_CASE("callback is-registered", "[sandbox_callback]") 153 | { 154 | RL sandbox; 155 | sandbox.create_sandbox(); 156 | 157 | using T_F = int (*)(int); 158 | auto cb = std::make_unique>( 159 | sandbox.register_callback(test_cb)); 160 | 161 | REQUIRE(!cb->is_unregistered()); 162 | cb->unregister(); 163 | REQUIRE(cb->is_unregistered()); 164 | 165 | sandbox.destroy_sandbox(); 166 | } 167 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_comparison.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test_include.hpp" 6 | 7 | using rlbox::tainted; 8 | using rlbox::tainted_boolean_hint; 9 | 10 | // NOLINTNEXTLINE 11 | TEST_CASE("Test comparisons to nullptr", "[tainted_compare]") 12 | { 13 | rlbox::rlbox_sandbox sandbox; 14 | sandbox.create_sandbox(); 15 | 16 | auto ptr = sandbox.malloc_in_sandbox(); 17 | REQUIRE(ptr != nullptr); 18 | REQUIRE(!(ptr == nullptr)); 19 | REQUIRE(!!ptr); 20 | 21 | sandbox.destroy_sandbox(); 22 | } 23 | 24 | // NOLINTNEXTLINE 25 | TEST_CASE("Test disallowed comparisons to tainted", "[tainted_compare]") 26 | { 27 | rlbox::rlbox_sandbox sandbox; 28 | sandbox.create_sandbox(); 29 | 30 | auto ptr = sandbox.malloc_in_sandbox(); 31 | auto ptr2 = sandbox.malloc_in_sandbox(); 32 | REQUIRE_COMPILE_ERR(ptr < ptr2); 33 | 34 | sandbox.destroy_sandbox(); 35 | } 36 | 37 | // NOLINTNEXTLINE 38 | TEST_CASE("Test comparisons to tainted", "[tainted_compare]") 39 | { 40 | const uint32_t testVal = 3; 41 | tainted a = testVal; 42 | 43 | SECTION("Tainted Untainted compare") // NOLINT 44 | { 45 | auto result = a == testVal; 46 | REQUIRE(std::is_same_v>); 47 | REQUIRE(result.UNSAFE_unverified()); 48 | REQUIRE(!((!result).UNSAFE_unverified())); 49 | 50 | auto result2 = a != testVal; 51 | REQUIRE(std::is_same_v>); 52 | REQUIRE(!(result2.UNSAFE_unverified())); 53 | REQUIRE((!result2).UNSAFE_unverified()); 54 | } 55 | 56 | SECTION("Untainted Tainted compare") // NOLINT 57 | { 58 | auto result = testVal == a; 59 | REQUIRE(std::is_same_v>); 60 | REQUIRE(result.UNSAFE_unverified()); 61 | REQUIRE(!((!result).UNSAFE_unverified())); 62 | 63 | auto result2 = testVal != a; 64 | REQUIRE(std::is_same_v>); 65 | REQUIRE(!(result2.UNSAFE_unverified())); 66 | REQUIRE((!result2).UNSAFE_unverified()); 67 | } 68 | 69 | SECTION("Tainted Tainted compare") // NOLINT 70 | { 71 | auto result = a == a; // NOLINT(misc-redundant-expression) 72 | REQUIRE(std::is_same_v>); 73 | REQUIRE(result.UNSAFE_unverified()); 74 | REQUIRE(!((!result).UNSAFE_unverified())); 75 | 76 | auto result2 = a != a; // NOLINT(misc-redundant-expression) 77 | REQUIRE(std::is_same_v>); 78 | REQUIRE(!(result2.UNSAFE_unverified())); 79 | REQUIRE((!result2).UNSAFE_unverified()); 80 | } 81 | } 82 | 83 | // NOLINTNEXTLINE 84 | TEST_CASE("Test comparisons to tainted_volatile", "[tainted_compare]") 85 | { 86 | rlbox::rlbox_sandbox sandbox; 87 | sandbox.create_sandbox(); 88 | 89 | const uint32_t testVal = 3; 90 | tainted t = testVal; 91 | auto t_ptr = sandbox.malloc_in_sandbox(); 92 | *t_ptr = testVal; 93 | 94 | SECTION("tainted with tainted_volatile") // NOLINT 95 | { 96 | auto result1 = t == (*t_ptr); 97 | auto result2 = t != (*t_ptr); 98 | REQUIRE(std::is_same_v); 99 | REQUIRE(std::is_same_v); 100 | REQUIRE(result1.unverified_safe_because("Testing")); 101 | REQUIRE(!result2.unverified_safe_because("Testing")); 102 | } 103 | SECTION("tainted_volatile with tainted") // NOLINT 104 | { 105 | auto result1 = (*t_ptr) == t; 106 | auto result2 = (*t_ptr) != t; 107 | REQUIRE(std::is_same_v); 108 | REQUIRE(std::is_same_v); 109 | REQUIRE(result1.unverified_safe_because("Testing")); 110 | REQUIRE(!result2.unverified_safe_because("Testing")); 111 | } 112 | SECTION("tainted_volatile with tainted_volatile") // NOLINT 113 | { 114 | auto result1 = (*t_ptr) == (*t_ptr); // NOLINT(misc-redundant-expression) 115 | auto result2 = (*t_ptr) != (*t_ptr); // NOLINT(misc-redundant-expression) 116 | REQUIRE(std::is_same_v); 117 | REQUIRE(std::is_same_v); 118 | REQUIRE(result1.unverified_safe_because("Testing")); 119 | REQUIRE(!result2.unverified_safe_because("Testing")); 120 | } 121 | SECTION("tainted_volatile with unwrapped") // NOLINT 122 | { 123 | auto result1 = (*t_ptr) == testVal; 124 | auto result2 = (*t_ptr) != testVal; 125 | REQUIRE(std::is_same_v); 126 | REQUIRE(std::is_same_v); 127 | REQUIRE(result1.unverified_safe_because("Testing")); 128 | REQUIRE(!result2.unverified_safe_because("Testing")); 129 | } 130 | SECTION("unwrapped with tainted_volatile") // NOLINT 131 | { 132 | auto result1 = testVal == (*t_ptr); 133 | auto result2 = testVal != (*t_ptr); 134 | REQUIRE(std::is_same_v); 135 | REQUIRE(std::is_same_v); 136 | REQUIRE(result1.unverified_safe_because("Testing")); 137 | REQUIRE(!result2.unverified_safe_because("Testing")); 138 | } 139 | 140 | sandbox.destroy_sandbox(); 141 | } 142 | 143 | // NOLINTNEXTLINE 144 | TEST_CASE("Test other comparison operators", "[tainted_compare]") 145 | { 146 | tainted a = 1; 147 | auto ret = a > static_cast(0); 148 | REQUIRE(ret.UNSAFE_unverified()); 149 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_conversion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_include.hpp" 7 | 8 | struct Foo 9 | { 10 | int a; 11 | }; 12 | 13 | using rlbox::detail::convert_type_fundamental_or_array; 14 | 15 | // NOLINTNEXTLINE 16 | TEST_CASE("convert_type_fundamental_or_array for numerics operates correctly", 17 | "[convert]") 18 | { 19 | const int32_t randValue = 5; 20 | 21 | { 22 | int64_t dest; 23 | convert_type_fundamental_or_array(dest, randValue); 24 | REQUIRE(dest == randValue); 25 | } 26 | 27 | { 28 | int64_t dest; 29 | convert_type_fundamental_or_array(dest, randValue); 30 | REQUIRE(dest == randValue); 31 | } 32 | } 33 | 34 | // NOLINTNEXTLINE 35 | TEST_CASE("convert_type_fundamental_or_array compile time checks for " 36 | "numerics operate correctly", 37 | "[convert]") 38 | { 39 | const int32_t randValue = 5; 40 | Foo a{ randValue }; 41 | // does not support classes 42 | { 43 | Foo dest{}; 44 | REQUIRE_COMPILE_ERR(convert_type_fundamental_or_array(dest, a)); 45 | } 46 | { 47 | Foo dest{}; 48 | REQUIRE_COMPILE_ERR(convert_type_fundamental_or_array(dest, randValue)); 49 | } 50 | { 51 | int dest; 52 | REQUIRE_COMPILE_ERR(convert_type_fundamental_or_array(dest, a)); 53 | } 54 | } 55 | 56 | // NOLINTNEXTLINE 57 | TEST_CASE("convert_type_fundamental_or_array dynamic bounds checks for " 58 | "numerics operate correctly", 59 | "[convert]") 60 | { 61 | uint64_t u32Max = std::numeric_limits::max(); 62 | const uint64_t randValue = 5; 63 | 64 | { 65 | uint32_t dest; 66 | convert_type_fundamental_or_array(dest, randValue); 67 | REQUIRE(dest == randValue); 68 | } 69 | 70 | { 71 | uint32_t dest; 72 | REQUIRE_THROWS(convert_type_fundamental_or_array(dest, u32Max + 1)); 73 | } 74 | } 75 | 76 | // NOLINTNEXTLINE 77 | TEST_CASE("convert_type_fundamental_or_array for arrays operates correctly", 78 | "[convert]") 79 | { 80 | const int32_t c_arr_1[4]{ 1, 2, 3, 4 }; // NOLINT 81 | const std::array std_arr_1{ { 1, 2, 3, 4 } }; 82 | 83 | const int64_t c_arr_2[4]{ 1, 2, 3, 4 }; // NOLINT 84 | const std::array std_arr_2{ { 1, 2, 3, 4 } }; 85 | 86 | { 87 | std::array dest{}; 88 | convert_type_fundamental_or_array(dest, c_arr_1); 89 | REQUIRE(dest == std_arr_1); 90 | } 91 | 92 | { 93 | int32_t dest[4]{}; // NOLINT 94 | convert_type_fundamental_or_array(dest, std_arr_1); 95 | REQUIRE(std::memcmp(&dest, &c_arr_1, sizeof(dest)) == 0); 96 | } 97 | 98 | { 99 | std::array dest{}; 100 | convert_type_fundamental_or_array(dest, c_arr_2); 101 | REQUIRE(dest == std_arr_1); 102 | } 103 | 104 | { 105 | int32_t dest[4]{}; // NOLINT 106 | convert_type_fundamental_or_array(dest, std_arr_2); 107 | REQUIRE(std::memcmp(&dest, &c_arr_1, sizeof(dest)) == 0); 108 | } 109 | } 110 | 111 | TEST_CASE( 112 | "convert_type_fundamental_or_array checks for arrays operate correctly", 113 | "[convert]") 114 | { 115 | // NOLINTNEXTLINE 116 | const int64_t c_arr[4]{ 1, std::numeric_limits::max(), 3, 4 }; 117 | int32_t dest[4]{}; // NOLINT 118 | REQUIRE_THROWS(convert_type_fundamental_or_array(dest, c_arr)); 119 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_include.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // IWYU pragma: begin_exports 12 | #include "catch2/catch.hpp" 13 | 14 | // Convert rlbox's compile time errors to exceptions throws for easy testing 15 | #define RLBOX_NO_COMPILE_CHECKS 16 | #define RLBOX_USE_EXCEPTIONS 17 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 18 | #define RLBOX_SINGLE_THREADED_INVOCATIONS 19 | #include "rlbox.hpp" 20 | #include "rlbox_noop_sandbox.hpp" 21 | 22 | // IWYU pragma: end_exports 23 | 24 | // Since we are convert static errors to exceptions, we can now test this easily 25 | #define REQUIRE_COMPILE_ERR REQUIRE_THROWS 26 | #define REQUIRE_NO_COMPILE_ERR REQUIRE_NOTHROW 27 | 28 | #define UNUSED(...) (void)__VA_ARGS__ 29 | 30 | // The point here is to use the C types as is, so turn off the links 31 | using CallbackType = int (*)(unsigned, const char*, unsigned[1]); // NOLINT 32 | 33 | enum testBasicEnum 34 | { 35 | testBasicEnumVal1, 36 | testBasicEnumVal2 37 | }; 38 | 39 | struct testVarietyStruct 40 | { 41 | unsigned long fieldLong; // NOLINT 42 | const char* fieldString; // NOLINT 43 | unsigned int fieldBool; // NOLINT 44 | char fieldFixedArr[8]; // NOLINT 45 | int (*fieldFnPtr)(unsigned, const char*, unsigned[1]); // NOLINT 46 | struct unknownClass* fieldUnknownPtr; // NOLINT 47 | void* voidPtr; // NOLINT 48 | CallbackType fnArray[8]; // NOLINT 49 | }; 50 | 51 | namespace rlbox { 52 | class rlbox_test_sandbox; 53 | } 54 | 55 | using TestSandbox = rlbox::rlbox_test_sandbox; 56 | 57 | namespace rlbox { 58 | class rlbox_test_sandbox 59 | { 60 | private: 61 | std::pair pow2SizeAlignedMalloc(size_t size) 62 | { 63 | size_t paddedSize = size * 2 + 1; 64 | std::byte* mem = new std::byte[paddedSize]; 65 | uintptr_t memU = reinterpret_cast(mem); 66 | if ((memU & size) == 0) { 67 | return std::make_pair(mem, mem); 68 | } 69 | 70 | uintptr_t alignedMemU = memU; 71 | while ((alignedMemU & size) != 0) { 72 | alignedMemU++; 73 | } 74 | 75 | if ((alignedMemU + size) > (memU + paddedSize)) { 76 | // Unexpected error while aligning memory 77 | std::abort(); 78 | } 79 | 80 | auto alignedMem = reinterpret_cast(alignedMemU); 81 | return std::make_pair(mem, alignedMem); 82 | } 83 | 84 | size_t CurrFreeAddress = 8; // NOLINT 85 | // Some sandboxed encode functions as regular pointers, others use an 86 | // indirection table. Using an indirection table here as this is the more 87 | // complicated case 88 | mutable std::mutex function_table_mutex; 89 | mutable std::vector function_table; 90 | 91 | public: 92 | using T_LongLongType = int64_t; 93 | using T_LongType = int32_t; 94 | using T_IntType = int32_t; 95 | using T_PointerType = uint32_t; 96 | using T_ShortType = int16_t; 97 | 98 | inline static const uint32_t SandboxMemorySize = 0xFFF; 99 | inline static const uintptr_t SandboxMemoryBaseMask = 100 | ~(static_cast(SandboxMemorySize)); 101 | uintptr_t UnalignedSandboxMemory; 102 | uintptr_t SandboxMemoryBase; 103 | 104 | protected: 105 | template 106 | inline void impl_create_sandbox(T_Args...) 107 | { 108 | auto unalignedAndAligned = pow2SizeAlignedMalloc(SandboxMemorySize); 109 | UnalignedSandboxMemory = 110 | reinterpret_cast(unalignedAndAligned.first); 111 | SandboxMemoryBase = reinterpret_cast(unalignedAndAligned.second); 112 | } 113 | 114 | inline void impl_destroy_sandbox() 115 | { 116 | delete[](reinterpret_cast(UnalignedSandboxMemory)); 117 | } 118 | 119 | template 120 | inline void* impl_get_unsandboxed_pointer(T_PointerType p) const 121 | { 122 | if constexpr (std::is_function_v>) { 123 | std::lock_guard lock(function_table_mutex); 124 | return const_cast(function_table[p]); 125 | } else { 126 | return reinterpret_cast(SandboxMemoryBase + 127 | static_cast(p)); 128 | } 129 | } 130 | 131 | template 132 | inline T_PointerType impl_get_sandboxed_pointer(const void* p) const 133 | { 134 | if constexpr (std::is_function_v>) { 135 | std::lock_guard lock(function_table_mutex); 136 | int len = function_table.size(); 137 | function_table.push_back(p); 138 | return static_cast(len); 139 | } else { 140 | return static_cast(reinterpret_cast(p) - 141 | SandboxMemoryBase); 142 | } 143 | } 144 | 145 | template 146 | static inline void* impl_get_unsandboxed_pointer_no_ctx( 147 | T_PointerType p, 148 | const void* example_unsandboxed_ptr, 149 | TestSandbox* (*expensive_sandbox_finder)( 150 | const void* example_unsandboxed_ptr)) 151 | { 152 | RLBOX_DEBUG_ASSERT(example_unsandboxed_ptr != nullptr); 153 | if constexpr (std::is_function_v>) { 154 | // swizzling function pointers needs access to the function pointer tables 155 | // and thus cannot be done without context 156 | RLBOX_UNUSED(example_unsandboxed_ptr); 157 | auto sandbox = expensive_sandbox_finder(example_unsandboxed_ptr); 158 | return sandbox->impl_get_unsandboxed_pointer(p); 159 | } else { 160 | auto mask = SandboxMemoryBaseMask & 161 | reinterpret_cast(example_unsandboxed_ptr); 162 | return reinterpret_cast(mask + static_cast(p)); 163 | } 164 | } 165 | 166 | template 167 | static inline T_PointerType impl_get_sandboxed_pointer_no_ctx( 168 | const void* p, 169 | const void* example_unsandboxed_ptr, 170 | TestSandbox* (*expensive_sandbox_finder)( 171 | const void* example_unsandboxed_ptr)) 172 | { 173 | RLBOX_DEBUG_ASSERT(example_unsandboxed_ptr != nullptr); 174 | if constexpr (std::is_function_v>) { 175 | // swizzling function pointers needs access to the function pointer tables 176 | // and thus cannot be done without context 177 | RLBOX_UNUSED(example_unsandboxed_ptr); 178 | auto sandbox = expensive_sandbox_finder(example_unsandboxed_ptr); 179 | return sandbox->impl_get_sandboxed_pointer(p); 180 | } else { 181 | auto ret = SandboxMemorySize & reinterpret_cast(p); 182 | return static_cast(ret); 183 | } 184 | } 185 | 186 | inline T_PointerType impl_malloc_in_sandbox(size_t size) 187 | { 188 | auto ret = static_cast(CurrFreeAddress); 189 | // Malloc normally produces pointers aligned to sizeof(uintptr) bytes 190 | size_t roundedTo8Size = (size + 7) & ~7; 191 | CurrFreeAddress += roundedTo8Size; 192 | if (CurrFreeAddress > SandboxMemorySize) { 193 | std::abort(); 194 | } 195 | return ret; 196 | } 197 | 198 | inline void impl_free_in_sandbox(T_PointerType) {} 199 | 200 | static inline bool impl_is_in_same_sandbox(const void* p1, const void* p2) 201 | { 202 | auto mask1 = SandboxMemoryBaseMask & reinterpret_cast(p1); 203 | auto mask2 = SandboxMemoryBaseMask & reinterpret_cast(p2); 204 | return mask1 == mask2; 205 | } 206 | 207 | inline bool impl_is_pointer_in_sandbox_memory(const void* p) 208 | { 209 | auto mask = SandboxMemoryBaseMask & reinterpret_cast(p); 210 | return mask == SandboxMemoryBase; 211 | } 212 | 213 | inline bool impl_is_pointer_in_app_memory(const void* p) 214 | { 215 | return !(impl_is_pointer_in_sandbox_memory(p)); 216 | } 217 | 218 | inline size_t impl_get_total_memory() { return SandboxMemorySize; } 219 | 220 | inline void* impl_get_memory_location() 221 | { 222 | return reinterpret_cast(SandboxMemoryBase); 223 | } 224 | 225 | // adding a template so that we can use static_assert to fire only if this 226 | // function is invoked 227 | template 228 | void* impl_lookup_symbol(const char* /* func_name */) 229 | { 230 | // Will fire if this impl_lookup_symbol is ever called for the static 231 | // sandbox 232 | constexpr bool fail = std::is_same_v; 233 | rlbox_detail_static_fail_because( 234 | fail, 235 | "The no_op_sandbox uses static calls and thus developers should add\n\n" 236 | "#define RLBOX_USE_STATIC_CALLS rlbox_test_sandbox_lookup_symbol\n\n" 237 | "to their code, to ensure that static calls are handled correctly."); 238 | 239 | return nullptr; 240 | } 241 | 242 | #define rlbox_test_sandbox_lookup_symbol(func_name) \ 243 | reinterpret_cast(&func_name) /* NOLINT */ 244 | 245 | template 246 | inline T_PointerType impl_register_callback(void*, void*) 247 | { 248 | return 0; 249 | } 250 | 251 | static inline std::pair 252 | impl_get_executed_callback_sandbox_and_key() 253 | { 254 | TestSandbox* s = nullptr; 255 | void* k = nullptr; 256 | return std::make_pair(s, k); 257 | } 258 | 259 | template 260 | inline void impl_unregister_callback(void*) 261 | {} 262 | }; 263 | } 264 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_operators.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test_include.hpp" 6 | 7 | using rlbox::tainted; 8 | 9 | // NOLINTNEXTLINE 10 | TEST_CASE("Test operator + for numerics", "[operator]") 11 | { 12 | tainted a = 3; 13 | tainted b = 3 + 4; 14 | tainted c = a + 3; 15 | tainted d = 3 + a; 16 | tainted e = a + b; 17 | REQUIRE(a.UNSAFE_unverified() == 3); 18 | REQUIRE(b.UNSAFE_unverified() == 7); 19 | REQUIRE(c.UNSAFE_unverified() == 6); 20 | REQUIRE(d.UNSAFE_unverified() == 6); 21 | REQUIRE(e.UNSAFE_unverified() == 10); 22 | 23 | tainted ovWrap = std::numeric_limits::max(); 24 | ovWrap = ovWrap + 1; 25 | REQUIRE(ovWrap.UNSAFE_unverified() == 0); 26 | } 27 | 28 | struct test_tainted_struct_vals 29 | { 30 | tainted a{ 3 }; // NOLINT 31 | tainted b{ 7 }; // NOLINT 32 | }; 33 | 34 | // NOLINTNEXTLINE 35 | TEST_CASE("Test operator + with const refs", "[operator]") 36 | { 37 | test_tainted_struct_vals vals; 38 | const auto& ref = vals; 39 | REQUIRE((ref.a + ref.b).UNSAFE_unverified() == 10); 40 | } 41 | 42 | // NOLINTNEXTLINE 43 | TEST_CASE("Test compound assignment operators", "[operator]") 44 | { 45 | const int32_t a = 3; 46 | tainted b = a; 47 | tainted c = b; 48 | c += 1; 49 | tainted d = b; 50 | REQUIRE(b.UNSAFE_unverified() == a); 51 | REQUIRE(c.UNSAFE_unverified() == a + 1); 52 | REQUIRE(d.UNSAFE_unverified() == a); 53 | } 54 | 55 | // NOLINTNEXTLINE 56 | TEST_CASE("Test pre/post increment operators", "[operator]") 57 | { 58 | const int32_t val = 3; 59 | 60 | SECTION("Test pre increment") // NOLINT 61 | { 62 | int32_t a = val; 63 | int32_t b = ++a; 64 | int32_t c = a; 65 | 66 | tainted t_a = val; 67 | tainted t_b = ++t_a; 68 | tainted t_c = t_a; 69 | 70 | REQUIRE(t_a.UNSAFE_unverified() == a); 71 | REQUIRE(t_b.UNSAFE_unverified() == b); 72 | REQUIRE(t_c.UNSAFE_unverified() == c); 73 | } 74 | 75 | SECTION("Test post increment") // NOLINT 76 | { 77 | int32_t a = val; 78 | int32_t b = a++; 79 | int32_t c = a; 80 | 81 | tainted t_a = val; 82 | tainted t_b = t_a++; 83 | tainted t_c = t_a; 84 | 85 | REQUIRE(t_a.UNSAFE_unverified() == a); 86 | REQUIRE(t_b.UNSAFE_unverified() == b); 87 | REQUIRE(t_c.UNSAFE_unverified() == c); 88 | } 89 | } 90 | 91 | TEST_CASE("Test operators that produce new values for numerics", "[operator]") 92 | { 93 | const uint32_t a = 11; 94 | const uint32_t b = 17; 95 | const uint32_t c = 13; 96 | const uint32_t d = 17; 97 | const uint32_t e = 2; 98 | uint32_t r = -(((((a + b) - c) * d) / e)); 99 | 100 | tainted s_a = a; 101 | tainted s_b = b; 102 | tainted s_c = c; 103 | tainted s_d = d; 104 | tainted s_e = e; 105 | tainted s_r = -(((((s_a + s_b) - s_c) * s_d) / s_e)); 106 | 107 | REQUIRE(s_r.UNSAFE_unverified() == r); 108 | } 109 | 110 | // NOLINTNEXTLINE 111 | TEST_CASE("Test operator +, - for pointers", "[operator]") 112 | { 113 | rlbox::rlbox_sandbox sandbox; 114 | sandbox.create_sandbox(); 115 | 116 | tainted pc = sandbox.malloc_in_sandbox(); 117 | tainted inc = pc + 1; 118 | 119 | auto diff = reinterpret_cast(inc.UNSAFE_unverified()) - // NOLINT 120 | reinterpret_cast(pc.UNSAFE_unverified()); // NOLINT 121 | REQUIRE(diff == 4); 122 | 123 | tainted nullPtr = nullptr; 124 | // operation on null pointer should throw 125 | REQUIRE_THROWS(nullPtr + 1); 126 | 127 | // pointer addition overflow sandbox bounds should throw 128 | REQUIRE_THROWS(pc + TestSandbox::SandboxMemorySize); 129 | 130 | tainted dec = inc - 1; 131 | REQUIRE(pc.UNSAFE_unverified() == dec.UNSAFE_unverified()); 132 | 133 | auto pc2 = sandbox.malloc_in_sandbox(); 134 | auto inc2 = pc2 + 1; 135 | 136 | auto diff2 = reinterpret_cast(inc2.UNSAFE_unverified()) - // NOLINT 137 | reinterpret_cast(pc2.UNSAFE_unverified()); // NOLINT 138 | REQUIRE(diff2 == 1); 139 | 140 | auto pc3 = sandbox.malloc_in_sandbox(); 141 | auto inc3 = pc3 + 1; 142 | 143 | auto diff3 = reinterpret_cast(inc3.UNSAFE_unverified()) - // NOLINT 144 | reinterpret_cast(pc3.UNSAFE_unverified()); // NOLINT 145 | REQUIRE(diff3 == sizeof(TestSandbox::T_PointerType)); 146 | 147 | sandbox.destroy_sandbox(); 148 | } 149 | 150 | // NOLINTNEXTLINE 151 | TEST_CASE( 152 | "Test operators that produce new values for tainted_volatile numerics", 153 | "[operator]") 154 | { 155 | rlbox::rlbox_sandbox sandbox; 156 | sandbox.create_sandbox(); 157 | 158 | // uint64_t on 64 bit platforms is "unsigned long" which is 64 bits in the app 159 | // but long is 32-bits in our test sandbox env 160 | auto pc = sandbox.malloc_in_sandbox(); 161 | 162 | uint64_t max32Val = std::numeric_limits::max(); 163 | *pc = max32Val; 164 | 165 | const int rhs = 1; 166 | { 167 | tainted result = (*pc) + rhs; 168 | uint64_t expected_result = max32Val + rhs; 169 | REQUIRE(result.UNSAFE_unverified() == expected_result); 170 | } 171 | 172 | { 173 | tainted result = (*pc) + (*pc); 174 | uint64_t expected_result = max32Val + max32Val; 175 | REQUIRE(result.UNSAFE_unverified() == expected_result); 176 | } 177 | 178 | sandbox.destroy_sandbox(); 179 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_function_assignment.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // NOLINTNEXTLINE 4 | #define RLBOX_USE_STATIC_CALLS() rlbox_test_sandbox_lookup_symbol 5 | #include "test_include.hpp" 6 | 7 | using rlbox::tainted; 8 | using RL = rlbox::rlbox_sandbox; 9 | 10 | static int test_fn(int a) 11 | { 12 | return a; 13 | } 14 | 15 | // NOLINTNEXTLINE 16 | TEST_CASE("Test sandbox_function assignment", "[sandbox_function]") 17 | { 18 | RL sandbox; 19 | sandbox.create_sandbox(); 20 | 21 | using T_F = int (*)(int); 22 | 23 | auto ptr = sandbox.malloc_in_sandbox(); 24 | auto cb = sandbox.get_sandbox_function_address(test_fn); // NOLINT 25 | 26 | tainted val = nullptr; 27 | 28 | // Assignment to another tainted is ok 29 | REQUIRE_NO_COMPILE_ERR(val = cb); 30 | 31 | // Assignment to tainted_volatile is fine 32 | REQUIRE_NO_COMPILE_ERR(*ptr = cb); 33 | 34 | sandbox.destroy_sandbox(); 35 | } 36 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_noop_sandbox.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTNEXTLINE 2 | #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol 3 | 4 | #include "test_include.hpp" 5 | 6 | using rlbox::rlbox_noop_sandbox; 7 | using rlbox::tainted; 8 | using RL = rlbox::rlbox_sandbox; 9 | 10 | int GlobalVal = 0; 11 | static void test_func_void(int param) 12 | { 13 | GlobalVal = param; 14 | } 15 | 16 | static int test_func_int(int param) 17 | { 18 | return param; 19 | } 20 | 21 | static testBasicEnum test_func_enum(testBasicEnum val) 22 | { 23 | return val; 24 | } 25 | 26 | static void* test_func_ptr(int* ptr) 27 | { 28 | return ptr; 29 | } 30 | 31 | // NOLINTNEXTLINE 32 | TEST_CASE("invoke in no_op sandbox", "[no_op_sandbox]") 33 | { 34 | RL sandbox; 35 | sandbox.create_sandbox(); 36 | 37 | const int TestFuncVal = 3; 38 | sandbox.invoke_sandbox_function(test_func_void, TestFuncVal); // NOLINT 39 | REQUIRE(GlobalVal == TestFuncVal); // NOLINT 40 | 41 | auto result = 42 | sandbox.invoke_sandbox_function(test_func_int, TestFuncVal); // NOLINT 43 | REQUIRE(result.UNSAFE_unverified() == TestFuncVal); // NOLINT 44 | 45 | auto t = tainted(TestFuncVal); 46 | auto result2 = sandbox.invoke_sandbox_function(test_func_int, t); // NOLINT 47 | REQUIRE(result2.UNSAFE_unverified() == TestFuncVal); // NOLINT 48 | 49 | auto result3 = 50 | sandbox.invoke_sandbox_function(test_func_int, t.to_opaque()); // NOLINT 51 | REQUIRE(result3.UNSAFE_unverified() == TestFuncVal); // NOLINT 52 | 53 | auto result4 = sandbox.invoke_sandbox_function(test_func_enum, 54 | testBasicEnumVal1); // NOLINT 55 | REQUIRE(result4.UNSAFE_unverified() == testBasicEnumVal1); // NOLINT 56 | 57 | auto result5 = 58 | sandbox.invoke_sandbox_function(test_func_ptr, nullptr); // NOLINT 59 | REQUIRE(result5.UNSAFE_unverified() == nullptr); // NOLINT 60 | 61 | sandbox.destroy_sandbox(); 62 | } 63 | 64 | using T_Func_int_int = int (*)(int); 65 | 66 | static tainted test_callback( 67 | RL&, // NOLINT 68 | tainted val) 69 | { 70 | return val + 1; 71 | } 72 | 73 | static int test_invoker(T_Func_int_int cb, int val) 74 | { 75 | return (cb(val)) + 1; 76 | } 77 | 78 | // NOLINTNEXTLINE 79 | TEST_CASE("callback in no_op sandbox", "[no_op_sandbox]") 80 | { 81 | RL sandbox; 82 | sandbox.create_sandbox(); 83 | 84 | rlbox::sandbox_callback cb = 85 | sandbox.register_callback(test_callback); 86 | 87 | const int test_val = 5; 88 | tainted ret = 89 | sandbox.invoke_sandbox_function(test_invoker, cb, test_val); // NOLINT 90 | 91 | REQUIRE(ret.UNSAFE_unverified() == test_val + 2); 92 | 93 | sandbox.destroy_sandbox(); 94 | } 95 | 96 | static void simplePointerWrite(int* ptr, int val) 97 | { 98 | *ptr = val; 99 | } 100 | 101 | TEST_CASE("incremental porting operates correctly", "[port]") 102 | { 103 | RL sandbox; 104 | sandbox.create_sandbox(); 105 | 106 | int ptr_loc = 0; 107 | int* ptr = &ptr_loc; 108 | auto tainted_ptr = sandbox.UNSAFE_accept_pointer(ptr); 109 | 110 | const int test_val = 42; 111 | sandbox.invoke_sandbox_function(simplePointerWrite, tainted_ptr, test_val); 112 | 113 | REQUIRE(*ptr == test_val); 114 | REQUIRE(tainted_ptr.UNSAFE_unverified() == ptr); 115 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_noop_sandbox_invoke_fail.cpp: -------------------------------------------------------------------------------- 1 | #define RLBOX_NO_COMPILE_CHECKS 2 | #define RLBOX_USE_EXCEPTIONS 3 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 4 | #define RLBOX_SINGLE_THREADED_INVOCATIONS 5 | #include "rlbox_noop_sandbox.hpp" // IWYU pragma: keep 6 | #include "test_include.hpp" 7 | 8 | using RL = rlbox::rlbox_sandbox; 9 | 10 | static void test_func() {} 11 | 12 | // This test has to be in a separate file as it is testing the missing #define 13 | // before the #include "rlbox_noop_sandbox.hpp" 14 | // NOLINTNEXTLINE 15 | TEST_CASE("sandbox_lookup_symbol on no_op sandbox without #define causes error", 16 | "[no_op_sandbox]") 17 | { 18 | RL sandbox; 19 | sandbox.create_sandbox(); 20 | 21 | // Error due to the missing #define described above 22 | REQUIRE_COMPILE_ERR(sandbox.invoke_sandbox_function(test_func)); // NOLINT 23 | UNUSED(test_func); 24 | 25 | sandbox.destroy_sandbox(); 26 | } 27 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_ptr_conversion.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "test_include.hpp" 5 | 6 | using T_Ptr = typename TestSandbox::T_PointerType; 7 | using RL = rlbox::rlbox_sandbox; 8 | 9 | // NOLINTNEXTLINE 10 | TEST_CASE("Type get_[un]sandboxed_pointer", "[get_sandboxed]") 11 | { 12 | RL sandbox; 13 | sandbox.create_sandbox(); 14 | 15 | const T_Ptr testPointerSboxRep1 = 0xCD; 16 | const T_Ptr testPointerSboxRep2 = 0xBC; 17 | uintptr_t base = sandbox.get_sandbox_impl()->SandboxMemoryBase; 18 | 19 | // NOLINTNEXTLINE (cppcoreguidelines-pro-type-reinterpret-cast) 20 | void* testPointer1 = reinterpret_cast(base + testPointerSboxRep1); 21 | // NOLINTNEXTLINE (cppcoreguidelines-pro-type-reinterpret-cast) 22 | void* testPointer2 = reinterpret_cast(base + testPointerSboxRep2); 23 | const T_Ptr nullptrSboxRep = 0; 24 | 25 | REQUIRE(RL::get_sandboxed_pointer_no_ctx(testPointer1, testPointer2) == 26 | testPointerSboxRep1); // NOLINT 27 | REQUIRE(RL::get_unsandboxed_pointer_no_ctx( 28 | testPointerSboxRep1, testPointer2) == testPointer1); // NOLINT 29 | REQUIRE(sandbox.get_sandboxed_pointer(testPointer1) == 30 | testPointerSboxRep1); // NOLINT 31 | REQUIRE(sandbox.get_unsandboxed_pointer(testPointerSboxRep1) == 32 | testPointer1); // NOLINT 33 | 34 | REQUIRE(RL::get_sandboxed_pointer_no_ctx(nullptr, testPointer2) == 35 | nullptrSboxRep); // NOLINT 36 | REQUIRE(RL::get_unsandboxed_pointer_no_ctx( 37 | nullptrSboxRep, testPointer2) == nullptr); // NOLINT 38 | REQUIRE(sandbox.get_sandboxed_pointer(nullptr) == 39 | nullptrSboxRep); // NOLINT 40 | REQUIRE(sandbox.get_unsandboxed_pointer(nullptrSboxRep) == 41 | nullptr); // NOLINT 42 | 43 | sandbox.destroy_sandbox(); 44 | } 45 | 46 | // NOLINTNEXTLINE 47 | TEST_CASE("Type get_[un]sandboxed_pointer for const pointers", 48 | "[get_sandboxed]") 49 | { 50 | RL sandbox; 51 | sandbox.create_sandbox(); 52 | 53 | const T_Ptr testPointerSboxRep1 = 0xCD; 54 | const T_Ptr testPointerSboxRep2 = 0xBC; 55 | uintptr_t base = sandbox.get_sandbox_impl()->SandboxMemoryBase; 56 | 57 | // NOLINTNEXTLINE (cppcoreguidelines-pro-type-reinterpret-cast) 58 | auto testPointer1 = reinterpret_cast(base + testPointerSboxRep1); 59 | // NOLINTNEXTLINE (cppcoreguidelines-pro-type-reinterpret-cast) 60 | auto testPointer2 = reinterpret_cast(base + testPointerSboxRep2); 61 | 62 | REQUIRE(RL::get_sandboxed_pointer_no_ctx( 63 | testPointer1, testPointer2) == testPointerSboxRep1); // NOLINT 64 | REQUIRE(RL::get_unsandboxed_pointer_no_ctx( 65 | testPointerSboxRep1, testPointer2) == testPointer1); // NOLINT 66 | REQUIRE(sandbox.get_sandboxed_pointer(testPointer1) == 67 | testPointerSboxRep1); // NOLINT 68 | REQUIRE(sandbox.get_unsandboxed_pointer(testPointerSboxRep1) == 69 | testPointer1); // NOLINT 70 | 71 | sandbox.destroy_sandbox(); 72 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_transition_customization.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTNEXTLINE 2 | #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol 3 | #define RLBOX_USE_EXCEPTIONS 4 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 5 | namespace rlbox { 6 | enum class rlbox_transition; 7 | } 8 | void on_transition_in(rlbox::rlbox_transition transition, 9 | const char* func_name, 10 | const void* func_ptr, 11 | void* saved_state); 12 | void on_transition_out(rlbox::rlbox_transition transition, 13 | const char* func_name, 14 | const void* func_ptr, 15 | void* saved_state); 16 | #define RLBOX_TRANSITION_ACTION_IN(transition, func_name, func_ptr, state) \ 17 | on_transition_in(transition, func_name, func_ptr, state) 18 | #define RLBOX_TRANSITION_ACTION_OUT(transition, func_name, func_ptr, state) \ 19 | on_transition_out(transition, func_name, func_ptr, state) 20 | #include "test_include.hpp" 21 | 22 | using RL = rlbox::rlbox_sandbox; 23 | 24 | static int add(int a, int b) 25 | { 26 | return a + b; 27 | } 28 | 29 | static int transition_in_count = 0; 30 | static int transition_out_count = 0; 31 | 32 | void on_transition_in(rlbox::rlbox_transition transition, 33 | const char* func_name, 34 | const void* func_ptr, 35 | void* saved_state) 36 | { 37 | UNUSED(transition); 38 | UNUSED(func_name); 39 | UNUSED(func_ptr); 40 | UNUSED(saved_state); 41 | transition_in_count++; 42 | } 43 | 44 | void on_transition_out(rlbox::rlbox_transition transition, 45 | const char* func_name, 46 | const void* func_ptr, 47 | void* saved_state) 48 | { 49 | UNUSED(transition); 50 | UNUSED(func_name); 51 | UNUSED(func_ptr); 52 | UNUSED(saved_state); 53 | transition_out_count++; 54 | } 55 | 56 | // NOLINTNEXTLINE 57 | TEST_CASE("sandbox transition customization", 58 | "[sandbox_transition_customization]") 59 | { 60 | RL sandbox; 61 | sandbox.create_sandbox(); 62 | 63 | transition_in_count = 0; 64 | transition_out_count = 0; 65 | 66 | const int val1 = 2; 67 | const int val2 = 3; 68 | sandbox.invoke_sandbox_function(add, val1, val2); 69 | REQUIRE(transition_in_count == 1); 70 | REQUIRE(transition_out_count == 1); 71 | 72 | sandbox.destroy_sandbox(); 73 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_transition_timings.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTNEXTLINE 2 | #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol 3 | #define RLBOX_USE_EXCEPTIONS 4 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 5 | #define RLBOX_MEASURE_TRANSITION_TIMES 6 | #include "rlbox_noop_sandbox.hpp" 7 | #include "test_include.hpp" 8 | 9 | #include 10 | #include 11 | 12 | using RL = rlbox::rlbox_sandbox; 13 | 14 | static int add(int a, int b) 15 | { 16 | return a + b; 17 | } 18 | 19 | // NOLINTNEXTLINE 20 | TEST_CASE("sandbox timing tests", "[sandbox_timing_tests]") 21 | { 22 | RL sandbox; 23 | sandbox.create_sandbox(); 24 | 25 | SECTION("Per function test") // NOLINT 26 | { 27 | const int iterations = 10; 28 | const int val1 = 2; 29 | const int val2 = 3; 30 | for (int i = 0; i < iterations; i++) { 31 | auto result1 = sandbox.invoke_sandbox_function(add, val1, val2) 32 | .unverified_safe_because("test"); 33 | REQUIRE(result1 == val1 + val2); 34 | } 35 | 36 | auto& transition_times = sandbox.process_and_get_transition_times(); 37 | REQUIRE(transition_times.size() == iterations); 38 | REQUIRE(sandbox.get_total_ns_time_in_sandbox_and_transitions() > 0); 39 | } 40 | 41 | sandbox.destroy_sandbox(); 42 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_sandbox_types.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | 3 | RLBOX_DEFINE_BASE_TYPES_FOR(testlib, test); 4 | 5 | // NOLINTNEXTLINE 6 | TEST_CASE("Test rlbox type macro", "[rlbox types]") 7 | { 8 | rlbox_sandbox_testlib sandbox; 9 | sandbox.create_sandbox(); 10 | 11 | tainted_testlib a = 4; 12 | 13 | sandbox.destroy_sandbox(); 14 | 15 | // This is a compile time test so no requires. 16 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_stdlib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "test_include.hpp" 9 | 10 | using rlbox::memset; 11 | using rlbox::tainted; 12 | 13 | // NOLINTNEXTLINE 14 | TEST_CASE("test sandbox_reinterpret_cast", "[stdlib]") 15 | { 16 | rlbox::rlbox_sandbox sandbox; 17 | sandbox.create_sandbox(); 18 | 19 | const auto testVal = 0xAB; 20 | auto ptr = sandbox.malloc_in_sandbox(); 21 | *ptr = testVal; 22 | auto ptr2 = rlbox::sandbox_reinterpret_cast(ptr); 23 | 24 | REQUIRE(std::is_same_v>); 25 | REQUIRE(std::is_same_v>); 26 | REQUIRE(ptr2->UNSAFE_unverified() == testVal); 27 | 28 | sandbox.free_in_sandbox(ptr); 29 | sandbox.destroy_sandbox(); 30 | } 31 | 32 | // NOLINTNEXTLINE 33 | TEST_CASE("test sandbox_const_cast", "[stdlib]") 34 | { 35 | rlbox::rlbox_sandbox sandbox; 36 | sandbox.create_sandbox(); 37 | 38 | const auto testVal = 0xAB; 39 | auto ptr = sandbox.malloc_in_sandbox(); 40 | auto ptr2 = rlbox::sandbox_const_cast(ptr); 41 | *ptr2 = testVal; 42 | 43 | REQUIRE(std::is_same_v>); 44 | REQUIRE(std::is_same_v>); 45 | REQUIRE(ptr->UNSAFE_unverified() == testVal); 46 | 47 | sandbox.destroy_sandbox(); 48 | } 49 | 50 | // NOLINTNEXTLINE 51 | TEST_CASE("test sandbox_static_cast", "[stdlib]") 52 | { 53 | const uint64_t a = std::numeric_limits::max(); 54 | const auto b = static_cast(a); 55 | tainted t_a = a; 56 | auto t_b = rlbox::sandbox_static_cast(t_a); 57 | REQUIRE(std::is_same_v>); 58 | REQUIRE(b == t_b.UNSAFE_unverified()); 59 | } 60 | 61 | // NOLINTNEXTLINE 62 | TEST_CASE("test memset", "[stdlib]") 63 | { 64 | rlbox::rlbox_sandbox sandbox; 65 | sandbox.create_sandbox(); 66 | 67 | auto initVal = sandbox.malloc_in_sandbox(12); // NOLINT 68 | auto fifth = initVal + 4; 69 | 70 | const uint32_t max32Val = 0xFFFFFFFF; 71 | 72 | // Memset with untainted val and untainted size 73 | for (int i = 0; i < 12; i++) { // NOLINT 74 | *(initVal + i) = max32Val; 75 | } 76 | memset(sandbox, fifth, 0, sizeof(tainted) * 4); 77 | 78 | for (int i = 0; i < 4; i++) { // NOLINT 79 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == max32Val); 80 | } 81 | for (int i = 4; i < 8; i++) { // NOLINT 82 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == 0); 83 | } 84 | for (int i = 8; i < 12; i++) { // NOLINT 85 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == max32Val); 86 | } 87 | 88 | // Memset with tainted val and untainted size 89 | tainted val = 0; 90 | for (int i = 0; i < 12; i++) { // NOLINT 91 | *(initVal + i) = max32Val; 92 | } 93 | memset(sandbox, fifth, val, sizeof(tainted) * 4); 94 | 95 | for (int i = 0; i < 4; i++) { // NOLINT 96 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == max32Val); 97 | } 98 | for (int i = 4; i < 8; i++) { // NOLINT 99 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == 0); 100 | } 101 | for (int i = 8; i < 12; i++) { // NOLINT 102 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == max32Val); 103 | } 104 | 105 | // Memset with tainted val and untainted size 106 | tainted size = 107 | sizeof(tainted) * 4; 108 | for (int i = 0; i < 12; i++) { // NOLINT 109 | *(initVal + i) = max32Val; 110 | } 111 | memset(sandbox, fifth, val, size); 112 | 113 | for (int i = 0; i < 4; i++) { // NOLINT 114 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == max32Val); 115 | } 116 | for (int i = 4; i < 8; i++) { // NOLINT 117 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == 0); 118 | } 119 | for (int i = 8; i < 12; i++) { // NOLINT 120 | REQUIRE(*((initVal + i).UNSAFE_unverified()) == max32Val); 121 | } 122 | 123 | sandbox.destroy_sandbox(); 124 | } 125 | 126 | // NOLINTNEXTLINE 127 | TEST_CASE("test memcpy", "[stdlib]") 128 | { 129 | rlbox::rlbox_sandbox sandbox; 130 | sandbox.create_sandbox(); 131 | 132 | const uint32_t max32Val = 0xFFFFFFFF; 133 | 134 | auto dest = sandbox.malloc_in_sandbox(12); // NOLINT 135 | auto dest_fifth = dest + 4; 136 | 137 | // tainted src 138 | for (int i = 0; i < 12; i++) { // NOLINT 139 | *(dest + i) = 0; 140 | } 141 | auto src = sandbox.malloc_in_sandbox(12); // NOLINT 142 | auto src_fifth = src + 4; 143 | for (int i = 0; i < 12; i++) { // NOLINT 144 | *(src + i) = max32Val; 145 | } 146 | memcpy(sandbox, 147 | dest_fifth, 148 | src_fifth, 149 | sizeof(tainted) * 4); 150 | for (int i = 0; i < 4; i++) { // NOLINT 151 | REQUIRE(*((dest + i).UNSAFE_unverified()) == 0); 152 | } 153 | for (int i = 4; i < 8; i++) { // NOLINT 154 | REQUIRE(*((dest + i).UNSAFE_unverified()) == max32Val); 155 | } 156 | for (int i = 8; i < 12; i++) { // NOLINT 157 | REQUIRE(*((dest + i).UNSAFE_unverified()) == 0); 158 | } 159 | 160 | // untainted src 161 | unsigned int* src2 = new unsigned int[12]; // NOLINT 162 | auto src2_fifth = src2 + 4; // NOLINT 163 | for (int i = 0; i < 12; i++) { // NOLINT 164 | *(src2 + i) = max32Val; // NOLINT 165 | } 166 | memcpy(sandbox, 167 | dest_fifth, 168 | src2_fifth, 169 | sizeof(tainted) * 4); 170 | for (int i = 0; i < 4; i++) { // NOLINT 171 | REQUIRE(*((dest + i).UNSAFE_unverified()) == 0); 172 | } 173 | for (int i = 4; i < 8; i++) { // NOLINT 174 | REQUIRE(*((dest + i).UNSAFE_unverified()) == max32Val); 175 | } 176 | for (int i = 8; i < 12; i++) { // NOLINT 177 | REQUIRE(*((dest + i).UNSAFE_unverified()) == 0); 178 | } 179 | 180 | delete[] src2; // NOLINT 181 | sandbox.free_in_sandbox(src); 182 | sandbox.free_in_sandbox(dest); 183 | 184 | sandbox.destroy_sandbox(); 185 | } 186 | 187 | static int normalize(int a) 188 | { 189 | if (a > 0) { 190 | return 1; 191 | } else if (a < 0) { 192 | return -1; 193 | } 194 | return 0; 195 | } 196 | 197 | // NOLINTNEXTLINE 198 | TEST_CASE("test memcmp", "[stdlib]") 199 | { 200 | rlbox::rlbox_sandbox sandbox; 201 | sandbox.create_sandbox(); 202 | 203 | const char* buffer1 = "abcd"; 204 | const char* buffer2 = "abCD"; 205 | 206 | const uint32_t max_length = 100; 207 | auto buffer1_t = sandbox.malloc_in_sandbox(max_length); // NOLINT 208 | auto buffer2_t = sandbox.malloc_in_sandbox(max_length); // NOLINT 209 | 210 | std::strncpy(buffer1_t.UNSAFE_unverified(), buffer1, max_length); 211 | std::strncpy(buffer2_t.UNSAFE_unverified(), buffer2, max_length); 212 | 213 | auto b1b1 = std::memcmp(buffer1, buffer1, strlen(buffer1)); 214 | auto b1b2 = std::memcmp(buffer1, buffer2, strlen(buffer1)); 215 | auto b2b1 = std::memcmp(buffer2, buffer1, strlen(buffer1)); 216 | 217 | // NOLINTNEXTLINE 218 | auto tb1b1 = rlbox::memcmp(sandbox, buffer1_t, buffer1, strlen(buffer1)) 219 | .unverified_safe_because("test"); 220 | // NOLINTNEXTLINE 221 | auto tb1b2 = rlbox::memcmp(sandbox, buffer1_t, buffer2, strlen(buffer1)) 222 | .unverified_safe_because("test"); 223 | // NOLINTNEXTLINE 224 | auto tb2b1 = rlbox::memcmp(sandbox, buffer2_t, buffer1, strlen(buffer1)) 225 | .unverified_safe_because("test"); 226 | 227 | // NOLINTNEXTLINE 228 | auto tb1tb1 = rlbox::memcmp(sandbox, buffer1_t, buffer1_t, strlen(buffer1)) 229 | .unverified_safe_because("test"); 230 | // NOLINTNEXTLINE 231 | auto tb1tb2 = rlbox::memcmp(sandbox, buffer1_t, buffer2_t, strlen(buffer1)) 232 | .unverified_safe_because("test"); 233 | // NOLINTNEXTLINE 234 | auto tb2tb1 = rlbox::memcmp(sandbox, buffer2_t, buffer1_t, strlen(buffer1)) 235 | .unverified_safe_because("test"); 236 | 237 | b1b1 = normalize(b1b1); 238 | b1b2 = normalize(b1b2); 239 | b2b1 = normalize(b2b1); 240 | tb1b1 = normalize(tb1b1); 241 | tb1b2 = normalize(tb1b2); 242 | tb2b1 = normalize(tb2b1); 243 | tb1tb1 = normalize(tb1tb1); 244 | tb1tb2 = normalize(tb1tb2); 245 | tb2tb1 = normalize(tb2tb1); 246 | 247 | REQUIRE(b1b1 == tb1b1); 248 | REQUIRE(b1b2 == tb1b2); 249 | REQUIRE(b2b1 == tb2b1); 250 | 251 | REQUIRE(b1b1 == tb1tb1); 252 | REQUIRE(b1b2 == tb1tb2); 253 | REQUIRE(b2b1 == tb2tb1); 254 | 255 | sandbox.free_in_sandbox(buffer1_t); 256 | sandbox.free_in_sandbox(buffer2_t); 257 | 258 | sandbox.destroy_sandbox(); 259 | } 260 | 261 | // NOLINTNEXTLINE 262 | TEST_CASE("test grant deny access single", "[stdlib]") 263 | { 264 | rlbox::rlbox_sandbox sandbox; 265 | sandbox.create_sandbox(); 266 | 267 | char* src = static_cast(malloc(sizeof(char))); // NOLINT 268 | const char test_val = 42; 269 | *src = test_val; 270 | 271 | bool used_copy = false; 272 | 273 | auto transfered = 274 | rlbox::copy_memory_or_grant_access(sandbox, src, 1, true, used_copy); 275 | REQUIRE((*transfered == test_val).unverified_safe_because("test")); 276 | 277 | auto transfered2 = 278 | rlbox::copy_memory_or_deny_access(sandbox, transfered, 1, true, used_copy); 279 | REQUIRE(*transfered2 == test_val); 280 | 281 | free(transfered2); 282 | 283 | sandbox.destroy_sandbox(); 284 | } 285 | 286 | // NOLINTNEXTLINE 287 | TEST_CASE("test grant deny access many", "[stdlib]") 288 | { 289 | rlbox::rlbox_sandbox sandbox; 290 | sandbox.create_sandbox(); 291 | 292 | float* src = static_cast(malloc(2 * sizeof(float))); // NOLINT 293 | const float test_val1 = 42; 294 | const float test_val2 = 43; 295 | src[0] = test_val1; 296 | src[1] = test_val2; 297 | 298 | bool used_copy = false; 299 | 300 | auto transfered = 301 | rlbox::copy_memory_or_grant_access(sandbox, src, 2, true, used_copy); 302 | REQUIRE((transfered[0] == test_val1).unverified_safe_because("test")); 303 | REQUIRE((transfered[1] == test_val2).unverified_safe_because("test")); 304 | 305 | auto transfered2 = 306 | rlbox::copy_memory_or_deny_access(sandbox, transfered, 2, true, used_copy); 307 | REQUIRE(transfered2[0] == test_val1); 308 | REQUIRE(transfered2[1] == test_val2); 309 | 310 | free(transfered2); 311 | 312 | sandbox.destroy_sandbox(); 313 | } 314 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_tainted_assignment.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test_include.hpp" 6 | 7 | using rlbox::tainted; 8 | using rlbox::tainted_volatile; 9 | 10 | // NOLINTNEXTLINE 11 | TEST_CASE("tainted assignment operates correctly", "[tainted_assignment]") 12 | { 13 | const int RandomVal1 = 4; 14 | const int RandomVal2 = 5; 15 | 16 | // Avoid warnings about uninitialized var 17 | tainted a; // NOLINT 18 | a = RandomVal1; 19 | tainted b = RandomVal2; 20 | tainted c = b; 21 | tainted d; // NOLINT 22 | d = b; 23 | const float FloatVal1 = 2.4; 24 | tainted e = 2.4; // NOLINT 25 | REQUIRE(a.UNSAFE_unverified() == RandomVal1); // NOLINT 26 | REQUIRE(b.UNSAFE_unverified() == RandomVal2); // NOLINT 27 | REQUIRE(c.UNSAFE_unverified() == RandomVal2); // NOLINT 28 | REQUIRE(d.UNSAFE_unverified() == RandomVal2); // NOLINT 29 | REQUIRE(e.UNSAFE_unverified() == FloatVal1); // NOLINT 30 | } 31 | 32 | // NOLINTNEXTLINE 33 | TEST_CASE("tainted_volatile assignment operates correctly", 34 | "[tainted_assignment]") 35 | { 36 | rlbox::rlbox_sandbox sandbox; 37 | sandbox.create_sandbox(); 38 | 39 | // On 64 bit platforms, "unsigned long" is 64 bits in the app 40 | // but unsigned long is 32-bits in our test sandbox env 41 | // NOLINTNEXTLINE(google-runtime-int) 42 | auto pc = sandbox.malloc_in_sandbox(); 43 | 44 | // Only run this test for platforms where unsigned long is 64 bits 45 | // NOLINTNEXTLINE(google-runtime-int) 46 | if constexpr (sizeof(unsigned long) == sizeof(uint64_t)) { 47 | uint64_t max32Val = std::numeric_limits::max(); 48 | *pc = max32Val; 49 | 50 | REQUIRE((*pc).UNSAFE_unverified() == max32Val); 51 | REQUIRE(pc->UNSAFE_unverified() == max32Val); 52 | 53 | uint64_t max64Val = std::numeric_limits::max(); 54 | REQUIRE_THROWS(*pc = max64Val); 55 | } 56 | 57 | sandbox.destroy_sandbox(); 58 | } 59 | 60 | // NOLINTNEXTLINE 61 | TEST_CASE("tainted tainted_volatile conversion operates correctly", 62 | "[tainted_assignment]") 63 | { 64 | rlbox::rlbox_sandbox sandbox; 65 | sandbox.create_sandbox(); 66 | 67 | auto ptr = sandbox.malloc_in_sandbox(); 68 | REQUIRE(std::is_same_v>); 69 | REQUIRE(ptr.UNSAFE_unverified() != nullptr); 70 | 71 | auto& val = *ptr; 72 | REQUIRE( 73 | std::is_same_v&>); 74 | REQUIRE( 75 | std::is_same_v>); 76 | 77 | REQUIRE( 78 | std::is_same_v>); 79 | REQUIRE( 80 | std::is_same_v>); 81 | 82 | tainted ptr2 = 83 | sandbox.malloc_in_sandbox(); 84 | auto& deref = *ptr2; 85 | REQUIRE( 86 | std::is_same_v&>); 87 | REQUIRE( 88 | std::is_same_v&>); 89 | 90 | REQUIRE( 91 | std::is_same_v&>); 92 | 93 | sandbox.destroy_sandbox(); 94 | } 95 | 96 | // NOLINTNEXTLINE 97 | TEST_CASE("tainted pointer assignments", "[tainted_assignment]") 98 | { 99 | rlbox::rlbox_sandbox sandbox; 100 | sandbox.create_sandbox(); 101 | 102 | tainted pa = nullptr; 103 | pa = nullptr; 104 | tainted pb = nullptr; 105 | pb = nullptr; 106 | 107 | tainted pc = sandbox.malloc_in_sandbox(); 108 | *pc = nullptr; 109 | pb = *pc; 110 | 111 | tainted pv = sandbox.malloc_in_sandbox(); 112 | *pv = nullptr; 113 | 114 | sandbox.destroy_sandbox(); 115 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_tainted_opaque.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "test_include.hpp" 4 | 5 | using rlbox::sandbox_reinterpret_cast; 6 | using rlbox::tainted; 7 | using rlbox::tainted_opaque; 8 | 9 | #include "test_tainted_structs.hpp" 10 | 11 | // NOLINTNEXTLINE 12 | TEST_CASE("tainted opaque operates correctly", "[tainted_opaque]") 13 | { 14 | const int test_val = 5; 15 | tainted a = test_val; 16 | tainted_opaque b = a.to_opaque(); 17 | auto c = rlbox::from_opaque(b); 18 | REQUIRE(c.UNSAFE_unverified() == test_val); 19 | 20 | b.set_zero(); 21 | REQUIRE(rlbox::from_opaque(b).UNSAFE_unverified() == 0); 22 | 23 | rlbox::rlbox_sandbox sandbox; 24 | sandbox.create_sandbox(); 25 | 26 | const auto fieldLong = 7; 27 | const auto strSize = 10; 28 | auto fieldString = sandbox.malloc_in_sandbox(strSize); 29 | std::strncpy(fieldString.UNSAFE_unverified(), "Hello", strSize); 30 | const auto fieldBool = 1; 31 | 32 | tainted s{}; 33 | s.fieldLong = fieldLong; 34 | s.fieldString = sandbox_reinterpret_cast(fieldString); 35 | s.fieldBool = fieldBool; 36 | // char* temp = (&(s.fieldFixedArr[0]))->UNSAFE_unverified(); 37 | // std::strncpy(temp, "Bye", sizeof(s.fieldFixedArr)); 38 | s.voidPtr = nullptr; 39 | 40 | tainted_opaque s2 = s.to_opaque(); 41 | auto s3 = rlbox::from_opaque(s2); 42 | 43 | REQUIRE(s3.fieldLong.UNSAFE_unverified() == fieldLong); 44 | REQUIRE(std::strcmp(s3.fieldString.UNSAFE_unverified(), "Hello") == 0); 45 | REQUIRE(s3.fieldBool.UNSAFE_unverified() == fieldBool); 46 | // auto fixedArr = s3.fieldFixedArr.UNSAFE_unverified(); 47 | // REQUIRE(std::strcmp(&fixedArr[0], "Bye") == 0); 48 | 49 | tainted voidPtr = s3.voidPtr; 50 | REQUIRE(voidPtr == nullptr); 51 | REQUIRE(s3.voidPtr.UNSAFE_unverified() == nullptr); 52 | REQUIRE(voidPtr.UNSAFE_unverified() == nullptr); 53 | 54 | tainted stringPtr = s3.fieldString; 55 | tainted_opaque stringOpaquePtr = 56 | stringPtr.to_opaque(); 57 | stringOpaquePtr.set_zero(); 58 | REQUIRE(rlbox::from_opaque(stringOpaquePtr).UNSAFE_unverified() == nullptr); 59 | 60 | sandbox.destroy_sandbox(); 61 | } 62 | 63 | // NOLINTNEXTLINE 64 | TEST_CASE("tainted opaque free operates correctly", "[tainted_opaque]") 65 | { 66 | rlbox::rlbox_sandbox sandbox; 67 | sandbox.create_sandbox(); 68 | auto fieldString = sandbox.malloc_in_sandbox(1).to_opaque(); 69 | sandbox.free_in_sandbox(fieldString); 70 | sandbox.destroy_sandbox(); 71 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_tainted_sizes.cpp: -------------------------------------------------------------------------------- 1 | #include "test_include.hpp" 2 | #include "test_tainted_structs.hpp" 3 | 4 | using rlbox::tainted; 5 | using rlbox::tainted_volatile; 6 | 7 | template 8 | using T_Convert = 9 | rlbox::detail::convert_to_sandbox_equivalent_t; 10 | 11 | // NOLINTNEXTLINE 12 | TEST_CASE("Tainted sizes work as expected", "[tainted_size]") 13 | { 14 | tainted a{}; 15 | UNUSED(a); 16 | REQUIRE(sizeof(tainted) == 17 | sizeof(long long)); // NOLINT 18 | REQUIRE(sizeof(tainted) == sizeof(long)); // NOLINT 19 | REQUIRE(sizeof(tainted) == sizeof(int)); // NOLINT 20 | REQUIRE(sizeof(tainted) == sizeof(void*)); // NOLINT 21 | REQUIRE(sizeof(tainted) == 22 | sizeof(testVarietyStruct)); // NOLINT 23 | REQUIRE(sizeof(tainted) == 24 | sizeof(testVarietyStruct*)); // NOLINT 25 | 26 | REQUIRE(sizeof(tainted_volatile) == 27 | sizeof(TestSandbox::T_LongLongType)); // NOLINT 28 | REQUIRE(sizeof(tainted_volatile) == 29 | sizeof(TestSandbox::T_LongType)); // NOLINT 30 | REQUIRE(sizeof(tainted_volatile) == 31 | sizeof(TestSandbox::T_IntType)); // NOLINT 32 | REQUIRE(sizeof(tainted_volatile) == 33 | sizeof(TestSandbox::T_ShortType)); // NOLINT 34 | REQUIRE(sizeof(tainted_volatile) == 35 | sizeof(TestSandbox::T_PointerType)); // NOLINT 36 | REQUIRE(sizeof(tainted_volatile) == 37 | sizeof(T_Convert)); // NOLINT 38 | REQUIRE(sizeof(tainted_volatile) == 39 | sizeof(T_Convert)); // NOLINT 40 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_tainted_structs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_include.hpp" 7 | #include "test_tainted_structs.hpp" 8 | 9 | using rlbox::sandbox_reinterpret_cast; 10 | using rlbox::tainted; 11 | 12 | // NOLINTNEXTLINE 13 | TEST_CASE("Tainted struct assignment", "[tainted_struct]") 14 | { 15 | rlbox::rlbox_sandbox sandbox; 16 | sandbox.create_sandbox(); 17 | 18 | const auto fieldLong = 7; 19 | const auto strSize = 10; 20 | auto fieldString = sandbox.malloc_in_sandbox(strSize); 21 | std::strncpy(fieldString.UNSAFE_unverified(), "Hello", strSize); 22 | const auto fieldBool = 1; 23 | 24 | tainted s{}; 25 | s.fieldLong = fieldLong; 26 | s.fieldString = sandbox_reinterpret_cast(fieldString); 27 | s.fieldBool = fieldBool; 28 | char* temp = reinterpret_cast(&(s.fieldFixedArr[0])); // NOLINT 29 | std::strncpy(temp, "Bye", sizeof(s.fieldFixedArr)); 30 | s.voidPtr = nullptr; 31 | 32 | REQUIRE(s.fieldLong.UNSAFE_unverified() == fieldLong); 33 | REQUIRE(std::strcmp(s.fieldString.UNSAFE_unverified(), "Hello") == 0); 34 | REQUIRE(s.fieldBool.UNSAFE_unverified() == fieldBool); 35 | auto fixedArr = s.fieldFixedArr.UNSAFE_unverified(); 36 | REQUIRE(std::strcmp(&fixedArr[0], "Bye") == 0); 37 | 38 | tainted voidPtr = s.voidPtr; 39 | REQUIRE(voidPtr == nullptr); 40 | REQUIRE(s.voidPtr.UNSAFE_unverified() == nullptr); 41 | REQUIRE(voidPtr.UNSAFE_unverified() == nullptr); 42 | 43 | sandbox.destroy_sandbox(); 44 | } 45 | 46 | // NOLINTNEXTLINE 47 | TEST_CASE("Tainted struct pointer assignment", "[tainted_struct]") 48 | { 49 | rlbox::rlbox_sandbox sandbox; 50 | sandbox.create_sandbox(); 51 | 52 | const auto fieldLong = 7; 53 | const auto strSize = 10; 54 | auto fieldString = sandbox.malloc_in_sandbox(strSize); 55 | std::strncpy(fieldString.UNSAFE_unverified(), "Hello", strSize); 56 | const auto fieldBool = 1; 57 | 58 | auto ps = sandbox.malloc_in_sandbox(); 59 | ps->fieldLong = fieldLong; 60 | ps->fieldString = sandbox_reinterpret_cast(fieldString); 61 | ps->fieldBool = fieldBool; 62 | tainted arrayAddr = 63 | sandbox_reinterpret_cast(&(ps->fieldFixedArr)); 64 | std::strncpy(arrayAddr.UNSAFE_unverified(), "Bye", sizeof(ps->fieldFixedArr)); 65 | ps->voidPtr = nullptr; 66 | 67 | REQUIRE(ps->fieldLong.UNSAFE_unverified() == fieldLong); 68 | REQUIRE(std::strcmp(ps->fieldString.UNSAFE_unverified(), "Hello") == 0); 69 | REQUIRE(ps->fieldBool.UNSAFE_unverified() == fieldBool); 70 | auto fixedArr = ps->fieldFixedArr.UNSAFE_unverified(); 71 | REQUIRE(std::strcmp(&fixedArr[0], "Bye") == 0); 72 | 73 | tainted voidPtr = ps->voidPtr; 74 | REQUIRE(voidPtr == nullptr); 75 | REQUIRE(ps->voidPtr.UNSAFE_unverified() == nullptr); 76 | REQUIRE(voidPtr.UNSAFE_unverified() == nullptr); 77 | 78 | sandbox.free_in_sandbox(ps->fieldString); 79 | 80 | // Disabled until function pointers are handled correctly 81 | // tainted s = *ps; 82 | // UNUSED(s); 83 | 84 | sandbox.destroy_sandbox(); 85 | } 86 | 87 | // NOLINTNEXTLINE 88 | TEST_CASE("Tainted const structs", "[tainted_struct]") 89 | { 90 | rlbox::rlbox_sandbox sandbox; 91 | sandbox.create_sandbox(); 92 | 93 | const auto fieldLong = 7; 94 | 95 | auto ps = sandbox.malloc_in_sandbox(); 96 | ps->fieldLong = fieldLong; 97 | auto cps = rlbox::sandbox_const_cast(ps); 98 | REQUIRE(std::is_same_v>); 100 | REQUIRE(cps->fieldLong.UNSAFE_unverified() == fieldLong); 101 | 102 | sandbox.destroy_sandbox(); 103 | } 104 | 105 | // NOLINTNEXTLINE 106 | TEST_CASE("Tainted full struct assignment", "[tainted_struct]") 107 | { 108 | rlbox::rlbox_sandbox sandbox; 109 | sandbox.create_sandbox(); 110 | 111 | auto p = sandbox.malloc_in_sandbox(); 112 | tainted o{}; 113 | *p = o; 114 | sandbox.free_in_sandbox(p); 115 | 116 | sandbox.destroy_sandbox(); 117 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_tainted_structs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test_include.hpp" 4 | #include "testing_structs_for_cpp_api.h" 5 | rlbox_load_structs_from_library(testing); 6 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_type_traits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test_include.hpp" 6 | 7 | using rlbox::detail::all_extents_same; 8 | using rlbox::detail::is_c_or_std_array_v; 9 | using rlbox::detail::std_array_to_c_arr_t; 10 | 11 | // NOLINTNEXTLINE 12 | TEST_CASE("all_extents_same", "[type_traits]") 13 | { 14 | REQUIRE(all_extents_same); // NOLINT 15 | REQUIRE(!all_extents_same); // NOLINT 16 | REQUIRE(all_extents_same); // NOLINT 17 | REQUIRE(!all_extents_same); // NOLINT 18 | REQUIRE(!all_extents_same); // NOLINT 19 | REQUIRE(!all_extents_same); // NOLINT 20 | } 21 | 22 | template 23 | struct W 24 | {}; 25 | 26 | // NOLINTNEXTLINE 27 | TEST_CASE("is_c_or_std_array_v", "[type_traits]") 28 | { 29 | REQUIRE(is_c_or_std_array_v); // NOLINT 30 | REQUIRE(is_c_or_std_array_v>); // NOLINT 31 | REQUIRE(is_c_or_std_array_v>); // NOLINT 32 | REQUIRE(is_c_or_std_array_v[4]>); // NOLINT 33 | REQUIRE(!is_c_or_std_array_v); // NOLINT 34 | REQUIRE(!is_c_or_std_array_v>); // NOLINT 35 | REQUIRE(is_c_or_std_array_v[4]>); // NOLINT 36 | } 37 | 38 | // NOLINTNEXTLINE 39 | TEST_CASE("std_array_to_c_arr_t", "[type_traits]") 40 | { 41 | REQUIRE( 42 | std::is_same_v>, int[4]>); // NOLINT 43 | REQUIRE(std::is_same_v, int>); // NOLINT 44 | REQUIRE(std::is_same_v, int[4]>); // NOLINT 45 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_verification.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test_include.hpp" 6 | 7 | using rlbox::tainted; 8 | using rlbox::tainted_boolean_hint; 9 | using rlbox::tainted_int_hint; 10 | 11 | // NOLINTNEXTLINE 12 | TEST_CASE("RLBox test basic verification", "[verification]") 13 | { 14 | const auto testVal = 5; 15 | const auto lb = 0; 16 | const auto ub = 10; 17 | 18 | tainted test = testVal; 19 | auto result = test.copy_and_verify( 20 | [&](int val) { return val > lb && val < ub ? val : -1; }); 21 | REQUIRE(result == 5); 22 | } 23 | 24 | // NOLINTNEXTLINE 25 | TEST_CASE("RLBox test enum verification", "[verification]") 26 | { 27 | using Example_Enum = enum { 28 | ENUM_UNKNOWN, 29 | ENUM_FIRST, 30 | ENUM_SECOND, 31 | ENUM_THIRD 32 | }; 33 | 34 | tainted ref = ENUM_FIRST; 35 | auto enumVal = ref.copy_and_verify( 36 | [](Example_Enum val) { return val <= ENUM_THIRD ? val : ENUM_UNKNOWN; }); 37 | UNUSED(enumVal); 38 | } 39 | 40 | // NOLINTNEXTLINE 41 | TEST_CASE("RLBox test pointer verification", "[verification]") 42 | { 43 | const auto testVal = 5; 44 | const auto lb = 0; 45 | const auto ub = 10; 46 | 47 | rlbox::rlbox_sandbox sandbox; 48 | sandbox.create_sandbox(); 49 | 50 | tainted pa = sandbox.malloc_in_sandbox(); 51 | *pa = testVal; 52 | 53 | auto result1 = pa.copy_and_verify([&](std::unique_ptr val) { 54 | return *val > lb && *val < ub ? std::move(val) : nullptr; 55 | }); 56 | REQUIRE(result1 != nullptr); 57 | REQUIRE(*result1 == testVal); 58 | 59 | auto result2 = pa.copy_and_verify_address([](uintptr_t val) { return val; }); 60 | REQUIRE(pa.UNSAFE_unverified() == reinterpret_cast(result2)); // NOLINT 61 | 62 | sandbox.destroy_sandbox(); 63 | } 64 | 65 | // NOLINTNEXTLINE 66 | TEST_CASE("RLBox test function pointer verification", "[verification]") 67 | { 68 | using T_Func = int (*)(int); 69 | 70 | rlbox::rlbox_sandbox sandbox; 71 | sandbox.create_sandbox(); 72 | 73 | tainted a = nullptr; 74 | REQUIRE(a.UNSAFE_unverified() == nullptr); 75 | REQUIRE_COMPILE_ERR( 76 | a.copy_and_verify([](std::unique_ptr val) { return val; })); 77 | REQUIRE(a.copy_and_verify_address([](uintptr_t val) { 78 | return reinterpret_cast(val); // NOLINT 79 | }) == nullptr); 80 | 81 | // Disabled until function pointers are handled correctly 82 | // auto b = sandbox.malloc_in_sandbox(); 83 | // *b = a; 84 | 85 | sandbox.destroy_sandbox(); 86 | } 87 | 88 | // NOLINTNEXTLINE 89 | TEST_CASE("RLBox tainted hint verification", "[verification]") 90 | { 91 | tainted_boolean_hint a = true; 92 | REQUIRE_COMPILE_ERR(a.copy_and_verify([](bool val) { return val; })); 93 | 94 | tainted_int_hint b = 1; 95 | REQUIRE_COMPILE_ERR(b.copy_and_verify()); 96 | } 97 | 98 | // NOLINTNEXTLINE 99 | TEST_CASE("RLBox test unverified value", "[verification]") 100 | { 101 | const auto testVal = 5; 102 | tainted test = testVal; 103 | auto result1 = test.UNSAFE_unverified(); 104 | auto result2 = test.copy_and_verify([](int val) { return val; }); 105 | auto result3 = test.unverified_safe_because("Reason: testing"); 106 | REQUIRE(result1 == testVal); 107 | REQUIRE(result2 == testVal); 108 | REQUIRE(result3 == testVal); 109 | } 110 | 111 | // NOLINTNEXTLINE 112 | TEST_CASE("RLBox test unverified pointer", "[verification]") 113 | { 114 | rlbox::rlbox_sandbox sandbox; 115 | sandbox.create_sandbox(); 116 | tainted pa = sandbox.malloc_in_sandbox(); 117 | 118 | const auto elementCountSafe = 1; 119 | REQUIRE_NOTHROW(pa.unverified_safe_pointer_because(elementCountSafe, 120 | "Reading within range")); 121 | 122 | const auto elementCountUnsafe = 123 | (TestSandbox::SandboxMemorySize / sizeof(int)) + 1; 124 | REQUIRE_THROWS(pa.unverified_safe_pointer_because( 125 | elementCountUnsafe, "Definitely out of sandbox memory. Unsafe!")); 126 | 127 | sandbox.destroy_sandbox(); 128 | } 129 | -------------------------------------------------------------------------------- /code/tests/rlbox/test_verify_arrays.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test_include.hpp" 7 | 8 | // NOLINTNEXTLINE 9 | TEST_CASE("RLBox test array verification", "[verification]") 10 | { 11 | rlbox::rlbox_sandbox sandbox; 12 | sandbox.create_sandbox(); 13 | 14 | auto pa = sandbox.malloc_in_sandbox(); // NOLINT 15 | 16 | const int32_t testVal1 = 10; 17 | const int32_t testVal2 = 11; 18 | const int32_t testVal3 = 12; 19 | const int32_t testVal4 = 13; 20 | 21 | (*pa)[0] = testVal1; 22 | (*pa)[1] = testVal2; 23 | (*pa)[2] = testVal3; 24 | (*pa)[3] = testVal4; 25 | 26 | const int32_t defaultVal1 = 210; 27 | const int32_t defaultVal2 = 211; 28 | const int32_t defaultVal3 = 212; 29 | const int32_t defaultVal4 = 213; 30 | std::array def = // NOLINT 31 | { { defaultVal1, defaultVal2, defaultVal3, defaultVal4 } }; 32 | 33 | auto result_fail = pa->copy_and_verify([&def](std::array) { // NOLINT 34 | return def; 35 | }); 36 | 37 | REQUIRE(result_fail[0] == defaultVal1); 38 | REQUIRE(result_fail[1] == defaultVal2); 39 | REQUIRE(result_fail[2] == defaultVal3); 40 | REQUIRE(result_fail[3] == defaultVal4); 41 | 42 | auto result_success = 43 | pa->copy_and_verify([](std::array val) { // NOLINT 44 | return val; 45 | }); 46 | 47 | REQUIRE(result_success[0] == testVal1); 48 | REQUIRE(result_success[1] == testVal2); 49 | REQUIRE(result_success[2] == testVal3); 50 | REQUIRE(result_success[3] == testVal4); 51 | 52 | sandbox.destroy_sandbox(); 53 | } 54 | 55 | // NOLINTNEXTLINE 56 | TEST_CASE("RLBox test range verification", "[verification]") 57 | { 58 | rlbox::rlbox_sandbox sandbox; 59 | sandbox.create_sandbox(); 60 | 61 | // long long is the 64 bit type in the TestSandbox 62 | const unsigned long long val64 = 0x1234567890ABCDEF; // NOLINT 63 | auto pa = sandbox.malloc_in_sandbox(); // NOLINT 64 | *pa = val64; 65 | 66 | // int is the 32 bit type 67 | rlbox::tainted pa_cast = 68 | rlbox::sandbox_reinterpret_cast(pa); // NOLINT 69 | 70 | auto checked_range = pa_cast.copy_and_verify_range( 71 | [](std::unique_ptr val) { // NOLINT 72 | return val; 73 | }, 74 | 2); 75 | 76 | auto val32_ptr = reinterpret_cast(&val64); // NOLINT 77 | REQUIRE(checked_range[0] == val32_ptr[0]); // NOLINT 78 | REQUIRE(checked_range[1] == val32_ptr[1]); // NOLINT 79 | REQUIRE(sandbox.is_pointer_in_app_memory(checked_range.get())); 80 | 81 | sandbox.destroy_sandbox(); 82 | } 83 | 84 | // NOLINTNEXTLINE 85 | TEST_CASE("RLBox test string verification", "[verification]") 86 | { 87 | rlbox::rlbox_sandbox sandbox; 88 | sandbox.create_sandbox(); 89 | 90 | const uint32_t max_length = 100; 91 | auto pc = sandbox.malloc_in_sandbox(max_length); // NOLINT 92 | 93 | std::strncpy(pc.UNSAFE_unverified(), "Hello", max_length); 94 | 95 | { 96 | auto checked_string = 97 | pc.copy_and_verify_string([](std::unique_ptr val) { // NOLINT 98 | return val; 99 | }); 100 | 101 | REQUIRE(strcmp(checked_string.get(), "Hello") == 0); // NOLINT 102 | REQUIRE(sandbox.is_pointer_in_app_memory(checked_string.get())); 103 | } 104 | 105 | { 106 | auto checked_string = 107 | pc.copy_and_verify_string([](std::string val) { // NOLINT 108 | return val; 109 | }); 110 | 111 | REQUIRE(strcmp(checked_string.c_str(), "Hello") == 0); // NOLINT 112 | REQUIRE(sandbox.is_pointer_in_app_memory(checked_string.c_str())); 113 | } 114 | 115 | sandbox.destroy_sandbox(); 116 | } -------------------------------------------------------------------------------- /code/tests/rlbox/test_wrapper_traits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "test_include.hpp" 4 | #include "test_tainted_structs.hpp" // IWYU pragma: keep 5 | 6 | using rlbox::sandbox_callback; 7 | using rlbox::tainted; 8 | using rlbox::tainted_volatile; 9 | using rlbox::detail::rlbox_is_tainted_v; 10 | using rlbox::detail::rlbox_remove_wrapper_t; 11 | 12 | // NOLINTNEXTLINE 13 | TEST_CASE("rlbox_is_tainted_v", "[wrapper_traits]") 14 | { 15 | // Unwrapped 16 | REQUIRE(!rlbox_is_tainted_v); 17 | REQUIRE(!rlbox_is_tainted_v); 18 | REQUIRE(!rlbox_is_tainted_v); // NOLINT 19 | REQUIRE(!rlbox_is_tainted_v); 20 | 21 | // fundamental tainted 22 | REQUIRE(rlbox_is_tainted_v>); 23 | REQUIRE(!rlbox_is_tainted_v>); 24 | 25 | // enum tainted 26 | REQUIRE(rlbox_is_tainted_v>); 27 | REQUIRE(!rlbox_is_tainted_v>); 28 | 29 | // pointer tainted 30 | REQUIRE(rlbox_is_tainted_v>); 31 | REQUIRE(!rlbox_is_tainted_v>); 32 | 33 | // static array tainted 34 | // NOLINTNEXTLINE 35 | REQUIRE(rlbox_is_tainted_v>); // NOLINT 36 | REQUIRE(!rlbox_is_tainted_v>); 38 | 39 | // struct tainted 40 | REQUIRE(rlbox_is_tainted_v>); 41 | REQUIRE( 42 | !rlbox_is_tainted_v>); 43 | 44 | // sandbox_callback 45 | REQUIRE(!rlbox_is_tainted_v>); 46 | } 47 | 48 | // NOLINTNEXTLINE 49 | TEST_CASE("rlbox_remove_wrapper_t", "[wrapper_traits]") 50 | { 51 | // Unwrapped 52 | REQUIRE(std::is_same_v, int>); 53 | REQUIRE(std::is_same_v, int*>); 54 | REQUIRE(std::is_same_v, char[4]>); // NOLINT 55 | REQUIRE(std::is_same_v, 56 | testVarietyStruct>); 57 | 58 | // fundamental tainted 59 | REQUIRE( 60 | std::is_same_v>, int>); 61 | REQUIRE( 62 | std::is_same_v>, 63 | int>); 64 | 65 | // enum tainted 66 | REQUIRE( 67 | std::is_same_v>, 68 | testBasicEnum>); 69 | REQUIRE(std::is_same_v< 70 | rlbox_remove_wrapper_t>, 71 | testBasicEnum>); 72 | 73 | // pointer tainted 74 | REQUIRE( 75 | std::is_same_v>, int*>); 76 | REQUIRE( 77 | std::is_same_v>, 78 | int*>); 79 | 80 | // static array tainted 81 | // NOLINTNEXTLINE 82 | REQUIRE(std::is_same_v>, 83 | char[4]>); // NOLINT 84 | REQUIRE( 85 | std::is_same_v< 86 | rlbox_remove_wrapper_t>, // NOLINT 87 | char[4]>); // NOLINT 88 | 89 | // struct tainted 90 | REQUIRE(std::is_same_v< 91 | rlbox_remove_wrapper_t>, 92 | testVarietyStruct>); 93 | REQUIRE( 94 | std::is_same_v< 95 | rlbox_remove_wrapper_t>, 96 | testVarietyStruct>); 97 | 98 | // sandbox_callback 99 | REQUIRE(std::is_same_v< 100 | rlbox_remove_wrapper_t>, 101 | int (*)(int)>); 102 | } -------------------------------------------------------------------------------- /code/tests/rlbox/testing_structs_for_cpp_api.h: -------------------------------------------------------------------------------- 1 | #if defined(__clang__) 2 | # pragma clang diagnostic push 3 | # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" 4 | #elif defined(__GNUC__) || defined(__GNUG__) 5 | // Can't turn off the variadic macro warning emitted from -pedantic 6 | # pragma GCC system_header 7 | #elif defined(_MSC_VER) 8 | // Doesn't seem to emit the warning 9 | #else 10 | // Don't know the compiler... just let it go through 11 | #endif 12 | 13 | // clang-format off 14 | 15 | #define sandbox_fields_reflection_testing_class_testVarietyStruct(f, g, ...) \ 16 | f(unsigned long, fieldLong, FIELD_NORMAL, ##__VA_ARGS__) g() \ 17 | f(const char*, fieldString, FIELD_NORMAL, ##__VA_ARGS__) g() \ 18 | f(unsigned int, fieldBool, FIELD_NORMAL, ##__VA_ARGS__) g() \ 19 | f(char[8], fieldFixedArr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 20 | f(int (*)(unsigned, const char*, unsigned[1]), \ 21 | fieldFnPtr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 22 | f(struct unknownClass*, fieldUnknownPtr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 23 | f(void*, voidPtr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 24 | f(int (*[8])(unsigned, const char*, unsigned[1]), \ 25 | fnArray, FIELD_NORMAL, ##__VA_ARGS__) g() 26 | 27 | #define sandbox_fields_reflection_testing_allClasses(f, ...) \ 28 | f(testVarietyStruct, testing, ##__VA_ARGS__) 29 | 30 | // clang-format on 31 | 32 | #if defined(__clang__) 33 | # pragma clang diagnostic pop 34 | #elif defined(__GNUC__) || defined(__GNUG__) 35 | #elif defined(_MSC_VER) 36 | #else 37 | #endif 38 | -------------------------------------------------------------------------------- /code/tests/rlbox_glue/lib/libtest.c: -------------------------------------------------------------------------------- 1 | #include "libtest.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | unsigned long simpleAddNoPrintTest(unsigned long a, unsigned long b) 8 | { 9 | return a + b; 10 | } 11 | 12 | unsigned long simpleCallbackLoop(unsigned long a, 13 | unsigned long b, 14 | unsigned long iterations, 15 | CallbackType3 callback) 16 | { 17 | unsigned long ret = 0; 18 | for (unsigned long i = 0; i < iterations; i++) { 19 | ret += callback(a, b); 20 | } 21 | return ret; 22 | } 23 | 24 | float callbackTypeFloatTest(float val, CallbackTypeFloat callback) 25 | { 26 | return callback(val); 27 | } 28 | 29 | double callbackTypeDoubleTest(double val, CallbackTypeDouble callback) 30 | { 31 | return callback(val); 32 | } 33 | 34 | long long int callbackTypeLongLongTest(long long int val, 35 | CallbackTypeLongLong callback) 36 | { 37 | return callback(val); 38 | } 39 | 40 | double simpleDivideTest(double a, double b) 41 | { 42 | return a / b; 43 | } 44 | 45 | int simpleAddTest(int a, int b) 46 | { 47 | printf("simpleAddTest\n"); 48 | fflush(stdout); 49 | return a + b; 50 | } 51 | 52 | size_t simpleStrLenTest(const char* str) 53 | { 54 | printf("simpleStrLenTest\n"); 55 | fflush(stdout); 56 | return strlen(str); 57 | } 58 | 59 | int simpleCallbackTest(unsigned a, const char* b, CallbackType callback) 60 | { 61 | int ret; 62 | 63 | printf("simpleCallbackTest\n"); 64 | fflush(stdout); 65 | 66 | ret = callback(a + 1, b, &a); 67 | return ret; 68 | } 69 | 70 | int simpleWriteToFileTest(FILE* file, const char* str) 71 | { 72 | printf("simpleWriteToFileTest\n"); 73 | fflush(stdout); 74 | return fputs(str, file); 75 | } 76 | 77 | char* simpleEchoTest(char* str) 78 | { 79 | printf("simpleEchoTest\n"); 80 | fflush(stdout); 81 | return str; 82 | } 83 | 84 | float simpleFloatAddTest(const float a, const float b) 85 | { 86 | printf("simpleFloatAddTest\n"); 87 | return a + b; 88 | } 89 | 90 | double simpleDoubleAddTest(const double a, const double b) 91 | { 92 | printf("simpleDoubleAddTest\n"); 93 | return a + b; 94 | } 95 | 96 | unsigned long simpleLongAddTest(unsigned long a, unsigned long b) 97 | { 98 | printf("simpleLongAddTest\n"); 99 | fflush(stdout); 100 | return a + b; 101 | } 102 | 103 | struct testStruct simpleTestStructVal() 104 | { 105 | struct testStruct ret; 106 | ret.fieldLong = 7; 107 | ret.fieldString = "Hello"; 108 | ret.fieldBool = 1; 109 | strcpy(ret.fieldFixedArr, "Bye"); 110 | return ret; 111 | } 112 | 113 | struct testStruct* simpleTestStructPtr() 114 | { 115 | struct testStruct* ret = 116 | (struct testStruct*)malloc(sizeof(struct testStruct)); 117 | ret->fieldLong = 7; 118 | ret->fieldString = "Hello"; 119 | ret->fieldBool = 1; 120 | strcpy(ret->fieldFixedArr, "Bye"); 121 | return ret; 122 | } 123 | 124 | struct testStruct simpleTestStructValBadPtr() 125 | { 126 | struct testStruct ret = simpleTestStructVal(); 127 | // explicitly mess up the top bits of the pointer. The sandbox checks outside 128 | // the sandbox should catch this 129 | if (sizeof(void*) == 4) { 130 | ret.fieldString = 131 | (char*)((((uintptr_t)ret.fieldString) & 0x3FFFFFFF) | 0xC0000000); 132 | } else { 133 | ret.fieldString = 134 | (char*)((((uintptr_t)ret.fieldString) & 0xFFFFFFFF) | 0x1234567800000000); 135 | } 136 | return ret; 137 | } 138 | 139 | struct testStruct* simpleTestStructPtrBadPtr() 140 | { 141 | struct testStruct* ret = simpleTestStructPtr(); 142 | // explicitly mess up the top bits of the pointer. The sandbox checks outside 143 | // the sandbox should catch this 144 | if (sizeof(void*) == 4) { 145 | ret->fieldString = 146 | (char*)((((uintptr_t)ret->fieldString) & 0x3FFFFFFF) | 0xC0000000); 147 | } else { 148 | ret->fieldString = (char*)((((uintptr_t)ret->fieldString) & 0xFFFFFFFF) | 149 | 0x1234567800000000); 150 | } 151 | return ret; 152 | } 153 | 154 | long simpleTestStructParam(struct testStruct param) 155 | { 156 | return (long) param.fieldLong + (long) (param.fieldString? strlen(param.fieldString) : 0); 157 | } 158 | 159 | int* echoPointer(int* pointer) 160 | { 161 | return pointer; 162 | } 163 | 164 | double simplePointerValAddTest(double* ptr, double val) 165 | { 166 | printf("simplePointerValAddTest\n"); 167 | return val + *ptr; 168 | } 169 | 170 | struct pointersStruct initializePointerStruct(char* initVal) 171 | { 172 | struct pointersStruct ret; 173 | ret.firstPointer = initVal; 174 | ret.pointerArray[0] = (char*)(((uintptr_t)initVal) + 1); 175 | ret.pointerArray[1] = (char*)(((uintptr_t)initVal) + 2); 176 | ret.pointerArray[2] = (char*)(((uintptr_t)initVal) + 3); 177 | ret.pointerArray[3] = (char*)(((uintptr_t)initVal) + 4); 178 | ret.lastPointer = (char*)(((uintptr_t)initVal) + 5); 179 | return ret; 180 | } 181 | 182 | struct pointersStruct* initializePointerStructPtr(char* initVal) 183 | { 184 | struct pointersStruct* ret = 185 | (struct pointersStruct*)malloc(sizeof(struct pointersStruct)); 186 | ret->firstPointer = initVal; 187 | ret->pointerArray[0] = (char*)(((uintptr_t)initVal) + 1); 188 | ret->pointerArray[1] = (char*)(((uintptr_t)initVal) + 2); 189 | ret->pointerArray[2] = (char*)(((uintptr_t)initVal) + 3); 190 | ret->pointerArray[3] = (char*)(((uintptr_t)initVal) + 4); 191 | ret->lastPointer = (char*)(((uintptr_t)initVal) + 5); 192 | return ret; 193 | } 194 | 195 | int internalCallback(unsigned a, const char* b, unsigned c[1]) 196 | { 197 | (void)c; 198 | return (int) a + (int) strlen(b); 199 | } 200 | 201 | void simplePointerWrite(int* ptr, int val) 202 | { 203 | *ptr = val; 204 | } 205 | 206 | int simpleCallbackTest2(unsigned long startVal, CallbackType2 cb) 207 | { 208 | return cb(startVal, 209 | startVal + 1, 210 | startVal + 2, 211 | startVal + 3, 212 | startVal + 4, 213 | startVal + 5); 214 | } 215 | 216 | unsigned long stackParametersTest(unsigned long a1, unsigned long a2, 217 | unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6, 218 | unsigned long a7, unsigned long a8, unsigned long a9) 219 | { 220 | return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9; 221 | } 222 | 223 | unsigned long long stackParametersTestLongLong(unsigned long long a1, unsigned long long a2, 224 | unsigned long long a3, unsigned long long a4, unsigned long long a5, unsigned long long a6, 225 | unsigned long long a7, unsigned long long a8, unsigned long long a9, unsigned long long a10) 226 | { 227 | return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10; 228 | } 229 | 230 | unsigned int stackParametersTestInt(unsigned int a1, unsigned int a2, 231 | unsigned int a3, unsigned int a4, unsigned int a5, unsigned int a6, 232 | unsigned int a7, unsigned int a8, unsigned int a9, unsigned int a10, unsigned int a11) 233 | { 234 | return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11; 235 | } 236 | 237 | int isNonNullChar(unsigned char p) 238 | { 239 | if (p) { 240 | return 1; 241 | } else { 242 | return 0; 243 | } 244 | } 245 | 246 | void* malloc_wrapper(size_t s) { 247 | return malloc(s); 248 | } 249 | 250 | void free_wrapper(void* p){ 251 | free(p); 252 | } 253 | -------------------------------------------------------------------------------- /code/tests/rlbox_glue/lib/libtest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" 8 | { 9 | #endif 10 | typedef int (*CallbackType)(unsigned, const char*, unsigned[1]); 11 | typedef int (*CallbackType2)(unsigned long, 12 | unsigned long, 13 | unsigned long, 14 | unsigned long, 15 | unsigned long, 16 | unsigned long); 17 | typedef unsigned long (*CallbackType3)(unsigned long, unsigned long); 18 | typedef float (*CallbackTypeFloat)(float val); 19 | typedef double (*CallbackTypeDouble)(double val); 20 | typedef long long int (*CallbackTypeLongLong)(long long int val); 21 | 22 | struct testStruct 23 | { 24 | unsigned long fieldLong; 25 | const char* fieldString; 26 | unsigned int fieldBool; 27 | char fieldFixedArr[8]; 28 | int (*fieldFnPtr)(unsigned, const char*, unsigned[1]); 29 | struct unknownClass* fieldUnknownPtr; 30 | void* voidPtr; 31 | CallbackType fnArray[4]; 32 | }; 33 | 34 | struct frozenStruct 35 | { 36 | int normalField; 37 | int fieldForFreeze; 38 | struct frozenStructTest* next; 39 | }; 40 | 41 | struct pointersStruct 42 | { 43 | char* firstPointer; 44 | char* pointerArray[4]; 45 | char* lastPointer; 46 | }; 47 | 48 | unsigned long simpleAddNoPrintTest(unsigned long a, unsigned long b); 49 | unsigned long simpleCallbackLoop(unsigned long a, 50 | unsigned long b, 51 | unsigned long iterations, 52 | CallbackType3 callback); 53 | float callbackTypeFloatTest(float val, CallbackTypeFloat callback); 54 | double callbackTypeDoubleTest(double val, CallbackTypeDouble callback); 55 | long long int callbackTypeLongLongTest(long long int val, 56 | CallbackTypeLongLong callback); 57 | double simpleDivideTest(double a, double b); 58 | int simpleAddTest(int a, int b); 59 | size_t simpleStrLenTest(const char* str); 60 | int simpleCallbackTest(unsigned a, const char* b, CallbackType callback); 61 | int simpleWriteToFileTest(FILE* file, const char* str); 62 | char* simpleEchoTest(char* str); 63 | float simpleFloatAddTest(const float a, const float b); 64 | double simpleDoubleAddTest(const double a, const double b); 65 | unsigned long simpleLongAddTest(unsigned long a, unsigned long b); 66 | struct testStruct simpleTestStructVal(); 67 | struct testStruct* simpleTestStructPtr(); 68 | struct testStruct simpleTestStructValBadPtr(); 69 | struct testStruct* simpleTestStructPtrBadPtr(); 70 | long simpleTestStructParam(struct testStruct param); 71 | int* echoPointer(int* pointer); 72 | double simplePointerValAddTest(double* ptr, double val); 73 | struct pointersStruct initializePointerStruct(char* initVal); 74 | struct pointersStruct* initializePointerStructPtr(char* initVal); 75 | int internalCallback(unsigned, const char*, unsigned[1]); 76 | void simplePointerWrite(int* ptr, int val); 77 | int simpleCallbackTest2(unsigned long startVal, CallbackType2 cb); 78 | unsigned long stackParametersTest(unsigned long a1, unsigned long a2, 79 | unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6, 80 | unsigned long a7, unsigned long a8, unsigned long a9); 81 | unsigned long long stackParametersTestLongLong(unsigned long long a1, unsigned long long a2, 82 | unsigned long long a3, unsigned long long a4, unsigned long long a5, unsigned long long a6, 83 | unsigned long long a7, unsigned long long a8, unsigned long long a9, unsigned long long a10); 84 | unsigned int stackParametersTestInt(unsigned int a1, unsigned int a2, 85 | unsigned int a3, unsigned int a4, unsigned int a5, unsigned int a6, 86 | unsigned int a7, unsigned int a8, unsigned int a9, unsigned int a10, unsigned int a11); 87 | int isNonNullChar(unsigned char p); 88 | void* malloc_wrapper(size_t s); 89 | void free_wrapper(void* p); 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | -------------------------------------------------------------------------------- /code/tests/rlbox_glue/lib/libtest_structs_for_cpp_api.h: -------------------------------------------------------------------------------- 1 | #if defined(__clang__) 2 | # pragma clang diagnostic push 3 | # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" 4 | #elif defined(__GNUC__) || defined(__GNUG__) 5 | // Can't turn off the variadic macro warning emitted from -pedantic 6 | # pragma GCC system_header 7 | #elif defined(_MSC_VER) 8 | // Doesn't seem to emit the warning 9 | #else 10 | // Don't know the compiler... just let it go through 11 | #endif 12 | 13 | // clang-format off 14 | #define sandbox_fields_reflection_libtest_class_testStruct(f, g, ...) \ 15 | f(unsigned long, fieldLong, FIELD_NORMAL, ##__VA_ARGS__) g() \ 16 | f(const char*, fieldString, FIELD_NORMAL, ##__VA_ARGS__) g() \ 17 | f(unsigned int, fieldBool, FIELD_NORMAL, ##__VA_ARGS__) g() \ 18 | f(char[8], fieldFixedArr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 19 | f(int (*)(unsigned, const char*, unsigned[1]), \ 20 | fieldFnPtr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 21 | f(struct unknownClass*, fieldUnknownPtr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 22 | f(void*, voidPtr, FIELD_NORMAL, ##__VA_ARGS__) g() \ 23 | f(int (*[4])(unsigned, const char*, unsigned[1]), \ 24 | fnArray, FIELD_NORMAL, ##__VA_ARGS__) g() 25 | 26 | #define sandbox_fields_reflection_libtest_class_frozenStruct(f, g, ...) \ 27 | f(int, normalField, FIELD_NORMAL, ##__VA_ARGS__) g() \ 28 | f(int, fieldForFreeze, FIELD_FREEZABLE, ##__VA_ARGS__) g() \ 29 | f(struct frozenStructTest*, next, FIELD_NORMAL, ##__VA_ARGS__) g() 30 | 31 | #define sandbox_fields_reflection_libtest_class_pointersStruct(f, g, ...) \ 32 | f(char*, firstPointer, FIELD_NORMAL, ##__VA_ARGS__) g() \ 33 | f(char * [4], pointerArray, FIELD_NORMAL, ##__VA_ARGS__) g() \ 34 | f(char*, lastPointer, FIELD_NORMAL, ##__VA_ARGS__) g() 35 | 36 | #define sandbox_fields_reflection_libtest_allClasses(f, ...) \ 37 | f(testStruct, libtest, ##__VA_ARGS__) \ 38 | f(frozenStruct, libtest, ##__VA_ARGS__) \ 39 | f(pointersStruct, libtest, ##__VA_ARGS__) 40 | 41 | // clang-format on 42 | 43 | #if defined(__clang__) 44 | # pragma clang diagnostic pop 45 | #elif defined(__GNUC__) || defined(__GNUG__) 46 | #elif defined(_MSC_VER) 47 | #else 48 | #endif 49 | -------------------------------------------------------------------------------- /code/tests/rlbox_glue/test_dylib_sandbox_glue.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTNEXTLINE 2 | #define RLBOX_USE_EXCEPTIONS 3 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 4 | #define RLBOX_SINGLE_THREADED_INVOCATIONS 5 | #include "rlbox_dylib_sandbox.hpp" 6 | 7 | // NOLINTNEXTLINE 8 | #define TestName "rlbox_dylib_sandbox" 9 | // NOLINTNEXTLINE 10 | #define TestType rlbox::rlbox_dylib_sandbox 11 | 12 | #ifndef GLUE_LIB_PATH 13 | # error "Missing definition for GLUE_LIB_PATH" 14 | #endif 15 | 16 | #if defined(_WIN32) 17 | // NOLINTNEXTLINE 18 | # define CreateSandbox(sandbox) sandbox.create_sandbox(L"" GLUE_LIB_PATH) 19 | #else 20 | // NOLINTNEXTLINE 21 | # define CreateSandbox(sandbox) sandbox.create_sandbox(GLUE_LIB_PATH) 22 | #endif 23 | 24 | #include "test_sandbox_glue.inc.cpp" 25 | -------------------------------------------------------------------------------- /code/tests/rlbox_glue/test_noop_sandbox_glue.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTNEXTLINE 2 | #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol 3 | #define RLBOX_USE_EXCEPTIONS 4 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 5 | #define RLBOX_SINGLE_THREADED_INVOCATIONS 6 | #include "rlbox_noop_sandbox.hpp" 7 | 8 | // NOLINTNEXTLINE 9 | #define TestName "rlbox_noop_sandbox" 10 | // NOLINTNEXTLINE 11 | #define TestType rlbox::rlbox_noop_sandbox 12 | // NOLINTNEXTLINE 13 | #define CreateSandbox(sandbox) sandbox.create_sandbox() 14 | 15 | #include "test_sandbox_glue.inc.cpp" -------------------------------------------------------------------------------- /code/tests/rlbox_glue/test_noop_sandbox_glue_configs.cpp: -------------------------------------------------------------------------------- 1 | // NOLINTNEXTLINE 2 | #define RLBOX_USE_STATIC_CALLS() rlbox_noop_sandbox_lookup_symbol 3 | #define RLBOX_USE_EXCEPTIONS 4 | #define RLBOX_ENABLE_DEBUG_ASSERTIONS 5 | #define RLBOX_SINGLE_THREADED_INVOCATIONS 6 | #define RLBOX_EMBEDDER_PROVIDES_TLS_STATIC_VARIABLES 7 | #include "rlbox_noop_sandbox.hpp" 8 | 9 | // NOLINTNEXTLINE 10 | #define TestName "rlbox_noop_sandbox configs" 11 | // NOLINTNEXTLINE 12 | #define TestType rlbox::rlbox_noop_sandbox 13 | // NOLINTNEXTLINE 14 | #define CreateSandbox(sandbox) sandbox.create_sandbox() 15 | 16 | RLBOX_NOOP_SANDBOX_STATIC_VARIABLES(); 17 | 18 | #include "test_sandbox_glue.inc.cpp" -------------------------------------------------------------------------------- /code/tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch2/catch.hpp" -------------------------------------------------------------------------------- /iwyu.imp: -------------------------------------------------------------------------------- 1 | [ 2 | { include: ["", "private", "", "public"] }, 3 | { include: ["", "private", "", "public"] }, 4 | { include: ["", "private", "", "public"] } 5 | ] -------------------------------------------------------------------------------- /leak_suppressions.txt: -------------------------------------------------------------------------------- 1 | # This is not a leak, but somehow the vector maintaining the transition times trips up the leak sanitizer 2 | leak:test_sandbox_transition_timings.cpp -------------------------------------------------------------------------------- /ub_suppressions.txt: -------------------------------------------------------------------------------- 1 | # RLBox allows automatic handling of differing machine models between the appplication and sandbox. 2 | # To do this, RLBox carefully changes the types of function pointers to the sandbox with ones suitable for the applicaton. 3 | # Unfortunately, this triggers the ubsan's check for calling functions with incorrect types. So disabling this. 4 | function:impl_invoke_with_func_ptr --------------------------------------------------------------------------------