@cryptotaxi247 / netdata-1 / commits / 26b26ac25

fix(journal): improve journal file access error handling and validation (#22310)

* fix(journal): improve journal file access error handling and validation * Address review comment * Address review comment 2 * fix(rrdengine): simplify journal search logic and remove redundant checks

Stelios Fragkakis committed Apr 30, 2026 at 15:44 UTC 26b26ac25aa56808b4449f37fdb683860288b674
1 file changed +43 -15
src/database/engine/rrdengine.c
+43 -15
@@ -1167,29 +1167,51 @@ static time_t find_uuid_first_time(
1167
1168 bool agent_shutdown = false;
1169 while (datafile) {
1170 + size_t journal_v2_file_size = 0;
1171 struct journal_v2_header *j2_header = journalfile_v2_data_acquire_with_hint(
1171 - datafile->journalfile, NULL, 0, 0, JOURNALFILE_V2_ACCESS_RANDOM);
1172 + datafile->journalfile, &journal_v2_file_size, 0, 0, JOURNALFILE_V2_ACCESS_RANDOM);
1173 if (!j2_header) {
1174 datafile = datafile_release_and_acquire_next_for_retention(ctx, datafile);
1175 continue;
1176 }
1177
1178 bool any_matching = false;
1179 + bool journal_access_failed = false;
1180
1179 - time_t journal_start_time_s = (time_t)(j2_header->start_time_ut / USEC_PER_SEC);
1180 -
1181 - if (journal_start_time_s < global_first_time_s)
1182 - global_first_time_s = journal_start_time_s;
1183 -
1184 - struct journal_metric_list *uuid_list =
1185 - (struct journal_metric_list *)((uint8_t *)j2_header + j2_header->metric_offset);
1186 - struct uuid_first_time_s *uuid_original_entry;
1187 -
1188 - size_t journal_metric_count = j2_header->metric_count;
1181 char file_path[RRDENG_PATH_MAX];
1182 journalfile_v2_generate_path(datafile, file_path, sizeof(file_path));
1183 PROTECTED_ACCESS_SETUP(datafile->journalfile->mmap.data, datafile->journalfile->mmap.size, file_path, "read");
1184 if (no_signal_received) {
1185 + time_t journal_start_time_s = (time_t)(j2_header->start_time_ut / USEC_PER_SEC);
1186 +
1187 + if (journal_start_time_s < global_first_time_s)
1188 + global_first_time_s = journal_start_time_s;
1189 +
1190 + size_t metric_offset = j2_header->metric_offset;
1191 + size_t journal_metric_count = j2_header->metric_count;
1192 + size_t metric_list_size;
1193 + if (__builtin_mul_overflow(journal_metric_count, sizeof(struct journal_metric_list), &metric_list_size)) {
1194 + nd_log_daemon(NDLP_ERR,
1195 + "DBENGINE: metric list size overflow in journalfile \"%s\" "
1196 + "(metric_count=%zu, entry_size=%zu), skipping it",
1197 + file_path, journal_metric_count, sizeof(struct journal_metric_list));
1198 + journal_access_failed = true;
1199 + goto release_journal;
1200 + }
1201 + if (metric_offset > journal_v2_file_size ||
1202 + metric_list_size > journal_v2_file_size - metric_offset) {
1203 + nd_log_daemon(NDLP_ERR,
1204 + "DBENGINE: metric list exceeds journal file size in journalfile \"%s\" "
1205 + "(metric_offset=%zu, list_size=%zu, file_size=%zu), skipping it",
1206 + file_path, metric_offset, metric_list_size, journal_v2_file_size);
1207 + journal_access_failed = true;
1208 + goto release_journal;
1209 + }
1210 +
1211 + struct journal_metric_list *uuid_list =
1212 + (struct journal_metric_list *)((uint8_t *)j2_header + metric_offset);
1213 + struct uuid_first_time_s *uuid_original_entry;
1214 +
1215 size_t journal_search_start = 0; // Start of remaining search space
1216 any_matching = false;
1217 for (size_t index = 0; index < count; ++index) {
@@ -1199,6 +1221,9 @@ static time_t find_uuid_first_time(
1221 continue;
1222
1223 any_matching = true;
1224 + if (journal_search_start >= journal_metric_count)
1225 + break;
1226 +
1227 struct journal_metric_list *live_entry = &uuid_list[journal_search_start];
1228 // Check if we avoid bsearch
1229 if (journal_metric_uuid_compare(uuid_original_entry->uuid, live_entry->uuid) != 0) {
@@ -1218,10 +1243,8 @@ static time_t find_uuid_first_time(
1243 size_t found_index = live_entry - uuid_list;
1244 journal_search_start = found_index + 1; // Next search starts after this match
1245
1221 - if (journal_search_start >= journal_metric_count) {
1222 - not_matching_bsearches += (count - index - 1);
1246 + if (journal_search_start >= journal_metric_count)
1247 break;
1224 - }
1248
1249 uuid_original_entry->pages_found += live_entry->entries;
1250 uuid_original_entry->df_matched++;
@@ -1241,8 +1264,10 @@ static time_t find_uuid_first_time(
1264 }
1265 }
1266 } else {
1244 - nd_log_daemon(NDLP_ERR, "DBENGINE: journalfile \"%s\" is corrupted, skipping it", file_path);
1267 + journal_access_failed = true;
1268 }
1269 +
1270 +release_journal:
1271 journalfile_v2_data_release(datafile->journalfile);
1272
1273 if (agent_shutdown) {
@@ -1252,6 +1277,9 @@ static time_t find_uuid_first_time(
1277
1278 journalfile_count++;
1279 datafile = datafile_release_and_acquire_next_for_retention(ctx, datafile);
1280 + if (journal_access_failed)
1281 + continue;
1282 +
1283 if (!any_matching) {
1284 if (datafile)
1285 datafile_release(datafile, DATAFILE_ACQUIRE_RETENTION);