jq: Fix CVEs

Adds backported patches to fix CVE-2024-23339, CVE-2024-53427, and
CVE-2025-48060.

Signed-off-by: Colin Pinnell McAllister <colin.mcallister@garmin.com>
Change-Id: Ibc2db956b7fd5d0388dbed1a81ddf9aa58431fb1
Signed-off-by: Armin Kuster <akuster808@gmail.com>
This commit is contained in:
Colin McAllister 2025-07-07 16:39:43 -05:00 committed by Armin Kuster
parent f73c3e4b77
commit 9daee866d1
5 changed files with 397 additions and 1 deletions

View File

@ -0,0 +1,219 @@
From 35cde320ac7ee9ad6da5ce422922fafe592c4c60 Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Wed, 21 May 2025 07:45:00 +0900
Subject: [PATCH 1/2] Fix signed integer overflow in jvp_array_write and
jvp_object_rehash
This commit fixes signed integer overflow and SEGV issues on growing
arrays and objects. The size of arrays and objects is now limited to
`536870912` (`0x20000000`). This fixes CVE-2024-23337 and fixes #3262.
CVE: CVE-2024-23337
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/de21386681c0df0104a99d9d09db23a9b2a78b1e]
Signed-off-by: Colin Pinnell McAllister <colin.mcallister@garmin.com>
---
src/jv.c | 45 ++++++++++++++++++++++++++++++++++++---------
src/jv_aux.c | 9 +++++----
tests/jq.test | 4 ++++
3 files changed, 45 insertions(+), 13 deletions(-)
diff --git a/src/jv.c b/src/jv.c
index 9784b22..33ccee9 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -1006,6 +1006,11 @@ jv jv_array_set(jv j, int idx, jv val) {
jv_free(val);
return jv_invalid_with_msg(jv_string("Out of bounds negative array index"));
}
+ if (idx > (INT_MAX >> 2) - jvp_array_offset(j)) {
+ jv_free(j);
+ jv_free(val);
+ return jv_invalid_with_msg(jv_string("Array index too large"));
+ }
// copy/free of val,j coalesced
jv* slot = jvp_array_write(&j, idx);
jv_free(*slot);
@@ -1025,6 +1030,7 @@ jv jv_array_concat(jv a, jv b) {
// FIXME: could be faster
jv_array_foreach(b, i, elem) {
a = jv_array_append(a, elem);
+ if (!jv_is_valid(a)) break;
}
jv_free(b);
return a;
@@ -1296,6 +1302,7 @@ jv jv_string_indexes(jv j, jv k) {
p = jstr;
while ((p = _jq_memmem(p, (jstr + jlen) - p, idxstr, idxlen)) != NULL) {
a = jv_array_append(a, jv_number(p - jstr));
+ if (!jv_is_valid(a)) break;
p += idxlen;
}
}
@@ -1318,14 +1325,17 @@ jv jv_string_split(jv j, jv sep) {
if (seplen == 0) {
int c;
- while ((jstr = jvp_utf8_next(jstr, jend, &c)))
+ while ((jstr = jvp_utf8_next(jstr, jend, &c))) {
a = jv_array_append(a, jv_string_append_codepoint(jv_string(""), c));
+ if (!jv_is_valid(a)) break;
+ }
} else {
for (p = jstr; p < jend; p = s + seplen) {
s = _jq_memmem(p, jend - p, sepstr, seplen);
if (s == NULL)
s = jend;
a = jv_array_append(a, jv_string_sized(p, s - p));
+ if (!jv_is_valid(a)) break;
// Add an empty string to denote that j ends on a sep
if (s + seplen == jend && seplen != 0)
a = jv_array_append(a, jv_string(""));
@@ -1343,8 +1353,10 @@ jv jv_string_explode(jv j) {
const char* end = i + len;
jv a = jv_array_sized(len);
int c;
- while ((i = jvp_utf8_next(i, end, &c)))
+ while ((i = jvp_utf8_next(i, end, &c))) {
a = jv_array_append(a, jv_number(c));
+ if (!jv_is_valid(a)) break;
+ }
jv_free(j);
return a;
}
@@ -1617,10 +1629,13 @@ static void jvp_object_free(jv o) {
}
}
-static jv jvp_object_rehash(jv object) {
+static int jvp_object_rehash(jv *objectp) {
+ jv object = *objectp;
assert(JVP_HAS_KIND(object, JV_KIND_OBJECT));
assert(jvp_refcnt_unshared(object.u.ptr));
int size = jvp_object_size(object);
+ if (size > INT_MAX >> 2)
+ return 0;
jv new_object = jvp_object_new(size * 2);
for (int i=0; i<size; i++) {
struct object_slot* slot = jvp_object_get_slot(object, i);
@@ -1633,7 +1648,8 @@ static jv jvp_object_rehash(jv object) {
}
// references are transported, just drop the old table
jv_mem_free(jvp_object_ptr(object));
- return new_object;
+ *objectp = new_object;
+ return 1;
}
static jv jvp_object_unshare(jv object) {
@@ -1662,27 +1678,32 @@ static jv jvp_object_unshare(jv object) {
return new_object;
}
-static jv* jvp_object_write(jv* object, jv key) {
+static int jvp_object_write(jv* object, jv key, jv **valpp) {
*object = jvp_object_unshare(*object);
int* bucket = jvp_object_find_bucket(*object, key);
struct object_slot* slot = jvp_object_find_slot(*object, key, bucket);
if (slot) {
// already has the key
jvp_string_free(key);
- return &slot->value;
+ *valpp = &slot->value;
+ return 1;
}
slot = jvp_object_add_slot(*object, key, bucket);
if (slot) {
slot->value = jv_invalid();
} else {
- *object = jvp_object_rehash(*object);
+ if (!jvp_object_rehash(object)) {
+ *valpp = NULL;
+ return 0;
+ }
bucket = jvp_object_find_bucket(*object, key);
assert(!jvp_object_find_slot(*object, key, bucket));
slot = jvp_object_add_slot(*object, key, bucket);
assert(slot);
slot->value = jv_invalid();
}
- return &slot->value;
+ *valpp = &slot->value;
+ return 1;
}
static int jvp_object_delete(jv* object, jv key) {
@@ -1783,7 +1804,11 @@ jv jv_object_set(jv object, jv key, jv value) {
assert(JVP_HAS_KIND(object, JV_KIND_OBJECT));
assert(JVP_HAS_KIND(key, JV_KIND_STRING));
// copy/free of object, key, value coalesced
- jv* slot = jvp_object_write(&object, key);
+ jv* slot;
+ if (!jvp_object_write(&object, key, &slot)) {
+ jv_free(object);
+ return jv_invalid_with_msg(jv_string("Object too big"));
+ }
jv_free(*slot);
*slot = value;
return object;
@@ -1808,6 +1833,7 @@ jv jv_object_merge(jv a, jv b) {
assert(JVP_HAS_KIND(a, JV_KIND_OBJECT));
jv_object_foreach(b, k, v) {
a = jv_object_set(a, k, v);
+ if (!jv_is_valid(a)) break;
}
jv_free(b);
return a;
@@ -1827,6 +1853,7 @@ jv jv_object_merge_recursive(jv a, jv b) {
jv_free(elem);
a = jv_object_set(a, k, v);
}
+ if (!jv_is_valid(a)) break;
}
jv_free(b);
return a;
diff --git a/src/jv_aux.c b/src/jv_aux.c
index 994285a..0753aef 100644
--- a/src/jv_aux.c
+++ b/src/jv_aux.c
@@ -162,18 +162,19 @@ jv jv_set(jv t, jv k, jv v) {
if (slice_len < insert_len) {
// array is growing
int shift = insert_len - slice_len;
- for (int i = array_len - 1; i >= end; i--) {
+ for (int i = array_len - 1; i >= end && jv_is_valid(t); i--) {
t = jv_array_set(t, i + shift, jv_array_get(jv_copy(t), i));
}
} else if (slice_len > insert_len) {
// array is shrinking
int shift = slice_len - insert_len;
- for (int i = end; i < array_len; i++) {
+ for (int i = end; i < array_len && jv_is_valid(t); i++) {
t = jv_array_set(t, i - shift, jv_array_get(jv_copy(t), i));
}
- t = jv_array_slice(t, 0, array_len - shift);
+ if (jv_is_valid(t))
+ t = jv_array_slice(t, 0, array_len - shift);
}
- for (int i=0; i < insert_len; i++) {
+ for (int i = 0; i < insert_len && jv_is_valid(t); i++) {
t = jv_array_set(t, start + i, jv_array_get(jv_copy(v), i));
}
jv_free(v);
diff --git a/tests/jq.test b/tests/jq.test
index 2d5c36b..c6c6ee5 100644
--- a/tests/jq.test
+++ b/tests/jq.test
@@ -186,6 +186,10 @@ null
[0,1,2]
[0,5,2]
+try (.[999999999] = 0) catch .
+null
+"Array index too large"
+
#
# Multiple outputs, iteration
#
--
2.49.0

View File

@ -0,0 +1,69 @@
From 4240af6a20465894dce871707271e11a05432dac Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Sun, 16 Feb 2025 22:08:36 +0900
Subject: [PATCH 1/2] fix: `jv_number_value` should cache the double value of
literal numbers (#3245)
The code of `jv_number_value` is intended to cache the double value of
literal numbers, but it does not work because it accepts the `jv` struct
by value. This patch fixes the behavior by checking if the double value
is `NaN`, which indicates the unconverted value. This patch improves the
performance of major use cases; e.g. `range(1000000)` runs 25% faster.
CVE: CVE-2024-53427
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/b86ff49f46a4a37e5a8e75a140cb5fd6e1331384]
Signed-off-by: Colin Pinnell McAllister <colin.mcallister@garmin.com>
---
src/jv.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/src/jv.c b/src/jv.c
index 4d7bba1..9051f65 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -209,9 +209,6 @@ enum {
JVP_NUMBER_DECIMAL = 1
};
-#define JV_NUMBER_SIZE_INIT (0)
-#define JV_NUMBER_SIZE_CONVERTED (1)
-
#define JVP_FLAGS_NUMBER_NATIVE JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_NATIVE, 0))
#define JVP_FLAGS_NUMBER_NATIVE_STR JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_NATIVE, 1))
#define JVP_FLAGS_NUMBER_LITERAL JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_DECIMAL, 1))
@@ -619,8 +616,12 @@ static jv jvp_literal_number_new(const char * literal) {
jv_mem_free(n);
return JV_INVALID;
}
+ if (decNumberIsNaN(&n->num_decimal)) {
+ jv_mem_free(n);
+ return jv_number(NAN);
+ }
- jv r = {JVP_FLAGS_NUMBER_LITERAL, 0, 0, JV_NUMBER_SIZE_INIT, {&n->refcnt}};
+ jv r = {JVP_FLAGS_NUMBER_LITERAL, 0, 0, 0, {&n->refcnt}};
return r;
}
@@ -719,9 +720,8 @@ double jv_number_value(jv j) {
if (JVP_HAS_FLAGS(j, JVP_FLAGS_NUMBER_LITERAL)) {
jvp_literal_number* n = jvp_literal_number_ptr(j);
- if (j.size != JV_NUMBER_SIZE_CONVERTED) {
+ if (isnan(n->num_double)) {
n->num_double = jvp_literal_number_to_double(j);
- j.size = JV_NUMBER_SIZE_CONVERTED;
}
return n->num_double;
@@ -755,6 +755,7 @@ int jvp_number_is_nan(jv n) {
} else {
return n.u.number != n.u.number;
}
+ return isnan(n.u.number);
}
int jvp_number_cmp(jv a, jv b) {
--
2.49.0

View File

@ -0,0 +1,56 @@
From aea65caf03c129f3303d044044d2d1105be81b71 Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Wed, 5 Mar 2025 07:43:54 +0900
Subject: [PATCH 2/2] Reject NaN with payload while parsing JSON
This commit drops support for parsing NaN with payload in JSON like
`NaN123` and fixes CVE-2024-53427. Other JSON extensions like `NaN` and
`Infinity` are still supported. Fixes #3023, fixes #3196, fixes #3246.
CVE: CVE-2024-53427
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/a09a4dfd55e6c24d04b35062ccfe4509748b1dd3]
Signed-off-by: Colin Pinnell McAllister <colin.mcallister@garmin.com>
---
src/jv.c | 5 +++++
tests/jq.test | 12 ++++++++++++
2 files changed, 17 insertions(+)
diff --git a/src/jv.c b/src/jv.c
index 9051f65..4da5ba8 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -617,6 +617,11 @@ static jv jvp_literal_number_new(const char * literal) {
return JV_INVALID;
}
if (decNumberIsNaN(&n->num_decimal)) {
+ // Reject NaN with payload.
+ if (n->num_decimal.digits > 1 || *n->num_decimal.lsu != 0) {
+ jv_mem_free(n);
+ return JV_INVALID;
+ }
jv_mem_free(n);
return jv_number(NAN);
}
diff --git a/tests/jq.test b/tests/jq.test
index f783493..0ab21ef 100644
--- a/tests/jq.test
+++ b/tests/jq.test
@@ -1724,3 +1724,15 @@ false
try 0[implode] catch .
[]
"Cannot index number with string \"\""
+
+# NaN with payload is not parsed
+.[] | try (fromjson | isnan) catch .
+["NaN","-NaN","NaN1","NaN10","NaN100","NaN1000","NaN10000","NaN100000"]
+true
+true
+"Invalid numeric literal at EOF at line 1, column 4 (while parsing 'NaN1')"
+"Invalid numeric literal at EOF at line 1, column 5 (while parsing 'NaN10')"
+"Invalid numeric literal at EOF at line 1, column 6 (while parsing 'NaN100')"
+"Invalid numeric literal at EOF at line 1, column 7 (while parsing 'NaN1000')"
+"Invalid numeric literal at EOF at line 1, column 8 (while parsing 'NaN10000')"
+"Invalid numeric literal at EOF at line 1, column 9 (while parsing 'NaN100000')"
--
2.49.0

View File

@ -0,0 +1,46 @@
From 9e23fd7e88bb2d76ddf3fbfc805199f848cd1b92 Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Sat, 31 May 2025 11:46:40 +0900
Subject: [PATCH 2/2] Fix heap buffer overflow when formatting an empty string
The `jv_string_empty` did not properly null-terminate the string data,
which could lead to a heap buffer overflow. The test case of
GHSA-p7rr-28xf-3m5w (`0[""*0]`) was fixed by the commit dc849e9bb74a,
but another case (`0[[]|implode]`) was still vulnerable. This commit
ensures string data is properly null-terminated, and fixes CVE-2025-48060.
CVE: CVE-2025-48060
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/c6e041699d8cd31b97375a2596217aff2cfca85b]
Signed-off-by: Colin Pinnell McAllister <colin.mcallister@garmin.com>
---
src/jv.c | 1 +
tests/jq.test | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/src/jv.c b/src/jv.c
index 33ccee9..4d7bba1 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -1131,6 +1131,7 @@ static jv jvp_string_empty_new(uint32_t length) {
jvp_string* s = jvp_string_alloc(length);
s->length_hashed = 0;
memset(s->data, 0, length);
+ s->data[length] = 0;
jv r = {JVP_FLAGS_STRING, 0, 0, 0, {&s->refcnt}};
return r;
}
diff --git a/tests/jq.test b/tests/jq.test
index c6c6ee5..f783493 100644
--- a/tests/jq.test
+++ b/tests/jq.test
@@ -1720,3 +1720,7 @@ false
. |= try . catch .
1
1
+
+try 0[implode] catch .
+[]
+"Cannot index number with string \"\""
--
2.49.0

View File

@ -9,7 +9,13 @@ LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://COPYING;md5=2814b59e00e7918c864fa3b6bbe049b4"
PV = "1.6+git${SRCPV}"
SRC_URI = "git://github.com/stedolan/jq;protocol=https;branch=master"
SRC_URI = " \
git://github.com/stedolan/jq;protocol=https;branch=master \
file://CVE-2024-23337.patch \
file://CVE-2025-48060.patch \
file://CVE-2024-53427-01.patch \
file://CVE-2024-53427-02.patch \
"
SRCREV = "a9f97e9e61a910a374a5d768244e8ad63f407d3e"
S = "${WORKDIR}/git"