Harden putpwent, putgrent, putspent, putspent against injection [BZ #18724]

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 '+'.
This commit is contained in:
Florian Weimer 2015-10-02 11:34:13 +02:00
parent b0f81637d5
commit 676599b36a
22 changed files with 1018 additions and 109 deletions

View File

@ -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

10
NEWS
View File

@ -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.

View 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

View 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)
@ -27,13 +29,14 @@
/* 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? */

167
grp/tst-putgrent.c Normal file
View File

@ -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"

View 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

View File

@ -15,9 +15,11 @@
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)

168
gshadow/tst-putsgent.c Normal file
View File

@ -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"

View 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 */

View 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,

View 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

View 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

51
nss/rewrite_field.c Normal file
View File

@ -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;
}

101
nss/tst-field.c Normal file
View File

@ -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"

31
nss/valid_field.c Normal file
View File

@ -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;
}

35
nss/valid_list_field.c Normal file
View File

@ -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;
}

View 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

View File

@ -18,37 +18,47 @@
#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;
}

168
pwd/tst-putpwent.c Normal file
View File

@ -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"

View 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

View 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)

164
shadow/tst-putspent.c Normal file
View File

@ -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"