fork: reseed Python/SSL PRNGs

Mac's SSL seems to have a pthread_atfork handler or similar that does
this for us, no clue if Linux is the same.
pull/178/head
David Wilson 6 years ago
parent 7f4368db87
commit 4903052f42

@ -575,10 +575,19 @@ Router Class
object destructors called, including TLS usage by native extension
code, triggering many new variants of all the issues above.
* Pseudo-Random Number Generator state that is easily observable by
network peers to be duplicate, violating requirements of
cryptographic protocols through one-time state reuse. In the worst
case, children continually reuse the same state due to repeatedly
forking from a static parent.
:py:meth:`fork` cleans up Mitogen-internal objects, in addition to
locks held by the :py:mod:`logging` package. You must arrange for your
program's state, including any third party packages in use, to be
cleaned up by specifying an `on_fork` function.
locks held by the :py:mod:`logging` package, reseeds
:py:func:`random.random`, and the OpenSSL PRNG via
:py:func:`ssl.RAND_add`, but only if the :py:mod:`ssl` module is
already loaded. You must arrange for your program's state, including
any third party packages in use, to be cleaned up by specifying an
`on_fork` function.
The associated stream implementation is
:py:class:`mitogen.fork.Stream`.

@ -28,6 +28,8 @@
import logging
import os
import random
import sys
import threading
import mitogen.core
@ -37,6 +39,17 @@ import mitogen.parent
LOG = logging.getLogger('mitogen')
def fixup_prngs():
"""
Add 256 bits of /dev/urandom to OpenSSL's PRNG in the child, and re-seed
the random package with the same data.
"""
s = os.urandom(256 / 8)
random.seed(s)
if 'ssl' in sys.modules:
sys.modules['ssl'].RAND_add(s, 75.0)
def break_logging_locks():
"""
After fork, ensure any logging.Handler locks are recreated, as a variety of
@ -87,6 +100,7 @@ class Stream(mitogen.parent.Stream):
mitogen.core.Latch._on_fork()
mitogen.core.Side._on_fork()
break_logging_locks()
fixup_prngs()
if self.on_fork:
self.on_fork()
mitogen.core.set_block(childfp.fileno())

@ -1,5 +1,10 @@
import ctypes
import os
import random
import ssl
import struct
import sys
import mitogen
import unittest2
@ -8,12 +13,44 @@ import testlib
import plain_old_module
IS_64BIT = struct.calcsize('P') == 8
PLATFORM_TO_PATH = {
('darwin', False): '/usr/lib/libssl.dylib',
('darwin', True): '/usr/lib/libssl.dylib',
('linux2', False): '/usr/lib/libssl.so',
('linux2', True): '/usr/lib/x86_64-linux-gnu/libssl.so',
}
c_ssl = ctypes.CDLL(PLATFORM_TO_PATH[sys.platform, IS_64BIT])
c_ssl.RAND_pseudo_bytes.argtypes = [ctypes.c_char_p, ctypes.c_int]
c_ssl.RAND_pseudo_bytes.restype = ctypes.c_int
def random_random():
return random.random()
def RAND_pseudo_bytes(n=32):
buf = ctypes.create_string_buffer(n)
assert 1 == c_ssl.RAND_pseudo_bytes(buf, n)
return buf[:]
class ForkTest(testlib.RouterMixin, unittest2.TestCase):
def test_okay(self):
context = self.router.fork()
self.assertNotEqual(context.call(os.getpid), os.getpid())
self.assertEqual(context.call(os.getppid), os.getpid())
def test_random_module_diverges(self):
context = self.router.fork()
self.assertNotEqual(context.call(random_random), random_random())
def test_ssl_module_diverges(self):
context = self.router.fork()
self.assertNotEqual(context.call(RAND_pseudo_bytes),
RAND_pseudo_bytes())
if __name__ == '__main__':
unittest2.main()

Loading…
Cancel
Save