From 44e58f7342536dc12fc436b32e3179629313ec2c Mon Sep 17 00:00:00 2001 From: Eran Markus Date: Sat, 6 Jun 2026 14:11:23 +0300 Subject: [PATCH 1/2] fix: account for DST in XCCDF result timestamp offset The start-time/end-time attributes on XCCDF TestResult elements are produced by _get_timestamp(). On glibc the offset was taken from the global `timezone` variable, which holds the standard-time offset and does not account for daylight saving time. The wall-clock fields come from localtime_r() and are DST-correct, so during DST the printed offset was wrong by one hour, making the timestamp denote the wrong instant (e.g. an America/Los_Angeles scan in summer reported -08:00 instead of -07:00). Use struct tm.tm_gmtoff when available; it carries the actual UTC offset in effect at the represented local time, DST included. A new HAVE_STRUCT_TM_TM_GMTOFF CMake feature check guards it, falling back to the timezone global (plus a DST hour) on platforms without tm_gmtoff such as the MSVC runtime. This also drops the FreeBSD-specific branch that previously re-added an hour to deliberately mirror the glibc bug. Fixes #1972 Co-Authored-By: Claude Opus 4.8 (1M context) --- CMakeLists.txt | 6 ++++++ config.h.in | 1 + src/XCCDF/result.c | 24 +++++++++++++----------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b7d1288e0..6ef1ce270f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,12 @@ check_function_exists(fts_open HAVE_FTS_OPEN) check_function_exists(strsep HAVE_STRSEP) check_function_exists(strptime HAVE_STRPTIME) +# struct tm.tm_gmtoff is a BSD/GNU extension (present on glibc, musl, the BSDs +# and macOS, absent on the MSVC runtime). When available it carries the UTC +# offset including daylight saving time, unlike the global timezone variable. +include(CheckStructHasMember) +check_struct_has_member("struct tm" tm_gmtoff time.h HAVE_STRUCT_TM_TM_GMTOFF) + check_include_file(syslog.h HAVE_SYSLOG_H) check_include_file(stdio_ext.h HAVE_STDIO_EXT_H) check_include_file(shadow.h HAVE_SHADOW_H) diff --git a/config.h.in b/config.h.in index 305bd231cf..7479e82299 100644 --- a/config.h.in +++ b/config.h.in @@ -85,6 +85,7 @@ #cmakedefine HAVE_STRSEP #cmakedefine HAVE_FLOCK #cmakedefine HAVE_STRPTIME +#cmakedefine HAVE_STRUCT_TM_TM_GMTOFF #cmakedefine OPENSCAP_PROBE_INDEPENDENT_ENVIRONMENTVARIABLE #cmakedefine OPENSCAP_PROBE_INDEPENDENT_ENVIRONMENTVARIABLE58 diff --git a/src/XCCDF/result.c b/src/XCCDF/result.c index fab6bc512c..8b8151a3fc 100644 --- a/src/XCCDF/result.c +++ b/src/XCCDF/result.c @@ -1842,7 +1842,10 @@ static inline const char *_get_timestamp(void) if (!lt) return NULL; -#if defined(OS_FREEBSD) +#if defined(HAVE_STRUCT_TM_TM_GMTOFF) + /* tm_gmtoff is the offset east of UTC in seconds and already accounts + * for daylight saving time, so it reflects the actual offset in effect + * at the represented local time. */ tz_diff = lt->tm_gmtoff; if (tz_diff < 0) { @@ -1851,20 +1854,19 @@ static inline const char *_get_timestamp(void) } else { tz_sign = '+'; } - - /* glibc's timezone offset does not account for daylight savings time. - * So we match that behavior here by adding 3600 seconds - */ - if (lt->tm_isdst) - tz_diff += 3600; #else - /* timezone is a global variable set by localtime(3) */ - if (timezone <= 0) { + /* Fallback for platforms without tm_gmtoff (e.g. the MSVC runtime). + * timezone is a global variable set by localtime(3) holding the offset + * of standard time; add an hour when daylight saving time is in effect. */ + long std_diff = timezone; + if (lt->tm_isdst > 0) + std_diff -= 3600; + if (std_diff <= 0) { tz_sign = '+'; - tz_diff = -timezone; + tz_diff = -std_diff; } else { tz_sign = '-'; - tz_diff = timezone; + tz_diff = std_diff; } #endif tz_diff /= 60; From 908899d4b483bbf6fc6b8566733d7809ec2d9443 Mon Sep 17 00:00:00 2001 From: Eran Markus Date: Sat, 6 Jun 2026 16:38:39 +0300 Subject: [PATCH 2/2] docs: clarify DST adjustment comment in timestamp fallback The fallback path subtracts 3600 seconds from `timezone`, but the comment described this as "add an hour", which reads as contradicting the code. Reword to explain that `timezone` is seconds west of UTC and DST shifts local time one hour east (closer to UTC), so the westward offset is reduced by 3600 seconds. No behavior change. --- src/XCCDF/result.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/XCCDF/result.c b/src/XCCDF/result.c index 8b8151a3fc..1aedd1a3fa 100644 --- a/src/XCCDF/result.c +++ b/src/XCCDF/result.c @@ -1856,8 +1856,10 @@ static inline const char *_get_timestamp(void) } #else /* Fallback for platforms without tm_gmtoff (e.g. the MSVC runtime). - * timezone is a global variable set by localtime(3) holding the offset - * of standard time; add an hour when daylight saving time is in effect. */ + * timezone is a global variable set by localtime(3) holding the number + * of seconds west of UTC of standard time. Daylight saving time moves + * the local time one hour east, i.e. closer to UTC, so subtract 3600 + * seconds from the westward offset when DST is in effect. */ long std_diff = timezone; if (lt->tm_isdst > 0) std_diff -= 3600;