Skip to content

Commit d16d33a

Browse files
[3.14] gh-160962: Fix PyInitConfig_SetStr() with NULL value (GH-161258) (#161292)
gh-160962: Fix PyInitConfig_SetStr() with NULL value (GH-161258) Previously, passing NULL to an optional string option crashed. Document optional string options. Add more tests on the PyInitConfig API. (cherry picked from commit 6d69297) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 6802496 commit d16d33a

5 files changed

Lines changed: 145 additions & 22 deletions

File tree

‎Doc/c-api/init_config.rst‎

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ UTF-8 encoded string. See :ref:`Configuration Options <pyinitconfig-opts>`.
156156
* Set *\*value*, and return ``0`` on success.
157157
* Set an error in *config* and return ``-1`` on error.
158158
159-
*\*value* can be set to ``NULL`` if the option is an optional string and the
160-
option is unset.
159+
*\*value* can be set to ``NULL`` if the option is unset or if the option is
160+
an optional string.
161+
Optional strings are documented with the type ``str | None`` in the
162+
:ref:`Configuration Options <pyinitconfig-opts>` table.
161163
162164
On success, the string must be released with ``free(value)`` if it's not
163165
``NULL``.
@@ -205,6 +207,9 @@ only implemented when ``Py_InitializeFromInitConfig()`` is called, not by the
205207
Set a string configuration option from a null-terminated UTF-8
206208
encoded string. The string is copied.
207209
210+
Options documented with the type ``str | None`` in the :ref:`Configuration
211+
Options <pyinitconfig-opts>` table can be set to ``NULL``.
212+
208213
* Return ``0`` on success.
209214
* Set an error in *config* and return ``-1`` on error.
210215
@@ -275,15 +280,15 @@ Configuration Options
275280
- Public
276281
* - ``"base_exec_prefix"``
277282
- :c:member:`base_exec_prefix <PyConfig.base_exec_prefix>`
278-
- ``str``
283+
- ``str | None``
279284
- Public
280285
* - ``"base_executable"``
281286
- :c:member:`base_executable <PyConfig.base_executable>`
282-
- ``str``
287+
- ``str | None``
283288
- Public
284289
* - ``"base_prefix"``
285290
- :c:member:`base_prefix <PyConfig.base_prefix>`
286-
- ``str``
291+
- ``str | None``
287292
- Public
288293
* - ``"buffered_stdio"``
289294
- :c:member:`buffered_stdio <PyConfig.buffered_stdio>`
@@ -331,15 +336,15 @@ Configuration Options
331336
- Read-only
332337
* - ``"dump_refs_file"``
333338
- :c:member:`dump_refs_file <PyConfig.dump_refs_file>`
334-
- ``str``
339+
- ``str | None``
335340
- Read-only
336341
* - ``"exec_prefix"``
337342
- :c:member:`exec_prefix <PyConfig.exec_prefix>`
338-
- ``str``
343+
- ``str | None``
339344
- Public
340345
* - ``"executable"``
341346
- :c:member:`executable <PyConfig.executable>`
342-
- ``str``
347+
- ``str | None``
343348
- Public
344349
* - ``"faulthandler"``
345350
- :c:member:`faulthandler <PyConfig.faulthandler>`
@@ -359,7 +364,7 @@ Configuration Options
359364
- Read-only
360365
* - ``"home"``
361366
- :c:member:`home <PyConfig.home>`
362-
- ``str``
367+
- ``str | None``
363368
- Read-only
364369
* - ``"import_time"``
365370
- :c:member:`import_time <PyConfig.import_time>`
@@ -431,35 +436,35 @@ Configuration Options
431436
- Public
432437
* - ``"prefix"``
433438
- :c:member:`prefix <PyConfig.prefix>`
434-
- ``str``
439+
- ``str | None``
435440
- Public
436441
* - ``"program_name"``
437442
- :c:member:`program_name <PyConfig.program_name>`
438443
- ``str``
439444
- Read-only
440445
* - ``"pycache_prefix"``
441446
- :c:member:`pycache_prefix <PyConfig.pycache_prefix>`
442-
- ``str``
447+
- ``str | None``
443448
- Public
444449
* - ``"quiet"``
445450
- :c:member:`quiet <PyConfig.quiet>`
446451
- ``bool``
447452
- Public
448453
* - ``"run_command"``
449454
- :c:member:`run_command <PyConfig.run_command>`
450-
- ``str``
455+
- ``str | None``
451456
- Read-only
452457
* - ``"run_filename"``
453458
- :c:member:`run_filename <PyConfig.run_filename>`
454-
- ``str``
459+
- ``str | None``
455460
- Read-only
456461
* - ``"run_module"``
457462
- :c:member:`run_module <PyConfig.run_module>`
458-
- ``str``
463+
- ``str | None``
459464
- Read-only
460465
* - ``"run_presite"``
461466
- :c:member:`run_presite <PyConfig.run_presite>`
462-
- ``str``
467+
- ``str | None``
463468
- Read-only
464469
* - ``"safe_path"``
465470
- :c:member:`safe_path <PyConfig.safe_path>`
@@ -479,15 +484,15 @@ Configuration Options
479484
- Read-only
480485
* - ``"stdio_encoding"``
481486
- :c:member:`stdio_encoding <PyConfig.stdio_encoding>`
482-
- ``str``
487+
- ``str | None``
483488
- Read-only
484489
* - ``"stdio_errors"``
485490
- :c:member:`stdio_errors <PyConfig.stdio_errors>`
486491
- ``str``
487492
- Read-only
488493
* - ``"stdlib_dir"``
489494
- :c:member:`stdlib_dir <PyConfig.stdlib_dir>`
490-
- ``str``
495+
- ``str | None``
491496
- Public
492497
* - ``"tracemalloc"``
493498
- :c:member:`tracemalloc <PyConfig.tracemalloc>`
@@ -542,6 +547,9 @@ Configuration Options
542547
- ``bool``
543548
- Read-only
544549
550+
The type ``str | None`` means that the option is an optional string which can
551+
be set to ``NULL``.
552+
545553
Visibility:
546554
547555
* Public: Can be retrieved by :c:func:`PyConfig_Get` and set by

‎Lib/test/test_embed.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,6 +1919,9 @@ def test_initconfig_api(self):
19191919
self.check_all_configs("test_initconfig_api", config, preconfig,
19201920
api=API_ISOLATED, ignore_stderr=True)
19211921

1922+
def test_initconfig_set_errors(self):
1923+
self.run_embedded_interpreter("test_initconfig_set_errors")
1924+
19221925
def test_initconfig_get_api(self):
19231926
self.run_embedded_interpreter("test_initconfig_get_api")
19241927

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :c:func:`PyInitConfig_SetStr` when setting an optional string option to
2+
``NULL``. Previously, the function crashed. Patch by Victor Stinner.

‎Programs/_testembed.c‎

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,7 @@ static int test_initconfig_api(void)
18591859
goto error;
18601860
}
18611861

