@cryptotaxi247 / netdata / commits / 1608f65b0

fix(dbengine): validate extent disk size and fix uncompressed page bounds (#22324)

* fix(dbengine): validate extent disk size and fix uncompressed page bounds Add rrdeng_valid_extent_disk_size() and check at journal restore, open-cache add, and disk read. Fix missing bounds check in the uncompressed page path that could read past payload_length on corrupt extent metadata. Treat short reads from uv_fs_read as errors. * fix(dbengine): improve logging for invalid extent size handling * fix(pdc): enhance boundary checks for page offsets and lengths

Stelios Fragkakis committed May 2, 2026 at 21:04 UTC 1608f65b04124c7e20384667162195875dad4349
5 files changed +63 -10
src/database/engine/journalfile.c
+1 -1
@@ -702,7 +702,7 @@ static void journalfile_restore_extent_metadata(struct rrdengine_instance *ctx,
702 count = jf_metric_data->number_of_pages;
703 descr_size = sizeof(*jf_metric_data->descr) * count;
704 payload_length = sizeof(*jf_metric_data) + descr_size;
705 - if (payload_length > max_size) {
705 + if (payload_length > max_size || !rrdeng_valid_extent_disk_size(jf_metric_data->extent_size)) {
706 netdata_log_error("DBENGINE: corrupted transaction payload.");
707 return;
708 }
src/database/engine/pagecache.c
+8
@@ -1079,6 +1079,14 @@ void pgc_open_add_hot_page(
1079 uint64_t extent_offset,
1080 unsigned extent_size)
1081 {
1082 + if (unlikely(!rrdeng_valid_extent_disk_size(extent_size))) {
1083 + nd_log_limit_static_thread_var(erl, 10, 0);
1084 + nd_log_limit(&erl, NDLS_DAEMON, NDLP_ERR,
1085 + "DBENGINE: skipped adding open-cache page for datafile %u of tier %u, "
1086 + "extent at offset %" PRIu64 " has invalid size %u",
1087 + datafile->fileno, datafile->tier, extent_offset, extent_size);
1088 + return;
1089 + }
1090
1091 if(!datafile_acquire(datafile, DATAFILE_ACQUIRE_OPEN_CACHE)) { // for open cache item
1092 nd_log_limit_static_thread_var(erl, 10, 0);
src/database/engine/pdc.c
+31 -8
@@ -1047,7 +1047,7 @@ static bool epdl_populate_pages_from_extent_data(
1047 uLong crc;
1048
1049 bool can_use_data = true;
1050 - if(data_length < sizeof(*header) + sizeof(header->descr[0]) + sizeof(*trailer)) {
1050 + if(!rrdeng_valid_extent_disk_size(data_length)) {
1051 can_use_data = false;
1052
1053 // added to satisfy the requirements of older compilers (prevent warnings)
@@ -1079,7 +1079,7 @@ static bool epdl_populate_pages_from_extent_data(
1079 }
1080
1081 crc = crc32(0L, Z_NULL, 0);
1082 - crc = crc32(crc, data, epdl->extent_size - sizeof(*trailer));
1082 + crc = crc32(crc, data, data_length - sizeof(*trailer));
1083 if (unlikely(crc32cmp(trailer->checksum, crc))) {
1084 ctx_io_error(ctx);
1085 have_read_error = true;
@@ -1178,13 +1178,27 @@ static bool epdl_populate_pages_from_extent_data(
1178 }
1179 else {
1180 if (RRDENG_COMPRESSION_NONE == header->compression_algorithm) {
1181 - pgd = pgd_create_from_disk_data(header->descr[i].type,
1182 - data + payload_offset + page_offset,
1183 - vd.page_length);
1184 - stats_load_uncompressed++;
1181 + if (unlikely(vd.page_length > payload_length ||
1182 + page_offset > payload_length - vd.page_length)) {
1183 + char log[200 + 1];
1184 + snprintfz(log, sizeof(log) - 1, "page %u (out of %u) offset %u + page length %zu, "
1185 + "exceeds the payload size %" PRIu64,
1186 + i, count, page_offset, vd.page_length, payload_length);
1187 + epdl_extent_loading_error_log(ctx, epdl, &header->descr[i], log, NDLP_ERR);
1188 +
1189 + pgd = PGD_EMPTY;
1190 + stats_load_invalid_page++;
1191 + }
1192 + else {
1193 + pgd = pgd_create_from_disk_data(header->descr[i].type,
1194 + data + payload_offset + page_offset,
1195 + vd.page_length);
1196 + stats_load_uncompressed++;
1197 + }
1198 }
1199 else {
1187 - if (unlikely(page_offset + vd.page_length > uncompressed_payload_length)) {
1200 + if (unlikely(vd.page_length > uncompressed_payload_length ||
1201 + page_offset > uncompressed_payload_length - vd.page_length)) {
1202 char log[200 + 1];
1203 snprintfz(log, sizeof(log) - 1, "page %u (out of %u) offset %u + page length %zu, "
1204 "exceeds the uncompressed buffer size %u",
@@ -1275,6 +1289,15 @@ static bool epdl_populate_pages_from_extent_data(
1289
1290 static inline void *datafile_extent_read(struct rrdengine_instance *ctx, uv_file file, uint32_t block, unsigned size_bytes)
1291 {
1292 + if (unlikely(!rrdeng_valid_extent_disk_size(size_bytes))) {
1293 + nd_log_limit_static_global_var(erl, 1, 0);
1294 + nd_log_limit(&erl, NDLS_DAEMON, NDLP_ERR,
1295 + "DBENGINE: refusing to read extent at offset %" PRIu64 " with invalid size %u",
1296 + BLOCK_TO_OFFSET(block), size_bytes);
1297 + ctx_io_error(ctx);
1298 + return NULL;
1299 + }
1300 +
1301 void *buffer = NULL;
1302 uv_fs_t request;
1303
@@ -1283,7 +1306,7 @@ static inline void *datafile_extent_read(struct rrdengine_instance *ctx, uv_file
1306
1307 uv_buf_t iov = uv_buf_init(buffer, real_io_size);
1308 int ret = uv_fs_read(NULL, &request, file, &iov, 1, (int64_t) BLOCK_TO_OFFSET(block), NULL);
1286 - if (unlikely(-1 == ret)) {
1309 + if (unlikely(ret < 0 || (unsigned)ret != real_io_size)) {
1310 ctx_io_error(ctx);
1311 posix_memalign_freez(buffer);
1312 buffer = NULL;
src/database/engine/rrdengine.c
+6 -1
@@ -692,6 +692,10 @@ extent_flush_to_open(struct rrdengine_instance *ctx, struct extent_io_descriptor
692 datafile = xt_io_descr->datafile;
693
694 bool still_running = ctx_is_available_for_queries(ctx);
695 + if (likely(still_running && !have_error))
696 + internal_fatal(!rrdeng_valid_extent_disk_size(xt_io_descr->bytes),
697 + "DBENGINE: flushed extent has invalid size %u",
698 + xt_io_descr->bytes);
699
700 usec_t max_end_time_ut = 0;
701 for (i = 0 ; i < xt_io_descr->descr_count ; ++i) {
@@ -700,7 +704,7 @@ extent_flush_to_open(struct rrdengine_instance *ctx, struct extent_io_descriptor
704 if (descr->end_time_ut > max_end_time_ut)
705 max_end_time_ut = descr->end_time_ut;
706
703 - if (likely(still_running && !have_error))
707 + if (likely(still_running && !have_error)) {
708 pgc_open_add_hot_page(
709 (Word_t)ctx,
710 descr->metric_id,
@@ -710,6 +714,7 @@ extent_flush_to_open(struct rrdengine_instance *ctx, struct extent_io_descriptor
714 datafile,
715 xt_io_descr->pos,
716 xt_io_descr->bytes);
717 + }
718
719 page_descriptor_release(descr);
720 }
src/database/engine/rrdengine.h
+17
@@ -58,6 +58,23 @@ struct rrdeng_cmd;
58
59 #define MAX_EXTENT_UNCOMPRESSED_SIZE (MAX_PAGES_PER_EXTENT * (RRDENG_BLOCK_SIZE + RRDENG_GORILLA_32BIT_BUFFER_SIZE))
60
61 +static inline size_t rrdeng_min_extent_disk_size(void) {
62 + return sizeof(struct rrdeng_df_extent_header) +
63 + sizeof(struct rrdeng_extent_page_descr) +
64 + sizeof(struct rrdeng_df_extent_trailer);
65 +}
66 +
67 +static inline size_t rrdeng_max_extent_disk_size(void) {
68 + return sizeof(struct rrdeng_df_extent_header) +
69 + sizeof(struct rrdeng_extent_page_descr) * MAX_PAGES_PER_EXTENT +
70 + MAX_EXTENT_UNCOMPRESSED_SIZE +
71 + sizeof(struct rrdeng_df_extent_trailer);
72 +}
73 +
74 +static inline bool rrdeng_valid_extent_disk_size(size_t size) {
75 + return size >= rrdeng_min_extent_disk_size() && size <= rrdeng_max_extent_disk_size();
76 +}
77 +
78
79 #define RRDENG_FILE_NUMBER_SCAN_TMPL "%1u-%10u"
80 #define RRDENG_FILE_NUMBER_PRINT_TMPL "%1.1u-%10.10u"