Skip to content

Commit 243b5f0

Browse files
committed
Fix data race in _struct module endian table initialization
1 parent da65f38 commit 243b5f0

File tree

2 files changed

+54
-39
lines changed

2 files changed

+54
-39
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :mod:`_struct` data race in endian table initialization with
2+
subinterpreters. Patch by Shamil Abdulaev.

Modules/_struct.c

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "pycore_long.h" // _PyLong_AsByteArray()
1313
#include "pycore_moduleobject.h" // _PyModule_GetState()
1414
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
15+
#include "pycore_lock.h" // _PyOnceFlag_CallOnce()
1516

1617
#include <stddef.h> // offsetof()
1718

@@ -1505,6 +1506,55 @@ static formatdef lilendian_table[] = {
15051506
{0}
15061507
};
15071508

1509+
/* Ensure endian table optimization happens exactly once across all interpreters */
1510+
static _PyOnceFlag endian_tables_init_once = {0};
1511+
1512+
static int
1513+
init_endian_tables(void *arg)
1514+
{
1515+
(void)arg; // Unused but required by _Py_once_fn_t signature
1516+
1517+
const formatdef *native = native_table;
1518+
formatdef *other, *ptr;
1519+
#if PY_LITTLE_ENDIAN
1520+
other = lilendian_table;
1521+
#else
1522+
other = bigendian_table;
1523+
#endif
1524+
/* Scan through the native table, find a matching
1525+
entry in the endian table and swap in the
1526+
native implementations whenever possible
1527+
(64-bit platforms may not have "standard" sizes) */
1528+
while (native->format != '\0' && other->format != '\0') {
1529+
ptr = other;
1530+
while (ptr->format != '\0') {
1531+
if (ptr->format == native->format) {
1532+
/* Match faster when formats are
1533+
listed in the same order */
1534+
if (ptr == other)
1535+
other++;
1536+
/* Only use the trick if the
1537+
size matches */
1538+
if (ptr->size != native->size)
1539+
break;
1540+
/* Skip float and double, could be
1541+
"unknown" float format */
1542+
if (ptr->format == 'd' || ptr->format == 'f')
1543+
break;
1544+
/* Skip _Bool, semantics are different for standard size */
1545+
if (ptr->format == '?')
1546+
break;
1547+
ptr->pack = native->pack;
1548+
ptr->unpack = native->unpack;
1549+
break;
1550+
}
1551+
ptr++;
1552+
}
1553+
native++;
1554+
}
1555+
return 0;
1556+
}
1557+
15081558

15091559
static const formatdef *
15101560
whichtable(const char **pfmt)
@@ -2711,45 +2761,8 @@ _structmodule_exec(PyObject *m)
27112761
}
27122762

27132763
/* Check endian and swap in faster functions */
2714-
{
2715-
const formatdef *native = native_table;
2716-
formatdef *other, *ptr;
2717-
#if PY_LITTLE_ENDIAN
2718-
other = lilendian_table;
2719-
#else
2720-
other = bigendian_table;
2721-
#endif
2722-
/* Scan through the native table, find a matching
2723-
entry in the endian table and swap in the
2724-
native implementations whenever possible
2725-
(64-bit platforms may not have "standard" sizes) */
2726-
while (native->format != '\0' && other->format != '\0') {
2727-
ptr = other;
2728-
while (ptr->format != '\0') {
2729-
if (ptr->format == native->format) {
2730-
/* Match faster when formats are
2731-
listed in the same order */
2732-
if (ptr == other)
2733-
other++;
2734-
/* Only use the trick if the
2735-
size matches */
2736-
if (ptr->size != native->size)
2737-
break;
2738-
/* Skip float and double, could be
2739-
"unknown" float format */
2740-
if (ptr->format == 'd' || ptr->format == 'f')
2741-
break;
2742-
/* Skip _Bool, semantics are different for standard size */
2743-
if (ptr->format == '?')
2744-
break;
2745-
ptr->pack = native->pack;
2746-
ptr->unpack = native->unpack;
2747-
break;
2748-
}
2749-
ptr++;
2750-
}
2751-
native++;
2752-
}
2764+
if (_PyOnceFlag_CallOnce(&endian_tables_init_once, init_endian_tables, NULL) == -1) {
2765+
return -1;
27532766
}
27542767

27552768
/* Add some symbolic constants to the module */

0 commit comments

Comments
 (0)