2012-11-28  Jeff Law  <law@redhat.com>

	[BZ #13761]
	* nscd/grpcache.c (cache_addgr): Rename alloca_used to
	dataset_temporary.  Track alloca usage into alloca_used.
	If dataset is large allocate and release it via malloc/free.

2012-03-28  Siddhesh Poyarekar  <siddhesh@redhat.com>

	[BZ #13761]
	* nis/nss_compat/compat-initgroups.c (getgrent_next_nss,
	_nss_compat_initgroups_dyn): Fall back to malloc/free
	for large group memberships.

Index: glibc-2.11.3/nis/nss_compat/compat-initgroups.c
===================================================================
--- glibc-2.11.3.orig/nis/nss_compat/compat-initgroups.c
+++ glibc-2.11.3/nis/nss_compat/compat-initgroups.c
@@ -303,6 +303,8 @@ getgrent_next_nss (ent_t *ent, char *buf
       if (nss_initgroups_dyn (user, group, &mystart, &mysize, &mygroups,
 			      limit, errnop) == NSS_STATUS_SUCCESS)
 	{
+	  status = NSS_STATUS_NOTFOUND;
+
 	  /* If there is no blacklist we can trust the underlying
 	     initgroups implementation.  */
 	  if (ent->blacklist.current <= 1)
@@ -315,6 +317,7 @@ getgrent_next_nss (ent_t *ent, char *buf
 		 overwrite the pointer with one to a bigger buffer.  */
 	      char *tmpbuf = buffer;
 	      size_t tmplen = buflen;
+	      bool use_malloc = false;
 
 	      for (int i = 0; i < mystart; i++)
 		{
@@ -322,21 +325,36 @@ getgrent_next_nss (ent_t *ent, char *buf
 						   tmpbuf, tmplen, errnop))
 			 == NSS_STATUS_TRYAGAIN
 			 && *errnop == ERANGE)
-		    if (tmpbuf == buffer)
-		      {
-			tmplen *= 2;
-			tmpbuf = __alloca (tmplen);
-		      }
-		    else
-		      tmpbuf = extend_alloca (tmpbuf, tmplen, 2 * tmplen);
+                    {
+                      if (__libc_use_alloca (tmplen * 2))
+                        {
+                          if (tmpbuf == buffer)
+                            {
+                              tmplen *= 2;
+                              tmpbuf = __alloca (tmplen);
+                            }
+                          else
+                            tmpbuf = extend_alloca (tmpbuf, tmplen, tmplen * 2);
+                        }
+                      else
+                        {
+                          tmplen *= 2;
+                          char *newbuf = realloc (use_malloc ? tmpbuf : NULL, tmplen);
+
+                          if (newbuf == NULL)
+                            {
+                              status = NSS_STATUS_TRYAGAIN;
+			      goto done;
+                            }
+                          use_malloc = true;
+                          tmpbuf = newbuf;
+                        }
+                    }
 
 		  if (__builtin_expect  (status != NSS_STATUS_NOTFOUND, 1))
 		    {
 		      if (__builtin_expect  (status != NSS_STATUS_SUCCESS, 0))
-			{
-			  free (mygroups);
-			  return status;
-			}
+		        goto done;
 
 		      if (!in_blacklist (grpbuf.gr_name,
 					 strlen (grpbuf.gr_name), ent)
@@ -354,11 +372,17 @@ getgrent_next_nss (ent_t *ent, char *buf
 			}
 		    }
 		}
+
+	      status = NSS_STATUS_NOTFOUND;
+
+ done:
+	      if (use_malloc)
+	        free (tmpbuf);
 	    }
 
 	  free (mygroups);
 
-	  return NSS_STATUS_NOTFOUND;
+	  return status;
 	}
 
       free (mygroups);
@@ -515,6 +539,7 @@ _nss_compat_initgroups_dyn (const char *
   char *tmpbuf;
   enum nss_status status;
   ent_t intern = { true, false, false, NULL, {NULL, 0, 0} };
+  bool use_malloc = false;
 
   status = internal_setgrent (&intern);
   if (status != NSS_STATUS_SUCCESS)
@@ -528,13 +553,32 @@ _nss_compat_initgroups_dyn (const char *
 					    user, group, start, size,
 					    groupsp, limit, errnop))
 	     == NSS_STATUS_TRYAGAIN && *errnop == ERANGE)
-	tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
+        if (__libc_use_alloca (buflen * 2))
+          tmpbuf = extend_alloca (tmpbuf, buflen, 2 * buflen);
+        else
+          {
+            buflen *= 2;
+            char *newbuf = realloc (use_malloc ? tmpbuf : NULL, buflen);
+            if (newbuf == NULL)
+              {
+                status = NSS_STATUS_TRYAGAIN;
+                goto done;
+              }
+            use_malloc = true;
+            tmpbuf = newbuf;
+          }
     }
   while (status == NSS_STATUS_SUCCESS);
 
