Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > ebe084c140192657f9094e135a84202c > files > 71

libvirt-0.8.2-29.el5.src.rpm

From 06135051bd5a9c8e0033e138ba66c3276ab191d3 Mon Sep 17 00:00:00 2001
From: Eric Blake <eblake@redhat.com>
Date: Mon, 16 May 2011 15:37:15 -0600
Subject: [PATCH] build: tolerate unlimited group size
To: libvir-list@redhat.com

For https://bugzilla.redhat.com/show_bug.cgi?id=771720

POSIX allows sysconf(_SC_GETPW_R_SIZE_MAX) to return -1 if there
is no fixed limit, and requires ERANGE errors to track real size.
Model our behavior after the example in POSIX itself:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html

Also, on error for get*_r functions, errno is undefined, and the
real error was the return value.

* src/util/util.c (virGetUserEnt, virGetUserID, virGetGroupID)
(virSetUIDGID):  Cope with sysconf failure or too small buffer.
Reported by Matthias Bolte.

cherry-picked from b3918fabda42e3aecf1b0bfe1079a05e6cad62f7

Conflicts:

	src/util/util.c

Addition:
        src/util/memory.h src/util/memory.c src/libvirt_private.syms
        add the VIR_RESIZE_N macro and virResizeN, virExpandN functions
        that it relies upon.

Signed-off-by: Daniel Veillard <veillard@redhat.com>
---
 src/libvirt_private.syms |    1 +
 src/util/memory.c        |   66 ++++++++++++++++++++++++
 src/util/memory.h        |   26 ++++++++++
 src/util/util.c          |  123 ++++++++++++++++++++++++++++++++++++++-------
 4 files changed, 197 insertions(+), 19 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 186c67c..b830b18 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -466,6 +466,7 @@ virLogUnlock;
 virAlloc;
 virAllocN;
 virReallocN;
+virResizeN;
 virFree;
 
 
diff --git a/src/util/memory.c b/src/util/memory.c
index dd1216b..9693bc5 100644
--- a/src/util/memory.c
+++ b/src/util/memory.c
@@ -165,6 +165,72 @@ int virReallocN(void *ptrptr, size_t size, size_t count)
 }
 
 /**
+ * virExpandN:
+ * @ptrptr: pointer to pointer for address of allocated memory
+ * @size: number of bytes per element
+ * @countptr: pointer to number of elements in array
+ * @add: number of elements to add
+ *
+ * Resize the block of memory in 'ptrptr' to be an array of
+ * '*countptr' + 'add' elements, each 'size' bytes in length.
+ * Update 'ptrptr' and 'countptr'  with the details of the newly
+ * allocated memory. On failure, 'ptrptr' and 'countptr' are not
+ * changed. Any newly allocated memory in 'ptrptr' is zero-filled.
+ *
+ * Returns -1 on failure to allocate, zero on success
+ */
+int virExpandN(void *ptrptr, size_t size, size_t *countptr, size_t add)
+{
+    int ret;
+
+    if (*countptr + add < *countptr) {
+        errno = ENOMEM;
+        return -1;
+    }
+    ret = virReallocN(ptrptr, size, *countptr + add);
+    if (ret == 0) {
+        memset(*(char **)ptrptr + (size * *countptr), 0, size * add);
+        *countptr += add;
+    }
+    return ret;
+}
+
+/**
+ * virResizeN:
+ * @ptrptr: pointer to pointer for address of allocated memory
+ * @size: number of bytes per element
+ * @allocptr: pointer to number of elements allocated in array
+ * @count: number of elements currently used in array
+ * @add: minimum number of additional elements to support in array
+ *
+ * If 'count' + 'add' is larger than '*allocptr', then resize the
+ * block of memory in 'ptrptr' to be an array of at least 'count' +
+ * 'add' elements, each 'size' bytes in length. Update 'ptrptr' and
+ * 'allocptr' with the details of the newly allocated memory. On
+ * failure, 'ptrptr' and 'allocptr' are not changed. Any newly
+ * allocated memory in 'ptrptr' is zero-filled.
+ *
+ * Returns -1 on failure to allocate, zero on success
+ */
+int virResizeN(void *ptrptr, size_t size, size_t *allocptr, size_t count,
+               size_t add)
+{
+    size_t delta;
+
+    if (count + add < count) {
+        errno = ENOMEM;
+        return -1;
+    }
+    if (count + add <= *allocptr)
+        return 0;
+
+    delta = count + add - *allocptr;
+    if (delta < *allocptr / 2)
+        delta = *allocptr / 2;
+    return virExpandN(ptrptr, size, allocptr, delta);
+}
+
+/**
  * Vir_Alloc_Var:
  * @ptrptr: pointer to hold address of allocated memory
  * @struct_size: size of initial struct
diff --git a/src/util/memory.h b/src/util/memory.h
index 60e2be6..084296e 100644
--- a/src/util/memory.h
+++ b/src/util/memory.h
@@ -49,6 +49,9 @@
 int virAlloc(void *ptrptr, size_t size) ATTRIBUTE_RETURN_CHECK;
 int virAllocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK;
 int virReallocN(void *ptrptr, size_t size, size_t count) ATTRIBUTE_RETURN_CHECK;
+int virResizeN(void *ptrptr, size_t size, size_t *alloc, size_t count,
+               size_t desired)
+    ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3);
 int virAllocVar(void *ptrptr,
                 size_t struct_size,
                 size_t element_size,
@@ -93,6 +96,29 @@ void virFree(void *ptrptr);
  */
 # define VIR_REALLOC_N(ptr, count) virReallocN(&(ptr), sizeof(*(ptr)), (count))
 
