Blame nss/tst-cancel-getpwuid_r.c

Packit 6c4009
/* Test cancellation of getpwuid_r.
Packit 6c4009
   Copyright (C) 2016-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
/* Test if cancellation of getpwuid_r incorrectly leaves internal
Packit 6c4009
   function state locked resulting in hang of subsequent calls to
Packit 6c4009
   getpwuid_r.  The main thread creates a second thread which will do
Packit 6c4009
   the calls to getpwuid_r.  A semaphore is used by the second thread to
Packit 6c4009
   signal to the main thread that it is as close as it can be to the
Packit 6c4009
   call site of getpwuid_r.  The goal of the semaphore is to avoid any
Packit 6c4009
   cancellable function calls between the sem_post and the call to
Packit 6c4009
   getpwuid_r.  The main thread then attempts to cancel the second
Packit 6c4009
   thread.  Without the fixes the cancellation happens at any number of
Packit 6c4009
   calls to cancellable functions in getpuid_r, but with the fix the
Packit 6c4009
   cancellation either does not happen or happens only at expected
Packit 6c4009
   points where the internal state is consistent.  We use an explicit
Packit 6c4009
   pthread_testcancel call to terminate the loop in a timely fashion
Packit 6c4009
   if the implementation does not have a cancellation point.  */
