@cryptotaxi247 / netdata-1 / commits / ea07b4c1e

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 }