jq: upgrade 1.8.1 -> 1.8.2

Dropped patches that are part of this version.

Release Notes:
https://github.com/jqlang/jq/releases/tag/jq-1.8.2

PTESTS passed:

root@qemuarm64:~# ptest-runner jq
START: ptest-runner
2026-07-24T02:00
BEGIN: /usr/lib/jq/ptest
...
...
END: /usr/lib/jq/ptest
2026-07-24T02:00
STOP: ptest-runner
TOTAL: 1 FAIL: 0

Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
Signed-off-by: Khem Raj <khem.raj@oss.qualcomm.com>
This commit is contained in:
Ankur Tyagi
2026-07-24 14:14:03 +12:00
committed by Khem Raj
parent ec1029f71b
commit 64a7f6443a
7 changed files with 1 additions and 772 deletions
@@ -1,44 +0,0 @@
From 27f417f4812e688a59fc5186b7768cec004cd6e5 Mon Sep 17 00:00:00 2001
From: Peter Kjellerstedt <peter.kjellerstedt@gmail.com>
Date: Wed, 8 Apr 2026 05:58:49 +0200
Subject: [PATCH] Support building with --disable-maintainer-mode and source !=
build dir (#3518)
If --disable-maintainer-mode is enabled, then the rules for generating
parser.[ch] and lexer.[ch] did nothing. This worked fine if the source
and build directories are the same as the pre-generated parser.c and
lexer.c files would suffice. However, if the build directory is not the
same as the source directory, then the rest of the Make rules expect
parser.[ch] and lexer.[ch] to have been created in the build directory
if their source files (parser.y and lexer.l) are newer than the target
files, which can happen in case the source is fetched using Git.
Avoid the problem by copying the files to the build directory if needed.
Co-authored-by: Peter Kjellerstedt <pkj@axis.com>
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/27f417f4812e688a59fc5186b7768cec004cd6e5]
---
Makefile.am | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 96d6038..acb9443 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -41,9 +41,14 @@ src/lexer.h: src/lexer.c
else
BUILT_SOURCES = src/builtin.inc src/config_opts.inc src/version.h
.y.c:
- $(AM_V_YACC) echo "NOT building parser.c!"
+ $(AM_V_YACC) [ "$(<D)" = "$(@D)" ] || cp $(<D)/$(@F) $@
+ $(AM_V_YACC) [ "$(<D)" = "$(@D)" ] || cp $(<D)/$(*F).h $*.h
+ $(AM_V_YACC) touch $@ $*.h
+
.l.c:
- $(AM_V_LEX) echo "NOT building lexer.c!"
+ $(AM_V_LEX) [ "$(<D)" = "$(@D)" ] || cp $(<D)/$(@F) $@
+ $(AM_V_LEX) [ "$(<D)" = "$(@D)" ] || cp $(<D)/$(*F).h $*.h
+ $(AM_V_LEX) touch $@ $*.h
endif
# Tell YACC (Bison) autoconf macros that you want a header file created.
@@ -1,53 +0,0 @@
From 321e62b356df2d4ed47aba4f3818e447ec4d77fc Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Thu, 12 Mar 2026 20:28:43 +0900
Subject: [PATCH] Fix heap buffer overflow in `jvp_string_append` and
`jvp_string_copy_replace_bad`
In `jvp_string_append`, the allocation size `(currlen + len) * 2` could
overflow `uint32_t` when `currlen + len` exceeds `INT_MAX`, causing a small
allocation followed by a large `memcpy`.
In `jvp_string_copy_replace_bad`, the output buffer size calculation
`length * 3 + 1` could overflow `uint32_t`, again resulting in a small
allocation followed by a large write.
Add overflow checks to both functions to return an error for strings
that would exceed `INT_MAX` in length. Fixes CVE-2026-32316.
CVE: CVE-2026-32316
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/e47e56d226519635768e6aab2f38f0ab037c09e5]
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
src/jv.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/jv.c b/src/jv.c
index e4529a4..74be05a 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -1114,7 +1114,12 @@ static jv jvp_string_copy_replace_bad(const char* data, uint32_t length) {
const char* end = data + length;
const char* i = data;
- uint32_t maxlength = length * 3 + 1; // worst case: all bad bytes, each becomes a 3-byte U+FFFD
+ // worst case: all bad bytes, each becomes a 3-byte U+FFFD
+ uint64_t maxlength = (uint64_t)length * 3 + 1;
+ if (maxlength >= INT_MAX) {
+ return jv_invalid_with_msg(jv_string("String too long"));
+ }
+
jvp_string* s = jvp_string_alloc(maxlength);
char* out = s->data;
int c = 0;
@@ -1174,6 +1179,10 @@ static uint32_t jvp_string_remaining_space(jvp_string* s) {
static jv jvp_string_append(jv string, const char* data, uint32_t len) {
jvp_string* s = jvp_string_ptr(string);
uint32_t currlen = jvp_string_length(s);
+ if ((uint64_t)currlen + len >= INT_MAX) {
+ jv_free(string);
+ return jv_invalid_with_msg(jv_string("String too long"));
+ }
if (jvp_refcnt_unshared(string.u.ptr) &&
jvp_string_remaining_space(s) >= len) {
@@ -1,104 +0,0 @@
From 5fd935884a6f5b3d8ecdcacfc5d3982140f3a478 Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Mon, 13 Apr 2026 11:23:40 +0900
Subject: [PATCH] Limit path depth to prevent stack overflow
Deeply nested path arrays can cause unbounded recursion in
`jv_setpath`, `jv_getpath`, and `jv_delpaths`, leading to
stack overflow. Add a depth limit of 10000 to match the
existing `tojson` depth limit. This fixes CVE-2026-33947.
CVE: CVE-2026-33947
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/fb59f1491058d58bdc3e8dd28f1773d1ac690a1f]
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
src/jv_aux.c | 21 +++++++++++++++++++++
tests/jq.test | 25 +++++++++++++++++++++++++
2 files changed, 46 insertions(+)
diff --git a/src/jv_aux.c b/src/jv_aux.c
index bc1405f..594a21f 100644
--- a/src/jv_aux.c
+++ b/src/jv_aux.c
@@ -375,6 +375,10 @@ static jv jv_dels(jv t, jv keys) {
return t;
}
+#ifndef MAX_PATH_DEPTH
+#define MAX_PATH_DEPTH (10000)
+#endif
+
jv jv_setpath(jv root, jv path, jv value) {
if (jv_get_kind(path) != JV_KIND_ARRAY) {
jv_free(value);
@@ -382,6 +386,12 @@ jv jv_setpath(jv root, jv path, jv value) {
jv_free(path);
return jv_invalid_with_msg(jv_string("Path must be specified as an array"));
}
+ if (jv_array_length(jv_copy(path)) > MAX_PATH_DEPTH) {
+ jv_free(value);
+ jv_free(root);
+ jv_free(path);
+ return jv_invalid_with_msg(jv_string("Path too deep"));
+ }
if (!jv_is_valid(root)){
jv_free(value);
jv_free(path);
@@ -434,6 +444,11 @@ jv jv_getpath(jv root, jv path) {
jv_free(path);
return jv_invalid_with_msg(jv_string("Path must be specified as an array"));
}
+ if (jv_array_length(jv_copy(path)) > MAX_PATH_DEPTH) {
+ jv_free(root);
+ jv_free(path);
+ return jv_invalid_with_msg(jv_string("Path too deep"));
+ }
if (!jv_is_valid(root)) {
jv_free(path);
return root;
@@ -511,6 +526,12 @@ jv jv_delpaths(jv object, jv paths) {
jv_free(elem);
return err;
}
+ if (jv_array_length(jv_copy(elem)) > MAX_PATH_DEPTH) {
+ jv_free(object);
+ jv_free(paths);
+ jv_free(elem);
+ return jv_invalid_with_msg(jv_string("Path too deep"));
+ }
jv_free(elem);
}
if (jv_array_length(jv_copy(paths)) == 0) {
diff --git a/tests/jq.test b/tests/jq.test
index 4ecf72f..6186d8b 100644
--- a/tests/jq.test
+++ b/tests/jq.test
@@ -2507,3 +2507,28 @@ strflocaltime("" | ., @uri)
0
""
""
+
+# regression test for CVE-2026-33947
+setpath([range(10000) | 0]; 0) | flatten
+null
+[0]
+
+try setpath([range(10001) | 0]; 0) catch .
+null
+"Path too deep"
+
+getpath([range(10000) | 0])
+null
+null
+
+try getpath([range(10001) | 0]) catch .
+null
+"Path too deep"
+
+delpaths([[range(10000) | 0]])
+null
+null
+
+try delpaths([[range(10001) | 0]]) catch .
+null
+"Path too deep"
@@ -1,49 +0,0 @@
From 19a792c4cdb6b91c056eac033ac3367af6e67755 Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Mon, 13 Apr 2026 08:46:11 +0900
Subject: [PATCH] Fix NUL truncation in the JSON parser
This fixes CVE-2026-33948.
CVE: CVE-2026-33948
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/6374ae0bcdfe33a18eb0ae6db28493b1f34a0a5b]
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
src/util.c | 8 +-------
tests/shtest | 6 ++++++
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/util.c b/src/util.c
index bcb86da..60ec4d5 100644
--- a/src/util.c
+++ b/src/util.c
@@ -309,13 +309,7 @@ static int jq_util_input_read_more(jq_util_input_state *state) {
if (p != NULL)
state->current_line++;
- if (p == NULL && state->parser != NULL) {
- /*
- * There should be no NULs in JSON texts (but JSON text
- * sequences are another story).
- */
- state->buf_valid_len = strlen(state->buf);
- } else if (p == NULL && feof(state->current_input)) {
+ if (p == NULL && feof(state->current_input)) {
size_t i;
/*
diff --git a/tests/shtest b/tests/shtest
index 887a6bb..a046afe 100755
--- a/tests/shtest
+++ b/tests/shtest
@@ -842,4 +842,10 @@ if ! $msys && ! $mingw; then
fi
fi
+# CVE-2026-33948: No NUL truncation in the JSON parser
+if printf '{}\x00{}' | $JQ >/dev/null 2> /dev/null; then
+ printf 'Error expected but jq exited successfully\n' 1>&2
+ exit 1
+fi
+
exit 0
@@ -1,31 +0,0 @@
From ac09f274b6c029a23e3dffc38afac819b5daacc4 Mon Sep 17 00:00:00 2001
From: itchyny <itchyny@cybozu.co.jp>
Date: Mon, 13 Apr 2026 11:04:52 +0900
Subject: [PATCH] Fix out-of-bounds read in jv_parse_sized()
This fixes CVE-2026-39979.
Co-authored-by: Mattias Wadman <mattias.wadman@gmail.com>
CVE: CVE-2026-39979
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/2f09060afab23fe9390cce7cb860b10416e1bf5f]
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
src/jv_parse.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/jv_parse.c b/src/jv_parse.c
index ffcf51f..e6b8aa9 100644
--- a/src/jv_parse.c
+++ b/src/jv_parse.c
@@ -892,8 +892,9 @@ jv jv_parse_sized_custom_flags(const char* string, int length, int flags) {
if (!jv_is_valid(value) && jv_invalid_has_msg(jv_copy(value))) {
jv msg = jv_invalid_get_msg(value);
- value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%s')",
+ value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%.*s')",
jv_string_value(msg),
+ length,
string));
jv_free(msg);
}
@@ -1,484 +0,0 @@
From 63ae676bc6e97635d8246c78f5947ec47eb85a26 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.
Signed-off-by: Anton Skorup <antonsk@axis.com>
Upstream-Status: Backport [https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831]
---
src/builtin.c | 36 +++++++++++++++--
src/jv.c | 46 +++++++++++++++++-----
src/jv_aux.c | 105 ++++++++++++++++++++++++++++++++++++++++++--------
tests/jq.test | 40 +++++++++++++++++++
4 files changed, 198 insertions(+), 29 deletions(-)
diff --git a/src/builtin.c b/src/builtin.c
index ac56f9f..ac25f90 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) ||
@@ -845,6 +861,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;
@@ -1136,6 +1158,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 f701b46..e079d08 100644
--- a/src/jv.c
+++ b/src/jv.c
@@ -912,16 +912,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;
}
@@ -1071,7 +1075,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;
@@ -1828,7 +1839,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++) {
@@ -1837,7 +1848,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;
@@ -2032,7 +2044,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;
@@ -2048,13 +2069,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;
@@ -2066,6 +2087,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 594a21f..a39f1f1 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"));
@@ -471,8 +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)))
- j++;
+ jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)) == 1);
// if i <= entry < j, then entry starts with key
if (delkey) {
// deleting this entire key, we don't care about any more specific deletions
@@ -606,7 +623,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 +644,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 +674,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 +685,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 +705,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 +717,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 +756,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 +771,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 +789,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 +828,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 {
diff --git a/tests/jq.test b/tests/jq.test
index 5013bce..f35ef94 100644
--- a/tests/jq.test
+++ b/tests/jq.test
@@ -2560,3 +2560,43 @@ null
try delpaths([[range(10001) | 0]]) catch .
null
"Path too deep"
+
+# regression test for CVE-2026-40612
+reduce range(10000) as $_ ([]; [.]) | contains([[]])
+null
+true
+
+try (reduce range(10001) as $_ ([]; [.]) as $x | $x | contains($x)) catch .
+null
+"Containment check too deep"
+
+# regression test for CVE-2026-43896
+reduce range(10000) as $_ ({}; {a: .}) as $x | $x * $x | length
+null
+1
+
+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.43.0
@@ -8,16 +8,10 @@ SECTION = "utils"
LICENSE = "BSD-2-Clause AND MIT"
LIC_FILES_CHKSUM = "file://COPYING;md5=cf7fcb0a1def4a7ad62c028f7d0dca47"
SRCREV = "4467af7068b1bcd7f882defff6e7ea674c5357f4"
SRCREV = "34f7186b86743a083a589741b6cea95293524108"
SRC_URI = "git://github.com/jqlang/jq.git;protocol=https;branch=master;tag=jq-${PV} \
file://run-ptest \
file://0001-Support-building-with-disable-maintainer-mode-and-so.patch \
file://CVE-2026-32316.patch \
file://CVE-2026-33947.patch \
file://CVE-2026-33948.patch \
file://CVE-2026-39979.patch \
file://CVE-2026-47770.patch \
"
inherit autotools ptest