1862+
// Set an integer option
18621863
if (PyInitConfig_SetInt(config, "hash_seed", 10) < 0) {
18631864
goto error;
18641865
}
@@ -1885,6 +1886,11 @@ static int test_initconfig_api(void)
18851886
goto error;
18861887
}
18871888

1889+
// Set an optional string to NULL explicitly (must not crash).
1890+
if (PyInitConfig_SetStr(config, "prefix", NULL) < 0) {
1891+
goto error;
1892+
}
1893+
18881894
if (Py_InitializeFromInitConfig(config) < 0) {
18891895
goto error;
18901896
}
@@ -1905,6 +1911,99 @@ static int test_initconfig_api(void)
19051911
}
19061912

19071913

1914+
static void
1915+
check_initconfig_error(PyInitConfig *config, const char *expected)
1916+
{
1917+
const char *err_msg;
1918+
assert(PyInitConfig_GetError(config, &err_msg) == 1);
1919+
assert(strcmp(err_msg, expected) == 0);
1920+
}
1921+
1922+
1923+
// Test errors of PyInitConfig_Set*() functions
1924+
static int test_initconfig_set_errors(void)
1925+
{
1926+
char* strlist[] = {"value"};
1927+
1928+
PyInitConfig *config = PyInitConfig_Create();
1929+
if (config == NULL) {
1930+
printf("Init allocation error\n");
1931+
return 1;
1932+
}
1933+
1934+
// Unknown option
1935+
assert(PyInitConfig_SetInt(config, "non-existent", 123) < 0);
1936+
check_initconfig_error(config, "unknown config option name");
1937+
assert(PyInitConfig_SetStr(config, "non-existent", "abc") < 0);
1938+
check_initconfig_error(config, "unknown config option name");
1939+
assert(PyInitConfig_SetStrList(config, "non-existent",
1940+
Py_ARRAY_LENGTH(strlist), strlist) < 0);
1941+
check_initconfig_error(config, "unknown config option name");
1942+
1943+
1944+
// Wrong type
1945+
assert(PyInitConfig_SetInt(config, "platlibdir", 123) == -1);
1946+
check_initconfig_error(config, "config option type is not int");
1947+
1948+
assert(PyInitConfig_SetStr(config, "cpu_count", "abc") == -1);
1949+
check_initconfig_error(config, "config option type is not string");
1950+
1951+
assert(PyInitConfig_SetStrList(config, "cpu_count",
1952+
Py_ARRAY_LENGTH(strlist), strlist) < 0);
1953+
check_initconfig_error(config, "config option type is not strings list");
1954+
1955+
1956+
// Test SetInt() range: cpu_count option type is PyConfig_MEMBER_INT
1957+
assert(PyInitConfig_SetInt(config, "cpu_count", INT_MIN) == 0);
1958+
assert(PyInitConfig_SetInt(config, "cpu_count", INT_MAX) == 0);
1959+
1960+
assert(PyInitConfig_SetInt(config, "cpu_count", INT64_MIN) == -1);
1961+
check_initconfig_error(config, "config option value is out of int range");
1962+
assert(PyInitConfig_SetInt(config, "cpu_count", INT64_MAX) == -1);
1963+
check_initconfig_error(config, "config option value is out of int range");
1964+
1965+
1966+
// Test SetInt() range: verbose option type is PyConfig_MEMBER_UINT
1967+
assert(PyInitConfig_SetInt(config, "verbose", 0) == 0);
1968+
assert(PyInitConfig_SetInt(config, "verbose", UINT_MAX) == 0);
1969+
1970+
assert(PyInitConfig_SetInt(config, "verbose", -1) == -1);
1971+
check_initconfig_error(config, "config option value is out of unsigned int range");
1972+
assert(PyInitConfig_SetInt(config, "verbose", INT64_MAX) == -1);
1973+
check_initconfig_error(config, "config option value is out of unsigned int range");
1974+
1975+
1976+
// SetStr() decode error
1977+
assert(PyInitConfig_SetStr(config, "platlibdir", "\xff") == -1);
1978+
check_initconfig_error(config, "decoding error");
1979+
1980+
1981+
// Setting a non-optional string to NULL fails with an error,
1982+
// but leaves the optional unchanged.
1983+
if (PyInitConfig_SetStr(config, "program_name", PROGRAM_NAME_UTF8) < 0) {
1984+
goto error;
1985+
}
1986+
assert(PyInitConfig_SetStr(config, "program_name", NULL) < 0);
1987+
check_initconfig_error(config, "config option string cannot be NULL");
1988+
char *str;
1989+
assert(PyInitConfig_GetStr(config, "program_name", &str) == 0);
1990+
assert(strcmp(str, PROGRAM_NAME_UTF8) == 0);
1991+
free(str);
1992+
1993+
1994+
PyInitConfig_Free(config);
1995+
return 0;
1996+
1997+
error:
1998+
{
1999+
const char *err_msg;
2000+
(void)PyInitConfig_GetError(config, &err_msg);
2001+
printf("Python init failed: %s\n", err_msg);
2002+
exit(1);
2003+
}
2004+
}
2005+
2006+
19082007
static int test_initconfig_get_api(void)
19092008
{
19102009
PyInitConfig *config = PyInitConfig_Create();
@@ -1917,6 +2016,7 @@ static int test_initconfig_get_api(void)
19172016
assert(PyInitConfig_HasOption(config, "verbose") == 1);
19182017
assert(PyInitConfig_HasOption(config, "utf8_mode") == 1);
19192018
assert(PyInitConfig_HasOption(config, "non-existent") == 0);
2019+
assert(PyInitConfig_HasOption(config, "") == 0);
19202020

19212021
// test PyInitConfig_GetInt()
19222022
assert(initconfig_getint(config, "dev_mode") == 0);
@@ -1928,7 +2028,7 @@ static int test_initconfig_get_api(void)
19282028
assert(PyInitConfig_SetInt(config, "utf8_mode", 1) == 0);
19292029
assert(initconfig_getint(config, "utf8_mode") == 1);
19302030

1931-
// test PyInitConfig_GetStr()
2031+
// test PyInitConfig_GetStr() on an unset string (NULL by default)
19322032
char *str;
19332033
assert(PyInitConfig_GetStr(config, "program_name", &str) == 0);
19342034
assert(str == NULL);
@@ -1954,8 +2054,11 @@ static int test_initconfig_get_api(void)
19542054

19552055
// Setting hash_seed sets use_hash_seed
19562056
assert(initconfig_getint(config, "use_hash_seed") == 0);
2057+
assert(initconfig_getint(config, "hash_seed") == 0);
2058+
19572059
assert(PyInitConfig_SetInt(config, "hash_seed", 123) == 0);
19582060
assert(initconfig_getint(config, "use_hash_seed") == 1);
2061+
assert(initconfig_getint(config, "hash_seed") == 123);
19592062

19602063
// Setting module_search_paths sets module_search_paths_set
19612064
assert(initconfig_getint(config, "module_search_paths_set") == 0);
@@ -1964,6 +2067,7 @@ static int test_initconfig_get_api(void)
19642067
Py_ARRAY_LENGTH(paths), paths) == 0);
19652068
assert(initconfig_getint(config, "module_search_paths_set") == 1);
19662069

