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);