fix lookup_group/lookup_user for ERANGE cases

lookup_group uses getgrnam_r to lookup a group's gid, and
lookup_user uses getpwnam_r to lookup a user's uid.

Those 2 functions can return ERANGE if the passed buffer is
too small to hold the group (or user) information. Fix the
code logic by using sysconf(_SC_GETGR_R_SIZE_MAX) and
sysconf(_SC_GETPW_R_SIZE_MAX) only as hints for the minimal
buffer size to use instead of the max size.

When ERANGE is returned, retry with a buffer twice as big,
until we get valid data, up to 1 MiB.

Change-Id: Id91232e0faffa47356d80d487442d73bc907af98
diff --git a/system.c b/system.c
index 81fd393..ae7f02c 100644
--- a/system.c
+++ b/system.c
@@ -48,6 +48,10 @@
 _Static_assert(SECURE_ALL_BITS == 0x55, "SECURE_ALL_BITS == 0x55.");
 #endif
 
+/* Used by lookup_(user|group) functions. */
+#define MAX_PWENT_SZ (1 << 20)
+#define MAX_GRENT_SZ (1 << 20)
+
 int secure_noroot_set_and_locked(uint64_t mask)
 {
 	return (mask & (SECBIT_NOROOT | SECBIT_NOROOT_LOCKED)) ==
@@ -384,35 +388,45 @@
 	char *buf = NULL;
 	struct passwd pw;
 	struct passwd *ppw = NULL;
+	/*
+	 * sysconf(_SC_GETPW_R_SIZE_MAX), under glibc, is documented to return
+	 * a suggested starting size for the buffer, so let's try getting this
+	 * size first, and fallback to a default othersise.
+	 */
 	ssize_t sz = sysconf(_SC_GETPW_R_SIZE_MAX);
 	if (sz == -1)
 		sz = 65536; /* your guess is as good as mine... */
 
-	/*
-	 * sysconf(_SC_GETPW_R_SIZE_MAX), under glibc, is documented to return
-	 * the maximum needed size of the buffer, so we don't have to search.
-	 */
-	buf = malloc(sz);
-	if (!buf)
-		return -ENOMEM;
+	do {
+		buf = malloc(sz);
+		if (!buf)
+			return -ENOMEM;
+		int err = getpwnam_r(user, &pw, buf, sz, &ppw);
+		/*
+		 * We're safe to free the buffer here. The strings inside |pw|
+		 * point inside |buf|, but we don't use any of them; this leaves
+		 * the pointers dangling but it's safe.
+		 * |ppw| points at |pw| if getpwnam_r(3) succeeded.
+		 */
+		free(buf);
+		if (err == ERANGE) {
+			/* |buf| was too small, retry with a bigger one. */
+			sz <<= 1;
+		} else if (err != 0) {
+			/* We got an error not related to the size of |buf|. */
+			return -err;
+		} else if (!ppw) {
+			/* Not found. */
+			return -ENOENT;
+		} else {
+			*uid = ppw->pw_uid;
+			*gid = ppw->pw_gid;
+			return 0;
+		}
+	} while (sz <= MAX_PWENT_SZ);
 
-	int ret = getpwnam_r(user, &pw, buf, sz, &ppw);
-	/*
-	 * We're safe to free the buffer here. The strings inside |pw| point
-	 * inside |buf|, but we don't use any of them; this leaves the pointers
-	 * dangling but it's safe. |ppw| points at |pw| if getpwnam_r(3)
-	 * succeeded.
-	 */
-	free(buf);
-
-	if (ret != 0)
-		return -ret;  /* Error */
-	if (!ppw)
-		return -ENOENT;  /* Not found */
-
-	*uid = ppw->pw_uid;
-	*gid = ppw->pw_gid;
-	return 0;
+	/* A buffer of size MAX_PWENT_SZ is still too small, return an error. */
+	return -ERANGE;
 }
 
 /*
@@ -423,32 +437,44 @@
 	char *buf = NULL;
 	struct group gr;
 	struct group *pgr = NULL;
+	/*
+	 * sysconf(_SC_GETGR_R_SIZE_MAX), under glibc, is documented to return
+	 * a suggested starting size for the buffer, so let's try getting this
+	 * size first, and fallback to a default otherwise.
+	 */
 	ssize_t sz = sysconf(_SC_GETGR_R_SIZE_MAX);
 	if (sz == -1)
 		sz = 65536; /* and mine is as good as yours, really */
 
-	/*
-	 * sysconf(_SC_GETGR_R_SIZE_MAX), under glibc, is documented to return
-	 * the maximum needed size of the buffer, so we don't have to search.
-	 */
-	buf = malloc(sz);
-	if (!buf)
-		return -ENOMEM;
-	int ret = getgrnam_r(group, &gr, buf, sz, &pgr);
-	/*
-	 * We're safe to free the buffer here. The strings inside gr point
-	 * inside buf, but we don't use any of them; this leaves the pointers
-	 * dangling but it's safe. pgr points at gr if getgrnam_r succeeded.
-	 */
-	free(buf);
+	do {
+		buf = malloc(sz);
+		if (!buf)
+			return -ENOMEM;
+		int err = getgrnam_r(group, &gr, buf, sz, &pgr);
+		/*
+		 * We're safe to free the buffer here. The strings inside |gr|
+		 * point inside |buf|, but we don't use any of them; this leaves
+		 * the pointers dangling but it's safe.
+		 * |pgr| points at |gr| if getgrnam_r(3) succeeded.
+		 */
+		free(buf);
+		if (err == ERANGE) {
+			/* |buf| was too small, retry with a bigger one. */
+			sz <<= 1;
+		} else if (err != 0) {
+			/* We got an error not related to the size of |buf|. */
+			return -err;
+		} else if (!pgr) {
+			/* Not found. */
+			return -ENOENT;
+		} else {
+			*gid = pgr->gr_gid;
+			return 0;
+		}
+	} while (sz <= MAX_GRENT_SZ);
 
-	if (ret != 0)
-		return -ret;  /* Error */
-	if (!pgr)
-		return -ENOENT;  /* Not found */
-
-	*gid = pgr->gr_gid;
-	return 0;
+	/* A buffer of size MAX_GRENT_SZ is still too small, return an error. */
+	return -ERANGE;
 }
 
 static int seccomp_action_is_available(const char *wanted)