2070+
PyInitConfig_Free(config);
19672071
return 0;
19682072
}
19692073

@@ -2703,6 +2807,7 @@ static struct TestCase TestCases[] = {
27032807
{"test_init_is_python_build", test_init_is_python_build},
27042808
{"test_init_warnoptions", test_init_warnoptions},
27052809
{"test_initconfig_api", test_initconfig_api},
2810+
{"test_initconfig_set_errors", test_initconfig_set_errors},
27062811
{"test_initconfig_get_api", test_initconfig_get_api},
27072812
{"test_initconfig_exit", test_initconfig_exit},
27082813
{"test_initconfig_module", test_initconfig_module},

‎Python/initconfig.c‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4139,10 +4139,15 @@ PyInitConfig_SetStr(PyInitConfig *config, const char *name, const char* value)
41394139
}
41404140

41414141
wchar_t **member = raw_member;
4142-
4143-
wchar_t *wstr = utf8_to_wstr(config, value);
4144-
if (wstr == NULL) {
4145-
return -1;
4142+
wchar_t *wstr;
4143+
if (value != NULL) {
4144+
wstr = utf8_to_wstr(config, value);
4145+
if (wstr == NULL) {
4146+
return -1;
4147+
}
4148+
}
4149+
else {
4150+
wstr = NULL;
41464151
}
41474152
free(*member);
41484153
*member = wstr;

0 commit comments

Comments
 (0)