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/Value.h"
26 #include "util/DiagnosticAssertions.h"
27 
28 /*
29  * Allow extra GC poisoning to be enabled in crash-diagnostics and zeal
30  * builds. Except in debug builds, this must be enabled by setting the
31  * JSGC_EXTRA_POISONING environment variable.
32  */
33 #if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL)
34 #  define JS_GC_ALLOW_EXTRA_POISONING 1
35 #endif
36 
37 namespace mozilla {
38 
39 /**
40  * Set the first |aNElem| T elements in |aDst| to |aSrc|.
41  */
42 template <typename T>
PodSet(T * aDst,const T & aSrc,size_t aNElem)43 static MOZ_ALWAYS_INLINE void PodSet(T* aDst, const T& aSrc, size_t aNElem) {
44   for (const T* dstend = aDst + aNElem; aDst < dstend; ++aDst) {
45     *aDst = aSrc;
46   }
47 }
48 
49 } /* namespace mozilla */
50 
51 /*
52  * Patterns used by SpiderMonkey to overwrite unused memory. If you are
53  * accessing an object with one of these pattern, you probably have a dangling
54  * pointer. These values should be odd, see the comment in IsThingPoisoned.
55  *
56  * Note: new patterns should also be added to the array in IsThingPoisoned!
57  *
58  * We try to keep our IRC bot, mrgiggles, up to date with these and other
59  * patterns:
60  * https://bitbucket.org/sfink/mrgiggles/src/default/plugins/knowledge/__init__.py
61  */
62 const uint8_t JS_FRESH_NURSERY_PATTERN = 0x2F;
63 const uint8_t JS_SWEPT_NURSERY_PATTERN = 0x2B;
64 const uint8_t JS_ALLOCATED_NURSERY_PATTERN = 0x2D;
65 const uint8_t JS_FRESH_TENURED_PATTERN = 0x4F;
66 const uint8_t JS_MOVED_TENURED_PATTERN = 0x49;
67 const uint8_t JS_SWEPT_TENURED_PATTERN = 0x4B;
68 const uint8_t JS_ALLOCATED_TENURED_PATTERN = 0x4D;
69 const uint8_t JS_FREED_HEAP_PTR_PATTERN = 0x6B;
70 const uint8_t JS_FREED_CHUNK_PATTERN = 0x8B;
71 const uint8_t JS_FREED_ARENA_PATTERN = 0x9B;
72 const uint8_t JS_SWEPT_TI_PATTERN = 0x6F;
73 const uint8_t JS_FRESH_MARK_STACK_PATTERN = 0x9F;
74 const uint8_t JS_RESET_VALUE_PATTERN = 0xBB;
75 const uint8_t JS_POISONED_JSSCRIPT_DATA_PATTERN = 0xDB;
76 const uint8_t JS_OOB_PARSE_NODE_PATTERN = 0xFF;
77 const uint8_t JS_LIFO_UNDEFINED_PATTERN = 0xcd;
78 const uint8_t JS_LIFO_UNINITIALIZED_PATTERN = 0xce;
79 
80 // Even ones
81 const uint8_t JS_NEW_NATIVE_ITERATOR_PATTERN = 0xCC;
82 const uint8_t JS_SCOPE_DATA_TRAILING_NAMES_PATTERN = 0xCC;
83 
84 /*
85  * Ensure JS_SWEPT_CODE_PATTERN is a byte pattern that will crash immediately
86  * when executed, so either an undefined instruction or an instruction that's
87  * illegal in user mode.
88  */
89 #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \
90     defined(JS_CODEGEN_NONE)
91 #  define JS_SWEPT_CODE_PATTERN 0xED  // IN instruction, crashes in user mode.
92 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
93 #  define JS_SWEPT_CODE_PATTERN 0xA3  // undefined instruction
94 #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
95 #  define JS_SWEPT_CODE_PATTERN 0x01  // undefined instruction
96 #elif defined(JS_CODEGEN_LOONG64)
97 #  define JS_SWEPT_CODE_PATTERN 0x01  // undefined instruction
98 #else
99 #  error "JS_SWEPT_CODE_PATTERN not defined for this platform"
100 #endif
101 
102 enum class MemCheckKind : uint8_t {
103   // Marks a region as poisoned. Memory sanitizers like ASan will crash when
104   // accessing it (both reads and writes).
105   MakeNoAccess,
106 
107   // Marks a region as having undefined contents. In ASan builds this just
108   // unpoisons the memory. MSan and Valgrind can also use this to find
109   // reads of uninitialized memory.
110   MakeUndefined,
111 };
112 
SetMemCheckKind(void * ptr,size_t bytes,MemCheckKind kind)113 static MOZ_ALWAYS_INLINE void SetMemCheckKind(void* ptr, size_t bytes,
114                                               MemCheckKind kind) {
115   switch (kind) {
116     case MemCheckKind::MakeUndefined:
117       MOZ_MAKE_MEM_UNDEFINED(ptr, bytes);
118       return;
119     case MemCheckKind::MakeNoAccess:
120       MOZ_MAKE_MEM_NOACCESS(ptr, bytes);
121       return;
122   }
123   MOZ_CRASH("Invalid kind");
124 }
125 
126 namespace js {
127 
PoisonImpl(void * ptr,uint8_t value,size_t num)128 static inline void PoisonImpl(void* ptr, uint8_t value, size_t num) {
129   // Without a valid Value tag, a poisoned Value may look like a valid
130   // floating point number. To ensure that we crash more readily when
131   // observing a poisoned Value, we make the poison an invalid ObjectValue.
132   // Unfortunately, this adds about 2% more overhead, so we can only enable
133   // it in debug.
134 #if defined(DEBUG)
135   if (!num) {
136     return;
137   }
138 
139   uintptr_t poison;
140   memset(&poison, value, sizeof(poison));
141 #  if defined(JS_PUNBOX64)
142   poison = poison & ((uintptr_t(1) << JSVAL_TAG_SHIFT) - 1);
143 #  endif
144   JS::Value v = js::PoisonedObjectValue(poison);
145 
146 #  if defined(JS_NUNBOX32)
147   // On 32-bit arch, `ptr` is 4 bytes aligned, and it's less than
148   // `sizeof(JS::Value)` == 8 bytes.
149   //
150   // `mozilla::PodSet` with `v` requires the pointer to be 8 bytes aligned if
151   // `value_count > 0`.
152   //
153   // If the pointer isn't 8 bytes aligned, fill the leading 1-4 bytes
154   // separately here, so that either the pointer is 8 bytes aligned, or
155   // we have no more bytes to fill.
156   uintptr_t begin_count = std::min(num, uintptr_t(ptr) % sizeof(JS::Value));
157   if (begin_count) {
158     uint8_t* begin = static_cast<uint8_t*>(ptr);
159     mozilla::PodSet(begin, value, begin_count);
160     ptr = begin + begin_count;
161     num -= begin_count;
162 
163     if (!num) {
164       return;
165     }
166   }
167 #  endif
168 
169   MOZ_ASSERT(uintptr_t(ptr) % sizeof(JS::Value) == 0);
170 
171   size_t value_count = num / sizeof(v);
172   size_t byte_count = num % sizeof(v);
173   mozilla::PodSet(reinterpret_cast<JS::Value*>(ptr), v, value_count);
174   if (byte_count) {
175     uint8_t* bytes = static_cast<uint8_t*>(ptr);
176     uint8_t* end = bytes + num;
177     mozilla::PodSet(end - byte_count, value, byte_count);
178   }
179 #else   // !DEBUG
180   memset(ptr, value, num);
181 #endif  // !DEBUG
182 }
183 
184 // Unconditionally poison a region on memory.
AlwaysPoison(void * ptr,uint8_t value,size_t num,MemCheckKind kind)185 static inline void AlwaysPoison(void* ptr, uint8_t value, size_t num,
186                                 MemCheckKind kind) {
187   PoisonImpl(ptr, value, num);
188   SetMemCheckKind(ptr, num, kind);
189 }
190 
191 #if defined(JS_GC_ALLOW_EXTRA_POISONING)
192 extern bool gExtraPoisoningEnabled;
193 #endif
194 
195 // Conditionally poison a region of memory in debug builds and nightly builds
196 // when enabled by setting the JSGC_EXTRA_POISONING environment variable. Used
197 // by the GC in places where poisoning has a performance impact.
Poison(void * ptr,uint8_t value,size_t num,MemCheckKind kind)198 static inline void Poison(void* ptr, uint8_t value, size_t num,
199                           MemCheckKind kind) {
200 #if defined(JS_GC_ALLOW_EXTRA_POISONING)
201   if (js::gExtraPoisoningEnabled) {
202     PoisonImpl(ptr, value, num);
203   }
204 #endif
205   SetMemCheckKind(ptr, num, kind);
206 }
207 
208 // Poison a region of memory in debug builds. Can be disabled by setting the
209 // JSGC_EXTRA_POISONING environment variable.
DebugOnlyPoison(void * ptr,uint8_t value,size_t num,MemCheckKind kind)210 static inline void DebugOnlyPoison(void* ptr, uint8_t value, size_t num,
211                                    MemCheckKind kind) {
212 #if defined(DEBUG)
213   Poison(ptr, value, num, kind);
214 #else
215   SetMemCheckKind(ptr, num, kind);
216 #endif
217 }
218 
219 }  // namespace js
220 
221 #endif /* util_Poison_h */
222