Skip to content

Commit 358e5e1

Browse files
authored
bpo-32329: Fix -R option for hash randomization (#4873)
bpo-32329, bpo-32030: * The -R option now turns on hash randomization when the PYTHONHASHSEED environment variable is set to 0 Previously, the option was ignored. * sys.flags.hash_randomization is now properly set to 0 when hash randomization is turned off by PYTHONHASHSEED=0. * _PyCoreConfig_ReadEnv() now reads the PYTHONHASHSEED environment variable. _Py_HashRandomization_Init() now only apply the configuration, it doesn't read PYTHONHASHSEED anymore.
1 parent 96a5e50 commit 358e5e1

File tree

6 files changed

+63
-39
lines changed

6 files changed

+63
-39
lines changed

Doc/using/cmdline.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,9 @@ Miscellaneous options
277277

278278
.. cmdoption:: -R
279279

280-
Kept for compatibility. On Python 3.3 and greater, hash randomization is
281-
turned on by default.
280+
Turn on hash randomization. This option only has an effect if the
281+
:envvar:`PYTHONHASHSEED` environment variable is set to ``0``, since hash
282+
randomization is enabled by default.
282283

283284
On previous versions of Python, this option turns on hash randomization,
284285
so that the :meth:`__hash__` values of str, bytes and datetime

Include/pylifecycle.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,13 @@ PyAPI_FUNC(_PyInitError) _PyImportHooks_Init(void);
136136
PyAPI_FUNC(int) _PyFrame_Init(void);
137137
PyAPI_FUNC(int) _PyFloat_Init(void);
138138
PyAPI_FUNC(int) PyByteArray_Init(void);
139-
PyAPI_FUNC(_PyInitError) _Py_HashRandomization_Init(_PyCoreConfig *core_config);
139+
PyAPI_FUNC(_PyInitError) _Py_HashRandomization_Init(const _PyCoreConfig *);
140+
#endif
141+
#ifdef Py_BUILD_CORE
142+
PyAPI_FUNC(int) _Py_ReadHashSeed(
143+
const char *seed_text,
144+
int *use_hash_seed,
145+
unsigned long *hash_seed);
140146
#endif
141147

142148
/* Various internal finalizers */

Lib/test/test_cmd_line.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,16 @@ def test_hash_randomization(self):
432432

433433
# Verify that sys.flags contains hash_randomization
434434
code = 'import sys; print("random is", sys.flags.hash_randomization)'
435-
rc, out, err = assert_python_ok('-c', code)
436-
self.assertEqual(rc, 0)
435+
rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='')
436+
self.assertIn(b'random is 1', out)
437+
438+
rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='random')
439+
self.assertIn(b'random is 1', out)
440+
441+
rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='0')
442+
self.assertIn(b'random is 0', out)
443+
444+
rc, out, err = assert_python_ok('-R', '-c', code, PYTHONHASHSEED='0')
437445
self.assertIn(b'random is 1', out)
438446

439447
def test_del___main__(self):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The :option:`-R` option now turns on hash randomization when the
2+
:envvar:`PYTHONHASHSEED` environment variable is set to ``0``. Previously,
3+
the option was ignored. Moreover, ``sys.flags.hash_randomization`` is now
4+
properly set to 0 when hash randomization is turned off by
5+
``PYTHONHASHSEED=0``.

Modules/main.c

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ pymain_parse_cmdline_impl(_PyMain *pymain)
726726
break;
727727

728728
case 'R':
729-
/* Ignored */
729+
pymain->core_config.use_hash_seed = 0;
730730
break;
731731

732732
/* This space reserved for other options */
@@ -1293,6 +1293,10 @@ pymain_set_global_config(_PyMain *pymain)
12931293

12941294
Py_IgnoreEnvironmentFlag = pymain->core_config.ignore_environment;
12951295
Py_UTF8Mode = pymain->core_config.utf8_mode;
1296+
1297+
/* Random or non-zero hash seed */
1298+
Py_HashRandomizationFlag = (pymain->core_config.use_hash_seed == 0 ||
1299+
pymain->core_config.hash_seed != 0);
12961300
}
12971301

12981302

@@ -1694,6 +1698,24 @@ config_init_home(_PyCoreConfig *config)
16941698
}
16951699

16961700

