Sun Apr 21 08:56:49 2024 UTC (18d)
make: fix out-of-bounds read when evaluating :gmtime and :localtime

The function TryParseTime takes a pointer to a string, but the LazyBuf
returns a Substring, which is not guaranteed to be null-terminated or
delimited.  In TryParseTime, calling strtoul on the Substring read past
the end of the substring.

Noticed in the NetBSD build in libntp, where the :gmtime modifier is
used in two places with the same timestamp value, of which the first was
evaluated correctly and the second wasn't.

The bug was introduced in var.c 1.1050 from 2023-05-09, when the
argument of the :gmtime and :localtime modifiers was allowed to be an
expression instead of an integer constant.


(rillig)
diff -r1.1102 -r1.1103 src/usr.bin/make/var.c

cvs diff -r1.1102 -r1.1103 src/usr.bin/make/var.c (expand / switch to unified diff)

--- src/usr.bin/make/var.c 2024/04/20 10:18:55 1.1102
+++ src/usr.bin/make/var.c 2024/04/21 08:56:49 1.1103
@@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
1/* $NetBSD: var.c,v 1.1102 2024/04/20 10:18:55 rillig Exp $ */ 1/* $NetBSD: var.c,v 1.1103 2024/04/21 08:56:49 rillig Exp $ */
2 2
3/* 3/*
4 * Copyright (c) 1988, 1989, 1990, 1993 4 * Copyright (c) 1988, 1989, 1990, 1993
5 * The Regents of the University of California. All rights reserved. 5 * The Regents of the University of California. All rights reserved.
6 * 6 *
7 * This code is derived from software contributed to Berkeley by 7 * This code is derived from software contributed to Berkeley by
8 * Adam de Boor. 8 * Adam de Boor.
9 * 9 *
10 * Redistribution and use in source and binary forms, with or without 10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions 11 * modification, are permitted provided that the following conditions
12 * are met: 12 * are met:
13 * 1. Redistributions of source code must retain the above copyright 13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer. 14 * notice, this list of conditions and the following disclaimer.
@@ -127,27 +127,27 @@ @@ -127,27 +127,27 @@
127#include <sys/types.h> 127#include <sys/types.h>
128#include <regex.h> 128#include <regex.h>
129#include <errno.h> 129#include <errno.h>
130#include <inttypes.h> 130#include <inttypes.h>
131#include <limits.h> 131#include <limits.h>
132#include <time.h> 132#include <time.h>
133 133
134#include "make.h" 134#include "make.h"
135#include "dir.h" 135#include "dir.h"
136#include "job.h" 136#include "job.h"
137#include "metachar.h" 137#include "metachar.h"
138 138
139/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */ 139/* "@(#)var.c 8.3 (Berkeley) 3/19/94" */
140MAKE_RCSID("$NetBSD: var.c,v 1.1102 2024/04/20 10:18:55 rillig Exp $"); 140MAKE_RCSID("$NetBSD: var.c,v 1.1103 2024/04/21 08:56:49 rillig Exp $");
141 141
142/* 142/*
143 * Variables are defined using one of the VAR=value assignments. Their 143 * Variables are defined using one of the VAR=value assignments. Their
144 * value can be queried by expressions such as $V, ${VAR}, or with modifiers 144 * value can be queried by expressions such as $V, ${VAR}, or with modifiers
145 * such as ${VAR:S,from,to,g:Q}. 145 * such as ${VAR:S,from,to,g:Q}.
146 * 146 *
147 * There are 3 kinds of variables: scope variables, environment variables, 147 * There are 3 kinds of variables: scope variables, environment variables,
148 * undefined variables. 148 * undefined variables.
149 * 149 *
150 * Scope variables are stored in a GNode.scope. The only way to undefine 150 * Scope variables are stored in a GNode.scope. The only way to undefine
151 * a scope variable is using the .undef directive. In particular, it must 151 * a scope variable is using the .undef directive. In particular, it must
152 * not be possible to undefine a variable during the evaluation of an 152 * not be possible to undefine a variable during the evaluation of an
153 * expression, or Var.name might point nowhere. (There is another, 153 * expression, or Var.name might point nowhere. (There is another,
@@ -2505,50 +2505,69 @@ TryParseTime(const char **pp, time_t *ou @@ -2505,50 +2505,69 @@ TryParseTime(const char **pp, time_t *ou
2505 if (!ch_isdigit(**pp)) 2505 if (!ch_isdigit(**pp))
2506 return false; 2506 return false;
2507 2507
2508 errno = 0; 2508 errno = 0;
2509 n = strtoul(*pp, &end, 10); 2509 n = strtoul(*pp, &end, 10);
2510 if (n == ULONG_MAX && errno == ERANGE) 2510 if (n == ULONG_MAX && errno == ERANGE)
2511 return false; 2511 return false;
2512 2512
2513 *pp = end; 2513 *pp = end;
2514 *out_time = (time_t)n; /* ignore possible truncation for now */ 2514 *out_time = (time_t)n; /* ignore possible truncation for now */
2515 return true; 2515 return true;
2516} 2516}
2517 2517
 2518static bool
 2519Substring_ParseTime(Substring s, time_t *out_time)
 2520{
 2521 const char *p;
 2522 unsigned long n;
 2523
 2524 n = 0;
 2525 for (p = s.start; p != s.end && ch_isdigit(*p); p++) {
 2526 unsigned long next = 10 * n + ((unsigned)*p - '0');
 2527 if (next < n)
 2528 return false;
 2529 n = next;
 2530 }
 2531 if (p == s.start || p != s.end)
 2532 return false;
 2533
 2534 *out_time = (time_t)n; /* ignore possible truncation for now */
 2535 return true;
 2536}
 2537
