[PATCH] Linux: Fixes for getrandom fork handling

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Thu Jan 16 18:41:24 GMT 2025



On 16/01/25 15:18, Florian Weimer wrote:
> Careful updates of grnd_alloc.len are required to ensure that
> after fork, grnd_alloc.states does not contain entries that
> are also encountered by __getrandom_reset_state in TCBs.
> For the same reason, it is necessary to overwrite the TCB state
> pointer with NULL before updating grnd_alloc.states in
> __getrandom_vdso_release.
> 
> Before this change, different TCBs could share the same getrandom
> state after multi-threaded fork.  This would be a critical security
> bug (predictable randomness) if not caught during development.
> 
> The additional check in stdlib/tst-arc4random-thread makes it more
> likely that the test fails due to the bugs mentioned above.
> 
> Both __getrandom_reset_state and __getrandom_vdso_release could
> put reserved NULL pointers into the states array.  This is also
> fixed with this commit.  After these changes, no null pointers were
> observed in the states array during testing.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> 
> ---
>  stdlib/tst-arc4random-thread.c      | 16 +++++++++++++++-
>  sysdeps/unix/sysv/linux/getrandom.c | 26 +++++++++++++++++++++++---
>  2 files changed, 38 insertions(+), 4 deletions(-)
> 
> diff --git a/stdlib/tst-arc4random-thread.c b/stdlib/tst-arc4random-thread.c
> index b7889b653f..cb7c3514af 100644
> --- a/stdlib/tst-arc4random-thread.c
> +++ b/stdlib/tst-arc4random-thread.c
> @@ -49,7 +49,7 @@ static const int sizes[] = { 12, 15, 16, 17, 24, 31, max_size };
>  struct blob
>  {
>    unsigned int size;
> -  int thread_id;
> +  int thread_id;		/* -1 means after fork.  */
>    unsigned int index;
>    unsigned char bytes[max_size];
>  };
> @@ -323,6 +323,20 @@ do_test_func (const char *fname, void (*func)(unsigned char *, size_t))
>          }
>      }
>  
> +  for (struct blob *p = dynarray_blob_begin (&global_result);
> +       p < end; ++p)
> +    {
> +      unsigned int sum = 0;
> +      for (unsigned int i = 0; i < p->size; ++i)
> +	sum += p->bytes[i];
> +      if (sum == 0)
> +	{
> +          support_record_failure ();
> +	  printf ("error: all-zero result of length %u on thread %d\n",
> +		  p->size, p->thread_id);
> +	}
> +    }
> +
>    dynarray_blob_free (&global_result);
>  
>    return 0;

Ok.

> diff --git a/sysdeps/unix/sysv/linux/getrandom.c b/sysdeps/unix/sysv/linux/getrandom.c
> index eab2b6806f..6b7be5ee97 100644
> --- a/sysdeps/unix/sysv/linux/getrandom.c
> +++ b/sysdeps/unix/sysv/linux/getrandom.c
> @@ -168,6 +168,11 @@ vgetrandom_get_state (void)
>    if (grnd_alloc.len > 0 || vgetrandom_get_state_alloc ())
>      state = grnd_alloc.states[--grnd_alloc.len];
>  
> +  /* Barrier needed by fork: The state must be gone from the array
> +     through len update before it becomes visible in the TCB.  (There
> +     is also a release barrier implied by the unlock, but issue a
> +     stronger barrier to help fork.)  */
> +  atomic_thread_fence_seq_cst ();
>    __libc_lock_unlock (grnd_alloc.lock);
>    internal_signal_restore_set (&set);
>  
> @@ -278,7 +283,10 @@ void
>  __getrandom_reset_state (struct pthread *curp)
>  {
>  #ifdef HAVE_GETRANDOM_VSYSCALL
> -  if (grnd_alloc.states == NULL || curp->getrandom_buf == NULL)
> +  /* The pointer can be reserved if the fork happened during a
> +     getrandom call.  */
> +  void *buf = release_ptr (curp->getrandom_buf);
> +  if (grnd_alloc.states == NULL || buf == NULL)
>      return;

Hum I have not considered this case, but it makes sense.

>    assert (grnd_alloc.len < grnd_alloc.cap);
>    grnd_alloc.states[grnd_alloc.len++] = release_ptr (curp->getrandom_buf);
> @@ -294,11 +302,23 @@ void
>  __getrandom_vdso_release (struct pthread *curp)
>  {
>  #ifdef HAVE_GETRANDOM_VSYSCALL
> -  if (curp->getrandom_buf == NULL)
> +  /* The pointer can be reserved if the thread was canceled in a
> +     signal handler.  */
> +  void *buf = release_ptr (curp->getrandom_buf);
> +  if (buf == NULL)
>      return;

Another catch, thanks.

>  
>    __libc_lock_lock (grnd_alloc.lock);
> -  grnd_alloc.states[grnd_alloc.len++] = curp->getrandom_buf;
> +
> +  size_t len = grnd_alloc.len;
> +  grnd_alloc.states[len] = curp->getrandom_buf;
> +  curp->getrandom_buf = NULL;
> +  /* Barrier needed by fork: The state must vanish from the TCB before
> +     it becomes visible in the states array.  Also avoid exposing the
> +     previous entry value at the same index in the states array (which
> +     may be in use by another thread).  */
> +  atomic_thread_fence_seq_cst ();
> +  grnd_alloc.len = len + 1;
>    __libc_lock_unlock (grnd_alloc.lock);
>  #endif
>  }
> 
> base-commit: 252fc3628bc2dd66b38dff7b5c22432bb34a8829
> 



More information about the Libc-alpha mailing list