Problem: fd leak in tweetnacl with one ctx per thread

Solution: add a crypto [de-]initialiser, refcounted and serialised
through critical sections.
This is necessary as utility APIs such as zmq_curve_keypair also
call into the sodium/tweetnacl libraries and need the initialisation
outside of the zmq context.
Also the libsodium documentation explicitly says that sodium_init
must not be called concurrently from multiple threads, which could
have happened until now. Also the randombytes_close function does
not appear to be thread safe either.
This change guarantees that the library is initialised only once at
any given time across the whole program.
Fixes #2632
This commit is contained in:
Luca Boccassi
2017-07-27 14:43:14 +01:00
parent a7bf010ee2
commit e015a0f8b9
7 changed files with 113 additions and 37 deletions

View File

@@ -1,5 +1,5 @@
/*
Copyright (c) 2007-2016 Contributors as noted in the AUTHORS file
Copyright (c) 2007-2017 Contributors as noted in the AUTHORS file
This file is part of libzmq, the ZeroMQ core engine in C++.
@@ -37,6 +37,14 @@
#include "random.hpp"
#include "stdint.hpp"
#include "clock.hpp"
#include "mutex.hpp"
#include "macros.hpp"
#if defined (ZMQ_USE_TWEETNACL)
#include "tweetnacl.h"
#elif defined (ZMQ_USE_LIBSODIUM)
#include "sodium.h"
#endif
void zmq::seed_random ()
{
@@ -57,3 +65,54 @@ uint32_t zmq::generate_random ()
return high | low;
}
// When different threads have their own context the file descriptor
// variable is shared and is subject to race conditions in tweetnacl,
// that lead to file descriptors leaks. In long-running programs with
// ephemeral threads this is a problem as it accumulates.
// thread-local storage cannot be used to initialise the file descriptor
// as it is perfectly legal to share a context among many threads, each
// of which might call curve APIs.
// Also libsodium documentation specifically states that sodium_init
// must not be called concurrently from multiple threads, for the
// same reason. Inspecting the code also reveals that the close API is
// not thread safe.
// The context class cannot be used with static variables as the curve
// utility APIs like zmq_curve_keypair also call into the crypto
// library.
// The safest solution for all use cases therefore is to have a global,
// static lock to serialize calls into an initialiser and a finaliser,
// using refcounts to make sure that a thread does not close the library
// while another is still using it.
static unsigned int random_refcount = 0;
static zmq::mutex_t random_sync;
void zmq::random_open (void)
{
#if defined (ZMQ_USE_LIBSODIUM) || \
(defined (ZMQ_USE_TWEETNACL) && !defined (ZMQ_HAVE_WINDOWS))
scoped_lock_t locker (random_sync);
if (random_refcount == 0) {
int rc = sodium_init ();
zmq_assert (rc != -1);
}
++random_refcount;
#else
LIBZMQ_UNUSED (random_refcount);
#endif
}
void zmq::random_close (void)
{
#if defined (ZMQ_USE_LIBSODIUM) || \
(defined (ZMQ_USE_TWEETNACL) && !defined (ZMQ_HAVE_WINDOWS))
scoped_lock_t locker (random_sync);
--random_refcount;
if (random_refcount == 0) {
randombytes_close ();
}
#endif
}