]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
Harden putpwent, putgrent, putspent, putspent against injection [BZ #18724]
authorFlorian Weimer <fweimer@redhat.com>
Fri, 2 Oct 2015 09:34:13 +0000 (11:34 +0200)
committerFlorian Weimer <fweimer@redhat.com>
Fri, 2 Oct 2015 09:34:13 +0000 (11:34 +0200)
This prevents injection of ':' and '\n' into output functions which
use the NSS files database syntax.  Critical fields (user/group names
and file system paths) are checked strictly.  For backwards
compatibility, the GECOS field is rewritten instead.

The getent program is adjusted to use the put*ent functions in libc,
instead of local copies.  This changes the behavior of getent if user
names start with '-' or '+'.

22 files changed:
ChangeLog
NEWS
grp/Makefile
grp/putgrent.c
grp/tst-putgrent.c [new file with mode: 0644]
gshadow/Makefile
gshadow/putsgent.c
gshadow/tst-putsgent.c [new file with mode: 0644]
include/nss.h
include/pwd.h
nss/Makefile
nss/getent.c
nss/rewrite_field.c [new file with mode: 0644]
nss/tst-field.c [new file with mode: 0644]
nss/valid_field.c [new file with mode: 0644]
nss/valid_list_field.c [new file with mode: 0644]
pwd/Makefile
pwd/putpwent.c
pwd/tst-putpwent.c [new file with mode: 0644]
shadow/Makefile
shadow/putspent.c
shadow/tst-putspent.c [new file with mode: 0644]

index d410e0feef6371396063e42aa83c0f73e9149a7a..20953eebc974722b977956c45929279491d73ab9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,41 @@
+2015-10-02  Florian Weimer  <fweimer@redhat.com>
+
+       [BZ #18724]
+       * include/nss.h (NSS_INVALID_FIELD_CHARACTERS): Define.
+       (__nss_invalid_field_characters, __nss_valid_field)
+       (__nss_valid_list_field, __nss_rewrite_field): Declare.
+       * nss/valid_field.c, nss/valid_list_field, nss/rewrite_field.c,
+       tst-field.c: New file.
+       * nss/Makefile (routines): Add valid_field, rewrite_field.
+       (tests-static): Define unconditionally.
+       (tests): Include tests-static.
+       [build-static-nss] (tests-static): Use append.
+       [build-static-nss] (tests): Remove modification.
+       * nss/getent.c (print_group): Call putgrent.  Report error.
+       (print_gshadow): Call putsgent.  Report error.
+       (print_passwd): Call putpwent.  Report error.
+       (print_shadow): Call putspent.  Report error.
+       * include/pwd.h: Include <nss.h> instead of <nss/nss.h>.
+       * pwd/pwd.h (putpwent): Remove incorrect nonnull attribute.
+       * pwd/putpwent.c (putpwent): Use ISO function definition.  Check
+       name, password, directory, shell fields for valid syntax.  Rewrite
+       GECOS field to match syntax.
+       * pwd/Makefile (tests): Add tst-putpwent.
+       * pwd/tst-putpwent.c: New file.
+       * grp/putgrent.c (putgrent): Convert to ISO function definition.
+       Check grName, grpasswd, gr_mem fields for valid syntax.
+       Change loop variable i to size_t.
+       * grp/Makefile (tests): Add tst-putgrent.
+       * grp/tst-putgrent.c: New file.
+       * shadow/putspent.c (putspent): Check sp_namp, sp_pwdp fields for
+       valid syntax.
+       * shadow/Makefile (tests): Add tst-putspent.
+       * shadow/tst-putspent.c: New file.
+       * gshadow/putsgent.c (putsgent): Check sg_namp, sg_passwd, sg_adm,
+       sg_mem fields for valid syntax.
+       * gshadow/Makefile (tests): Add tst-putsgent.
+       * gshadow/tst-putsgent.c: New file.
+
 2015-10-01  Gabriel F. T. Gomes  <gftg@linux.vnet.ibm.com>
 
        * sysdeps/powerpc/powerpc64/power8/strncpy.S: Added comments to some
diff --git a/NEWS b/NEWS
index e8b59a46768997f3507093de4512a640081f8828..4634b74ebf6b3b5c83583837f1c54a301624ff63 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,11 +13,11 @@ Version 2.23
   15918, 16141, 16296, 16347, 16415, 16517, 16519, 16520, 16521, 16620,
   16734, 16973, 16985, 17118, 17243, 17244, 17250, 17441, 17787, 17886,
   17887, 17905, 18084, 18086, 18240, 18265, 18370, 18421, 18480, 18525,
-  18595, 18610, 18618, 18647, 18661, 18674, 18675, 18681, 18757, 18778,
-  18781, 18787, 18789, 18790, 18795, 18796, 18803, 18820, 18823, 18824,
-  18825, 18857, 18863, 18870, 18872, 18873, 18875, 18887, 18921, 18951,
-  18952, 18956, 18961, 18966, 18967, 18969, 18970, 18977, 18980, 18981,
-  18985, 19003, 19016, 19032, 19046.
+  18595, 18610, 18618, 18647, 18661, 18674, 18675, 18681, 18724, 18757,
+  18778, 18781, 18787, 18789, 18790, 18795, 18796, 18803, 18820, 18823,
+  18824, 18825, 18857, 18863, 18870, 18872, 18873, 18875, 18887, 18921,
+  18951, 18952, 18956, 18961, 18966, 18967, 18969, 18970, 18977, 18980,
+  18981, 18985, 19003, 19016, 19032, 19046.
 
 * The obsolete header <regexp.h> has been removed.  Programs that require
   this header must be updated to use <regex.h> instead.
index c63b552a65de6989e51f53fde4c83a5418949c74..ed8cc2b0564f0e3842cd78f24a4e0788d659bbc4 100644 (file)
@@ -28,7 +28,7 @@ routines := fgetgrent initgroups setgroups \
            getgrent getgrgid getgrnam putgrent \
            getgrent_r getgrgid_r getgrnam_r fgetgrent_r
 
-tests := testgrp
+tests := testgrp tst-putgrent
 
 ifeq (yes,$(build-shared))
 test-srcs :=  tst_fgetgrent
index e4d662c6b50fe3ad4a9cd2e1c05c44c358a55045..1ac7f19a0b4b7fd249fc05e538efda58f69f2abe 100644 (file)
@@ -16,7 +16,9 @@
    <http://www.gnu.org/licenses/>.  */
 
 #include <errno.h>
+#include <nss.h>
 #include <stdio.h>
+#include <string.h>
 #include <grp.h>
 
 #define flockfile(s) _IO_flockfile (s)
 /* Write an entry to the given stream.
    This must know the format of the group file.  */
 int
-putgrent (gr, stream)
-     const struct group *gr;
-     FILE *stream;
+putgrent (const struct group *gr, FILE *stream)
 {
   int retval;
 
-  if (__glibc_unlikely (gr == NULL) || __glibc_unlikely (stream == NULL))
+  if (__glibc_unlikely (gr == NULL) || __glibc_unlikely (stream == NULL)
+      || gr->gr_name == NULL || !__nss_valid_field (gr->gr_name)
+      || !__nss_valid_field (gr->gr_passwd)
+      || !__nss_valid_list_field (gr->gr_mem))
     {
       __set_errno (EINVAL);
       return -1;
@@ -56,9 +59,7 @@ putgrent (gr, stream)
 
   if (gr->gr_mem != NULL)
     {
-      int i;
-
-      for (i = 0 ; gr->gr_mem[i] != NULL; i++)
+      for (size_t i = 0; gr->gr_mem[i] != NULL; i++)
        if (fprintf (stream, i == 0 ? "%s" : ",%s", gr->gr_mem[i]) < 0)
          {
            /* What else can we do?  */
diff --git a/grp/tst-putgrent.c b/grp/tst-putgrent.c
new file mode 100644 (file)
index 0000000..0a07ef6
--- /dev/null
@@ -0,0 +1,167 @@
+/* Test for processing of invalid group entries.  [BZ #18724]
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <grp.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool errors;
+
+static void
+check (struct group e, const char *expected)
+{
+  char *buf;
+  size_t buf_size;
+  FILE *f = open_memstream (&buf, &buf_size);
+
+  if (f == NULL)
+    {
+      printf ("open_memstream: %m\n");
+      errors = true;
+      return;
+    }
+
+  int ret = putgrent (&e, f);
+
+  if (expected == NULL)
+    {
+      if (ret == -1)
+       {
+         if (errno != EINVAL)
+           {
+             printf ("putgrent: unexpected error code: %m\n");
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("putgrent: unexpected success (\"%s\", \"%s\")\n",
+                 e.gr_name, e.gr_passwd);
+         errors = true;
+       }
+    }
+  else
+    {
+      /* Expect success.  */
+      size_t expected_length = strlen (expected);
+      if (ret == 0)
+       {
+         long written = ftell (f);
+
+         if (written <= 0 || fflush (f) < 0)
+           {
+             printf ("stream error: %m\n");
+             errors = true;
+           }
+         else if (buf[written - 1] != '\n')
+           {
+             printf ("FAILED: \"%s\" without newline\n", expected);
+             errors = true;
+           }
+         else if (strncmp (buf, expected, written - 1) != 0
+                  || written - 1 != expected_length)
+           {
+             buf[written - 1] = '\0';
+             printf ("FAILED: \"%s\" (%ld), expected \"%s\" (%zu)\n",
+                     buf, written - 1, expected, expected_length);
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("FAILED: putgrent (expected \"%s\"): %m\n", expected);
+         errors = true;
+       }
+    }
+
+  fclose (f);
+  free (buf);
+}
+
+static int
+do_test (void)
+{
+  check ((struct group) {
+      .gr_name = (char *) "root",
+    },
+    "root::0:");
+  check ((struct group) {
+      .gr_name = (char *) "root",
+      .gr_passwd = (char *) "password",
+      .gr_gid = 1234,
+      .gr_mem = (char *[2]) {(char *) "member1", NULL}
+    },
+    "root:password:1234:member1");
+  check ((struct group) {
+      .gr_name = (char *) "root",
+      .gr_passwd = (char *) "password",
+      .gr_gid = 1234,
+      .gr_mem = (char *[3]) {(char *) "member1", (char *) "member2", NULL}
+    },
+    "root:password:1234:member1,member2");
+
+  /* Bad values.  */
+  {
+    static const char *const bad_strings[] = {
+      ":",
+      "\n",
+      ":bad",
+      "\nbad",
+      "b:ad",
+      "b\nad",
+      "bad:",
+      "bad\n",
+      "b:a\nd"
+      ",",
+      "\n,",
+      ":,",
+      ",bad",
+      "b,ad",
+      "bad,",
+      NULL
+    };
+    for (const char *const *bad = bad_strings; *bad != NULL; ++bad)
+      {
+       char *members[]
+         = {(char *) "first", (char *) *bad, (char *) "last", NULL};
+       if (strpbrk (*bad, ":\n") != NULL)
+         {
+           check ((struct group) {
+               .gr_name = (char *) *bad,
+             }, NULL);
+           check ((struct group) {
+               .gr_name = (char *) "root",
+               .gr_passwd = (char *) *bad,
+             }, NULL);
+         }
+       check ((struct group) {
+           .gr_name = (char *) "root",
+           .gr_passwd = (char *) "password",
+           .gr_mem = members,
+         }, NULL);
+      }
+  }
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
index 883e1c8a2af22407400598f47337d31b695199c6..c811041a55b8bd861125b334ff328a8e72782ad2 100644 (file)
@@ -26,7 +26,7 @@ headers               = gshadow.h
 routines       = getsgent getsgnam sgetsgent fgetsgent putsgent \
                  getsgent_r getsgnam_r sgetsgent_r fgetsgent_r
 
-tests = tst-gshadow
+tests = tst-gshadow tst-putsgent
 
 CFLAGS-getsgent_r.c = -fexceptions
 CFLAGS-getsgent.c = -fexceptions
index 0b2cad6eaafdf546b2136b2d4bd9b108591bfaef..c1cb92159562f27cc8c6213b083054c9fff39894 100644 (file)
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <errno.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <gshadow.h>
+#include <nss.h>
 
 #define _S(x)  x ? x : ""
 
@@ -29,6 +31,15 @@ putsgent (const struct sgrp *g, FILE *stream)
 {
   int errors = 0;
 
+  if (g->sg_namp == NULL || !__nss_valid_field (g->sg_namp)
+      || !__nss_valid_field (g->sg_passwd)
+      || !__nss_valid_list_field (g->sg_adm)
+      || !__nss_valid_list_field (g->sg_mem))
+    {
+      __set_errno (EINVAL);
+      return -1;
+    }
+
   _IO_flockfile (stream);
 
   if (fprintf (stream, "%s:%s:", g->sg_namp, _S (g->sg_passwd)) < 0)
diff --git a/gshadow/tst-putsgent.c b/gshadow/tst-putsgent.c
new file mode 100644 (file)
index 0000000..5b0f381
--- /dev/null
@@ -0,0 +1,168 @@
+/* Test for processing of invalid gshadow entries.  [BZ #18724]
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <gshadow.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool errors;
+
+static void
+check (struct sgrp e, const char *expected)
+{
+  char *buf;
+  size_t buf_size;
+  FILE *f = open_memstream (&buf, &buf_size);
+
+  if (f == NULL)
+    {
+      printf ("open_memstream: %m\n");
+      errors = true;
+      return;
+    }
+
+  int ret = putsgent (&e, f);
+
+  if (expected == NULL)
+    {
+      if (ret == -1)
+       {
+         if (errno != EINVAL)
+           {
+             printf ("putsgent: unexpected error code: %m\n");
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("putsgent: unexpected success (\"%s\")\n", e.sg_namp);
+         errors = true;
+       }
+    }
+  else
+    {
+      /* Expect success.  */
+      size_t expected_length = strlen (expected);
+      if (ret == 0)
+       {
+         long written = ftell (f);
+
+         if (written <= 0 || fflush (f) < 0)
+           {
+             printf ("stream error: %m\n");
+             errors = true;
+           }
+         else if (buf[written - 1] != '\n')
+           {
+             printf ("FAILED: \"%s\" without newline\n", expected);
+             errors = true;
+           }
+         else if (strncmp (buf, expected, written - 1) != 0
+                  || written - 1 != expected_length)
+           {
+             printf ("FAILED: \"%s\" (%ld), expected \"%s\" (%zu)\n",
+                     buf, written - 1, expected, expected_length);
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("FAILED: putsgent (expected \"%s\"): %m\n", expected);
+         errors = true;
+       }
+    }
+
+  fclose (f);
+  free (buf);
+}
+
+static int
+do_test (void)
+{
+  check ((struct sgrp) {
+      .sg_namp = (char *) "root",
+    },
+    "root:::");
+  check ((struct sgrp) {
+      .sg_namp = (char *) "root",
+      .sg_passwd = (char *) "password",
+    },
+    "root:password::");
+  check ((struct sgrp) {
+      .sg_namp = (char *) "root",
+      .sg_passwd = (char *) "password",
+      .sg_adm = (char *[]) {(char *) "adm1", (char *) "adm2", NULL},
+      .sg_mem = (char *[]) {(char *) "mem1", (char *) "mem2", NULL},
+    },
+    "root:password:adm1,adm2:mem1,mem2");
+
+  /* Bad values.  */
+  {
+    static const char *const bad_strings[] = {
+      ":",
+      "\n",
+      ":bad",
+      "\nbad",
+      "b:ad",
+      "b\nad",
+      "bad:",
+      "bad\n",
+      "b:a\nd",
+      ",",
+      "\n,",
+      ":,",
+      ",bad",
+      "b,ad",
+      "bad,",
+      NULL
+    };
+    for (const char *const *bad = bad_strings; *bad != NULL; ++bad)
+      {
+       char *members[]
+         = {(char *) "first", (char *) *bad, (char *) "last", NULL};
+       if (strpbrk (*bad, ":\n") != NULL)
+         {
+           check ((struct sgrp) {
+               .sg_namp = (char *) *bad,
+             }, NULL);
+           check ((struct sgrp) {
+               .sg_namp = (char *) "root",
+               .sg_passwd = (char *) *bad,
+             }, NULL);
+         }
+       check ((struct sgrp) {
+           .sg_namp = (char *) "root",
+           .sg_passwd = (char *) "password",
+           .sg_adm = members
+         }, NULL);
+       check ((struct sgrp) {
+           .sg_namp = (char *) "root",
+           .sg_passwd = (char *) "password",
+           .sg_mem = members
+         }, NULL);
+      }
+  }
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
index 0541335c18b6ea9356142c82dbf4e5546ed57f9e..1e8cc3910d3b318731f6808049181617aa0b3c72 100644 (file)
@@ -1 +1,14 @@
+#ifndef _NSS_H
 #include <nss/nss.h>
+
+#define NSS_INVALID_FIELD_CHARACTERS ":\n"
+extern const char __nss_invalid_field_characters[] attribute_hidden;
+
+_Bool __nss_valid_field (const char *value)
+  attribute_hidden internal_function;
+_Bool __nss_valid_list_field (char **list)
+  attribute_hidden internal_function;
+const char *__nss_rewrite_field (const char *value, char **to_be_freed)
+  attribute_hidden internal_function;
+
+#endif /* _NSS_H */
index bd7fecc16e8211d64430fe28913f8fec3e7cc88e..3b0f72540c4614a9f86445746b83e203080a9385 100644 (file)
@@ -24,7 +24,7 @@ extern int __fgetpwent_r (FILE * __stream, struct passwd *__resultbuf,
                          char *__buffer, size_t __buflen,
                          struct passwd **__result);
 
-#include <nss/nss.h>
+#include <nss.h>
 
 struct parser_data;
 extern int _nss_files_parse_pwent (char *line, struct passwd *result,
index 02a50160cb5fbd2662edc55661059037e243b9e1..bbbad85d7e35a098a7e6f2367faeb9ac7999ba6e 100644 (file)
@@ -26,6 +26,7 @@ headers                       := nss.h
 
 # This is the trivial part which goes into libc itself.
 routines               = nsswitch getnssent getnssent_r digits_dots \
+                         valid_field valid_list_field rewrite_field \
                          $(addsuffix -lookup,$(databases))
 
 # These are the databases that go through nss dispatch.
@@ -47,8 +48,10 @@ install-bin             := getent makedb
 makedb-modules = xmalloc hash-string
 extra-objs             += $(makedb-modules:=.o)
 
+tests-static            = tst-field
 tests                  = test-netdb tst-nss-test1 test-digits-dots \
-                         tst-nss-getpwent bug17079
+                         tst-nss-getpwent bug17079 \
+                         $(tests-static)
 xtests                 = bug-erange
 
 # Specify rules for the nss_* modules.  We have some services.
@@ -83,8 +86,7 @@ libnss_db-inhibit-o   = $(filter-out .os,$(object-suffixes))
 ifeq ($(build-static-nss),yes)
 routines                += $(libnss_files-routines)
 static-only-routines    += $(libnss_files-routines)
-tests-static           = tst-nss-static
-tests                  += $(tests-static)
+tests-static           += tst-nss-static
 endif
 
 include ../Rules
index 34df8487a9814f3b02b3dce3cf4897319070addc..996d378af775fbd60cf5affc43dd32da244dc350 100644 (file)
@@ -184,20 +184,8 @@ ethers_keys (int number, char *key[])
 static void
 print_group (struct group *grp)
 {
-  unsigned int i = 0;
-
-  printf ("%s:%s:%lu:", grp->gr_name ? grp->gr_name : "",
-         grp->gr_passwd ? grp->gr_passwd : "",
-         (unsigned long int) grp->gr_gid);
-
-  while (grp->gr_mem[i] != NULL)
-    {
-      fputs_unlocked (grp->gr_mem[i], stdout);
-      ++i;
-      if (grp->gr_mem[i] != NULL)
-       putchar_unlocked (',');
-    }
-  putchar_unlocked ('\n');
+  if (putgrent (grp, stdout) != 0)
+    fprintf (stderr, "error writing group entry: %m\n");
 }
 
 static int
@@ -241,32 +229,8 @@ group_keys (int number, char *key[])
 static void
 print_gshadow (struct sgrp *sg)
 {
-  unsigned int i = 0;
-
-  printf ("%s:%s:",
-         sg->sg_namp ? sg->sg_namp : "",
-         sg->sg_passwd ? sg->sg_passwd : "");
-
-  while (sg->sg_adm[i] != NULL)
-    {
-      fputs_unlocked (sg->sg_adm[i], stdout);
-      ++i;
-      if (sg->sg_adm[i] != NULL)
-       putchar_unlocked (',');
-    }
-
-  putchar_unlocked (':');
-
-  i = 0;
-  while (sg->sg_mem[i] != NULL)
-    {
-      fputs_unlocked (sg->sg_mem[i], stdout);
-      ++i;
-      if (sg->sg_mem[i] != NULL)
-       putchar_unlocked (',');
-    }
-
-  putchar_unlocked ('\n');
+  if (putsgent (sg, stdout) != 0)
+    fprintf (stderr, "error writing gshadow entry: %m\n");
 }
 
 static int
@@ -603,14 +567,8 @@ networks_keys (int number, char *key[])
 static void
 print_passwd (struct passwd *pwd)
 {
-  printf ("%s:%s:%lu:%lu:%s:%s:%s\n",
-         pwd->pw_name ? pwd->pw_name : "",
-         pwd->pw_passwd ? pwd->pw_passwd : "",
-         (unsigned long int) pwd->pw_uid,
-         (unsigned long int) pwd->pw_gid,
-         pwd->pw_gecos ? pwd->pw_gecos : "",
-         pwd->pw_dir ? pwd->pw_dir : "",
-         pwd->pw_shell ? pwd->pw_shell : "");
+  if (putpwent (pwd, stdout) != 0)
+    fprintf (stderr, "error writing passwd entry: %m\n");
 }
 
 static int
@@ -812,26 +770,8 @@ services_keys (int number, char *key[])
 static void
 print_shadow (struct spwd *sp)
 {
-  printf ("%s:%s:",
-         sp->sp_namp ? sp->sp_namp : "",
-         sp->sp_pwdp ? sp->sp_pwdp : "");
-
-#define SHADOW_FIELD(n) \
-  if (sp->n == -1)                                                           \
-    putchar_unlocked (':');                                                  \
-  else                                                                       \
-    printf ("%ld:", sp->n)
-
-  SHADOW_FIELD (sp_lstchg);
-  SHADOW_FIELD (sp_min);
-  SHADOW_FIELD (sp_max);
-  SHADOW_FIELD (sp_warn);
-  SHADOW_FIELD (sp_inact);
-  SHADOW_FIELD (sp_expire);
-  if (sp->sp_flag == ~0ul)
-    putchar_unlocked ('\n');
-  else
-    printf ("%lu\n", sp->sp_flag);
+  if (putspent (sp, stdout) != 0)
+    fprintf (stderr, "error writing shadow entry: %m\n");
 }
 
 static int
diff --git a/nss/rewrite_field.c b/nss/rewrite_field.c
new file mode 100644 (file)
index 0000000..fb9d274
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <nss.h>
+#include <string.h>
+
+/* Rewrite VALUE to a valid field value in the NSS database.  Invalid
+   characters are replaced with a single space character ' '.  If
+   VALUE is NULL, the empty string is returned.  *TO_BE_FREED is
+   overwritten with a pointer the caller has to free if the function
+   returns successfully.  On failure, return NULL.  */
+const char *
+__nss_rewrite_field (const char *value, char **to_be_freed)
+{
+  *to_be_freed = NULL;
+  if (value == NULL)
+    return "";
+
+  /* Search for non-allowed characters.  */
+  const char *p = strpbrk (value, __nss_invalid_field_characters);
+  if (p == NULL)
+    return value;
+  *to_be_freed = __strdup (value);
+  if (*to_be_freed == NULL)
+    return NULL;
+
+  /* Switch pointer to freshly-allocated buffer.  */
+  char *bad = *to_be_freed + (p - value);
+  do
+    {
+      *bad = ' ';
+      bad = strpbrk (bad + 1, __nss_invalid_field_characters);
+    }
+  while (bad != NULL);
+
+  return *to_be_freed;
+}
diff --git a/nss/tst-field.c b/nss/tst-field.c
new file mode 100644 (file)
index 0000000..f97d3ae
--- /dev/null
@@ -0,0 +1,101 @@
+/* Test for invalid field handling in file-style NSS databases.  [BZ #18724]
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* This test needs to be statically linked because it access hidden
+   functions.  */
+
+#include <nss.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool errors;
+
+static void
+check (const char *what, bool expr)
+{
+  if (!expr)
+    {
+      printf ("FAIL: %s\n", what);
+      errors = true;
+    }
+}
+
+#define CHECK(expr) check (#expr, (expr))
+
+static void
+check_rewrite (const char *input, const char *expected)
+{
+  char *to_free;
+  const char *result = __nss_rewrite_field (input, &to_free);
+  CHECK (result != NULL);
+  if (result != NULL && strcmp (result, expected) != 0)
+    {
+      printf ("FAIL: rewrite \"%s\" -> \"%s\", expected \"%s\"\n",
+             input, result, expected);
+      errors = true;
+    }
+  free (to_free);
+}
+
+static int
+do_test (void)
+{
+  CHECK (__nss_valid_field (NULL));
+  CHECK (__nss_valid_field (""));
+  CHECK (__nss_valid_field ("+"));
+  CHECK (__nss_valid_field ("-"));
+  CHECK (__nss_valid_field (" "));
+  CHECK (__nss_valid_field ("abcdef"));
+  CHECK (__nss_valid_field ("abc def"));
+  CHECK (__nss_valid_field ("abc\tdef"));
+  CHECK (!__nss_valid_field ("abcdef:"));
+  CHECK (!__nss_valid_field ("abcde:f"));
+  CHECK (!__nss_valid_field (":abcdef"));
+  CHECK (!__nss_valid_field ("abcdef\n"));
+  CHECK (!__nss_valid_field ("\nabcdef"));
+  CHECK (!__nss_valid_field (":"));
+  CHECK (!__nss_valid_field ("\n"));
+
+  CHECK (__nss_valid_list_field (NULL));
+  CHECK (__nss_valid_list_field ((char *[]) {(char *) "good", NULL}));
+  CHECK (!__nss_valid_list_field ((char *[]) {(char *) "g,ood", NULL}));
+  CHECK (!__nss_valid_list_field ((char *[]) {(char *) "g\nood", NULL}));
+  CHECK (!__nss_valid_list_field ((char *[]) {(char *) "g:ood", NULL}));
+
+  check_rewrite (NULL, "");
+  check_rewrite ("", "");
+  check_rewrite ("abc", "abc");
+  check_rewrite ("abc\n", "abc ");
+  check_rewrite ("abc:", "abc ");
+  check_rewrite ("\nabc", " abc");
+  check_rewrite (":abc", " abc");
+  check_rewrite (":", " ");
+  check_rewrite ("\n", " ");
+  check_rewrite ("a:b:c", "a b c");
+  check_rewrite ("a\nb\nc", "a b c");
+  check_rewrite ("a\nb:c", "a b c");
+  check_rewrite ("aa\nbb\ncc", "aa bb cc");
+  check_rewrite ("aa\nbb:cc", "aa bb cc");
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
diff --git a/nss/valid_field.c b/nss/valid_field.c
new file mode 100644 (file)
index 0000000..5fcddc5
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <nss.h>
+#include <string.h>
+
+const char __nss_invalid_field_characters[] = NSS_INVALID_FIELD_CHARACTERS;
+
+/* Check that VALUE is either NULL or a NUL-terminated string which
+   does not contain characters not permitted in NSS database
+   fields.  */
+_Bool
+__nss_valid_field (const char *value)
+{
+  return value == NULL
+    || strpbrk (value, __nss_invalid_field_characters) == NULL;
+}
diff --git a/nss/valid_list_field.c b/nss/valid_list_field.c
new file mode 100644 (file)
index 0000000..98ab93b
--- /dev/null
@@ -0,0 +1,35 @@
+/* Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <nss.h>
+#include <stdbool.h>
+#include <string.h>
+
+static const char invalid_characters[] = NSS_INVALID_FIELD_CHARACTERS ",";
+
+/* Check that all list members match the field syntax requirements and
+   do not contain the character ','.  */
+_Bool
+__nss_valid_list_field (char **list)
+{
+  if (list == NULL)
+    return true;
+  for (; *list != NULL; ++list)
+    if (strpbrk (*list, invalid_characters) != NULL)
+      return false;
+  return true;
+}
index 7f6de03b57cb53bbded865cb49fa8fe86aee0a8c..af0973455ba043bf72ea0907948ac403d9c4f3f2 100644 (file)
@@ -28,7 +28,7 @@ routines := fgetpwent getpw putpwent \
            getpwent getpwnam getpwuid \
            getpwent_r getpwnam_r getpwuid_r fgetpwent_r
 
-tests := tst-getpw
+tests := tst-getpw tst-putpwent
 
 include ../Rules
 
index 8be58c27ae27464d34d2d66ca3c4cae5369d04de..16b9c6f80f437b8926dcb29bee645eccddbd77fd 100644 (file)
 #include <errno.h>
 #include <stdio.h>
 #include <pwd.h>
+#include <nss.h>
 
 #define _S(x)  x ?: ""
 
-/* Write an entry to the given stream.
-   This must know the format of the password file.  */
+/* Write an entry to the given stream.  This must know the format of
+   the password file.  If the input contains invalid characters,
+   return EINVAL, or replace them with spaces (if they are contained
+   in the GECOS field).  */
 int
-putpwent (p, stream)
-     const struct passwd *p;
-     FILE *stream;
+putpwent (const struct passwd *p, FILE *stream)
 {
-  if (p == NULL || stream == NULL)
+  if (p == NULL || stream == NULL
+      || p->pw_name == NULL || !__nss_valid_field (p->pw_name)
+      || !__nss_valid_field (p->pw_passwd)
+      || !__nss_valid_field (p->pw_dir)
+      || !__nss_valid_field (p->pw_shell))
     {
       __set_errno (EINVAL);
       return -1;
     }
 
+  int ret;
+  char *gecos_alloc;
+  const char *gecos = __nss_rewrite_field (p->pw_gecos, &gecos_alloc);
+
+  if (gecos == NULL)
+    return -1;
+
   if (p->pw_name[0] == '+' || p->pw_name[0] == '-')
-    {
-      if (fprintf (stream, "%s:%s:::%s:%s:%s\n",
-                  p->pw_name, _S (p->pw_passwd),
-                  _S (p->pw_gecos), _S (p->pw_dir), _S (p->pw_shell)) < 0)
-       return -1;
-    }
+      ret = fprintf (stream, "%s:%s:::%s:%s:%s\n",
+                    p->pw_name, _S (p->pw_passwd),
+                    gecos, _S (p->pw_dir), _S (p->pw_shell));
   else
-    {
-      if (fprintf (stream, "%s:%s:%lu:%lu:%s:%s:%s\n",
-                  p->pw_name, _S (p->pw_passwd),
-                  (unsigned long int) p->pw_uid,
-                  (unsigned long int) p->pw_gid,
-                  _S (p->pw_gecos), _S (p->pw_dir), _S (p->pw_shell)) < 0)
-       return -1;
-    }
-  return 0;
+      ret = fprintf (stream, "%s:%s:%lu:%lu:%s:%s:%s\n",
+                    p->pw_name, _S (p->pw_passwd),
+                    (unsigned long int) p->pw_uid,
+                    (unsigned long int) p->pw_gid,
+                    gecos, _S (p->pw_dir), _S (p->pw_shell));
+
+  free (gecos_alloc);
+  if (ret >= 0)
+    ret = 0;
+  return ret;
 }
diff --git a/pwd/tst-putpwent.c b/pwd/tst-putpwent.c
new file mode 100644 (file)
index 0000000..a408e43
--- /dev/null
@@ -0,0 +1,168 @@
+/* Test for processing of invalid passwd entries.  [BZ #18724]
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static bool errors;
+
+static void
+check (struct passwd p, const char *expected)
+{
+  char *buf;
+  size_t buf_size;
+  FILE *f = open_memstream (&buf, &buf_size);
+
+  if (f == NULL)
+    {
+      printf ("open_memstream: %m\n");
+      errors = true;
+      return;
+    }
+
+  int ret = putpwent (&p, f);
+
+  if (expected == NULL)
+    {
+      if (ret == -1)
+       {
+         if (errno != EINVAL)
+           {
+             printf ("putpwent: unexpected error code: %m\n");
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("putpwent: unexpected success (\"%s\")\n", p.pw_name);
+         errors = true;
+       }
+    }
+  else
+    {
+      /* Expect success.  */
+      size_t expected_length = strlen (expected);
+      if (ret == 0)
+       {
+         long written = ftell (f);
+
+         if (written <= 0 || fflush (f) < 0)
+           {
+             printf ("stream error: %m\n");
+             errors = true;
+           }
+         else if (buf[written - 1] != '\n')
+           {
+             printf ("FAILED: \"%s\" without newline\n", expected);
+             errors = true;
+           }
+         else if (strncmp (buf, expected, written - 1) != 0
+                  || written - 1 != expected_length)
+           {
+             printf ("FAILED: \"%s\" (%ld), expected \"%s\" (%zu)\n",
+                     buf, written - 1, expected, expected_length);
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("FAILED: putpwent (expected \"%s\"): %m\n", expected);
+         errors = true;
+       }
+    }
+
+  fclose (f);
+  free (buf);
+}
+
+static int
+do_test (void)
+{
+  check ((struct passwd) {
+      .pw_name = (char *) "root",
+    },
+    "root::0:0:::");
+  check ((struct passwd) {
+      .pw_name = (char *) "root",
+      .pw_passwd = (char *) "password",
+    },
+    "root:password:0:0:::");
+  check ((struct passwd) {
+      .pw_name = (char *) "root",
+      .pw_passwd = (char *) "password",
+      .pw_uid = 12,
+      .pw_gid = 34,
+      .pw_gecos = (char *) "gecos",
+      .pw_dir = (char *) "home",
+      .pw_shell = (char *) "shell",
+    },
+    "root:password:12:34:gecos:home:shell");
+  check ((struct passwd) {
+      .pw_name = (char *) "root",
+      .pw_passwd = (char *) "password",
+      .pw_uid = 12,
+      .pw_gid = 34,
+      .pw_gecos = (char *) ":ge\n:cos\n",
+      .pw_dir = (char *) "home",
+      .pw_shell = (char *) "shell",
+    },
+    "root:password:12:34: ge  cos :home:shell");
+
+  /* Bad values.  */
+  {
+    static const char *const bad_strings[] = {
+      ":",
+      "\n",
+      ":bad",
+      "\nbad",
+      "b:ad",
+      "b\nad",
+      "bad:",
+      "bad\n",
+      "b:a\nd",
+      NULL
+    };
+    for (const char *const *bad = bad_strings; *bad != NULL; ++bad)
+      {
+       check ((struct passwd) {
+           .pw_name = (char *) *bad,
+         }, NULL);
+       check ((struct passwd) {
+           .pw_name = (char *) "root",
+           .pw_passwd = (char *) *bad,
+         }, NULL);
+       check ((struct passwd) {
+           .pw_name = (char *) "root",
+           .pw_dir = (char *) *bad,
+         }, NULL);
+       check ((struct passwd) {
+           .pw_name = (char *) "root",
+           .pw_shell = (char *) *bad,
+         }, NULL);
+      }
+  }
+
+  return errors > 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
index 8291c61622c86d6e2d032d16fbcf17a8f5a19ad4..6990f339f762542e1c94661d325fa6583eac8759 100644 (file)
@@ -27,7 +27,7 @@ routines      = getspent getspnam sgetspent fgetspent putspent \
                  getspent_r getspnam_r sgetspent_r fgetspent_r \
                  lckpwdf
 
-tests = tst-shadow
+tests = tst-shadow tst-putspent
 
 CFLAGS-getspent_r.c = -fexceptions
 CFLAGS-getspent.c = -fexceptions
index 142e697e6479703a3456b09d9a98c5369b9697b8..ba8230a482c58f7c170b3503d8d84ba52db3e5cb 100644 (file)
@@ -15,6 +15,8 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <errno.h>
+#include <nss.h>
 #include <stdio.h>
 #include <shadow.h>
 
@@ -31,6 +33,13 @@ putspent (const struct spwd *p, FILE *stream)
 {
   int errors = 0;
 
+  if (p->sp_namp == NULL || !__nss_valid_field (p->sp_namp)
+      || !__nss_valid_field (p->sp_pwdp))
+    {
+      __set_errno (EINVAL);
+      return -1;
+    }
+
   flockfile (stream);
 
   if (fprintf (stream, "%s:%s:", p->sp_namp, _S (p->sp_pwdp)) < 0)
diff --git a/shadow/tst-putspent.c b/shadow/tst-putspent.c
new file mode 100644 (file)
index 0000000..d00c022
--- /dev/null
@@ -0,0 +1,164 @@
+/* Test for processing of invalid shadow entries.  [BZ #18724]
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <shadow.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool errors;
+
+static void
+check (struct spwd p, const char *expected)
+{
+  char *buf;
+  size_t buf_size;
+  FILE *f = open_memstream (&buf, &buf_size);
+
+  if (f == NULL)
+    {
+      printf ("open_memstream: %m\n");
+      errors = true;
+      return;
+    }
+
+  int ret = putspent (&p, f);
+
+  if (expected == NULL)
+    {
+      if (ret == -1)
+       {
+         if (errno != EINVAL)
+           {
+             printf ("putspent: unexpected error code: %m\n");
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("putspent: unexpected success (\"%s\")\n", p.sp_namp);
+         errors = true;
+       }
+    }
+  else
+    {
+      /* Expect success.  */
+      size_t expected_length = strlen (expected);
+      if (ret == 0)
+       {
+         long written = ftell (f);
+
+         if (written <= 0 || fflush (f) < 0)
+           {
+             printf ("stream error: %m\n");
+             errors = true;
+           }
+         else if (buf[written - 1] != '\n')
+           {
+             printf ("FAILED: \"%s\" without newline\n", expected);
+             errors = true;
+           }
+         else if (strncmp (buf, expected, written - 1) != 0
+                  || written - 1 != expected_length)
+           {
+             printf ("FAILED: \"%s\" (%ld), expected \"%s\" (%zu)\n",
+                     buf, written - 1, expected, expected_length);
+             errors = true;
+           }
+       }
+      else
+       {
+         printf ("FAILED: putspent (expected \"%s\"): %m\n", expected);
+         errors = true;
+       }
+    }
+
+  fclose (f);
+  free (buf);
+}
+
+static int
+do_test (void)
+{
+  check ((struct spwd) {
+      .sp_namp = (char *) "root",
+    },
+    "root::0:0:0:0:0:0:0");
+  check ((struct spwd) {
+      .sp_namp = (char *) "root",
+      .sp_pwdp = (char *) "password",
+    },
+    "root:password:0:0:0:0:0:0:0");
+  check ((struct spwd) {
+      .sp_namp = (char *) "root",
+      .sp_pwdp = (char *) "password",
+      .sp_lstchg = -1,
+      .sp_min = -1,
+      .sp_max = -1,
+      .sp_warn = -1,
+      .sp_inact = -1,
+      .sp_expire = -1,
+      .sp_flag = -1
+    },
+    "root:password:::::::");
+  check ((struct spwd) {
+      .sp_namp = (char *) "root",
+      .sp_pwdp = (char *) "password",
+      .sp_lstchg = 1,
+      .sp_min = 2,
+      .sp_max = 3,
+      .sp_warn = 4,
+      .sp_inact = 5,
+      .sp_expire = 6,
+      .sp_flag = 7
+    },
+    "root:password:1:2:3:4:5:6:7");
+
+  /* Bad values.  */
+  {
+    static const char *const bad_strings[] = {
+      ":",
+      "\n",
+      ":bad",
+      "\nbad",
+      "b:ad",
+      "b\nad",
+      "bad:",
+      "bad\n",
+      "b:a\nd",
+      NULL
+    };
+    for (const char *const *bad = bad_strings; *bad != NULL; ++bad)
+      {
+       check ((struct spwd) {
+           .sp_namp = (char *) *bad,
+         }, NULL);
+       check ((struct spwd) {
+           .sp_namp = (char *) "root",
+           .sp_pwdp = (char *) *bad,
+         }, NULL);
+      }
+  }
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"