Version in base suite: 1.7.1-6+deb13u2 Base version: jq_1.7.1-6+deb13u2 Target version: jq_1.7.1-6+deb13u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/j/jq/jq_1.7.1-6+deb13u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/j/jq/jq_1.7.1-6+deb13u3.dsc changelog | 30 + patches/CVE-2024-53427.patch | 104 ++- patches/CVE-2026-32316.patch | 50 + patches/CVE-2026-40612.patch | 134 ++++ patches/CVE-2026-41256.patch | 43 + patches/CVE-2026-41257.patch | 48 + patches/CVE-2026-43894.patch | 45 + patches/CVE-2026-43895.patch | 178 +++++ patches/CVE-2026-43896.patch | 80 ++ patches/CVE-2026-44777.patch | 210 +++++++ patches/CVE-2026-47770.patch | 418 ++++++++++++++ patches/CVE-2026-49839.patch | 27 patches/CVE-2026-54679.patch | 61 ++ patches/GHSA-gf4g-95wj-4q4r.patch | 121 ++++ patches/GHSA-ggc9-rpv2-xgpm_and_GHSA-gvwx-xj9r-3frq.patch | 32 + patches/fix-abort-on-string-repeat-overflow.patch | 77 ++ patches/fix-invalid-jv-propagation.patch | 155 +++++ patches/series | 15 18 files changed, 1809 insertions(+), 19 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpjzg8ih81/jq_1.7.1-6+deb13u2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpjzg8ih81/jq_1.7.1-6+deb13u3.dsc: no acceptable signature found diff -Nru jq-1.7.1/debian/changelog jq-1.7.1/debian/changelog --- jq-1.7.1/debian/changelog 2026-04-17 09:08:56.000000000 +0000 +++ jq-1.7.1/debian/changelog 2026-08-04 12:56:56.000000000 +0000 @@ -1,3 +1,33 @@ +jq (1.7.1-6+deb13u3) trixie-security; urgency=high + + * Non-maintainer upload by the Security Team. + * Cherry-pick upstream commit for the following: + CVE-2026-41256, CVE-2026-41257, CVE-2026-43896, CVE-2026-43895, CVE-2026-44777, + CVE-2026-43894, CVE-2026-47770, CVE-2026-49839, CVE-2026-54679, CVE-2026-40612, + GHSA-ggc9-rpv2-xgpm, GHSA-gvwx-xj9r-3frq, GHSA-gf4g-95wj-4q4r + * Add missing patch for CVE-2026-32316, a prerequisite for CVE-2026-54679 fix. + * Fix CVE-2024-53427 for real. The patch carried since 1.7.1-5 placed the + NaN payload check inside the DEC_Conversion_syntax branch, which already + returns JV_INVALID unconditionally, so it never had any effect and + "NaN123" still parsed. Move the check to the decNumberIsNaN branch as + upstream does, and update the two tests that encoded the old behaviour. + * Do not abort when repeating a string past the length bound. The + CVE-2026-32316 fix made jvp_string_append() able to return an invalid jv; + binop_multiply() appended in a loop without checking, so an input like + {"s":"abc","n":1000000000} with a filter of .s * .n aborted on an + assertion. Reject the operation up front and stop the loop on failure. + * Propagate invalid jv instead of aborting on it. The same change of + contract affects jvp_string_append(), jv_string_concat() and jv_sort(); + callers written against the old always-valid contract abort on an + assertion. Guard centrally in jv.c so the @base64, @csv, @tsv, @sh, @uri + and escape_string loops are all covered, and guard jv_delpaths(), + jv_dump_string_trunc() and the jv_dump_string() results printed by + main.c. delpaths and the error-message paths are regressions against + previous version; the string-format ones replace the CVE-2026-32316 + integer overflow with a proper error. + + -- Aron Xu Tue, 04 Aug 2026 20:56:56 +0800 + jq (1.7.1-6+deb13u2) trixie; urgency=medium * Cherry-pick upstream commit for the following: diff -Nru jq-1.7.1/debian/patches/CVE-2024-53427.patch jq-1.7.1/debian/patches/CVE-2024-53427.patch --- jq-1.7.1/debian/patches/CVE-2024-53427.patch 2026-04-17 09:08:56.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2024-53427.patch 2026-08-04 12:56:56.000000000 +0000 @@ -1,25 +1,91 @@ -From: =?utf-8?b?IkNoYW5nWmh1byBDaGVuICjpmbPmmIzlgKwpIg==?= - -Date: Sat, 12 Apr 2025 15:58:51 +0800 -Subject: Reject NaN with payload while parsing JSON +Description: Reject NaN with payload while parsing JSON (CVE-2024-53427) + decNumberFromString() parses "NaN123" as a NaN carrying the payload 123. + Such values then flow into the comparison and printing paths, where the + payload digits are treated as significant, and CVE-2024-53427 is the + resulting unbounded recursion. + . + Upstream rejects a literal NaN that carries a payload, and returns a plain + native NaN for a bare "NaN". The check belongs in a decNumberIsNaN() branch: + decNumberFromString() reports a *successful* conversion for "NaN123", so + DEC_Conversion_syntax is not set and that branch is never taken for the + inputs this CVE is about. + . + The version of this patch shipped in 1.7.1-5 (and hence in trixie as + 1.7.1-6+deb13u2) placed the payload check inside the DEC_Conversion_syntax + branch, which already returns JV_INVALID unconditionally on the two lines + that follow it. The added block was therefore a no-op and the issue remained + unfixed: "NaN123" still parsed. Verified against the built package -- + echo NaN123 | jq . printed null with exit status 0. This revision moves the + check to the correct branch, which makes the same input fail to parse. + . + The tests/jq.test stanza that asserted "nan1234" parses, and the + CVE-2023-50268 Nan4000 case in tests/shtest, both encode the old behaviour + and are updated as upstream does. +Author: ChangZhuo Chen (陳昌倬) +Origin: upstream, https://github.com/jqlang/jq/commit/a09a4dfd55e6c24d04b35062ccfe4509748b1dd3 +Origin: upstream, https://github.com/jqlang/jq/commit/b86ff49f46a4a37e5a8e75a140cb5fd6e1331384 +Applied-Upstream: 1.8.0 +Bug: https://github.com/jqlang/jq/issues/3196 +Forwarded: not-needed +Comment: The decNumberIsNaN() branch is taken from b86ff49, which upstream + introduced as part of a jv_number_value() caching refactor. Only the branch + itself is backported here; the caching change is a performance optimisation + that is not needed for the fix and would touch the jv number representation. ---- - src/jv.c | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/src/jv.c b/src/jv.c -index e23d8ec..7f798d8 100644 ---- a/src/jv.c -+++ b/src/jv.c -@@ -586,6 +586,11 @@ static jv jvp_literal_number_new(const char * literal) { - n->num_double = NAN; - - if (ctx->status & DEC_Conversion_syntax) { +Index: jq-1.7.1/src/jv.c +=================================================================== +--- jq-1.7.1.orig/src/jv.c ++++ jq-1.7.1/src/jv.c +@@ -589,6 +589,15 @@ static jv jvp_literal_number_new(const c + jv_mem_free(n); + 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_INVALID; - } ++ jv_mem_free(n); ++ return jv_number(NAN); ++ } + + jv r = {JVP_FLAGS_NUMBER_LITERAL, 0, 0, JV_NUMBER_SIZE_INIT, {&n->refcnt}}; + return r; +Index: jq-1.7.1/tests/jq.test +=================================================================== +--- jq-1.7.1.orig/tests/jq.test ++++ jq-1.7.1/tests/jq.test +@@ -1938,10 +1938,14 @@ tojson | fromjson + {"a":nan} + {"a":null} + +-# also "nan with payload" #2985 +-fromjson | isnan +-"nan1234" ++# NaN with payload is not parsed ++.[] | try (fromjson | isnan) catch . ++["NaN","-NaN","NaN1","NaN10","NaN100"] + 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')" + + + # calling input/0, or debug/0 in a test doesn't crash jq +Index: jq-1.7.1/tests/shtest +=================================================================== +--- jq-1.7.1.orig/tests/shtest ++++ jq-1.7.1/tests/shtest +@@ -594,10 +594,6 @@ if ! x=$($JQ -n "1 # foo$cr + 2") || [ " + exit 1 + fi + +-# CVE-2023-50268: No stack overflow comparing a nan with a large payload +-$VALGRIND $Q $JQ '1 != .' <<\EOF >/dev/null +-Nan4000 +-EOF + + # Allow passing the inline jq script before -- #2919 + if ! r=$($JQ --args -rn -- '$ARGS.positional[0]' bar) || [ "$r" != bar ]; then diff -Nru jq-1.7.1/debian/patches/CVE-2026-32316.patch jq-1.7.1/debian/patches/CVE-2026-32316.patch --- jq-1.7.1/debian/patches/CVE-2026-32316.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-32316.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,50 @@ +From: itchyny +Date: Thu, 12 Mar 2026 20:28:43 +0900 +Subject: 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. + +Origin: https://github.com/jqlang/jq/commit/e47e56d226519635768e6aab2f38f0ab037c09e5 +--- + src/jv.c | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +Index: jq-1.7.1/src/jv.c +=================================================================== +--- jq-1.7.1.orig/src/jv.c ++++ jq-1.7.1/src/jv.c +@@ -1105,7 +1105,12 @@ static jv jvp_string_copy_replace_bad(co + 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; +@@ -1165,6 +1170,10 @@ static uint32_t jvp_string_remaining_spa + 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) { diff -Nru jq-1.7.1/debian/patches/CVE-2026-40612.patch jq-1.7.1/debian/patches/CVE-2026-40612.patch --- jq-1.7.1/debian/patches/CVE-2026-40612.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-40612.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,134 @@ +From: itchyny +Date: Fri, 24 Apr 2026 22:02:24 +0900 +Subject: Limit the containment check depth + +This fixes CVE-2026-40612. + +Origin: https://github.com/jqlang/jq/commit/d1a12569d91641135976a8536776a4a329c02cc2 +(cherry picked from commit d1a12569d91641135976a8536776a4a329c02cc2) + +[Rebased for 1.7.1: the two upstream regression tests were reparenthesised as +"(reduce ...) as $x" because the jq 1.7 grammar only accepts a Term before +"as", unlike the 1.8 grammar used upstream.] +--- +Index: jq-1.7.1/src/builtin.c +=================================================================== +--- jq-1.7.1.orig/src/builtin.c ++++ jq-1.7.1/src/builtin.c +@@ -487,7 +487,10 @@ jv binop_greatereq(jv a, jv b) { + + static jv f_contains(jq_state *jq, jv a, jv b) { + if (jv_get_kind(a) == jv_get_kind(b)) { +- return jv_bool(jv_contains(a, b)); ++ int r = jv_contains(a, b); ++ if (r < 0) ++ return jv_invalid_with_msg(jv_string("Containment check too deep")); ++ return jv_bool(r); + } else { + return type_error2(a, b, "cannot have their containment checked"); + } +Index: jq-1.7.1/src/jv.c +=================================================================== +--- jq-1.7.1.orig/src/jv.c ++++ jq-1.7.1/src/jv.c +@@ -917,19 +917,19 @@ static void jvp_clamp_slice_params(int l + } + + +-static int jvp_array_contains(jv a, jv b) { ++static int jvp_contains(jv a, jv b, int depth); ++ ++static int jvp_array_contains(jv a, jv b, int depth) { + int r = 1; + jv_array_foreach(b, bi, belem) { + int ri = 0; + jv_array_foreach(a, ai, aelem) { +- if (jv_contains(aelem, jv_copy(belem))) { +- ri = 1; +- break; +- } ++ ri = jvp_contains(aelem, jv_copy(belem), depth); ++ if (ri) break; + } + jv_free(belem); +- if (!ri) { +- r = 0; ++ if (ri <= 0) { ++ r = ri; + break; + } + } +@@ -1801,7 +1801,7 @@ static int jvp_object_equal(jv o1, jv o2 + return len1 == len2; + } + +-static int jvp_object_contains(jv a, jv b) { ++static int jvp_object_contains(jv a, jv b, int depth) { + assert(JVP_HAS_KIND(a, JV_KIND_OBJECT)); + assert(JVP_HAS_KIND(b, JV_KIND_OBJECT)); + int r = 1; +@@ -1809,9 +1809,9 @@ static int jvp_object_contains(jv a, jv + jv_object_foreach(b, key, b_val) { + jv a_val = jv_object_get(jv_copy(a), key); + +- r = jv_contains(a_val, b_val); ++ r = jvp_contains(a_val, b_val, depth); + +- if (!r) break; ++ if (r <= 0) break; + } + return r; + } +@@ -2077,14 +2077,23 @@ int jv_identical(jv a, jv b) { + return r; + } + +-int jv_contains(jv a, jv b) { ++#ifndef MAX_CONTAINS_DEPTH ++#define MAX_CONTAINS_DEPTH (10000) ++#endif ++ ++static int jvp_contains(jv a, jv b, int depth) { ++ if (depth > MAX_CONTAINS_DEPTH) { ++ jv_free(a); ++ jv_free(b); ++ return -1; ++ } + int r = 1; + if (jv_get_kind(a) != jv_get_kind(b)) { + r = 0; + } else if (JVP_HAS_KIND(a, JV_KIND_OBJECT)) { +- r = jvp_object_contains(a, b); ++ r = jvp_object_contains(a, b, depth + 1); + } else if (JVP_HAS_KIND(a, JV_KIND_ARRAY)) { +- r = jvp_array_contains(a, b); ++ r = jvp_array_contains(a, b, depth + 1); + } else if (JVP_HAS_KIND(a, JV_KIND_STRING)) { + int b_len = jv_string_length_bytes(jv_copy(b)); + if (b_len != 0) { +@@ -2100,3 +2109,8 @@ int jv_contains(jv a, jv b) { + jv_free(b); + return r; + } ++ ++// Returns 1 (contained), 0 (not contained), or -1 (too deep) ++int jv_contains(jv a, jv b) { ++ return jvp_contains(a, b, 0); ++} +Index: jq-1.7.1/tests/jq.test +=================================================================== +--- jq-1.7.1.orig/tests/jq.test ++++ jq-1.7.1/tests/jq.test +@@ -2188,3 +2188,12 @@ null + try ((reduce range(10001) as $_ ({}; {a: .})) as $x | [$x, $x] | unique) catch . + null + "Comparison 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" diff -Nru jq-1.7.1/debian/patches/CVE-2026-41256.patch jq-1.7.1/debian/patches/CVE-2026-41256.patch --- jq-1.7.1/debian/patches/CVE-2026-41256.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-41256.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,43 @@ +From: itchyny +Date: Fri, 24 Apr 2026 22:15:08 +0900 +Subject: Fix NUL truncation in program files loaded with -f + +This fixes CVE-2026-41256. + +Origin: https://github.com/jqlang/jq/commit/5a015deae35d19e3ebbc65db6c157a80e76df738 +--- + src/main.c | 8 ++++++++ + tests/shtest | 7 +++++++ + 2 files changed, 15 insertions(+) + +--- a/src/main.c ++++ b/src/main.c +@@ -677,6 +677,14 @@ int main(int argc, char* argv[]) { + ret = JQ_ERROR_SYSTEM; + goto out; + } ++ int len = jv_string_length_bytes(jv_copy(data)); ++ if ((size_t)len != strlen(jv_string_value(data))) { ++ fprintf(stderr, "jq: program file contains NUL bytes\n"); ++ free(program_origin); ++ jv_free(data); ++ ret = JQ_ERROR_SYSTEM; ++ goto out; ++ } + jq_set_attr(jq, jv_string("PROGRAM_ORIGIN"), jq_realpath(jv_string(dirname(program_origin)))); + ARGS = JV_OBJECT(jv_string("positional"), ARGS, + jv_string("named"), jv_copy(program_arguments)); +--- a/tests/shtest ++++ b/tests/shtest +@@ -620,4 +620,11 @@ if printf '{}\x00{}' | $JQ >/dev/null 2> + exit 1 + fi + ++# CVE-2026-41256: No NUL truncation in program files loaded with -f ++printf '.\x00invalid' > "$d/nul_prog.jq" ++if echo '42' | $JQ -f "$d/nul_prog.jq" >/dev/null 2>/dev/null; then ++ printf 'Error expected for program file with NUL bytes\n' 1>&2 ++ exit 1 ++fi ++ + exit 0 diff -Nru jq-1.7.1/debian/patches/CVE-2026-41257.patch jq-1.7.1/debian/patches/CVE-2026-41257.patch --- jq-1.7.1/debian/patches/CVE-2026-41257.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-41257.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,48 @@ +From: itchyny +Date: Fri, 24 Apr 2026 22:09:44 +0900 +Subject: Fix signed-int overflow in `stack_reallocate` + +This fixes CVE-2026-41257. + +Origin: https://github.com/jqlang/jq/commit/01b3cded76daacbfddb7f8763700b0803bcb5c6f +--- + src/exec_stack.h | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +--- a/src/exec_stack.h ++++ b/src/exec_stack.h +@@ -2,8 +2,10 @@ + #define EXEC_STACK_H + #include + #include ++#include + #include + #include ++#include + #include "jv_alloc.h" + + /* +@@ -81,15 +83,19 @@ static stack_ptr* stack_block_next(struc + } + + static void stack_reallocate(struct stack* s, size_t sz) { +- int old_mem_length = -(s->bound) + ALIGNMENT; +- char* old_mem_start = (s->mem_end != NULL) ? (s->mem_end - old_mem_length) : NULL; ++ size_t old_mem_length = (size_t)(-(s->bound)) + ALIGNMENT; ++ char* old_mem_start = s->mem_end != NULL ? s->mem_end - old_mem_length : NULL; + +- int new_mem_length = align_round_up((old_mem_length + sz + 256) * 2); ++ size_t new_mem_length = align_round_up((old_mem_length + sz + 256) * 2); ++ if (new_mem_length > INT_MAX) { ++ fprintf(stderr, "jq: error: cannot allocate memory\n"); ++ abort(); ++ } + char* new_mem_start = jv_mem_realloc(old_mem_start, new_mem_length); + memmove(new_mem_start + (new_mem_length - old_mem_length), + new_mem_start, old_mem_length); + s->mem_end = new_mem_start + new_mem_length; +- s->bound = -(new_mem_length - ALIGNMENT); ++ s->bound = -(int)(new_mem_length - ALIGNMENT); + } + + static stack_ptr stack_push_block(struct stack* s, stack_ptr p, size_t sz) { diff -Nru jq-1.7.1/debian/patches/CVE-2026-43894.patch jq-1.7.1/debian/patches/CVE-2026-43894.patch --- jq-1.7.1/debian/patches/CVE-2026-43894.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-43894.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,45 @@ +From: itchyny +Date: Wed, 6 May 2026 19:45:24 +0900 +Subject: Reject numeric literals longer than DEC_MAX_DIGITS (999999999) + +A signed-int overflow in decNumber's D2U macro lets huge literals +write attacker-controlled bytes past a stack buffer. Cap the length +before calling decNumberFromString, and pre-slice long strings in +jv_dump_string_trunc so the resulting error message doesn't itself +allocate a multi-GiB buffer. + +This fixes CVE-2026-43894. + +Origin: https://github.com/jqlang/jq/commit/9761ceb7d6cc48c16b25f0ab1baaef0e701927e4 +--- + src/jv.c | 5 ++++- + src/jv_print.c | 4 ++++ + 2 files changed, 8 insertions(+), 1 deletion(-) + +--- a/src/jv.c ++++ b/src/jv.c +@@ -580,7 +580,10 @@ static jvp_literal_number* jvp_literal_n + + static jv jvp_literal_number_new(const char * literal) { + +- jvp_literal_number * n = jvp_literal_number_alloc(strlen(literal)); ++ size_t len = strlen(literal); ++ if (len > DEC_MAX_DIGITS) ++ return JV_INVALID; ++ jvp_literal_number * n = jvp_literal_number_alloc(len); + + n->refcnt = JV_REFCNT_INIT; + n->literal_data = NULL; +--- a/src/jv_print.c ++++ b/src/jv_print.c +@@ -387,6 +387,10 @@ jv jv_dump_string(jv x, int flags) { + } + + char *jv_dump_string_trunc(jv x, char *outbuf, size_t bufsize) { ++ if (jv_get_kind(x) == JV_KIND_STRING && ++ (size_t)jv_string_length_bytes(jv_copy(x)) > bufsize) { ++ x = jv_string_slice(x, 0, bufsize); ++ } + x = jv_dump_string(x,0); + const char* p = jv_string_value(x); + const size_t len = strlen(p); diff -Nru jq-1.7.1/debian/patches/CVE-2026-43895.patch jq-1.7.1/debian/patches/CVE-2026-43895.patch --- jq-1.7.1/debian/patches/CVE-2026-43895.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-43895.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,178 @@ +From: itchyny +Date: Sat, 9 May 2026 17:08:43 +0900 +Subject: Reject embedded NUL bytes in module import paths + +jq accepts embedded NUL bytes at the language level but resolves +module import paths through NUL-terminated C strings, so the path +validated by policy or audit code could differ from the on-disk +path jq actually opens. Pass jv through gen_import so the AST +preserves the original bytes, and reject embedded NULs in +validate_relpath. + +This fixes CVE-2026-43895. + +Origin: https://github.com/jqlang/jq/commit/9d223f153c3632a207fa071caaa6292da33ae361 +--- + src/compile.c | 12 ++++++++---- + src/compile.h | 2 +- + src/linker.c | 6 +++++- + src/parser.c | 16 +++------------- + src/parser.y | 16 +++------------- + tests/shtest | 17 +++++++++++++++++ + 6 files changed, 33 insertions(+), 36 deletions(-) + +--- a/src/compile.c ++++ b/src/compile.c +@@ -526,13 +526,17 @@ jv block_module_meta(block b) { + return jv_null(); + } + +-block gen_import(const char* name, const char* as, int is_data) { ++block gen_import(jv name, jv as, int is_data) { ++ assert(jv_get_kind(name) == JV_KIND_STRING); ++ assert(!jv_is_valid(as) || jv_get_kind(as) == JV_KIND_STRING); + inst* i = inst_new(DEPS); + jv meta = jv_object(); +- if (as != NULL) +- meta = jv_object_set(meta, jv_string("as"), jv_string(as)); ++ if (jv_is_valid(as)) ++ meta = jv_object_set(meta, jv_string("as"), as); ++ else ++ jv_free(as); + meta = jv_object_set(meta, jv_string("is_data"), is_data ? jv_true() : jv_false()); +- meta = jv_object_set(meta, jv_string("relpath"), jv_string(name)); ++ meta = jv_object_set(meta, jv_string("relpath"), name); + i->imm.constant = meta; + return inst_block(i); + } +--- a/src/compile.h ++++ b/src/compile.h +@@ -33,7 +33,7 @@ block gen_op_pushk_under(jv constant); + + block gen_module(block metadata); + jv block_module_meta(block b); +-block gen_import(const char* name, const char *as, int is_data); ++block gen_import(jv name, jv as, int is_data); + block gen_import_meta(block import, block metadata); + block gen_function(const char* name, block formals, block body); + block gen_param_regular(const char* name); +--- a/src/linker.c ++++ b/src/linker.c +@@ -93,6 +93,10 @@ static jv build_lib_search_chain(jq_stat + // in between). + static jv validate_relpath(jv name) { + const char *s = jv_string_value(name); ++ if (strlen(s) != (size_t)jv_string_length_bytes(jv_copy(name))) { ++ jv_free(name); ++ return jv_invalid_with_msg(jv_string("Module path contains a NUL byte")); ++ } + if (strchr(s, '\\')) { + jv res = jv_invalid_with_msg(jv_string_fmt("Modules must be named by relative paths using '/', not '\\' (%s)", s)); + jv_free(name); +@@ -423,7 +427,7 @@ int load_program(jq_state *jq, struct lo + char* home = getenv("HOME"); + if (home) { // silently ignore no $HOME + /* Import ~/.jq as a library named "" found in $HOME */ +- block import = gen_import_meta(gen_import("", NULL, 0), ++ block import = gen_import_meta(gen_import(jv_string(""), jv_invalid(), 0), + gen_const(JV_OBJECT( + jv_string("optional"), jv_true(), + jv_string("search"), jv_string(home)))); +--- a/src/parser.c ++++ b/src/parser.c +@@ -3081,13 +3081,8 @@ yyreduce: + case 50: /* ImportWhat: "import" ImportFrom "as" BINDING */ + #line 506 "src/parser.y" + { +- jv v = block_const((yyvsp[-2].blk)); +- // XXX Make gen_import take only blocks and the int is_data so we +- // don't have to free so much stuff here +- (yyval.blk) = gen_import(jv_string_value(v), jv_string_value((yyvsp[0].literal)), 1); ++ (yyval.blk) = gen_import(block_const((yyvsp[-2].blk)), (yyvsp[0].literal), 1); + block_free((yyvsp[-2].blk)); +- jv_free((yyvsp[0].literal)); +- jv_free(v); + } + #line 3093 "src/parser.c" + break; +@@ -3095,11 +3090,8 @@ yyreduce: + case 51: /* ImportWhat: "import" ImportFrom "as" IDENT */ + #line 515 "src/parser.y" + { +- jv v = block_const((yyvsp[-2].blk)); +- (yyval.blk) = gen_import(jv_string_value(v), jv_string_value((yyvsp[0].literal)), 0); ++ (yyval.blk) = gen_import(block_const((yyvsp[-2].blk)), (yyvsp[0].literal), 0); + block_free((yyvsp[-2].blk)); +- jv_free((yyvsp[0].literal)); +- jv_free(v); + } + #line 3105 "src/parser.c" + break; +@@ -3107,10 +3099,8 @@ yyreduce: + case 52: /* ImportWhat: "include" ImportFrom */ + #line 522 "src/parser.y" + { +- jv v = block_const((yyvsp[0].blk)); +- (yyval.blk) = gen_import(jv_string_value(v), NULL, 0); ++ (yyval.blk) = gen_import(block_const((yyvsp[0].blk)), jv_invalid(), 0); + block_free((yyvsp[0].blk)); +- jv_free(v); + } + #line 3116 "src/parser.c" + break; +--- a/src/parser.y ++++ b/src/parser.y +@@ -504,26 +504,16 @@ ImportWhat Exp ';' { + + ImportWhat: + "import" ImportFrom "as" BINDING { +- jv v = block_const($2); +- // XXX Make gen_import take only blocks and the int is_data so we +- // don't have to free so much stuff here +- $$ = gen_import(jv_string_value(v), jv_string_value($4), 1); ++ $$ = gen_import(block_const($2), $4, 1); + block_free($2); +- jv_free($4); +- jv_free(v); + } | + "import" ImportFrom "as" IDENT { +- jv v = block_const($2); +- $$ = gen_import(jv_string_value(v), jv_string_value($4), 0); ++ $$ = gen_import(block_const($2), $4, 0); + block_free($2); +- jv_free($4); +- jv_free(v); + } | + "include" ImportFrom { +- jv v = block_const($2); +- $$ = gen_import(jv_string_value(v), NULL, 0); ++ $$ = gen_import(block_const($2), jv_invalid(), 0); + block_free($2); +- jv_free(v); + } + + ImportFrom: +--- a/tests/shtest ++++ b/tests/shtest +@@ -627,4 +627,21 @@ if echo '42' | $JQ -f "$d/nul_prog.jq" > + exit 1 + fi + ++# CVE-2026-43895: No NUL bytes in module/data import paths ++printf 'import "a\\u0000b" as $x; .' > "$d/nul_import.jq" ++if $JQ -nf "$d/nul_import.jq" >/dev/null 2>/dev/null; then ++ printf 'Error expected for import path with NUL bytes\n' 1>&2 ++ exit 1 ++fi ++printf 'include "a\\u0000b"; .' > "$d/nul_include.jq" ++if $JQ -nf "$d/nul_include.jq" >/dev/null 2>/dev/null; then ++ printf 'Error expected for include path with NUL bytes\n' 1>&2 ++ exit 1 ++fi ++printf '"a\\u0000b" | modulemeta' > "$d/nul_modulemeta.jq" ++if $JQ -nf "$d/nul_modulemeta.jq" >/dev/null 2>/dev/null; then ++ printf 'Error expected for modulemeta with NUL bytes\n' 1>&2 ++ exit 1 ++fi ++ + exit 0 diff -Nru jq-1.7.1/debian/patches/CVE-2026-43896.patch jq-1.7.1/debian/patches/CVE-2026-43896.patch --- jq-1.7.1/debian/patches/CVE-2026-43896.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-43896.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,80 @@ +From: itchyny +Date: Tue, 5 May 2026 22:44:02 +0900 +Subject: Limit recursive object merge depth to prevent stack overflow + +This fixes CVE-2026-43896. + +Origin: https://github.com/jqlang/jq/commit/532ccea6080ed6758f39fe9f6208a44b665023d2 +--- + src/jv.c | 25 +++++++++++++++++++++++-- + tests/jq.test | 9 +++++++++ + 2 files changed, 32 insertions(+), 2 deletions(-) + +Index: jq-1.7.1/src/jv.c +=================================================================== +--- jq-1.7.1.orig/src/jv.c ++++ jq-1.7.1/src/jv.c +@@ -1862,16 +1862,33 @@ jv jv_object_merge(jv a, jv b) { + return a; + } + +-jv jv_object_merge_recursive(jv a, jv b) { ++#ifndef MAX_OBJECT_MERGE_DEPTH ++#define MAX_OBJECT_MERGE_DEPTH (10000) ++#endif ++ ++static jv jvp_object_merge_recursive(jv a, jv b, int depth) { + assert(JVP_HAS_KIND(a, JV_KIND_OBJECT)); + assert(JVP_HAS_KIND(b, JV_KIND_OBJECT)); + ++ if (depth > MAX_OBJECT_MERGE_DEPTH) { ++ jv_free(a); ++ jv_free(b); ++ return jv_invalid_with_msg(jv_string("Object merge too deep")); ++ } ++ + jv_object_foreach(b, k, v) { + jv elem = jv_object_get(jv_copy(a), jv_copy(k)); + if (jv_is_valid(elem) && + JVP_HAS_KIND(elem, JV_KIND_OBJECT) && + JVP_HAS_KIND(v, JV_KIND_OBJECT)) { +- a = jv_object_set(a, k, jv_object_merge_recursive(elem, v)); ++ jv merged = jvp_object_merge_recursive(elem, v, depth + 1); ++ if (!jv_is_valid(merged)) { ++ jv_free(k); ++ jv_free(a); ++ jv_free(b); ++ return merged; ++ } ++ a = jv_object_set(a, k, merged); + } else { + jv_free(elem); + a = jv_object_set(a, k, v); +@@ -1882,6 +1899,10 @@ jv jv_object_merge_recursive(jv a, jv b) + return a; + } + ++jv jv_object_merge_recursive(jv a, jv b) { ++ return jvp_object_merge_recursive(a, b, 0); ++} ++ + /* + * Object iteration (internal helpers) + */ +Index: jq-1.7.1/tests/jq.test +=================================================================== +--- jq-1.7.1.orig/tests/jq.test ++++ jq-1.7.1/tests/jq.test +@@ -2157,3 +2157,12 @@ null + try delpaths([[range(10001) | 0]]) catch . + null + "Path 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" diff -Nru jq-1.7.1/debian/patches/CVE-2026-44777.patch jq-1.7.1/debian/patches/CVE-2026-44777.patch --- jq-1.7.1/debian/patches/CVE-2026-44777.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-44777.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,210 @@ +From: itchyny +Date: Mon, 11 May 2026 20:41:38 +0900 +Subject: Detect circular module imports to prevent stack overflow + +jq used to recurse without bound on mutual or self-referential +`import` declarations, exhausting the stack. Track each library's +load state with a `loading` flag set before its dependencies are +processed; a recursive reference to an in-progress library now +reports "circular import of X". + +This fixes CVE-2026-44777. + +Origin: https://github.com/jqlang/jq/commit/f58787c41835d9b17795730cb04925fdba25c71c +Reviewed-by: Aron Xu +--- + Makefile.am | 2 ++ + src/linker.c | 60 ++++++++++++++++++++++++++++++--------------- + tests/modules/cycle_a.jq | 2 ++ + tests/modules/cycle_b.jq | 2 ++ + tests/modules/cycle_self.jq | 2 ++ + tests/shtest | 23 +++++++++++++++++ + 6 files changed, 71 insertions(+), 20 deletions(-) + create mode 100644 tests/modules/cycle_a.jq + create mode 100644 tests/modules/cycle_b.jq + create mode 100644 tests/modules/cycle_self.jq + +--- a/Makefile.am ++++ b/Makefile.am +@@ -215,6 +215,8 @@ EXTRA_DIST = $(DOC_FILES) $(man_MANS) $( + tests/modules/test_bind_order0.jq \ + tests/modules/test_bind_order1.jq \ + tests/modules/test_bind_order2.jq \ ++ tests/modules/cycle_a.jq tests/modules/cycle_b.jq \ ++ tests/modules/cycle_self.jq \ + tests/onig.supp tests/local.supp \ + tests/setup tests/torture/input0.json \ + tests/optional.test tests/man.test tests/manonig.test \ +--- a/src/linker.c ++++ b/src/linker.c +@@ -21,9 +21,13 @@ + #include "compile.h" + #include "jv_alloc.h" + ++struct lib_entry { ++ char *name; ++ block def; ++ int loading; ++}; + struct lib_loading_state { +- char **names; +- block *defs; ++ struct lib_entry *entries; + uint64_t ct; + }; + static int load_library(jq_state *jq, jv lib_path, +@@ -303,14 +307,24 @@ static int process_dependencies(jq_state + } else { + uint64_t state_idx = 0; + for (; state_idx < lib_state->ct; ++state_idx) { +- if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0) ++ if (strcmp(lib_state->entries[state_idx].name, jv_string_value(resolved)) == 0) + break; + } + + if (state_idx < lib_state->ct) { // Found ++ if (lib_state->entries[state_idx].loading) { ++ jq_report_error(jq, jv_string_fmt("jq: error: circular import of %s\n", ++ jv_string_value(resolved))); ++ jv_free(resolved); ++ jv_free(as); ++ jv_free(deps); ++ jv_free(jq_origin); ++ jv_free(lib_origin); ++ return 1; ++ } + jv_free(resolved); + // Bind the library to the program +- bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, as_str); ++ bk = block_bind_library(lib_state->entries[state_idx].def, bk, OP_IS_CALL_PSEUDO, as_str); + } else { // Not found. Add it to the table before binding. + block dep_def_block = gen_noop(); + nerrors += load_library(jq, resolved, is_data, raw, optional, as_str, &dep_def_block, lib_state); +@@ -356,25 +370,35 @@ static int load_library(jq_state *jq, jv + } else if (is_data) { + // import "foo" as $bar; + program = gen_const_global(jv_copy(data), as); ++ state_idx = lib_state->ct++; ++ lib_state->entries = jv_mem_realloc(lib_state->entries, lib_state->ct * sizeof(struct lib_entry)); ++ lib_state->entries[state_idx].name = strdup(jv_string_value(lib_path)); ++ lib_state->entries[state_idx].def = program; ++ lib_state->entries[state_idx].loading = 0; + } else { + // import "foo" as bar; + src = locfile_init(jq, jv_string_value(lib_path), jv_string_value(data), jv_string_length_bytes(jv_copy(data))); + nerrors += jq_parse_library(src, &program); + locfile_free(src); + if (nerrors == 0) { ++ // Register the library before processing its dependencies so that ++ // circular imports can be detected. ++ state_idx = lib_state->ct++; ++ lib_state->entries = jv_mem_realloc(lib_state->entries, lib_state->ct * sizeof(struct lib_entry)); ++ lib_state->entries[state_idx].name = strdup(jv_string_value(lib_path)); ++ lib_state->entries[state_idx].def = gen_noop(); ++ lib_state->entries[state_idx].loading = 1; ++ + char *lib_origin = strdup(jv_string_value(lib_path)); + nerrors += process_dependencies(jq, jq_get_jq_origin(jq), + jv_string(dirname(lib_origin)), + &program, lib_state); + free(lib_origin); + program = block_bind_self(program, OP_IS_CALL_PSEUDO); ++ lib_state->entries[state_idx].def = program; ++ lib_state->entries[state_idx].loading = 0; + } + } +- state_idx = lib_state->ct++; +- lib_state->names = jv_mem_realloc(lib_state->names, lib_state->ct * sizeof(const char *)); +- lib_state->defs = jv_mem_realloc(lib_state->defs, lib_state->ct * sizeof(block)); +- lib_state->names[state_idx] = strdup(jv_string_value(lib_path)); +- lib_state->defs[state_idx] = program; + out: + *out_block = program; + jv_free(lib_path); +@@ -413,7 +437,7 @@ jv load_module_meta(jq_state *jq, jv mod + int load_program(jq_state *jq, struct locfile* src, block *out_block) { + int nerrors = 0; + block program; +- struct lib_loading_state lib_state = {0,0,0}; ++ struct lib_loading_state lib_state = {0,0}; + nerrors = jq_parse(src, &program); + if (nerrors) + return nerrors; +@@ -437,14 +461,13 @@ int load_program(jq_state *jq, struct lo + nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state); + block libs = gen_noop(); + for (uint64_t i = 0; i < lib_state.ct; ++i) { +- free(lib_state.names[i]); +- if (nerrors == 0 && !block_is_const(lib_state.defs[i])) +- libs = block_join(libs, lib_state.defs[i]); ++ free(lib_state.entries[i].name); ++ if (nerrors == 0 && !block_is_const(lib_state.entries[i].def)) ++ libs = block_join(libs, lib_state.entries[i].def); + else +- block_free(lib_state.defs[i]); ++ block_free(lib_state.entries[i].def); + } +- free(lib_state.names); +- free(lib_state.defs); ++ free(lib_state.entries); + if (nerrors) + block_free(program); + else +--- /dev/null ++++ b/tests/modules/cycle_a.jq +@@ -0,0 +1,2 @@ ++import "cycle_b" as b; ++def f: null; +--- /dev/null ++++ b/tests/modules/cycle_b.jq +@@ -0,0 +1,2 @@ ++import "cycle_a" as a; ++def f: null; +--- /dev/null ++++ b/tests/modules/cycle_self.jq +@@ -0,0 +1,2 @@ ++import "cycle_self" as s; ++def f: null; +--- a/tests/shtest ++++ b/tests/shtest +@@ -369,17 +369,40 @@ if ! HOME="$mods/home2" $VALGRIND $Q $JQ + exit 1 + fi + ++( + cd "$JQBASEDIR" # so that relative library paths are guaranteed correct + if ! $VALGRIND $Q $JQ -L ./tests/modules -ne 'import "test_bind_order" as check; check::check==true'; then + echo "Issue #817 regression?" 1>&2 + exit 1 + fi ++) + ++( + cd "$JQBASEDIR" + if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; check::check==true'; then + echo "Issue #817 regression?" 1>&2 + exit 1 + fi ++) ++ ++# CVE-2026-44777: Circular imports should be detected ++if $VALGRIND $JQ -L "$mods" -ne 'import "cycle_a" as a; null' 2> $d/out; then ++ echo "Mutual import should be rejected" 1>&2 ++ exit 1 ++fi ++if ! grep -q "circular import" $d/out; then ++ echo "Expected circular import error" 1>&2 ++ exit 1 ++fi ++ ++if $VALGRIND $JQ -L "$mods" -ne 'import "cycle_self" as s; null' 2> $d/out; then ++ echo "Self import should be rejected" 1>&2 ++ exit 1 ++fi ++if ! grep -q "circular import" $d/out; then ++ echo "Expected circular import error" 1>&2 ++ exit 1 ++fi + + ## Halt + diff -Nru jq-1.7.1/debian/patches/CVE-2026-47770.patch jq-1.7.1/debian/patches/CVE-2026-47770.patch --- jq-1.7.1/debian/patches/CVE-2026-47770.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-47770.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,418 @@ +From: Yu-Fu Fu +Date: Fri, 22 May 2026 04:07:16 -0700 +Subject: 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". + +This fixes CVE-2026-47770. + +Origin: https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831 +--- + src/builtin.c | 30 ++++++++++++++++++-- + src/jv.c | 46 +++++++++++++++++++++++------- + src/jv_aux.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- + tests/jq.test | 22 +++++++++++++++ + 4 files changed, 162 insertions(+), 25 deletions(-) + +Index: jq-1.7.1/src/builtin.c +=================================================================== +--- jq-1.7.1.orig/src/builtin.c ++++ jq-1.7.1/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) || +@@ -1062,6 +1078,14 @@ static jv minmax_by(jv values, jv keys, + for (int i=1; istring); + 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; +@@ -1988,7 +2001,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; +@@ -2004,13 +2026,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; +@@ -2022,6 +2044,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 +Index: jq-1.7.1/src/jv_aux.c +=================================================================== +--- jq-1.7.1.orig/src/jv_aux.c ++++ jq-1.7.1/src/jv_aux.c +@@ -15,6 +15,24 @@ static double jv_number_get_value_and_co + 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 + 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); +@@ -622,9 +646,9 @@ int jv_cmp(jv a, jv b) { + + 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 +672,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 +683,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 +703,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 +715,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 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); +Index: jq-1.7.1/tests/jq.test +=================================================================== +--- jq-1.7.1.orig/tests/jq.test ++++ jq-1.7.1/tests/jq.test +@@ -2166,3 +2166,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" diff -Nru jq-1.7.1/debian/patches/CVE-2026-49839.patch jq-1.7.1/debian/patches/CVE-2026-49839.patch --- jq-1.7.1/debian/patches/CVE-2026-49839.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-49839.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,27 @@ +From: itchyny +Date: Mon, 8 Jun 2026 22:14:48 +0900 +Subject: Fix heap-buffer-overflow in raw file loading + +When `jv_string_append_buf` overflows the string length limit, +it returns an invalid `jv`; `jv_load_file` then re-entered it +on the invalid value and overran the heap. Break out of the loop +once the value is invalid. + +Fixes CVE-2026-49839. + +Origin: https://github.com/jqlang/jq/commit/e987df0d463d85fd70825e042a082427e8275b86 +--- + src/jv_file.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/src/jv_file.c ++++ b/src/jv_file.c +@@ -57,6 +57,8 @@ jv jv_load_file(const char* filename, in + + if (raw) { + data = jv_string_append_buf(data, buf, n); ++ if (!jv_is_valid(data)) ++ break; + } else { + jv_parser_set_buf(parser, buf, n, !feof(file)); + jv value; diff -Nru jq-1.7.1/debian/patches/CVE-2026-54679.patch jq-1.7.1/debian/patches/CVE-2026-54679.patch --- jq-1.7.1/debian/patches/CVE-2026-54679.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/CVE-2026-54679.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,61 @@ +From: itchyny +Date: Tue, 16 Jun 2026 14:31:14 +0900 +Subject: Tighten string length bounds and propagate invalid jv in implode +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +The bound added in CVE-2026-32316 (e47e56d22) still allowed +`sizeof(jvp_string) + (currlen + len) * 2 + 1` to wrap `size_t` on +32-bit platforms. Tighten the threshold so the final allocation +fits in 32-bit `size_t`. + +Also break out of `jv_string_implode` and `f_string_implode` once +`jv_string_append_codepoint` returns an invalid `jv`; otherwise the +next iteration triggers the assertion in `jvp_string_ptr` (or +invokes undefined behavior under `-DNDEBUG`). + +Fixes CVE-2026-54679. + +Co-authored-by: Dirk Müller + +Origin: https://github.com/jqlang/jq/commit/46d1da30944ce93dd671ac72b6513fc0eb747837 +Reviewed-by: Aron Xu +--- + src/builtin.c | 1 + + src/jv.c | 3 ++- + 2 files changed, 3 insertions(+), 1 deletion(-) + +Index: jq-1.7.1/src/builtin.c +=================================================================== +--- jq-1.7.1.orig/src/builtin.c ++++ jq-1.7.1/src/builtin.c +@@ -1269,6 +1269,7 @@ static jv f_string_implode(jq_state *jq, + if (nv < 0 || nv > 0x10FFFF || (nv >= 0xD800 && nv <= 0xDFFF)) + nv = 0xFFFD; // U+FFFD REPLACEMENT CHARACTER + s = jv_string_append_codepoint(s, nv); ++ if (!jv_is_valid(s)) break; + } + + jv_free(a); +Index: jq-1.7.1/src/jv.c +=================================================================== +--- jq-1.7.1.orig/src/jv.c ++++ jq-1.7.1/src/jv.c +@@ -1170,7 +1170,7 @@ static uint32_t jvp_string_remaining_spa + 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) { ++ if ((uint64_t)currlen + len >= INT_MAX - sizeof(jvp_string) / 2) { + jv_free(string); + return jv_invalid_with_msg(jv_string("String too long")); + } +@@ -1424,6 +1424,7 @@ jv jv_string_implode(jv j) { + if (nv < 0 || nv > 0x10FFFF || (nv >= 0xD800 && nv <= 0xDFFF)) + nv = 0xFFFD; // U+FFFD REPLACEMENT CHARACTER + s = jv_string_append_codepoint(s, nv); ++ if (!jv_is_valid(s)) break; + } + + jv_free(j); diff -Nru jq-1.7.1/debian/patches/GHSA-gf4g-95wj-4q4r.patch jq-1.7.1/debian/patches/GHSA-gf4g-95wj-4q4r.patch --- jq-1.7.1/debian/patches/GHSA-gf4g-95wj-4q4r.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/GHSA-gf4g-95wj-4q4r.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,121 @@ +From: Scott Seal +Date: Mon, 9 Mar 2026 03:30:08 -0500 +Subject: Fix use-after-free in args2obj() array argument path (#3498) + +Copy `kk` and `vk` before passing to jv_object_get(), which consumes +its arguments. Without copies, subsequent loop iterations read freed +memory (CWE-416) and post-loop jv_free() double-frees (CWE-415). + +This bug was introduced in `b279713e` when array processing was hoisted +out of the loop into locals without accounting for jv_object_get() +semantics. + +Fixes GHSA-gf4g-95wj-4q4r. No CVE has been assigned. Reachable through +the public libjq jq_compile_args() API when it is passed an array of +{"name":..,"value":..} objects; the jq CLI itself always passes an +object, which returns early, so the jq binary is not affected. + +Origin: https://github.com/jqlang/jq/commit/3985b80ce50bd75c6eb5a97cb3348c3f835ca8e0 +--- +Index: jq-1.7.1/src/execute.c +=================================================================== +--- jq-1.7.1.orig/src/execute.c ++++ jq-1.7.1/src/execute.c +@@ -1220,7 +1220,7 @@ args2obj(jv args) + jv kk = jv_string("name"); + jv vk = jv_string("value"); + jv_array_foreach(args, i, v) +- r = jv_object_set(r, jv_object_get(jv_copy(v), kk), jv_object_get(v, vk)); ++ r = jv_object_set(r, jv_object_get(jv_copy(v), jv_copy(kk)), jv_object_get(v, jv_copy(vk))); + jv_free(args); + jv_free(kk); + jv_free(vk); +Index: jq-1.7.1/src/jq_test.c +=================================================================== +--- jq-1.7.1.orig/src/jq_test.c ++++ jq-1.7.1/src/jq_test.c +@@ -10,6 +10,7 @@ + + static void jv_test(); + static void run_jq_tests(jv, int, FILE *, int, int); ++static void run_jq_compile_args_tests(void); + #ifdef HAVE_PTHREAD + static void run_jq_pthread_tests(); + #endif +@@ -37,6 +38,7 @@ int jq_testsuite(jv libdirs, int verbose + } + } + run_jq_tests(libdirs, verbose, testdata, skip, take); ++ run_jq_compile_args_tests(); + #ifdef HAVE_PTHREAD + run_jq_pthread_tests(); + #endif +@@ -249,6 +251,68 @@ static void run_jq_tests(jv lib_dirs, in + } + + ++static void compile_args_and_check(jq_state *jq, const char *prog, ++ jv args, jv input, jv expected) { ++ printf(" subtest: %s\n", prog); ++ int compiled = jq_compile_args(jq, prog, args); ++ assert(compiled); ++ ++ jq_start(jq, input, 0); ++ jv result = jq_next(jq); ++ assert(jv_is_valid(result)); ++ assert(jv_equal(result, expected)); ++ ++ jv extra = jq_next(jq); ++ assert(!jv_is_valid(extra)); ++ jv_free(extra); ++} ++ ++// Test that jq_compile_args() with array arguments handles jv ++// refcounting correctly in args2obj(). The array path converts ++// [{"name":"k","value":"v"}, ...] into {"k":"v", ...}. ++static void run_jq_compile_args_tests(void) { ++ printf("Test jq_compile_args with array args\n"); ++ jq_state *jq = jq_init(); ++ assert(jq); ++ ++ // Empty array: loop body never runs, kk/vk allocated and freed ++ compile_args_and_check(jq, "42", ++ jv_array(), jv_null(), jv_number(42)); ++ ++ // Single element: one iteration, no reuse of kk/vk ++ compile_args_and_check(jq, "$val", ++ JV_ARRAY(JV_OBJECT(jv_string("name"), jv_string("val"), ++ jv_string("value"), jv_number(42))), ++ jv_null(), jv_number(42)); ++ ++ // Two elements: minimum to trigger former UAF on iteration 2 ++ compile_args_and_check(jq, "$x + $y", ++ JV_ARRAY(JV_OBJECT(jv_string("name"), jv_string("x"), ++ jv_string("value"), jv_number(1)), ++ JV_OBJECT(jv_string("name"), jv_string("y"), ++ jv_string("value"), jv_number(2))), ++ jv_null(), jv_number(3)); ++ ++ // Three elements: exercises further loop iterations ++ compile_args_and_check(jq, "$a + $b + $c", ++ JV_ARRAY(JV_OBJECT(jv_string("name"), jv_string("a"), ++ jv_string("value"), jv_string("hello")), ++ JV_OBJECT(jv_string("name"), jv_string("b"), ++ jv_string("value"), jv_string(" ")), ++ JV_OBJECT(jv_string("name"), jv_string("c"), ++ jv_string("value"), jv_string("world"))), ++ jv_null(), jv_string("hello world")); ++ ++ // Object args: bypasses args2obj loop via early return ++ compile_args_and_check(jq, "$x * $y", ++ JV_OBJECT(jv_string("x"), jv_number(10), ++ jv_string("y"), jv_number(20)), ++ jv_null(), jv_number(200)); ++ ++ jq_teardown(&jq); ++} ++ ++ + /// pthread regression test + #ifdef HAVE_PTHREAD + #define NUMBER_OF_THREADS 3 diff -Nru jq-1.7.1/debian/patches/GHSA-ggc9-rpv2-xgpm_and_GHSA-gvwx-xj9r-3frq.patch jq-1.7.1/debian/patches/GHSA-ggc9-rpv2-xgpm_and_GHSA-gvwx-xj9r-3frq.patch --- jq-1.7.1/debian/patches/GHSA-ggc9-rpv2-xgpm_and_GHSA-gvwx-xj9r-3frq.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/GHSA-ggc9-rpv2-xgpm_and_GHSA-gvwx-xj9r-3frq.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,32 @@ +From: theyoucheng +Date: Sat, 23 May 2026 10:11:11 +0900 +Subject: Fix undefined pointer arithmetic in UTF-8 helpers + +Fixes GHSA-ggc9-rpv2-xgpm and GHSA-gvwx-xj9r-3frq. + +Origin: https://github.com/jqlang/jq/commit/df924eae91af10cc236a907cdadd97813827aa1f +--- + src/jv_unicode.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +--- a/src/jv_unicode.c ++++ b/src/jv_unicode.c +@@ -15,7 +15,8 @@ const char* jvp_utf8_backtrack(const cha + } + int length = 0; + int seen = 1; +- while (start >= min && (length = utf8_coding_length[(unsigned char)*start]) == UTF8_CONTINUATION_BYTE) { ++ while ((length = utf8_coding_length[(unsigned char)*start]) == UTF8_CONTINUATION_BYTE) { ++ if (start == min) break; + start--; + seen++; + } +@@ -41,7 +42,7 @@ const char* jvp_utf8_next(const char* in + } else if (length == 0 || length == UTF8_CONTINUATION_BYTE) { + /* Bad single byte - either an invalid byte or an out-of-place continuation byte */ + length = 1; +- } else if (in + length > end) { ++ } else if (length > end - in) { + /* String ends before UTF8 sequence ends */ + length = end - in; + } else { diff -Nru jq-1.7.1/debian/patches/fix-abort-on-string-repeat-overflow.patch jq-1.7.1/debian/patches/fix-abort-on-string-repeat-overflow.patch --- jq-1.7.1/debian/patches/fix-abort-on-string-repeat-overflow.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/fix-abort-on-string-repeat-overflow.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,77 @@ +Description: Do not abort when a string repetition exceeds the length bound + The fix for CVE-2026-32316 made jvp_string_append() return an invalid jv + once the result would exceed INT_MAX, where it had previously always + returned a valid string. binop_multiply() repeats a string by appending in + a loop and never checked the result, so the iteration after the bound was + reached passed an invalid jv to jvp_string_ptr(), which asserts: + . + $ echo '{"s":"abc","n":1000000000}' | jq '.s * .n' + jq: src/jv.c:1093: jvp_string_ptr: Assertion `JVP_HAS_KIND(a, JV_KIND_STRING)' failed. + Aborted + . + Both operands are ordinary data, so any filter that multiplies a string by + a number can be driven into the abort by its input. 1.7.1-6+deb13u2 returns + the repeated string here, so this is a regression introduced by the + CVE-2026-32316 backport rather than a pre-existing defect. + . + The same statement also passed alen * n to jv_string_empty(), which takes an + int, so the size of the initial allocation was computed by a conversion that + overflowed. Both problems are addressed by rejecting the operation up front + when the result cannot fit, and by stopping the loop if the append fails + anyway -- jvp_string_append() applies a slightly lower bound than INT_MAX + (it subtracts the string header), and that exact bound is private to jv.c. + . + Upstream fixed this in 1.8.0 by rewriting the repetition around a new + jv_string_repeat() function. That is not backported here because + jv_string_repeat is an exported symbol and adding it would extend the + libjq1 ABI surface in a stable update. The error message is kept identical + to the one upstream produces for this case. +Author: Aron Xu +Forwarded: not-needed +Last-Update: 2026-08-05 + +Index: jq-1.7.1/src/builtin.c +=================================================================== +--- jq-1.7.1.orig/src/builtin.c ++++ jq-1.7.1/src/builtin.c +@@ -382,11 +382,22 @@ jv binop_multiply(jv a, jv b) { + if (d < 0 || isnan(d)) { + res = jv_null(); + } else { +- int n = d; + size_t alen = jv_string_length_bytes(jv_copy(str)); ++ // The result has to stay within the length bound that ++ // jvp_string_append() enforces, and alen * n has to survive the ++ // conversion to the int taken by jv_string_empty(). ++ if (d > INT_MAX || (uint64_t)alen * (uint64_t)d >= INT_MAX) { ++ jv_free(str); ++ jv_free(num); ++ return jv_invalid_with_msg(jv_string("Repeat string result too long")); ++ } ++ int n = d; + res = jv_string_empty(alen * n); + for (; n > 0; n--) { + res = jv_string_append_buf(res, jv_string_value(str), alen); ++ // jvp_string_append() returns invalid once the result would exceed ++ // its length bound; appending to that would assert in jvp_string_ptr(). ++ if (!jv_is_valid(res)) break; + } + } + jv_free(str); +Index: jq-1.7.1/tests/jq.test +=================================================================== +--- jq-1.7.1.orig/tests/jq.test ++++ jq-1.7.1/tests/jq.test +@@ -2137,6 +2137,12 @@ try ltrimstr("x") catch "x", try rtrimst + "ok" + "ok" + ++# repeating a string past the length bound errors instead of aborting ++ ++[.[] | try (.s * .n) catch .] ++[{"s":"abc","n":1000000000},{"s":"a","n":3},{"s":"abc","n":0}] ++["Repeat string result too long","aaa",""] ++ + # regression test for CVE-2026-33947 + setpath([range(10000) | 0]; 0) | flatten + null diff -Nru jq-1.7.1/debian/patches/fix-invalid-jv-propagation.patch jq-1.7.1/debian/patches/fix-invalid-jv-propagation.patch --- jq-1.7.1/debian/patches/fix-invalid-jv-propagation.patch 1970-01-01 00:00:00.000000000 +0000 +++ jq-1.7.1/debian/patches/fix-invalid-jv-propagation.patch 2026-08-04 12:56:56.000000000 +0000 @@ -0,0 +1,155 @@ +Description: Propagate invalid jv instead of aborting on it + Several fixes in this series made functions that had always returned a valid + jv able to return an invalid one: jvp_string_append() and + jvp_string_copy_replace_bad() (CVE-2026-32316, CVE-2026-54679) and jv_sort() + (CVE-2026-47770). Callers written against the old contract feed the invalid + value straight back into an accessor, which asserts: + . + $ jq -n '(reduce range(10000) as $i ([];[.])) as $x + | null | delpaths([[$x],[$x]])' + jq: src/jv.c:986: jv_array_length: Assertion `JVP_HAS_KIND(j, JV_KIND_ARRAY)' failed. + . + $ head -c 360000000 /dev/zero | tr '\0' "'" > big.txt + $ jq -Rr '@html|length' big.txt + jq: src/jv.c:1097: jvp_string_ptr: Assertion `JVP_HAS_KIND(a, JV_KIND_STRING)' failed. + . + The two cases differ. 1.7.1-6+deb13u2 answers the delpaths one correctly, so + that is a regression this series introduces. On the @html one deb13u2 exits 0 + but prints 12516352 where the answer is 2160000000 -- it is the CVE-2026-32316 + integer overflow itself, so the abort is the fix landing without a graceful + error path rather than lost functionality. Both are addressed here. + . + Rather than guard each of the append loops in builtin.c (@base64, @csv, @tsv, + @sh, @uri, escape_string) separately, the check goes into jvp_string_append() + and jv_string_concat(), which every one of them funnels through. That also + covers call sites not reached during review. jv_string_concat() needs both + sides checked because it evaluates jv_string_value(b) before appending. + . + Guarding the string path then relocates the abort into error reporting: + jv_dump_string_trunc() only pre-truncates strings, so dumping a large array + or object for a type-error message returns invalid. Same for the three + jv_dump_string() results printed by main.c. deb13u2 reports these cleanly, so + they are regressions too and are guarded here. + . + Upstream 1.8.2 aborts identically on the delpaths, error-message and @html + cases, so there is nothing to cherry-pick. +Author: Aron Xu +Forwarded: no +Last-Update: 2026-08-06 + +Index: jq-1.7.1/src/jv.c +=================================================================== +--- jq-1.7.1.orig/src/jv.c ++++ jq-1.7.1/src/jv.c +@@ -1173,6 +1173,9 @@ static uint32_t jvp_string_remaining_spa + } + + static jv jvp_string_append(jv string, const char* data, uint32_t len) { ++ // Once the length bound below has been hit the string is invalid, and ++ // callers that append in a loop would otherwise pass it back in here. ++ if (!jv_is_valid(string)) return string; + jvp_string* s = jvp_string_ptr(string); + uint32_t currlen = jvp_string_length(s); + if ((uint64_t)currlen + len >= INT_MAX - sizeof(jvp_string) / 2) { +@@ -1498,6 +1501,15 @@ jv jv_string_slice(jv j, int start, int + } + + jv jv_string_concat(jv a, jv b) { ++ // Either side may already be invalid; jvp_string_ptr() asserts on those. ++ if (!jv_is_valid(a)) { ++ jv_free(b); ++ return a; ++ } ++ if (!jv_is_valid(b)) { ++ jv_free(a); ++ return b; ++ } + a = jvp_string_append(a, jv_string_value(b), + jvp_string_length(jvp_string_ptr(b))); + jv_free(b); +Index: jq-1.7.1/src/jv_aux.c +=================================================================== +--- jq-1.7.1.orig/src/jv_aux.c ++++ jq-1.7.1/src/jv_aux.c +@@ -535,6 +535,11 @@ jv jv_delpaths(jv object, jv paths) { + return jv_invalid_with_msg(jv_string("Paths must be specified as an array")); + } + paths = jv_sort(paths, jv_copy(paths)); ++ // jv_sort() reports a too-deep comparison by returning an invalid jv. ++ if (!jv_is_valid(paths)) { ++ jv_free(object); ++ return paths; ++ } + jv_array_foreach(paths, i, elem) { + if (jv_get_kind(elem) != JV_KIND_ARRAY) { + jv_free(object); +Index: jq-1.7.1/tests/jq.test +=================================================================== +--- jq-1.7.1.orig/tests/jq.test ++++ jq-1.7.1/tests/jq.test +@@ -2137,6 +2137,12 @@ try ltrimstr("x") catch "x", try rtrimst + "ok" + "ok" + ++# delpaths reports a too-deep path comparison instead of aborting ++ ++(reduce range(10000) as $i ([];[.])) as $x | try (null | delpaths([[$x],[$x]])) catch . ++null ++"Comparison too deep" ++ + # repeating a string past the length bound errors instead of aborting + + [.[] | try (.s * .n) catch .] +Index: jq-1.7.1/src/main.c +=================================================================== +--- jq-1.7.1.orig/src/main.c ++++ jq-1.7.1/src/main.c +@@ -247,7 +247,8 @@ static int process(jq_state *jq, jv valu + // Halt with no output + } else if (jv_is_valid(error_message)) { + error_message = jv_dump_string(error_message, 0); +- fprintf(stderr, "%s\n", jv_string_value(error_message)); ++ fprintf(stderr, "%s\n", jv_is_valid(error_message) ? ++ jv_string_value(error_message) : ""); + } // else no message on stderr; use --debug-trace to see a message + fflush(stderr); + jv_free(error_message); +@@ -261,7 +262,8 @@ static int process(jq_state *jq, jv valu + } else { + msg = jv_dump_string(msg, 0); + fprintf(stderr, "jq: error (at %s) (not a string): %s\n", +- jv_string_value(input_pos), jv_string_value(msg)); ++ jv_string_value(input_pos), jv_is_valid(msg) ? ++ jv_string_value(msg) : ""); + } + ret = JQ_ERROR_UNKNOWN; + jv_free(input_pos); +@@ -284,7 +286,8 @@ static void stderr_cb(void *data, jv inp + stderr, dumpopts & JV_PRINT_ISATTY); + } else { + input = jv_dump_string(input, 0); +- fprintf(stderr, "%s", jv_string_value(input)); ++ fprintf(stderr, "%s", jv_is_valid(input) ? ++ jv_string_value(input) : ""); + } + jv_free(input); + } +Index: jq-1.7.1/src/jv_print.c +=================================================================== +--- jq-1.7.1.orig/src/jv_print.c ++++ jq-1.7.1/src/jv_print.c +@@ -392,6 +392,14 @@ char *jv_dump_string_trunc(jv x, char *o + x = jv_string_slice(x, 0, bufsize); + } + x = jv_dump_string(x,0); ++ // Only strings are pre-truncated above, so dumping a large array or object ++ // can exceed the string length bound and come back invalid. ++ if (!jv_is_valid(x)) { ++ jv_free(x); ++ strncpy(outbuf, "", bufsize); ++ outbuf[bufsize - 1] = 0; ++ return outbuf; ++ } + const char* p = jv_string_value(x); + const size_t len = strlen(p); + strncpy(outbuf, p, bufsize); diff -Nru jq-1.7.1/debian/patches/series jq-1.7.1/debian/patches/series --- jq-1.7.1/debian/patches/series 2026-04-17 09:08:56.000000000 +0000 +++ jq-1.7.1/debian/patches/series 2026-08-04 12:56:56.000000000 +0000 @@ -12,3 +12,18 @@ CVE-2026-39956.patch CVE-2026-39979.patch CVE-2026-40164.patch +CVE-2026-41256.patch +CVE-2026-41257.patch +CVE-2026-43896.patch +CVE-2026-43895.patch +CVE-2026-44777.patch +CVE-2026-43894.patch +CVE-2026-47770.patch +CVE-2026-49839.patch +GHSA-ggc9-rpv2-xgpm_and_GHSA-gvwx-xj9r-3frq.patch +CVE-2026-32316.patch +CVE-2026-54679.patch +CVE-2026-40612.patch +GHSA-gf4g-95wj-4q4r.patch +fix-abort-on-string-repeat-overflow.patch +fix-invalid-jv-propagation.patch