mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-08-30 00:33:19 +00:00
jq: Fix CVE-2026-47770
This patch applies the upstream fix for CVE-2026-47770 as referenced in [2], using the upstream commit identified in [1]. [1] https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831 [2] https://github.com/jqlang/jq/security/advisories/GHSA-3pgx-frr7-3jxp Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
committed by
Anuj Mittal
parent
da15df26e6
commit
624fedbfa0
@@ -0,0 +1,449 @@
|
||||
From 0f31d6f32040ed421d8ccb694aaf767865972e88 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 2/2] 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-Status: Backport [https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831]
|
||||
|
||||
Backport Changes:
|
||||
- Omitted the newer f_bsearch C helper.
|
||||
jq 1.7.1 neither contains nor registers this helper.
|
||||
Adding it would introduce unrelated newer functionality.
|
||||
- Omitted the newer jv_unique C helper.
|
||||
jq 1.7.1 has no matching helper or declaration.
|
||||
Adding the entire function was unnecessary for the target.
|
||||
- Preserved jv_array_indexes iterator ownership.
|
||||
jq 1.7.1 frees belem after each comparison.
|
||||
Passing a copy prevents a double-free on the normal path.
|
||||
- Preserved the jq 1.7.1 delpaths_sorted loop.
|
||||
The target uses a while loop instead of the newer do-while form.
|
||||
Only the equality sentinel handling was needed.
|
||||
- Kept existing empty-array handling in sort_items().
|
||||
The newer empty-array block was context, not part of the fix.
|
||||
Only too_deep initialization was required.
|
||||
|
||||
(cherry picked from commit 7122866869960b55cea3646bc91334ef55787831)
|
||||
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
|
||||
---
|
||||
src/builtin.c | 30 +++++++++++++++--
|
||||
src/jv.c | 47 +++++++++++++++++++++------
|
||||
src/jv_aux.c | 90 +++++++++++++++++++++++++++++++++++++++++++--------
|
||||
tests/jq.test | 22 +++++++++++++
|
||||
4 files changed, 163 insertions(+), 26 deletions(-)
|
||||
|
||||
diff --git a/src/builtin.c b/src/builtin.c
|
||||
index 378be02..08a8a98 100644
|
||||
--- a/src/builtin.c
|
||||
+++ b/src/builtin.c
|
||||
@@ -336,7 +336,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;
|
||||
}
|
||||
@@ -431,11 +439,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 {
|
||||
@@ -447,6 +461,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) ||
|
||||
@@ -1065,6 +1081,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 a68b171..fe27168 100644
|
||||
--- a/src/jv.c
|
||||
+++ b/src/jv.c
|
||||
@@ -891,16 +891,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;
|
||||
}
|
||||
@@ -1050,7 +1054,15 @@ jv jv_array_indexes(jv a, jv b) {
|
||||
jv_array_foreach(a, ai, aelem) {
|
||||
jv_free(aelem);
|
||||
jv_array_foreach(b, bi, belem) {
|
||||
- if (!jv_equal(jv_array_get(jv_copy(a), ai + bi), jv_copy(belem)))
|
||||
+ int equal = jv_equal(jv_array_get(jv_copy(a), ai + bi), jv_copy(belem));
|
||||
+ if (equal < 0) {
|
||||
+ jv_free(res);
|
||||
+ jv_free(belem);
|
||||
+ 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;
|
||||
@@ -1782,7 +1794,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++) {
|
||||
@@ -1791,7 +1803,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;
|
||||
@@ -2007,7 +2020,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;
|
||||
@@ -2023,13 +2045,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;
|
||||
@@ -2041,6 +2063,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 0855053..75497c5 100644
|
||||
--- a/src/jv_aux.c
|
||||
+++ b/src/jv_aux.c
|
||||
@@ -15,6 +15,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"));
|
||||
@@ -471,7 +489,7 @@ static jv delpaths_sorted(jv object, jv paths, int start) {
|
||||
int delkey = jv_array_length(jv_array_get(jv_copy(paths), i)) == start + 1;
|
||||
jv key = jv_array_get(jv_array_get(jv_copy(paths), i), start);
|
||||
while (j < jv_array_length(jv_copy(paths)) &&
|
||||
- jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)))
|
||||
+ jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)) == 1)
|
||||
j++;
|
||||
// if i <= entry < j, then entry starts with key
|
||||
if (delkey) {
|
||||
@@ -602,7 +620,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);
|
||||
@@ -617,14 +641,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);
|
||||
}
|
||||
@@ -648,7 +671,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;
|
||||
@@ -657,13 +682,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);
|
||||
@@ -676,6 +702,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;
|
||||
@@ -683,19 +714,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;
|
||||
struct sort_entry* entries = jv_mem_calloc(n, sizeof(struct sort_entry));
|
||||
for (int i=0; i<n; i++) {
|
||||
entries[i].object = jv_array_get(jv_copy(objects), i);
|
||||
@@ -704,7 +748,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;
|
||||
}
|
||||
|
||||
@@ -713,7 +763,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);
|
||||
@@ -728,13 +781,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);
|
||||
diff --git a/tests/jq.test b/tests/jq.test
|
||||
index 6d1518d..018d2d0 100644
|
||||
--- a/tests/jq.test
|
||||
+++ b/tests/jq.test
|
||||
@@ -2172,3 +2172,25 @@ null
|
||||
try ((reduce range(10001) as $_ ({}; {a: .})) as $x | $x * $x) catch .
|
||||
null
|
||||
"Object merge too deep"
|
||||
+
|
||||
+# regression test for deep structural equality recursion
|
||||
+try ((reduce range(10001) as $_ ([]; [.])) as $x | (reduce range(10001) as $_ ([]; [.])) as $y | $x == $y) catch .
|
||||
+null
|
||||
+"Equality check too deep"
|
||||
+
|
||||
+# regression tests for deep ordering comparisons
|
||||
+try ((reduce range(10001) as $_ ([]; [.])) as $x | [$x, $x] | sort) catch .
|
||||
+null
|
||||
+"Comparison too deep"
|
||||
+
|
||||
+try ((reduce range(10001) as $_ ([]; [.])) as $x | [$x, $x] | unique) catch .
|
||||
+null
|
||||
+"Comparison too deep"
|
||||
+
|
||||
+try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | sort) catch .
|
||||
+null
|
||||
+"Comparison too deep"
|
||||
+
|
||||
+try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | unique) catch .
|
||||
+null
|
||||
+"Comparison too deep"
|
||||
--
|
||||
2.44.4
|
||||
|
||||
@@ -26,6 +26,7 @@ SRC_URI = "${GITHUB_BASE_URI}/download/${BPN}-${PV}/${BPN}-${PV}.tar.gz \
|
||||
file://CVE-2026-43894.patch \
|
||||
file://CVE-2026-43896.patch \
|
||||
file://CVE-2026-43895.patch \
|
||||
file://CVE-2026-47770.patch \
|
||||
"
|
||||
SRC_URI[sha256sum] = "478c9ca129fd2e3443fe27314b455e211e0d8c60bc8ff7df703873deeee580c2"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user