[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