1 #pragma once 2 // IWYU pragma: private, include "rlbox.hpp" 3 // IWYU pragma: friend "rlbox_.*\.hpp" 4 5 #include <type_traits> 6 #include <utility> 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<typename T_Sbx, typename T_Ret, typename... T_Args> 18 using T_Cb = 19 std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>> (*)( 20 rlbox_sandbox<T_Sbx>&, 21 tainted<T_Args, T_Sbx>...); 22 23 template<typename T_Sbx, typename T_Ret, typename... T_Args> 24 T_Cb<T_Sbx, T_Ret, T_Args...> callback_type_helper(T_Ret (*)(T_Args...)); 25 26 // Compute the expected type of the interceptor 27 template<typename T_Sbx, typename T_Ret, typename... T_Args> 28 using T_I = detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> (*)( 29 detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...); 30 31 template<typename T_Sbx, typename T_Ret, typename... T_Args> 32 T_I<T_Sbx, T_Ret, T_Args...> interceptor_type_helper(T_Ret (*)(T_Args...)); 33 } 34 35 template<typename T, typename T_Sbx> 36 class sandbox_callback 37 { 38 KEEP_CLASSES_FRIENDLY 39 40 private: 41 rlbox_sandbox<T_Sbx>* sandbox; 42 43 using T_Callback = 44 decltype(callback_detail::callback_type_helper<T_Sbx>(std::declval<T>())); 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 for 49 // wrapping and converting callback arguments, returns etc. to their 50 // appropriate representations 51 using T_Interceptor = 52 decltype(callback_detail::interceptor_type_helper<T_Sbx>( 53 std::declval<T>())); 54 T_Interceptor callback_interceptor; 55 56 // The trampoline is the internal sandbox representation of the callback 57 // Depending on the sandbox type, this could be the callback pointer directly 58 // or a trampoline function that gates exits from the sandbox. 59 using T_Trampoline = detail::convert_to_sandbox_equivalent_t<T, T_Sbx>; 60 T_Trampoline callback_trampoline; 61 62 // The unique key representing the callback to pass to unregister_callback on 63 // destruction 64 void* key; 65 move_obj(sandbox_callback && other)66 inline void move_obj(sandbox_callback&& other) 67 { 68 sandbox = other.sandbox; 69 callback = other.callback; 70 callback_interceptor = other.callback_interceptor; 71 callback_trampoline = other.callback_trampoline; 72 key = other.key; 73 other.sandbox = nullptr; 74 other.callback = nullptr; 75 other.callback_interceptor = nullptr; 76 other.callback_trampoline = 0; 77 other.key = nullptr; 78 } 79 80 template<typename T_Ret, typename... T_Args> unregister_helper(T_Ret (*)(T_Args...))81 inline void unregister_helper(T_Ret (*)(T_Args...)) 82 { 83 if (callback != nullptr) { 84 // Don't need to worry about race between unregister and move as 85 // 1) this will not happen in a correctly written program 86 // 2) if this does happen, the worst that can happen is an invocation of a 87 // null function pointer, which causes a crash that cannot be exploited 88 // for RCE 89 sandbox->template unregister_callback<T_Ret, T_Args...>(key); 90 sandbox = nullptr; 91 callback = nullptr; 92 callback_interceptor = nullptr; 93 callback_trampoline = 0; 94 key = nullptr; 95 } 96 } 97 get_raw_value() const98 inline T_Callback get_raw_value() const noexcept { return callback; } get_raw_sandbox_value() const99 inline T_Trampoline get_raw_sandbox_value() const noexcept 100 { 101 return callback_trampoline; 102 } get_raw_value()103 inline T_Callback get_raw_value() noexcept { return callback; } get_raw_sandbox_value()104 inline T_Trampoline get_raw_sandbox_value() noexcept 105 { 106 return callback_trampoline; 107 } 108 109 // Keep constructor private as only rlbox_sandbox should be able to create 110 // this object sandbox_callback(rlbox_sandbox<T_Sbx> * p_sandbox,T_Callback p_callback,T_Interceptor p_callback_interceptor,T_Trampoline p_callback_trampoline,void * p_key)111 sandbox_callback(rlbox_sandbox<T_Sbx>* p_sandbox, 112 T_Callback p_callback, 113 T_Interceptor p_callback_interceptor, 114 T_Trampoline p_callback_trampoline, 115 void* p_key) 116 : sandbox(p_sandbox) 117 , callback(p_callback) 118 , callback_interceptor(p_callback_interceptor) 119 , callback_trampoline(p_callback_trampoline) 120 , key(p_key) 121 { 122 detail::dynamic_check(sandbox != nullptr, 123 "Unexpected null sandbox when creating a callback"); 124 } 125 126 public: sandbox_callback()127 sandbox_callback() 128 : sandbox(nullptr) 129 , callback(nullptr) 130 , callback_interceptor(nullptr) 131 , callback_trampoline(0) 132 , key(nullptr) 133 {} 134 sandbox_callback(sandbox_callback && other)135 sandbox_callback(sandbox_callback&& other) 136 { 137 move_obj(std::forward<sandbox_callback>(other)); 138 } 139 operator =(sandbox_callback && other)140 inline sandbox_callback& operator=(sandbox_callback&& other) 141 { 142 if (this != &other) { 143 move_obj(std::forward<sandbox_callback>(other)); 144 } 145 return *this; 146 } 147 unregister()148 void unregister() 149 { 150 T dummy = nullptr; 151 unregister_helper(dummy); 152 } 153 ~sandbox_callback()154 ~sandbox_callback() { unregister(); } 155 156 /** 157 * @brief Check if callback is _not_ registered. 158 */ is_unregistered() const159 inline bool is_unregistered() const noexcept 160 { 161 return get_raw_value() == nullptr; 162 } 163 164 /** 165 * @brief Unwrap a callback without verification. This is an unsafe operation 166 * and should be used with care. 167 */ UNSAFE_unverified() const168 inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); } 169 /** 170 * @brief Like UNSAFE_unverified, but get the underlying sandbox 171 * representation. 172 * 173 * @param sandbox Reference to sandbox. 174 */ UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox) const175 inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const noexcept 176 { 177 RLBOX_UNUSED(sandbox); 178 return get_raw_sandbox_value(); 179 } UNSAFE_unverified()180 inline auto UNSAFE_unverified() noexcept { return get_raw_value(); } UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox)181 inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) noexcept 182 { 183 RLBOX_UNUSED(sandbox); 184 return get_raw_sandbox_value(); 185 } 186 }; 187 188 template<typename T, typename T_Sbx> 189 class app_pointer 190 { 191 KEEP_CLASSES_FRIENDLY 192 193 private: 194 app_pointer_map<typename T_Sbx::T_PointerType>* map; 195 typename T_Sbx::T_PointerType idx; 196 T idx_unsandboxed; 197 move_obj(app_pointer && other)198 inline void move_obj(app_pointer&& other) 199 { 200 map = other.map; 201 idx = other.idx; 202 idx_unsandboxed = other.idx_unsandboxed; 203 other.map = nullptr; 204 other.idx = 0; 205 other.idx_unsandboxed = nullptr; 206 } 207 get_raw_value() const208 inline T get_raw_value() const noexcept 209 { 210 return to_tainted().get_raw_value(); 211 } get_raw_sandbox_value() const212 inline typename T_Sbx::T_PointerType get_raw_sandbox_value() const noexcept 213 { 214 return idx; 215 } get_raw_value()216 inline T get_raw_value() noexcept { return to_tainted().get_raw_value(); } get_raw_sandbox_value()217 inline typename T_Sbx::T_PointerType get_raw_sandbox_value() noexcept 218 { 219 return idx; 220 } 221 app_pointer(app_pointer_map<typename T_Sbx::T_PointerType> * a_map,typename T_Sbx::T_PointerType a_idx,T a_idx_unsandboxed)222 app_pointer(app_pointer_map<typename T_Sbx::T_PointerType>* a_map, 223 typename T_Sbx::T_PointerType a_idx, 224 T a_idx_unsandboxed) 225 : map(a_map) 226 , idx(a_idx) 227 , idx_unsandboxed(a_idx_unsandboxed) 228 {} 229 230 public: app_pointer()231 app_pointer() 232 : map(nullptr) 233 , idx(0) 234 , idx_unsandboxed(0) 235 {} 236 ~app_pointer()237 ~app_pointer() { unregister(); } 238 app_pointer(app_pointer && other)239 app_pointer(app_pointer&& other) 240 { 241 move_obj(std::forward<app_pointer>(other)); 242 } 243 operator =(app_pointer && other)244 inline app_pointer& operator=(app_pointer&& other) 245 { 246 if (this != &other) { 247 move_obj(std::forward<app_pointer>(other)); 248 } 249 return *this; 250 } 251 unregister()252 void unregister() 253 { 254 if (idx != 0) { 255 map->remove_app_ptr(idx); 256 map = nullptr; 257 idx = 0; 258 idx_unsandboxed = nullptr; 259 } 260 } 261 to_tainted()262 tainted<T, T_Sbx> to_tainted() 263 { 264 return tainted<T, T_Sbx>::internal_factory( 265 reinterpret_cast<T>(idx_unsandboxed)); 266 } 267 268 /** 269 * @brief Check if app pointer is _not_ registered. 270 */ is_unregistered() const271 inline bool is_unregistered() const noexcept { return idx == 0; } 272 273 /** 274 * @brief Unwrap app_pointer without verification. This is an unsafe operation 275 * and should be used with care. 276 */ UNSAFE_unverified() const277 inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); } 278 /** 279 * @brief Like UNSAFE_unverified, but get the underlying sandbox 280 * representation. 281 * 282 * @param sandbox Reference to sandbox. 283 */ UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox) const284 inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const noexcept 285 { 286 RLBOX_UNUSED(sandbox); 287 return get_raw_sandbox_value(); 288 } UNSAFE_unverified()289 inline auto UNSAFE_unverified() noexcept { return get_raw_value(); } UNSAFE_sandboxed(rlbox_sandbox<T_Sbx> & sandbox)290 inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) noexcept 291 { 292 RLBOX_UNUSED(sandbox); 293 return get_raw_sandbox_value(); 294 } 295 }; 296 297 /** 298 * @brief Tainted boolean value that serves as a "hint" and not a definite 299 * answer. Comparisons with a tainted_volatile return such hints. They are 300 * not `tainted<bool>` values because a compromised sandbox can modify 301 * tainted_volatile data at any time. 302 */ 303 class tainted_boolean_hint 304 { 305 private: 306 bool val; 307 308 public: tainted_boolean_hint(bool init)309 tainted_boolean_hint(bool init) 310 : val(init) 311 {} 312 tainted_boolean_hint(const tainted_boolean_hint&) = default; operator =(bool rhs)313 inline tainted_boolean_hint& operator=(bool rhs) 314 { 315 val = rhs; 316 return *this; 317 } operator !()318 inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); } 319 template<size_t N> unverified_safe_because(const char (& reason)[N]) const320 inline bool unverified_safe_because(const char (&reason)[N]) const 321 { 322 (void)reason; /* unused */ 323 return val; 324 } UNSAFE_unverified() const325 inline bool UNSAFE_unverified() const { return val; } UNSAFE_unverified()326 inline bool UNSAFE_unverified() { return val; } INTERNAL_unverified_safe()327 inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); } INTERNAL_unverified_safe() const328 inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } 329 330 // Add a template parameter to make sure the assert only fires when called 331 template<typename T = void> copy_and_verify(...) const332 inline bool copy_and_verify(...) const 333 { 334 rlbox_detail_static_fail_because( 335 detail::true_v<T>, 336 "You can't call copy_and_verify on this value, as this is a result of a " 337 "comparison with memory accessible by the sandbox. \n" 338 "The sandbox could unexpectedly change the value leading to " 339 "time-of-check-time-of-use attacks. \n" 340 "You can avoid this by making a local copy of the data." 341 "For example, if your original code, looked like \n" 342 "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n" 343 "Change this to \n\n" 344 "tainted<int> val = tainted_ptr->member\n" 345 "if ((val == 5).copy_and_verify(...)) { ... } \n\n" 346 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n" 347 "Alternately, if you are sure your code is safe you can use the " 348 "unverified_safe_because API to remove tainting\n"); 349 350 // this is never executed, but we need it for the function to type-check 351 return false; 352 } 353 }; 354 355 /** 356 * @brief Tainted integer value that serves as a "hint" and not a definite 357 * answer. Comparisons with a tainted_volatile return such hints. They are 358 * not `tainted<int>` values because a compromised sandbox can modify 359 * tainted_volatile data at any time. 360 */ 361 class tainted_int_hint 362 { 363 private: 364 int val; 365 366 public: tainted_int_hint(int init)367 tainted_int_hint(int init) 368 : val(init) 369 {} 370 tainted_int_hint(const tainted_int_hint&) = default; operator =(int rhs)371 inline tainted_int_hint& operator=(int rhs) 372 { 373 val = rhs; 374 return *this; 375 } operator !()376 inline tainted_boolean_hint operator!() { return tainted_boolean_hint(!val); } 377 template<size_t N> unverified_safe_because(const char (& reason)[N]) const378 inline int unverified_safe_because(const char (&reason)[N]) const 379 { 380 (void)reason; /* unused */ 381 return val; 382 } UNSAFE_unverified() const383 inline int UNSAFE_unverified() const { return val; } UNSAFE_unverified()384 inline int UNSAFE_unverified() { return val; } INTERNAL_unverified_safe()385 inline auto INTERNAL_unverified_safe() { return UNSAFE_unverified(); } INTERNAL_unverified_safe() const386 inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); } 387 388 // Add a template parameter to make sure the assert only fires when called 389 template<typename T = void> copy_and_verify(...) const390 inline int copy_and_verify(...) const 391 { 392 rlbox_detail_static_fail_because( 393 detail::true_v<T>, 394 "You can't call copy_and_verify on this value, as this is a result of a " 395 "comparison with memory accessible by the sandbox. \n" 396 "The sandbox could unexpectedly change the value leading to " 397 "time-of-check-time-of-use attacks. \n" 398 "You can avoid this by making a local copy of the data." 399 "For example, if your original code, looked like \n" 400 "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n" 401 "Change this to \n\n" 402 "tainted<int> val = tainted_ptr->member\n" 403 "if ((val == 5).copy_and_verify(...)) { ... } \n\n" 404 "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n" 405 "Alternately, if you are sure your code is safe you can use the " 406 "unverified_safe_because API to remove tainting\n"); 407 408 // this is never executed, but we need it for the function to type-check 409 return 0; 410 } 411 }; 412 413 } 414