You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If you discover a potential security issue in s2n we ask that you notify
AWS Security via our vulnerability reporting page. Please do not create a public github issue.
Problem:
Practical impact from identified issues
s2n has always-on prediction resistance for the CTR-DRBG. This means that the thread-local drbg states are randomised every time one is used to generate randomness. The randomisation occurs before generating randomness. Hence, there is no direct practical impact as a result of the issues described in this document.
Why care then?
Fork detection is a defense-in-depth mechanism. And, of course, if prediction resistance is ever relaxed, a false assumption could be made that fork detection works in a complete way.
Problem
s2n employs two different mechanisms to detect forks: MADV_WIPEONFORK and pthread_atfork(). The former is more comprehensive than the latter. For example, pthread_atfork() doesn’t execute the registered child callback if a fork is performed directly through e.g. clone().
We first describe how fork detection is implemented in s2n and then describe two different issues:
Issue 1: MADV_WIPEOFORK is never (in fact, can never be) enabled as currently implemented.
Issue 2: the fork detection implementation does not randomise the drbg state in all fork cases.
Fork detection in s2n
Fork detection using MADV_WIPEONFORK is a stateful system. s2n inspects a special memory page that is managed by the kernel. The kernel zeroises the memory page if it detects a fork. By inspecting the memory page, s2n can detect a fork and execute a drbg state randomisation in the forked process. Below is a description of how this is implemented in s2n.
Global memory is used by s2n as the special memory page zero_when_forked_page, which either has the value 0 (signals a fork has occurred) and 1 (signals no action needed). A thread-local pointer zero_if_forked_ptr holds a reference to zero_when_forked_page.
madvise():
Since glibc 2.19:
_DEFAULT_SOURCE
Up to and including glibc 2.19:
_BSD_SOURCE
This is not defined in s2n_random.c.
Solution
The solution is to define a feature test macro. In addition, define the pre-processor directive MADV_WIPEFORK if kernel headers doesn’t define it as expected; kernel should support the operation anyway. Something like this worked in my small test - but should be verified more thoroughly:
#define _GNU_SOURCE 1
#include <sys/mman.h>
#if defined(MADV_WIPEONFORK)
static_assert(MADV_WIPEONFORK == 18, "MADV_WIPEONFORK is not 18");
#else
#define MADV_WIPEONFORK 18
#endif
The above implementation is not strictly C99 because of the use of static_assert().
MADV_WIPEOFORK tagged memory page is not necessarily private and anonymous
where addr points to the memory page that is going to be tagged with MADV_WIPEONFORK.
Issue 2: current fork detection mechanism implementation does not cover all fork use-cases
First we make two observations about the fork detection implementation:
Each thread uses the same global memory zero_when_forked_page referenced through thread-local pointers zero_if_forked_ptr to signal whether a fork has occurred.
If a fork is detected, the thread that first reaches the end of s2n_defend_if_forked (unconditionally) resets zero_when_forked_page back to a “not detected” state. There is no synchronisation.
Now, let's say we have a process P1 that has an initialised s2n drbg:
Fork P1. This creates a new child process P2 of P1. We now have one thread in P2: call this thread T1.
Because of fork detection, this sets *zero_when_forked_page to 0 in T1. The s2n drbg states in P1 and T1 are duplicated at this point in time.
Spawn a new thread T2 in P2. Note that we have *zero_if_forked_ptr = 0 in T2.
In T2 make a call to s2n_get_{public,private}_random_data which will initialise the thread-local drbg states in T2.
After initilisation in T2, the memory page *zero_when_forked_page is set to 1 and the thread-local pointer zero_if_forked_ptr points to zero_when_forked_page.
Any subsequent calls to s2n_get_{public,private}_random_data() by T1 (in P2) will not randomise the thread-local drbg states because the global memory is now *zero_when_forked_page != 0.
P1 and T2 have duplicated drbg states.
Tiny PoC: looking at output from calling s2n_get_public_random_data() in T2, T1, and P1.
This was tested after fixing other issues with the MADV_WIPEONFORK implementation
Solution:
Implement fork generation number that abstracts the fork detection and provides a synchronisation mechanisms that lets each thread handle forking events.
Does this change what S2N sends over the wire? No
Does this change any public APIs? No
Which versions of TLS will this impact? Not TLS-specific
The text was updated successfully, but these errors were encountered:
Security issue notifications
If you discover a potential security issue in s2n we ask that you notify
AWS Security via our vulnerability reporting page. Please do not create a public github issue.
Problem:
Practical impact from identified issues
s2n has always-on prediction resistance for the CTR-DRBG. This means that the thread-local drbg states are randomised every time one is used to generate randomness. The randomisation occurs before generating randomness. Hence, there is no direct practical impact as a result of the issues described in this document.
Why care then?
Fork detection is a defense-in-depth mechanism. And, of course, if prediction resistance is ever relaxed, a false assumption could be made that fork detection works in a complete way.
Problem
s2n employs two different mechanisms to detect forks:
MADV_WIPEONFORK
andpthread_atfork()
. The former is more comprehensive than the latter. For example,pthread_atfork()
doesn’t execute the registered child callback if a fork is performed directly through e.g.clone()
.We first describe how fork detection is implemented in s2n and then describe two different issues:
MADV_WIPEOFORK
is never (in fact, can never be) enabled as currently implemented.Fork detection in s2n
Fork detection using
MADV_WIPEONFORK
is a stateful system. s2n inspects a special memory page that is managed by the kernel. The kernel zeroises the memory page if it detects a fork. By inspecting the memory page, s2n can detect a fork and execute a drbg state randomisation in the forked process. Below is a description of how this is implemented in s2n.Global memory is used by s2n as the special memory page
zero_when_forked_page
, which either has the value 0 (signals a fork has occurred) and 1 (signals no action needed). A thread-local pointerzero_if_forked_ptr
holds a reference tozero_when_forked_page
.The second fork detection mechanism uses
pthread_atfork()
that registers a callbacks2n_on_fork
. The callback function zeroiseszero_when_forked_page
directly:https://github.com/aws/s2n-tls/blob/main/utils/s2n_random.c#L131
zero_if_forked
is a convenience macro that dereferenceszero_if_forked_ptr
.Issue 1:
MADV_WIPEONFORK
is never (can never be) enabledMADV_WIPEONFORK
is instantiated as follows:There are 3 distinct sub-issues (tested on AL2 and Ubuntu 20.04).
madvise
andMADV_WIPEONFORK
's header file not includedThe header file defining
MADV_WIPEONFORK
and declaring the functionmadvise()
is not included in thes2n_random.c
compilation unit. Henceis never executed.
Solution
Include the header file
ys/mman.h
(https://man7.org/linux/man-pages/man2/madvise.2.html). Noting that not all systems defineMADV_WIPEONFORK
in this header file.Feature test macro requirements not met
Including the header file
sys/mman.h
is actually not enough, because there are also feature test macro requirements formadvise()
.https://man7.org/linux/man-pages/man2/madvise.2.html
This is not defined in
s2n_random.c
.Solution
The solution is to define a feature test macro. In addition, define the pre-processor directive
MADV_WIPEFORK
if kernel headers doesn’t define it as expected; kernel should support the operation anyway. Something like this worked in my small test - but should be verified more thoroughly:The above implementation is not strictly C99 because of the use of
static_assert()
.MADV_WIPEOFORK tagged memory page is not necessarily private and anonymous
MADV_WIPEFORK
requires:https://man7.org/linux/man-pages/man2/madvise.2.html
The
MADV_WIPEONFORK
operation can be applied only to private anonymous pages (seemmap(2)
).However, this is not explicitly satisfied in the implementation. This doesn’t seem to be an issue on the platforms I tested on, though. It does seem to be verified by the Linux Kernel though https://elixir.bootlin.com/linux/v5.11-rc6/source/mm/madvise.c#L98.
Solution
mmap
can be used to produce aligned memory with privateMAP_PRIVATE
and anonymousMAP_ANONYMOUS
pages.An implementation could be:
where
addr
points to the memory page that is going to be tagged withMADV_WIPEONFORK
.Issue 2: current fork detection mechanism implementation does not cover all fork use-cases
First we make two observations about the fork detection implementation:
zero_when_forked_page
referenced through thread-local pointerszero_if_forked_ptr
to signal whether a fork has occurred.s2n_defend_if_forked
(unconditionally) resetszero_when_forked_page
back to a “not detected” state. There is no synchronisation.Now, let's say we have a process
P1
that has an initialised s2n drbg:P1
. This creates a new child processP2
ofP1
. We now have one thread inP2
: call this threadT1
.*zero_when_forked_page
to0
inT1
. The s2n drbg states inP1
andT1
are duplicated at this point in time.T2
inP2
. Note that we have*zero_if_forked_ptr = 0
inT2
.T2
make a call tos2n_get_{public,private}_random_data
which will initialise the thread-local drbg states inT2
.T2
, the memory page*zero_when_forked_page
is set to1
and the thread-local pointerzero_if_forked_ptr
points tozero_when_forked_page
.Any subsequent calls to
s2n_get_{public,private}_random_data()
byT1
(inP2
) will not randomise the thread-local drbg states because the global memory is now*zero_when_forked_page != 0
.P1
andT2
have duplicated drbg states.Tiny PoC: looking at output from calling
s2n_get_public_random_data()
inT2
,T1
, andP1
.This was tested after fixing other issues with the
MADV_WIPEONFORK
implementationSolution:
Implement fork generation number that abstracts the fork detection and provides a synchronisation mechanisms that lets each thread handle forking events.
The text was updated successfully, but these errors were encountered: