Commit 4903052f authored by David Wilson's avatar David Wilson

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.
parent 7f4368db
...@@ -575,10 +575,19 @@ Router Class ...@@ -575,10 +575,19 @@ Router Class
object destructors called, including TLS usage by native extension object destructors called, including TLS usage by native extension
code, triggering many new variants of all the issues above. 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 :py:meth:`fork` cleans up Mitogen-internal objects, in addition to
locks held by the :py:mod:`logging` package. You must arrange for your locks held by the :py:mod:`logging` package, reseeds
program's state, including any third party packages in use, to be :py:func:`random.random`, and the OpenSSL PRNG via
cleaned up by specifying an `on_fork` function. :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 The associated stream implementation is
:py:class:`mitogen.fork.Stream`. :py:class:`mitogen.fork.Stream`.
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
import logging import logging
import os import os
import random
import sys
import threading import threading
import mitogen.core import mitogen.core
...@@ -37,6 +39,17 @@ import mitogen.parent ...@@ -37,6 +39,17 @@ import mitogen.parent
LOG = logging.getLogger('mitogen') 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(): def break_logging_locks():
""" """
After fork, ensure any logging.Handler locks are recreated, as a variety of After fork, ensure any logging.Handler locks are recreated, as a variety of
...@@ -87,6 +100,7 @@ class Stream(mitogen.parent.Stream): ...@@ -87,6 +100,7 @@ class Stream(mitogen.parent.Stream):
mitogen.core.Latch._on_fork() mitogen.core.Latch._on_fork()
mitogen.core.Side._on_fork() mitogen.core.Side._on_fork()
break_logging_locks() break_logging_locks()
fixup_prngs()
if self.on_fork: if self.on_fork:
self.on_fork() self.on_fork()
mitogen.core.set_block(childfp.fileno()) mitogen.core.set_block(childfp.fileno())
......
import ctypes
import os import os
import random
import ssl
import struct
import sys
import mitogen import mitogen
import unittest2 import unittest2
...@@ -8,12 +13,44 @@ import testlib ...@@ -8,12 +13,44 @@ import testlib
import plain_old_module 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): class ForkTest(testlib.RouterMixin, unittest2.TestCase):
def test_okay(self): def test_okay(self):
context = self.router.fork() context = self.router.fork()
self.assertNotEqual(context.call(os.getpid), os.getpid()) self.assertNotEqual(context.call(os.getpid), os.getpid())
self.assertEqual(context.call(os.getppid), 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__': if __name__ == '__main__':
unittest2.main() unittest2.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment