bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [bug-gnulib] getgrouplist


From: Bruno Haible
Subject: Re: [bug-gnulib] getgrouplist
Date: Fri, 19 May 2006 14:59:35 +0200
User-agent: KMail/1.5

Jim Meyering wrote:
> It'd be great if someone would write a gnulib-style getgrouplist
> replacement function that provides a poor-man's implementation (using
> something like coreutils' existing code) for systems that lack a useful
> function by that name.

Here is an implementation of the getgrouplist module you ask for.

It is based on the getugroups.c file.

About the glibc-2.3.2 bug workaround:
  - I don't know how to write an autoconf test for the bug (take e.g. a
    system where all users are only in their primary group).
  - The bug is unlikely to be present in other systems, therefore activating
    the workaround only on glibc-2.3.x seems acceptable.
  - Even on glibc-2.3.2 systems which have the bug, the getgrouplist
    function can be used if a protection against the destination overflow
    is installed. And if it's really faster than a getgrent() loop, it
    _should_ be used. The overhead added by the workaround is a small
    number of library calls (2 x sigaction, 1 x setjmp, 1 x memcpy).

Notes:
  - The array element type is gid_t. I don't see why the GETGROUPS_T
    workaround should be applied to a function that is up to now only
    implemented by glibc.
  - The group argument can be any gid_t; if you pass (gid_t)(-1), you
    will have (gid_t)(-1) in the resulting array. This is how glibc's
    implementation works; therefore this implementation works the same.
    Whereas the getugroups() function treated (gid_t)(-1) specially.
  - The getgrent() loop in getugroups.c is O(n^2). I reduced this to
    O(n log n). You could also make it O(n) by use of a hash table.

Proofreading welcome, as always! This is new, untested code.


2006-05-18  Bruno Haible  <address@hidden>

        * m4/getgrouplist.m4: New file.
        * lib/getgrouplist.h: New file.
        * lib/getgrouplist.c: New file, based on lib/getugroups.c.
        * modules/getgrouplist: New file.

============================= modules/getgrouplist =============================
Description:
getgrouplist() function: return all the group IDs of a given user.

Files:
lib/getgrouplist.h
lib/getgrouplist.c
m4/getgrouplist.m4

Depends-on:

configure.ac:
gl_FUNC_GETGROUPLIST

Makefile.am:
EXTRA_DIST += getgrouplist.h

Include:
#include "getgrouplist.h"

License:
GPL

Maintainer:
Jim Meyering, Bruno Haible

============================= lib/getgrouplist.h =============================
/* Lookup of groups of a user.
   Copyright (C) 2006 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */

#ifndef _GETGROUPLIST_H
#define _GETGROUPLIST_H

#include <sys/types.h>

#if HAVE_GETGROUPLIST && !(defined __GNU_LIBRARY__ && (__GLIBC__ == 2 && 
__GLIBC_MINOR__ == 3))

/* Use the system's getgrouplist() function.  */
# include <grp.h>

#else

/* Return the groups to which the user with name USER belongs, including the
   given GROUP.
   GROUPS is a pointer to *NGROUPS elements.  Up to *NGROUPS elements are
   stored at GROUPS.  Upon return *NGROUPS is then set to the total number of
   elements that would have been stored if enough space had been given.
   Return the number of elements, if they all fit into the given space, or -1
   if only some of the elements could be stored.  */
extern int getgrouplist (const char *user, gid_t group,
                         gid_t *groups, int *ngroups);

#endif

#endif /* _GETGROUPLIST_H */
============================= lib/getgrouplist.c =============================
/* Lookup of groups of a user.
   Copyright (C) 1990-1991, 1998-2000, 2003-2006 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */

/* Written by David MacKenzie and Bruno Haible.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

/* Specification.  */
#include "getgrouplist.h"

#undef getgrouplist

#include <sys/types.h>
#include <grp.h>
#include <stdlib.h>
#include <string.h>

#if HAVE_GETGROUPLIST && defined __GNU_LIBRARY__

# include <setjmp.h>
# include <signal.h>
# include <unistd.h>
# include <sys/mman.h>
# include <sys/param.h>

# define getgrouplist emulated_getgrouplist
# define STATIC static

#endif


/* Emulate getgrouplist.  */

/* Some old header files might not declare setgrent, getgrent, and endgrent.
   If you don't have them at all, we can't implement this function.
   You lose!  */
