/src/freeradius-server/src/lib/util/debug.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** Functions to help with debugging |
18 | | * |
19 | | * @file src/lib/util/debug.c |
20 | | * |
21 | | * @copyright 2013 The FreeRADIUS server project |
22 | | * @copyright 2013 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
23 | | */ |
24 | | #include <freeradius-devel/util/backtrace.h> |
25 | | #include <freeradius-devel/util/debug.h> |
26 | | #include <freeradius-devel/util/hash.h> |
27 | | #include <freeradius-devel/util/strerror.h> |
28 | | #include <freeradius-devel/util/syserror.h> |
29 | | |
30 | | #include <pthread.h> |
31 | | #include <stdio.h> |
32 | | #include <stdlib.h> |
33 | | #include <sys/stat.h> |
34 | | #include <sys/wait.h> |
35 | | |
36 | | #if defined(HAVE_MALLOPT) && defined(HAVE_MALLOC_H) |
37 | | # include <malloc.h> |
38 | | #endif |
39 | | |
40 | | #ifdef HAVE_SYS_PRCTL_H |
41 | | # include <sys/prctl.h> |
42 | | #endif |
43 | | |
44 | | #ifdef HAVE_SYS_PROCCTL_H |
45 | | # include <sys/procctl.h> |
46 | | #endif |
47 | | |
48 | | #ifdef HAVE_SYS_PTRACE_H |
49 | | # include <sys/ptrace.h> |
50 | | # if !defined(PT_ATTACH) && defined(PTRACE_ATTACH) |
51 | | # define PT_ATTACH PTRACE_ATTACH |
52 | | # endif |
53 | | # if !defined(PT_DETACH) && defined(PTRACE_DETACH) |
54 | | # define PT_DETACH PTRACE_DETACH |
55 | | # endif |
56 | | #endif |
57 | | |
58 | | #ifdef HAVE_SYS_RESOURCE_H |
59 | | # include <sys/resource.h> |
60 | | #endif |
61 | | |
62 | | #ifdef __APPLE__ |
63 | | #include <sys/sysctl.h> |
64 | | #endif |
65 | | |
66 | | static char panic_action[512]; //!< The command to execute when panicking. |
67 | | static fr_fault_cb_t panic_cb = NULL; //!< Callback to execute whilst panicking, before the |
68 | | //!< panic_action. |
69 | | |
70 | | static bool dump_core; //!< Whether we should drop a core on fatal signals. |
71 | | |
72 | | int fr_fault_log_fd = STDERR_FILENO; //!< Where to write debug output. |
73 | | |
74 | | fr_debug_state_t fr_debug_state = DEBUGGER_STATE_UNKNOWN; //!< Whether we're attached to by a debugger. |
75 | | |
76 | | #ifdef HAVE_SYS_RESOURCE_H |
77 | | static struct rlimit init_core_limit; |
78 | | #endif |
79 | | |
80 | | static TALLOC_CTX *talloc_autofree_ctx; |
81 | | |
82 | | /* |
83 | | * On BSD systems, ptrace(PT_DETACH) uses a third argument for |
84 | | * resume address, with the magic value (void *)1 to resume where |
85 | | * process stopped. Specifying NULL there leads to a crash because |
86 | | * process resumes at address 0. |
87 | | */ |
88 | | #if defined(HAVE_SYS_PTRACE_H) |
89 | | # ifdef __linux__ |
90 | 0 | # define _PTRACE(_x, _y) ptrace(_x, _y, NULL, NULL) |
91 | 0 | # define _PTRACE_DETACH(_x) ptrace(PT_DETACH, _x, NULL, NULL) |
92 | | # elif !defined(__APPLE__) && !defined(__EMSCRIPTEN__) && !defined(HAVE_SYS_PROCCTL_H) |
93 | | # define _PTRACE(_x, _y) ptrace(_x, _y, NULL, 0) |
94 | | # define _PTRACE_DETACH(_x) ptrace(PT_DETACH, _x, (void *)1, 0) |
95 | | #endif |
96 | | |
97 | | # ifdef HAVE_CAPABILITY_H |
98 | | # include <sys/capability.h> |
99 | | # endif |
100 | | #endif |
101 | | |
102 | | #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H |
103 | | # include <sanitizer/lsan_interface.h> |
104 | | #endif |
105 | | |
106 | | #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H |
107 | | static int lsan_test_pipe[2] = {-1, -1}; |
108 | | static int lsan_test_pid = -1; |
109 | | static int lsan_state = INT_MAX; |
110 | | static bool lsan_disable = false; //!< Explicitly disable LSAN |
111 | | |
112 | | /* |
113 | | * Some versions of lsan_interface.h are broken and don't declare |
114 | | * the prototypes of the functions properly, omitting the zero argument |
115 | | * specifier (void), so we need to disable the warning. |
116 | | * |
117 | | * Observed with clang 5. |
118 | | */ |
119 | | DIAG_OFF(missing-prototypes) |
120 | | /** Callback for LSAN - do not rename |
121 | | * |
122 | | */ |
123 | | char const CC_HINT(used) *__lsan_default_suppressions(void) |
124 | 13 | { |
125 | 13 | return |
126 | 13 | "leak:CRYPTO_THREAD_lock_new\n" /* OpenSSL init leak - reported by heaptrack */ |
127 | | #if defined(__APPLE__) |
128 | | "leak:*gmtsub*\n" |
129 | | "leak:ImageLoaderMachO::doImageInit\n" |
130 | | "leak:_st_tzset_basic\n" |
131 | | "leak:attachCategories\n" |
132 | | "leak:fork\n" |
133 | | "leak:getaddrinfo\n" |
134 | | "leak:getpwuid_r\n" |
135 | | "leak:libSystem_atfork_child\n" |
136 | | "leak:libsystem_notify\n" |
137 | | "leak:load_images\n" |
138 | | "leak:newlocale\n" |
139 | | /* Perl >= 5.32.0 - Upstream bug, tracked by https://github.com/Perl/perl5/issues/18108 */ |
140 | | "leak:perl_construct\n" |
141 | | "leak:realizeClassWithoutSwift\n" |
142 | | "leak:tzset\n" |
143 | | "leak:tzsetwall_basic\n" |
144 | | #elif defined(__linux__) |
145 | | "leak:*getpwnam_r*\n" /* libc startup leak - reported by heaptrack */ |
146 | 13 | "leak:_dl_init\n" /* dl startup leak - reported by heaptrack */ |
147 | 13 | "leak:initgroups\n" /* libc startup leak - reported by heaptrack */ |
148 | 13 | "leak:kqueue\n" |
149 | 13 | #endif |
150 | 13 | ; |
151 | 13 | } |
152 | | |
153 | | /** Callback for LSAN - do not rename |
154 | | * |
155 | | * Turn off suppressions by default as it interferes with interpreting |
156 | | * output from some of the test utilities. |
157 | | */ |
158 | | char const CC_HINT(used) *__lsan_default_options(void) |
159 | 0 | { |
160 | 0 | return "print_suppressions=0"; |
161 | 0 | } |
162 | | |
163 | | /** Callback for LSAN - do not rename |
164 | | * |
165 | | */ |
166 | | int CC_HINT(used) __lsan_is_turned_off(void) |
167 | 38 | { |
168 | 38 | uint8_t ret = 1; |
169 | | |
170 | | /* Disable LSAN explicitly - Used for tests involving fork() */ |
171 | 38 | if (lsan_disable) return 1; |
172 | | |
173 | | /* Parent */ |
174 | 38 | if (lsan_test_pid != 0) return 0; |
175 | | |
176 | | /* Child */ |
177 | 22 | if (write(lsan_test_pipe[1], &ret, sizeof(ret)) < 0) { |
178 | 0 | fprintf(stderr, "Writing LSAN status failed: %s", fr_syserror(errno)); |
179 | 0 | } |
180 | 22 | close(lsan_test_pipe[1]); |
181 | 22 | return 0; |
182 | 38 | } |
183 | | DIAG_ON(missing-prototypes) |
184 | | |
185 | | /** Determine if we're running under LSAN (Leak Sanitizer) |
186 | | * |
187 | | * @return |
188 | | * - 0 if we're not. |
189 | | * - 1 if we are. |
190 | | * - -1 if we can't tell because of an error. |
191 | | * - -2 if we can't tell because we were compiled with support for the LSAN interface. |
192 | | */ |
193 | | int fr_get_lsan_state(void) |
194 | 38 | { |
195 | 38 | uint8_t ret = 0; |
196 | | |
197 | 38 | if (lsan_state != INT_MAX) return lsan_state;/* Use cached result */ |
198 | | |
199 | 38 | if (pipe(lsan_test_pipe) < 0) { |
200 | 0 | fr_strerror_printf("Failed opening internal pipe: %s", fr_syserror(errno)); |
201 | 0 | return -1; |
202 | 0 | } |
203 | | |
204 | 38 | lsan_test_pid = fork(); |
205 | 38 | if (lsan_test_pid == -1) { |
206 | 0 | fr_strerror_printf("Error forking: %s", fr_syserror(errno)); |
207 | 0 | return -1; |
208 | 0 | } |
209 | | |
210 | | /* Child */ |
211 | 38 | if (lsan_test_pid == 0) { |
212 | 22 | close(lsan_test_pipe[0]); /* Close parent's side */ |
213 | 22 | exit(EXIT_SUCCESS); /* Results in LSAN calling __lsan_is_turned_off via onexit handler */ |
214 | 22 | } |
215 | | |
216 | | /* Parent */ |
217 | 16 | close(lsan_test_pipe[1]); /* Close child's side */ |
218 | | |
219 | 16 | while ((read(lsan_test_pipe[0], &ret, sizeof(ret)) < 0) && (errno == EINTR)); |
220 | | |
221 | 16 | close(lsan_test_pipe[0]); /* Close our side (so we don't leak FDs) */ |
222 | | |
223 | | /* Collect child */ |
224 | 16 | waitpid(lsan_test_pid, NULL, 0); |
225 | | |
226 | 16 | lsan_state = ret; /* Cache test results */ |
227 | | |
228 | 16 | return ret; |
229 | 38 | } |
230 | | #else |
231 | | int fr_get_lsan_state(void) |
232 | | { |
233 | | fr_strerror_const("Not built with support for LSAN interface"); |
234 | | return -2; |
235 | | } |
236 | | #endif |
237 | | |
238 | | #if defined(HAVE_SYS_PROCCTL_H) |
239 | | int fr_get_debug_state(void) |
240 | | { |
241 | | int status; |
242 | | |
243 | | if (procctl(P_PID, getpid(), PROC_TRACE_STATUS, &status) == -1) { |
244 | | fr_strerror_printf("Cannot get dumpable flag: procctl(PROC_TRACE_STATUS) failed: %s", fr_syserror(errno)); |
245 | | return DEBUGGER_STATE_UNKNOWN; |
246 | | } |
247 | | |
248 | | /* |
249 | | * As FreeBSD docs say about "PROC_TRACE_STATUS": |
250 | | * |
251 | | * Returns the current tracing status for the specified process in the |
252 | | * integer variable pointed to by data. If tracing is disabled, data |
253 | | * is set to -1. If tracing is enabled, but no debugger is attached by |
254 | | * the ptrace(2) syscall, data is set to 0. If a debugger is attached, |
255 | | * data is set to the pid of the debugger process. |
256 | | */ |
257 | | if (status <= 0) return DEBUGGER_STATE_NOT_ATTACHED; |
258 | | |
259 | | return DEBUGGER_STATE_ATTACHED; |
260 | | } |
261 | | #elif defined(__APPLE__) |
262 | | /** The ptrace_attach() method no longer works as of macOS 11.4 (we always get eperm) |
263 | | * |
264 | | * Apple published this helpful article here which provides the |
265 | | * magical invocation: https://developer.apple.com/library/archive/qa/qa1361/_index.html |
266 | | * |
267 | | * @return |
268 | | * - 0 if we're not. |
269 | | * - 1 if we are. |
270 | | * - -1 |
271 | | */ |
272 | | int fr_get_debug_state(void) |
273 | | { |
274 | | int ret; |
275 | | int mib[4]; |
276 | | struct kinfo_proc info; |
277 | | size_t size; |
278 | | |
279 | | /* |
280 | | * Initialize the flags so that, if sysctl fails for some |
281 | | * reason, we get a predictable result. |
282 | | */ |
283 | | info.kp_proc.p_flag = 0; |
284 | | |
285 | | /* |
286 | | * Initialize mib, which tells sysctl the info we want, in this case |
287 | | * we're looking for information about a specific process ID. |
288 | | */ |
289 | | mib[0] = CTL_KERN; |
290 | | mib[1] = KERN_PROC; |
291 | | mib[2] = KERN_PROC_PID; |
292 | | mib[3] = getpid(); |
293 | | |
294 | | /* Call sysctl */ |
295 | | size = sizeof(info); |
296 | | ret = sysctl(mib, NUM_ELEMENTS(mib), &info, &size, NULL, 0); |
297 | | if (ret != 0) return -1; |
298 | | |
299 | | /* We're being debugged if the P_TRACED flag is set */ |
300 | | return ((info.kp_proc.p_flag & P_TRACED) != 0); |
301 | | } |
302 | | #elif defined(HAVE_SYS_PTRACE_H) && !defined(__EMSCRIPTEN__) |
303 | | /** Determine if we're running under a debugger by attempting to attach using pattach |
304 | | * |
305 | | * @return |
306 | | * - 0 if we're not. |
307 | | * - 1 if we are. |
308 | | * - -1 if we can't tell because of an error. |
309 | | * - -2 if we can't tell because we don't have the CAP_SYS_PTRACE capability. |
310 | | */ |
311 | | int fr_get_debug_state(void) |
312 | 16 | { |
313 | 16 | int pid; |
314 | | |
315 | 16 | int from_child[2] = {-1, -1}; |
316 | | |
317 | | #ifdef HAVE_CAPABILITY_H |
318 | | cap_flag_value_t state; |
319 | | cap_t caps; |
320 | | |
321 | | /* |
322 | | * If we're running under linux, we first need to check if we have |
323 | | * permission to to ptrace. We do that using the capabilities |
324 | | * functions. |
325 | | */ |
326 | | caps = cap_get_proc(); |
327 | | if (!caps) { |
328 | | fr_strerror_printf("Failed getting process capabilities: %s", fr_syserror(errno)); |
329 | | return DEBUGGER_STATE_UNKNOWN; |
330 | | } |
331 | | |
332 | | if (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_PERMITTED, &state) < 0) { |
333 | | fr_strerror_printf("Failed getting CAP_SYS_PTRACE permitted state: %s", |
334 | | fr_syserror(errno)); |
335 | | cap_free(caps); |
336 | | return DEBUGGER_STATE_UNKNOWN; |
337 | | } |
338 | | |
339 | | if ((state == CAP_SET) && (cap_get_flag(caps, CAP_SYS_PTRACE, CAP_EFFECTIVE, &state) < 0)) { |
340 | | fr_strerror_printf("Failed getting CAP_SYS_PTRACE effective state: %s", |
341 | | fr_syserror(errno)); |
342 | | cap_free(caps); |
343 | | return DEBUGGER_STATE_UNKNOWN; |
344 | | } |
345 | | |
346 | | /* |
347 | | * We don't have permission to ptrace, so this test will always fail. |
348 | | */ |
349 | | if (state == CAP_CLEAR) { |
350 | | fr_strerror_printf("ptrace capability not set. If debugger detection is required run as root or: " |
351 | | "setcap cap_sys_ptrace+ep <path_to_binary>"); |
352 | | cap_free(caps); |
353 | | return DEBUGGER_STATE_UNKNOWN_NO_PTRACE_CAP; |
354 | | } |
355 | | cap_free(caps); |
356 | | #endif |
357 | | |
358 | 16 | if (pipe(from_child) < 0) { |
359 | 0 | fr_strerror_printf("Error opening internal pipe: %s", fr_syserror(errno)); |
360 | 0 | return DEBUGGER_STATE_UNKNOWN; |
361 | 0 | } |
362 | | |
363 | 16 | pid = fork(); |
364 | 16 | if (pid == -1) { |
365 | 0 | fr_strerror_printf("Error forking: %s", fr_syserror(errno)); |
366 | 0 | return DEBUGGER_STATE_UNKNOWN; |
367 | 0 | } |
368 | | |
369 | | /* Child */ |
370 | 16 | if (pid == 0) { |
371 | 0 | int8_t ret = DEBUGGER_STATE_NOT_ATTACHED; |
372 | 0 | int ppid = getppid(); |
373 | 0 | int flags; |
374 | | |
375 | | /* |
376 | | * Disable the leak checker for this forked process |
377 | | * so we don't get spurious leaks reported. |
378 | | */ |
379 | 0 | #ifdef HAVE_SANITIZER_LSAN_INTERFACE_H |
380 | 0 | lsan_disable = true; |
381 | 0 | #endif |
382 | |
|
383 | 0 | DIAG_OFF(deprecated-declarations) |
384 | 0 | flags = PT_ATTACH; |
385 | 0 | DIAG_ON(deprecated-declarations) |
386 | | |
387 | | /* Close parent's side */ |
388 | 0 | close(from_child[0]); |
389 | | |
390 | | /* |
391 | | * FreeBSD is extremely picky about the order of operations here |
392 | | * we need to attach, wait *then* write whilst the parent is still |
393 | | * suspended, then detach, continuing the process. |
394 | | * |
395 | | * If we don't do it in that order the read in the parent triggers |
396 | | * a SIGKILL. |
397 | | */ |
398 | 0 | errno = 0; |
399 | 0 | _PTRACE(flags, ppid); |
400 | 0 | if (errno == 0) { |
401 | | /* Wait for the parent to stop */ |
402 | 0 | waitpid(ppid, NULL, 0); |
403 | | |
404 | | /* Tell the parent what happened */ |
405 | 0 | send_status: |
406 | 0 | if (write(from_child[1], &ret, sizeof(ret)) < 0) { |
407 | 0 | fprintf(stderr, "Writing ptrace status to parent failed: %s\n", fr_syserror(errno)); |
408 | 0 | } |
409 | | |
410 | | /* Detach */ |
411 | 0 | _PTRACE_DETACH(ppid); |
412 | | |
413 | | |
414 | | /* |
415 | | * We call _exit() instead of exit(). This means that we skip the atexit() handlers, |
416 | | * which don't need to run in a temporary child process. Skipping them means that we |
417 | | * avoid dirtying those pages to "clean things up", which is then immediately followed by |
418 | | * exiting. |
419 | | * |
420 | | * Skipping the atexit() handlers also means that we're not worried about memory leaks |
421 | | * because things "aren't cleaned up correctly". We're not exiting cleanly here (and |
422 | | * don't care to exit cleanly). So just exiting with no cleanups is fine. |
423 | | */ |
424 | 0 | _exit(0); /* don't run the atexit() handlers. */ |
425 | | /* |
426 | | * man ptrace says the following: |
427 | | * |
428 | | * EPERM The specified process cannot be traced. This could be |
429 | | * because the tracer has insufficient privileges (the |
430 | | * required capability is CAP_SYS_PTRACE); unprivileged |
431 | | * processes cannot trace processes that they cannot send |
432 | | * signals to or those running set-user-ID/set-group-ID |
433 | | * programs, for obvious reasons. Alternatively, the process |
434 | | * may already be being traced, or (before Linux 2.6.26) be |
435 | | * init(1) (PID 1). |
436 | | * |
437 | | * In any case, we are very unlikely to be able to attach to |
438 | | * the process from the panic action. |
439 | | * |
440 | | * We checked for CAP_SYS_PTRACE previously, so know that |
441 | | * we _should_ haven been ablle to attach, so if we can't, it's |
442 | | * likely that we're already being traced. |
443 | | */ |
444 | 0 | } else if (errno == EPERM) { |
445 | 0 | ret = DEBUGGER_STATE_ATTACHED; |
446 | 0 | goto send_status; |
447 | 0 | } |
448 | | |
449 | | /* |
450 | | * Unexpected error, we don't know whether we're already running |
451 | | * under a debugger or not... |
452 | | */ |
453 | 0 | ret = DEBUGGER_STATE_UNKNOWN; |
454 | 0 | fprintf(stderr, "Debugger check failed to attach to parent with unexpected error: %s\n", fr_syserror(errno)); |
455 | 0 | goto send_status; |
456 | | /* Parent */ |
457 | 16 | } else { |
458 | 16 | int8_t ret = DEBUGGER_STATE_UNKNOWN; |
459 | | |
460 | | /* |
461 | | * The child writes errno (reason) if pattach failed else 0. |
462 | | * |
463 | | * This read may be interrupted by pattach, |
464 | | * which is why we need the loop. |
465 | | */ |
466 | 16 | while ((read(from_child[0], &ret, sizeof(ret)) < 0) && (errno == EINTR)); |
467 | | |
468 | | /* Close the pipes here (if we did it above, it might race with pattach) */ |
469 | 16 | close(from_child[1]); |
470 | 16 | close(from_child[0]); |
471 | | |
472 | | /* Collect the status of the child */ |
473 | 16 | waitpid(pid, NULL, 0); |
474 | | |
475 | 16 | return ret; |
476 | 16 | } |
477 | 16 | } |
478 | | #else |
479 | | int fr_get_debug_state(void) |
480 | | { |
481 | | fr_strerror_const("PTRACE not available"); |
482 | | |
483 | | return DEBUGGER_STATE_UNKNOWN_NO_PTRACE; |
484 | | } |
485 | | #endif |
486 | | |
487 | | /** Should be run before using setuid or setgid to get useful results |
488 | | * |
489 | | * @note sets the fr_debug_state global. |
490 | | */ |
491 | | void fr_debug_state_store(void) |
492 | 0 | { |
493 | 0 | fr_debug_state = fr_get_debug_state(); |
494 | |
|
495 | 0 | #ifndef NDEBUG |
496 | | /* |
497 | | * There are many reasons why this might happen with |
498 | | * a vanilla install, so we don't want to spam users |
499 | | * with messages they won't understand and may not |
500 | | * want to resolve. |
501 | | */ |
502 | 0 | if (fr_debug_state < 0) fprintf(stderr, "Getting debug state failed: %s\n", fr_strerror()); |
503 | 0 | #endif |
504 | 0 | } |
505 | | |
506 | | /** Return current value of debug_state |
507 | | * |
508 | | * @param state to translate into a humanly readable value. |
509 | | * @return humanly readable version of debug state. |
510 | | */ |
511 | | char const *fr_debug_state_to_msg(fr_debug_state_t state) |
512 | 0 | { |
513 | 0 | switch (state) { |
514 | 0 | case DEBUGGER_STATE_UNKNOWN_NO_PTRACE: |
515 | 0 | return "Debug state unknown (ptrace functionality not available)"; |
516 | | |
517 | 0 | case DEBUGGER_STATE_UNKNOWN_NO_PTRACE_CAP: |
518 | 0 | return "Debug state unknown (cap_sys_ptrace capability not set)"; |
519 | | |
520 | 0 | case DEBUGGER_STATE_UNKNOWN: |
521 | 0 | return "Debug state unknown"; |
522 | | |
523 | 0 | case DEBUGGER_STATE_ATTACHED: |
524 | 0 | return "Found debugger attached"; |
525 | | |
526 | 0 | case DEBUGGER_STATE_NOT_ATTACHED: |
527 | 0 | return "Debugger not attached"; |
528 | 0 | } |
529 | | |
530 | 0 | return "<INVALID>"; |
531 | 0 | } |
532 | | |
533 | | /** Break in debugger (if were running under a debugger) |
534 | | * |
535 | | * If the server is running under a debugger this will raise a |
536 | | * SIGTRAP which will pause the running process. |
537 | | * |
538 | | * If the server is not running under debugger then this will do nothing. |
539 | | */ |
540 | | void fr_debug_break(bool always) |
541 | 0 | { |
542 | 0 | if (always) raise(SIGTRAP); |
543 | |
|
544 | 0 | if (fr_debug_state < 0) fr_debug_state = fr_get_debug_state(); |
545 | 0 | if (fr_debug_state == DEBUGGER_STATE_ATTACHED) { |
546 | 0 | fprintf(stderr, "Debugger detected, raising SIGTRAP\n"); |
547 | 0 | fflush(stderr); |
548 | |
|
549 | 0 | raise(SIGTRAP); |
550 | 0 | } |
551 | 0 | } |
552 | | |
553 | | static int _panic_on_free(UNUSED char *foo) |
554 | 0 | { |
555 | 0 | fr_fault(SIGABRT); |
556 | 0 | return -1; /* this should make the free fail */ |
557 | 0 | } |
558 | | |
559 | | /** Insert memory into the context of another talloc memory chunk which |
560 | | * causes a panic when freed. |
561 | | * |
562 | | * @param ctx TALLOC_CTX to monitor for frees. |
563 | | */ |
564 | | void fr_panic_on_free(TALLOC_CTX *ctx) |
565 | 0 | { |
566 | 0 | char *ptr; |
567 | |
|
568 | 0 | ptr = talloc(ctx, char); |
569 | 0 | talloc_set_destructor(ptr, _panic_on_free); |
570 | 0 | } |
571 | | |
572 | | /** Set the dumpable flag, also controls whether processes can PATTACH |
573 | | * |
574 | | * @param dumpable whether we should allow core dumping |
575 | | */ |
576 | | #if defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE) && !defined(__EMSCRIPTEN__) |
577 | | static int fr_set_pr_dumpable_flag(bool dumpable) |
578 | 0 | { |
579 | 0 | if (prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0) < 0) { |
580 | 0 | fr_strerror_printf("Cannot re-enable core dumps: prctl(PR_SET_DUMPABLE) failed: %s", |
581 | 0 | fr_syserror(errno)); |
582 | 0 | return -1; |
583 | 0 | } |
584 | | |
585 | 0 | return 0; |
586 | 0 | } |
587 | | #elif defined(HAVE_SYS_PROCCTL_H) |
588 | | static int fr_set_pr_dumpable_flag(bool dumpable) |
589 | | { |
590 | | int mode = dumpable ? PROC_TRACE_CTL_ENABLE : PROC_TRACE_CTL_DISABLE; |
591 | | |
592 | | if (procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode) == -1) { |
593 | | fr_strerror_printf("Cannot re-enable core dumps: procctl(PROC_TRACE_CTL) failed: %s", |
594 | | fr_syserror(errno)); |
595 | | return -1; |
596 | | } |
597 | | |
598 | | return 0; |
599 | | } |
600 | | #else |
601 | | static int fr_set_pr_dumpable_flag(UNUSED bool dumpable) |
602 | | { |
603 | | fr_strerror_const("Changing value of PR_DUMPABLE not supported on this system"); |
604 | | return -2; |
605 | | } |
606 | | #endif |
607 | | |
608 | | /** Get the processes dumpable flag |
609 | | * |
610 | | */ |
611 | | #if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_DUMPABLE) && !defined(__EMSCRIPTEN__) |
612 | | static int fr_get_pr_dumpable_flag(void) |
613 | 0 | { |
614 | 0 | int ret; |
615 | |
|
616 | 0 | ret = prctl(PR_GET_DUMPABLE); |
617 | 0 | if (ret < 0) { |
618 | 0 | fr_strerror_printf("Cannot get dumpable flag: %s", fr_syserror(errno)); |
619 | 0 | return -1; |
620 | 0 | } |
621 | | |
622 | | /* |
623 | | * Linux is crazy and prctl sometimes returns 2 for disabled |
624 | | */ |
625 | 0 | if (ret != 1) return 0; |
626 | 0 | return 1; |
627 | 0 | } |
628 | | #elif defined(HAVE_SYS_PROCCTL_H) |
629 | | static int fr_get_pr_dumpable_flag(void) |
630 | | { |
631 | | int status; |
632 | | |
633 | | if (procctl(P_PID, getpid(), PROC_TRACE_CTL, &status) == -1) { |
634 | | fr_strerror_printf("Cannot get dumpable flag: procctl(PROC_TRACE_CTL) failed: %s", fr_syserror(errno)); |
635 | | return -1; |
636 | | } |
637 | | |
638 | | /* |
639 | | * There are a few different kinds of disabled, but only |
640 | | * one ENABLE. |
641 | | */ |
642 | | if (status != PROC_TRACE_CTL_ENABLE) return 0; |
643 | | |
644 | | return 1; |
645 | | } |
646 | | #else |
647 | | static int fr_get_pr_dumpable_flag(void) |
648 | | { |
649 | | fr_strerror_const("Getting value of PR_DUMPABLE not supported on this system"); |
650 | | return -2; |
651 | | } |
652 | | #endif |
653 | | |
654 | | |
655 | | /** Get the current maximum for core files |
656 | | * |
657 | | * Do this before anything else so as to ensure it's properly initialized. |
658 | | */ |
659 | | int fr_set_dumpable_init(void) |
660 | 0 | { |
661 | 0 | #ifdef HAVE_SYS_RESOURCE_H |
662 | 0 | if (getrlimit(RLIMIT_CORE, &init_core_limit) < 0) { |
663 | 0 | fr_strerror_printf("Failed to get current core limit: %s", fr_syserror(errno)); |
664 | 0 | return -1; |
665 | 0 | } |
666 | 0 | #endif |
667 | 0 | return 0; |
668 | 0 | } |
669 | | |
670 | | /** Enable or disable core dumps |
671 | | * |
672 | | * @param allow_core_dumps whether to enable or disable core dumps. |
673 | | */ |
674 | | int fr_set_dumpable(bool allow_core_dumps) |
675 | 0 | { |
676 | 0 | dump_core = allow_core_dumps; |
677 | |
|
678 | 0 | #ifdef HAVE_SYS_RESOURCE_H |
679 | 0 | { |
680 | 0 | struct rlimit current; |
681 | | |
682 | | /* |
683 | | * Reset the core limits (or disable them) |
684 | | */ |
685 | 0 | if (getrlimit(RLIMIT_CORE, ¤t) < 0) { |
686 | 0 | fr_strerror_printf("Failed to get current core limit: %s", fr_syserror(errno)); |
687 | 0 | return -1; |
688 | 0 | } |
689 | | |
690 | 0 | if (allow_core_dumps) { |
691 | 0 | if ((current.rlim_cur != init_core_limit.rlim_cur) || |
692 | 0 | (current.rlim_max != init_core_limit.rlim_max)) { |
693 | 0 | if (setrlimit(RLIMIT_CORE, &init_core_limit) < 0) { |
694 | 0 | fr_strerror_printf("Cannot update core dump limit: %s", fr_syserror(errno)); |
695 | |
|
696 | 0 | return -1; |
697 | 0 | } |
698 | 0 | } |
699 | | /* |
700 | | * We've been told to disable core dumping, |
701 | | * rlim_cur is not set to zero. |
702 | | * |
703 | | * Set rlim_cur to zero, but leave rlim_max |
704 | | * set to whatever the current value is. |
705 | | * |
706 | | * This is because, later, we may need to |
707 | | * re-enable core dumps to allow the debugger |
708 | | * to attach *sigh*. |
709 | | */ |
710 | 0 | } else if (current.rlim_cur != 0) { |
711 | 0 | struct rlimit no_core; |
712 | |
|
713 | 0 | no_core.rlim_cur = 0; |
714 | 0 | no_core.rlim_max = current.rlim_max; |
715 | |
|
716 | 0 | if (setrlimit(RLIMIT_CORE, &no_core) < 0) { |
717 | 0 | fr_strerror_printf("Failed disabling core dumps: %s", fr_syserror(errno)); |
718 | |
|
719 | 0 | return -1; |
720 | 0 | } |
721 | 0 | } |
722 | 0 | } |
723 | 0 | #endif |
724 | | /* |
725 | | * Macro needed so we don't emit spurious errors |
726 | | */ |
727 | 0 | #if defined(HAVE_SYS_PROCCTL_H) || (defined(HAVE_SYS_PRCTL_H) && defined(PR_SET_DUMPABLE)) |
728 | 0 | if (fr_set_pr_dumpable_flag(allow_core_dumps) < 0) return -1; |
729 | 0 | #endif |
730 | | |
731 | 0 | return 0; |
732 | 0 | } |
733 | | |
734 | | /** Reset dumpable state to previously configured value |
735 | | * |
736 | | * Needed after suid up/down |
737 | | * |
738 | | * @return |
739 | | * - 0 on success. |
740 | | * - -1 on failure. |
741 | | */ |
742 | | int fr_reset_dumpable(void) |
743 | 0 | { |
744 | 0 | return fr_set_dumpable(dump_core); |
745 | 0 | } |
746 | | |
747 | | /** Check to see if panic_action file is world writable |
748 | | * |
749 | | * @return |
750 | | * - 0 if file is OK. |
751 | | * - -1 if the file is world writable. |
752 | | */ |
753 | | static int fr_fault_check_permissions(void) |
754 | 16 | { |
755 | 16 | char const *p, *q; |
756 | 16 | size_t len; |
757 | 16 | char filename[256]; |
758 | 16 | struct stat statbuf; |
759 | | |
760 | | /* |
761 | | * Try and guess which part of the command is the binary, and check to see if |
762 | | * it's world writable, to try and save the admin from their own stupidity. |
763 | | * |
764 | | * @fixme we should do this properly and take into account single and double |
765 | | * quotes. |
766 | | */ |
767 | 16 | if ((q = strchr(panic_action, ' '))) { |
768 | | /* |
769 | | * need to use a static buffer, because allocing memory in a signal handler |
770 | | * is a bad idea and can result in deadlock. |
771 | | */ |
772 | 0 | len = snprintf(filename, sizeof(filename), "%.*s", (int)(q - panic_action), panic_action); |
773 | 0 | if (is_truncated(len, sizeof(filename))) { |
774 | 0 | fr_strerror_const("Failed writing panic_action to temporary buffer (truncated)"); |
775 | 0 | return -1; |
776 | 0 | } |
777 | 0 | p = filename; |
778 | 16 | } else { |
779 | 16 | p = panic_action; |
780 | 16 | } |
781 | | |
782 | 16 | if (stat(p, &statbuf) == 0) { |
783 | 0 | #ifdef S_IWOTH |
784 | 0 | if ((statbuf.st_mode & S_IWOTH) != 0) { |
785 | 0 | fr_strerror_printf("panic_action file \"%s\" is globally writable", p); |
786 | 0 | return -1; |
787 | 0 | } |
788 | 0 | #endif |
789 | 0 | } |
790 | | |
791 | 16 | return 0; |
792 | 16 | } |
793 | | |
794 | | /** Prints a simple backtrace (if execinfo is available) and calls panic_action if set. |
795 | | * |
796 | | * @param sig caught |
797 | | */ |
798 | | NEVER_RETURNS void fr_fault(int sig) |
799 | 0 | { |
800 | 0 | char cmd[sizeof(panic_action) + 20]; |
801 | 0 | char *out = cmd; |
802 | 0 | size_t left = sizeof(cmd), ret; |
803 | |
|
804 | 0 | char const *p = panic_action; |
805 | 0 | char const *q; |
806 | |
|
807 | 0 | int code; |
808 | | |
809 | | /* |
810 | | * If a debugger is attached, we don't want to run the panic action, |
811 | | * as it may interfere with the operation of the debugger. |
812 | | * If something calls us directly we just raise the signal and let |
813 | | * the debugger handle it how it wants. |
814 | | */ |
815 | 0 | if (fr_debug_state == DEBUGGER_STATE_ATTACHED) { |
816 | 0 | FR_FAULT_LOG("RAISING SIGNAL: %s", strsignal(sig)); |
817 | 0 | raise(sig); |
818 | 0 | } |
819 | | |
820 | | /* |
821 | | * Makes the backtraces slightly cleaner |
822 | | */ |
823 | 0 | memset(cmd, 0, sizeof(cmd)); |
824 | |
|
825 | 0 | FR_FAULT_LOG("CAUGHT SIGNAL: %s", strsignal(sig)); |
826 | | |
827 | | /* |
828 | | * Run the callback if one was registered |
829 | | */ |
830 | 0 | if (panic_cb && (panic_cb(sig) < 0)) goto finish; |
831 | | |
832 | 0 | fr_backtrace(); |
833 | | |
834 | | /* No panic action set... */ |
835 | 0 | if (panic_action[0] == '\0') { |
836 | 0 | FR_FAULT_LOG("No panic action set"); |
837 | 0 | goto finish; |
838 | 0 | } |
839 | | |
840 | | /* |
841 | | * Check for administrator sanity. |
842 | | */ |
843 | 0 | if (fr_fault_check_permissions() < 0) { |
844 | 0 | FR_FAULT_LOG("Refusing to execute panic action: %s", fr_strerror()); |
845 | 0 | goto finish; |
846 | 0 | } |
847 | | |
848 | | /* Substitute %p for the current PID (useful for attaching a debugger) */ |
849 | 0 | while ((q = strstr(p, "%p"))) { |
850 | 0 | out += ret = snprintf(out, left, "%.*s%d", (int) (q - p), p, (int) getpid()); |
851 | 0 | if (left <= ret) { |
852 | 0 | oob: |
853 | 0 | FR_FAULT_LOG("Panic action too long"); |
854 | 0 | fr_exit_now(128 + sig); |
855 | 0 | } |
856 | 0 | left -= ret; |
857 | 0 | p = q + 2; |
858 | 0 | } |
859 | 0 | if (strlen(p) >= left) goto oob; |
860 | 0 | strlcpy(out, p, left); |
861 | |
|
862 | 0 | { |
863 | 0 | bool disable = false; |
864 | |
|
865 | 0 | FR_FAULT_LOG("Calling: %s", cmd); |
866 | | |
867 | | /* |
868 | | * Here we temporarily enable the dumpable flag so if GBD or LLDB |
869 | | * is called in the panic_action, they can pattach to the running |
870 | | * process. |
871 | | */ |
872 | 0 | if (fr_get_pr_dumpable_flag() == 0) { |
873 | 0 | if ((fr_set_pr_dumpable_flag(true) < 0) || !fr_get_pr_dumpable_flag()) { |
874 | 0 | FR_FAULT_LOG("Failed setting dumpable flag, pattach may not work: %s", fr_strerror()); |
875 | 0 | } else { |
876 | 0 | disable = true; |
877 | 0 | } |
878 | 0 | FR_FAULT_LOG("Temporarily setting PR_DUMPABLE to 1"); |
879 | 0 | } |
880 | |
|
881 | 0 | code = system(cmd); |
882 | | |
883 | | /* |
884 | | * We only want to error out here, if dumpable was originally disabled |
885 | | * and we managed to change the value to enabled, but failed |
886 | | * setting it back to disabled. |
887 | | */ |
888 | 0 | if (disable) { |
889 | 0 | FR_FAULT_LOG("Resetting PR_DUMPABLE to 0"); |
890 | 0 | if (fr_set_pr_dumpable_flag(false) < 0) { |
891 | 0 | FR_FAULT_LOG("Failed resetting dumpable flag to off: %s", fr_strerror()); |
892 | 0 | FR_FAULT_LOG("Exiting due to insecure process state"); |
893 | 0 | fr_exit_now(EXIT_FAILURE); |
894 | 0 | } |
895 | 0 | } |
896 | |
|
897 | 0 | FR_FAULT_LOG("Panic action exited with %i", code); |
898 | |
|
899 | 0 | fr_exit_now(128 + sig); |
900 | 0 | } |
901 | |
|
902 | 0 | finish: |
903 | | /* |
904 | | * (Re-)Raise the signal, so that if we're running under |
905 | | * a debugger. |
906 | | * |
907 | | * This allows debuggers to function normally and catch |
908 | | * fatal signals. |
909 | | */ |
910 | 0 | fr_unset_signal(sig); /* Make sure we don't get into a loop */ |
911 | 0 | raise(sig); |
912 | 0 | fr_exit_now(128 + sig); /* Function marked as noreturn */ |
913 | 0 | } |
914 | | |
915 | | /** Callback executed on fatal talloc error |
916 | | * |
917 | | * This is the simple version which mostly behaves the same way as the default |
918 | | * one, and will not call panic_action. |
919 | | * |
920 | | * @param reason string provided by talloc. |
921 | | */ |
922 | | static void _fr_talloc_fault_simple(char const *reason) CC_HINT(noreturn); |
923 | | static void _fr_talloc_fault_simple(char const *reason) |
924 | 0 | { |
925 | 0 | FR_FAULT_LOG("talloc abort: %s\n", reason); |
926 | |
|
927 | 0 | fr_backtrace(); |
928 | 0 | abort(); |
929 | 0 | } |
930 | | |
931 | | /** Callback executed on fatal talloc error |
932 | | * |
933 | | * Translates a talloc abort into a fr_fault call. |
934 | | * Mostly to work around issues with some debuggers not being able to |
935 | | * attach after a SIGABRT has been raised. |
936 | | * |
937 | | * @param reason string provided by talloc. |
938 | | */ |
939 | | static void _fr_talloc_fault(char const *reason) CC_HINT(noreturn); |
940 | | static void _fr_talloc_fault(char const *reason) |
941 | 0 | { |
942 | 0 | FR_FAULT_LOG("talloc abort: %s", reason); |
943 | 0 | #ifdef SIGABRT |
944 | 0 | fr_fault(SIGABRT); |
945 | 0 | #endif |
946 | 0 | fr_exit_now(128 + SIGABRT); |
947 | 0 | } |
948 | | |
949 | | /** Wrapper to pass talloc log output to our fr_fault_log function |
950 | | * |
951 | | */ |
952 | | static void _fr_talloc_log(char const *msg) |
953 | 0 | { |
954 | 0 | fr_fault_log("%s\n", msg); |
955 | 0 | } |
956 | | |
957 | | /** Generate a talloc memory report for a context and print to stderr/stdout |
958 | | * |
959 | | * @param ctx to generate a report for, may be NULL in which case the root context is used. |
960 | | */ |
961 | | int fr_log_talloc_report(TALLOC_CTX const *ctx) |
962 | 0 | { |
963 | 0 | #define TALLOC_REPORT_MAX_DEPTH 20 |
964 | |
|
965 | 0 | FILE *log; |
966 | 0 | int fd; |
967 | |
|
968 | 0 | fd = dup(fr_fault_log_fd); |
969 | 0 | if (fd < 0) { |
970 | 0 | fr_strerror_printf("Couldn't write memory report, failed to dup log fd: %s", fr_syserror(errno)); |
971 | 0 | return -1; |
972 | 0 | } |
973 | 0 | log = fdopen(fd, "w"); |
974 | 0 | if (!log) { |
975 | 0 | close(fd); |
976 | 0 | fr_strerror_printf("Couldn't write memory report, fdopen failed: %s", fr_syserror(errno)); |
977 | 0 | return -1; |
978 | 0 | } |
979 | | |
980 | 0 | if (!ctx) { |
981 | 0 | fprintf(log, "Current state of talloced memory:\n"); |
982 | 0 | talloc_report_full(talloc_null_ctx(), log); |
983 | 0 | } else { |
984 | 0 | int i; |
985 | |
|
986 | 0 | fprintf(log, "Talloc chunk lineage:\n"); |
987 | 0 | fprintf(log, "%p (%s)", ctx, talloc_get_name(ctx)); |
988 | |
|
989 | 0 | i = 0; |
990 | 0 | while ((i < TALLOC_REPORT_MAX_DEPTH) && (ctx = talloc_parent(ctx))) { |
991 | 0 | fprintf(log, " < %p (%s)", ctx, talloc_get_name(ctx)); |
992 | 0 | i++; |
993 | 0 | } |
994 | 0 | fprintf(log, "\n"); |
995 | |
|
996 | 0 | i = 0; |
997 | 0 | do { |
998 | 0 | fprintf(log, "Talloc context level %i:\n", i++); |
999 | 0 | talloc_report_full(ctx, log); |
1000 | 0 | } while ((ctx = talloc_parent(ctx)) && |
1001 | 0 | (i < TALLOC_REPORT_MAX_DEPTH) && |
1002 | 0 | (talloc_parent(ctx) != talloc_autofree_ctx) && /* Stop before we hit the autofree ctx */ |
1003 | 0 | (talloc_parent(ctx) != talloc_null_ctx())); /* Stop before we hit NULL ctx */ |
1004 | 0 | } |
1005 | |
|
1006 | 0 | fclose(log); |
1007 | |
|
1008 | 0 | return 0; |
1009 | 0 | } |
1010 | | |
1011 | | static int _disable_null_tracking(UNUSED bool *p) |
1012 | 16 | { |
1013 | 16 | talloc_disable_null_tracking(); |
1014 | 16 | return 0; |
1015 | 16 | } |
1016 | | |
1017 | | /** Disable the null tracking context when a talloc chunk is freed |
1018 | | * |
1019 | | */ |
1020 | | void fr_disable_null_tracking_on_free(TALLOC_CTX *ctx) |
1021 | 16 | { |
1022 | 16 | bool *marker; |
1023 | | |
1024 | | /* |
1025 | | * Disable null tracking on exit, else valgrind complains |
1026 | | */ |
1027 | 16 | marker = talloc(ctx, bool); |
1028 | 16 | talloc_set_destructor(marker, _disable_null_tracking); |
1029 | 16 | } |
1030 | | |
1031 | | /** Register talloc fault handlers |
1032 | | * |
1033 | | * Just register the fault handlers we need to make talloc |
1034 | | * produce useful debugging output. |
1035 | | */ |
1036 | | void fr_talloc_fault_setup(void) |
1037 | 38 | { |
1038 | 38 | talloc_set_log_fn(_fr_talloc_log); |
1039 | 38 | talloc_set_abort_fn(_fr_talloc_fault_simple); |
1040 | 38 | } |
1041 | | |
1042 | | /** Registers signal handlers to execute panic_action on fatal signal |
1043 | | * |
1044 | | * May be called multiple time to change the panic_action/program. |
1045 | | * |
1046 | | * @param[in] ctx to allocate autofreeable resources in. |
1047 | | * @param[in] cmd to execute on fault. If present %p will be substituted |
1048 | | * for the parent PID before the command is executed, and %e |
1049 | | * will be substituted for the currently running program. |
1050 | | * @param program Name of program currently executing (argv[0]). |
1051 | | * @return |
1052 | | * - 0 on success. |
1053 | | * - -1 on failure. |
1054 | | */ |
1055 | | int fr_fault_setup(TALLOC_CTX *ctx, char const *cmd, char const *program) |
1056 | 16 | { |
1057 | 16 | static bool setup = false; |
1058 | | |
1059 | 16 | char *out = panic_action; |
1060 | 16 | size_t left = sizeof(panic_action); |
1061 | | |
1062 | 16 | char const *p = cmd; |
1063 | 16 | char const *q; |
1064 | | |
1065 | 16 | if (cmd) { |
1066 | 0 | size_t ret; |
1067 | | |
1068 | | /* Substitute %e for the current program */ |
1069 | 0 | while ((q = strstr(p, "%e"))) { |
1070 | 0 | out += ret = snprintf(out, left, "%.*s%s", (int) (q - p), p, program ? program : ""); |
1071 | 0 | if (left <= ret) { |
1072 | 0 | oob: |
1073 | 0 | fr_strerror_const("Panic action too long"); |
1074 | 0 | return -1; |
1075 | 0 | } |
1076 | 0 | left -= ret; |
1077 | 0 | p = q + 2; |
1078 | 0 | } |
1079 | 0 | if (strlen(p) >= left) goto oob; |
1080 | 0 | strlcpy(out, p, left); |
1081 | 16 | } else { |
1082 | 16 | *panic_action = '\0'; |
1083 | 16 | } |
1084 | | |
1085 | | /* |
1086 | | * Check for administrator sanity. |
1087 | | */ |
1088 | 16 | if (fr_fault_check_permissions() < 0) return -1; |
1089 | | |
1090 | | /* Unsure what the side effects of changing the signal handler mid execution might be */ |
1091 | 16 | if (!setup) { |
1092 | 16 | char *env; |
1093 | | |
1094 | | /* |
1095 | | * Installing signal handlers interferes with some debugging |
1096 | | * operations. Give the developer control over whether the |
1097 | | * signal handlers are installed or not. |
1098 | | */ |
1099 | 16 | env = getenv("DEBUGGER_ATTACHED"); |
1100 | 16 | if (env && (strcmp(env, "yes") == 0)) { |
1101 | 0 | fr_debug_state = DEBUGGER_STATE_ATTACHED; /* i.e. disable signal handlers */ |
1102 | |
|
1103 | 16 | } else if (env && (strcmp(env, "no") == 0)) { |
1104 | 0 | fr_debug_state = DEBUGGER_STATE_NOT_ATTACHED; /* i.e. enable signal handlers */ |
1105 | | |
1106 | | /* |
1107 | | * Figure out if we were started under a debugger |
1108 | | */ |
1109 | 16 | } else { |
1110 | 16 | if (fr_debug_state < 0) fr_debug_state = fr_get_debug_state(); |
1111 | 16 | } |
1112 | | |
1113 | 16 | talloc_set_log_fn(_fr_talloc_log); |
1114 | | |
1115 | | /* |
1116 | | * These signals can't be properly dealt with in the debugger |
1117 | | * if we set our own signal handlers. |
1118 | | */ |
1119 | 16 | switch (fr_debug_state) { |
1120 | 0 | default: |
1121 | 16 | case DEBUGGER_STATE_NOT_ATTACHED: |
1122 | 16 | #ifdef SIGABRT |
1123 | 16 | if (fr_set_signal(SIGABRT, fr_fault) < 0) return -1; |
1124 | | |
1125 | | /* |
1126 | | * Use this instead of abort so we get a |
1127 | | * full backtrace with broken versions of LLDB |
1128 | | */ |
1129 | 16 | talloc_set_abort_fn(_fr_talloc_fault); |
1130 | 16 | #endif |
1131 | 16 | #ifdef SIGILL |
1132 | 16 | if (fr_set_signal(SIGILL, fr_fault) < 0) return -1; |
1133 | 16 | #endif |
1134 | 16 | #ifdef SIGFPE |
1135 | 16 | if (fr_set_signal(SIGFPE, fr_fault) < 0) return -1; |
1136 | 16 | #endif |
1137 | 16 | #ifdef SIGSEGV |
1138 | 16 | if (fr_set_signal(SIGSEGV, fr_fault) < 0) return -1; |
1139 | 16 | #endif |
1140 | 16 | #ifdef SIGALRM |
1141 | | /* |
1142 | | * This is used be jlibtool to terminate |
1143 | | * processes which have been running too |
1144 | | * long. |
1145 | | */ |
1146 | 16 | if (fr_set_signal(SIGALRM, fr_fault) < 0) return -1; |
1147 | 16 | #endif |
1148 | 16 | break; |
1149 | | |
1150 | 16 | case DEBUGGER_STATE_ATTACHED: |
1151 | 0 | break; |
1152 | 16 | } |
1153 | | |
1154 | | /* |
1155 | | * Needed for memory reports |
1156 | | */ |
1157 | 16 | fr_disable_null_tracking_on_free(ctx); |
1158 | | |
1159 | 16 | #if defined(HAVE_MALLOPT) && !defined(NDEBUG) |
1160 | | /* |
1161 | | * If were using glibc malloc > 2.4 this scribbles over |
1162 | | * uninitialised and freed memory, to make memory issues easier |
1163 | | * to track down. |
1164 | | */ |
1165 | 16 | # ifdef M_PERTURB |
1166 | 16 | if (!getenv("TALLOC_FREE_FILL")) mallopt(M_PERTURB, 0x42); |
1167 | 16 | # endif |
1168 | 16 | # ifdef M_CHECK_ACTION |
1169 | 16 | mallopt(M_CHECK_ACTION, 3); |
1170 | 16 | # endif |
1171 | 16 | #endif |
1172 | 16 | fr_backtrace_init(program); |
1173 | 16 | } |
1174 | 16 | setup = true; |
1175 | | |
1176 | 16 | return 0; |
1177 | 16 | } |
1178 | | |
1179 | | /** Set a callback to be called before fr_fault() |
1180 | | * |
1181 | | * @param func to execute. If callback returns < 0 |
1182 | | * fr_fault will exit before running panic_action code. |
1183 | | */ |
1184 | | void fr_fault_set_cb(fr_fault_cb_t func) |
1185 | 0 | { |
1186 | 0 | panic_cb = func; |
1187 | 0 | } |
1188 | | |
1189 | | /** Log output to the fr_fault_log_fd |
1190 | | * |
1191 | | * We used to support a user defined callback, which was set to a radlog |
1192 | | * function. Unfortunately, when logging to syslog, syslog would alloc memory |
1193 | | * which would result in a deadlock if fr_fault was triggered from within |
1194 | | * a allocation call. |
1195 | | * |
1196 | | * Now we just write directly to the FD. |
1197 | | */ |
1198 | | void fr_fault_log(char const *msg, ...) |
1199 | 0 | { |
1200 | 0 | va_list ap; |
1201 | |
|
1202 | 0 | if (fr_fault_log_fd < 0) return; |
1203 | | |
1204 | 0 | va_start(ap, msg); |
1205 | 0 | vdprintf(fr_fault_log_fd, msg, ap); |
1206 | 0 | va_end(ap); |
1207 | 0 | } |
1208 | | |
1209 | | /** Print data as a hex block |
1210 | | * |
1211 | | */ |
1212 | | void fr_fault_log_hex(uint8_t const *data, size_t data_len) |
1213 | 0 | { |
1214 | 0 | size_t i, j, len; |
1215 | 0 | char buffer[(0x10 * 3) + 1]; |
1216 | 0 | char *p, *end = buffer + sizeof(buffer); |
1217 | |
|
1218 | 0 | for (i = 0; i < data_len; i += 0x10) { |
1219 | 0 | len = 0x10; |
1220 | 0 | if ((i + len) > data_len) len = data_len - i; |
1221 | |
|
1222 | 0 | for (p = buffer, j = 0; j < len; j++, p += 3) snprintf(p, end - p, "%02x ", data[i + j]); |
1223 | |
|
1224 | 0 | dprintf(fr_fault_log_fd, "%04x: %s\n", (unsigned int) i, buffer); |
1225 | 0 | } |
1226 | 0 | } |
1227 | | |
1228 | | /** Set a file descriptor to log memory reports to. |
1229 | | * |
1230 | | * @param fd to write output to. |
1231 | | */ |
1232 | | void fr_fault_set_log_fd(int fd) |
1233 | 0 | { |
1234 | 0 | fr_fault_log_fd = fd; |
1235 | 0 | } |
1236 | | |
1237 | | /** A soft assertion which triggers the fault handler in debug builds |
1238 | | * |
1239 | | * @param[in] file the assertion failed in. |
1240 | | * @param[in] line of the assertion in the file. |
1241 | | * @param[in] expr that was evaluated. |
1242 | | * @param[in] msg Message to print (may be NULL). |
1243 | | * @param[in] ... Arguments for msg string. |
1244 | | * @return the value of cond. |
1245 | | */ |
1246 | | bool _fr_assert_fail(char const *file, int line, char const *expr, char const *msg, ...) |
1247 | 0 | { |
1248 | 0 | if (msg) { |
1249 | 0 | char str[256]; /* Decent compilers won't allocate this unless fmt is !NULL... */ |
1250 | 0 | va_list ap; |
1251 | |
|
1252 | 0 | va_start(ap, msg); |
1253 | 0 | (void)vsnprintf(str, sizeof(str), msg, ap); |
1254 | 0 | va_end(ap); |
1255 | |
|
1256 | 0 | #ifndef NDEBUG |
1257 | 0 | FR_FAULT_LOG("ASSERT FAILED %s[%d]: %s: %s", file, line, expr, str); |
1258 | 0 | fr_fault(SIGABRT); |
1259 | | #else |
1260 | | FR_FAULT_LOG("ASSERT WOULD FAIL %s[%d]: %s: %s", file, line, expr, str); |
1261 | | return false; |
1262 | | #endif |
1263 | 0 | } |
1264 | |
|
1265 | 0 | #ifndef NDEBUG |
1266 | 0 | FR_FAULT_LOG("ASSERT FAILED %s[%d]: %s", file, line, expr); |
1267 | 0 | fr_fault(SIGABRT); |
1268 | | #else |
1269 | | FR_FAULT_LOG("ASSERT WOULD FAIL %s[%d]: %s", file, line, expr); |
1270 | | return false; |
1271 | | #endif |
1272 | 0 | } |
1273 | | |
1274 | | /** A fatal assertion which triggers the fault handler in debug builds or exits |
1275 | | * |
1276 | | * @param[in] file the assertion failed in. |
1277 | | * @param[in] line of the assertion in the file. |
1278 | | * @param[in] expr that was evaluated. |
1279 | | * @param[in] msg Message to print (may be NULL). |
1280 | | * @param[in] ... Arguments for msg string. |
1281 | | */ |
1282 | | void _fr_assert_fatal(char const *file, int line, char const *expr, char const *msg, ...) |
1283 | 0 | { |
1284 | 0 | if (msg) { |
1285 | 0 | char str[256]; /* Decent compilers won't allocate this unless fmt is !NULL... */ |
1286 | 0 | va_list ap; |
1287 | |
|
1288 | 0 | va_start(ap, msg); |
1289 | 0 | (void)vsnprintf(str, sizeof(str), msg, ap); |
1290 | 0 | va_end(ap); |
1291 | |
|
1292 | 0 | FR_FAULT_LOG("FATAL ASSERT %s[%d]: %s: %s", file, line, expr, str); |
1293 | 0 | } else { |
1294 | 0 | FR_FAULT_LOG("FATAL ASSERT %s[%d]: %s", file, line, expr); |
1295 | 0 | } |
1296 | |
|
1297 | | #ifdef NDEBUG |
1298 | | _fr_exit(file, line, 128 + SIGABRT, true); |
1299 | | #else |
1300 | 0 | fr_fault(SIGABRT); |
1301 | 0 | #endif |
1302 | 0 | } |
1303 | | |
1304 | | /** Exit possibly printing a message about why we're exiting. |
1305 | | * |
1306 | | * @note Use the fr_exit(status) macro instead of calling this function directly. |
1307 | | * |
1308 | | * @param[in] file where fr_exit() was called. |
1309 | | * @param[in] line where fr_exit() was called. |
1310 | | * @param[in] status we're exiting with. |
1311 | | * @param[in] now Exit immediately. |
1312 | | */ |
1313 | | #ifndef NDEBUG |
1314 | | NEVER_RETURNS void _fr_exit(char const *file, int line, int status, bool now) |
1315 | 0 | { |
1316 | 0 | if (status != EXIT_SUCCESS) { |
1317 | 0 | char const *error = fr_strerror(); |
1318 | |
|
1319 | 0 | if (error && *error && (status != 0)) { |
1320 | 0 | FR_FAULT_LOG("%sEXIT(%i) CALLED %s[%d]. Last error was: %s", now ? "_" : "", |
1321 | 0 | status, file, line, error); |
1322 | 0 | } else { |
1323 | 0 | FR_FAULT_LOG("%sEXIT(%i) CALLED %s[%d]", now ? "_" : "", status, file, line); |
1324 | 0 | } |
1325 | |
|
1326 | 0 | fr_debug_break(false); /* If running under GDB we'll break here */ |
1327 | 0 | } |
1328 | |
|
1329 | 0 | if (now) _Exit(status); |
1330 | 0 | exit(status); |
1331 | 0 | } |
1332 | | #else |
1333 | | NEVER_RETURNS void _fr_exit(UNUSED char const *file, UNUSED int line, int status, bool now) |
1334 | | { |
1335 | | if (status != EXIT_SUCCESS) fr_debug_break(false); /* If running under GDB we'll break here */ |
1336 | | |
1337 | | if (now) _Exit(status); |
1338 | | exit(status); |
1339 | | } |
1340 | | #endif |