[Python-checkins] cpython (merge 3.3 -> default): Issue #18747: Re-seed OpenSSL's pseudo-random number generator after fork.

christian.heimes python-checkins at python.org
Wed Aug 21 13:43:31 CEST 2013


http://hg.python.org/cpython/rev/49e23a3adf26
changeset:   85291:49e23a3adf26
parent:      85289:4e79c3ae8a12
parent:      85290:8e1194c39bed
user:        Christian Heimes <christian at cheimes.de>
date:        Wed Aug 21 13:26:34 2013 +0200
summary:
  Issue #18747: Re-seed OpenSSL's pseudo-random number generator after fork.
A pthread_atfork() child handler is used to seeded the PRNG with pid, time
and some stack data.

files:
  Lib/test/test_ssl.py |  32 ++++++++++++++
  Misc/NEWS            |   4 +
  Modules/_ssl.c       |  72 ++++++++++++++++++++++++++++++++
  configure            |  11 ++++
  configure.ac         |   1 +
  pyconfig.h.in        |   3 +
  6 files changed, 123 insertions(+), 0 deletions(-)


diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -143,6 +143,38 @@
         self.assertRaises(TypeError, ssl.RAND_egd, 'foo', 1)
         ssl.RAND_add("this is a random string", 75.0)
 
+    @unittest.skipUnless(os.name == 'posix', 'requires posix')
+    def test_random_fork(self):
+        status = ssl.RAND_status()
+        if not status:
+            self.fail("OpenSSL's PRNG has insufficient randomness")
+
+        rfd, wfd = os.pipe()
+        pid = os.fork()
+        if pid == 0:
+            try:
+                os.close(rfd)
+                child_random = ssl.RAND_pseudo_bytes(16)[0]
+                self.assertEqual(len(child_random), 16)
+                os.write(wfd, child_random)
+                os.close(wfd)
+            except BaseException:
+                os._exit(1)
+            else:
+                os._exit(0)
+        else:
+            os.close(wfd)
+            self.addCleanup(os.close, rfd)
+            _, status = os.waitpid(pid, 0)
+            self.assertEqual(status, 0)
+
+            child_random = os.read(rfd, 16)
+            self.assertEqual(len(child_random), 16)
+            parent_random = ssl.RAND_pseudo_bytes(16)[0]
+            self.assertEqual(len(parent_random), 16)
+
+            self.assertNotEqual(child_random, parent_random)
+
     def test_parse_cert(self):
         # note that this uses an 'unofficial' function in _ssl.c,
         # provided solely for this test, to exercise the certificate
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -38,6 +38,10 @@
 Library
 -------
 
+- Issue #18747: Re-seed OpenSSL's pseudo-random number generator after fork.
+  A pthread_atfork() child handler is used to seeded the PRNG with pid, time
+  and some stack data.
+
 - Issue #8865: Concurrent invocation of select.poll.poll() now raises a
   RuntimeError exception.  Patch by Christian Schubert.
 
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -18,6 +18,11 @@
 
 #ifdef WITH_THREAD
 #include "pythread.h"
+
+#ifdef HAVE_PTHREAD_ATFORK
+#  include <pthread.h>
+#endif
+
 #define PySSL_BEGIN_ALLOW_THREADS_S(save) \
     do { if (_ssl_locks_count>0) { (save) = PyEval_SaveThread(); } } while (0)
 #define PySSL_END_ALLOW_THREADS_S(save) \
@@ -2936,7 +2941,69 @@
 Returns number of bytes read.  Raises SSLError if connection to EGD\n\
 fails or if it does not provide enough data to seed PRNG.");
 
+/* Seed OpenSSL's PRNG at fork(), http://bugs.python.org/issue18747
+ *
+ * The child handler seeds the PRNG from pseudo-random data like pid, the
+ * current time (nanoseconds, miliseconds or seconds) and an uninitialized
+ * array. The array contains stack variables that are impossible to predict
+ * on most systems, e.g. function return address (subject to ASLR), the
+ * stack protection canary and automatic variables.
+ * The code is inspired by Apache's ssl_rand_seed() function.
+ *
+ * Note:
+ * The code uses pthread_atfork() until Python has a proper atfork API. The
+ * handlers are not removed from the child process.
+ */
+
+#if defined(HAVE_PTHREAD_ATFORK) && defined(WITH_THREAD)
+#define PYSSL_RAND_ATFORK 1
+
+static void
+PySSL_RAND_atfork_child(void)
+{
+    struct {
+        char stack[128];    /* uninitialized (!) stack data, 128 is an
+                               arbitrary number. */
+        pid_t pid;          /* current pid */
+        _PyTime_timeval tp; /* current time */
+    } seed;
+
+#ifdef WITH_VALGRIND
+    VALGRIND_MAKE_MEM_DEFINED(seed.stack, sizeof(seed.stack));
 #endif
+    seed.pid = getpid();
+    _PyTime_gettimeofday(&(seed.tp));
+
+#if 0
+    fprintf(stderr, "PySSL_RAND_atfork_child() seeds %i bytes in pid %i\n",
+            (int)sizeof(seed), seed.pid);
+#endif
+    RAND_add((unsigned char *)&seed, sizeof(seed), 0.0);
+}
+
+static int
+PySSL_RAND_atfork(void)
+{
+    static int registered = 0;
+    int retval;
+
+    if (registered)
+        return 0;
+
+    retval = pthread_atfork(NULL,                     /* prepare */
+                            NULL,                     /* parent */
+                            PySSL_RAND_atfork_child); /* child */
+    if (retval != 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+    registered = 1;
+    return 0;
+}
+#endif /* HAVE_PTHREAD_ATFORK */
+
+#endif /* HAVE_OPENSSL_RAND */
+
 
 PyDoc_STRVAR(PySSL_get_default_verify_paths_doc,
 "get_default_verify_paths() -> tuple\n\
@@ -3549,5 +3616,10 @@
     if (r == NULL || PyModule_AddObject(m, "_OPENSSL_API_VERSION", r))
         return NULL;
 
+#ifdef PYSSL_RAND_ATFORK
+    if (PySSL_RAND_atfork() == -1)
+        return NULL;
+#endif
+
     return m;
 }
diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -9809,6 +9809,17 @@
 fi
 done
 
+      for ac_func in pthread_atfork
+do :
+  ac_fn_c_check_func "$LINENO" "pthread_atfork" "ac_cv_func_pthread_atfork"
+if test "x$ac_cv_func_pthread_atfork" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_PTHREAD_ATFORK 1
+_ACEOF
+
+fi
+done
+
 fi
 
 
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -2512,6 +2512,7 @@
             [Define if pthread_sigmask() does not work on your system.])
             ;;
         esac])
+      AC_CHECK_FUNCS(pthread_atfork)
 fi
 
 
diff --git a/pyconfig.h.in b/pyconfig.h.in
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -633,6 +633,9 @@
 /* Define if your compiler supports function prototype */
 #undef HAVE_PROTOTYPES
 
+/* Define to 1 if you have the `pthread_atfork' function. */
+#undef HAVE_PTHREAD_ATFORK
+
 /* Defined for Solaris 2.6 bug in pthread header. */
 #undef HAVE_PTHREAD_DESTRUCTOR
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list