+/**
+ * VIR_RESIZE_N:
+ * @ptr: pointer to hold address of allocated memory
+ * @alloc: variable tracking number of elements currently allocated
+ * @count: number of elements currently in use
+ * @add: minimum number of elements to additionally support
+ *
+ * Blindly using VIR_EXPAND_N(array, alloc, 1) in a loop scales
+ * quadratically, because every iteration must copy contents from
+ * all prior iterations.  But amortized linear scaling can be achieved
+ * by tracking allocation size separately from the number of used
+ * elements, and growing geometrically only as needed.
+ *
+ * If 'count' + 'add' is larger than 'alloc', then geometrically reallocate
+ * the array of 'alloc' elements, each sizeof(*ptr) bytes long, and store
+ * the address of allocated memory in 'ptr' and the new size in 'alloc'.
+ * The new elements are filled with zero.
+ *
+ * Returns -1 on failure, 0 on success
+ */
+# define VIR_RESIZE_N(ptr, alloc, count, add) \
+    virResizeN(&(ptr), sizeof(*(ptr)), &(alloc), count, add)
+
 /*
  * VIR_ALLOC_VAR_OVERSIZED:
  * @M: size of base structure
diff --git a/src/util/util.c b/src/util/util.c
index 40d7572..51347a9 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -563,7 +563,7 @@ __virExec(const char *const*argv,
         goto fork_error;
     }
 
-    openmax = sysconf (_SC_OPEN_MAX);
+    openmax = sysconf(_SC_OPEN_MAX);
     for (i = 3; i < openmax; i++)
         if (i != infd &&
             i != null &&
@@ -2610,11 +2610,11 @@ static char *virGetUserEnt(uid_t uid,
     struct passwd *pw = NULL;
     long val = sysconf(_SC_GETPW_R_SIZE_MAX);
     size_t strbuflen = val;
+    int rc;
 
-    if (val < 0) {
-        virReportSystemError(errno, "%s", _("sysconf failed"));
-        return NULL;
-    }
+    /* sysconf is a hint; if it fails, fall back to a reasonable size */
+    if (val < 0)
+        strbuflen = 1024;
 
     if (VIR_ALLOC_N(strbuf, strbuflen) < 0) {
         virReportOOMError();
@@ -2628,8 +2628,15 @@ static char *virGetUserEnt(uid_t uid,
      *  0 or ENOENT or ESRCH or EBADF or EPERM or ...
      *        The given name or uid was not found.
      */
-    if (getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) {
-        virReportSystemError(errno,
+    while ((rc = getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw)) == ERANGE) {
+        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) {
+            virReportOOMError();
+            VIR_FREE(strbuf);
+            return NULL;
+        }
+    }
+    if (rc != 0 || pw == NULL) {
+        virReportSystemError(rc,
                              _("Failed to find user record for uid '%u'"),
                              (unsigned int) uid);
         VIR_FREE(strbuf);
@@ -2667,11 +2674,11 @@ int virGetUserID(const char *name,
     struct passwd *pw = NULL;
     long val = sysconf(_SC_GETPW_R_SIZE_MAX);
     size_t strbuflen = val;
+    int rc;
 
-    if (val < 0) {
-        virReportSystemError(errno, "%s", _("sysconf failed"));
-        return -1;
-    }
+    /* sysconf is a hint; if it fails, fall back to a reasonable size */
+    if (val < 0)
+        strbuflen = 1024;
 
     if (VIR_ALLOC_N(strbuf, strbuflen) < 0) {
         virReportOOMError();
@@ -2685,8 +2692,15 @@ int virGetUserID(const char *name,
      *  0 or ENOENT or ESRCH or EBADF or EPERM or ...
      *        The given name or uid was not found.
      */
-    if (getpwnam_r(name, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) {
-        virReportSystemError(errno,
+    while ((rc = getpwnam_r(name, &pwbuf, strbuf, strbuflen, &pw)) == ERANGE) {
+        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) {
+            virReportOOMError();
+            VIR_FREE(strbuf);
+            return -1;
+        }
+    }
+    if (rc != 0 || pw == NULL) {
+        virReportSystemError(rc,
                              _("Failed to find user record for name '%s'"),
                              name);
         VIR_FREE(strbuf);
@@ -2709,11 +2723,11 @@ int virGetGroupID(const char *name,
     struct group *gr = NULL;
     long val = sysconf(_SC_GETGR_R_SIZE_MAX);
     size_t strbuflen = val;
+    int rc;
 
-    if (val < 0) {
-        virReportSystemError(errno, "%s", _("sysconf failed"));
-        return -1;
-    }
+    /* sysconf is a hint; if it fails, fall back to a reasonable size */
+    if (val < 0)
+        strbuflen = 1024;
 
     if (VIR_ALLOC_N(strbuf, strbuflen) < 0) {
         virReportOOMError();
@@ -2727,8 +2741,15 @@ int virGetGroupID(const char *name,
      *  0 or ENOENT or ESRCH or EBADF or EPERM or ...
      *        The given name or uid was not found.
      */
-    if (getgrnam_r(name, &grbuf, strbuf, strbuflen, &gr) != 0 || gr == NULL) {
-        virReportSystemError(errno,
+    while ((rc = getgrnam_r(name, &grbuf, strbuf, strbuflen, &gr)) == ERANGE) {
+        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) {
+            virReportOOMError();
+            VIR_FREE(strbuf);
+            return -1;
+        }
+    }
+    if (rc != 0 || gr == NULL) {
+        virReportSystemError(rc,
                              _("Failed to find group record for name '%s'"),
                              name);
         VIR_FREE(strbuf);
@@ -2742,6 +2763,70 @@ int virGetGroupID(const char *name,
     return 0;
 }
 
+/* Set the real and effective uid and gid to the given values, and call
+ * initgroups so that the process has all the assumed group membership of
+ * that uid. return 0 on success, -1 on failure.
+ */
+int
+virSetUIDGID(uid_t uid, gid_t gid)
+{
+    if (gid > 0) {
+        if (setregid(gid, gid) < 0) {
+            virReportSystemError(errno,
+                                 _("cannot change to '%d' group"),
+                                 (unsigned int) gid);
+            return -1;
+        }
+    }
+
+    if (uid > 0) {
+# ifdef HAVE_INITGROUPS
+        struct passwd pwd, *pwd_result;
+        char *buf = NULL;
+        size_t bufsize;
+        int rc;
+
+        bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+        if (bufsize == -1)
+            bufsize = 16384;
+
+        if (VIR_ALLOC_N(buf, bufsize) < 0) {
+            virReportOOMError();
+            return -1;
+        }
+        while ((rc = getpwuid_r(uid, &pwd, buf, bufsize,
+                                &pwd_result)) == ERANGE) {
+            if (VIR_RESIZE_N(buf, bufsize, bufsize, bufsize) < 0) {
+                virReportOOMError();
+                VIR_FREE(buf);
+                return -1;
+            }
+        }
+        if (rc || !pwd_result) {
+            virReportSystemError(rc, _("cannot getpwuid_r(%d)"),
+                                 (unsigned int) uid);
+            VIR_FREE(buf);
+            return -1;
+        }
+        if (initgroups(pwd.pw_name, pwd.pw_gid) < 0) {
+            virReportSystemError(errno,
+                                 _("cannot initgroups(\"%s\", %d)"),
+                                 pwd.pw_name, (unsigned int) pwd.pw_gid);
+            VIR_FREE(buf);
+            return -1;
+        }
+        VIR_FREE(buf);
+# endif
+        if (setreuid(uid, uid) < 0) {
+            virReportSystemError(errno,
+                                 _("cannot change to uid to '%d'"),
+                                 (unsigned int) uid);
+            return -1;
+        }
+    }
+    return 0;
+}
+
 #else /* HAVE_GETPWUID_R */
 
 char *
-- 
1.7.7.4