Files
buildroot/package/jq/0013-guard-deep-structural-equality-and-comparison-recursion.patch
Thomas Perale e8191c3e17 package/jq: patch various CVEs
Fixes the following vulnerabilities by importing upstream patches:

- CVE-2026-39979: 2f09060afa

- CVE-2026-33948: 6374ae0bcd

- CVE-2026-33947: fb59f14910

- CVE-2026-32316: e47e56d226

- CVE-2026-40164: 0c7d133c3c

- CVE-2026-40612: d1a12569d9

- CVE-2026-41256: 5a015deae3

- CVE-2026-41257: 01b3cded76

- CVE-2026-43894: 9761ceb7d6

- CVE-2026-43895: 9d223f153c

- CVE-2026-43896: 532ccea608

- CVE-2026-44777: f58787c418

- CVE-2026-47770: 7122866869

- CVE-2026-49839: e987df0d46

- CVE-2026-54679: 46d1da3094

Tests were stripped out of the patches.

Signed-off-by: Thomas Perale <thomas.perale@mind.be>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
2026-07-02 08:32:30 +02:00

424 lines
13 KiB
Diff

From 7122866869960b55cea3646bc91334ef55787831 Mon Sep 17 00:00:00 2001
From: Yu-Fu Fu <yufu@yfu.tw>
Date: Fri, 22 May 2026 04:07:16 -0700
Subject: [PATCH] Guard deep structural equality and comparison recursion
(#3539)
jv_equal and jv_cmp overflows the C stack on deeply nested
input. Cap recursion at 10000 with -1 / INT_MIN sentinels;
operators that compose user expressions surface this as
"Equality check too deep" / "Comparison too deep".
Fixes CVE-2026-47770.
CVE: CVE-2026-47770
Upstream: https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
---
src/builtin.c | 36 +++++++++++++++--
src/jv.c | 46 +++++++++++++++++-----
src/jv_aux.c | 104 ++++++++++++++++++++++++++++++++++++++++++--------
4 files changed, 180 insertions(+), 28 deletions(-)
diff --git a/src/builtin.c b/src/builtin.c
index 2b2a2d40da..8ee605ed2b 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -295,7 +295,15 @@ jv binop_minus(jv a, jv b) {
jv_array_foreach(a, i, x) {
int include = 1;
jv_array_foreach(b, j, y) {
- if (jv_equal(jv_copy(x), y)) {
+ int equal = jv_equal(jv_copy(x), y);
+ if (equal < 0) {
+ jv_free(out);
+ jv_free(x);
+ jv_free(a);
+ jv_free(b);
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
+ }
+ if (equal) {
include = 0;
break;
}
@@ -379,11 +387,17 @@ jv binop_mod(jv a, jv b) {
#undef dtoi
jv binop_equal(jv a, jv b) {
- return jv_bool(jv_equal(a, b));
+ int r = jv_equal(a, b);
+ if (r < 0)
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
+ return jv_bool(r);
}
jv binop_notequal(jv a, jv b) {
- return jv_bool(!jv_equal(a, b));
+ int r = jv_equal(a, b);
+ if (r < 0)
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
+ return jv_bool(!r);
}
enum cmp_op {
@@ -395,6 +409,8 @@ enum cmp_op {
static jv order_cmp(jv a, jv b, enum cmp_op op) {
int r = jv_cmp(a, b);
+ if (r == INT_MIN)
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
return jv_bool((op == CMP_OP_LESS && r < 0) ||
(op == CMP_OP_LESSEQ && r <= 0) ||
(op == CMP_OP_GREATEREQ && r >= 0) ||
@@ -848,6 +864,12 @@ static jv f_bsearch(jq_state *jq, jv input, jv target) {
while (start < end) {
int mid = start + (end - start) / 2;
int result = jv_cmp(jv_copy(target), jv_array_get(jv_copy(input), mid));
+ if (result == INT_MIN) {
+ jv_free(answer);
+ jv_free(input);
+ jv_free(target);
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
+ }
if (result == 0) {
answer = jv_number(mid);
break;
@@ -1139,6 +1161,14 @@ static jv minmax_by(jv values, jv keys, int is_min) {
for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
jv item = jv_array_get(jv_copy(keys), i);
int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
+ if (cmp == INT_MIN) {
+ jv_free(item);
+ jv_free(values);
+ jv_free(keys);
+ jv_free(retkey);
+ jv_free(ret);
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
+ }
if ((cmp < 0) == (is_min == 1)) {
jv_free(retkey);
retkey = item;
diff --git a/src/jv.c b/src/jv.c
index 074ee310c5..bcd7c9b5b0 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -915,16 +915,20 @@ static jv* jvp_array_write(jv* a, int i) {
}
}
-static int jvp_array_equal(jv a, jv b) {
+static int jvp_equal(jv a, jv b, int depth);
+
+static int jvp_array_equal(jv a, jv b, int depth) {
if (jvp_array_length(a) != jvp_array_length(b))
return 0;
if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
jvp_array_offset(a) == jvp_array_offset(b))
return 1;
for (int i=0; i<jvp_array_length(a); i++) {
- if (!jv_equal(jv_copy(*jvp_array_read(a, i)),
- jv_copy(*jvp_array_read(b, i))))
- return 0;
+ int r = jvp_equal(jv_copy(*jvp_array_read(a, i)),
+ jv_copy(*jvp_array_read(b, i)),
+ depth);
+ if (r <= 0)
+ return r;
}
return 1;
}
@@ -1074,7 +1078,14 @@ jv jv_array_indexes(jv a, jv b) {
int alen = jv_array_length(jv_copy(a));
for (int ai = 0; ai < alen; ++ai) {
jv_array_foreach(b, bi, belem) {
- if (!jv_equal(jv_array_get(jv_copy(a), ai + bi), belem))
+ int equal = jv_equal(jv_array_get(jv_copy(a), ai + bi), belem);
+ if (equal < 0) {
+ jv_free(res);
+ jv_free(a);
+ jv_free(b);
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
+ }
+ if (!equal)
idx = -1;
else if (bi == 0 && idx == -1)
idx = ai;
@@ -1831,7 +1842,7 @@ static int jvp_object_length(jv object) {
return n;
}
-static int jvp_object_equal(jv o1, jv o2) {
+static int jvp_object_equal(jv o1, jv o2, int depth) {
int len2 = jvp_object_length(o2);
int len1 = 0;
for (int i=0; i<jvp_object_size(o1); i++) {
@@ -1840,7 +1851,8 @@ static int jvp_object_equal(jv o1, jv o2) {
jv* slot2 = jvp_object_read(o2, slot->string);
if (!slot2) return 0;
// FIXME: do less refcounting here
- if (!jv_equal(jv_copy(slot->value), jv_copy(*slot2))) return 0;
+ int r = jvp_equal(jv_copy(slot->value), jv_copy(*slot2), depth);
+ if (r <= 0) return r;
len1++;
}
return len1 == len2;
@@ -2056,7 +2068,16 @@ int jv_get_refcnt(jv j) {
* Higher-level operations
*/
-int jv_equal(jv a, jv b) {
+#ifndef MAX_EQUAL_DEPTH
+#define MAX_EQUAL_DEPTH (10000)
+#endif
+
+static int jvp_equal(jv a, jv b, int depth) {
+ if (depth > MAX_EQUAL_DEPTH) {
+ jv_free(a);
+ jv_free(b);
+ return -1;
+ }
int r;
if (jv_get_kind(a) != jv_get_kind(b)) {
r = 0;
@@ -2072,13 +2093,13 @@ int jv_equal(jv a, jv b) {
r = jvp_number_equal(a, b);
break;
case JV_KIND_ARRAY:
- r = jvp_array_equal(a, b);
+ r = jvp_array_equal(a, b, depth + 1);
break;
case JV_KIND_STRING:
r = jvp_string_equal(a, b);
break;
case JV_KIND_OBJECT:
- r = jvp_object_equal(a, b);
+ r = jvp_object_equal(a, b, depth + 1);
break;
default:
r = 1;
@@ -2090,6 +2111,11 @@ int jv_equal(jv a, jv b) {
return r;
}
+// Returns 1 if equal, 0 if not equal, or -1 if the comparison is too deep
+int jv_equal(jv a, jv b) {
+ return jvp_equal(a, b, 0);
+}
+
int jv_identical(jv a, jv b) {
int r;
if (a.kind_flags != b.kind_flags
diff --git a/src/jv_aux.c b/src/jv_aux.c
index fd5ff96684..4f5be0ab14 100644
--- a/src/jv_aux.c
+++ b/src/jv_aux.c
@@ -16,6 +16,24 @@ static double jv_number_get_value_and_consume(jv number) {
return value;
}
+#ifndef MAX_CMP_DEPTH
+#define MAX_CMP_DEPTH (10000)
+#endif
+
+struct sort_cmp_state {
+ int too_deep;
+};
+
+#ifdef _MSC_VER
+static __declspec(thread) struct sort_cmp_state sort_cmp_state;
+#else
+#ifdef HAVE___THREAD
+static __thread struct sort_cmp_state sort_cmp_state;
+#else
+static struct sort_cmp_state sort_cmp_state;
+#endif
+#endif
+
static jv parse_slice(jv j, jv slice, int* pstart, int* pend) {
// Array slices
jv start_jv = jv_object_get(jv_copy(slice), jv_string("start"));
@@ -606,7 +624,13 @@ jv jv_keys(jv x) {
}
}
-int jv_cmp(jv a, jv b) {
+static int jvp_cmp(jv a, jv b, int depth) {
+ if (depth > MAX_CMP_DEPTH) {
+ jv_free(a);
+ jv_free(b);
+ return INT_MIN;
+ }
+
if (jv_get_kind(a) != jv_get_kind(b)) {
int r = (int)jv_get_kind(a) - (int)jv_get_kind(b);
jv_free(a);
@@ -621,14 +645,13 @@ int jv_cmp(jv a, jv b) {
case JV_KIND_FALSE:
case JV_KIND_TRUE:
// there's only one of each of these values
- r = 0;
break;
case JV_KIND_NUMBER: {
if (jvp_number_is_nan(a)) {
- r = jv_cmp(jv_null(), jv_copy(b));
+ r = jvp_cmp(jv_null(), jv_copy(b), depth);
} else if (jvp_number_is_nan(b)) {
- r = jv_cmp(jv_copy(a), jv_null());
+ r = jvp_cmp(jv_copy(a), jv_null(), depth);
} else {
r = jvp_number_cmp(a, b);
}
@@ -652,7 +675,9 @@ int jv_cmp(jv a, jv b) {
}
jv xa = jv_array_get(jv_copy(a), i);
jv xb = jv_array_get(jv_copy(b), i);
- r = jv_cmp(xa, xb);
+ r = jvp_cmp(xa, xb, depth + 1);
+ if (r == INT_MIN)
+ break;
i++;
}
break;
@@ -661,13 +686,14 @@ int jv_cmp(jv a, jv b) {
case JV_KIND_OBJECT: {
jv keys_a = jv_keys(jv_copy(a));
jv keys_b = jv_keys(jv_copy(b));
- r = jv_cmp(jv_copy(keys_a), keys_b);
+ r = jvp_cmp(jv_copy(keys_a), keys_b, depth + 1);
if (r == 0) {
jv_array_foreach(keys_a, i, key) {
jv xa = jv_object_get(jv_copy(a), jv_copy(key));
jv xb = jv_object_get(jv_copy(b), key);
- r = jv_cmp(xa, xb);
- if (r) break;
+ r = jvp_cmp(xa, xb, depth + 1);
+ if (r != 0)
+ break;
}
}
jv_free(keys_a);
@@ -680,6 +706,11 @@ int jv_cmp(jv a, jv b) {
return r;
}
+// Returns <0, 0, >0 if a is less than, equal to, or greater than b, or
+// INT_MIN if the comparison is too deep
+int jv_cmp(jv a, jv b) {
+ return jvp_cmp(a, b, 0);
+}
struct sort_entry {
jv object;
@@ -687,19 +718,32 @@ struct sort_entry {
int index;
};
+static void sort_entry_array_free(struct sort_entry* entries, int start, int n) {
+ for (int i = start; i < n; i++) {
+ jv_free(entries[i].key);
+ jv_free(entries[i].object);
+ }
+ jv_mem_free(entries);
+}
+
static int sort_cmp(const void* pa, const void* pb) {
const struct sort_entry* a = pa;
const struct sort_entry* b = pb;
int r = jv_cmp(jv_copy(a->key), jv_copy(b->key));
+ if (r == INT_MIN) {
+ sort_cmp_state.too_deep = 1;
+ return 0;
+ }
// comparing by index if r == 0 makes the sort stable
return r ? r : (a->index - b->index);
}
-static struct sort_entry* sort_items(jv objects, jv keys) {
+static struct sort_entry* sort_items(jv objects, jv keys, int *too_deep) {
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
+ *too_deep = 0;
if (n == 0) {
jv_free(objects);
jv_free(keys);
@@ -713,7 +757,13 @@ static struct sort_entry* sort_items(jv objects, jv keys) {
}
jv_free(objects);
jv_free(keys);
+ sort_cmp_state.too_deep = 0;
qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
+ if (sort_cmp_state.too_deep) {
+ sort_entry_array_free(entries, 0, n);
+ *too_deep = 1;
+ return NULL;
+ }
return entries;
}
@@ -722,7 +772,10 @@ jv jv_sort(jv objects, jv keys) {
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
- struct sort_entry* entries = sort_items(objects, keys);
+ int too_deep = 0;
+ struct sort_entry* entries = sort_items(objects, keys, &too_deep);
+ if (too_deep)
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
jv ret = jv_array();
for (int i=0; i<n; i++) {
jv_free(entries[i].key);
@@ -737,13 +790,24 @@ jv jv_group(jv objects, jv keys) {
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
- struct sort_entry* entries = sort_items(objects, keys);
+ int too_deep = 0;
+ struct sort_entry* entries = sort_items(objects, keys, &too_deep);
+ if (too_deep)
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
jv ret = jv_array();
if (n > 0) {
jv curr_key = entries[0].key;
jv group = jv_array_append(jv_array(), entries[0].object);
for (int i = 1; i < n; i++) {
- if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
+ int equal = jv_equal(jv_copy(curr_key), jv_copy(entries[i].key));
+ if (equal < 0) {
+ jv_free(curr_key);
+ jv_free(group);
+ sort_entry_array_free(entries, i, n);
+ jv_free(ret);
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
+ }
+ if (equal) {
jv_free(entries[i].key);
} else {
jv_free(curr_key);
@@ -765,11 +829,21 @@ jv jv_unique(jv objects, jv keys) {
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
- struct sort_entry* entries = sort_items(objects, keys);
+ int too_deep = 0;
+ struct sort_entry* entries = sort_items(objects, keys, &too_deep);
+ if (too_deep)
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
jv ret = jv_array();
jv curr_key = jv_invalid();
for (int i = 0; i < n; i++) {
- if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
+ int equal = jv_equal(jv_copy(curr_key), jv_copy(entries[i].key));
+ if (equal < 0) {
+ jv_free(curr_key);
+ sort_entry_array_free(entries, i, n);
+ jv_free(ret);
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
+ }
+ if (equal) {
jv_free(entries[i].key);
jv_free(entries[i].object);
} else {