Poison.h (6982B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * Memory poisoning. 9 */ 10 11 #ifndef util_Poison_h 12 #define util_Poison_h 13 14 #include "mozilla/Assertions.h" 15 #include "mozilla/Attributes.h" 16 #include "mozilla/MemoryChecking.h" 17 18 #include <algorithm> // std::min 19 #include <stddef.h> 20 #include <stdint.h> 21 #include <string.h> 22 23 #include "jstypes.h" 24 25 #include "js/Prefs.h" 26 #include "js/Value.h" 27 #include "util/DiagnosticAssertions.h" 28 29 /* 30 * Allow extra GC poisoning to be enabled in crash-diagnostics and zeal 31 * builds. Except in debug builds, this must be enabled by setting the 32 * javascript.options.extra_gc_poisoning pref. 33 */ 34 #if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL) 35 # define JS_GC_ALLOW_EXTRA_POISONING 1 36 #endif 37 38 namespace mozilla { 39 40 /** 41 * Set the first |aNElem| T elements in |aDst| to |aSrc|. 42 */ 43 template <typename T> 44 static MOZ_ALWAYS_INLINE void PodSet(T* aDst, const T& aSrc, size_t aNElem) { 45 for (const T* dstend = aDst + aNElem; aDst < dstend; ++aDst) { 46 *aDst = aSrc; 47 } 48 } 49 50 } /* namespace mozilla */ 51 52 /* 53 * Patterns used by SpiderMonkey to overwrite unused memory. If you are 54 * accessing an object with one of these patterns, you probably have a dangling 55 * pointer. These values should be odd. 56 */ 57 const uint8_t JS_FRESH_NURSERY_PATTERN = 0x2F; 58 const uint8_t JS_SWEPT_NURSERY_PATTERN = 0x2B; 59 const uint8_t JS_ALLOCATED_NURSERY_PATTERN = 0x2D; 60 const uint8_t JS_FRESH_TENURED_PATTERN = 0x4F; 61 const uint8_t JS_MOVED_TENURED_PATTERN = 0x49; 62 const uint8_t JS_SWEPT_TENURED_PATTERN = 0x4B; 63 const uint8_t JS_ALLOCATED_TENURED_PATTERN = 0x4D; 64 const uint8_t JS_FREED_HEAP_PTR_PATTERN = 0x6B; 65 const uint8_t JS_FREED_CHUNK_PATTERN = 0x8B; 66 const uint8_t JS_FREED_ARENA_PATTERN = 0x9B; 67 const uint8_t JS_FRESH_MARK_STACK_PATTERN = 0x9F; 68 const uint8_t JS_FREED_BUFFER_PATTERN = 0xAB; 69 const uint8_t JS_ALLOCATED_BUFFER_PATTERN = 0xAD; 70 const uint8_t JS_RESET_VALUE_PATTERN = 0xBB; 71 const uint8_t JS_POISONED_JSSCRIPT_DATA_PATTERN = 0xDB; 72 const uint8_t JS_OOB_PARSE_NODE_PATTERN = 0xFF; 73 const uint8_t JS_LIFO_UNDEFINED_PATTERN = 0xcd; 74 const uint8_t JS_LIFO_UNINITIALIZED_PATTERN = 0xce; 75 76 // Even ones 77 const uint8_t JS_SCOPE_DATA_TRAILING_NAMES_PATTERN = 0xCC; 78 79 /* 80 * Ensure JS_SWEPT_CODE_PATTERN is a byte pattern that will crash immediately 81 * when executed, so either an undefined instruction or an instruction that's 82 * illegal in user mode. 83 */ 84 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \ 85 defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32) 86 # define JS_SWEPT_CODE_PATTERN 0xED // IN instruction, crashes in user mode. 87 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) 88 # define JS_SWEPT_CODE_PATTERN 0xA3 // undefined instruction 89 #elif defined(JS_CODEGEN_MIPS64) 90 # define JS_SWEPT_CODE_PATTERN 0x01 // undefined instruction 91 #elif defined(JS_CODEGEN_LOONG64) 92 # define JS_SWEPT_CODE_PATTERN 0x01 // undefined instruction 93 #elif defined(JS_CODEGEN_RISCV64) 94 # define JS_SWEPT_CODE_PATTERN \ 95 0x29 // illegal sb instruction, crashes in user mode. 96 #else 97 # error "JS_SWEPT_CODE_PATTERN not defined for this platform" 98 #endif 99 100 enum class MemCheckKind : uint8_t { 101 // Marks a region as poisoned. Memory sanitizers like ASan will crash when 102 // accessing it (both reads and writes). 103 MakeNoAccess, 104 105 // Marks a region as having undefined contents. In ASan builds this just 106 // unpoisons the memory. MSan and Valgrind can also use this to find 107 // reads of uninitialized memory. 108 MakeUndefined, 109 }; 110 111 static MOZ_ALWAYS_INLINE void SetMemCheckKind(void* ptr, size_t bytes, 112 MemCheckKind kind) { 113 switch (kind) { 114 case MemCheckKind::MakeUndefined: 115 MOZ_MAKE_MEM_UNDEFINED(ptr, bytes); 116 return; 117 case MemCheckKind::MakeNoAccess: 118 MOZ_MAKE_MEM_NOACCESS(ptr, bytes); 119 return; 120 } 121 MOZ_CRASH("Invalid kind"); 122 } 123 124 namespace js { 125 126 static inline void PoisonImpl(void* ptr, uint8_t value, size_t num) { 127 // Without a valid Value tag, a poisoned Value may look like a valid 128 // floating point number. To ensure that we crash more readily when 129 // observing a poisoned Value, we make the poison an invalid ObjectValue. 130 // Unfortunately, this adds about 2% more overhead, so we can only enable 131 // it in debug. 132 #if defined(DEBUG) 133 if (!num) { 134 return; 135 } 136 137 uintptr_t poison; 138 memset(&poison, value, sizeof(poison)); 139 # if defined(JS_PUNBOX64) 140 poison = poison & ((uintptr_t(1) << JSVAL_TAG_SHIFT) - 1); 141 # endif 142 JS::Value v = js::PoisonedObjectValue(poison); 143 144 # if defined(JS_NUNBOX32) 145 // On 32-bit arch, `ptr` is 4 bytes aligned, and it's less than 146 // `sizeof(JS::Value)` == 8 bytes. 147 // 148 // `mozilla::PodSet` with `v` requires the pointer to be 8 bytes aligned if 149 // `value_count > 0`. 150 // 151 // If the pointer isn't 8 bytes aligned, fill the leading 1-4 bytes 152 // separately here, so that either the pointer is 8 bytes aligned, or 153 // we have no more bytes to fill. 154 uintptr_t begin_count = std::min(num, uintptr_t(ptr) % sizeof(JS::Value)); 155 if (begin_count) { 156 uint8_t* begin = static_cast<uint8_t*>(ptr); 157 mozilla::PodSet(begin, value, begin_count); 158 ptr = begin + begin_count; 159 num -= begin_count; 160 161 if (!num) { 162 return; 163 } 164 } 165 # endif 166 167 MOZ_ASSERT(uintptr_t(ptr) % sizeof(JS::Value) == 0); 168 169 size_t value_count = num / sizeof(v); 170 size_t byte_count = num % sizeof(v); 171 mozilla::PodSet(reinterpret_cast<JS::Value*>(ptr), v, value_count); 172 if (byte_count) { 173 uint8_t* bytes = static_cast<uint8_t*>(ptr); 174 uint8_t* end = bytes + num; 175 mozilla::PodSet(end - byte_count, value, byte_count); 176 } 177 #else // !DEBUG 178 memset(ptr, value, num); 179 #endif // !DEBUG 180 } 181 182 // Unconditionally poison a region on memory. 183 static inline void AlwaysPoison(void* ptr, uint8_t value, size_t num, 184 MemCheckKind kind) { 185 PoisonImpl(ptr, value, num); 186 SetMemCheckKind(ptr, num, kind); 187 } 188 189 // Conditionally poison a region of memory in debug builds and nightly builds 190 // when enabled by setting the javascript.options.extra_gc_poisoning pref. Used 191 // by the GC in places where poisoning has a performance impact. 192 static inline void Poison(void* ptr, uint8_t value, size_t num, 193 MemCheckKind kind) { 194 #if defined(JS_GC_ALLOW_EXTRA_POISONING) 195 if (JS::Prefs::extra_gc_poisoning()) { 196 PoisonImpl(ptr, value, num); 197 } 198 #endif 199 SetMemCheckKind(ptr, num, kind); 200 } 201 202 // Poison a region of memory in debug builds only. 203 static inline void DebugOnlyPoison(void* ptr, uint8_t value, size_t num, 204 MemCheckKind kind) { 205 #if defined(DEBUG) 206 Poison(ptr, value, num, kind); 207 #else 208 SetMemCheckKind(ptr, num, kind); 209 #endif 210 } 211 212 } // namespace js 213 214 #endif /* util_Poison_h */