@cryptotaxi247 / netdata-1 / commits / 025e21565

Fix coverity report (#22104)

* Add templates for timestamp range validation based on `time_t` size to prevent out-of-range errors in ML model processing and k-means deserialization. * Rename time_t validation templates for clarity - Updated template functions to use clearer naming conventions (`ml_sqlite_int64_fits_time_t` and `ml_int64_fits_nonnegative_time_t`) across ML model processing and k-means deserialization to improve readability and maintainability. * Make ml_sqlite_int64_fits_time_t and ml_int64_fits_nonnegative_time_t static inline for improved linkage and optimization * Encapsulate time_t validation functions in unnamed namespaces and remove static inline qualifiers * Refactor time_t range validation templates to improve clarity and ensure accurate bounds checks across ML model processing. Update related unit tests for consistency.

Stelios Fragkakis committed Apr 1, 2026 at 19:50 UTC 025e21565e8def5b52a141592729f5642888b034
3 files changed +60 -8
src/ml/ml-unittest.cc
+2 -2
@@ -179,9 +179,9 @@ static void test_features_zero_smooth_matches_one()
179
180 ML_TEST_ASSERT(pf0.size() == pf1.size(), "smooth_n=0 and smooth_n=1 should produce the same number of vectors");
181 for (size_t i = 0; i < pf0.size() && i < pf1.size(); i++) {
182 - for (long j = 0; j < pf0[i].size(); j++) {
182 + for (size_t j = 0; j < features0.lag_n + 1; j++) {
183 char msg[128];
184 - snprintf(msg, sizeof(msg), "smooth_n=0 should match smooth_n=1 at feature[%zu](%ld)", i, j);
184 + snprintf(msg, sizeof(msg), "smooth_n=0 should match smooth_n=1 at feature[%zu](%zu)", i, j);
185 ML_TEST_ASSERT_DOUBLE_EQ(pf0[i](j), pf1[i](j), 1e-12, msg);
186 }
187 }
src/ml/ml.cc
+40 -4
@@ -28,6 +28,39 @@ static void __attribute__((destructor)) destroy_mutex(void) {
28 netdata_mutex_destroy(&db_mutex);
29 }
30
31 +namespace {
32 +
33 +template <bool NeedLowerBound, bool NeedUpperBound>
34 +inline bool ml_sqlite_int64_fits_time_t(sqlite3_int64 value)
35 +{
36 + (void)value;
37 + return true;
38 +}
39 +
40 +template <>
41 +inline bool ml_sqlite_int64_fits_time_t<true, false>(sqlite3_int64 value)
42 +{
43 + const sqlite3_int64 kTimeMin = (sqlite3_int64) std::numeric_limits<time_t>::min();
44 + return value >= kTimeMin;
45 +}
46 +
47 +template <>
48 +inline bool ml_sqlite_int64_fits_time_t<false, true>(sqlite3_int64 value)
49 +{
50 + const sqlite3_int64 kTimeMax = (sqlite3_int64) std::numeric_limits<time_t>::max();
51 + return value <= kTimeMax;
52 +}
53 +
54 +template <>
55 +inline bool ml_sqlite_int64_fits_time_t<true, true>(sqlite3_int64 value)
56 +{
57 + const sqlite3_int64 kTimeMin = (sqlite3_int64) std::numeric_limits<time_t>::min();
58 + const sqlite3_int64 kTimeMax = (sqlite3_int64) std::numeric_limits<time_t>::max();
59 + return value >= kTimeMin && value <= kTimeMax;
60 +}
61 +
62 +}
63 +
64 static inline size_t ml_dimension_smoothing_window(const ml_dimension_t *dim)
65 {
66 unsigned chart_update_every = dim->rd->rrdset->update_every;
@@ -431,13 +464,16 @@ int ml_dimension_load_models(RRDDIM *rd, sqlite3_stmt **active_stmt) {
464
465 sqlite3_int64 raw_after = sqlite3_column_int64(res, 0);
466 sqlite3_int64 raw_before = sqlite3_column_int64(res, 1);
467 + constexpr bool kNeedLowerBound =
468 + !std::numeric_limits<time_t>::is_signed ||
469 + std::numeric_limits<time_t>::digits < std::numeric_limits<sqlite3_int64>::digits;
470 + constexpr bool kNeedUpperBound =
471 + std::numeric_limits<time_t>::digits < std::numeric_limits<sqlite3_int64>::digits;
472
473 // Protect against silent truncation when time_t is narrower than int64_t
474 // (e.g. 32-bit builds, corrupted DB, or far-future timestamps).
437 - static constexpr sqlite3_int64 kTimeMin = std::numeric_limits<time_t>::min();
438 - static constexpr sqlite3_int64 kTimeMax = std::numeric_limits<time_t>::max();
439 - if (raw_after < kTimeMin || raw_after > kTimeMax ||
440 - raw_before < kTimeMin || raw_before > kTimeMax) {
475 + if (!ml_sqlite_int64_fits_time_t<kNeedLowerBound, kNeedUpperBound>(raw_after) ||
476 + !ml_sqlite_int64_fits_time_t<kNeedLowerBound, kNeedUpperBound>(raw_before)) {
477 error_report("Skipping ML model row with out-of-range timestamps: after=%" PRId64 " before=%" PRId64,
478 (int64_t) raw_after, (int64_t) raw_before);
479 continue;
src/ml/ml_kmeans.cc
+18 -2
@@ -12,6 +12,22 @@ using std::isinf;
12 using std::isnan;
13 #endif
14
15 +namespace {
16 +
17 +template <bool TimeTNarrowerThanInt64>
18 +inline bool ml_int64_fits_nonnegative_time_t(int64_t value)
19 +{
20 + return value >= 0;
21 +}
22 +
23 +template <>
24 +inline bool ml_int64_fits_nonnegative_time_t<true>(int64_t value)
25 +{
26 + return value >= 0 && value <= (int64_t) std::numeric_limits<time_t>::max();
27 +}
28 +
29 +}
30 +
31 void
32 ml_kmeans_init(ml_kmeans_t *kmeans)
33 {
@@ -188,7 +204,7 @@ bool ml_kmeans_deserialize(ml_kmeans_inlined_t *inlined_km, struct json_object *
204 }
205 int64_t raw_after = json_object_get_int64(value);
206 // Timestamps must be non-negative Unix epoch seconds and fit in time_t.
191 - if (raw_after < 0 || raw_after > (int64_t) std::numeric_limits<time_t>::max()) {
207 + if (!ml_int64_fits_nonnegative_time_t<(sizeof(time_t) < sizeof(int64_t))>(raw_after)) {
208 netdata_log_error("Failed to deserialize kmeans: out-of-range value for 'after': %" PRId64, raw_after);
209 return false;
210 }
@@ -204,7 +220,7 @@ bool ml_kmeans_deserialize(ml_kmeans_inlined_t *inlined_km, struct json_object *
220 }
221 int64_t raw_before = json_object_get_int64(value);
222 // Same contract as 'after': non-negative and fits in time_t.
207 - if (raw_before < 0 || raw_before > (int64_t) std::numeric_limits<time_t>::max()) {
223 + if (!ml_int64_fits_nonnegative_time_t<(sizeof(time_t) < sizeof(int64_t))>(raw_before)) {
224 netdata_log_error("Failed to deserialize kmeans: out-of-range value for 'before': %" PRId64, raw_before);
225 return false;
226 }