| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "libnetdata/libnetdata.h" |
| 4 | #include "protected-access.h" |
| 5 | |
| 6 | __thread protected_access_t protected_access_state = {0}; |
| 7 | |
| 8 | // Declare the thread-local state variable, initialized to zero/inactive. |
| 9 | // *** RELIES ON ASYNC-SIGNAL-SAFE ACCESS TO THIS VARIABLE *** |
| 10 | |
| 11 | // Helper function to get diagnostic information from the last fault |
| 12 | const protected_access_frame_t *protected_access_get_last_fault(void) { |
| 13 | if (protected_access_state.depth < 1) |
| 14 | return NULL; |
| 15 | |
| 16 | protected_access_frame_t *frame = &protected_access_state.stack[protected_access_state.depth-1]; |
| 17 | if (frame->is_active != 2) // Not a frame with a fault |
| 18 | return NULL; |
| 19 | |
| 20 | return frame; |
| 21 | } |
| 22 | |
| 23 | // Format a string with diagnostic information about the last fault |
| 24 | void protected_access_format_error(char *buffer, size_t buffer_size) { |
| 25 | const protected_access_frame_t *frame = protected_access_get_last_fault(); |
| 26 | if (!frame) { |
| 27 | snprintf(buffer, buffer_size, "No protected access fault information available"); |
| 28 | return; |
| 29 | } |
| 30 | |
| 31 | // Use the proper public API for signal code formatting |
| 32 | char signal_code_buf[128]; |
| 33 | SIGNAL_CODE_2str_h(frame->signal_code, signal_code_buf, sizeof(signal_code_buf)); |
| 34 | |
| 35 | snprintf(buffer, buffer_size, |
| 36 | "Protected access fault in %s: %s %s failed with signal %s\n" |
| 37 | " Fault address: %p (offset +%lu within protected region %p-%p)", |
| 38 | frame->caller, |
| 39 | frame->operation, |
| 40 | frame->resource_name, |
| 41 | signal_code_buf, |
| 42 | frame->fault_address, |
| 43 | (unsigned long)((char*)frame->fault_address - (char*)frame->protected_start_addr), |
| 44 | frame->protected_start_addr, |
| 45 | (void*)((char*)frame->protected_start_addr + frame->protected_size) |
| 46 | ); |
| 47 | } |
| 48 | |
| 49 | // --- Public API Function (called by signal handler) --- |
| 50 | void signal_protected_access_check(int sig, siginfo_t *si, void *context __maybe_unused) { |
| 51 | // --- ASYNC-SIGNAL-SAFETY WARNING --- |
| 52 | // The following access to the thread-local 'protected_access' |
| 53 | // variable MUST be async-signal-safe on your specific target platform. |
| 54 | // This includes reading is_active, protected_start_addr, protected_size, |
| 55 | // AND the subsequent call to siglongjmp referencing state->jump_buffer. |
| 56 | // Standard C/POSIX do NOT guarantee safety for general TLS access here. |
| 57 | // Use with extreme caution and verify thoroughly. |
| 58 | // --- END WARNING --- |
| 59 | |
| 60 | protected_access_t *state = &protected_access_state; |
| 61 | |
| 62 | // Make sure we have active frames |
| 63 | if (state->depth == 0) |
| 64 | return; // No protection active, handler should ignore. |
| 65 | |
| 66 | // 2. Is it a signal we want to handle this way? |
| 67 | // Typically SIGBUS or SIGSEGV for memory access errors. |
| 68 | if (sig != SIGBUS && sig != SIGSEGV) |
| 69 | return; // Not a signal we are designed to recover from. |
| 70 | |
| 71 | // 3. Did the fault occur within the registered protected range? |
| 72 | // Ensure siginfo_t is valid (it should be if SA_SIGINFO was used) |
| 73 | if (si == NULL) |
| 74 | return; // This shouldn't happen if sigaction was set up correctly with SA_SIGINFO |
| 75 | |
| 76 | void *fault_addr = si->si_addr; |
| 77 | |
| 78 | // Start from the most recent frame and work backwards |
| 79 | for (sig_atomic_t i = state->depth - 1; i >= 0; i--) { |
| 80 | protected_access_frame_t *frame = &state->stack[i]; |
| 81 | |
| 82 | // Skip inactive frames (shouldn't happen but check anyway) |
| 83 | if (frame->is_active != 1) |
| 84 | continue; |
| 85 | |
| 86 | void *start_addr = frame->protected_start_addr; |
| 87 | |
| 88 | // Check if start_addr is valid |
| 89 | if (start_addr == NULL) |
| 90 | continue; |
| 91 | |
| 92 | // Calculate end address (exclusive) |
| 93 | void *end_addr = (unsigned char *)start_addr + frame->protected_size; |
| 94 | |
| 95 | if (fault_addr >= start_addr && fault_addr < end_addr) { |
| 96 | // --- Conditions met! Perform recovery jump --- |
| 97 | |
| 98 | // Mark that recovery jump is occurring *before* jumping. |
| 99 | // Set frame to '2'. This prevents handler re-entry if another signal occurs |
| 100 | // immediately, and signals to start() that recovery happened. |
| 101 | frame->is_active = 2; |
| 102 | |
| 103 | // Store diagnostic information about the fault |
| 104 | frame->fault_address = fault_addr; |
| 105 | frame->signal_code = signal_code(sig, si->si_code); |
| 106 | |
| 107 | // Update the depth to unwind all nested frames up to this one |
| 108 | state->depth = i; |
| 109 | |
| 110 | // Jump back to the sigsetjmp point in PROTECTED_ACCESS_START |
| 111 | // The '1' becomes the non-zero return value of sigsetjmp. |
| 112 | siglongjmp(frame->jump_buffer, 1); |
| 113 | |
| 114 | // --- Execution should not reach here after siglongjmp --- |
| 115 | // If it somehow did, something is fundamentally broken. |
| 116 | fprintf(stderr, "FATAL: siglongjmp returned in signal handler!\n"); |
| 117 | abort(); |
| 118 | return; // Should be unreachable |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | // Signal occurred while active, but fault address was outside all protected ranges. |
| 123 | // Let the default handler deal with it. |
| 124 | } |