Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 30 Jun 2026 16:59:38 +0000
From:      bugzilla-noreply@freebsd.org
To:        threads@FreeBSD.org
Subject:   [Bug 296410] sprintf/vfprintf not thread safe on AArch64 due to localeconv_l using atomic_store_int, with relaxed semantics
Message-ID:  <bug-296410-13406@https.bugs.freebsd.org/bugzilla/>

index | next in thread | raw e-mail

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=296410

            Bug ID: 296410
           Summary: sprintf/vfprintf not thread safe on AArch64 due to
                    localeconv_l using atomic_store_int, with relaxed
                    semantics
           Product: Base System
           Version: 14.4-RELEASE
          Hardware: arm64
                OS: Any
            Status: New
          Severity: Affects Some People
          Priority: ---
         Component: threads
          Assignee: threads@FreeBSD.org
          Reporter: tomas@vondra.me

While running tests (for ECPG in Postgres), we're getting occasional segfaults
in tests using threads. This seems to affect only the AArch64 machine (which is
an rpi5), while the two x86_64 machines don't seem to have this issue.

The crashes generally look like this:

------
Program terminated with signal SIGSEGV, Segmentation fault.
Address not mapped to object.
#0  __vfprintf (fp=fp@entry=0x889dab00, locale=locale@entry=0x841dfcf0
<__xlocale_global_locale>, serrno=0, fmt0=fmt0@entry=0x20087a
"Connection: %d", ap=...) at /usr/src/lib/libc/stdio/vfprintf.c:477

warning: 477    /usr/src/lib/libc/stdio/vfprintf.c: No such file or directory
[Current thread is 1 (LWP 933707)]
(gdb) bt
#0  __vfprintf (fp=fp@entry=0x889dab00, locale=locale@entry=0x841dfcf0
<__xlocale_global_locale>, serrno=0, fmt0=fmt0@entry=0x20087a
"Connection: %d", ap=...) at /usr/src/lib/libc/stdio/vfprintf.c:477
#1  0x000000008411d434 in vsprintf_l (locale=0x841dfcf0
<__xlocale_global_locale>, ap=..., str=<optimized out>, fmt=<optimized
out>) at /usr/src/lib/libc/stdio/vsprintf.c:61
#2  vsprintf (str=0x889daefc "", fmt=<optimized out>, ap=...) at
/usr/src/lib/libc/stdio/vsprintf.c:68
#3  0x00000000841123f4 in sprintf (str=0x0, str@entry=0x889daefc "",
fmt=0x841dfcf0 <__xlocale_global_locale> "") at
/usr/src/lib/libc/stdio/sprintf.c:56
#4  0x0000000000210ed8 in fn (arg=0x0) at prep.pgc:39
#5  0x00000000847844fc in thread_start (curthread=0x63044546d010) at
/usr/src/lib/libthr/thread/thr_create.c:291
#6  0x0000000000000000 in ?? ()
------

That is, it's a crash in vfprintf, on this line:

decpt_len = (decimal_point[1] == '\0' ? 1 : strlen(decimal_point));

After investigating a bit, I believe this is due to localeconv_l using atomics
in relaxed mode when updating the decimal_point:

    if (atomic_load_acq_int(&loc->numeric_locale_changed) != 0) {
        ...
        N_ASSIGN_STR(decimal_point);
        ...
        atomic_store_int(&loc->numeric_locale_changed, 0);
    }

On architectures with weakly-ordered memory model (like the AArch64), this
means the other threads may see the update to numeric_locale_changed, without
seeing the updated decimal_point pointer. Which leads to the crash.

This can be reproduced by a simple program:

------
#include <stdio.h>
#include <pthread.h>

void *fn(void *arg) {
    char buf[128];
    for(int i=0; i<10000; i++)
        sprintf(buf, "%d", 1);
    return NULL;
}

int main() {
    pthread_t th[100];

    for(int i=0; i<100; i++)
        pthread_create(&th[i], NULL, fn, NULL);

    for(int i=0; i<100; i++)
        pthread_join(th[i], NULL);

    return 0;
}
------

It's a race condition, so it may not crash. But for me it usually takes only
~10-20 tries to crash with a segfault.

I believe the code should use atomic_store_rel_int instead, to get the proper
memory ordering guarantees. Not just for decimal_point, but for the other
fields too. (I haven't looked at other places using the relaxed atomics
variants, but there might be more similar bugs there.)

In practice, this race condition may be fairly unlikely to hit - the window is
fairly narrow. And it's probably enough for the "main" process to do a single
sprintf (which initializes the cache) before starting the threads. Which our
tests don't do, but bigger apps probably do. Adding such sprintf call to the
reproducer seems to fix it.

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

home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-296410-13406>