Hi gnutls team, https://bugs.debian.org/803197 was reported against libldap in Debian.
When libldap is linked with gnutls, and SOGo is configured to use LDAP with TLS security as its authentication backend, when the first LDAP request is made and libldap initializes TLS, gnutls closes a file descriptor used by the application and opens /dev/urandom over it.
I am attaching a test program that demonstrates this. On a Debian system, install libldap2-dev, and link it with -lldap. I verified the problem with current Debian unstable (gnutls 3.4.8-2).
Quoting myself in the bug linked above, what happens is:
1. libraries are loaded 2. gnutls' constructor does the global init, open("/dev/urandom") -> 3 3. WOWatchDogApplicationMain runs, closes everything 4. libldap goes to set up gnutls 5. gnutls prepares to re-init itself, closes the remembered urandom fd 6. things go downhill from here.
Now libldap's TLS setup calls gnutls_global_set_mutex followed by gnutls_global_init. This happens lazily, when TLS is first used on an LDAP connection. In the case of SOGo this is when a user actually attempts to log in.
I am aware that this runs contrary to the gnutls documentation in at least two ways: first, that applications that close all fds should immediately call gnutls_global_init, and second, that gnutls_global_set_mutex should not be called by libraries. However, please consider that SOGo and Sope do not actually use gnutls and are not even aware that it is being loaded; it is only an implementation detail that libldap links it, so without seriously re-designing how libldap works, or adding explicit knowledge of gnutls to Sope, I don't see a way to move the gnutls_global_init call earlier.
As far as removing the gnutls_global_set_mutex call from libldap, if it's not possible to address this from the gnutls side, I'm willing to bring this up with openldap upstream. However, it looks like it currently supports threading on more platforms than gnutls on its own does, so removing that might be considered a regression.
I see that gnutls attempts to sanity-check the urandom fd during initialization. I wonder if you would consider adding a similar check during de-init, before closing the remembered fd, and avoid touching it if there's a possibility the application closed it _and_ reused it?
The Sope authors have committed a workaround for this in the mean time: https://github.com/inverse-inc/sope/pull/32 however they would prefer to revert that eventually. thanks, Ryan
#include <assert.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <ldap.h> int main() { int i, fd, ld_opt, rc; ssize_t sz; LDAP *ld; char procfdpath[sizeof("/proc/self/fd/65536")]; char linkpath[sizeof("/dev/urandom")]; /* From https://github.com/inverse-inc/sope/blob/e870145b7ed381c1bdef5945075ed48948b86d5b/sope-appserver/NGObjWeb/WOWatchDogApplicationMain.m#L1014 */ /* Close all open file descriptors */ for (i = getdtablesize(); i >= 3; --i) close(i); freopen("/dev/null", "a", stdin); fd = open("/dev/zero", O_RDONLY); snprintf(procfdpath, sizeof(procfdpath), "/proc/self/fd/%i", fd); sz = readlink(procfdpath, linkpath, sizeof(linkpath)-1); assert(sz > 0); linkpath[sz] = '\0'; printf("%s -> %s\n", procfdpath, linkpath); printf("starting up LDAP... "); rc = ldap_initialize(&ld, "ldap://ldap.forumsys.com"); assert(rc == LDAP_SUCCESS); ld_opt = 3; ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ld_opt); assert(rc == LDAP_SUCCESS); printf("and TLS... "); ld_opt = 0; ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, &ld_opt); assert(rc == LDAP_SUCCESS); ldap_start_tls_s(ld, NULL, NULL); assert(rc == LDAP_SUCCESS); printf("ok.\n"); snprintf(procfdpath, sizeof(procfdpath), "/proc/self/fd/%i", fd); sz = readlink(procfdpath, linkpath, sizeof(linkpath)-1); assert(sz > 0); linkpath[sz] = '\0'; printf("%s -> %s\n", procfdpath, linkpath); return 0; }