Module Name:    src
Committed By:   riastradh
Date:           Sun Jun 26 22:31:38 UTC 2022

Modified Files:
        src/sys/kern: subr_time.c
        src/sys/sys: time.h

Log Message:
kern: New functions timespecaddok, timespecsubok.

Return false if timespecadd or timespecsub with the same arguments
would overflow (possibly in an intermediate calculation), true if OK.

Typical usage:

sys_wotsit(...)
{
        ...
        if (!timespecsubok(x, y))
                return EINVAL;
        timespecub(x, y, xydelta);
        ...
}


To generate a diff of this commit:
cvs rdiff -u -r1.32 -r1.33 src/sys/kern/subr_time.c
cvs rdiff -u -r1.79 -r1.80 src/sys/sys/time.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/kern/subr_time.c
diff -u src/sys/kern/subr_time.c:1.32 src/sys/kern/subr_time.c:1.33
--- src/sys/kern/subr_time.c:1.32	Sun Mar 13 17:52:45 2022
+++ src/sys/kern/subr_time.c	Sun Jun 26 22:31:38 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: subr_time.c,v 1.32 2022/03/13 17:52:45 riastradh Exp $	*/
+/*	$NetBSD: subr_time.c,v 1.33 2022/06/26 22:31:38 riastradh Exp $	*/
 
 /*
  * Copyright (c) 1982, 1986, 1989, 1993
@@ -33,7 +33,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: subr_time.c,v 1.32 2022/03/13 17:52:45 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: subr_time.c,v 1.33 2022/06/26 22:31:38 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/kernel.h>
@@ -364,3 +364,221 @@ ts2timo(clockid_t clock_id, int flags, s
 
 	return 0;
 }
+
+bool
+timespecaddok(const struct timespec *tsp, const struct timespec *usp)
+{
+	enum { TIME_MIN = __type_min(time_t), TIME_MAX = __type_max(time_t) };
+	time_t a = tsp->tv_sec;
+	time_t b = usp->tv_sec;
+	bool carry;
+
+	/*
+	 * Caller is responsible for guaranteeing valid timespec
+	 * inputs.  Any user-controlled inputs must be validated or
+	 * adjusted.
+	 */
+	KASSERT(tsp->tv_nsec >= 0);
+	KASSERT(usp->tv_nsec >= 0);
+	KASSERT(tsp->tv_nsec < 1000000000L);
+	KASSERT(usp->tv_nsec < 1000000000L);
+	CTASSERT(1000000000L <= __type_max(long) - 1000000000L);
+
+	/*
+	 * Fail if a + b + carry overflows TIME_MAX, or if a + b
+	 * overflows TIME_MIN because timespecadd adds the carry after
+	 * computing a + b.
+	 *
+	 * Break it into two mutually exclusive and exhaustive cases:
+	 * I. a >= 0
+	 * II. a < 0
+	 */
+	carry = (tsp->tv_nsec + usp->tv_nsec >= 1000000000L);
+	if (a >= 0) {
+		/*
+		 * Case I: a >= 0.  If b < 0, then b + 1 <= 0, so
+		 *
+		 *	a + b + 1 <= a + 0 <= TIME_MAX,
+		 *
+		 * and
+		 *
+		 *	a + b >= 0 + b = b >= TIME_MIN,
+		 *
+		 * so this can't overflow.
+		 *
+		 * If b >= 0, then a + b + carry >= a + b >= 0, so
+		 * negative results and thus results below TIME_MIN are
+		 * impossible; we need only avoid
+		 *
+		 *	a + b + carry > TIME_MAX,
+		 *
+		 * which we will do by rejecting if
+		 *
+		 *	b > TIME_MAX - a - carry,
+		 *
+		 * which in turn is incidentally always false if b < 0
+		 * so we don't need extra logic to discriminate on the
+		 * b >= 0 and b < 0 cases.
+		 *
+		 * Since 0 <= a <= TIME_MAX, we know
+		 *
+		 *	0 <= TIME_MAX - a <= TIME_MAX,
+		 *
+		 * and hence
+		 *
+		 *	-1 <= TIME_MAX - a - 1 < TIME_MAX.
+		 *
+		 * So we can compute TIME_MAX - a - carry (i.e., either
+		 * TIME_MAX - a or TIME_MAX - a - 1) safely without
+		 * overflow.
+		 */
+		if (b > TIME_MAX - a - carry)
+			return false;
+	} else {
+		/*
+		 * Case II: a < 0.  If b >= 0, then since a + 1 <= 0,
+		 * we have
+		 *
+		 *	a + b + 1 <= b <= TIME_MAX,
+		 *
+		 * and
+		 *
+		 *	a + b >= a >= TIME_MIN,
+		 *
+		 * so this can't overflow.
+		 *
+		 * If b < 0, then the intermediate a + b is negative
+		 * and the outcome a + b + 1 is nonpositive, so we need
+		 * only avoid
+		 *
+		 *	a + b < TIME_MIN,
+		 *
+		 * which we will do by rejecting if
+		 *
+		 *	a < TIME_MIN - b.
+		 *
+		 * (Reminder: The carry is added afterward in
+		 * timespecadd, so to avoid overflow it is not enough
+		 * to merely reject a + b + carry < TIME_MIN.)
+		 *
+		 * It is safe to compute the difference TIME_MIN - b
+		 * because b is negative, so the result lies in
+		 * (TIME_MIN, 0].
+		 */
+		if (b < 0 && a < TIME_MIN - b)
+			return false;
+	}
+
+	return true;
+}
+
+bool
+timespecsubok(const struct timespec *tsp, const struct timespec *usp)
+{
+	enum { TIME_MIN = __type_min(time_t), TIME_MAX = __type_max(time_t) };
+	time_t a = tsp->tv_sec, b = usp->tv_sec;
+	bool borrow;
+
+	/*
+	 * Caller is responsible for guaranteeing valid timespec
+	 * inputs.  Any user-controlled inputs must be validated or
+	 * adjusted.
+	 */
+	KASSERT(tsp->tv_nsec >= 0);
+	KASSERT(usp->tv_nsec >= 0);
+	KASSERT(tsp->tv_nsec < 1000000000L);
+	KASSERT(usp->tv_nsec < 1000000000L);
+	CTASSERT(1000000000L <= __type_max(long) - 1000000000L);
+
+	/*
+	 * Fail if a - b - borrow overflows TIME_MIN, or if a - b
+	 * overflows TIME_MAX because timespecsub subtracts the borrow
+	 * after computing a - b.
+	 *
+	 * Break it into two mutually exclusive and exhaustive cases:
+	 * I. a < 0
+	 * II. a >= 0
+	 */
+	borrow = (tsp->tv_nsec - usp->tv_nsec < 0);
+	if (a < 0) {
+		/*
+		 * Case I: a < 0.  If b < 0, then -b - 1 >= 0, so
+		 *
+		 *	a - b - 1 >= a + 0 >= TIME_MIN,
+		 *
+		 * and, since a <= -1, provided that TIME_MIN <=
+		 * -TIME_MAX - 1 so that TIME_MAX <= -TIME_MIN - 1 (in
+		 * fact, equality holds, under the assumption of
+		 * two's-complement arithmetic),
+		 *
+		 *	a - b <= -1 - b = -b - 1 <= TIME_MAX,
+		 *
+		 * so this can't overflow.
+		 */
+		CTASSERT(TIME_MIN <= -TIME_MAX - 1);
+
+		/*
+		 * If b >= 0, then a - b - borrow <= a - b < 0, so
+		 * positive results and thus results above TIME_MAX are
+		 * impossible; we need only avoid
+		 *
+		 *	a - b - borrow < TIME_MIN,
+		 *
+		 * which we will do by rejecting if
+		 *
+		 *	a < TIME_MIN + b + borrow.
+		 *
+		 * The right-hand side is safe to evaluate for any
+		 * values of b and borrow as long as TIME_MIN +
+		 * TIME_MAX + 1 <= TIME_MAX, i.e., TIME_MIN <= -1.
+		 * (Note: If time_t were unsigned, this would fail!)
+		 *
+		 * Note: Unlike Case I in timespecaddok, this criterion
+		 * does not work for b < 0, nor can the roles of a and
+		 * b in the inequality be reversed (e.g., -b < TIME_MIN
+		 * - a + borrow) without extra cases like checking for
+		 * b = TEST_MIN.
+		 */
+		CTASSERT(TIME_MIN < -1);
+		if (b >= 0 && a < TIME_MIN + b + borrow)
+			return false;
+	} else {
+		/*
+		 * Case II: a >= 0.  If b >= 0, then
+		 *
+		 *	a - b <= a <= TIME_MAX,
+		 *
+		 * and, provided TIME_MIN <= -TIME_MAX - 1 (in fact,
+		 * equality holds, under the assumption of
+		 * two's-complement arithmetic)
+		 *
+		 *	a - b - 1 >= -b - 1 >= -TIME_MAX - 1 >= TIME_MIN,
+		 *
+		 * so this can't overflow.
+		 */
+		CTASSERT(TIME_MIN <= -TIME_MAX - 1);
+
+		/*
+		 * If b < 0, then a - b >= a >= 0, so negative results
+		 * and thus results below TIME_MIN are impossible; we
+		 * need only avoid
+		 *
+		 *	a - b > TIME_MAX,
+		 *
+		 * which we will do by rejecting if
+		 *
+		 *	a > TIME_MAX + b.
+		 *
+		 * (Reminder: The borrow is subtracted afterward in
+		 * timespecsub, so to avoid overflow it is not enough
+		 * to merely reject a - b - borrow > TIME_MAX.)
+		 *
+		 * It is safe to compute the sum TIME_MAX + b because b
+		 * is negative, so the result lies in [0, TIME_MAX).
+		 */
+		if (b < 0 && a > TIME_MAX + b)
+			return false;
+	}
+
+	return true;
+}

Index: src/sys/sys/time.h
diff -u src/sys/sys/time.h:1.79 src/sys/sys/time.h:1.80
--- src/sys/sys/time.h:1.79	Tue Jan 17 15:28:34 2017
+++ src/sys/sys/time.h	Sun Jun 26 22:31:38 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: time.h,v 1.79 2017/01/17 15:28:34 maya Exp $	*/
+/*	$NetBSD: time.h,v 1.80 2022/06/26 22:31:38 riastradh Exp $	*/
 
 /*
  * Copyright (c) 1982, 1986, 1993
@@ -265,6 +265,12 @@ ns2bintime(uint64_t ns)
 		}							\
 	} while (/* CONSTCOND */ 0)
 #define timespec2ns(x) (((uint64_t)(x)->tv_sec) * 1000000000L + (x)->tv_nsec)
+
+#ifdef _KERNEL
+bool timespecaddok(const struct timespec *, const struct timespec *) __pure;
+bool timespecsubok(const struct timespec *, const struct timespec *) __pure;
+#endif
+
 #endif /* _NETBSD_SOURCE */
 
 /*

Reply via email to