extern struct group *getgrent ();

static int
group_compare (const void *p1, const void *p2)
{
  gid_t group1 = *(const gid_t *)p1;
  gid_t group2 = *(const gid_t *)p2;

  if (group1 < group2)
    return -1;
  if (group1 > group2)
    return 1;
  return 0;
}

#ifdef STATIC
STATIC
#endif
int
getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups)
{
  int maxcount = *ngroups;
  int count = 0;
  struct group *grp;

  /* Put the given group first.  */
  if (count < maxcount)
    groups[count] = group;
  count++;

  /* Scan all groups.  */
  setgrent ();
  while ((grp = getgrent ()) != NULL)
    /* No need to re-consider the given group.  */
    if (grp->gr_gid != group)
      {
        char **grp_members;

        for (grp_members = grp->gr_mem; *grp_members != NULL; grp_members++)
          if (strcmp (*grp_members, user) == 0)
            /* Found a group containing the given user.  */
            {
              if (count < maxcount)
                groups[count] = grp->gr_gid;
              count++;
            }
      }
  endgrent ();

  if (count <= maxcount && count > 2)
    {
      int i, j;

      /* Sort the array of supplementary groups.  This is a preparation step,
         so that the next step, the removal of duplicates, is not O(n^2).  */
      qsort (groups + 1, count - 1, sizeof (gid_t), group_compare);

      /* Remove duplicates from the array of supplementary groups.
         Copy from i to j, keeping 0 <= j <= i.  */
      for (i = j = 1; i < count; i++)
        if (j == 1 || groups[i] != groups[j - 1])
          {
            if (j < i)
              groups[j] = groups[i];
            j++;
          }
      count = j;
    }

  *ngroups = count;
  return (count <= maxcount ? count : -1);
}


#if HAVE_GETGROUPLIST && defined __GNU_LIBRARY__

/* Use getgrouplist, but work around its bugs.
   On glibc-2.3.2, getgrouplist() computes the total list into a private
   malloc()ed and realloc()ed buffer and finally copies this buffer into
   the given one - writing past the end of the given buffer if there are
   more than *NGROUPS groups to be returned.
   We work around this problem by allocating a guard page behind the buffer
   and catching the SIGSEGV signal (or SIGBUS signal, in the case of Hurd)
   that occurs when the glibc function writes past the buffer's end.  But
   since this leaks memory (glibc's private buffer is not freed), we do
   this only once, and fall back to the slower but memory-leak-free
   emulation afterwards.  */

# undef getgrouplist

static jmp_buf safe_point;

static void
my_sig_handler (int signum)
{
  /* On glibc systems (unlike Solaris or *BSD systems), it is possible to
     longjmp from a signal handler to the main program.  */
  longjmp (safe_point, 1);
}

static size_t pagesize;
static void *small_memory_with_guard_page;

static void
init_small_memory_with_guard_page (void)
{
  size_t memsize = pagesize * sizeof (gid_t);
  void *mem;

  mem = mmap (NULL, memsize + pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
              -1, 0);
  if (mem == NULL)
    return;
  if (mprotect ((char *) mem + memsize, pagesize, PROT_NONE) < 0)
    {
      munmap (mem, memsize + pagesize);
      return;
    }
  small_memory_with_guard_page = mem;
}

static int caught_signal;

