file handle exhaustion with openvpn and pam_ldap

Andreas Metzler ametzler at downhill.at.eu.org
Sun Oct 25 09:35:05 CET 2009


Hello,

this is <http://bugs.debian.org/543941>:

When using openvpn and pam_ldap against an LDAP server with TLS
support on every authentication, a file handle to /dev/urandom is
created but never released. (libldap-2.4-2 is using gnutls, openvpn
isn't.)

------------------------
Lars Ellenberg has debuged the issue, I am forwarding his comments:
attached is a simple program to reproduce,
and workaround the issue.

libgcrypt standard behaviour is, at least on linux,
to open /dev/urandom once,
save that file descriptor in some static variable,
and re-use it wherever appropriate.
and never ever close that file descriptor,
but on exit or fork.

problem is:
pam_start() via various indirections may dlopen()s libgcrypt,
pam_stop() will dlclose() it again.

which means the libgcrypt will be unloaded,
and its static urandom fd with it.

but there is no destructor to close the FD.

on the next iteration,
a new instance of libgcrypt will be loaded,
with freshly initialized data segment,
resulting in an additional open of urandom.

that is the leak.

Workaround:
grab an additional reference on libgcrypt.
these workarounds seem to have precedence, see the
void nasty_pthread_hack (void) __attribute__ ((constructor));
void nasty_ssl_hack (void) __attribute__ ((constructor));
in libpam_ldap: pam_ldap.c

This should only be done as a short term workaround, though.

Real fix would be for libgcrypt to properly clean up on unload,
i.e. to provide proper destructor functions.

try.c is attached.
example session:

~/src/try$ gcc -o try try.c -lpam -ldl -pthread
~/src/try$ strace -e open ./try 2>&1 | grep urandom
open("/dev/urandom", O_RDONLY)          = 4
open("/dev/urandom", O_RDONLY)          = 6
open("/dev/urandom", O_RDONLY)          = 7

~/src/try$ gcc -DFIXIT -o try try.c -lpam -ldl -pthread
~/src/try$ strace -e open ./try 2>&1 | grep urandom
open("/dev/urandom", O_RDONLY)          = 4


----------------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <security/pam_appl.h>

#define USERNAME "dummy1"
#define PASSWORD "dummy2"

/*
 * PAM conversation function
 */

/* copied and shortened from openvpn source */
static int
my_conv (int n, const struct pam_message **msg_array,
	 struct pam_response **response_array, void *appdata_ptr)
{
  struct pam_response *aresp;
  int i;
  int ret = PAM_SUCCESS;

  *response_array = NULL;

  if (n <= 0 || n > PAM_MAX_NUM_MSG)
    return (PAM_CONV_ERR);
  if ((aresp = calloc (n, sizeof *aresp)) == NULL)
    return (PAM_BUF_ERR);

  /* loop through each PAM-module query */
  for (i = 0; i < n; ++i)
    {
      const struct pam_message *msg = msg_array[i];
      aresp[i].resp_retcode = 0;
      aresp[i].resp = NULL;

      /* use PAM_PROMPT_ECHO_x hints */
      switch (msg->msg_style)
	{
	case PAM_PROMPT_ECHO_OFF:
	  aresp[i].resp = strdup (PASSWORD);
	  if (aresp[i].resp == NULL)
	    ret = PAM_CONV_ERR;
	  break;

	case PAM_PROMPT_ECHO_ON:
	  aresp[i].resp = strdup (USERNAME);
	  if (aresp[i].resp == NULL)
	    ret = PAM_CONV_ERR;
	  break;

	case PAM_ERROR_MSG:
	case PAM_TEXT_INFO:
	  break;

	default:
	  ret = PAM_CONV_ERR;
	  break;
	}
    }

  if (ret == PAM_SUCCESS)
    *response_array = aresp;
  return ret;
}

int main(int argc, char **argv)
{
#ifdef FIXIT
	void *dlh = dlopen("libgcrypt.so", RTLD_LAZY);
#endif
	struct pam_conv conv = { .conv = my_conv, };
	pam_handle_t *pamh;
	int i;
	for (i = 0; i < 3; i++) {
		pam_start("openvpn", USERNAME, &conv, &pamh);
		pam_authenticate(pamh, 0);
		pam_end(pamh, PAM_SUCCESS);
	}
	return 0;
}
----------------------------------------------

cu andreas
-- 
`What a good friend you are to him, Dr. Maturin. His other friends are
so grateful to you.'
`I sew his ears on from time to time, sure'




More information about the Gcrypt-devel mailing list