Clamp time64 values passed to GDateTime functions to the valid range.

Fixes a bug reported on the mailing list wherein a date of 0000-00-00
in a MySql database would crash GnuCash. Such dates may come from a bad
conversion of 1970-01-01 or from a crash.
This commit is contained in:
John Ralls 2017-06-16 15:31:49 -07:00
parent 385ca0c324
commit 5f8f9b9a84
3 changed files with 52 additions and 48 deletions

View File

@ -73,6 +73,17 @@
# define GNC_T_FMT "%r"
#endif
/* t < MINTIME is probably from a bad conversion from t 0 to
* 0000-00-00, so restore it to the Unix Epoch. t anywhere near
* MAXTIME is obviously an error, but we don't want to crash with a
* bad date-time so just clamp it to MAXTIME.
*/
static inline time64
clamp_time(time64 t)
{
return t < MINTIME ? 0 : t > MAXTIME ? MAXTIME : t;
}
const char *gnc_default_strftime_date_format =
#ifdef G_OS_WIN32
/* The default date format for use with strftime in Win32. */
@ -141,7 +152,7 @@ GDateTime*
gnc_g_date_time_new_from_unix_local (time64 time)
{
GTimeZone *tz = gnc_g_time_zone_new_local ();
GDateTime *gdt = g_date_time_new_from_unix_utc (time);
GDateTime *gdt = g_date_time_new_from_unix_utc (clamp_time (time));
if (gdt)
gdt = gnc_g_date_time_adjust_for_dst (gdt, tz);
return gdt;
@ -249,7 +260,7 @@ struct tm*
gnc_localtime_r (const time64 *secs, struct tm* time)
{
guint index = 0;
GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs);
GDateTime *gdt = gnc_g_date_time_new_from_unix_local (clamp_time (*secs));
g_return_val_if_fail (gdt != NULL, NULL);
gnc_g_date_time_fill_struct_tm (gdt, time);
@ -271,7 +282,7 @@ struct tm*
gnc_gmtime (const time64 *secs)
{
struct tm *time;
GDateTime *gdt = g_date_time_new_from_unix_utc (*secs);
GDateTime *gdt = g_date_time_new_from_unix_utc (clamp_time (*secs));
g_return_val_if_fail (gdt != NULL, NULL);
time = g_slice_alloc0 (sizeof (struct tm));
gnc_g_date_time_fill_struct_tm (gdt, time);
@ -389,10 +400,10 @@ gnc_timegm (struct tm* time)
gchar*
gnc_ctime (const time64 *secs)
{
GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs);
gchar *string = g_date_time_format (gdt, "%a %b %e %H:%M:%S %Y");
g_date_time_unref (gdt);
return string;
GDateTime *gdt = gnc_g_date_time_new_from_unix_local (clamp_time (*secs));
gchar *string = g_date_time_format (gdt, "%a %b %e %H:%M:%S %Y");
g_date_time_unref (gdt);
return string;
}
time64
@ -887,7 +898,7 @@ size_t
qof_print_date_buff (char * buff, size_t len, time64 t)
{
struct tm theTime;
time64 bt = t;
time64 bt = clamp_time (t);
size_t actual;
if (!buff) return 0 ;
if (!gnc_localtime_r(&bt, &theTime))
@ -1635,7 +1646,7 @@ gnc_timezone (const struct tm *tm)
void
timespecFromTime64 ( Timespec *ts, time64 t )
{
ts->tv_sec = t;
ts->tv_sec = clamp_time (t);
ts->tv_nsec = 0;
}

View File

@ -70,7 +70,7 @@
#include <glib-object.h>
#include <time.h>
#include <stdint.h>
/**
* Many systems, including Microsoft Windows and BSD-derived Unixes
* like Darwin, are retaining the int-32 typedef for time_t. Since
@ -100,7 +100,8 @@ extern const char *gnc_default_strftime_date_format;
/** The maximum length of a string created by the date printers */
#define MAX_DATE_LENGTH 34
#define MAXTIME INT64_C(253402300799)
#define MINTIME INT64_C(-62135596800)
/** Constants *******************************************************/
/** \brief UTC date format string.

View File

@ -58,6 +58,17 @@ typedef struct
static _GncDateTime gncdt;
extern void _gnc_date_time_init (_GncDateTime *);
/* t < MINTIME is probably from a bad conversion from t 0 to
* 0000-00-00, so restore it to the Unix Epoch. t anywhere near
* MAXTIME is obviously an error, but we don't want to crash with a
* bad date-time so just clamp it to MAXTIME.
*/
static inline time64
clamp_time(time64 t)
{
return t < MINTIME ? 0 : t > MAXTIME ? MAXTIME : t;
}
/* gnc_localtime just creates a tm on the heap and calls
* gnc_localtime_r with it, so this suffices to test both.
*/
@ -70,18 +81,6 @@ test_gnc_localtime (void)
// difference between g_date_time and tm->tm_wday)
};
guint ind;
#if defined(__clang__) && __clang_major__ < 6
#define _func "struct tm *gnc_localtime_r(const time64 *, struct tm *)"
#else
#define _func "gnc_localtime_r"
#endif
gchar *msg = _func ": assertion " _Q "gdt != NULL' failed";
#undef _func
gint loglevel = G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL;
gchar *logdomain = "qof";
TestErrorStruct check = {loglevel, logdomain, msg, 0};
GLogFunc hdlr = g_log_set_default_handler ((GLogFunc)test_null_handler, &check);
g_test_log_set_fatal_handler ((GTestLogFatalFunc)test_checked_handler, &check);
for (ind = 0; ind < G_N_ELEMENTS (secs); ind++)
{
@ -112,8 +111,6 @@ test_gnc_localtime (void)
g_date_time_unref (gdt);
gnc_tm_free (time);
}
g_assert_cmpint (check.hits, ==, 1);
g_log_set_default_handler (hdlr, NULL);
}
static void
@ -129,35 +126,23 @@ test_gnc_gmtime (void)
{ 48, 51, 23, 18, 11, 69, 4, 352, 0, 0, NULL },
{ 41, 12, 0, 6, 0, 70, 2, 6, 0, 0, NULL },
{ 32, 30, 2, 3, 11, 92, 4, 338, 0, 0, NULL },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL },
{ 59, 59, 23, 31, 11, 8099, 5, 365, 0, 0, NULL },
{ 6, 47, 16, 7, 3, 107, 6, 97, 0, 0, NULL },
#else
{ 6, 41, 2, 24, 9, -1301, 4, 297, 0 },
{ 48, 51, 23, 18, 11, 69, 4, 352, 0 },
{ 41, 12, 0, 6, 0, 70, 2, 6, 0 },
{ 32, 30, 2, 3, 11, 92, 4, 338, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 59, 50, 23, 31, 11, 8099, 5, 365, 0 },
{ 6, 47, 16, 7, 3, 107, 6, 97, 0 },
#endif
};
guint ind;
#if defined(__clang__) && __clang_major__ < 6
#define _func "struct tm *gnc_gmtime(const time64 *)"
#else
#define _func "gnc_gmtime"
#endif
gchar *msg = _func ": assertion " _Q "gdt != NULL' failed";
#undef _func
gint loglevel = G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL;
gchar *logdomain = "qof";
TestErrorStruct check = {loglevel, logdomain, msg, 0};
GLogFunc hdlr = g_log_set_default_handler ((GLogFunc)test_null_handler, &check);
g_test_log_set_fatal_handler ((GTestLogFatalFunc)test_checked_handler, &check);
for (ind = 0; ind < G_N_ELEMENTS (secs); ind++)
{
struct tm* time = gnc_gmtime (&secs[ind]);
GDateTime *gdt = g_date_time_new_from_unix_utc (secs[ind]);
GDateTime *gdt = g_date_time_new_from_unix_utc (clamp_time (secs[ind]));
if (gdt == NULL)
{
g_assert (time == NULL);
@ -178,8 +163,6 @@ test_gnc_gmtime (void)
g_date_time_unref (gdt);
gnc_tm_free (time);
}
g_assert_cmpint (check.hits, ==, 1);
g_log_set_default_handler (hdlr, NULL);
}
static void
@ -2037,14 +2020,23 @@ gnc_timezone (const struct tm *tm)// C: 5 in 2 Local: 2:0:0
test_gnc_timezone (void)
{
}*/
/* timespecFromtime64
/* timespecFromTime64
void
timespecFromtime64( Timespec *ts, time64 t )// C: 22 in 11 Local: 0:0:0
timespecFromTime64( Timespec *ts, time64 t )// C: 22 in 11 Local: 0:0:0
*/
/* static void
test_timespecFromtime64 (void)
static void
test_timespecFromTime64 (void)
{
}*/
Timespec ts = {-9999, 0};
timespecFromTime64 (&ts, MINTIME - 1);
g_assert_cmpint (0, ==, ts.tv_sec);
timespecFromTime64 (&ts, MINTIME + 1);
g_assert_cmpint (MINTIME + 1, ==, ts.tv_sec);
timespecFromTime64 (&ts, MAXTIME + 1);
g_assert_cmpint (MAXTIME, ==, ts.tv_sec);
timespecFromTime64 (&ts, MAXTIME - 1);
g_assert_cmpint (MAXTIME - 1, ==, ts.tv_sec);
}
/* timespec_now
Timespec
timespec_now()// C: 2 in 2 Local: 0:0:0
@ -2474,7 +2466,7 @@ test_suite_gnc_date (void)
GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec end", test_gnc_dmy2timespec_end);
GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec Neutral", test_gnc_dmy2timespec_neutral);
// GNC_TEST_ADD_FUNC (suitename, "gnc timezone", test_gnc_timezone);
// GNC_TEST_ADD_FUNC (suitename, "timespecFromTime t", test_timespecFromtime64);
GNC_TEST_ADD_FUNC (suitename, "timespecFromTime64", test_timespecFromTime64);
// GNC_TEST_ADD_FUNC (suitename, "timespec now", test_timespec_now);
// GNC_TEST_ADD_FUNC (suitename, "timespecToTime t", test_timespecTotime64);
GNC_TEST_ADD_FUNC (suitename, "timespec to gdate", test_timespec_to_gdate);