changeset 31229:8705dd395e0b

priv-set: new module and accompanying tests; adapt write-any-file * lib/priv-set.c: New file. * lib/priv-set.h: New file. * lib/unlinkdir.c: Make cannot_unlink_dir thread-safe. * lib/write-any-file.c: Simplify by using priv-set module. * m4/priv-set.m4: New file. * modules/priv-set: New file. * modules/unlinkdir: Add dependency on priv-set module. * modules/write-any-file: Likewise. These changes address a problem that originally arose with GNU tar: http://thread.gmane.org/gmane.comp.gnu.tar.bugs/3242 Tests for module 'priv-set'. * modules/priv-set-tests: New file.
author David Bartley <dtbartle@csclub.uwaterloo.ca>
date Wed, 29 Apr 2009 03:52:26 -0400
parents 1d041d870246
children 06a05fb16e67
files ChangeLog lib/priv-set.c lib/priv-set.h lib/unlinkdir.c lib/write-any-file.c m4/priv-set.m4 modules/priv-set modules/priv-set-tests modules/unlinkdir modules/write-any-file tests/test-priv-set.c
diffstat 11 files changed, 375 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun May 03 14:32:16 2009 +0200
+++ b/ChangeLog	Wed Apr 29 03:52:26 2009 -0400
@@ -1,3 +1,19 @@
+2009-04-27  David Bartley  <dtbartle@csclub.uwaterloo.ca>
+
+	priv-set: new module and tests; adapt write-any-file
+	* lib/priv-set.c: New file.
+	* lib/priv-set.h: New file.
+	* lib/unlinkdir.c: Make cannot_unlink_dir thread-safe.
+	* lib/write-any-file.c: Simplify by using priv-set module.
+	* m4/priv-set.m4: New file.
+	* modules/priv-set: New file.
+	* modules/unlinkdir: Add dependency on priv-set module.
+	* modules/write-any-file: Likewise.
+
+	Tests for module 'priv-set'.
+	* modules/priv-set-tests: New file.
+	* tests/test-priv-set.c: New file.
+
 2009-05-03  Jim Meyering  <meyering@redhat.com>
             Bruno Haible  <bruno@clisp.org>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/priv-set.c	Wed Apr 29 03:52:26 2009 -0400
