From bc9614682571fa2e8185398c89322c0300f92f76 Mon Sep 17 00:00:00 2001 From: Emanuele Torre Date: Tue, 19 Mar 2024 02:01:53 +0100 Subject: builtin.c: jv2tm: fix UB and accept array inputs with not all the values Now, time functions accept array inputs even if they don't have all the elements, 0 will be assumed if a value is not present. Also, jv2tm now properly clamps large number values to a signed 32-bit integer and rejects nan. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=65885 --- src/builtin.c | 50 ++++++++++++++++++++++++++++---------------------- tests/jq.test | 4 ++++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/builtin.c b/src/builtin.c index 5e31795c..05bd8592 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1461,30 +1461,35 @@ static jv f_strptime(jq_state *jq, jv a, jv b) { return r; } -#define TO_TM_FIELD(t, j, i) \ - do { \ - jv n = jv_array_get(jv_copy(j), (i)); \ - if (jv_get_kind(n) != (JV_KIND_NUMBER)) { \ - jv_free(n); \ - jv_free(j); \ - return 0; \ - } \ - t = jv_number_value(n); \ - jv_free(n); \ - } while (0) - static int jv2tm(jv a, struct tm *tm) { memset(tm, 0, sizeof(*tm)); - TO_TM_FIELD(tm->tm_year, a, 0); - tm->tm_year -= 1900; - TO_TM_FIELD(tm->tm_mon, a, 1); - TO_TM_FIELD(tm->tm_mday, a, 2); - TO_TM_FIELD(tm->tm_hour, a, 3); - TO_TM_FIELD(tm->tm_min, a, 4); - TO_TM_FIELD(tm->tm_sec, a, 5); - TO_TM_FIELD(tm->tm_wday, a, 6); - TO_TM_FIELD(tm->tm_yday, a, 7); - jv_free(a); + static size_t offsets[] = { + offsetof(struct tm, tm_year), + offsetof(struct tm, tm_mon), + offsetof(struct tm, tm_mday), + offsetof(struct tm, tm_hour), + offsetof(struct tm, tm_min), + offsetof(struct tm, tm_sec), + offsetof(struct tm, tm_wday), + offsetof(struct tm, tm_yday), + }; + + for (size_t i = 0; i < (sizeof offsets / sizeof *offsets); ++i) { + jv n = jv_array_get(jv_copy(a), i); + if (!jv_is_valid(n)) + break; + if (jv_get_kind(n) != JV_KIND_NUMBER || jvp_number_is_nan(n)) { + jv_free(a); + jv_free(n); + return 0; + } + double d = jv_number_value(n); + if (i == 0) /* year */ + d -= 1900; + *(int *)((void *)tm + offsets[i]) = d < INT_MIN ? INT_MIN : + d > INT_MAX ? INT_MAX : (int)d; + jv_free(n); + } // We use UTC everywhere (gettimeofday, gmtime) and UTC does not do DST. // Setting tm_isdst to 0 is done by the memset. @@ -1494,6 +1499,7 @@ static int jv2tm(jv a, struct tm *tm) { // hope it is okay to initialize them to zero, because the standard does not // provide an alternative. + jv_free(a); return 1; } diff --git a/tests/jq.test b/tests/jq.test index 463161c7..584ab2b6 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -1568,6 +1568,10 @@ strftime("%A, %B %d, %Y") 1435677542.822351 "Tuesday, June 30, 2015" +strftime("%Y-%m-%dT%H:%M:%SZ") +[2024,2,15] +"2024-03-15T00:00:00Z" + gmtime 1425599507 [2015,2,5,23,51,47,4,63] -- cgit v1.2.3