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