1701+
static _PyInitError
1702+
config_init_hash_seed(_PyCoreConfig *config)
1703+
{
1704+
if (config->use_hash_seed < 0) {
1705+
const char *seed_text = pymain_get_env_var("PYTHONHASHSEED");
1706+
int use_hash_seed;
1707+
unsigned long hash_seed;
1708+
if (_Py_ReadHashSeed(seed_text, &use_hash_seed, &hash_seed) < 0) {
1709+
return _Py_INIT_USER_ERR("PYTHONHASHSEED must be \"random\" "
1710+
"or an integer in range [0; 4294967295]");
1711+
}
1712+
config->use_hash_seed = use_hash_seed;
1713+
config->hash_seed = hash_seed;
1714+
}
1715+
return _Py_INIT_OK();
1716+
}
1717+
1718+
16971719
_PyInitError
16981720
_PyCoreConfig_ReadEnv(_PyCoreConfig *config)
16991721
{
@@ -1712,6 +1734,11 @@ _PyCoreConfig_ReadEnv(_PyCoreConfig *config)
17121734
return err;
17131735
}
17141736

1737+
err = config_init_hash_seed(config);
1738+
if (_Py_INIT_FAILED(err)) {
1739+
return err;
1740+
}
1741+
17151742
return _Py_INIT_OK();
17161743
}
17171744

@@ -1777,12 +1804,6 @@ pymain_parse_envvars(_PyMain *pymain)
17771804
/* Get environment variables */
17781805
pymain_set_flags_from_env(pymain);
17791806

1780-
/* The variable is only tested for existence here;
1781-
_Py_HashRandomization_Init will check its value further. */
1782-
if (pymain_get_env_var("PYTHONHASHSEED")) {
1783-
Py_HashRandomizationFlag = 1;
1784-
}
1785-
17861807
if (pymain_warnings_envvar(pymain) < 0) {
17871808
return -1;
17881809
}

Python/bootstrap_hash.c

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -533,9 +533,10 @@ _PyOS_URandomNonblock(void *buffer, Py_ssize_t size)
533533
return pyurandom(buffer, size, 0, 1);
534534
}
535535

536-
int Py_ReadHashSeed(const char *seed_text,
537-
int *use_hash_seed,
538-
unsigned long *hash_seed)
536+
int
537+
_Py_ReadHashSeed(const char *seed_text,
538+
int *use_hash_seed,
539+
unsigned long *hash_seed)
539540
{
540541
Py_BUILD_ASSERT(sizeof(_Py_HashSecret_t) == sizeof(_Py_HashSecret.uc));
541542
/* Convert a text seed to a numeric one */
@@ -561,9 +562,9 @@ int Py_ReadHashSeed(const char *seed_text,
561562
return 0;
562563
}
563564

564-
static _PyInitError
565-
init_hash_secret(int use_hash_seed,
566-
unsigned long hash_seed)
565+
566+
_PyInitError
567+
_Py_HashRandomization_Init(const _PyCoreConfig *config)
567568
{
568569
void *secret = &_Py_HashSecret;
569570
Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
@@ -573,14 +574,14 @@ init_hash_secret(int use_hash_seed,
573574
}
574575
_Py_HashSecret_Initialized = 1;
575576

576-
if (use_hash_seed) {
577-
if (hash_seed == 0) {
577+
if (config->use_hash_seed) {
578+
if (config->hash_seed == 0) {
578579
/* disable the randomized hash */
579580
memset(secret, 0, secret_size);
580581
}
581582
else {
582583
/* use the specified hash seed */
583-
lcg_urandom(hash_seed, secret, secret_size);
584+
lcg_urandom(config->hash_seed, secret, secret_size);
584585
}
585586
}
586587
else {
@@ -601,24 +602,6 @@ init_hash_secret(int use_hash_seed,
601602
return _Py_INIT_OK();
602603
}
603604

604-
_PyInitError
605-
_Py_HashRandomization_Init(_PyCoreConfig *core_config)
606-
{
607-
const char *seed_text;
608-
int use_hash_seed = core_config->use_hash_seed;
609-
unsigned long hash_seed = core_config->hash_seed;
610-
611-
if (use_hash_seed < 0) {
612-
seed_text = Py_GETENV("PYTHONHASHSEED");
613-
if (Py_ReadHashSeed(seed_text, &use_hash_seed, &hash_seed) < 0) {
614-
return _Py_INIT_USER_ERR("PYTHONHASHSEED must be \"random\" "
615-
"or an integer in range [0; 4294967295]");
616-
}
617-
core_config->use_hash_seed = use_hash_seed;
618-
core_config->hash_seed = hash_seed;
619-
}
620-
return init_hash_secret(use_hash_seed, hash_seed);
621-
}
622605

623606
void
624607
_Py_HashRandomization_Fini(void)

0 commit comments

Comments
 (0)