Skip to content

bpo-32329: Fix -R option for hash randomization #4873

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,9 @@ Miscellaneous options

.. cmdoption:: -R

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

On previous versions of Python, this option turns on hash randomization,
so that the :meth:`__hash__` values of str, bytes and datetime
Expand Down
8 changes: 7 additions & 1 deletion Include/pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,13 @@ PyAPI_FUNC(_PyInitError) _PyImportHooks_Init(void);
PyAPI_FUNC(int) _PyFrame_Init(void);
PyAPI_FUNC(int) _PyFloat_Init(void);
PyAPI_FUNC(int) PyByteArray_Init(void);
PyAPI_FUNC(_PyInitError) _Py_HashRandomization_Init(_PyCoreConfig *core_config);
PyAPI_FUNC(_PyInitError) _Py_HashRandomization_Init(const _PyCoreConfig *);
#endif
#ifdef Py_BUILD_CORE
PyAPI_FUNC(int) _Py_ReadHashSeed(
const char *seed_text,
int *use_hash_seed,
unsigned long *hash_seed);
#endif

/* Various internal finalizers */
Expand Down
12 changes: 10 additions & 2 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,16 @@ def test_hash_randomization(self):

# Verify that sys.flags contains hash_randomization
code = 'import sys; print("random is", sys.flags.hash_randomization)'
rc, out, err = assert_python_ok('-c', code)
self.assertEqual(rc, 0)
rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='')
self.assertIn(b'random is 1', out)

rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='random')
self.assertIn(b'random is 1', out)

rc, out, err = assert_python_ok('-c', code, PYTHONHASHSEED='0')
self.assertIn(b'random is 0', out)

rc, out, err = assert_python_ok('-R', '-c', code, PYTHONHASHSEED='0')
self.assertIn(b'random is 1', out)

def test_del___main__(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The :option:`-R` option now turns on hash randomization when the
:envvar:`PYTHONHASHSEED` environment variable is set to ``0``. Previously,
the option was ignored. Moreover, ``sys.flags.hash_randomization`` is now
properly set to 0 when hash randomization is turned off by
``PYTHONHASHSEED=0``.
35 changes: 28 additions & 7 deletions Modules/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ pymain_parse_cmdline_impl(_PyMain *pymain)
break;

case 'R':
/* Ignored */
pymain->core_config.use_hash_seed = 0;
break;

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

Py_IgnoreEnvironmentFlag = pymain->core_config.ignore_environment;
Py_UTF8Mode = pymain->core_config.utf8_mode;

/* Random or non-zero hash seed */
Py_HashRandomizationFlag = (pymain->core_config.use_hash_seed == 0 ||
pymain->core_config.hash_seed != 0);
}


Expand Down Expand Up @@ -1694,6 +1698,24 @@ config_init_home(_PyCoreConfig *config)
}


static _PyInitError
config_init_hash_seed(_PyCoreConfig *config)
{
if (config->use_hash_seed < 0) {
const char *seed_text = pymain_get_env_var("PYTHONHASHSEED");
int use_hash_seed;
unsigned long hash_seed;
if (_Py_ReadHashSeed(seed_text, &use_hash_seed, &hash_seed) < 0) {
return _Py_INIT_USER_ERR("PYTHONHASHSEED must be \"random\" "
"or an integer in range [0; 4294967295]");
}
config->use_hash_seed = use_hash_seed;
config->hash_seed = hash_seed;
}
return _Py_INIT_OK();
}


_PyInitError
_PyCoreConfig_ReadEnv(_PyCoreConfig *config)
{
Expand All @@ -1712,6 +1734,11 @@ _PyCoreConfig_ReadEnv(_PyCoreConfig *config)
return err;
}

err = config_init_hash_seed(config);
if (_Py_INIT_FAILED(err)) {
return err;
}

return _Py_INIT_OK();
}

Expand Down Expand Up @@ -1777,12 +1804,6 @@ pymain_parse_envvars(_PyMain *pymain)
/* Get environment variables */
pymain_set_flags_from_env(pymain);

/* The variable is only tested for existence here;
_Py_HashRandomization_Init will check its value further. */
if (pymain_get_env_var("PYTHONHASHSEED")) {
Py_HashRandomizationFlag = 1;
}

if (pymain_warnings_envvar(pymain) < 0) {
return -1;
}
Expand Down
37 changes: 10 additions & 27 deletions Python/bootstrap_hash.c
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,10 @@ _PyOS_URandomNonblock(void *buffer, Py_ssize_t size)
return pyurandom(buffer, size, 0, 1);
}

int Py_ReadHashSeed(const char *seed_text,
int *use_hash_seed,
unsigned long *hash_seed)
int
_Py_ReadHashSeed(const char *seed_text,
int *use_hash_seed,
unsigned long *hash_seed)
{
Py_BUILD_ASSERT(sizeof(_Py_HashSecret_t) == sizeof(_Py_HashSecret.uc));
/* Convert a text seed to a numeric one */
Expand All @@ -561,9 +562,9 @@ int Py_ReadHashSeed(const char *seed_text,
return 0;
}

static _PyInitError
init_hash_secret(int use_hash_seed,
unsigned long hash_seed)

_PyInitError
_Py_HashRandomization_Init(const _PyCoreConfig *config)
{
void *secret = &_Py_HashSecret;
Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
Expand All @@ -573,14 +574,14 @@ init_hash_secret(int use_hash_seed,
}
_Py_HashSecret_Initialized = 1;

if (use_hash_seed) {
if (hash_seed == 0) {
if (config->use_hash_seed) {
if (config->hash_seed == 0) {
/* disable the randomized hash */
memset(secret, 0, secret_size);
}
else {
/* use the specified hash seed */
lcg_urandom(hash_seed, secret, secret_size);
lcg_urandom(config->hash_seed, secret, secret_size);
}
}
else {
Expand All @@ -601,24 +602,6 @@ init_hash_secret(int use_hash_seed,
return _Py_INIT_OK();
}

_PyInitError
_Py_HashRandomization_Init(_PyCoreConfig *core_config)
{
const char *seed_text;
int use_hash_seed = core_config->use_hash_seed;
unsigned long hash_seed = core_config->hash_seed;

if (use_hash_seed < 0) {
seed_text = Py_GETENV("PYTHONHASHSEED");
if (Py_ReadHashSeed(seed_text, &use_hash_seed, &hash_seed) < 0) {
return _Py_INIT_USER_ERR("PYTHONHASHSEED must be \"random\" "
"or an integer in range [0; 4294967295]");
}
core_config->use_hash_seed = use_hash_seed;
core_config->hash_seed = hash_seed;
}
return init_hash_secret(use_hash_seed, hash_seed);
}

void
_Py_HashRandomization_Fini(void)
Expand Down