@cryptotaxi247 / netdata-1 / commits / c4a4b7cf1

make sure the daemon status hash does not depend on random bytes (#19874)

* make sure the daemon status hash does not depend on random bytes * report events every 23 hours, not 24, for cron randomness

Costa Tsaousis committed Mar 15, 2025 at 21:38 UTC c4a4b7cf1be7891a1af2d8d402a906103b41bb82
1 file changed +53 -25
src/daemon/daemon-status-file.c
+53 -25
@@ -9,6 +9,8 @@
9 #include <openssl/pem.h>
10 #include <openssl/err.h>
11
12 +#define REPORT_EVENTS_EVERY (86400 - 3600) // -1 hour to tolerate cron randomness
13 +
14 #ifdef ENABLE_SENTRY
15 #include "sentry-native/sentry-native.h"
16 #endif
@@ -89,14 +91,15 @@ static void set_stack_trace_message_if_empty(DAEMON_STATUS_FILE *ds, const char
91 // json generation
92
93 static uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg, const char *cause) {
92 - dsf_acquire(*ds);
94 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
95 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
96
97 struct {
98 uint32_t v;
99 DAEMON_STATUS status;
97 - EXIT_REASON exit_reason;
100 SIGNAL_CODE signal_code;
101 ND_PROFILE profile;
102 + EXIT_REASON exit_reason;
103 RRD_DB_MODE db_mode;
104 uint8_t db_tiers;
105 bool kubernetes;
@@ -113,26 +116,32 @@ static uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg,
116 char thread[sizeof(ds->fatal.thread)];
117 char msg[128];
118 char cause[32];
116 - } to_hash = {
117 - .v = ds->v,
118 - .status = ds->status,
119 - .signal_code = ds->fatal.signal_code,
120 - .exit_reason = ds->exit_reason,
121 - .profile = ds->profile,
122 - .db_mode = ds->db_mode,
123 - .db_tiers = ds->db_tiers,
124 - .kubernetes = ds->kubernetes,
125 - .sentry_available = ds->sentry_available,
126 - .sentry_fatal = ds->fatal.sentry,
127 - .host_id = ds->host_id,
128 - .machine_id = ds->machine_id,
129 - };
130 - memcpy(to_hash.version, ds->version, sizeof(ds->version));
131 - memcpy(to_hash.filename, ds->fatal.filename, sizeof(ds->fatal.filename));
132 - memcpy(to_hash.filename, ds->fatal.function, sizeof(ds->fatal.function));
133 - memcpy(to_hash.errno_str, ds->fatal.errno_str, sizeof(ds->fatal.errno_str));
134 - memcpy(to_hash.stack_trace, ds->fatal.stack_trace, sizeof(ds->fatal.stack_trace));
135 - memcpy(to_hash.thread, ds->fatal.thread, sizeof(ds->fatal.thread));
119 + } to_hash;
120 +
121 + // this is important to remove any random bytes from the structure
122 + memset(&to_hash, 0, sizeof(to_hash));
123 +
124 + dsf_acquire(*ds);
125 +
126 + to_hash.v = ds->v,
127 + to_hash.status = ds->status,
128 + to_hash.signal_code = ds->fatal.signal_code,
129 + to_hash.profile = ds->profile,
130 + to_hash.exit_reason = ds->exit_reason,
131 + to_hash.db_mode = ds->db_mode,
132 + to_hash.db_tiers = ds->db_tiers,
133 + to_hash.kubernetes = ds->kubernetes,
134 + to_hash.sentry_available = ds->sentry_available,
135 + to_hash.sentry_fatal = ds->fatal.sentry,
136 + to_hash.host_id = ds->host_id,
137 + to_hash.machine_id = ds->machine_id,
138 +
139 + strncpyz(to_hash.version, ds->version, sizeof(to_hash.version) - 1);
140 + strncpyz(to_hash.filename, ds->fatal.filename, sizeof(to_hash.filename) - 1);
141 + strncpyz(to_hash.filename, ds->fatal.function, sizeof(to_hash.function) - 1);
142 + strncpyz(to_hash.errno_str, ds->fatal.errno_str, sizeof(to_hash.errno_str) - 1);
143 + strncpyz(to_hash.stack_trace, ds->fatal.stack_trace, sizeof(to_hash.stack_trace) - 1);
144 + strncpyz(to_hash.thread, ds->fatal.thread, sizeof(to_hash.thread) - 1);
145
146 if(msg)
147 strncpyz(to_hash.msg, msg, sizeof(to_hash.msg) - 1);
@@ -147,6 +156,9 @@ static uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg,
156 }
157
158 static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
159 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
160 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
161 +
162 dsf_acquire(*ds);
163
164 buffer_json_member_add_datetime_rfc3339(wb, "@timestamp", ds->timestamp_ut, true); // ECS
@@ -668,7 +680,8 @@ void daemon_status_file_load(DAEMON_STATUS_FILE *ds) {
680 // save the current status
681
682 static bool save_status_file(const char *directory, const char *content, size_t content_size) {
671 - // THIS FUNCTION MUST USE ONLY ASYNC-SAFE OPERATIONS
683 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
684 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
685
686 // Linux: https://man7.org/linux/man-pages/man7/signal-safety.7.html
687 // memcpy(), strlen(), open(), write(), fsync(), close(), chmod(), rename(), unlink()
@@ -785,6 +798,11 @@ static void remove_old_status_files(const char *protected_dir) {
798 }
799
800 static void daemon_status_file_save(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool log) {
801 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
802 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
803 +
804 + // wb should have enough space to hold the JSON content, to avoid any allocations
805 +
806 buffer_flush(wb);
807 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
808 daemon_status_file_to_json(wb, ds);
@@ -825,6 +843,9 @@ static void daemon_status_file_save(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool log
843 // deduplication hashes management
844
845 static bool dedup_already_posted(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sentry) {
846 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
847 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
848 +
849 usec_t now_ut = now_realtime_usec();
850
851 for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
@@ -833,7 +854,7 @@ static bool dedup_already_posted(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sen
854
855 if(hash == ds->dedup.slot[i].hash &&
856 sentry == ds->dedup.slot[i].sentry &&
836 - now_ut - ds->dedup.slot[i].timestamp_ut < 86400 * USEC_PER_SEC) {
857 + now_ut - ds->dedup.slot[i].timestamp_ut < REPORT_EVENTS_EVERY * USEC_PER_SEC) {
858 // we have already posted this crash
859 return true;
860 }
@@ -843,6 +864,9 @@ static bool dedup_already_posted(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sen
864 }
865
866 static void dedup_keep_hash(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sentry) {
867 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
868 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
869 +
870 // find the same hash
871 for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
872 if(ds->dedup.slot[i].hash == hash && ds->dedup.slot[i].sentry == sentry) {
@@ -1195,6 +1219,9 @@ void daemon_status_file_check_crash(void) {
1219 }
1220
1221 static void daemon_status_file_save_twice_if_we_can_get_stack_trace(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool force) {
1222 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
1223 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
1224 +
1225 if(capture_stack_trace_available())
1226 set_stack_trace_message_if_empty(&session_status, STACK_TRACE_INFO_PREFIX "will now attempt to get stack trace - if you see this message, we couldn't get it.");
1227 else
@@ -1297,7 +1324,8 @@ static void daemon_status_file_out_of_memory(void) {
1324 bool daemon_status_file_deadly_signal_received(EXIT_REASON reason, SIGNAL_CODE code, bool chained_handler) {
1325 FUNCTION_RUN_ONCE_RET(true);
1326
1300 - // DO NOT LOCK OR ALLOCATE IN THIS FUNCTION - WE CRASHED ALREADY AND WE ARE INSIDE THE SIGNAL HANDLER!
1327 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
1328 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
1329
1330 dsf_acquire(session_status);
1331