[Bug 269207] localtime.c fails to detect mobile device timezone change

From: <bugzilla-noreply_at_freebsd.org>
Date: Sat, 28 Jan 2023 17:38:24 UTC
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=269207

            Bug ID: 269207
           Summary: localtime.c fails to detect mobile device timezone
                    change
           Product: Base System
           Version: CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Many People
          Priority: ---
         Component: bin
          Assignee: bugs@FreeBSD.org
          Reporter: fbsd@opal.com

Currently, localtime(3) determines the local timezone using either the TZ
environment variable or the /etc/localtime file.  Once it has obtained the zone
information, a flag is set, the zone information is cached and the information
is re-used on subsequent calls to localtime() by the same program.

In situations where long-running programs persist over mobile device moves that
span timezones, this behavior means that such programs will remain unaware of
any system timezone changes, resulting in events occurring at times not
expected by the user.

A real-world example is a laptop that executes cron(8) jobs at local times. 
The user suspends the laptop, travels to a new timezone, resumes the laptop and
updates the system timezone information using tzsetup(8) or other means. 
Currently, cron(8) continues to execute jobs at times of the original timezone.
 Desired behavior is that cron(8) starts to use the new timezone.  Similarly
for other long-running programs.

Of course, long-running programs can be restarted after the timezone move. 
However, this may be undesirable, and restarting system programs such as
cron(8) is not something that non-technical users may be aware of the need for
or even have sufficient privileges to do.

In localtime.c, the flag (the variable "lcl_is_set") is used both by
tzset_basic() and tzsetwall_basic().

In tzset_basic(), the flag is set to the length of the zoneinfo provided in the
TZ environment variable which, of course, will not change for the life of a
long-running program.

In tzsetwall_basic(), the flag is set to -1 when the zone information is read
from /etc/localtime.  As described above, this file could be changed during the
life of a long-running program.  The use of the lcl_is_set flag is therefore
not suitable in this case.

The attached patch removes the use of lcl_is_set from tzsetwall_basic() and
replaces it with examination of the zone file's mtime in tzload() instead. 
Once the zone information has been loaded from the file, the file's name and
mtime are stored.  On subsequent calls, if these are unchanged, the cached zone
information is returned.  Otherwise the file is re-read to obtain the new zone
information.

When the TZ environment variable is unset, the existing code has the following
calling sequence:
    localtime() => tzset_basic() => tzsetwall_basic() => tzload() => tzparse()
=> tzload()
The first call to tzload() is to load the /etc/localtime zone file.  The second
call to tzload() is to load the TZDEFRULES ("posixrules") zone file.  The
proposed patch is therefore aware of this and ignores that file when saving the
zone file's name and mtime.

The patch also reorders tzload()'s _open() and _fstat() sequence to be a stat()
followed by _open() in order to avoid unnecessarily opening an unchanged file
and therefore triggering an unnecessary update of the inode's atime.

Long-running programs started with a timezone specification in the TZ
environment variable are unaffected by this patch.  Even if /etc/localtime is
modified for a new timezone, such programs will continue to use the timezone
specification in their TZ variable.  This patch only affects programs for which
the TZ environment variable is not set and so /etc/localtime is used, or
programs calling tzsetwall(3) directly.

Also attached are a simple test program that calls localtime() repeatedly every
5 seconds and a Makefile to compile it and a patched localtime.c in a test
directory. Run the program and then use tzsetup() or otherwise modify
/etc/localtime to a different timezone while the program is running.

-- 
You are receiving this mail because:
You are the assignee for the bug.