Packit 6c4009
Packit 6c4009
#include <stdio.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <pthread.h>
Packit 6c4009
#include <pwd.h>
Packit 6c4009
#include <nss.h>
Packit 6c4009
#include <sys/types.h>
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <semaphore.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <support/support.h>
Packit 6c4009
Packit 6c4009
sem_t started;
Packit 6c4009
char *wbuf;
Packit 6c4009
long wbufsz;
Packit 6c4009
Packit 6c4009
void
Packit 6c4009
worker_free (void *arg)
Packit 6c4009
{
Packit 6c4009
  free (arg);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static void *
Packit 6c4009
worker (void *arg)
Packit 6c4009
{
Packit 6c4009
  int ret;
Packit 6c4009
  unsigned int iter = 0;
Packit 6c4009
  struct passwd pwbuf, *pw;
Packit 6c4009
  uid_t uid;
Packit 6c4009
Packit 6c4009
  uid = geteuid ();
Packit 6c4009
Packit 6c4009
  /* Use a reasonable sized buffer.  Note that _SC_GETPW_R_SIZE_MAX is
Packit 6c4009
     just a hint and not any kind of maximum value.  */
Packit 6c4009
  wbufsz = sysconf (_SC_GETPW_R_SIZE_MAX);
Packit 6c4009
  if (wbufsz == -1)
Packit 6c4009
    wbufsz = 1024;
Packit 6c4009
  wbuf = xmalloc (wbufsz);
Packit 6c4009
Packit 6c4009
  pthread_cleanup_push (worker_free, wbuf);
Packit 6c4009
  sem_post (&started);
Packit 6c4009
  while (1)
Packit 6c4009
    {
Packit 6c4009
      iter++;
Packit 6c4009
Packit 6c4009
      ret = getpwuid_r (uid, &pwbuf, wbuf, wbufsz, &pw;;
Packit 6c4009
Packit 6c4009
      /* The call to getpwuid_r may not cancel so we need to test
Packit 6c4009
	 for cancellation after some number of iterations of the
Packit 6c4009
	 function.  Choose an arbitrary 100,000 iterations of running
Packit 6c4009
	 getpwuid_r in a tight cancellation loop before testing for
Packit 6c4009
	 cancellation.  */
Packit 6c4009
      if (iter > 100000)
Packit 6c4009
	pthread_testcancel ();
Packit 6c4009
Packit 6c4009
      if (ret == ERANGE)
Packit 6c4009
	{
Packit 6c4009
	  /* Increase the buffer size.  */
Packit 6c4009
	  free (wbuf);
Packit 6c4009
	  wbufsz = wbufsz * 2;
Packit 6c4009
	  wbuf = xmalloc (wbufsz);
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
    }
Packit 6c4009
  pthread_cleanup_pop (1);
Packit 6c4009
Packit 6c4009
  return NULL;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
do_test (void)
Packit 6c4009
{
Packit 6c4009
  int ret;
Packit 6c4009
  char *buf;
Packit 6c4009
  long bufsz;
Packit 6c4009
  void *retval;
Packit 6c4009
  struct passwd pwbuf, *pw;
Packit 6c4009
  pthread_t thread;
Packit 6c4009
Packit 6c4009
  /* Configure the test to only use files. We control the files plugin
Packit 6c4009
     as part of glibc so we assert that it should be deferred
Packit 6c4009
     cancellation safe.  */
Packit 6c4009
  __nss_configure_lookup ("passwd", "files");
Packit 6c4009
Packit 6c4009
  /* Use a reasonable sized buffer.  Note that  _SC_GETPW_R_SIZE_MAX is
Packit 6c4009
     just a hint and not any kind of maximum value.  */
Packit 6c4009
  bufsz = sysconf (_SC_GETPW_R_SIZE_MAX);
Packit 6c4009
  if (bufsz == -1)
Packit 6c4009
    bufsz = 1024;
Packit 6c4009
  buf = xmalloc (bufsz);
Packit 6c4009
Packit 6c4009
  sem_init (&started, 0, 0);
Packit 6c4009
Packit 6c4009
  pthread_create (&thread, NULL, worker, NULL);
Packit 6c4009
Packit 6c4009
  do
Packit 6c4009
  {
Packit 6c4009
    ret = sem_wait (&started);
Packit 6c4009
    if (ret == -1 && errno != EINTR)
Packit 6c4009
      {
Packit 6c4009
        printf ("FAIL: Failed to wait for second thread to start.\n");
Packit 6c4009
	exit (EXIT_FAILURE);
Packit 6c4009
      }
Packit 6c4009
  }
Packit 6c4009
  while (ret != 0);
Packit 6c4009
Packit 6c4009
  printf ("INFO: Cancelling thread\n");
Packit 6c4009
  if ((ret = pthread_cancel (thread)) != 0)
Packit 6c4009
    {
Packit 6c4009
      printf ("FAIL: Failed to cancel thread. Returned %d\n", ret);
Packit 6c4009
      exit (EXIT_FAILURE);
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  printf ("INFO: Joining...\n");
Packit 6c4009
  pthread_join (thread, &retval);
Packit 6c4009
  if (retval != PTHREAD_CANCELED)
Packit 6c4009
    {
Packit 6c4009
      printf ("FAIL: Thread was not cancelled.\n");
Packit 6c4009
      exit (EXIT_FAILURE);
Packit 6c4009
    }
Packit 6c4009
  printf ("INFO: Joined, trying getpwuid_r call\n");
Packit 6c4009
Packit 6c4009
  /* Before the fix in 312be3f9f5eab1643d7dcc7728c76d413d4f2640 for this
Packit 6c4009
     issue the cancellation point could happen in any number of internal
Packit 6c4009
     calls, and therefore locks would be left held and the following
Packit 6c4009
     call to getpwuid_r would block and the test would time out.  */
Packit 6c4009
  do
Packit 6c4009
    {
Packit 6c4009
      ret = getpwuid_r (geteuid (), &pwbuf, buf, bufsz, &pw;;
Packit 6c4009
      if (ret == ERANGE)
Packit 6c4009
	{
Packit 6c4009
	  /* Increase the buffer size.  */
Packit 6c4009
	  free (buf);
Packit 6c4009
	  bufsz = bufsz * 2;
Packit 6c4009
	  buf = xmalloc (bufsz);
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
  while (ret == ERANGE);
Packit 6c4009
Packit 6c4009
  free (buf);
Packit 6c4009
Packit 6c4009
  /* Before the fix we would never get here.  */
Packit 6c4009
  printf ("PASS: Canceled getpwuid_r successfully"
Packit 6c4009
	  " and called it again without blocking.\n");
Packit 6c4009
Packit 6c4009
  return 0;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#define TIMEOUT 900
Packit 6c4009
#include <support/test-driver.c>