Improve UUID handling in SQLite functions and error reporting (#22233)
* fix: improve UUID handling in SQLite functions and error reporting * fix: ensure valid machine GUID handling in alert transition lookup * fix: correct UUID handling in SQLite metadata retrieval * fix: enhance UUID validation and error reporting in alert transition and configuration loading * fix: improve error reporting for invalid chart and dimension IDs with host GUID context
Stelios Fragkakis committed
Apr 20, 2026 at 23:47 UTC
ea07b4c1e761cc66491c5460fbf1a56058533296
5 files changed
+171
-36
src/database/sqlite/sqlite_aclk_alert.c
+4
-2
@@ -243,8 +243,10 @@ static inline char *sqlite3_uuid_unparse_strdupz(sqlite3_stmt *res, int iCol) {
243
244
if(sqlite3_column_type(res, iCol) == SQLITE_NULL)
245
uuid_str[0] = '\0';
246
- else
247
- uuid_unparse_lower(*((nd_uuid_t *) sqlite3_column_blob(res, iCol)), uuid_str);
246
+ else if (!sqlite3_column_uuid_unparse_lower(res, iCol, uuid_str)) {
247
+ error_report("ACLK ALERT: Got invalid UUID blob at column %d. Returning empty string.", iCol);
248
+ uuid_str[0] = '\0';
249
+ }
250
251
return strdupz(uuid_str);
252
}
src/database/sqlite/sqlite_context.c
+16
-3
@@ -98,10 +98,17 @@ void ctx_get_chart_list(nd_uuid_t *host_uuid, void (*dict_cb)(SQL_CHART_DATA *,
98
int param = 0;
99
SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, host_uuid, sizeof(*host_uuid), SQLITE_STATIC));
100
101
+ char host_guid[UUID_STR_LEN];
102
+ uuid_unparse_lower(*host_uuid, host_guid);
103
+
104
param = 0;
105
SQL_CHART_DATA chart_data = { 0 };
106
while (sqlite3_step_monitored(res) == SQLITE_ROW) {
104
- uuid_copy(chart_data.chart_id, *((nd_uuid_t *)sqlite3_column_blob(res, 0)));
107
+ if (unlikely(!sqlite3_column_uuid_copy(res, 0, chart_data.chart_id))) {
108
+ error_report("CTX [%s]: Got invalid chart id in column 0. Ignoring it.", host_guid);
109
+ continue;
110
+ }
111
+
112
chart_data.id = (char *) sqlite3_column_text(res, 1);
113
chart_data.name = (char *) sqlite3_column_text(res, 2);
114
chart_data.context = (char *) sqlite3_column_text(res, 3);
@@ -132,11 +139,18 @@ void ctx_get_dimension_list(nd_uuid_t *host_uuid, void (*dict_cb)(SQL_DIMENSION_
139
int param = 0;
140
SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, host_uuid, sizeof(*host_uuid), SQLITE_STATIC));
141
142
+ char host_guid[UUID_STR_LEN];
143
+ uuid_unparse_lower(*host_uuid, host_guid);
144
+
145
SQL_DIMENSION_DATA dimension_data;
146
147
param = 0;
148
while (sqlite3_step_monitored(res) == SQLITE_ROW) {
139
- uuid_copy(dimension_data.dim_id, *((nd_uuid_t *)sqlite3_column_blob(res, 0)));
149
+ if (unlikely(!sqlite3_column_uuid_copy(res, 0, dimension_data.dim_id))) {
150
+ error_report("CTX [%s]: Got invalid dimension id in column 0. Ignoring it.", host_guid);
151
+ continue;
152
+ }
153
+
154
dimension_data.id = (char *) sqlite3_column_text(res, 1);
155
dimension_data.name = (char *) sqlite3_column_text(res, 2);
156
dimension_data.hidden = sqlite3_column_int(res, 3);
@@ -426,4 +440,3 @@ int ctx_unittest(void)
440
441
return 0;
442
}
429
-
src/database/sqlite/sqlite_functions.h
+41
@@ -121,6 +121,47 @@ uint64_t sqlite_get_db_space(sqlite3 *db);
121
int get_free_page_count(sqlite3 *database);
122
int get_database_page_count(sqlite3 *database);
123
124
+// Return a pointer to the UUID blob stored in column iCol when the current row
125
+// contains a BLOB with exactly sizeof(nd_uuid_t) bytes. The caller must pass a
126
+// valid statement positioned on a row. The returned pointer refers to
127
+// SQLite-owned memory and is only valid until the statement is stepped, reset,
128
+// or finalized. Returns NULL for NULL, non-BLOB, or malformed values.
129
+static inline const nd_uuid_t *sqlite3_column_uuid_ptr(sqlite3_stmt *res, int iCol) {
130
+ if (sqlite3_column_type(res, iCol) != SQLITE_BLOB)
131
+ return NULL;
132
+
133
+ const void *uuid = sqlite3_column_blob(res, iCol);
134
+ if (!uuid || sqlite3_column_bytes(res, iCol) != sizeof(nd_uuid_t))
135
+ return NULL;
136
+
137
+ return (const nd_uuid_t *)uuid;
138
+}
139
+
140
+// Copy the UUID stored in column iCol into dst. The caller must pass a valid
141
+// statement positioned on a row and a writable destination UUID buffer.
142
+// Returns true on success, false when the column does not contain a valid UUID blob.
143
+static inline bool sqlite3_column_uuid_copy(sqlite3_stmt *res, int iCol, nd_uuid_t dst) {
144
+ const nd_uuid_t *uuid = sqlite3_column_uuid_ptr(res, iCol);
145
+ if (!uuid)
146
+ return false;
147
+
148
+ uuid_copy(dst, *uuid);
149
+ return true;
150
+}
151
+
152
+// Convert the UUID stored in column iCol to lowercase text in out. The caller
153
+// must pass a valid statement positioned on a row and a writable buffer large
154
+// enough for UUID_STR_LEN bytes. Returns true on success, false when the column
155
+// does not contain a valid UUID blob.
156
+static inline bool sqlite3_column_uuid_unparse_lower(sqlite3_stmt *res, int iCol, char *out) {
157
+ const nd_uuid_t *uuid = sqlite3_column_uuid_ptr(res, iCol);
158
+ if (!uuid)
159
+ return false;
160
+
161
+ uuid_unparse_lower(*uuid, out);
162
+ return true;
163
+}
164
+
165
int sqlite_library_init(void);
166
void sqlite_library_shutdown(void);
167
src/database/sqlite/sqlite_health.c
+92
-20
@@ -543,14 +543,20 @@ void sql_check_removed_alerts_state(RRDHOST *host)
543
544
param = 0;
545
while (sqlite3_step_monitored(res) == SQLITE_ROW) {
546
- uint32_t alarm_id, alarm_event_id, unique_id;
547
- RRDCALC_STATUS status;
546
+ const nd_uuid_t *transition_uuid = sqlite3_column_uuid_ptr(res, 4);
547
549
- status = (RRDCALC_STATUS)sqlite3_column_int(res, 0);
550
- unique_id = (uint32_t)sqlite3_column_int64(res, 1);
551
- alarm_id = (uint32_t)sqlite3_column_int64(res, 2);
552
- alarm_event_id = (uint32_t)sqlite3_column_int64(res, 3);
553
- uuid_copy(transition_id, *((nd_uuid_t *)sqlite3_column_blob(res, 4)));
548
+ RRDCALC_STATUS status = (RRDCALC_STATUS)sqlite3_column_int(res, 0);
549
+ uint32_t unique_id = (uint32_t)sqlite3_column_int64(res, 1);
550
+ uint32_t alarm_id = (uint32_t)sqlite3_column_int64(res, 2);
551
+ uint32_t alarm_event_id = (uint32_t)sqlite3_column_int64(res, 3);
552
+
553
+ if (unlikely(!transition_uuid)) {
554
+ error_report("HEALTH [%s]: Got invalid transition id while checking removed alerts. Ignoring it.",
555
+ rrdhost_hostname(host));
556
+ continue;
557
+ }
558
+
559
+ uuid_copy(transition_id, *transition_uuid);
560
561
if (unlikely(status != RRDCALC_STATUS_REMOVED)) {
562
if (unlikely(!max_unique_id))
@@ -715,13 +721,26 @@ void sql_health_alarm_log_load(RRDHOST *host)
721
}
722
}
723
724
+ if (sqlite3_column_type(res, 30) != SQLITE_NULL &&
725
+ unlikely(!sqlite3_column_uuid_ptr(res, 30))) {
726
+ error_report("HEALTH [%s]: Got invalid transition id. Ignoring entry.", rrdhost_hostname(host));
727
+ errored++;
728
+ continue;
729
+ }
730
+
731
ae = health_alarm_entry_create();
732
733
ae->unique_id = unique_id;
734
ae->alarm_id = alarm_id;
735
723
- if (sqlite3_column_type(res, 3) != SQLITE_NULL)
724
- uuid_copy(ae->config_hash_id, *((nd_uuid_t *) sqlite3_column_blob(res, 3)));
736
+ if (sqlite3_column_type(res, 3) != SQLITE_NULL) {
737
+ if (unlikely(!sqlite3_column_uuid_copy(res, 3, ae->config_hash_id))) {
738
+ error_report("HEALTH [%s]: Got invalid config hash id. Ignoring entry.", rrdhost_hostname(host));
739
+ errored++;
740
+ health_alarm_entry_destroy(ae);
741
+ continue;
742
+ }
743
+ }
744
745
ae->alarm_event_id = (uint32_t) sqlite3_column_int64(res, 2);
746
ae->updated_by_id = (uint32_t) sqlite3_column_int64(res, 4);
@@ -761,8 +780,11 @@ void sql_health_alarm_log_load(RRDHOST *host)
780
ae->type = SQLITE3_COLUMN_STRINGDUP_OR_NULL(res, 28);
781
ae->chart_context = SQLITE3_COLUMN_STRINGDUP_OR_NULL(res, 29);
782
764
- if (sqlite3_column_type(res, 30) != SQLITE_NULL)
765
- uuid_copy(ae->transition_id, *((nd_uuid_t *)sqlite3_column_blob(res, 30)));
783
+ if (sqlite3_column_type(res, 30) != SQLITE_NULL) {
784
+ bool copied = sqlite3_column_uuid_copy(res, 30, ae->transition_id);
785
+ internal_fatal(!copied, "HEALTH [%s]: transition id validation invariant violated while loading health log.",
786
+ rrdhost_hostname(host));
787
+ }
788
789
if (sqlite3_column_type(res, 31) != SQLITE_NULL)
790
ae->global_id = sqlite3_column_int64(res, 31);
@@ -1056,11 +1078,19 @@ void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, time_t after, const ch
1078
char new_value_string[100 + 1];
1079
1080
char config_hash_id[UUID_STR_LEN];
1059
- uuid_unparse_lower(*((nd_uuid_t *)sqlite3_column_blob(stmt_query, 3)), config_hash_id);
1081
+ if (unlikely(!sqlite3_column_uuid_unparse_lower(stmt_query, 3, config_hash_id))) {
1082
+ error_report("HEALTH [%s]: Got invalid config hash id while exporting health log. Ignoring entry.",
1083
+ rrdhost_hostname(host));
1084
+ continue;
1085
+ }
1086
1087
char transition_id[UUID_STR_LEN] = {0};
1062
- if (sqlite3_column_type(stmt_query, 30) != SQLITE_NULL)
1063
- uuid_unparse_lower(*((nd_uuid_t *)sqlite3_column_blob(stmt_query, 30)), transition_id);
1088
+ if (sqlite3_column_type(stmt_query, 30) != SQLITE_NULL &&
1089
+ unlikely(!sqlite3_column_uuid_unparse_lower(stmt_query, 30, transition_id))) {
1090
+ error_report("HEALTH [%s]: Got invalid transition id while exporting health log. Ignoring entry.",
1091
+ rrdhost_hostname(host));
1092
+ continue;
1093
+ }
1094
1095
char *edit_command = sqlite3_column_bytes(stmt_query, 16) > 0 ?
1096
health_edit_command_from_source((char *)sqlite3_column_text(stmt_query, 16)) :
@@ -1355,9 +1385,13 @@ bool sql_find_alert_transition(
1385
1386
param = 0;
1387
while (sqlite3_step_monitored(res) == SQLITE_ROW) {
1358
- ok = true;
1359
- uuid_unparse_lower(*(nd_uuid_t *) sqlite3_column_blob(res, 1), machine_guid);
1388
+ if (unlikely(!sqlite3_column_uuid_unparse_lower(res, 1, machine_guid))) {
1389
+ error_report("HEALTH: Got invalid machine guid while looking up alert transition. Ignoring it.");
1390
+ continue;
1391
+ }
1392
+
1393
cb(machine_guid, (const char *) sqlite3_column_text(res, 2), sqlite3_column_int(res, 0), data);
1394
+ ok = true;
1395
}
1396
1397
done:
@@ -1486,12 +1520,28 @@ void sql_alert_transitions(
1520
run_query:;
1521
1522
struct sql_alert_transition_data atd = {0 };
1523
+ nd_uuid_t host_id;
1524
+ nd_uuid_t config_hash_id;
1525
+ nd_uuid_t transition_id;
1526
+ size_t invalid_host_ids = 0;
1527
+ size_t invalid_config_hash_ids = 0;
1528
+ size_t invalid_transition_ids = 0;
1529
1530
param = 0;
1531
while (sqlite3_step(res) == SQLITE_ROW) {
1492
- atd.host_id = (nd_uuid_t *) sqlite3_column_blob(res, 0);
1532
+ if (unlikely(!sqlite3_column_uuid_copy(res, 0, host_id))) {
1533
+ invalid_host_ids++;
1534
+ continue;
1535
+ }
1536
+
1537
+ atd.host_id = &host_id;
1538
atd.alarm_id = sqlite3_column_int64(res, 1);
1494
- atd.config_hash_id = (nd_uuid_t *)sqlite3_column_blob(res, 2);
1539
+ if (unlikely(!sqlite3_column_uuid_copy(res, 2, config_hash_id))) {
1540
+ invalid_config_hash_ids++;
1541
+ continue;
1542
+ }
1543
+
1544
+ atd.config_hash_id = &config_hash_id;
1545
atd.alert_name = (const char *) sqlite3_column_text(res, 3);
1546
atd.chart = (const char *) sqlite3_column_text(res, 4);
1547
atd.chart_name = (const char *) sqlite3_column_text(res, 5);
@@ -1513,7 +1563,12 @@ run_query:;
1563
atd.new_value = (NETDATA_DOUBLE) sqlite3_column_double(res, 21);
1564
atd.old_value = (NETDATA_DOUBLE) sqlite3_column_double(res, 22);
1565
atd.last_repeat = sqlite3_column_int64(res, 23);
1516
- atd.transition_id = (nd_uuid_t *) sqlite3_column_blob(res, 24);
1566
+ if (unlikely(!sqlite3_column_uuid_copy(res, 24, transition_id))) {
1567
+ invalid_transition_ids++;
1568
+ continue;
1569
+ }
1570
+
1571
+ atd.transition_id = &transition_id;
1572
atd.global_id = sqlite3_column_int64(res, 25);
1573
atd.classification = (const char *) sqlite3_column_text(res, 26);
1574
atd.type = (const char *) sqlite3_column_text(res, 27);
@@ -1524,6 +1579,11 @@ run_query:;
1579
cb(&atd, data);
1580
}
1581
1582
+ if (unlikely(invalid_host_ids || invalid_config_hash_ids || invalid_transition_ids)) {
1583
+ error_report("HEALTH: Ignored invalid alert transition rows (host_id=%zu, config_hash_id=%zu, transition_id=%zu).",
1584
+ invalid_host_ids, invalid_config_hash_ids, invalid_transition_ids);
1585
+ }
1586
+
1587
done:
1588
REPORT_BIND_FAIL(res, param);
1589
SQLITE_FINALIZE(res);
@@ -1607,12 +1667,19 @@ int sql_get_alert_configuration(
1667
}
1668
1669
struct sql_alert_config_data acd = {0 };
1670
+ nd_uuid_t config_hash_id;
1671
+ size_t invalid_config_hash_ids = 0;
1672
1673
added = 0;
1674
int param;
1675
while (sqlite3_step(res) == SQLITE_ROW) {
1676
param = 0;
1615
- acd.config_hash_id = (nd_uuid_t *) sqlite3_column_blob(res, param++);
1677
+ if (unlikely(!sqlite3_column_uuid_copy(res, param++, config_hash_id))) {
1678
+ invalid_config_hash_ids++;
1679
+ continue;
1680
+ }
1681
+
1682
+ acd.config_hash_id = &config_hash_id;
1683
acd.name = (const char *) sqlite3_column_text(res, param++);
1684
acd.selectors.on_template = (const char *) sqlite3_column_text(res, param++);
1685
acd.selectors.on_key = (const char *) sqlite3_column_text(res, param++);
@@ -1653,6 +1720,11 @@ int sql_get_alert_configuration(
1720
added++;
1721
}
1722
1723
+ if (unlikely(invalid_config_hash_ids)) {
1724
+ error_report("HEALTH: Ignored %zu alert configuration rows with invalid config_hash_id.",
1725
+ invalid_config_hash_ids);
1726
+ }
1727
+
1728
SQLITE_FINALIZE(res);
1729
1730
fail_only_drop:
src/database/sqlite/sqlite_metadata.c
+18
-11
@@ -499,8 +499,9 @@ void sql_load_node_id(RRDHOST *host)
499
param = 0;
500
int rc = sqlite3_step_monitored(res);
501
if (likely(rc == SQLITE_ROW)) {
502
- if (likely(sqlite3_column_bytes(res, 0) == sizeof(nd_uuid_t)))
503
- set_host_node_id(host, (nd_uuid_t *)sqlite3_column_blob(res, 0));
502
+ nd_uuid_t node_id;
503
+ if (likely(sqlite3_column_uuid_copy(res, 0, node_id)))
504
+ set_host_node_id(host, &node_id);
505
else
506
set_host_node_id(host, NULL);
507
}
@@ -1298,14 +1299,19 @@ static bool run_cleanup_loop(
1299
uint32_t l_checked = 0;
1300
uint32_t l_deleted = 0;
1301
while (!time_expired && sqlite3_step_monitored(res) == SQLITE_ROW) {
1302
+ nd_uuid_t uuid = {0};
1303
+
1304
if (unlikely(SHUTDOWN_REQUESTED(config)))
1305
break;
1306
1307
*row_id = sqlite3_column_int64(res, 1);
1305
- rc = check_cb((nd_uuid_t *)sqlite3_column_blob(res, 0), check_stmt, check_flag);
1308
+ if (unlikely(!sqlite3_column_uuid_copy(res, 0, uuid)))
1309
+ continue;
1310
+
1311
+ rc = check_cb(&uuid, check_stmt, check_flag);
1312
1313
if (rc == true) {
1308
- action_cb((nd_uuid_t *)sqlite3_column_blob(res, 0), action_stmt, action_flag);
1314
+ action_cb(&uuid, action_stmt, action_flag);
1315
l_deleted++;
1316
// if (false == sql_metadata_wal_size_acceptable())
1317
// (void) sqlite3_wal_checkpoint(db_meta, NULL);
@@ -1747,14 +1753,15 @@ static bool clean_host_chart_dimensions(sqlite3_stmt **res, int64_t chart_row_id
1753
1754
can_continue = true;
1755
while (can_continue && sqlite3_step_monitored(*res) == SQLITE_ROW) {
1750
- if (sqlite3_column_bytes(*res, 0) != sizeof(nd_uuid_t))
1756
+ nd_uuid_t dim_uuid;
1757
+
1758
+ if (!sqlite3_column_uuid_copy(*res, 0, dim_uuid))
1759
continue;
1760
1753
- nd_uuid_t *dim_uuid = (nd_uuid_t *)sqlite3_column_blob(*res, 0);
1761
int64_t dimension_id = sqlite3_column_int64(*res, 1);
1762
1756
- if (dimension_can_be_deleted(dim_uuid, NULL, false)) {
1757
- delete_dimension_by_rowid(&dim_del_stmt, dimension_id, dim_uuid);
1763
+ if (dimension_can_be_deleted(&dim_uuid, NULL, false)) {
1764
+ delete_dimension_by_rowid(&dim_del_stmt, dimension_id, &dim_uuid);
1765
(*deleted)++;
1766
}
1767
(*checked)++;
@@ -2145,15 +2152,15 @@ size_t populate_metrics_from_database(void *mrg, void (*populate_cb)(void *mrg,
2152
2153
usec_t started_ut = now_monotonic_usec();
2154
while (sqlite3_step(res) == SQLITE_ROW) {
2148
- nd_uuid_t *uuid = (nd_uuid_t *)sqlite3_column_blob(res, 0);
2149
- if (!uuid || sqlite3_column_bytes(res, 0) != sizeof(nd_uuid_t))
2155
+ nd_uuid_t uuid;
2156
+ if (!sqlite3_column_uuid_copy(res, 0, uuid))
2157
continue;
2158
2159
for (size_t tier = 0; tier < nd_profile.storage_tiers ; tier++) {
2160
if (unlikely(!multidb_ctx[tier]))
2161
continue;
2162
2156
- populate_cb(mrg, (Word_t)multidb_ctx[tier], uuid);
2163
+ populate_cb(mrg, (Word_t)multidb_ctx[tier], &uuid);
2164
}
2165
count++;
2166
}