int
rpl_getgrouplist (const char *user, gid_t group, gid_t *groups, int *ngroups)
{
  /* Maximum number of groups specified by the caller.  */
  int maxcount;
  /* Temporary memory ending in a guard page.  */
  void *memory_with_guard_page;
  /* Amount of memory to cleanup at the end.  */
  size_t memory_size;
  /* Signal handlers to restore at the end.  */
  struct sigaction my_sig_action;
  struct sigaction old_sigsegv_action;
# ifndef __linux__
  struct sigaction old_sigbus_action;
# endif

  /* Don't leak memory more than once.  */
  if (caught_signal)
    goto fail;

  /* Initialize the pagesize cache.  */
  if (pagesize == 0)
    pagesize = getpagesize ();

  maxcount = *ngroups;
  if (maxcount <= pagesize)
    {
      /* Use the preallocated small memory region with a guard page.  */
      if (small_memory_with_guard_page == NULL)
        {
          init_small_memory_with_guard_page ();
          if (small_memory_with_guard_page == NULL)
            goto fail;
        }
      memory_with_guard_page = small_memory_with_guard_page;

      memory_size = 0;
    }
  else
    {
      /* Allocate a one-time memory region with a guard page.  */
      size_t memsize = maxcount * sizeof (gid_t);

      /* Round up to a multiple of pagesize.  */
      memsize = ((memsize + pagesize - 1) / pagesize) * pagesize;

      memory_with_guard_page =
        mmap (NULL, memsize + pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE,
              -1, 0);
      if (memory_with_guard_page == NULL)
        goto fail;
      if (mprotect ((char *) memory_with_guard_page + memsize, pagesize,
                    PROT_NONE) < 0)
        {
          munmap (memory_with_guard_page, memsize + pagesize);
          goto fail;
        }

      memory_size = memsize + pagesize;
    }

  if (setjmp (safe_point) == 0)
    {
      int result;

      /* Install handlers for SIGSEGV and SIGBUS.  */
      my_sig_action.sa_handler = my_sig_handler;
      sigemptyset (&my_sig_action.sa_mask);
      my_sig_action.sa_flags = SA_NOMASK;
      sigaction (SIGSEGV, &my_sig_action, &old_sigsegv_action);
# ifndef __linux__
      sigaction (SIGBUS, &my_sig_action, &old_sigbus_action);
# endif

      /* Call the original, possibly buggy, getgrouplist() function.  */
      result = getgrouplist (user, group, memory_with_guard_page, ngroups);

      /* Uninstall the signal handlers.  */
      sigaction (SIGSEGV, &old_sigsegv_action, NULL);
# ifndef __linux__
      sigaction (SIGBUS, &old_sigbus_action, NULL);
# endif

      /* The original function didn't crash, fine.  */
      memcpy (groups, memory_with_guard_page,
              MIN (maxcount, *ngroups) * sizeof (gid_t));

      /* Clean up allocated memory.  */
      if (memory_size > 0)
        munmap (memory_with_guard_page, memory_size);

      return result;
    }
  else
    {
      /* The original function crashed.  Use the emulation from now on.  */
      caught_signal = 1;

      /* Uninstall the signal handlers.  */
      sigaction (SIGSEGV, &old_sigsegv_action, NULL);
# ifndef __linux__
      sigaction (SIGBUS, &old_sigbus_action, NULL);
# endif

      /* Clean up allocated memory.  */
      if (memory_size > 0)
        munmap (memory_with_guard_page, memory_size);
    }

 fail:
  return emulated_getgrouplist (user, group, groups, ngroups);
}

#endif
============================= m4/getgrouplist.m4 =============================
# getgrouplist.m4 serial 1
dnl Copyright (C) 2006 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.

dnl From Bruno Haible.

AC_DEFUN([gl_FUNC_GETGROUPLIST],
[
  dnl Persuade glibc <grp.h> to declare getgrouplist.
  AC_REQUIRE([AC_GNU_SOURCE])
  AC_REQUIRE([AC_TYPE_UID_T])

  AC_CHECK_FUNCS([getgrouplist])
  AC_CACHE_CHECK(whether we are using the GNU C Library 2.3.x,
    gl_cv_gnu_library_2_3_x,
    [AC_EGREP_CPP([Potentially buggy], [
#include <features.h>
#ifdef __GNU_LIBRARY__
 #if __GLIBC__ == 2 && __GLIBC_MINOR__ == 3
  Potentially buggy
 #endif
#endif
      ],
      gl_cv_gnu_library_2_3_x=yes,
      gl_cv_gnu_library_2_3_x=no)
    ])
  if test $ac_cv_func_getgrouplist = no; then
    AC_LIBOBJ([getgrouplist])
  else
    if test $gl_cv_gnu_library_2_3_x = yes; then
      AC_LIBOBJ([getgrouplist])
      AC_DEFINE(getgrouplist, rpl_getgrouplist,
        [Define to rpl_getgrouplist if the replacement function should be 
used.])
      gl_PREREQ_GETGROUPLIST
    fi
  fi
])

# Prerequisites of lib/getgrouplist.c.
AC_DEFUN([gl_PREREQ_GETGROUPLIST], [:])
========================================================================





reply via email to

[Prev in Thread] Current Thread [Next in Thread]