[paramiko] [PATCH] osrandom doesn't work nicely in chroot() environment

Dwayne C. Litzenberger dlitz at dlitz.net
Sat Mar 1 22:44:35 PST 2008


I'm implementing an SFTP server for paramiko, and this server supports 
chroot()ing into an individual user's home directory.

Unfortunately, once you do os.chroot(), os.urandom() fails with 
NotImplementedError:

     >>> import paramiko, os
     >>> paramiko.randpool.get_bytes(4)
     '@\x92:\xa7'
     >>> os.chroot("/home/dwon")
     >>> paramiko.randpool.get_bytes(4)
     Traceback (most recent call last):
       File "<stdin>", line 1, in ?
       File "os.py", line 720, in urandom
     NotImplementedError: /dev/urandom (or equivalent) not found

I've attached a patch that works around this problem by making
OSRandomPool automatically fall back to using a previously-opened file 
descriptor connected to /dev/urandom.  The result is that if you import the 
paramiko module before performing os.chroot(), paramiko is still able to 
get random numbers.

This patch has not been tested on win32.  Someone should do that before the 
next paramiko release, as I am known to have typos in my patches. :)

Cheers,
 - Dwayne

-- 
Dwayne C. Litzenberger <dlitz at dlitz.net>
-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: dlitz at dlitz.net-20080302055903-yar80xyhaxf5zzs5
# target_branch: http://www.lag.net/paramiko/bzr/paramiko/
# testament_sha1: bdc3ebac67bb6319245b0c8a0e22d1740d56f486
# timestamp: 2008-03-02 00:34:46 -0600
# base_revision_id: robey at lag.net-20080220060035-yi82h5kc7jod5hlu
# 
# Begin patch
=== modified file 'paramiko/osrandom.py'
--- paramiko/osrandom.py	2008-02-18 05:12:29 +0000
+++ paramiko/osrandom.py	2008-03-02 05:59:03 +0000
@@ -20,74 +20,142 @@
 
 import sys
 
-# Detect an OS random number source
+##
+## Find potential random number sources
+##
+
+# Try to import os.urandom
+try:
+    from os import urandom
+except ImportError:
+    urandom = None
+
+# Try to import the "winrandom" module
+try:
+    from Crypto.Util import winrandom
+except ImportError:
+    winrandom = None
+
+# Try to open /dev/urandom now so that paramiko will be able to access
+# it even if os.chroot() is invoked later.
+try:
+    _dev_urandom = open("/dev/urandom", "rb", 0)
+except EnvironmentError:
+    _dev_urandom = None
+
+##
+## Define RandomPool classes
+##
+
+def _workaround_windows_cryptgenrandom_bug(self):
+    # According to "Cryptanalysis of the Random Number Generator of the
+    # Windows Operating System", by Leo Dorrendorf and Zvi Gutterman
+    # and Benny Pinkas <http://eprint.iacr.org/2007/419>,
+    # CryptGenRandom only updates its internal state using kernel-provided
+    # random data every 128KiB of output.
+    self.get_bytes(128*1024)    # discard 128 KiB of output
+
+class BaseOSRandomPool(object):
+    def __init__(self, numbytes=160, cipher=None, hash=None):
+        pass
+
+    def stir(self, s=''):
+        pass
+
+    def randomize(self, N=0):
+        self.stir()
+
+    def add_event(self, s=None):
+        pass
+
+class WinRandomPool(BaseOSRandomPool):
+    """RandomPool that uses the C{winrandom} module for input"""
+    def __init__(self, numbytes=160, cipher=None, hash=None):
+        self._wr = winrandom.new()
+        self.get_bytes = self._wr.get_bytes
+        self.randomize()
+
+    def stir(self, s=''):
+        _workaround_windows_cryptgenrandom_bug(self)
+
+class DevUrandomPool(BaseOSRandomPool):
+    """RandomPool that uses the C{/dev/urandom} special device node for input"""
+    def __init__(self, numbytes=160, cipher=None, hash=None):
+        self.randomize()
+
+    def get_bytes(self, n):
+        return _dev_urandom.read(n)
+
+class OSUrandomPool(BaseOSRandomPool):
+    """RandomPool that uses the C{os.urandom} function for input.
+    
+    This module falls back to reading /dev/urandom if os.urandom is unavailable.
+    """
+    def __init__(self, numbytes=160, cipher=None, hash=None):
+        self.randomize()
+
+    def stir(self, s=''):
+        if sys.platform == 'win32':
+            _workaround_windows_cryptgenrandom_bug(self)
+
+    def get_bytes(self, n):
+        try:
+            if urandom is None:
+                raise NotImplementedError
+            return urandom(n)   # might also raise NotImplementedError (especially in chroot environment)
+        except NotImplementedError:
+            return _dev_urandom.read(n)
+
+##
+## Detect default random number source
+##
 osrandom_source = None
 
 # Try os.urandom
