Bug 14023 - localtime_r unexpectedly writes tzname[]
Summary: localtime_r unexpectedly writes tzname[]
Status: ASSIGNED
Alias: None
Product: glibc
Classification: Unclassified
Component: time (show other bugs)
Version: unspecified
: P2 normal
Target Milestone: ---
Assignee: Paul Pluzhnikov
URL:
Keywords:
: 19179 (view as bug list)
Depends on:
Blocks:
 
Reported: 2012-04-25 16:57 UTC by Paul Pluzhnikov
Modified: 2015-11-02 14:45 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security?


Attachments
test case (462 bytes, text/x-csrc)
2012-04-25 16:57 UTC, Paul Pluzhnikov
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Paul Pluzhnikov 2012-04-25 16:57:27 UTC
Created attachment 6368 [details]
test case

Originally discovered as a thread-santizer reported race against glibc-2.11.

Reproduced in current git trunk.

People generally assume that localtime_r is thread-safe, and does not
update tzname[].

In __tz_convert() we call tzset_internal() with always==0 to avoid writing
tzname, only to reset tzname in __tzfile_compute() which we call immediately
afterward.

Attached test case shows:

./a.out 2
__tzname[0] = 'PST'
__tzname[1] = 'PDT'
a.out: localtime_r_race.c:21: fn: Assertion `tzname_copy[0] != ((void *)0)' failed.
Aborted

GDB shows:

__tzname[0] = 'PST'
__tzname[1] = 'PDT'
[New Thread 0x7ffff7a12700 (LWP 26081)]
[New Thread 0x7ffff7211700 (LWP 26082)]
a.out: localtime_r_race.c:21: fn: Assertion `tzname_copy[0] != ((void *)0)' failed.

Program received signal SIGABRT, Aborted.
[Switching to Thread 0x7ffff7a12700 (LWP 26081)]
0x00007ffff7a49f9b in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:66
66        int res = INLINE_SYSCALL (tgkill, 3, pid, selftid, sig);
(gdb) thread apply all where

Thread 3 (Thread 0x7ffff7211700 (LWP 26082)):
#0  __strcmp_sse2 () at ../sysdeps/x86_64/multiarch/../strcmp.S:2257
#1  0x00007ffff7abc797 in __tzstring (s=0x7ffff8201954 "PDT") at tzset.c:101
#2  0x00007ffff7abe682 in __tzfile_compute (timer=140737356241236, use_localtime=use_localtime@entry=1, leap_correct=leap_correct@entry=0x7ffff7210e48, 
    leap_hit=leap_hit@entry=0x7ffff7210e44, tp=tp@entry=0x7ffff7210e80) at tzfile.c:756
#3  0x00007ffff7abd399 in __tz_convert (timer=0x7ffff7210ed8, use_localtime=1, tp=0x7ffff7210e80) at tzset.c:626
#4  0x00000000004008e2 in fn (p=0x0) at localtime_r_race.c:17
#5  0x00007ffff7dc5fab in start_thread (arg=0x7ffff7211700) at pthread_create.c:304
#6  0x00007ffff7afb99d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:114

Thread 2 (Thread 0x7ffff7a12700 (LWP 26081)):
#0  0x00007ffff7a49f9b in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:66
#1  0x00007ffff7a4b458 in __GI_abort () at abort.c:90
#2  0x00007ffff7a42ff2 in __assert_fail_base (fmt=0x7ffff7b7b628 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", 
    assertion=assertion@entry=0x400b93 "tzname_copy[0] != ((void *)0)", file=file@entry=0x400b80 "localtime_r_race.c", line=line@entry=21, 
    function=function@entry=0x400c25 "fn") at assert.c:93
#3  0x00007ffff7a430a2 in __GI___assert_fail (assertion=0x400b93 "tzname_copy[0] != ((void *)0)", file=0x400b80 "localtime_r_race.c", line=21, function=0x400c25 "fn")
    at assert.c:102
#4  0x000000000040091a in fn (p=0x0) at localtime_r_race.c:21
#5  0x00007ffff7dc5fab in start_thread (arg=0x7ffff7a12700) at pthread_create.c:304
#6  0x00007ffff7afb99d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:114

Thread 1 (Thread 0x7ffff7a14700 (LWP 26080)):
#0  0x00007ffff7dc722f in pthread_join (threadid=140737347921664, thread_return=0x0) at pthread_join.c:88
#1  0x0000000000400a73 in main (argc=2, argv=0x7fffffffd630) at localtime_r_race.c:61
#2  0x00007ffff7a36705 in __libc_start_main (main=0x40093c <main>, argc=2, ubp_av=0x7fffffffd630, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, 
    stack_end=0x7fffffffd628) at libc-start.c:225
#3  0x00000000004007f9 in _start ()
Comment 1 Rich Felker 2012-04-26 15:53:38 UTC
POSIX explicitly allows but does not require this behavior:

"Unlike localtime(), the localtime_r() function is not required to set tzname. If localtime_r() does not set tzname, it shall not set daylight and shall not set timezone."

Source: http://pubs.opengroup.org/onlinepubs/9699919799/functions/localtime.html
Comment 2 Paul Pluzhnikov 2012-04-26 16:38:36 UTC
(In reply to comment #1)
> POSIX explicitly allows but does not require this behavior:

Yes, I've read that description before filing the bug.

I believe it's a quality-of-implementation issue: effectively one can't
use tzname at all in a glibc multi-threaded program, even if one calls only
"thread-safe" localtime_r.

This behaviour is very likely to surprise most developers.
Comment 3 Ondrej Bilka 2013-10-09 08:52:44 UTC
Here patch would be straigthforward, but would need some work. Paul could you take care of it?

First could we add additional argument to __tzfile_compute to suppress writing timezone? If not then we need rename it to __tzfile_compute_internal as wrapper.

We would add extra argument local_only
Then we need add local variable like tzname_local, rename __tzname references and write to __tzname when local_only is not set.
Comment 4 Paul Pluzhnikov 2013-10-09 12:58:00 UTC
(In reply to Ondrej Bilka from comment #3)
> Here patch would be straigthforward, but would need some work. Paul could
> you take care of it?

I'll prepare a patch.
Comment 5 Paul Pluzhnikov 2015-10-30 03:10:49 UTC
*** Bug 19179 has been marked as a duplicate of this bug. ***
Comment 6 Paul Pluzhnikov 2015-10-30 03:11:50 UTC
19179 is the same problem with gmtime_r