cpython: 4a31f6b11e7a (original) (raw)
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -253,11 +253,15 @@ always available.
:const:verbose
:option:-v
:const:bytes_warning
:option:-b
:const:quiet
:option:-q
- :const:
hash_randomization
:option:-R
============================= ============================= .. versionchanged:: 3.2 Addedquiet
attribute for the new :option:-q
flag. - .. versionadded:: 3.2.3
The ``hash_randomization`` attribute.[](#l1.14)
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -1272,6 +1272,8 @@ Basic customization
inheritance of :meth:__hash__
will be blocked, just as if :attr:__hash__
had been explicitly set to :const:None
.
--- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -24,7 +24,7 @@ Command line When invoking Python, you may specify any of these options::
The most common use case is, of course, a simple invocation of a script:: @@ -227,6 +227,29 @@ Miscellaneous options .. versionadded:: 3.2 +.. cmdoption:: -R +
- Turn on hash randomization, so that the :meth:
__hash__
values of str, bytes - and datetime objects are "salted" with an unpredictable random value.
- Although they remain constant within an individual Python process, they are
- not predictable between repeated invocations of Python. +
- This is intended to provide protection against a denial-of-service caused by
- carefully-chosen inputs that exploit the worst case performance of a dict
- insertion, O(n^2) complexity. See
- http://www.ocert.org/advisories/ocert-2011-003.html for details. +
- Changing hash values affects the order in which keys are retrieved from a
- dict. Although Python has never made guarantees about this ordering (and it
- typically varies between 32-bit and 64-bit builds), enough real-world code
- implicitly relies on this non-guaranteed behavior that the randomization is
- disabled by default. +
- See also :envvar:
PYTHONHASHSEED
. + - .. versionadded:: 3.2.3 +
+
.. cmdoption:: -s
Don't add the :data:user site-packages directory <site.USER_SITE>
to
@@ -350,6 +373,7 @@ Options you shouldn't use
.. _Jython: http://jython.org[](#l3.44)
+
.. _using-on-envvars:
Environment variables
@@ -458,6 +482,27 @@ These environment variables influence Py
option.
+.. envvar:: PYTHONHASHSEED
+
- If this variable is set to
random
, the effect is the same as specifying - the :option:
-R
option: a random value is used to seed the hashes of str, - bytes and datetime objects. +
- If :envvar:
PYTHONHASHSEED
is set to an integer value, it is used as a fixed - seed for generating the hash() of the types covered by the hash
- randomization. +
- Its purpose is to allow repeatable hashing, such as for selftests for the
- interpreter itself, or to allow a cluster of python processes to share hash
- values. +
- The integer must be a decimal number in the range [0,4294967295]. Specifying
- the value 0 will lead to the same hash values as when hash randomization is
- disabled. +
- .. versionadded:: 3.2.3 +
+ .. envvar:: PYTHONIOENCODING If this is set before running the interpreter, it overrides the encoding used
--- a/Include/object.h +++ b/Include/object.h @@ -517,6 +517,12 @@ PyAPI_FUNC(Py_hash_t) _Py_HashDouble(dou PyAPI_FUNC(Py_hash_t) _Py_HashPointer(void*); #endif +typedef struct {
+} _Py_HashSecret_t; +PyAPI_DATA(_Py_HashSecret_t) _Py_HashSecret; + /* Helper for passing objects to printf and the like */ #define PyObject_REPR(obj) _PyUnicode_AsString(PyObject_Repr(obj))
--- a/Include/pydebug.h +++ b/Include/pydebug.h @@ -20,6 +20,7 @@ PyAPI_DATA(int) Py_DivisionWarningFlag; PyAPI_DATA(int) Py_DontWriteBytecodeFlag; PyAPI_DATA(int) Py_NoUserSiteDirectory; PyAPI_DATA(int) Py_UnbufferedStdioFlag; +PyAPI_DATA(int) Py_HashRandomizationFlag; /* this is a wrapper around getenv() that pays attention to Py_IgnoreEnvironmentFlag. It should be used for getting variables like
--- a/Include/pythonrun.h +++ b/Include/pythonrun.h @@ -248,6 +248,8 @@ typedef void (PyOS_sighandler_t)(int); PyAPI_FUNC(PyOS_sighandler_t) PyOS_getsig(int); PyAPI_FUNC(PyOS_sighandler_t) PyOS_setsig(int, PyOS_sighandler_t); +/ Random */ +PyAPI_FUNC(int) _PyOS_URandom (void *buffer, Py_ssize_t size); #ifdef __cplusplus }
--- a/Lib/json/init.py +++ b/Lib/json/init.py @@ -31,7 +31,9 @@ Encoding basic Python object hierarchies Compact encoding:: >>> import json
--- a/Lib/os.py +++ b/Lib/os.py @@ -761,23 +761,6 @@ try: except NameError: # statvfs_result may not exist pass -if not _exists("urandom"):
Return a string of n random bytes suitable for cryptographic use.[](#l8.11)
"""[](#l8.13)
try:[](#l8.14)
_urandomfd = open("/dev/urandom", O_RDONLY)[](#l8.15)
except (OSError, IOError):[](#l8.16)
raise NotImplementedError("/dev/urandom (or equivalent) not found")[](#l8.17)
bs = b""[](#l8.18)
while len(bs) < n:[](#l8.19)
bs += read(_urandomfd, n - len(bs))[](#l8.20)
close(_urandomfd)[](#l8.21)
return bs[](#l8.22)
Supply os.popen()
def popen(cmd, mode="r", buffering=-1): if not isinstance(cmd, str):
--- a/Lib/test/mapping_tests.py +++ b/Lib/test/mapping_tests.py @@ -14,7 +14,7 @@ class BasicTestMappingProtocol(unittest. def _reference(self): """Return a dictionary of values which are invariant by storage in the object under test."""
return {1:2, "key1":"value1", "key2":(1,2,3)}[](#l9.7)
def _empty_mapping(self): """Return an empty mapping object""" return self.type2test()return {"1": "2", "key1":"value1", "key2":(1,2,3)}[](#l9.8)
--- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -496,6 +496,11 @@ def main(tests=None, testdir=None, verbo except ValueError: print("Couldn't find starting test (%s), using all tests" % start) if randomize:
hashseed = os.getenv('PYTHONHASHSEED')[](#l10.7)
if not hashseed:[](#l10.8)
os.environ['PYTHONHASHSEED'] = str(random_seed)[](#l10.9)
os.execv(sys.executable, [sys.executable] + sys.argv)[](#l10.10)
return[](#l10.11) random.seed(random_seed)[](#l10.12) print("Using random seed", random_seed)[](#l10.13) random.shuffle(selected)[](#l10.14)
--- a/Lib/test/script_helper.py +++ b/Lib/test/script_helper.py @@ -3,7 +3,6 @@ import sys import os -import re import os.path import tempfile import subprocess @@ -20,11 +19,15 @@ def _assert_python(expected_success, *ar cmd_line = [sys.executable] if not env_vars: cmd_line.append('-E')
- cmd_line.extend(args)
Need to preserve the original environment, for in-place testing of
env = os.environ.copy() shared library builds.
But a special flag that can be set to override -- in this case, the
caller is responsible to pass the full environment.
- if env_vars.pop('__cleanenv', None):
env.update(env_vars)env = {}[](#l11.22)
- cmd_line.extend(args) p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
--- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -330,6 +330,22 @@ class CmdLineTest(unittest.TestCase): def test_no_std_streams(self): self._test_no_stdio(['stdin', 'stdout', 'stderr'])
- def test_hash_randomization(self):
# Verify that -R enables hash randomization:[](#l12.8)
self.verify_valid_flag('-R')[](#l12.9)
hashes = [][](#l12.10)
for i in range(2):[](#l12.11)
code = 'print(hash("spam"))'[](#l12.12)
rc, out, err = assert_python_ok('-R', '-c', code)[](#l12.13)
self.assertEqual(rc, 0)[](#l12.14)
hashes.append(out)[](#l12.15)
self.assertNotEqual(hashes[0], hashes[1])[](#l12.16)
# Verify that sys.flags contains hash_randomization[](#l12.18)
code = 'import sys; print("random is", sys.flags.hash_randomization)'[](#l12.19)
rc, out, err = assert_python_ok('-R', '-c', code)[](#l12.20)
self.assertEqual(rc, 0)[](#l12.21)
self.assertIn(b'random is 1', out)[](#l12.22)
def test_main(): test.support.run_unittest(CmdLineTest)
--- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4474,8 +4474,18 @@ class DictProxyTests(unittest.TestCase): def test_repr(self): # Testing dict_proxy.repr
def sorted_dict_repr(repr_):[](#l13.7)
# Given the repr of a dict, sort the keys[](#l13.8)
assert repr_.startswith('{')[](#l13.9)
assert repr_.endswith('}')[](#l13.10)
kvs = repr_[1:-1].split(', ')[](#l13.11)
return '{' + ', '.join(sorted(kvs)) + '}'[](#l13.12) dict_ = {k: v for k, v in self.C.__dict__.items()}[](#l13.13)
self.assertEqual(repr(self.C.__dict__), 'dict_proxy({!r})'.format(dict_))[](#l13.14)
repr_ = repr(self.C.__dict__)[](#l13.15)
self.assert_(repr_.startswith('dict_proxy('))[](#l13.16)
self.assert_(repr_.endswith(')'))[](#l13.17)
self.assertEqual(sorted_dict_repr(repr_[len('dict_proxy('):-len(')')]),[](#l13.18)
sorted_dict_repr('{!r}'.format(dict_)))[](#l13.19)
class PTypesLongInitTest(unittest.TestCase):
--- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -52,13 +52,18 @@ class DebuggerTests(unittest.TestCase): """Test that the debugger can debug Python."""
Returns its stdout, stderr """
if env_vars:[](#l14.13)
env = os.environ.copy()[](#l14.14)
env.update(env_vars)[](#l14.15)
else:[](#l14.16)
env = None[](#l14.17) out, err = subprocess.Popen([](#l14.18)
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,[](#l14.19)
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env,[](#l14.20) ).communicate()[](#l14.21) return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace')[](#l14.22)
@@ -118,7 +123,7 @@ class DebuggerTests(unittest.TestCase): # print ' '.join(args) # Use "args" to invoke gdb, capturing stdout, stderr:
out, err = self.run_gdb(*args)[](#l14.28)
out, err = self.run_gdb(*args, PYTHONHASHSEED='0')[](#l14.29)
# Ignore some noise on stderr due to the pending breakpoint: err = err.replace('Function "%s" not defined.\n' % breakpoint, '') @@ -207,7 +212,8 @@ class PrettyPrintTests(DebuggerTests): 'Verify the pretty-printing of dictionaries' self.assertGdbRepr({}) self.assertGdbRepr({'foo': 'bar'})
self.assertGdbRepr({'foo': 'bar', 'douglas':42})[](#l14.37)
self.assertGdbRepr({'foo': 'bar', 'douglas': 42},[](#l14.38)
"{'foo': 'bar', 'douglas': 42}")[](#l14.39)
def test_lists(self): 'Verify the pretty-printing of lists' @@ -269,8 +275,8 @@ class PrettyPrintTests(DebuggerTests): def test_sets(self): 'Verify the pretty-printing of sets' self.assertGdbRepr(set())
self.assertGdbRepr(set(['a', 'b']))[](#l14.47)
self.assertGdbRepr(set([4, 5, 6]))[](#l14.48)
self.assertGdbRepr(set(['a', 'b']), "{'a', 'b'}")[](#l14.49)
self.assertGdbRepr(set([4, 5, 6]), "{4, 5, 6}")[](#l14.50)
# Ensure that we handle sets containing the "dummy" key value, # which happens on deletion: @@ -282,8 +288,8 @@ id(s)''') def test_frozensets(self): 'Verify the pretty-printing of frozensets' self.assertGdbRepr(frozenset())
self.assertGdbRepr(frozenset(['a', 'b']))[](#l14.58)
self.assertGdbRepr(frozenset([4, 5, 6]))[](#l14.59)
self.assertGdbRepr(frozenset(['a', 'b']), "frozenset({'a', 'b'})")[](#l14.60)
self.assertGdbRepr(frozenset([4, 5, 6]), "frozenset({4, 5, 6})")[](#l14.61)
def test_exceptions(self): # Test a RuntimeError
--- a/Lib/test/test_hash.py +++ b/Lib/test/test_hash.py @@ -3,10 +3,16 @@ #
Also test that hash implementations are inherited as expected
+import datetime +import os +import sys import unittest from test import support +from test.script_helper import assert_python_ok from collections import Hashable +IS_64BIT = sys.maxsize > 2**32 + class HashEqualityTestCase(unittest.TestCase): @@ -118,10 +124,92 @@ class HashBuiltinsTestCase(unittest.Test for obj in self.hashes_to_check: self.assertEqual(hash(obj), _default_hash(obj)) +class HashRandomizationTests(unittest.TestCase): +
- def get_hash(self, repr_, seed=None):
env = os.environ.copy()[](#l15.33)
env['__cleanenv'] = True # signal to assert_python not to do a copy[](#l15.34)
# of os.environ on its own[](#l15.35)
if seed is not None:[](#l15.36)
env['PYTHONHASHSEED'] = str(seed)[](#l15.37)
else:[](#l15.38)
env.pop('PYTHONHASHSEED', None)[](#l15.39)
out = assert_python_ok([](#l15.40)
'-c', self.get_hash_command(repr_),[](#l15.41)
**env)[](#l15.42)
stdout = out[1].strip()[](#l15.43)
return int(stdout)[](#l15.44)
- def test_randomized_hash(self):
# two runs should return different hashes[](#l15.47)
run1 = self.get_hash(self.repr_, seed='random')[](#l15.48)
run2 = self.get_hash(self.repr_, seed='random')[](#l15.49)
self.assertNotEqual(run1, run2)[](#l15.50)
+ +class StringlikeHashRandomizationTests(HashRandomizationTests):
- def test_null_hash(self):
# PYTHONHASHSEED=0 disables the randomized hash[](#l15.54)
if IS_64BIT:[](#l15.55)
known_hash_of_obj = 1453079729188098211[](#l15.56)
else:[](#l15.57)
known_hash_of_obj = -1600925533[](#l15.58)
# Randomization is disabled by default:[](#l15.60)
self.assertEqual(self.get_hash(self.repr_), known_hash_of_obj)[](#l15.61)
# It can also be disabled by setting the seed to 0:[](#l15.63)
self.assertEqual(self.get_hash(self.repr_, seed=0), known_hash_of_obj)[](#l15.64)
- def test_fixed_hash(self):
# test a fixed seed for the randomized hash[](#l15.67)
# Note that all types share the same values:[](#l15.68)
if IS_64BIT:[](#l15.69)
h = -4410911502303878509[](#l15.70)
else:[](#l15.71)
h = -206076799[](#l15.72)
self.assertEqual(self.get_hash(self.repr_, seed=42), h)[](#l15.73)
+ +class StrHashRandomizationTests(StringlikeHashRandomizationTests):
+ +class BytesHashRandomizationTests(StringlikeHashRandomizationTests):
+ +class DatetimeTests(HashRandomizationTests):
+ +class DatetimeDateTests(DatetimeTests):
+ +class DatetimeDatetimeTests(DatetimeTests):
+ +class DatetimeTimeTests(DatetimeTests):
+ + def test_main(): support.run_unittest(HashEqualityTestCase,
HashInheritanceTestCase,[](#l15.103)
HashBuiltinsTestCase)[](#l15.104)
HashInheritanceTestCase,[](#l15.105)
HashBuiltinsTestCase,[](#l15.106)
StrHashRandomizationTests,[](#l15.107)
BytesHashRandomizationTests,[](#l15.108)
DatetimeDateTests,[](#l15.109)
DatetimeDatetimeTests,[](#l15.110)
DatetimeTimeTests)[](#l15.111)
--- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -15,6 +15,7 @@ from test import support import contextlib import mmap import uuid +from test.script_helper import assert_python_ok
Detect whether we're on a Linux system that uses the (now outdated
and unmaintained) linuxthreads threading library. There's an issue
@@ -611,14 +612,33 @@ class DevNullTests(unittest.TestCase): self.assertEqual(f.read(), b'') class URandomTests(unittest.TestCase):
- def test_urandom(self):
try:[](#l16.16)
self.assertEqual(len(os.urandom(1)), 1)[](#l16.17)
self.assertEqual(len(os.urandom(10)), 10)[](#l16.18)
self.assertEqual(len(os.urandom(100)), 100)[](#l16.19)
self.assertEqual(len(os.urandom(1000)), 1000)[](#l16.20)
except NotImplementedError:[](#l16.21)
pass[](#l16.22)
- def test_urandom_length(self):
self.assertEqual(len(os.urandom(0)), 0)[](#l16.24)
self.assertEqual(len(os.urandom(1)), 1)[](#l16.25)
self.assertEqual(len(os.urandom(10)), 10)[](#l16.26)
self.assertEqual(len(os.urandom(100)), 100)[](#l16.27)
self.assertEqual(len(os.urandom(1000)), 1000)[](#l16.28)
- def test_urandom_value(self):
data1 = os.urandom(16)[](#l16.31)
data2 = os.urandom(16)[](#l16.32)
self.assertNotEqual(data1, data2)[](#l16.33)
- def get_urandom_subprocess(self, count):
code = '\n'.join(([](#l16.36)
'import os, sys',[](#l16.37)
'data = os.urandom(%s)' % count,[](#l16.38)
'sys.stdout.buffer.write(data)',[](#l16.39)
'sys.stdout.buffer.flush()'))[](#l16.40)
out = assert_python_ok('-c', code)[](#l16.41)
stdout = out[1][](#l16.42)
self.assertEqual(len(stdout), 16)[](#l16.43)
return stdout[](#l16.44)
- def test_urandom_subprocess(self):
data1 = self.get_urandom_subprocess(16)[](#l16.47)
data2 = self.get_urandom_subprocess(16)[](#l16.48)
self.assertNotEqual(data1, data2)[](#l16.49)
@contextlib.contextmanager def _execvpe_mockup(defpath=None):
--- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -733,6 +733,17 @@ class TestBasicOps(unittest.TestCase): if self.repr is not None: self.assertEqual(repr(self.set), self.repr)
- def check_repr_against_values(self):
text = repr(self.set)[](#l17.8)
self.assertTrue(text.startswith('{'))[](#l17.9)
self.assertTrue(text.endswith('}'))[](#l17.10)
result = text[1:-1].split(', ')[](#l17.12)
result.sort()[](#l17.13)
sorted_repr_values = [repr(value) for value in self.values][](#l17.14)
sorted_repr_values.sort()[](#l17.15)
self.assertEqual(result, sorted_repr_values)[](#l17.16)
+ def test_print(self): try: fo = open(support.TESTFN, "w") @@ -891,7 +902,9 @@ class TestBasicOpsString(TestBasicOps): self.set = set(self.values) self.dup = set(self.values) self.length = 3
self.repr = "{'a', 'c', 'b'}"[](#l17.25)
#------------------------------------------------------------------------------ @@ -902,7 +915,9 @@ class TestBasicOpsBytes(TestBasicOps): self.set = set(self.values) self.dup = set(self.values) self.length = 3
self.repr = "{b'a', b'c', b'b'}"[](#l17.36)
#------------------------------------------------------------------------------ @@ -916,11 +931,13 @@ class TestBasicOpsMixedStringBytes(TestB self.set = set(self.values) self.dup = set(self.values) self.length = 4
self.repr = "{'a', b'a', 'b', b'b'}"[](#l17.47)
def tearDown(self): self._warning_filters.exit(None, None, None)
+ #============================================================================== def baditer():
--- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -503,7 +503,7 @@ class SysModuleTest(unittest.TestCase): attrs = ("debug", "division_warning", "inspect", "interactive", "optimize", "dont_write_bytecode", "no_user_site", "no_site", "ignore_environment", "verbose",
"bytes_warning", "quiet")[](#l18.7)
"bytes_warning", "quiet", "hash_randomization")[](#l18.8) for attr in attrs:[](#l18.9) self.assertTrue(hasattr(sys.flags, attr), attr)[](#l18.10) self.assertEqual(type(getattr(sys.flags, attr)), int, attr)[](#l18.11)
--- a/Lib/test/test_urllib.py +++ b/Lib/test/test_urllib.py @@ -13,6 +13,7 @@ import sys import tempfile from base64 import b64encode +import collections def hexescape(char): """Escape char as RFC 2396 specifies""" @@ -953,8 +954,9 @@ class urlencode_Tests(unittest.TestCase) self.assertEqual("a=1&a=2", urllib.parse.urlencode({"a": [1, 2]}, True)) self.assertEqual("a=None&a=a", urllib.parse.urlencode({"a": [None, "a"]}, True))
data = collections.OrderedDict([("a", 1), ("b", 1)])[](#l19.15) self.assertEqual("a=a&a=b",[](#l19.16)
urllib.parse.urlencode({"a": {"a": 1, "b": 1}}, True))[](#l19.17)
urllib.parse.urlencode({"a": data}, True))[](#l19.18)
def test_urlencode_encoding(self): # ASCII encoding. Expect %3F with errors="replace'
old mode 100644 new mode 100755 --- a/Lib/test/test_urlparse.py +++ b/Lib/test/test_urlparse.py @@ -769,7 +769,8 @@ class UrlParseTestCase(unittest.TestCase # Other tests incidentally urlencode things; test non-covered cases: # Sequence and object values. result = urllib.parse.urlencode({'a': [1, 2], 'b': (3, 4, 5)}, True)
self.assertEqual(result, 'a=1&a=2&b=3&b=4&b=5')[](#l20.9)
# we cannot rely on ordering here[](#l20.10)
assert set(result.split('&')) == {'a=1', 'a=2', 'b=3', 'b=4', 'b=5'}[](#l20.11)
--- a/Lib/tkinter/test/test_ttk/test_functions.py +++ b/Lib/tkinter/test/test_ttk/test_functions.py @@ -143,7 +143,7 @@ class InternalFunctionsTest(unittest.Tes ('a', 'b', 'c')), ("test {a b} c", ())) # state spec and options self.assertEqual(ttk._format_elemcreate('image', False, 'test',
('a', 'b'), a='x', b='y'), ("test a b", ("-a", "x", "-b", "y")))[](#l21.7)
('a', 'b'), a='x'), ("test a b", ("-a", "x")))[](#l21.8) # format returned values as a tcl script[](#l21.9) # state spec with multiple states and an option with a multivalue[](#l21.10) self.assertEqual(ttk._format_elemcreate('image', True, 'test',[](#l21.11)
--- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -322,6 +322,7 @@ PYTHON_OBJS= [](#l22.3) Python/pystate.o [](#l22.4) Python/pythonrun.o [](#l22.5) Python/pytime.o [](#l22.6) + Python/random.o [](#l22.7) Python/structmember.o [](#l22.8) Python/symtable.o [](#l22.9) Python/sysmodule.o [](#l22.10)
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,11 @@ What's New in Python 3.2.3? Core and Builtins ----------------- +- Issue #13703: oCERT-2011-003: add -R command-line option and PYTHONHASHSEED
- environment variables, to provide an opt-in way to protect against denial of
- service attacks due to hash collisions within the dict and set types. Patch
- by David Malcolm, based on work by Victor Stinner. +
- Issue #13020: Fix a reference leak when allocating a structsequence object fails. Patch by Suman Saha.
--- a/Misc/python.man +++ b/Misc/python.man @@ -37,6 +37,9 @@ python - an interpreted, interactive, o .B -OO ] [ +.B -R +] +[ .B -Q .I argument ] @@ -152,6 +155,18 @@ Discard docstrings in addition to the \f Do not print the version and copyright messages. These messages are also suppressed in non-interactive mode. .TP +.B -R +Turn on "hash randomization", so that the hash() values of str, bytes and +datetime objects are "salted" with an unpredictable pseudo-random value. +Although they remain constant within an individual Python process, they are +not predictable between repeated invocations of Python. +.IP +This is intended to provide protection against a denial of service +caused by carefully-chosen inputs that exploit the worst case performance +of a dict insertion, O(n^2) complexity. See +http://www.ocert.org/advisories/ocert-2011-003.html[](#l24.26) +for details. +.TP .BI "-Q " argument Division control; see PEP 238. The argument must be one of "old" (the default, int/int and long/long return an int or long), "new" (new @@ -413,6 +428,20 @@ specifying \fB-v\fP multiple times. .IP PYTHONWARNINGS If this is set to a comma-separated string it is equivalent to specifying the \fB-W\fP option for each separate value. +.IP PYTHONHASHSEED +If this variable is set to "random", the effect is the same as specifying +the \fB-R\fP option: a random value is used to seed the hashes of str, +bytes and datetime objects. + +If PYTHONHASHSEED is set to an integer value, it is used as a fixed seed for +generating the hash() of the types covered by the hash randomization. Its +purpose is to allow repeatable hashing, such as for selftests for the +interpreter itself, or to allow a cluster of python processes to share hash +values. + +The integer must be a decimal number in the range [0,4294967295]. Specifying +the value 0 will lead to the same hash values as when hash randomization is +disabled. .SH AUTHOR The Python Software Foundation: http://www.python.org/psf[](#l24.51) .SH INTERNET RESOURCES
--- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -2785,10 +2785,12 @@ generic_hash(unsigned char *data, int le register Py_hash_t x; p = (unsigned char *) data;
- x = _Py_HashSecret.prefix;
- x ^= p << 7; while (--len >= 0) x = (1000003x) ^ *p++; x ^= len;
- x ^= _Py_HashSecret.suffix; if (x == -1) x = -2;
--- a/Modules/main.c +++ b/Modules/main.c @@ -46,7 +46,7 @@ static wchar_t *orig_argv; static int orig_argc; / command line options */ -#define BASE_OPTS L"bBc:dEhiJm:OqsStuvVW:xX:?" +#define BASE_OPTS L"bBc:dEhiJm:OqRsStuvVW:xX:?" #define PROGRAM_OPTS BASE_OPTS @@ -72,6 +72,9 @@ static char *usage_2 = "[](#l26.12) -O : optimize generated bytecode slightly; also PYTHONOPTIMIZE=x\n[](#l26.13) -OO : remove doc-strings in addition to the -O optimizations\n[](#l26.14) -q : don't print version and copyright messages on interactive startup\n[](#l26.15) +-R : use a pseudo-random salt to make hash() values of various types be\n[](#l26.16)
unpredictable between separate invocations of the interpreter, as\n\[](#l26.17)
a defence against denial-of-service attacks\n\[](#l26.18)
-s : don't add user site directory to sys.path; also PYTHONNOUSERSITE\n[](#l26.19)
-S : don't imply 'import site' on initialization\n[](#l26.20)
";
@@ -99,8 +102,14 @@ static char *usage_5 =
"PYTHONHOME : alternate directory (or %c).\n"
" The default module search path uses %s.\n"
"PYTHONCASEOK : ignore case in 'import' statements (Windows).\n"
-"PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n"
-;
+"PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n[](#l26.28)
+";
+static char *usage_6 = "[](#l26.30)
+PYTHONHASHSEED: if this variable is set to random
, the effect is the same \n[](#l26.31)
- as specifying the :option:
-R
option: a random value is used to seed the\n[](#l26.32) - hashes of str, bytes and datetime objects. It can also be set to an integer\n[](#l26.33)
- in the range [0,4294967295] to get hash values with a predictable seed.\n[](#l26.34) +";
static int usage(int exitcode, wchar_t* program) @@ -116,6 +125,7 @@ usage(int exitcode, wchar_t* program) fputs(usage_3, f); fprintf(f, usage_4, DELIM); fprintf(f, usage_5, DELIM, PYTHONHOMEHELP);
#if defined(__VMS) if (exitcode == 0) { @@ -429,6 +439,10 @@ Py_Main(int argc, wchar_t **argv) Py_QuietFlag++; break;
case 'R':[](#l26.51)
Py_HashRandomizationFlag++;[](#l26.52)
break;[](#l26.53)
+ /* This space reserved for other options */ default:
--- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7614,82 +7614,6 @@ posix_getloadavg(PyObject *self, PyObjec } #endif -#ifdef MS_WINDOWS - -PyDoc_STRVAR(win32_urandom__doc__, -"urandom(n) -> str\n\n[](#l27.10) -Return n random bytes suitable for cryptographic use."); - -typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,[](#l27.13)
LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\[](#l27.14)
DWORD dwFlags );[](#l27.15)
-typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,[](#l27.16)
BYTE *pbBuffer );[](#l27.17)
- -static CRYPTGENRANDOM pCryptGenRandom = NULL; -/* This handle is never explicitly released. Instead, the operating
-static PyObject* -win32_urandom(PyObject *self, PyObject *args) -{
- /* Read arguments */
- if (! PyArg_ParseTuple(args, "i:urandom", &howMany))
return NULL;[](#l27.32)
- if (howMany < 0)
return PyErr_Format(PyExc_ValueError,[](#l27.34)
"negative argument not allowed");[](#l27.35)
- if (hCryptProv == 0) {
HINSTANCE hAdvAPI32 = NULL;[](#l27.38)
CRYPTACQUIRECONTEXTA pCryptAcquireContext = NULL;[](#l27.39)
/* Obtain handle to the DLL containing CryptoAPI[](#l27.41)
This should not fail */[](#l27.42)
hAdvAPI32 = GetModuleHandle("advapi32.dll");[](#l27.43)
if(hAdvAPI32 == NULL)[](#l27.44)
return win32_error("GetModuleHandle", NULL);[](#l27.45)
/* Obtain pointers to the CryptoAPI functions[](#l27.47)
This will fail on some early versions of Win95 */[](#l27.48)
pCryptAcquireContext = (CRYPTACQUIRECONTEXTA)GetProcAddress([](#l27.49)
hAdvAPI32,[](#l27.50)
"CryptAcquireContextA");[](#l27.51)
if (pCryptAcquireContext == NULL)[](#l27.52)
return PyErr_Format(PyExc_NotImplementedError,[](#l27.53)
"CryptAcquireContextA not found");[](#l27.54)
pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress([](#l27.56)
hAdvAPI32, "CryptGenRandom");[](#l27.57)
if (pCryptGenRandom == NULL)[](#l27.58)
return PyErr_Format(PyExc_NotImplementedError,[](#l27.59)
"CryptGenRandom not found");[](#l27.60)
/* Acquire context */[](#l27.62)
if (! pCryptAcquireContext(&hCryptProv, NULL, NULL,[](#l27.63)
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))[](#l27.64)
return win32_error("CryptAcquireContext", NULL);[](#l27.65)
- }
- /* Allocate bytes */
- result = PyBytes_FromStringAndSize(NULL, howMany);
- if (result != NULL) {
/* Get random data */[](#l27.71)
memset(PyBytes_AS_STRING(result), 0, howMany); /* zero seed */[](#l27.72)
if (! pCryptGenRandom(hCryptProv, howMany, (unsigned char*)[](#l27.73)
PyBytes_AS_STRING(result))) {[](#l27.74)
Py_DECREF(result);[](#l27.75)
return win32_error("CryptGenRandom", NULL);[](#l27.76)
}[](#l27.77)
- }
- return result;
-} -#endif - PyDoc_STRVAR(device_encoding__doc__, "device_encoding(fd) -> str\n\n[](#l27.84) Return a string describing the encoding of the device\n[](#l27.85) @@ -7727,41 +7651,35 @@ device_encoding(PyObject self, PyObject return Py_None; } -#ifdef __VMS -/ Use openssl random routine / -#include <openssl/rand.h> -PyDoc_STRVAR(vms_urandom__doc__, +PyDoc_STRVAR(posix_urandom__doc__, "urandom(n) -> str\n\n[](#l27.95) Return n random bytes suitable for cryptographic use."); -static PyObject -vms_urandom(PyObject *self, PyObject *args) -{
- /* Read arguments */
- if (! PyArg_ParseTuple(args, "i:urandom", &howMany))
return NULL;[](#l27.106)
- if (howMany < 0)
+static PyObject * +posix_urandom(PyObject *self, PyObject *args) +{
/* Read arguments */[](#l27.115)
- if (!PyArg_ParseTuple(args, "n:urandom", &size))
return NULL;[](#l27.117)
- if (size < 0) return PyErr_Format(PyExc_ValueError, "negative argument not allowed");
- /* Allocate bytes */
- result = PyBytes_FromStringAndSize(NULL, howMany);
- if (result != NULL) {
/* Get random data */[](#l27.125)
if (RAND_pseudo_bytes((unsigned char*)[](#l27.126)
PyBytes_AS_STRING(result),[](#l27.127)
howMany) < 0) {[](#l27.128)
Py_DECREF(result);[](#l27.129)
return PyErr_Format(PyExc_ValueError,[](#l27.130)
"RAND_pseudo_bytes");[](#l27.131)
}[](#l27.132)
- ret = _PyOS_URandom(PyBytes_AS_STRING(result),
PyBytes_GET_SIZE(result));[](#l27.138)
- if (ret == -1) {
Py_DECREF(result);[](#l27.140)
} return result; } -#endifreturn NULL;[](#l27.141)
#ifdef HAVE_SETRESUID PyDoc_STRVAR(posix_setresuid__doc__, @@ -8137,12 +8055,7 @@ static PyMethodDef posix_methods[] = { #ifdef HAVE_GETLOADAVG {"getloadavg", posix_getloadavg, METH_NOARGS, posix_getloadavg__doc__}, #endif
- #ifdef MS_WINDOWS
- {"urandom", win32_urandom, METH_VARARGS, win32_urandom__doc__},
- #endif
- #ifdef __VMS
- {"urandom", vms_urandom, METH_VARARGS, vms_urandom__doc__},
- #endif
#ifdef HAVE_SETRESUID {"setresuid", posix_setresuid, METH_VARARGS, posix_setresuid__doc__}, #endif @@ -8155,7 +8068,6 @@ static PyMethodDef posix_methods[] = { #ifdef HAVE_GETRESGID {"getresgid", posix_getresgid, METH_NOARGS, posix_getresgid__doc__}, #endif - {NULL, NULL} /* Sentinel */ };
--- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -878,11 +878,21 @@ bytes_hash(PyBytesObject *a) if (a->ob_shash != -1) return a->ob_shash; len = Py_SIZE(a);
- /*
We make the hash of the empty string be 0, rather than using[](#l28.8)
(prefix ^ suffix), since this slightly obfuscates the hash secret[](#l28.9)
- */
- if (len == 0) {
a->ob_shash = 0;[](#l28.12)
return 0;[](#l28.13)
- } p = (unsigned char *) a->ob_sval;
- x = _Py_HashSecret.prefix;
- x ^= p << 7; while (--len >= 0) x = (_PyHASH_MULTIPLIERx) ^ *p++; x ^= Py_SIZE(a);
- x ^= _Py_HashSecret.suffix; if (x == -1) x = -2; a->ob_shash = x;
--- a/Objects/object.c +++ b/Objects/object.c @@ -754,6 +754,8 @@ PyObject_HashNotImplemented(PyObject *v) return -1; } +_Py_HashSecret_t _Py_HashSecret; + Py_hash_t PyObject_Hash(PyObject *v) {
--- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -7676,11 +7676,21 @@ unicode_hash(PyUnicodeObject *self) if (self->hash != -1) return self->hash; len = Py_SIZE(self);
- /*
We make the hash of the empty string be 0, rather than using[](#l30.8)
(prefix ^ suffix), since this slightly obfuscates the hash secret[](#l30.9)
- */
- if (len == 0) {
self->hash = 0;[](#l30.12)
return 0;[](#l30.13)
- } p = self->str;
- x = _Py_HashSecret.prefix;
- x ^= p << 7; while (--len >= 0) x = (_PyHASH_MULTIPLIERx) ^ *p++; x ^= Py_SIZE(self);
- x ^= _Py_HashSecret.suffix; if (x == -1) x = -2; self->hash = x;
--- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -1882,6 +1882,10 @@ RelativePath="..\Python\pythonrun.c" >
<File[](#l31.7)
RelativePath="..\Python\random.c"[](#l31.8)
>[](#l31.9)
</File>[](#l31.10) <File[](#l31.11) RelativePath="..\Python\structmember.c"[](#l31.12) >[](#l31.13)
--- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -70,6 +70,7 @@ extern void _PyUnicode_Init(void); extern void _PyUnicode_Fini(void); extern int _PyLong_Init(void); extern void PyLong_Fini(void); +extern void _PyRandom_Init(void); #ifdef WITH_THREAD extern void _PyGILState_Init(PyInterpreterState , PyThreadState ); @@ -89,6 +90,7 @@ int Py_FrozenFlag; / Needed by getpath. int Py_IgnoreEnvironmentFlag; / e.g. PYTHONPATH, PYTHONHOME / int Py_NoUserSiteDirectory = 0; / for -s and site.py / int Py_UnbufferedStdioFlag = 0; / Unbuffered binary std{in,out,err} / +int Py_HashRandomizationFlag = 0; / for -R and PYTHONHASHSEED */ PyThreadState *_Py_Finalizing = NULL; @@ -207,6 +209,12 @@ Py_InitializeEx(int install_sigs) Py_OptimizeFlag = add_flag(Py_OptimizeFlag, p); if ((p = Py_GETENV("PYTHONDONTWRITEBYTECODE")) && *p != '\0') Py_DontWriteBytecodeFlag = add_flag(Py_DontWriteBytecodeFlag, p);
- /* The variable is only tested for existence here; _PyRandom_Init will
check its value further. */[](#l32.24)
- if ((p = Py_GETENV("PYTHONHASHSEED")) && *p != '\0')
Py_HashRandomizationFlag = add_flag(Py_HashRandomizationFlag, p);[](#l32.26)
interp = PyInterpreterState_New(); if (interp == NULL)
new file mode 100644 --- /dev/null +++ b/Python/random.c @@ -0,0 +1,302 @@ +#include "Python.h" +#ifdef MS_WINDOWS +#include <windows.h> +#else +#include <fcntl.h> +#endif + +static int random_initialized = 0; + +#ifdef MS_WINDOWS +typedef BOOL (WINAPI *CRYPTACQUIRECONTEXTA)(HCRYPTPROV *phProv,[](#l33.15)
LPCSTR pszContainer, LPCSTR pszProvider, DWORD dwProvType,\[](#l33.16)
DWORD dwFlags );[](#l33.17)
+typedef BOOL (WINAPI *CRYPTGENRANDOM)(HCRYPTPROV hProv, DWORD dwLen,[](#l33.18)
BYTE *pbBuffer );[](#l33.19)
+ +static CRYPTGENRANDOM pCryptGenRandom = NULL; +/* This handle is never explicitly released. Instead, the operating
+static int +win32_urandom_init(int raise) +{
- /* Obtain handle to the DLL containing CryptoAPI. This should not fail. */
- hAdvAPI32 = GetModuleHandle("advapi32.dll");
- if(hAdvAPI32 == NULL)
goto error;[](#l33.35)
- /* Obtain pointers to the CryptoAPI functions. This will fail on some early
versions of Win95. */[](#l33.38)
- pCryptAcquireContext = (CRYPTACQUIRECONTEXTA)GetProcAddress(
hAdvAPI32, "CryptAcquireContextA");[](#l33.40)
- if (pCryptAcquireContext == NULL)
goto error;[](#l33.42)
- pCryptGenRandom = (CRYPTGENRANDOM)GetProcAddress(hAdvAPI32,
"CryptGenRandom");[](#l33.45)
- if (pCryptGenRandom == NULL)
goto error;[](#l33.47)
- /* Acquire context */
- if (! pCryptAcquireContext(&hCryptProv, NULL, NULL,
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))[](#l33.51)
goto error;[](#l33.52)
- if (raise)
PyErr_SetFromWindowsErr(0);[](#l33.58)
- else
Py_FatalError("Failed to initialize Windows random API (CryptoGen)");[](#l33.60)
- return -1;
+} + +/* Fill buffer with size pseudo-random bytes generated by the Windows CryptoGen
- API. Return 0 on success, or -1 on error. */ +static int +win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise) +{
- Py_ssize_t chunk;
- while (size > 0)
- {
chunk = size > INT_MAX ? INT_MAX : size;[](#l33.79)
if (!pCryptGenRandom(hCryptProv, chunk, buffer))[](#l33.80)
{[](#l33.81)
/* CryptGenRandom() failed */[](#l33.82)
if (raise)[](#l33.83)
PyErr_SetFromWindowsErr(0);[](#l33.84)
else[](#l33.85)
Py_FatalError("Failed to initialized the randomized hash "[](#l33.86)
"secret using CryptoGen)");[](#l33.87)
return -1;[](#l33.88)
}[](#l33.89)
buffer += chunk;[](#l33.90)
size -= chunk;[](#l33.91)
- }
- return 0;
+} +#endif /* MS_WINDOWS / + + +#ifdef __VMS +/ Use openssl random routine */ +#include <openssl/rand.h> +static int +vms_urandom(unsigned char *buffer, Py_ssize_t size, int raise) +{
- if (RAND_pseudo_bytes(buffer, size) < 0) {
if (raise) {[](#l33.105)
PyErr_Format(PyExc_ValueError,[](#l33.106)
"RAND_pseudo_bytes");[](#l33.107)
} else {[](#l33.108)
Py_FatalError("Failed to initialize the randomized hash "[](#l33.109)
"secret using RAND_pseudo_bytes");[](#l33.110)
}[](#l33.111)
return -1;[](#l33.112)
- }
- return 0;
+} +#endif /* __VMS / + + +#if !defined(MS_WINDOWS) && !defined(__VMS) + +/ Read size bytes from /dev/urandom into buffer.
- Call Py_FatalError() on error. */ +static void +dev_urandom_noraise(char *buffer, Py_ssize_t size) +{
- int fd;
- Py_ssize_t n;
- fd = open("/dev/urandom", O_RDONLY);
- if (fd < 0)
Py_FatalError("Failed to open /dev/urandom");[](#l33.133)
- while (0 < size)
- {
do {[](#l33.137)
n = read(fd, buffer, (size_t)size);[](#l33.138)
} while (n < 0 && errno == EINTR);[](#l33.139)
if (n <= 0)[](#l33.140)
{[](#l33.141)
/* stop on error or if read(size) returned 0 */[](#l33.142)
Py_FatalError("Failed to read bytes from /dev/urandom");[](#l33.143)
break;[](#l33.144)
}[](#l33.145)
buffer += n;[](#l33.146)
size -= (Py_ssize_t)n;[](#l33.147)
- }
- close(fd);
+} + +/* Read size bytes from /dev/urandom into buffer.
- Return 0 on success, raise an exception and return -1 on error. */ +static int +dev_urandom_python(char *buffer, Py_ssize_t size) +{
- int fd;
- Py_ssize_t n;
- Py_BEGIN_ALLOW_THREADS
- fd = open("/dev/urandom", O_RDONLY);
- Py_END_ALLOW_THREADS
- if (fd < 0)
- {
PyErr_SetFromErrnoWithFilename(PyExc_OSError, "/dev/urandom");[](#l33.168)
return -1;[](#l33.169)
- }
- Py_BEGIN_ALLOW_THREADS
- do {
do {[](#l33.174)
n = read(fd, buffer, (size_t)size);[](#l33.175)
} while (n < 0 && errno == EINTR);[](#l33.176)
if (n <= 0)[](#l33.177)
break;[](#l33.178)
buffer += n;[](#l33.179)
size -= (Py_ssize_t)n;[](#l33.180)
- } while (0 < size);
- Py_END_ALLOW_THREADS
- if (n <= 0)
- {
/* stop on error or if read(size) returned 0 */[](#l33.186)
if (n < 0)[](#l33.187)
PyErr_SetFromErrno(PyExc_OSError);[](#l33.188)
else[](#l33.189)
PyErr_Format(PyExc_RuntimeError,[](#l33.190)
"Failed to read %zi bytes from /dev/urandom",[](#l33.191)
size);[](#l33.192)
close(fd);[](#l33.193)
return -1;[](#l33.194)
- }
- close(fd);
- return 0;
+} +#endif /* !defined(MS_WINDOWS) && !defined(__VMS) / + +/ Fill buffer with pseudo-random bytes generated by a linear congruent
- Use bits 23..16 of x(n) to generate a byte. */ +static void +lcg_urandom(unsigned int x0, unsigned char *buffer, size_t size) +{
- size_t index;
- unsigned int x;
- x = x0;
- for (index=0; index < size; index++) {
x *= 214013;[](#l33.215)
x += 2531011;[](#l33.216)
/* modulo 2 ^ (8 * sizeof(int)) */[](#l33.217)
buffer[index] = (x >> 16) & 0xff;[](#l33.218)
- }
+} + +/* Fill buffer with size pseudo-random bytes, not suitable for cryptographic
- use, from the operating random number generator (RNG). +
- Return 0 on success, raise an exception and return -1 on error. */ +int +_PyOS_URandom(void *buffer, Py_ssize_t size) +{
- if (size < 0) {
PyErr_Format(PyExc_ValueError,[](#l33.230)
"negative argument not allowed");[](#l33.231)
return -1;[](#l33.232)
- }
- if (size == 0)
return 0;[](#l33.235)
+# endif +#endif +} + +void +_PyRandom_Init(void) +{
- /*
By default, hash randomization is disabled, and only[](#l33.260)
enabled if PYTHONHASHSEED is set to non-empty or if[](#l33.261)
"-R" is provided at the command line:[](#l33.262)
- */
- if (!Py_HashRandomizationFlag) {
/* Disable the randomized hash: */[](#l33.265)
memset(secret, 0, secret_size);[](#l33.266)
return;[](#l33.267)
- }
- /*
Hash randomization is enabled. Generate a per-process secret,[](#l33.271)
using PYTHONHASHSEED if provided.[](#l33.272)
- */
- env = Py_GETENV("PYTHONHASHSEED");
- if (env && *env != '\0' & strcmp(env, "random") != 0) {
char *endptr = env;[](#l33.277)
unsigned long seed;[](#l33.278)
seed = strtoul(env, &endptr, 10);[](#l33.279)
if (*endptr != '\0'[](#l33.280)
|| seed > 4294967295UL[](#l33.281)
|| (errno == ERANGE && seed == ULONG_MAX))[](#l33.282)
{[](#l33.283)
Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "[](#l33.284)
"in range [0; 4294967295]");[](#l33.285)
}[](#l33.286)
if (seed == 0) {[](#l33.287)
/* disable the randomized hash */[](#l33.288)
memset(secret, 0, secret_size);[](#l33.289)
}[](#l33.290)
else {[](#l33.291)
lcg_urandom(seed, (unsigned char*)secret, secret_size);[](#l33.292)
}[](#l33.293)
- }
- else {
(void)win32_urandom((unsigned char *)secret, secret_size, 0);[](#l33.297)
+#else /* #ifdef MS_WINDOWS */ +# ifdef __VMS
vms_urandom((unsigned char *)secret, secret_size, 0);[](#l33.300)
dev_urandom_noraise((char*)secret, secret_size);[](#l33.302)
--- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1368,6 +1368,7 @@ static PyStructSequence_Field flags_fiel /* {"skip_first", "-x"}, */ {"bytes_warning", "-b"}, {"quiet", "-q"},
- {"hash_randomization", "-R"}, {0} }; @@ -1376,9 +1377,9 @@ static PyStructSequence_Desc flags_desc flags__doc__, /* doc / flags_fields, / fields */
#endif }; @@ -1412,6 +1413,7 @@ make_flags(void) /* SetFlag(skipfirstline); */ SetFlag(Py_BytesWarningFlag); SetFlag(Py_QuietFlag);