+  status = NSS_STATUS_SUCCESS;
+
+ done:
+  if (use_malloc)
+    free (tmpbuf);
+
   internal_endgrent (&intern);
 
-  return NSS_STATUS_SUCCESS;
+  return status;
 }
 
 
Index: glibc-2.11.3/nscd/grpcache.c
===================================================================
--- glibc-2.11.3.orig/nscd/grpcache.c
+++ glibc-2.11.3/nscd/grpcache.c
@@ -170,7 +170,8 @@ cache_addgr (struct database_dyn *db, in
       char *cp;
       const size_t key_len = strlen (key);
       const size_t buf_len = 3 * sizeof (grp->gr_gid) + key_len + 1;
-      char *buf = alloca (buf_len);
+      size_t alloca_used = 0;
+      char *buf = alloca_account (buf_len, alloca_used);
       ssize_t n;
       size_t cnt;
 
@@ -182,7 +183,7 @@ cache_addgr (struct database_dyn *db, in
       /* Determine the length of all members.  */
       while (grp->gr_mem[gr_mem_cnt])
 	++gr_mem_cnt;
-      gr_mem_len = (uint32_t *) alloca (gr_mem_cnt * sizeof (uint32_t));
+      gr_mem_len = alloca_account (gr_mem_cnt * sizeof (uint32_t), alloca_used);
       for (gr_mem_cnt = 0; grp->gr_mem[gr_mem_cnt]; ++gr_mem_cnt)
 	{
 	  gr_mem_len[gr_mem_cnt] = strlen (grp->gr_mem[gr_mem_cnt]) + 1;
@@ -197,7 +198,8 @@ cache_addgr (struct database_dyn *db, in
 	 change.  Allocate memory on the cache since it is likely
 	 discarded anyway.  If it turns out to be necessary to have a
 	 new record we can still allocate real memory.  */
-      bool alloca_used = false;
+      bool dataset_temporary = false;
+      bool dataset_malloced = false;
       dataset = NULL;
 
       if (he == NULL)
@@ -217,10 +219,20 @@ cache_addgr (struct database_dyn *db, in
 	  /* We cannot permanently add the result in the moment.  But
 	     we can provide the result as is.  Store the data in some
 	     temporary memory.  */
-	  dataset = (struct dataset *) alloca (total + n);
+	  if (! __libc_use_alloca (alloca_used + total + n))
+	    {
+	      dataset = malloc (total + n);
+	      /* Perhaps we should log a message that we were unable
+		 to allocate memory for a large request.  */
+	      if (dataset == NULL)
+		goto out;
+	      dataset_malloced = true;
+	    }
+	  else
+	    dataset = alloca_account (total + n, alloca_used);
 
 	  /* We cannot add this record to the permanent database.  */
-	  alloca_used = true;
+	  dataset_temporary = true;
 	}
 
       dataset->head.allocsize = total + n;
@@ -274,6 +286,11 @@ cache_addgr (struct database_dyn *db, in
 		 allocated on the stack and need not be freed.  */
 	      dh->timeout = dataset->head.timeout;
 	      ++dh->nreloads;
+
+	      /* If the new record was allocated via malloc, then we must free
+		 it here.  */
+	      if (dataset_malloced)
+		free (dataset);
 	    }
 	  else
 	    {
@@ -289,7 +306,7 @@ cache_addgr (struct database_dyn *db, in
 		  key_copy = (char *) newp + (key_copy - (char *) dataset);
 
 		  dataset = memcpy (newp, dataset, total + n);
-		  alloca_used = false;
+		  dataset_temporary = false;
 		}
 
 	      /* Mark the old record as obsolete.  */
@@ -304,7 +321,7 @@ cache_addgr (struct database_dyn *db, in
 	  assert (fd != -1);
 
 #ifdef HAVE_SENDFILE
-	  if (__builtin_expect (db->mmap_used, 1) && !alloca_used)
+	  if (__builtin_expect (db->mmap_used, 1) && ! dataset_temporary)
 	    {
 	      assert (db->wr_fd != -1);
 	      assert ((char *) &dataset->resp > (char *) db->data);
@@ -331,7 +348,7 @@ cache_addgr (struct database_dyn *db, in
 
       /* Add the record to the database.  But only if it has not been
 	 stored on the stack.  */
-      if (! alloca_used)
+      if (! dataset_temporary)
 	{
 	  /* If necessary, we also propagate the data to disk.  */
 	  if (db->persistent)