@@ -0,0 +1,143 @@
+/* Query, remove, or restore a Solaris privilege.
+
+   Copyright (C) 2009 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+   Written by David Bartley.  */
+
+#include <config.h>
+#include "priv-set.h"
+
+#if HAVE_GETPPRIV
+
+# include <errno.h>
+# include <stdbool.h>
+# include <priv.h>
+
+/* Holds a (cached) copy of the effective set.  */
+static priv_set_t *eff_set;
+
+/* Holds a set of privileges that we have removed.  */
+static priv_set_t *rem_set;
+
+static bool initialized;
+
+static int
+priv_set_initialize (void)
+{
+  if (! initialized)
+    {
+      eff_set = priv_allocset ();
+      if (!eff_set)
+	{
+	  return -1;
+	}
+      rem_set = priv_allocset ();
+      if (!rem_set)
+	{
+	  priv_freeset (eff_set);
+	  return -1;
+	}
+      if (getppriv (PRIV_EFFECTIVE, eff_set) != 0)
+	{
+	  priv_freeset (eff_set);
+	  priv_freeset (rem_set);
+	  return -1;
+	}
+      priv_emptyset (rem_set);
+      initialized = true;
+    }
+
+  return 0;
+}
+
+
+/* Check if priv is in the effective set.
+   Returns 1 if priv is a member and 0 if not.
+   Returns -1 on error with errno set appropriately.  */
+int
+priv_set_ismember (const char *priv)
+{
+  if (! initialized && priv_set_initialize () != 0)
+    return -1;
+
+  return priv_ismember (eff_set, priv);
+}
+
+
+/* Try to remove priv from the effective set.
+   Returns 0 if priv was removed from or was not present in the effective set.
+   Returns -1 on error with errno set appropriately.  */
+int
+priv_set_remove (const char *priv)
+{
+  if (! initialized && priv_set_initialize () != 0)
+    return -1;
+
+  if (priv_ismember (eff_set, priv))
+    {
+      /* priv_addset/priv_delset can only fail if priv is invalid, which is
+	 checked above by the priv_ismember call.  */
+      priv_delset (eff_set, priv);
+      if (setppriv (PRIV_SET, PRIV_EFFECTIVE, eff_set) != 0)
+	{
+	  priv_addset (eff_set, priv);
+	  return -1;
+	}
+      priv_addset (rem_set, priv);
+    }
+  else
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  return 0;
+}
+
+
+/* Try to restore priv to the effective set.
+   Returns 0 if priv was re-added to the effective set (after being prviously
+   removed by a call to priv_set_remove) or if priv was already in the
+   effective set.
+   Returns -1 on error with errno set appropriately.  */
+int
+priv_set_restore (const char *priv)
+{
+  if (! initialized && priv_set_initialize () != 0)
+    return -1;
+
+  if (priv_ismember (rem_set, priv))
+    {
+      /* priv_addset/priv_delset can only fail if priv is invalid, which is
+	 checked above by the priv_ismember call.  */
+      priv_addset (eff_set, priv);
+      if (setppriv (PRIV_SET, PRIV_EFFECTIVE, eff_set) != 0)
+	{
+	  priv_delset (eff_set, priv);
+	  return -1;
+	}
+      priv_delset (rem_set, priv);
+    }
+  else
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  return 0;
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/priv-set.h	Wed Apr 29 03:52:26 2009 -0400
@@ -0,0 +1,50 @@
+/* Query, remove, or restore a Solaris privilege.
+
+   Copyright (C) 2009 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+   Written by David Bartley.  */
+
+#if HAVE_GETPPRIV
+
+# include <priv.h>
+
+int priv_set_ismember (const char *priv);
+int priv_set_remove (const char *priv);
+int priv_set_restore (const char *priv);
+
+static inline int priv_set_remove_linkdir (void)
+{
+  return priv_set_remove (PRIV_SYS_LINKDIR);
+}
+
+static inline int priv_set_restore_linkdir (void)
+{
+  return priv_set_restore (PRIV_SYS_LINKDIR);
+}
+
+#else
+
+static inline int priv_set_remove_linkdir (void)
+{
+  return -1;
+}
+
+static inline int priv_set_restore_linkdir (void)
+{
+  return -1;
+}
+
+#endif
--- a/lib/unlinkdir.c	Sun May 03 14:32:16 2009 +0200
+++ b/lib/unlinkdir.c	Wed Apr 29 03:52:26 2009 -0400
@@ -1,4 +1,4 @@
-/* unlinkdir.c - determine (and maybe change) whether we can unlink directories
+/* unlinkdir.c - determine whether we can unlink directories
 
    Copyright (C) 2005-2006, 2009 Free Software Foundation, Inc.
 
@@ -15,26 +15,18 @@
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
-/* Written by Paul Eggert and Jim Meyering.  */
+/* Written by Paul Eggert, Jim Meyering, and David Bartley.  */
 
 #include <config.h>
 
 #include "unlinkdir.h"
-
-#if HAVE_PRIV_H
-# include <priv.h>
-#endif
+#include "priv-set.h"
 #include <unistd.h>
 
 #if ! UNLINK_CANNOT_UNLINK_DIR
 
 /* Return true if we cannot unlink directories, false if we might be
-   able to unlink directories.  If possible, tell the kernel we don't
-   want to be able to unlink directories, so that we can return true.
-
-   Note: this function may modify the process privilege set, to remove
-   the PRIV_SYS_LINKDIR privilege, so is neither thread-safe, nor
-   appropriate for use in a library.  */
+   able to unlink directories.  */
 
 bool
 cannot_unlink_dir (void)
@@ -44,20 +36,11 @@
 
   if (! initialized)
     {
-# if defined PRIV_EFFECTIVE && defined PRIV_SYS_LINKDIR
+# if defined PRIV_SYS_LINKDIR
       /* We might be able to unlink directories if we cannot
 	 determine our privileges, or if we have the
-	 PRIV_SYS_LINKDIR privilege and cannot delete it.  */
-      priv_set_t *pset = priv_allocset ();
-      if (pset)
-	{
-	  cannot =
-	    (getppriv (PRIV_EFFECTIVE, pset) == 0
-	     && (! priv_ismember (pset, PRIV_SYS_LINKDIR)
-		 || (priv_delset (pset, PRIV_SYS_LINKDIR) == 0
-		     && setppriv (PRIV_SET, PRIV_EFFECTIVE, pset) == 0)));
-	  priv_freeset (pset);
-	}
+	 PRIV_SYS_LINKDIR privilege.  */
+      cannot = (priv_set_ismember (PRIV_SYS_LINKDIR) == 0);
 # else
       /* In traditional Unix, only root can unlink directories.  */
       cannot = (geteuid () != 0);
--- a/lib/write-any-file.c	Sun May 03 14:32:16 2009 +0200
+++ b/lib/write-any-file.c	Wed Apr 29 03:52:26 2009 -0400
@@ -20,10 +20,8 @@
 #include <config.h>
 
 #include "write-any-file.h"
+#include "priv-set.h"
 
-#if HAVE_PRIV_H
-# include <priv.h>
-#endif
 #include <unistd.h>
 
 /* Return true if we know that we can write any file, including
@@ -38,15 +36,8 @@
   if (! initialized)
     {
       bool can = false;
-#if defined PRIV_EFFECTIVE && defined PRIV_FILE_DAC_WRITE
-      priv_set_t *pset = priv_allocset ();
-      if (pset)
-	{
-	  can =
-	    (getppriv (PRIV_EFFECTIVE, pset) == 0
-	     && priv_ismember (pset, PRIV_FILE_DAC_WRITE));
-	  priv_freeset (pset);
-	}
+#if defined PRIV_FILE_DAC_WRITE
+      can = (priv_set_ismember (PRIV_FILE_DAC_WRITE) == 1);
 #else
       /* In traditional Unix, only root can unlink directories.  */
       can = (geteuid () == 0);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/m4/priv-set.m4	Wed Apr 29 03:52:26 2009 -0400
@@ -0,0 +1,15 @@
+# serial 6
+
+# Copyright (C) 2009 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Written by David Bartley.
+
+AC_DEFUN([gl_PRIV_SET],
+[
+  AC_REQUIRE([AC_C_INLINE])
+  AC_CHECK_FUNCS([getppriv])
+])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/priv-set	Wed Apr 29 03:52:26 2009 -0400
@@ -0,0 +1,26 @@
+Description:
+Query, remove or restore a Solaris privilege
+
+Files:
+lib/priv-set.h
+lib/priv-set.c
+m4/priv-set.m4
+
+Depends-on:
+errno
+stdbool
+
+configure.ac:
+gl_PRIV_SET
+
+Makefile.am:
+lib_SOURCES += priv-set.c
+
+Include:
+"priv-set.h"
+
+License:
+GPL
+
+Maintainer:
+David Bartley
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/priv-set-tests	Wed Apr 29 03:52:26 2009 -0400
@@ -0,0 +1,13 @@
+Files:
+tests/test-priv-set.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-priv-set
+check_PROGRAMS += test-priv-set
+
+License:
+GPL
--- a/modules/unlinkdir	Sun May 03 14:32:16 2009 +0200
+++ b/modules/unlinkdir	Wed Apr 29 03:52:26 2009 -0400
@@ -1,5 +1,5 @@
 Description:
-Determine (and maybe change) whether we can unlink directories.
+Determine whether we can unlink directories.
 
 Files:
 lib/unlinkdir.h
@@ -8,6 +8,7 @@
 
 Depends-on:
 stdbool
+priv-set
 
 configure.ac:
 gl_UNLINKDIR
--- a/modules/write-any-file	Sun May 03 14:32:16 2009 +0200
+++ b/modules/write-any-file	Wed Apr 29 03:52:26 2009 -0400
@@ -8,6 +8,7 @@
 
 Depends-on:
 stdbool
+priv-set
 
 configure.ac:
 gl_WRITE_ANY_FILE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-priv-set.c	Wed Apr 29 03:52:26 2009 -0400
@@ -0,0 +1,99 @@
+/* Test the priv-set module.
+   Copyright (C) 2009 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by David Bartley <dtbartle@csclub.uwaterloo.ca>, 2007.  */
+
+#include <config.h>
+#include "priv-set.h"
+
+#if HAVE_GETPPRIV
+# include <priv.h>
+#endif
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#define ASSERT(expr) \
+  do									     \
+    {									     \
+      if (!(expr))							     \
+        {								     \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+          fflush (stderr);						     \
+          abort ();							     \
+        }								     \
+    }									     \
+  while (0)
+
+int
+main (void)
+{
+#if HAVE_GETPPRIV
+    priv_set_t *set;
+
+    ASSERT (set = priv_allocset ());
+    ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+    ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 1);
+
+    /* Do a series of removes and restores making sure that the results are
+       consistent with our ismember function and solaris' priv_ismember.  */
+    ASSERT (priv_set_ismember (PRIV_PROC_EXEC) == 1);
+        ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+        ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 1);
+    ASSERT (priv_set_restore (PRIV_PROC_EXEC) == -1);
+        ASSERT (errno == EINVAL);
+    ASSERT (priv_set_ismember (PRIV_PROC_EXEC) == 1);
+        ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+        ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 1);
+    ASSERT (priv_set_remove (PRIV_PROC_EXEC) == 0);
+    ASSERT (priv_set_ismember (PRIV_PROC_EXEC) == 0);
+        ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+        ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 0);
+    ASSERT (priv_set_remove (PRIV_PROC_EXEC) == -1);
+        ASSERT (errno == EINVAL);
+    ASSERT (priv_set_ismember (PRIV_PROC_EXEC) == 0);
+        ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+        ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 0);
+    ASSERT (priv_set_restore (PRIV_PROC_EXEC) == 0);
+    ASSERT (priv_set_ismember (PRIV_PROC_EXEC) == 1);
+        ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+        ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 1);
+    ASSERT (priv_set_restore (PRIV_PROC_EXEC) == -1);
+        ASSERT (errno == EINVAL);
+    ASSERT (priv_set_ismember (PRIV_PROC_EXEC) == 1);
+        ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+        ASSERT (priv_ismember (set, PRIV_PROC_EXEC) == 1);
+
+    /* Test the priv_set_linkdir wrappers.  */
+    ASSERT (getppriv (PRIV_EFFECTIVE, set) == 0);
+    if (priv_ismember (set, PRIV_SYS_LINKDIR))
+      {
+        ASSERT (priv_set_restore_linkdir () == -1);
+            ASSERT (errno == EINVAL);
+        ASSERT (priv_set_remove_linkdir () == 0);
+        ASSERT (priv_set_remove_linkdir () == -1);
+            ASSERT (errno == EINVAL);
+        ASSERT (priv_set_restore_linkdir () == 0);
+      }
+#else
+    ASSERT (priv_set_restore_linkdir () == -1);
+    ASSERT (priv_set_remove_linkdir () == -1);
+#endif
+
+    return 0;
+}