Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions parse_tz.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,26 @@ static int read_preamble(const unsigned char **tzf, timelib_tzinfo *tz, unsigned
}
}

static void read_32bit_header(const unsigned char **tzf, timelib_tzinfo *tz)
/* Each transition is tagged with a single-byte index (see
* skip_32bit_transitions()/skip_64bit_transitions() below) into the types
* array, so no valid TZif file can ever have more than 256 types: an index
* beyond that could never be encoded. This bound is exact, not a guess.
*/
#define TIMELIB_TZINFO_MAX_TYPECNT 256

/* timecnt/charcnt aren't format-bounded the way typecnt is, so these are
* generous-but-real limits instead of exact ones. A scan of the full IANA
* tzdata distribution (both the 32-bit and larger 64-bit header counts,
* covering every historical rule change since each zone's start) found a
* worst case of 310 transitions (Asia/Hebron) and 40 abbreviation bytes
* (America/Anchorage) — comfortably inside these limits with room for
* decades of future rule changes, while still rejecting a header that
* claims a wildly implausible count as corrupt or malicious input before
* it's used for allocation and copying sizes. */
#define TIMELIB_TZINFO_MAX_TIMECNT 10000
#define TIMELIB_TZINFO_MAX_CHARCNT 2000

static int read_32bit_header(const unsigned char **tzf, timelib_tzinfo *tz)
{
uint32_t buffer[6];

Expand All @@ -176,6 +195,16 @@ static void read_32bit_header(const unsigned char **tzf, timelib_tzinfo *tz)
tz->_bit32.charcnt = timelib_conv_int_unsigned(buffer[5]);

*tzf += sizeof(buffer);

if (
tz->_bit32.timecnt > TIMELIB_TZINFO_MAX_TIMECNT ||
tz->_bit32.typecnt > TIMELIB_TZINFO_MAX_TYPECNT ||
tz->_bit32.charcnt > TIMELIB_TZINFO_MAX_CHARCNT
) {
return TIMELIB_ERROR_CORRUPT_HEADER_COUNTS;
}

return 0;
}

static int detect_slim_file(timelib_tzinfo *tz)
Expand Down Expand Up @@ -637,7 +666,7 @@ static int skip_64bit_preamble(const unsigned char **tzf, timelib_tzinfo *tz)
}
}

static void read_64bit_header(const unsigned char **tzf, timelib_tzinfo *tz)
static int read_64bit_header(const unsigned char **tzf, timelib_tzinfo *tz)
{
uint32_t buffer[6];

Expand All @@ -649,6 +678,16 @@ static void read_64bit_header(const unsigned char **tzf, timelib_tzinfo *tz)
tz->bit64.typecnt = timelib_conv_int_unsigned(buffer[4]);
tz->bit64.charcnt = timelib_conv_int_unsigned(buffer[5]);
*tzf += sizeof(buffer);

if (
tz->bit64.timecnt > TIMELIB_TZINFO_MAX_TIMECNT ||
tz->bit64.typecnt > TIMELIB_TZINFO_MAX_TYPECNT ||
tz->bit64.charcnt > TIMELIB_TZINFO_MAX_CHARCNT
) {
return TIMELIB_ERROR_CORRUPT_HEADER_COUNTS;
}

return 0;
}

static timelib_tzinfo* timelib_tzinfo_ctor(const char *name)
Expand All @@ -665,7 +704,7 @@ timelib_tzinfo *timelib_parse_tzfile(const char *timezone, const timelib_tzdb *t
const unsigned char *tzf;
timelib_tzinfo *tmp;
int version;
int transitions_result, types_result;
int header32_result, header64_result, transitions_result, types_result;
unsigned int type = TIMELIB_TZINFO_ZONEINFO; /* TIMELIB_TZINFO_PHP or TIMELIB_TZINFO_ZONEINFO */

*error_code = TIMELIB_ERROR_NO_ERROR;
Expand All @@ -681,7 +720,11 @@ timelib_tzinfo *timelib_parse_tzfile(const char *timezone, const timelib_tzdb *t
}
//printf("- timezone: %s, version: %0d\n", timezone, version);

read_32bit_header(&tzf, tmp);
if ((header32_result = read_32bit_header(&tzf, tmp)) != 0) {
*error_code = header32_result;
timelib_tzinfo_dtor(tmp);
return NULL;
}
skip_32bit_transitions(&tzf, tmp);
skip_32bit_types(&tzf, tmp);

Expand All @@ -691,7 +734,11 @@ timelib_tzinfo *timelib_parse_tzfile(const char *timezone, const timelib_tzdb *t
timelib_tzinfo_dtor(tmp);
return NULL;
}
read_64bit_header(&tzf, tmp);
if ((header64_result = read_64bit_header(&tzf, tmp)) != 0) {
*error_code = header64_result;
timelib_tzinfo_dtor(tmp);
return NULL;
}
if ((transitions_result = read_64bit_transitions(&tzf, tmp)) != 0) {
/* Corrupt file as transitions do not increase */
*error_code = transitions_result;
Expand Down
5 changes: 3 additions & 2 deletions timelib.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

#define TIMELIB_LLABS(y) (y < 0 ? (y * -1) : y)

const char *timelib_error_messages[10] = {
const char *timelib_error_messages[11] = {
"No error",
"Cannot allocate buffer for parsing",
"Corrupt tzfile: The transitions in the file don't always increase",
Expand All @@ -45,7 +45,8 @@ const char *timelib_error_messages[10] = {
"No timezone with this name could be found",
"A 'slim' timezone file has been detected",
"The embedded POSIX string is not valid",
"The embedded POSIX string is empty"
"The embedded POSIX string is empty",
"Corrupt tzfile: The header contains an implausibly large count field"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the three different fields how have different limits, the error messages ought to be three separate ones too, one for each of the fields.

};

const char *timelib_get_error_message(int error_code)
Expand Down
1 change: 1 addition & 0 deletions timelib.h
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ typedef struct _timelib_tzdb {
#define TIMELIB_ERROR_SLIM_FILE 0x07 /* Warns if the file is SLIM, but we can't read it */
#define TIMELIB_ERROR_CORRUPT_POSIX_STRING 0x08
#define TIMELIB_ERROR_EMPTY_POSIX_STRING 0x09 /* Warns if the POSIX string is empty, but still produces results */
#define TIMELIB_ERROR_CORRUPT_HEADER_COUNTS 0x0A

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you add the two other error codes (and change the name for this one), can you add a comment for each of them too?


#ifdef __cplusplus
extern "C" {
Expand Down