The OpenD Programming Language

1 /**
2  * Handle page protection errors using D errors (exceptions) or asserts.
3  *
4  * License: Distributed under the
5  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
6  *    (See accompanying file LICENSE_1_0.txt)
7  * Authors:   Amaury SECHET, FeepingCreature, Vladimir Panteleev
8  * Source: $(DRUNTIMESRC etc/linux/memory.d)
9  */
10 
11 module etc.linux.memoryerror;
12 
13 version (linux)
14 {
15     version (DigitalMars)
16     {
17         version (CRuntime_Glibc)
18         {
19             version (X86)
20                 version = MemoryErrorSupported;
21             else version (X86_64)
22                 version = MemoryErrorSupported;
23         }
24     }
25 }
26 
27 version (linux)
28 {
29     version (X86)
30         version = MemoryAssertSupported;
31     else version (X86_64)
32         version = MemoryAssertSupported;
33     else version (ARM)
34         version = MemoryAssertSupported;
35     else version (AArch64)
36         version = MemoryAssertSupported;
37     else version (PPC64)
38         version = MemoryAssertSupported;
39 }
40 
41 version (MemoryErrorSupported)
42     version = AnySupported;
43 else version (MemoryErrorSupported)
44     version = AnySupported;
45 
46 version (AnySupported):
47 
48 import core.sys.posix.signal;
49 import ucontext = core.sys.posix.ucontext;
50 
51 version (MemoryAssertSupported)
52 {
53     import core.sys.posix.signal : SA_ONSTACK, sigaltstack, SIGSTKSZ, stack_t;
54 }
55 
56 @system:
57 
58 // The first 64Kb are reserved for detecting null pointer dereferences.
59 // TODO: this is a platform-specific assumption, can be made more robust
60 private enum size_t MEMORY_RESERVED_FOR_NULL_DEREFERENCE = 4096 * 16;
61 
62 version (MemoryErrorSupported)
63 {
64     /**
65      * Register memory error handler, store the old handler.
66      *
67      * `NullPointerError` is thrown when dereferencing null pointers.
68      * A generic `InvalidPointerError` error is thrown in other cases.
69      *
70      * Returns: whether the registration was successful
71      *
72      * Limitations: Only x86 and x86_64 are supported for now.
73      */
74     bool registerMemoryErrorHandler() nothrow
75     {
76         sigaction_t action;
77         action.sa_sigaction = &handleSignal;
78         action.sa_flags = SA_SIGINFO;
79 
80         auto oldptr = &oldSigactionMemoryError;
81 
82         return !sigaction(SIGSEGV, &action, oldptr);
83     }
84 
85     /**
86      * Revert the memory error handler back to the one from before calling `registerMemoryErrorHandler()`.
87      *
88      * Returns: whether the registration of the old handler was successful
89      */
90     bool deregisterMemoryErrorHandler() nothrow
91     {
92         auto oldptr = &oldSigactionMemoryError;
93 
94         return !sigaction(SIGSEGV, oldptr, null);
95     }
96 
97     /**
98      * Thrown on POSIX systems when a SIGSEGV signal is received.
99      */
100     class InvalidPointerError : Error
101     {
102         this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow
103         {
104             super("", file, line, next);
105         }
106 
107         this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow
108         {
109             super("", file, line, next);
110         }
111     }
112 
113     /**
114      * Thrown on null pointer dereferences.
115      */
116     class NullPointerError : InvalidPointerError
117     {
118         this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) nothrow
119         {
120             super(file, line, next);
121         }
122 
123         this(Throwable next, string file = __FILE__, size_t line = __LINE__) nothrow
124         {
125             super(file, line, next);
126         }
127     }
128 
129     unittest
130     {
131         int* getNull() { return null; }
132 
133         assert(registerMemoryErrorHandler());
134 
135         bool b;
136 
137         try
138         {
139             *getNull() = 42;
140         }
141         catch (NullPointerError)
142         {
143             b = true;
144         }
145 
146         assert(b);
147 
148         b = false;
149 
150         try
151         {
152             *getNull() = 42;
153         }
154         catch (InvalidPointerError)
155         {
156             b = true;
157         }
158 
159         assert(b);
160 
161         assert(deregisterMemoryErrorHandler());
162     }
163 
164     // Signal handler space.
165 
166     private:
167 
168     __gshared sigaction_t oldSigactionMemoryError;
169 
170     alias RegType = typeof(ucontext.ucontext_t.init.uc_mcontext.gregs[0]);
171 
172     version (X86_64)
173     {
174         static RegType savedRDI, savedRSI;
175 
176         extern(C)
177         void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow
178         {
179             auto context = cast(ucontext.ucontext_t*)contextPtr;
180 
181             // Save registers into global thread local, to allow recovery.
182             savedRDI = context.uc_mcontext.gregs[ucontext.REG_RDI];
183             savedRSI = context.uc_mcontext.gregs[ucontext.REG_RSI];
184 
185             // Hijack current context so we call our handler.
186             auto rip = context.uc_mcontext.gregs[ucontext.REG_RIP];
187             auto addr = cast(RegType) info.si_addr;
188             context.uc_mcontext.gregs[ucontext.REG_RDI] = addr;
189             context.uc_mcontext.gregs[ucontext.REG_RSI] = rip;
190             context.uc_mcontext.gregs[ucontext.REG_RIP] = cast(RegType) ((rip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler);
191         }
192 
193         // All handler functions must be called with faulting address in RDI and original RIP in RSI.
194 
195         // This function is called when the segfault's cause is to call an invalid function pointer.
196         void sigsegvCodeHandler()
197         {
198             asm
199             {
200                 naked;
201 
202                 // Handle the stack for an invalid function call (segfault at RIP).
203                 // With the return pointer, the stack is now alligned.
204                 push RBP;
205                 mov RBP, RSP;
206 
207                 jmp sigsegvDataHandler;
208             }
209         }
210 
211         void sigsegvDataHandler()
212         {
213             asm
214             {
215                 naked;
216 
217                 push RSI;   // return address (original RIP).
218                 push RBP;   // old RBP
219                 mov RBP, RSP;
220 
221                 pushfq;     // Save flags.
222                 push RAX;   // RAX, RCX, RDX, and R8 to R11 are trash registers and must be preserved as local variables.
223                 push RCX;
224                 push RDX;
225                 push R8;
226                 push R9;
227                 push R10;
228                 push R11;    // With 10 pushes, the stack is still aligned.
229 
230                 // Parameter address is already set as RAX.
231                 call sigsegvUserspaceProcess;
232 
233                 // Restore RDI and RSI values.
234                 call restoreRDI;
235                 push RAX;   // RDI is in RAX. It is pushed and will be poped back to RDI.
236 
237                 call restoreRSI;
238                 mov RSI, RAX;
239 
240                 pop RDI;
241 
242                 // Restore trash registers value.
243                 pop R11;
244                 pop R10;
245                 pop R9;
246                 pop R8;
247                 pop RDX;
248                 pop RCX;
249                 pop RAX;
250                 popfq;      // Restore flags.
251 
252                 // Return
253                 pop RBP;
254                 ret;
255             }
256         }
257 
258         // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers.
259         RegType restoreRDI()
260         {
261             return savedRDI;
262         }
263 
264         RegType restoreRSI()
265         {
266             return savedRSI;
267         }
268     }
269     else version (X86)
270     {
271         static RegType savedEAX, savedEDX;
272 
273         extern(C)
274         void handleSignal(int signum, siginfo_t* info, void* contextPtr) nothrow
275         {
276             auto context = cast(ucontext.ucontext_t*)contextPtr;
277 
278             // Save registers into global thread local, to allow recovery.
279             savedEAX = context.uc_mcontext.gregs[ucontext.REG_EAX];
280             savedEDX = context.uc_mcontext.gregs[ucontext.REG_EDX];
281 
282             // Hijack current context so we call our handler.
283             auto eip = context.uc_mcontext.gregs[ucontext.REG_EIP];
284             auto addr = cast(RegType) info.si_addr;
285             context.uc_mcontext.gregs[ucontext.REG_EAX] = addr;
286             context.uc_mcontext.gregs[ucontext.REG_EDX] = eip;
287             context.uc_mcontext.gregs[ucontext.REG_EIP] = cast(RegType) ((eip != addr)?&sigsegvDataHandler:&sigsegvCodeHandler);
288         }
289 
290         // All handler functions must be called with faulting address in EAX and original EIP in EDX.
291 
292         // This function is called when the segfault's cause is to call an invalid function pointer.
293         void sigsegvCodeHandler()
294         {
295             asm
296             {
297                 naked;
298 
299                 // Handle the stack for an invalid function call (segfault at EIP).
300                 // 4 bytes are used for function pointer; We need 12 byte to keep stack aligned.
301                 sub ESP, 12;
302                 mov [ESP + 8], EBP;
303                 mov EBP, ESP;
304 
305                 jmp sigsegvDataHandler;
306             }
307         }
308 
309         void sigsegvDataHandler()
310         {
311             asm
312             {
313                 naked;
314 
315                 // We jump directly here if we are in a valid function call case.
316                 push EDX;   // return address (original EIP).
317                 push EBP;   // old EBP
318                 mov EBP, ESP;
319 
320                 pushfd;     // Save flags.
321                 push ECX;   // ECX is a trash register and must be preserved as local variable.
322                             // 4 pushes have been done. The stack is aligned.
323 
324                 // Parameter address is already set as EAX.
325                 call sigsegvUserspaceProcess;
326 
327                 // Restore register values and return.
328                 call restoreRegisters;
329 
330                 pop ECX;
331                 popfd;      // Restore flags.
332 
333                 // Return
334                 pop EBP;
335                 ret;
336             }
337         }
338 
339         // The return value is stored in EAX and EDX, so this function restore the correct value for theses registers.
340         RegType[2] restoreRegisters()
341         {
342             RegType[2] restore;
343             restore[0] = savedEAX;
344             restore[1] = savedEDX;
345 
346             return restore;
347         }
348     }
349     else
350     {
351         static assert(false, "Unsupported architecture.");
352     }
353 
354     // User space handler
355     void sigsegvUserspaceProcess(void* address)
356     {
357         // SEGV_MAPERR, SEGV_ACCERR.
358         // The first page is protected to detect null dereferences.
359         if ((cast(size_t) address) < MEMORY_RESERVED_FOR_NULL_DEREFERENCE)
360         {
361             throw new NullPointerError();
362         }
363 
364         throw new InvalidPointerError();
365     }
366 }
367 
368 version (MemoryAssertSupported)
369 {
370     private __gshared sigaction_t oldSigactionMemoryAssert; // sigaction before calling `registerMemoryAssertHandler`
371 
372     /**
373      * Registers a signal handler for SIGSEGV that turns them into an assertion failure,
374      * providing a more descriptive error message and stack trace if the program is
375      * compiled with debug info and D assertions (as opposed to C assertions).
376      *
377      * Differences with the `registerMemoryErrorHandler` version are:
378      * - The handler is registered with SA_ONSTACK, so it can handle stack overflows.
379      * - It uses `assert(0)` instead of `throw new Error` and doesn't support catching the error.
380      * - This is a template so that the -check and -checkaction flags of the compiled program are used,
381      *   instead of the ones used for compiling druntime.
382      *
383      * Returns: whether the registration was successful
384      */
385     bool registerMemoryAssertHandler()()
386     {
387         nothrow @nogc extern(C)
388         void _d_handleSignalAssert(int signum, siginfo_t* info, void* contextPtr)
389         {
390             // Guess the reason for the segfault by seeing if the faulting address
391             // is close to the stack pointer or the null pointer.
392 
393             const void* segfaultingPtr = info.si_addr;
394 
395             auto context = cast(ucontext.ucontext_t*) contextPtr;
396             version (X86_64)
397                 const stackPtr = cast(void*) context.uc_mcontext.gregs[ucontext.REG_RSP];
398             else version (X86)
399                 const stackPtr = cast(void*) context.uc_mcontext.gregs[ucontext.REG_ESP];
400             else version (ARM)
401                 const stackPtr = cast(void*) context.uc_mcontext.arm_sp;
402             else version (AArch64)
403                 const stackPtr = cast(void*) context.uc_mcontext.sp;
404             else version (PPC64)
405                 const stackPtr = cast(void*) context.uc_mcontext.regs.gpr[1];
406             else
407                 static assert(false, "Unsupported architecture."); // TODO: other architectures
408             auto distanceToStack = cast(ptrdiff_t) (stackPtr - segfaultingPtr);
409             if (distanceToStack < 0)
410                 distanceToStack = -distanceToStack;
411 
412             if (stackPtr && distanceToStack <= 4096)
413                 assert(false, "segmentation fault: call stack overflow");
414             else if (cast(size_t) segfaultingPtr < MEMORY_RESERVED_FOR_NULL_DEREFERENCE)
415                 assert(false, "segmentation fault: null pointer read/write operation");
416             else
417                 assert(false, "segmentation fault: invalid pointer read/write operation");
418         }
419 
420         sigaction_t action;
421         action.sa_sigaction = &_d_handleSignalAssert;
422         action.sa_flags = SA_SIGINFO | SA_ONSTACK;
423 
424         // Set up alternate stack, because segfaults can be caused by stack overflow,
425         // in which case the stack is already exhausted
426         __gshared ubyte[SIGSTKSZ] altStack;
427         stack_t ss;
428         ss.ss_sp = altStack.ptr;
429         ss.ss_size = altStack.length;
430         ss.ss_flags = 0;
431         if (sigaltstack(&ss, null) == -1)
432             return false;
433 
434         return !sigaction(SIGSEGV, &action, &oldSigactionMemoryAssert);
435     }
436 
437     /**
438      * Revert the memory error handler back to the one from before calling `registerMemoryAssertHandler()`.
439      *
440      * Returns: whether the registration of the old handler was successful
441      */
442     bool deregisterMemoryAssertHandler()
443     {
444         return !sigaction(SIGSEGV, &oldSigactionMemoryAssert, null);
445     }
446 
447     unittest
448     {
449         // Testing actual memory errors is done in the test suite
450         assert(registerMemoryAssertHandler());
451         assert(deregisterMemoryAssertHandler());
452     }
453 }