Improve date validation in HTTP headers

Check all date/time values passed to tor_timegm
and parse_rfc1123_time for validity, taking leap
years into account.
Improves HTTP header validation.

Avoid unlikely signed integer overflow in tor_timegm
on systems with 32-bit time_t.
Fixes bug 13476.
This commit is contained in:
teor 2014-10-20 02:40:27 +11:00
parent dd556fb1e6
commit 238b8eaa60
2 changed files with 58 additions and 18 deletions

View File

@ -2,3 +2,11 @@
- Set the correct day of year value when the system's localtime(_r) - Set the correct day of year value when the system's localtime(_r)
or gmtime(_r) functions fail to set struct tm. or gmtime(_r) functions fail to set struct tm.
Fixes bug 13476. Fixes bug 13476.
- Avoid unlikely signed integer overflow in tor_timegm on systems with
32-bit time_t.
Fixes bug 13476.
o Minor enhancements (validation):
- Check all date/time values passed to tor_timegm and parse_rfc1123_time
for validity, taking leap years into account.
Improves HTTP header validation.
Implemented with bug 13476.

View File

@ -1376,7 +1376,8 @@ n_leapdays(int y1, int y2)
--y2; --y2;
return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400); return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400);
} }
/** Number of days per month in non-leap year; used by tor_timegm. */ /** Number of days per month in non-leap year; used by tor_timegm and
* parse_rfc1123_time. */
static const int days_per_month[] = static const int days_per_month[] =
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
@ -1390,10 +1391,32 @@ tor_timegm(const struct tm *tm, time_t *time_out)
* It's way more brute-force than fiddling with tzset(). * It's way more brute-force than fiddling with tzset().
*/ */
time_t year, days, hours, minutes, seconds; time_t year, days, hours, minutes, seconds;
int i; int i, invalid_year, dpm;
/* avoid int overflow on addition */
if (tm->tm_year < INT32_MAX-1900) {
year = tm->tm_year + 1900; year = tm->tm_year + 1900;
if (year < 1970 || tm->tm_mon < 0 || tm->tm_mon > 11 || } else {
tm->tm_year >= INT32_MAX-1900) { /* clamp year */
year = INT32_MAX;
}
invalid_year = (year < 1970 || tm->tm_year >= INT32_MAX-1900);
if (tm->tm_mon >= 0 && tm->tm_mon <= 11) {
dpm = days_per_month[tm->tm_mon];
if (tm->tm_mon == 1 && !invalid_year && IS_LEAPYEAR(tm->tm_year)) {
dpm = 29;
}
} else {
/* invalid month - default to 0 days per month */
dpm = 0;
}
if (invalid_year ||
tm->tm_mon < 0 || tm->tm_mon > 11 ||
tm->tm_mday < 1 || tm->tm_mday > dpm ||
tm->tm_hour < 0 || tm->tm_hour > 23 ||
tm->tm_min < 0 || tm->tm_min > 59 ||
tm->tm_sec < 0 || tm->tm_sec > 60) {
log_warn(LD_BUG, "Out-of-range argument to tor_timegm"); log_warn(LD_BUG, "Out-of-range argument to tor_timegm");
return -1; return -1;
} }
@ -1457,8 +1480,9 @@ parse_rfc1123_time(const char *buf, time_t *t)
struct tm tm; struct tm tm;
char month[4]; char month[4];
char weekday[4]; char weekday[4];
int i, m; int i, m, invalid_year;
unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec; unsigned tm_mday, tm_year, tm_hour, tm_min, tm_sec;
unsigned dpm;
if (strlen(buf) != RFC1123_TIME_LEN) if (strlen(buf) != RFC1123_TIME_LEN)
return -1; return -1;
@ -1471,18 +1495,6 @@ parse_rfc1123_time(const char *buf, time_t *t)
tor_free(esc); tor_free(esc);
return -1; return -1;
} }
if (tm_mday < 1 || tm_mday > 31 || tm_hour > 23 || tm_min > 59 ||
tm_sec > 60 || tm_year >= INT32_MAX || tm_year < 1970) {
char *esc = esc_for_log(buf);
log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
tor_free(esc);
return -1;
}
tm.tm_mday = (int)tm_mday;
tm.tm_year = (int)tm_year;
tm.tm_hour = (int)tm_hour;
tm.tm_min = (int)tm_min;
tm.tm_sec = (int)tm_sec;
m = -1; m = -1;
for (i = 0; i < 12; ++i) { for (i = 0; i < 12; ++i) {
@ -1499,6 +1511,26 @@ parse_rfc1123_time(const char *buf, time_t *t)
} }
tm.tm_mon = m; tm.tm_mon = m;
invalid_year = (tm_year >= INT32_MAX || tm_year < 1970);
tor_assert(m >= 0 && m <= 11);
dpm = days_per_month[m];
if (m == 1 && !invalid_year && IS_LEAPYEAR(tm_year)) {
dpm = 29;
}
if (invalid_year || tm_mday < 1 || tm_mday > dpm ||
tm_hour > 23 || tm_min > 59 || tm_sec > 60) {
char *esc = esc_for_log(buf);
log_warn(LD_GENERAL, "Got invalid RFC1123 time %s", esc);
tor_free(esc);
return -1;
}
tm.tm_mday = (int)tm_mday;
tm.tm_year = (int)tm_year;
tm.tm_hour = (int)tm_hour;
tm.tm_min = (int)tm_min;
tm.tm_sec = (int)tm_sec;
if (tm.tm_year < 1970) { if (tm.tm_year < 1970) {
char *esc = esc_for_log(buf); char *esc = esc_for_log(buf);
log_warn(LD_GENERAL, log_warn(LD_GENERAL,