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