Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f77b4b2

Browse files
committed
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.
1 parent b1973c2 commit f77b4b2

6 files changed

Lines changed: 123 additions & 0 deletions

File tree

Lib/test/test_ssl.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,38 @@ def test_random(self):
130130
self.assertRaises(TypeError, ssl.RAND_egd, 'foo', 1)
131131
ssl.RAND_add("this is a random string", 75.0)
132132

133+
@unittest.skipUnless(os.name == 'posix', 'requires posix')
134+
def test_random_fork(self):
135+
status = ssl.RAND_status()
136+
if not status:
137+
self.fail("OpenSSL's PRNG has insufficient randomness")
138+
139+
rfd, wfd = os.pipe()
140+
pid = os.fork()
141+
if pid == 0:
142+
try:
143+
os.close(rfd)
144+
child_random = ssl.RAND_pseudo_bytes(16)[0]
145+
self.assertEqual(len(child_random), 16)
146+
os.write(wfd, child_random)
147+
os.close(wfd)
148+
except BaseException:
149+
os._exit(1)
150+
else:
151+
os._exit(0)
152+
else:
153+
os.close(wfd)
154+
self.addCleanup(os.close, rfd)
155+
_, status = os.waitpid(pid, 0)
156+
self.assertEqual(status, 0)
157+
158+
child_random = os.read(rfd, 16)
159+
self.assertEqual(len(child_random), 16)
160+
parent_random = ssl.RAND_pseudo_bytes(16)[0]
161+
self.assertEqual(len(parent_random), 16)
162+
163+
self.assertNotEqual(child_random, parent_random)
164+
133165
def test_parse_cert(self):
134166
# note that this uses an 'unofficial' function in _ssl.c,
135167
# provided solely for this test, to exercise the certificate

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ Core and Builtins
6666
Library
6767
-------
6868

69+
- Issue #18747: Re-seed OpenSSL's pseudo-random number generator after fork.
70+
A pthread_atfork() child handler is used to seeded the PRNG with pid, time
71+
and some stack data.
72+
6973
- Issue #8865: Concurrent invocation of select.poll.poll() now raises a
7074
RuntimeError exception. Patch by Christian Schubert.
7175

Modules/_ssl.c

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818

1919
#ifdef WITH_THREAD
2020
#include "pythread.h"
21+
22+
#ifdef HAVE_PTHREAD_ATFORK
23+
# include <pthread.h>
24+
#endif
25+
2126
#define PySSL_BEGIN_ALLOW_THREADS_S(save) \
2227
do { if (_ssl_locks_count>0) { (save) = PyEval_SaveThread(); } } while (0)
2328
#define PySSL_END_ALLOW_THREADS_S(save) \
@@ -2578,7 +2583,69 @@ Queries the entropy gather daemon (EGD) on the socket named by 'path'.\n\
25782583
Returns number of bytes read. Raises SSLError if connection to EGD\n\
25792584
fails or if it does not provide enough data to seed PRNG.");
25802585

2586+
/* Seed OpenSSL's PRNG at fork(), http://bugs.python.org/issue18747
2587+
*
2588+
* The child handler seeds the PRNG from pseudo-random data like pid, the
2589+
* current time (nanoseconds, miliseconds or seconds) and an uninitialized
2590+
* array. The array contains stack variables that are impossible to predict
2591+
* on most systems, e.g. function return address (subject to ASLR), the
2592+
* stack protection canary and automatic variables.
2593+
* The code is inspired by Apache's ssl_rand_seed() function.
2594+
*
2595+
* Note:
2596+
* The code uses pthread_atfork() until Python has a proper atfork API. The
2597+
* handlers are not removed from the child process.
2598+
*/
2599+
2600+
#if defined(HAVE_PTHREAD_ATFORK) && defined(WITH_THREAD)
2601+
#define PYSSL_RAND_ATFORK 1
2602+
2603+
static void
2604+
PySSL_RAND_atfork_child(void)
2605+
{
2606+
struct {
2607+
char stack[128]; /* uninitialized (!) stack data, 128 is an
2608+
arbitrary number. */
2609+
pid_t pid; /* current pid */
2610+
_PyTime_timeval tp; /* current time */
2611+
} seed;
2612+
2613+
#ifdef WITH_VALGRIND
2614+
VALGRIND_MAKE_MEM_DEFINED(seed.stack, sizeof(seed.stack));
2615+
#endif
2616+
seed.pid = getpid();
2617+
_PyTime_gettimeofday(&(seed.tp));
2618+
2619+
#if 0
2620+
fprintf(stderr, "PySSL_RAND_atfork_child() seeds %i bytes in pid %i\n",
2621+
(int)sizeof(seed), seed.pid);
25812622
#endif
2623+
RAND_add((unsigned char *)&seed, sizeof(seed), 0.0);
2624+
}
2625+
2626+
static int
2627+
PySSL_RAND_atfork(void)
2628+
{
2629+
static int registered = 0;
2630+
int retval;
2631+
2632+
if (registered)
2633+
return 0;
2634+
2635+
retval = pthread_atfork(NULL, /* prepare */
2636+
NULL, /* parent */
2637+
PySSL_RAND_atfork_child); /* child */
2638+
if (retval != 0) {
2639+
PyErr_SetFromErrno(PyExc_OSError);
2640+
return -1;
2641+
}
2642+
registered = 1;
2643+
return 0;
2644+
}
2645+
#endif /* HAVE_PTHREAD_ATFORK */
2646+
2647+
#endif /* HAVE_OPENSSL_RAND */
2648+
25822649

25832650

25842651

@@ -2956,5 +3023,10 @@ PyInit__ssl(void)
29563023
if (r == NULL || PyModule_AddObject(m, "_OPENSSL_API_VERSION", r))
29573024
return NULL;
29583025

3026+
#ifdef PYSSL_RAND_ATFORK
3027+
if (PySSL_RAND_atfork() == -1)
3028+
return NULL;
3029+
#endif
3030+
29593031
return m;
29603032
}

configure

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9791,6 +9791,17 @@ $as_echo "#define HAVE_BROKEN_PTHREAD_SIGMASK 1" >>confdefs.h
97919791

97929792
;;
97939793
esac
9794+
fi
9795+
done
9796+
9797+
for ac_func in pthread_atfork
9798+
do :
9799+
ac_fn_c_check_func "$LINENO" "pthread_atfork" "ac_cv_func_pthread_atfork"
9800+
if test "x$ac_cv_func_pthread_atfork" = xyes; then :
9801+
cat >>confdefs.h <<_ACEOF
9802+
#define HAVE_PTHREAD_ATFORK 1
9803+
_ACEOF
9804+
97949805
fi
97959806
done
97969807

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2500,6 +2500,7 @@ if test "$posix_threads" = "yes"; then
25002500
[Define if pthread_sigmask() does not work on your system.])
25012501
;;
25022502
esac])
2503+
AC_CHECK_FUNCS(pthread_atfork)
25032504
fi
25042505

25052506

pyconfig.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@
633633
/* Define if your compiler supports function prototype */
634634
#undef HAVE_PROTOTYPES
635635

636+
/* Define to 1 if you have the `pthread_atfork' function. */
637+
#undef HAVE_PTHREAD_ATFORK
638+
636639
/* Defined for Solaris 2.6 bug in pthread header. */
637640
#undef HAVE_PTHREAD_DESTRUCTOR
638641

0 commit comments

Comments
 (0)