[PATCH] nss: Add ERANGE testing to tst-nss-test4 (bug 33361)

DJ Delorie dj@redhat.com
Fri Nov 7 21:29:29 GMT 2025


"Carlos O'Donell" <carlos@redhat.com> writes:
> This adds testing for the fix added in commit:
> 0fceed254559836b57ee05188deac649bc505d05
> "nss: Group merge does not react to ERANGE during merge (bug 33361)"
>
> The in-use group size is increased large enough to trigger ERANGE
> for initial buffers and cause a retry.  The actualy size is
> approximately twice that required to trigger the defect, though
> any size larger than NSS_BUFLEN_GROUP triggers the defect.
>
> Without the fix the group is not merged and the failure is detected,
> but with the fix the ERANGE error is handled, buffers are enlarged
> and subsequently correctly merged.

My two concerns on this, which I consider non-blocking (i.e. we could do
better, but we don't need to ;), are:

1. The use of "files" instead of adding a test3 service, or
containerizing the "files" service table.

2. The location of the endgrent call

However, I think the alignment tests need reworking.  They happen to
work with our implementation, but they are not correct.

> +#include <assert.h>
>  #include <nss.h>
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <string.h>
> +#include <array_length.h>
> +/* For NSS_BUFLEN_GROUP define.  */
> +#include "nss/grp.h"
>  
> +#include <support/test-driver.h>
>  #include <support/support.h>

Ok.

> +/* Enough entries to exceed NSS_BUFLEN_GROUP and trigger ERANGE.  */
> +static char *group_2[256];
>  
> +static char *merge_1[array_length(group_1) + array_length (group_2) - 1];

Ok.

> +/* In order to trigger ERANGE checking the minimum size of
> +   group_table_data2 should exceed NSS_BUFLEN_GROUP which is used
> +   internally by getgrgid. We use 8 bytes per group_2 string as
> +   a lower bound.  */
> +_Static_assert (sizeof (group_table_data2) + array_length (group_2) * 8
> +		>= NSS_BUFLEN_GROUP,
> +		"test group table size should exceed NSS_BUFLEN_GROUP");
> +

Ok.

>  /* This is the data we compare against.  */
>  static struct group group_table[] = {
>    GRP_N(1, "name1", merge_1),

Noting that group_table's "name1" group links to the merge1 list.

> -  int i;
> +  int i, member_cnt;

Ok.

> -  __nss_configure_lookup ("group", "test1 [SUCCESS=merge] test2");
> +  /* At least 3 service modules are needed to reproduce BZ#33361. */
> +  __nss_configure_lookup ("group", "test1 [SUCCESS=merge] test2 files");

I'll note that the content of the "files" service is not controlled
here, but given we use "test names" for the groups instead of
likely-to-conflict realistic names, this is probably not an issue.

> +  /* Test increasing sizes of group_2 to see if we fail, starting with
> +     member_cnt == 1 to ensure we always check for no de-duplication
> +     e.g. { "foo", NULL } */
> +  for (member_cnt = 1; member_cnt < array_length (group_2); member_cnt++)
> +    {
> +      verbose_printf ("Outer loop - member_cnt is %d\n", member_cnt);

Ok.

> +      /* Initialize group_2 */
> +      for (i = 0; i < member_cnt; i++)
> +	{
> +	  /* Note that deduplication is NOT supposed to happen.  */
> +	  if (i == 0)
> +	    group_2[i] = xstrdup ("foo");
> +	  else
> +	    group_2[i] = xasprintf ("foobar%d", i);
> +	}
> +      group_2[member_cnt] = NULL;

Since member_cnt < array_length (256), member_cnt will max out at 255.
group_2[member_cnt] thus stops at group_2[255].  Ok.

> +      /* Create the merged list to verify against */
> +
> +      /* Copy group_1 to the merge list (excluding NULL) */
> +      for (i = 0; i < array_length (group_1) - 1; i++)
> +	{
> +	  merge_1[i] = xasprintf ("%s", group_1[i]);
> +	  verbose_printf ("MERGED LIST of [%d] is %s\n", i, merge_1[i]);
> +	}

Ok.

> +      /* Add group_2 to the merge list */
> +      int group2_index = 0;
> +      for (i = array_length (group_1) - 1;
> +	   i < array_length (group_1) - 1 + member_cnt; i++)
>  	{
> +	  merge_1[i] = xasprintf ("%s", group_2[group2_index++]);

starts group_2 at [0], ok.

last entry would be [3 - 1 + 255] vs size of [3 + 256 - 1]; ok.

> +	  verbose_printf ("MERGED LIST of [%d] is %s\n", i, merge_1[i]);
> +	}
> +      merge_1[array_length(group_1) - 1 + member_cnt]= NULL;

Ok.

> +      align_mask = __alignof__ (struct group *) - 1;

This notes the alignment of a pointer pointing to "struct group", not
the alignment of "struct group" itself.

> +      setgrent ();
> +
> +      for (i = 0; group_table[i].gr_gid; ++i)
> +	{

for each group in the list; only the first is the big merged table.

> +	  g = getgrgid (group_table[i].gr_gid);
> +	  if (g)
>  	    {
> +	      retval += compare_groups (i, g, & group_table[i]);

This compares the merged list.  Note, if the "files" service *does*
provide a matching group, this will fail.

> +	      if ((uintptr_t)g & align_mask)
> +		{
> +		  printf ("FAIL: [%d] unaligned group %p\n", i, g);
> +		  ++retval;
> +		}

This is comparing the alignment of the value of g (the address of what g
points to), with the calculated alignment of the storage of g itself
(not the storage that g points to).

Consider an implentation with "struct group" needing 128-bit alignment,
on a 32-bit host.  align_mask would be for 32-bit (~0x03) but the
pointer g must be aligned to 128-bit (~0x1f)

> +	      if ((uintptr_t)(g->gr_mem) & align_mask)
> +		{
> +		  printf ("FAIL: [%d] unaligned member list %p\n",
> +			  i, g->gr_mem);
> +		  ++retval;
> +		}
>  	    }

This is comparing the alignment of a pointer to the alignment of a
pointer, so is OK, but note that if you fix align_mask to be the
alignment of "struct group", you'll need to fix this too.

> +	  else
>  	    {
> +	      printf ("FAIL: [%d] group %u.%s not found\n", i,
> +		      group_table[i].gr_gid, group_table[i].gr_name);
>  	      ++retval;
>  	    }
>  	}

Ok.

>  
> -  endgrent ();
> +      /* Free malloc'd array members (including the NULL) */
> +      for (i = 0; i < member_cnt; i++)
> +	free (group_2[i]);
> +      for (i = 0; i < array_length (group_1) + member_cnt; i++)
> +	free (merge_1[i]);
> +
> +      endgrent ();
> +    }

I don't think it makes a difference in this case (because we control the
service list), but I think endgrent() should be called before you free
the data it might be looking at...



More information about the Libc-alpha mailing list