Fix Coverity issues (Timezone) (#21971)
thiagoftsm committed
Mar 19, 2026 at 17:46 UTC
9d076a1f85816cddb5dd137112c4f6b18e006b94
3 files changed
+55
-8
src/daemon/analytics.c
+41
-5
@@ -857,6 +857,8 @@ static bool timezone_user_configured = false;
857
// fallback which only produces bare abbreviations like "CEST" or "PST".
858
static bool timezone_is_tzdb_name = false;
859
860
+static bool timezone_abbrev_normalize(char *dst, size_t dst_size, const char *src);
861
+
862
bool system_timezone_is_user_configured(void) {
863
return timezone_user_configured;
864
}
@@ -922,14 +924,28 @@ void get_system_timezone(void)
924
// inicfg_get auto-populates it with the detected default.
925
timezone_user_configured = inicfg_exists(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone");
926
927
+ char safe_timezone[FILENAME_MAX + 1];
928
+ const char *default_timezone = timezone;
929
+
930
+ if (timezone_is_tzdb_name) {
931
+ if (!timezone_name_is_safe_tzdb_path(timezone)) {
932
+ netdata_log_error("TIMEZONE: detected unsafe tzdb timezone '%s', ignoring", timezone);
933
+ default_timezone = "unknown";
934
+ }
935
+ } else if (!timezone_abbrev_normalize(safe_timezone, sizeof(safe_timezone), timezone)) {
936
+ default_timezone = "unknown";
937
+ } else {
938
+ default_timezone = safe_timezone;
939
+ }
940
+
941
// inicfg_get returns a config-system-owned pointer, stable for the process lifetime
926
- const char *configured_tz = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", timezone);
942
+ const char *configured_tz = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", default_timezone);
943
944
// Treat "timezone =" (empty value) as not user-configured,
945
// and fall back to the auto-detected timezone.
946
if (timezone_user_configured && (!configured_tz || !*configured_tz)) {
947
timezone_user_configured = false;
932
- configured_tz = timezone;
948
+ configured_tz = default_timezone;
949
}
950
951
// If the user explicitly configured a timezone, treat it as a valid tzdb name
@@ -959,7 +975,7 @@ static inline int64_t tzif_read_be64(const unsigned char *src) {
975
(int64_t)(uint64_t)src[7];
976
}
977
962
-static bool timezone_name_is_safe_tzdb_path(const char *timezone) {
978
+bool timezone_name_is_safe_tzdb_path(const char *timezone) {
979
if (!timezone || !*timezone || *timezone == '/')
980
return false;
981
@@ -971,6 +987,25 @@ static bool timezone_name_is_safe_tzdb_path(const char *timezone) {
987
return true;
988
}
989
990
+static bool timezone_abbrev_normalize(char *dst, size_t dst_size, const char *src) {
991
+ if (!dst || dst_size < 2 || !src || !*src)
992
+ return false;
993
+
994
+ size_t len = 0;
995
+ char *end = dst + dst_size - 1;
996
+
997
+ while (*src && dst < end) {
998
+ if (!(isalnum((uint8_t)*src) || *src == '_' || *src == '+' || *src == '-'))
999
+ return false;
1000
+
1001
+ *dst++ = *src++;
1002
+ len++;
1003
+ }
1004
+
1005
+ *dst = '\0';
1006
+ return len != 0 && *src == '\0';
1007
+}
1008
+
1009
static bool timezone_info_from_tm(struct tm *tmp, char *abbrev, size_t abbrev_size, int32_t *offset) {
1010
if (!tmp)
1011
return false;
@@ -1219,6 +1254,7 @@ cleanup:
1254
void refresh_system_timezone(const char *timezone, bool is_tzdb_name) {
1255
time_t t;
1256
char abbrev[64];
1257
+ char safe_abbrev[64];
1258
const char *new_abbrev = "UTC";
1259
int32_t new_offset = 0;
1260
@@ -1249,8 +1285,8 @@ void refresh_system_timezone(const char *timezone, bool is_tzdb_name) {
1285
if (!ok)
1286
ok = current_process_timezone_info(t, abbrev, sizeof(abbrev), &new_offset);
1287
1252
- if (ok && *abbrev)
1253
- new_abbrev = abbrev;
1288
+ if (ok && timezone_abbrev_normalize(safe_abbrev, sizeof(safe_abbrev), abbrev))
1289
+ new_abbrev = safe_abbrev;
1290
1291
// Atomically update the system timezone triplet
1292
system_tz_set(timezone, new_abbrev, new_offset);
src/daemon/analytics.h
+1
@@ -91,6 +91,7 @@ void get_system_timezone(void);
91
bool system_timezone_is_user_configured(void);
92
bool system_timezone_is_tzdb_name(void);
93
const char *detect_system_timezone_name(char *buffer, size_t buffer_size);
94
+bool timezone_name_is_safe_tzdb_path(const char *timezone);
95
void refresh_system_timezone(const char *timezone, bool is_tzdb_name);
96
void analytics_reset(void);
97
void analytics_init(void);
src/daemon/pulse/pulse-daemon.c
+13
-3
@@ -102,12 +102,22 @@ static void pulse_daemon_timezone_do(bool extended __maybe_unused) {
102
// Auto-detected timezone — re-detect to pick up system changes.
103
char buf[FILENAME_MAX + 1];
104
const char *detected = detect_system_timezone_name(buf, sizeof(buf));
105
- if (detected) {
105
+ if (detected && timezone_name_is_safe_tzdb_path(detected)) {
106
refresh_system_timezone(detected, true);
107
} else {
108
- // Detection failed — use the stored name with its original tzdb flag.
108
+ if (detected)
109
+ // Detected a timezone that failed the safe-path check; skip it.
110
+ netdata_log_error("TIMEZONE: detected unsafe timezone name '%s', ignoring", detected);
111
+
112
+ // Detection failed or was rejected — re-use the stored name with its original tzdb flag.
113
+ // Validate the stored name before reusing it: a previously persisted unsafe tzdb path
114
+ // must not reach refresh_system_timezone() and propagate to labels or ACLK.
115
SYSTEM_TZ tz = system_tz_get();
110
- refresh_system_timezone(tz.timezone, system_timezone_is_tzdb_name());
116
+ bool is_tzdb = system_timezone_is_tzdb_name();
117
+ if (!is_tzdb || timezone_name_is_safe_tzdb_path(tz.timezone))
118
+ refresh_system_timezone(tz.timezone, is_tzdb);
119
+ else
120
+ netdata_log_error("TIMEZONE: stored unsafe tzdb timezone name '%s', ignoring", tz.timezone ? tz.timezone : "(null)");
121
system_tz_free(&tz);
122
}
123
}