changeset: 85291:49e23a3adf26 parent: 85289:4e79c3ae8a12 parent: 85290:8e1194c39bed user: Christian Heimes date: Wed Aug 21 13:26:34 2013 +0200 files: Lib/test/test_ssl.py Misc/NEWS Modules/_ssl.c configure configure.ac pyconfig.h.in description: 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. diff -r 4e79c3ae8a12 -r 49e23a3adf26 Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py Wed Aug 21 01:34:18 2013 -0700 +++ b/Lib/test/test_ssl.py Wed Aug 21 13:26:34 2013 +0200 @@ -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 -r 4e79c3ae8a12 -r 49e23a3adf26 Misc/NEWS --- a/Misc/NEWS Wed Aug 21 01:34:18 2013 -0700 +++ b/Misc/NEWS Wed Aug 21 13:26:34 2013 +0200 @@ -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 -r 4e79c3ae8a12 -r 49e23a3adf26 Modules/_ssl.c --- a/Modules/_ssl.c Wed Aug 21 01:34:18 2013 -0700 +++ b/Modules/_ssl.c Wed Aug 21 13:26:34 2013 +0200 @@ -18,6 +18,11 @@ #ifdef WITH_THREAD #include "pythread.h" + +#ifdef HAVE_PTHREAD_ATFORK +# include +#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 -r 4e79c3ae8a12 -r 49e23a3adf26 configure --- a/configure Wed Aug 21 01:34:18 2013 -0700 +++ b/configure Wed Aug 21 13:26:34 2013 +0200 @@ -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 -r 4e79c3ae8a12 -r 49e23a3adf26 configure.ac --- a/configure.ac Wed Aug 21 01:34:18 2013 -0700 +++ b/configure.ac Wed Aug 21 13:26:34 2013 +0200 @@ -2512,6 +2512,7 @@ [Define if pthread_sigmask() does not work on your system.]) ;; esac]) + AC_CHECK_FUNCS(pthread_atfork) fi diff -r 4e79c3ae8a12 -r 49e23a3adf26 pyconfig.h.in --- a/pyconfig.h.in Wed Aug 21 01:34:18 2013 -0700 +++ b/pyconfig.h.in Wed Aug 21 13:26:34 2013 +0200 @@ -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