-if osrandom_source is None:
-    try:
-        from os import urandom
-        osrandom_source = "os.urandom"
-    except ImportError:
-        pass
+if osrandom_source is None and urandom is not None:
+    osrandom_source = "os.urandom"
+    DefaultRandomPoolClass = OSUrandomPool
 
 # Try winrandom
-if osrandom_source is None:
-    try:
-        from Crypto.Util import winrandom
-        osrandom_source = "winrandom"
-    except ImportError:
-        pass
+if osrandom_source is None and winrandom is not None:
+    osrandom_source = "winrandom"
+    DefaultRandomPoolClass = WinRandomPool
 
 # Try /dev/urandom
-if osrandom_source is None:
-    try:
-        _dev_urandom = open("/dev/urandom", "rb", 0)
-        def urandom(bytes):
-            return _dev_urandom.read(bytes)
-        osrandom_source = "/dev/urandom"
-    except (OSError, IOError):
-        pass
+if osrandom_source is None and _dev_urandom is not None:
+    osrandom_source = "/dev/urandom"
+    DefaultRandomPoolClass = DevUrandomPool
 
 # Give up
 if osrandom_source is None:
     raise ImportError("Cannot find OS entropy source")
 
-class BaseOSRandomPool(object):
-    def __init__(self, numbytes=160, cipher=None, hash=None):
-        pass
+
+##
+## Define wrapper class
+##
+
+class OSRandomPool(object):
+    """RandomPool wrapper.
+
+    The C{randpool} attribute of this object may be modified by users of this class at runtime.
+    """
+
+    def __init__(self, instance=None):
+        if instance is None:
+            instance = DefaultRandomPoolClass()
+        self.randpool = instance
 
     def stir(self, s=''):
-        # According to "Cryptanalysis of the Random Number Generator of the
-        # Windows Operating System", by Leo Dorrendorf and Zvi Gutterman
-        # and Benny Pinkas <http://eprint.iacr.org/2007/419>,
-        # CryptGenRandom only updates its internal state using kernel-provided
-        # random data every 128KiB of output.
-        if osrandom_source == 'winrandom' or sys.platform == 'win32':
-            self.get_bytes(128*1024)    # discard 128 KiB of output
+        self.randpool.stir(s)
 
     def randomize(self, N=0):
-        self.stir()
+        self.randpool.randomize(N)
 
     def add_event(self, s=None):
-        pass
-
-class WinrandomOSRandomPool(BaseOSRandomPool):
-    def __init__(self, numbytes=160, cipher=None, hash=None):
-        self._wr = winrandom.new()
-        self.get_bytes = self._wr.get_bytes
-        self.randomize()
-
-class UrandomOSRandomPool(BaseOSRandomPool):
-    def __init__(self, numbytes=160, cipher=None, hash=None):
-        self.get_bytes = urandom
-        self.randomize()
-
-if osrandom_source in ("/dev/urandom", "os.urandom"):
-    OSRandomPool = UrandomOSRandomPool
-elif osrandom_source == "winrandom":
-    OSRandomPool = WinrandomOSRandomPool
-else:
-    raise AssertionError("Unrecognized osrandom_source %r" % (osrandom_source,))
+        self.randpool.add_event(s)
+
+    def get_bytes(self, N):
+        return self.randpool.get_bytes(N)
 
 # vim:set ts=4 sw=4 sts=4 expandtab:

# Begin bundle
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


More information about the paramiko mailing list