chage: Prevent signed integer overflows.

This is merely a stability fix, not a security fix.

As the root user, it is possible to set time values which later on
result in signed integer overflows.

For this to work, an sgetspent implementation must be used which
supports long values (glibc on amd64 only parses 32 bit, not 64).
Either use musl or simply call configure with following environment
variable:

$ ac_cv_func_sgetspent=no ./configure

Also it is recommended to compile with -fsanitize=undefined or
-ftrapv to see these issues easily.

Examples to trigger issues when calling "chage -l user":

$ chage -d 9223372036854775807 user

$ chage -d 106751991167300 user
$ chage -M 9999 user

$ chage -d 90000000000000 user
$ chage -I 90000000000000 user
$ chage -M 9999 user

$ chage -E 9223372036854775807 user

While at it, I fixed casting issues which could lead to signed integer
overflows on systems which still have a 32 bit time_t.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
This commit is contained in:
Tobias Stoeckmann 2020-07-12 17:56:38 +02:00
parent 607f1dd549
commit 83aa88466d

View File

@ -203,10 +203,10 @@ static int new_fields (void)
return 0; return 0;
} }
if (-1 == lstchgdate) { if (-1 == lstchgdate || lstchgdate > LONG_MAX / SCALE) {
strcpy (buf, "-1"); strcpy (buf, "-1");
} else { } else {
date_to_str (buf, sizeof buf, (time_t) lstchgdate * SCALE); date_to_str (buf, sizeof buf, (time_t) (lstchgdate * SCALE));
} }
change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)")); change_field (buf, sizeof buf, _("Last Password Change (YYYY-MM-DD)"));
@ -234,10 +234,10 @@ static int new_fields (void)
return 0; return 0;
} }
if (-1 == expdate) { if (-1 == expdate || LONG_MAX / SCALE < expdate) {
strcpy (buf, "-1"); strcpy (buf, "-1");
} else { } else {
date_to_str (buf, sizeof buf, (time_t) expdate * SCALE); date_to_str (buf, sizeof buf, (time_t) (expdate * SCALE));
} }
change_field (buf, sizeof buf, change_field (buf, sizeof buf,
@ -309,7 +309,7 @@ static void list_fields (void)
* was last modified. The date is the number of days since 1/1/1970. * was last modified. The date is the number of days since 1/1/1970.
*/ */
(void) fputs (_("Last password change\t\t\t\t\t: "), stdout); (void) fputs (_("Last password change\t\t\t\t\t: "), stdout);
if (lstchgdate < 0) { if (lstchgdate < 0 || lstchgdate > LONG_MAX / SCALE) {
(void) puts (_("never")); (void) puts (_("never"));
} else if (lstchgdate == 0) { } else if (lstchgdate == 0) {
(void) puts (_("password must be changed")); (void) puts (_("password must be changed"));
@ -327,7 +327,8 @@ static void list_fields (void)
(void) puts (_("password must be changed")); (void) puts (_("password must be changed"));
} else if ( (lstchgdate < 0) } else if ( (lstchgdate < 0)
|| (maxdays >= (10000 * (DAY / SCALE))) || (maxdays >= (10000 * (DAY / SCALE)))
|| (maxdays < 0)) { || (maxdays < 0)
|| ((LONG_MAX - changed) / SCALE < maxdays)) {
(void) puts (_("never")); (void) puts (_("never"));
} else { } else {
expires = changed + maxdays * SCALE; expires = changed + maxdays * SCALE;
@ -346,7 +347,9 @@ static void list_fields (void)
} else if ( (lstchgdate < 0) } else if ( (lstchgdate < 0)
|| (inactdays < 0) || (inactdays < 0)
|| (maxdays >= (10000 * (DAY / SCALE))) || (maxdays >= (10000 * (DAY / SCALE)))
|| (maxdays < 0)) { || (maxdays < 0)
|| (maxdays > LONG_MAX - inactdays)
|| ((LONG_MAX - changed) / SCALE < maxdays + inactdays)) {
(void) puts (_("never")); (void) puts (_("never"));
} else { } else {
expires = changed + (maxdays + inactdays) * SCALE; expires = changed + (maxdays + inactdays) * SCALE;
@ -358,7 +361,7 @@ static void list_fields (void)
* password expiring or not. * password expiring or not.
*/ */
(void) fputs (_("Account expires\t\t\t\t\t\t: "), stdout); (void) fputs (_("Account expires\t\t\t\t\t\t: "), stdout);
if (expdate < 0) { if (expdate < 0 || LONG_MAX / SCALE < expdate) {
(void) puts (_("never")); (void) puts (_("never"));
} else { } else {
expires = expdate * SCALE; expires = expdate * SCALE;