From 51e551d3837990b7c4491253a88bedd2513fe1de Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 7 Dec 2010 14:37:50 -0500 Subject: Detect and handle NULL returns from (gm/local)time_r These functions can return NULL for otherwise-valid values of time_t. Notably, the glibc gmtime manpage says it can return NULL if the year if greater than INT_MAX, and the windows MSDN gmtime page says it can return NULL for negative time_t values. Also, our formatting code is not guaranteed to correctly handle years after 9999 CE. This patch tries to correct this by detecting NULL values from gmtime/localtime_r, and trying to clip them to a reasonable end of the scale. If they are in the middle of the scale, we call it a downright error. Arguably, it's a bug to get out-of-bounds dates like this to begin with. But we've had bugs of this kind in the past, and warning when we see a bug is much kinder than doing a NULL-pointer dereference. Boboper found this one too. --- changes/gmtime_null | 6 +++ src/common/compat.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++------ src/common/compat.h | 9 ----- 3 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 changes/gmtime_null diff --git a/changes/gmtime_null b/changes/gmtime_null new file mode 100644 index 000000000..16a25408b --- /dev/null +++ b/changes/gmtime_null @@ -0,0 +1,6 @@ + o Minor bugfixes + - On some platforms, gmtime and localtime can return NULL under + certain circumstances even for well-defined values of time_t. + Try to detect and make up for this deficiency. Possible fix for + bug 2077. Bugfix on all versions of Tor. Found by boboper. + diff --git a/src/common/compat.c b/src/common/compat.c index 27489e568..9b7c0b782 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -1996,14 +1996,88 @@ tor_gettimeofday(struct timeval *timeval) #define TIME_FNS_NEED_LOCKS #endif +static struct tm * +correct_tm(int islocal, const time_t *timep, struct tm *resultbuf, + struct tm *r) +{ + const char *outcome; + + if (PREDICT_LIKELY(r)) { + if (r->tm_year > 8099) { /* We can't strftime dates after 9999 CE. */ + r->tm_year = 8099; + r->tm_mon = 11; + r->tm_mday = 31; + r->tm_yday = 365; + r->tm_hour = 23; + r->tm_min = 59; + r->tm_sec = 59; + } + return r; + } + + /* If we get here, gmtime or localtime returned NULL. It might have done + * this because of overrun or underrun, or it might have done it because of + * some other weird issue. */ + if (timep) { + if (*timep < 0) { + r = resultbuf; + r->tm_year = 70; /* 1970 CE */ + r->tm_mon = 0; + r->tm_mday = 1; + r->tm_yday = 1; + r->tm_hour = 0; + r->tm_min = 0 ; + r->tm_sec = 0; + outcome = "Rounding up to 1970"; + goto done; + } else if (*timep >= INT32_MAX) { + /* Rounding down to INT32_MAX isn't so great, but keep in mind that we + * only do it if gmtime/localtime tells us NULL. */ + r = resultbuf; + r->tm_year = 137; /* 2037 CE */ + r->tm_mon = 11; + r->tm_mday = 31; + r->tm_yday = 365; + r->tm_hour = 23; + r->tm_min = 59; + r->tm_sec = 59; + outcome = "Rounding down to 2037"; + goto done; + } + } + + /* If we get here, then gmtime/localtime failed without getting an extreme + * value for *timep */ + + tor_fragile_assert(); + r = resultbuf; + memset(resultbuf, 0, sizeof(struct tm)); + outcome="can't recover"; + done: + log_warn(LD_BUG, "%s("I64_FORMAT") failed with error %s: %s", + islocal?"localtime":"gmtime", + timep?I64_PRINTF_ARG(*timep):0, + strerror(errno), + outcome); + return r; +} + + /** @{ */ /** As localtime_r, but defined for platforms that don't have it: * * Convert *timep to a struct tm in local time, and store the value in * *result. Return the result on success, or NULL on failure. */ -#ifndef HAVE_LOCALTIME_R -#ifdef TIME_FNS_NEED_LOCKS +#ifdef HAVE_LOCALTIME_R +struct tm * +tor_localtime_r(const time_t *timep, struct tm *result) +{ + struct tm *r; + r = localtime_r(timep, result); + return correct_tm(1, timep, result, r); +} +#elif defined(TIME_FNS_NEED_LOCKS) struct tm * tor_localtime_r(const time_t *timep, struct tm *result) { @@ -2013,9 +2087,10 @@ tor_localtime_r(const time_t *timep, struct tm *result) tor_assert(result); tor_mutex_acquire(m); r = localtime(timep); - memcpy(result, r, sizeof(struct tm)); + if (r) + memcpy(result, r, sizeof(struct tm)); tor_mutex_release(m); - return result; + return correct_tm(1, timep, result, r); } #else struct tm * @@ -2024,11 +2099,11 @@ tor_localtime_r(const time_t *timep, struct tm *result) struct tm *r; tor_assert(result); r = localtime(timep); - memcpy(result, r, sizeof(struct tm)); - return result; + if (r) + memcpy(result, r, sizeof(struct tm)); + return correct_tm(1, timep, result, r); } #endif -#endif /** @} */ /** @{ */ @@ -2038,7 +2113,14 @@ tor_localtime_r(const time_t *timep, struct tm *result) * *result. Return the result on success, or NULL on failure. */ #ifndef HAVE_GMTIME_R -#ifdef TIME_FNS_NEED_LOCKS +struct tm * +tor_gmtime_r(const time_t *timep, struct tm *result) +{ + struct tm *r; + r = gmtime_r(timep, result); + return correct_tm(0, timep, result, r); +} +#elif defined(TIME_FNS_NEED_LOCKS) struct tm * tor_gmtime_r(const time_t *timep, struct tm *result) { @@ -2050,7 +2132,7 @@ tor_gmtime_r(const time_t *timep, struct tm *result) r = gmtime(timep); memcpy(result, r, sizeof(struct tm)); tor_mutex_release(m); - return result; + return correct_tm(0, timep, result, r); } #else struct tm * @@ -2060,11 +2142,9 @@ tor_gmtime_r(const time_t *timep, struct tm *result) tor_assert(result); r = gmtime(timep); memcpy(result, r, sizeof(struct tm)); - return result; + return correct_tm(0, timep, result, r); } #endif -#endif -/** @} */ #if defined(USE_WIN32_THREADS) void diff --git a/src/common/compat.h b/src/common/compat.h index 550c08b53..af795ffba 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -321,17 +321,8 @@ struct timeval { void tor_gettimeofday(struct timeval *timeval); -#ifdef HAVE_LOCALTIME_R -#define tor_localtime_r localtime_r -#else struct tm *tor_localtime_r(const time_t *timep, struct tm *result); -#endif - -#ifdef HAVE_GMTIME_R -#define tor_gmtime_r gmtime_r -#else struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); -#endif #ifndef timeradd /** Replacement for timeradd on platforms that do not have it: sets tvout to -- cgit v1.2.3 From 2dc9546eef6d748245d90b288f28ace1aa9b6f14 Mon Sep 17 00:00:00 2001 From: John Brooks Date: Mon, 3 Jan 2011 21:36:09 -0700 Subject: Correct the logic from f14754fbd for tor_gmtime_r --- src/common/compat.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/compat.c b/src/common/compat.c index 9b7c0b782..3644bd999 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -2112,7 +2112,7 @@ tor_localtime_r(const time_t *timep, struct tm *result) * Convert *timep to a struct tm in UTC, and store the value in * *result. Return the result on success, or NULL on failure. */ -#ifndef HAVE_GMTIME_R +#ifdef HAVE_GMTIME_R struct tm * tor_gmtime_r(const time_t *timep, struct tm *result) { @@ -2130,7 +2130,8 @@ tor_gmtime_r(const time_t *timep, struct tm *result) tor_assert(result); tor_mutex_acquire(m); r = gmtime(timep); - memcpy(result, r, sizeof(struct tm)); + if (r) + memcpy(result, r, sizeof(struct tm)); tor_mutex_release(m); return correct_tm(0, timep, result, r); } @@ -2141,7 +2142,8 @@ tor_gmtime_r(const time_t *timep, struct tm *result) struct tm *r; tor_assert(result); r = gmtime(timep); - memcpy(result, r, sizeof(struct tm)); + if (r) + memcpy(result, r, sizeof(struct tm)); return correct_tm(0, timep, result, r); } #endif -- cgit v1.2.3