Skip to content

Commit 7e25620

Browse files
Patch jq for CVE-2026-49839, CVE-2026-47770
1 parent c68dea1 commit 7e25620

3 files changed

Lines changed: 425 additions & 1 deletion

File tree

SPECS/jq/CVE-2026-47770.patch

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
From 7571f793ba3b02989f47e1f1c9dea5a4e2518ed7 Mon Sep 17 00:00:00 2001
2+
From: AllSpark <allspark@microsoft.com>
3+
Date: Sat, 27 Jun 2026 08:26:08 +0000
4+
Subject: [PATCH] Guard deep structural equality and comparison recursion
5+
(#3539)
6+
7+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
8+
Upstream-reference: AI Backport of None
9+
---
10+
src/builtin.c | 30 +++++++++++++++++--
11+
src/jv.c | 37 +++++++++++++++++------
12+
src/jv_aux.c | 81 ++++++++++++++++++++++++++++++++++++++++++++-------
13+
tests/jq.test | 22 ++++++++++++++
14+
4 files changed, 147 insertions(+), 23 deletions(-)
15+
16+
diff --git a/src/builtin.c b/src/builtin.c
17+
index 08b94ac..5e8a9ec 100644
18+
--- a/src/builtin.c
19+
+++ b/src/builtin.c
20+
@@ -336,7 +336,15 @@ jv binop_minus(jv a, jv b) {
21+
jv_array_foreach(a, i, x) {
22+
int include = 1;
23+
jv_array_foreach(b, j, y) {
24+
- if (jv_equal(jv_copy(x), y)) {
25+
+ int equal = jv_equal(jv_copy(x), y);
26+
+ if (equal < 0) {
27+
+ jv_free(out);
28+
+ jv_free(x);
29+
+ jv_free(a);
30+
+ jv_free(b);
31+
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
32+
+ }
33+
+ if (equal) {
34+
include = 0;
35+
break;
36+
}
37+
@@ -431,11 +439,17 @@ jv binop_mod(jv a, jv b) {
38+
#undef dtoi
39+
40+
jv binop_equal(jv a, jv b) {
41+
- return jv_bool(jv_equal(a, b));
42+
+ int r = jv_equal(a, b);
43+
+ if (r < 0)
44+
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
45+
+ return jv_bool(r);
46+
}
47+
48+
jv binop_notequal(jv a, jv b) {
49+
- return jv_bool(!jv_equal(a, b));
50+
+ int r = jv_equal(a, b);
51+
+ if (r < 0)
52+
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
53+
+ return jv_bool(!r);
54+
}
55+
56+
enum cmp_op {
57+
@@ -447,6 +461,8 @@ enum cmp_op {
58+
59+
static jv order_cmp(jv a, jv b, enum cmp_op op) {
60+
int r = jv_cmp(a, b);
61+
+ if (r == INT_MIN)
62+
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
63+
return jv_bool((op == CMP_OP_LESS && r < 0) ||
64+
(op == CMP_OP_LESSEQ && r <= 0) ||
65+
(op == CMP_OP_GREATEREQ && r >= 0) ||
66+
@@ -1065,6 +1081,14 @@ static jv minmax_by(jv values, jv keys, int is_min) {
67+
for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
68+
jv item = jv_array_get(jv_copy(keys), i);
69+
int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
70+
+ if (cmp == INT_MIN) {
71+
+ jv_free(item);
72+
+ jv_free(values);
73+
+ jv_free(keys);
74+
+ jv_free(retkey);
75+
+ jv_free(ret);
76+
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
77+
+ }
78+
if ((cmp < 0) == (is_min == 1)) {
79+
jv_free(retkey);
80+
retkey = item;
81+
diff --git a/src/jv.c b/src/jv.c
82+
index dbf62dc..9bdabc2 100644
83+
--- a/src/jv.c
84+
+++ b/src/jv.c
85+
@@ -888,16 +888,20 @@ static jv* jvp_array_write(jv* a, int i) {
86+
}
87+
}
88+
89+
-static int jvp_array_equal(jv a, jv b) {
90+
+static int jvp_equal(jv a, jv b, int depth);
91+
+
92+
+static int jvp_array_equal(jv a, jv b, int depth) {
93+
if (jvp_array_length(a) != jvp_array_length(b))
94+
return 0;
95+
if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
96+
jvp_array_offset(a) == jvp_array_offset(b))
97+
return 1;
98+
for (int i=0; i<jvp_array_length(a); i++) {
99+
- if (!jv_equal(jv_copy(*jvp_array_read(a, i)),
100+
- jv_copy(*jvp_array_read(b, i))))
101+
- return 0;
102+
+ int r = jvp_equal(jv_copy(*jvp_array_read(a, i)),
103+
+ jv_copy(*jvp_array_read(b, i)),
104+
+ depth);
105+
+ if (r <= 0)
106+
+ return r;
107+
}
108+
return 1;
109+
}
110+
@@ -1779,7 +1783,7 @@ static int jvp_object_length(jv object) {
111+
return n;
112+
}
113+
114+
-static int jvp_object_equal(jv o1, jv o2) {
115+
+static int jvp_object_equal(jv o1, jv o2, int depth) {
116+
int len2 = jvp_object_length(o2);
117+
int len1 = 0;
118+
for (int i=0; i<jvp_object_size(o1); i++) {
119+
@@ -1788,7 +1792,8 @@ static int jvp_object_equal(jv o1, jv o2) {
120+
jv* slot2 = jvp_object_read(o2, slot->string);
121+
if (!slot2) return 0;
122+
// FIXME: do less refcounting here
123+
- if (!jv_equal(jv_copy(slot->value), jv_copy(*slot2))) return 0;
124+
+ int r = jvp_equal(jv_copy(slot->value), jv_copy(*slot2), depth);
125+
+ if (r <= 0) return r;
126+
len1++;
127+
}
128+
return len1 == len2;
129+
@@ -2004,7 +2009,16 @@ int jv_get_refcnt(jv j) {
130+
* Higher-level operations
131+
*/
132+
133+
-int jv_equal(jv a, jv b) {
134+
+#ifndef MAX_EQUAL_DEPTH
135+
+#define MAX_EQUAL_DEPTH (10000)
136+
+#endif
137+
+
138+
+static int jvp_equal(jv a, jv b, int depth) {
139+
+ if (depth > MAX_EQUAL_DEPTH) {
140+
+ jv_free(a);
141+
+ jv_free(b);
142+
+ return -1;
143+
+ }
144+
int r;
145+
if (jv_get_kind(a) != jv_get_kind(b)) {
146+
r = 0;
147+
@@ -2020,13 +2034,13 @@ int jv_equal(jv a, jv b) {
148+
r = jvp_number_equal(a, b);
149+
break;
150+
case JV_KIND_ARRAY:
151+
- r = jvp_array_equal(a, b);
152+
+ r = jvp_array_equal(a, b, depth + 1);
153+
break;
154+
case JV_KIND_STRING:
155+
r = jvp_string_equal(a, b);
156+
break;
157+
case JV_KIND_OBJECT:
158+
- r = jvp_object_equal(a, b);
159+
+ r = jvp_object_equal(a, b, depth + 1);
160+
break;
161+
default:
162+
r = 1;
163+
@@ -2038,6 +2052,11 @@ int jv_equal(jv a, jv b) {
164+
return r;
165+
}
166+
167+
+// Returns 1 if equal, 0 if not equal, or -1 if the comparison is too deep
168+
+int jv_equal(jv a, jv b) {
169+
+ return jvp_equal(a, b, 0);
170+
+}
171+
+
172+
int jv_identical(jv a, jv b) {
173+
int r;
174+
if (a.kind_flags != b.kind_flags
175+
diff --git a/src/jv_aux.c b/src/jv_aux.c
176+
index 0855053..f44ccda 100644
177+
--- a/src/jv_aux.c
178+
+++ b/src/jv_aux.c
179+
@@ -15,6 +15,24 @@ static double jv_number_get_value_and_consume(jv number) {
180+
return value;
181+
}
182+
183+
+#ifndef MAX_CMP_DEPTH
184+
+#define MAX_CMP_DEPTH (10000)
185+
+#endif
186+
+
187+
+struct sort_cmp_state {
188+
+ int too_deep;
189+
+};
190+
+
191+
+#ifdef _MSC_VER
192+
+static __declspec(thread) struct sort_cmp_state sort_cmp_state;
193+
+#else
194+
+#ifdef HAVE___THREAD
195+
+static __thread struct sort_cmp_state sort_cmp_state;
196+
+#else
197+
+static struct sort_cmp_state sort_cmp_state;
198+
+#endif
199+
+#endif
200+
+
201+
static jv parse_slice(jv j, jv slice, int* pstart, int* pend) {
202+
// Array slices
203+
jv start_jv = jv_object_get(jv_copy(slice), jv_string("start"));
204+
@@ -471,7 +489,7 @@ static jv delpaths_sorted(jv object, jv paths, int start) {
205+
int delkey = jv_array_length(jv_array_get(jv_copy(paths), i)) == start + 1;
206+
jv key = jv_array_get(jv_array_get(jv_copy(paths), i), start);
207+
while (j < jv_array_length(jv_copy(paths)) &&
208+
- jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)))
209+
+ jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)) == 1)
210+
j++;
211+
// if i <= entry < j, then entry starts with key
212+
if (delkey) {
213+
@@ -602,7 +620,13 @@ jv jv_keys(jv x) {
214+
}
215+
}
216+
217+
-int jv_cmp(jv a, jv b) {
218+
+static int jvp_cmp(jv a, jv b, int depth) {
219+
+ if (depth > MAX_CMP_DEPTH) {
220+
+ jv_free(a);
221+
+ jv_free(b);
222+
+ return INT_MIN;
223+
+ }
224+
+
225+
if (jv_get_kind(a) != jv_get_kind(b)) {
226+
int r = (int)jv_get_kind(a) - (int)jv_get_kind(b);
227+
jv_free(a);
228+
@@ -622,9 +646,9 @@ int jv_cmp(jv a, jv b) {
229+
230+
case JV_KIND_NUMBER: {
231+
if (jvp_number_is_nan(a)) {
232+
- r = jv_cmp(jv_null(), jv_copy(b));
233+
+ r = jvp_cmp(jv_null(), jv_copy(b), depth);
234+
} else if (jvp_number_is_nan(b)) {
235+
- r = jv_cmp(jv_copy(a), jv_null());
236+
+ r = jvp_cmp(jv_copy(a), jv_null(), depth);
237+
} else {
238+
r = jvp_number_cmp(a, b);
239+
}
240+
@@ -648,7 +672,9 @@ int jv_cmp(jv a, jv b) {
241+
}
242+
jv xa = jv_array_get(jv_copy(a), i);
243+
jv xb = jv_array_get(jv_copy(b), i);
244+
- r = jv_cmp(xa, xb);
245+
+ r = jvp_cmp(xa, xb, depth + 1);
246+
+ if (r == INT_MIN)
247+
+ break;
248+
i++;
249+
}
250+
break;
251+
@@ -657,12 +683,12 @@ int jv_cmp(jv a, jv b) {
252+
case JV_KIND_OBJECT: {
253+
jv keys_a = jv_keys(jv_copy(a));
254+
jv keys_b = jv_keys(jv_copy(b));
255+
- r = jv_cmp(jv_copy(keys_a), keys_b);
256+
+ r = jvp_cmp(jv_copy(keys_a), keys_b, depth + 1);
257+
if (r == 0) {
258+
jv_array_foreach(keys_a, i, key) {
259+
jv xa = jv_object_get(jv_copy(a), jv_copy(key));
260+
jv xb = jv_object_get(jv_copy(b), key);
261+
- r = jv_cmp(xa, xb);
262+
+ r = jvp_cmp(xa, xb, depth + 1);
263+
if (r) break;
264+
}
265+
}
266+
@@ -683,19 +709,32 @@ struct sort_entry {
267+
int index;
268+
};
269+
270+
+static void sort_entry_array_free(struct sort_entry* entries, int start, int n) {
271+
+ for (int i = start; i < n; i++) {
272+
+ jv_free(entries[i].key);
273+
+ jv_free(entries[i].object);
274+
+ }
275+
+ jv_mem_free(entries);
276+
+}
277+
+
278+
static int sort_cmp(const void* pa, const void* pb) {
279+
const struct sort_entry* a = pa;
280+
const struct sort_entry* b = pb;
281+
int r = jv_cmp(jv_copy(a->key), jv_copy(b->key));
282+
+ if (r == INT_MIN) {
283+
+ sort_cmp_state.too_deep = 1;
284+
+ return 0;
285+
+ }
286+
// comparing by index if r == 0 makes the sort stable
287+
return r ? r : (a->index - b->index);
288+
}
289+
290+
-static struct sort_entry* sort_items(jv objects, jv keys) {
291+
+static struct sort_entry* sort_items(jv objects, jv keys, int *too_deep) {
292+
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
293+
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
294+
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
295+
int n = jv_array_length(jv_copy(objects));
296+
+ *too_deep = 0;
297+
struct sort_entry* entries = jv_mem_calloc(n, sizeof(struct sort_entry));
298+
for (int i=0; i<n; i++) {
299+
entries[i].object = jv_array_get(jv_copy(objects), i);
300+
@@ -704,7 +743,13 @@ static struct sort_entry* sort_items(jv objects, jv keys) {
301+
}
302+
jv_free(objects);
303+
jv_free(keys);
304+
+ sort_cmp_state.too_deep = 0;
305+
qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
306+
+ if (sort_cmp_state.too_deep) {
307+
+ sort_entry_array_free(entries, 0, n);
308+
+ *too_deep = 1;
309+
+ return NULL;
310+
+ }
311+
return entries;
312+
}
313+
314+
@@ -713,7 +758,10 @@ jv jv_sort(jv objects, jv keys) {
315+
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
316+
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
317+
int n = jv_array_length(jv_copy(objects));
318+
- struct sort_entry* entries = sort_items(objects, keys);
319+
+ int too_deep = 0;
320+
+ struct sort_entry* entries = sort_items(objects, keys, &too_deep);
321+
+ if (too_deep)
322+
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
323+
jv ret = jv_array();
324+
for (int i=0; i<n; i++) {
325+
jv_free(entries[i].key);
326+
@@ -728,13 +776,24 @@ jv jv_group(jv objects, jv keys) {
327+
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
328+
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
329+
int n = jv_array_length(jv_copy(objects));
330+
- struct sort_entry* entries = sort_items(objects, keys);
331+
+ int too_deep = 0;
332+
+ struct sort_entry* entries = sort_items(objects, keys, &too_deep);
333+
+ if (too_deep)
334+
+ return jv_invalid_with_msg(jv_string("Comparison too deep"));
335+
jv ret = jv_array();
336+
if (n > 0) {
337+
jv curr_key = entries[0].key;
338+
jv group = jv_array_append(jv_array(), entries[0].object);
339+
for (int i = 1; i < n; i++) {
340+
- if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
341+
+ int equal = jv_equal(jv_copy(curr_key), jv_copy(entries[i].key));
342+
+ if (equal < 0) {
343+
+ jv_free(curr_key);
344+
+ jv_free(group);
345+
+ sort_entry_array_free(entries, i, n);
346+
+ jv_free(ret);
347+
+ return jv_invalid_with_msg(jv_string("Equality check too deep"));
348+
+ }
349+
+ if (equal) {
350+
jv_free(entries[i].key);
351+
} else {
352+
jv_free(curr_key);
353+
diff --git a/tests/jq.test b/tests/jq.test
354+
index 7bc6f06..bbb120f 100644
355+
--- a/tests/jq.test
356+
+++ b/tests/jq.test
357+
@@ -2173,3 +2173,25 @@ null
358+
try ((reduce range(10001) as $_ ({}; {a: .})) as $x | $x * $x) catch .
359+
null
360+
"Object merge too deep"
361+
+
362+
+# regression test for deep structural equality recursion
363+
+try ((reduce range(10001) as $_ ([]; [.])) as $x | (reduce range(10001) as $_ ([]; [.])) as $y | $x == $y) catch .
364+
+null
365+
+"Equality check too deep"
366+
+
367+
+# regression tests for deep ordering comparisons
368+
+try ((reduce range(10001) as $_ ([]; [.])) as $x | [$x, $x] | sort) catch .
369+
+null
370+
+"Comparison too deep"
371+
+
372+
+try ((reduce range(10001) as $_ ([]; [.])) as $x | [$x, $x] | unique) catch .
373+
+null
374+
+"Comparison too deep"
375+
+
376+
+try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | sort) catch .
377+
+null
378+
+"Comparison too deep"
379+
+
380+
+try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | unique) catch .
381+
+null
382+
+"Comparison too deep"
383+
--
384+
2.45.4
385+

0 commit comments

Comments
 (0)