2518/* :gmtime and :localtime */ 2538/* :gmtime and :localtime */
2519static ApplyModifierResult 2539static ApplyModifierResult
2520ApplyModifier_Time(const char **pp, ModChain *ch) 2540ApplyModifier_Time(const char **pp, ModChain *ch)
2521{ 2541{
2522 Expr *expr; 2542 Expr *expr;
2523 time_t t; 2543 time_t t;
2524 const char *args; 2544 const char *args;
2525 const char *mod = *pp; 2545 const char *mod = *pp;
2526 bool gmt = mod[0] == 'g'; 2546 bool gmt = mod[0] == 'g';
2527 2547
2528 if (!ModMatchEq(mod, gmt ? "gmtime" : "localtime", ch)) 2548 if (!ModMatchEq(mod, gmt ? "gmtime" : "localtime", ch))
2529 return AMR_UNKNOWN; 2549 return AMR_UNKNOWN;
2530 args = mod + (gmt ? 6 : 9); 2550 args = mod + (gmt ? 6 : 9);
2531 2551
2532 if (args[0] == '=') { 2552 if (args[0] == '=') {
2533 const char *p = args + 1; 2553 const char *p = args + 1;
2534 LazyBuf buf; 2554 LazyBuf buf;
2535 if (!ParseModifierPartSubst(&p, true, '\0', ch->expr->emode, 2555 if (!ParseModifierPartSubst(&p, true, '\0', ch->expr->emode,
2536 ch, &buf, NULL, NULL)) 2556 ch, &buf, NULL, NULL))
2537 return AMR_CLEANUP; 2557 return AMR_CLEANUP;
2538 if (ModChain_ShouldEval(ch)) { 2558 if (ModChain_ShouldEval(ch)) {
2539 Substring arg = LazyBuf_Get(&buf); 2559 Substring arg = LazyBuf_Get(&buf);
2540 const char *arg_p = arg.start; 2560 if (!Substring_ParseTime(arg, &t)) {
2541 if (!TryParseTime(&arg_p, &t) || arg_p != arg.end) { 
2542 Parse_Error(PARSE_FATAL, 2561 Parse_Error(PARSE_FATAL,
2543 "Invalid time value \"%.*s\"", 2562 "Invalid time value \"%.*s\"",
2544 (int)Substring_Length(arg), arg.start); 2563 (int)Substring_Length(arg), arg.start);
2545 LazyBuf_Done(&buf); 2564 LazyBuf_Done(&buf);
2546 return AMR_CLEANUP; 2565 return AMR_CLEANUP;
2547 } 2566 }
2548 } else 2567 } else
2549 t = 0; 2568 t = 0;
2550 LazyBuf_Done(&buf); 2569 LazyBuf_Done(&buf);
2551 *pp = p; 2570 *pp = p;
2552 } else { 2571 } else {
2553 t = 0; 2572 t = 0;
2554 *pp = args; 2573 *pp = args;