| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #ifndef PROTECTED_ACCESS_H |
| 4 | #define PROTECTED_ACCESS_H |
| 5 | |
| 6 | #include "libnetdata/libnetdata.h" |
| 7 | #include <setjmp.h> |
| 8 | |
| 9 | // Maximum nesting depth for protected access regions |
| 10 | #define PROTECTED_ACCESS_MAX_NESTING 8 |
| 11 | |
| 12 | typedef struct { |
| 13 | const char *caller; // Function that initiated the protected region |
| 14 | sigjmp_buf jump_buffer; // Where to jump back to |
| 15 | void *protected_start_addr; // Start of the monitored memory range |
| 16 | size_t protected_size; // Size of the monitored memory range |
| 17 | // 0=inactive, 1=active (in protected block), 2=jump occurred |
| 18 | volatile sig_atomic_t is_active; // Must be sig_atomic_t for signal handler safety |
| 19 | |
| 20 | // Enhanced diagnostic information |
| 21 | const char *resource_name; // Name/path of the resource being accessed (often a file path) |
| 22 | const char *operation; // Operation being performed (e.g. "read", "write", "mmap") |
| 23 | SIGNAL_CODE signal_code; // Signal and code combination (e.g. SIGSEGV/SEGV_MAPERR) |
| 24 | void *fault_address; // The exact address that caused the fault |
| 25 | } protected_access_frame_t; |
| 26 | |
| 27 | typedef struct { |
| 28 | protected_access_frame_t stack[PROTECTED_ACCESS_MAX_NESTING]; |
| 29 | volatile sig_atomic_t depth; // Current nesting depth (0 = no active protection) |
| 30 | } protected_access_t; |
| 31 | |
| 32 | extern __thread protected_access_t protected_access_state; |
| 33 | |
| 34 | #define PROTECTED_ACCESS_START(start, size, resource, op) ({ \ |
| 35 | bool _rc = false; \ |
| 36 | \ |
| 37 | if (protected_access_state.depth >= PROTECTED_ACCESS_MAX_NESTING) \ |
| 38 | fatal("PROTECTED ACCESS: maximum nesting depth reached in function %s", \ |
| 39 | __FUNCTION__); \ |
| 40 | \ |
| 41 | if (start && size) { \ |
| 42 | /* Get the current frame on the stack */ \ |
| 43 | protected_access_frame_t *frame = \ |
| 44 | &protected_access_state.stack[protected_access_state.depth]; \ |
| 45 | \ |
| 46 | /* Initialize the frame */ \ |
| 47 | frame->protected_start_addr = start; \ |
| 48 | frame->protected_size = size; \ |
| 49 | frame->is_active = 1; \ |
| 50 | frame->caller = __FUNCTION__; \ |
| 51 | frame->resource_name = resource; \ |
| 52 | frame->operation = op; \ |
| 53 | frame->signal_code = 0; \ |
| 54 | frame->fault_address = NULL; \ |
| 55 | \ |
| 56 | /* Increase the stack depth before setting up the jump */ \ |
| 57 | protected_access_state.depth++; \ |
| 58 | \ |
| 59 | if (sigsetjmp(frame->jump_buffer, 1) == 0) { \ |
| 60 | /* Initial call successful, sigsetjmp returns 0. */ \ |
| 61 | _rc = true; \ |
| 62 | } else { \ |
| 63 | /* Returned here via siglongjmp from the signal handler. */ \ |
| 64 | /* The handler should have set frame->is_active = 2 */ \ |
| 65 | /* and populated diagnostic information. */ \ |
| 66 | /* Return false to indicate recovery path should be taken. */ \ |
| 67 | _rc = false; \ |
| 68 | } \ |
| 69 | } \ |
| 70 | _rc; \ |
| 71 | }) |
| 72 | |
| 73 | static inline void protected_access_end(volatile int *ptr __maybe_unused) { |
| 74 | if (protected_access_state.depth > 0) { |
| 75 | /* Decrease the stack depth */ |
| 76 | protected_access_state.depth--; |
| 77 | |
| 78 | /* Clear the frame at the current depth */ |
| 79 | protected_access_frame_t *frame = &protected_access_state.stack[protected_access_state.depth]; |
| 80 | frame->is_active = 0; |
| 81 | frame->protected_start_addr = NULL; |
| 82 | frame->protected_size = 0; |
| 83 | /* No need to clear jump_buffer explicitly */ |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | #define PROTECTED_ACCESS_AUTO_CLEANUP() \ |
| 88 | volatile int _pa_dummy_cleanup_var __attribute__((cleanup(protected_access_end), unused)) = 0; |
| 89 | |
| 90 | #define PROTECTED_ACCESS_END() protected_access_end(NULL); |
| 91 | |
| 92 | #define PROTECTED_ACCESS_SETUP(start, size, resource, op) \ |
| 93 | PROTECTED_ACCESS_AUTO_CLEANUP(); \ |
| 94 | bool no_signal_received = PROTECTED_ACCESS_START(start, size, resource, op); \ |
| 95 | if (!no_signal_received) { \ |
| 96 | char __pa_error_buf[1024]; \ |
| 97 | protected_access_format_error(__pa_error_buf, sizeof(__pa_error_buf)); \ |
| 98 | nd_log_limit_static_thread_var(_erl, 10, 0); \ |
| 99 | nd_log_limit(&_erl, NDLS_DAEMON, NDLP_ERR, "%s", __pa_error_buf); \ |
| 100 | } |
| 101 | |
| 102 | void signal_protected_access_check(int sig, siginfo_t *si, void *context); |
| 103 | |
| 104 | // Function declarations for diagnostic functions |
| 105 | const protected_access_frame_t *protected_access_get_last_fault(void); |
| 106 | void protected_access_format_error(char *buffer, size_t buffer_size); |
| 107 | |
| 108 | #endif // PROTECTED_ACCESS_H |