Hello,

I have a regression update for jq ready for review.

deb12u2 crashes where deb12u1 didn't. The CVE-2026-47770 depth cap made a too-deep delpaths() abort 
in jv_array_length() (exit 134). It now says "Comparison too deep", and the over-long 
string paths stop asserting. The CVE-2026-32316 length check left the repeat loop aborting, or 
allocating without a limit. That now fails with "Repeat string result too long", and a 
count of 0 or less still returns null. Same follow-ups Trixie put in 1.7.1-6+deb13u3, minus 
stderr_cb, which 1.6 doesn't have.

Two memory bugs came along while I was testing. Neither has a CVE. args2obj() double-frees when 
jq_compile_args() is given an array. The CLI always passes an object, so this is libjq only 
(upstream 3985b80c). And f_multiply() aliased the source string, so ("a"*200)*30 copies 
from freed memory. Upstream fixed that in 1.7 as 9163e096. "abc" * infinite now errors 
instead of returning null. 0, negatives, nan, and -infinite still return null.

The dla-needed note is the CVE-2026-44777 regression from DSA-6416-2. Bookworm 
has that commit, but the crash goes through block_mark_referenced, and 1.6 
doesn't have it. Those module cases pass on deb12u2 and the CVE itself is 
already fixed by DLA-4662-1.

A debdiff is attached, and I have a Salsa fork here: 
https://salsa.debian.org/tsimonq2/jq/-/tree/debian/bookworm?ref_type=heads

I built it in a Bookworm schroot. The library is still libjq.so.1, and the symbols didn't 
change. I ran deb12u1, deb12u2, and this package through the 1.6 suite and some large 
JSON, and they agree apart from these fixes. Under valgrind, args2obj goes from 27 errors 
to 0, and ("a"*200)*30 goes from 6300 to 0. The too-deep delpaths exits 5 
instead of aborting, and a huge repeat errors out at about 1 MB instead of running out of 
memory. Both PoCs print the same error text as Trixie.

There are 116 reverse dependencies in total between archive packages using jq 
and direct reverse dependencies; I tested builds and autopkgtests for all of 
them against this patch. They build and test successfully, except for 24 of 
them that fail with both deb12u2 and this patch.

Here is the Debusine work request: 
https://debusine.debian.net/debian/developers/work-request/1452436/
(Although it seems to be acting up, so some help diagnosing and fixing would be 
appreciated.)

This is my first ever regression update to a stable release in Debian, so 
feedback is certainly welcome. If nothing showstopping comes up, I'll upload 
this and issue DLA-4662-2 on Thursday afternoon (October 15th, 2026).

Best regards,
Simon Quigley
[email protected]
diff -Nru jq-1.6/debian/changelog jq-1.6/debian/changelog
--- jq-1.6/debian/changelog     2026-07-01 03:47:59.000000000 -0500
+++ jq-1.6/debian/changelog     2026-10-12 14:02:32.000000000 -0500
@@ -1,3 +1,24 @@
+jq (1.6-2.1+deb12u3) bookworm-security; urgency=high
+
+  * Non-maintainer upload by the LTS Team.
+  * DLA-4662-1 made a too-deep delpaths() abort in jv_array_length()
+    (exit 134). deb12u1 did not. It now says "Comparison too deep",
+    and the over-long string paths no longer assert. Repeating a
+    string past the length bound aborted, or just kept allocating.
+    That now fails with "Repeat string result too long". A count of
+    0 or less still returns null. Taken from Trixie's 1.7.1-6+deb13u3,
+    minus stderr_cb, which 1.6 doesn't have. No new CVE ids.
+  * args2obj() freed a name and its value twice when jq_compile_args()
+    was given an array (GHSA-gf4g-95wj-4q4r). The jq command line
+    always passes an object, so only libjq was affected. No CVE.
+  * f_multiply() aliased the string it was repeating, so the first
+    append that grew the buffer freed memory the rest of the loop was
+    still copying. ("a"*200)*30 was enough, and so was 30*("a"*200).
+    Same change as upstream 9163e096. Not a CVE. Found while testing
+    the regressions above.
+
+ -- Simon Quigley <[email protected]>  Mon, 12 Oct 2026 14:02:32 -0500
+
 jq (1.6-2.1+deb12u2) bookworm-security; urgency=high
 
   * Non-maintainer upload by the LTS Team.
diff -Nru jq-1.6/debian/patches/fix-abort-on-string-repeat-overflow.patch 
jq-1.6/debian/patches/fix-abort-on-string-repeat-overflow.patch
--- jq-1.6/debian/patches/fix-abort-on-string-repeat-overflow.patch     
1969-12-31 18:00:00.000000000 -0600
+++ jq-1.6/debian/patches/fix-abort-on-string-repeat-overflow.patch     
2026-10-12 14:02:32.000000000 -0500
@@ -0,0 +1,53 @@
+Origin: vendor, 1.7.1-6+deb13u3 
debian/patches/fix-abort-on-string-repeat-overflow.patch
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-10-08
+
+Backported to 1.6. Trixie edits binop_multiply(); 1.6 does the
+same check in f_multiply(), and keeps its results for a count below 1.
+
+---
+Index: jq-1.6/src/builtin.c
+===================================================================
+--- jq-1.6.orig/src/builtin.c
++++ jq-1.6/src/builtin.c
+@@ -318,10 +318,23 @@ static jv f_multiply(jq_state *jq, jv in
+     }
+     int n;
+     size_t alen = jv_string_length_bytes(jv_copy(str));
++    double d = jv_number_value(num);
++    // The result has to stay within the length bound that
++    // jvp_string_append() enforces, and the repeat count has to survive
++    // the conversion to int below.
++    if (d > INT_MAX || (d >= 1 && (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"));
++    }
+     jv res = str;
+ 
+-    for (n = jv_number_value(num) - 1; n > 0; n--)
++    for (n = jv_number_value(num) - 1; 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(num);
+     if (n < 0) {
+Index: jq-1.6/tests/jq.test
+===================================================================
+--- jq-1.6.orig/tests/jq.test
++++ jq-1.6/tests/jq.test
+@@ -1586,6 +1586,11 @@ null
+ null
+ "Comparison too deep"
+ 
++# 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",null]
++
+ # nan is parsed as a valid NaN value from JSON
+ 
+ fromjson | isnan
diff -Nru jq-1.6/debian/patches/fix-f_multiply-use-after-free.patch 
jq-1.6/debian/patches/fix-f_multiply-use-after-free.patch
--- jq-1.6/debian/patches/fix-f_multiply-use-after-free.patch   1969-12-31 
18:00:00.000000000 -0600
+++ jq-1.6/debian/patches/fix-f_multiply-use-after-free.patch   2026-10-12 
14:02:32.000000000 -0500
@@ -0,0 +1,40 @@
+Origin: 
https://github.com/jqlang/jq/commit/9163e09605383a88f6e953d6cb5cc2aebe18c84f
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-10-08
+
+Backported to 1.6's f_multiply(), on top of the repeat-length check
+in this series. The reference change matches upstream.
+
+From 9163e09605383a88f6e953d6cb5cc2aebe18c84f Mon Sep 17 00:00:00 2001
+From: itchyny <[email protected]>
+Date: Fri, 8 May 2020 14:22:25 +0900
+Subject: [PATCH] Fix multiple string multiplication
+
+---
+ src/builtin.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+Index: jq-1.6/src/builtin.c
+===================================================================
+--- jq-1.6.orig/src/builtin.c
++++ jq-1.6/src/builtin.c
+@@ -327,7 +327,7 @@ static jv f_multiply(jq_state *jq, jv in
+       jv_free(num);
+       return jv_invalid_with_msg(jv_string("Repeat string result too long"));
+     }
+-    jv res = str;
++    jv res = jv_copy(str);
+ 
+     for (n = jv_number_value(num) - 1; n > 0; n--) {
+       res = jv_string_append_buf(res, jv_string_value(str), alen);
+@@ -337,8 +337,9 @@ static jv f_multiply(jq_state *jq, jv in
+     }
+ 
+     jv_free(num);
++    jv_free(str);
+     if (n < 0) {
+-      jv_free(str);
++      jv_free(res);
+       return jv_null();
+     }
+     return res;
diff -Nru jq-1.6/debian/patches/fix-invalid-jv-propagation.patch 
jq-1.6/debian/patches/fix-invalid-jv-propagation.patch
--- jq-1.6/debian/patches/fix-invalid-jv-propagation.patch      1969-12-31 
18:00:00.000000000 -0600
+++ jq-1.6/debian/patches/fix-invalid-jv-propagation.patch      2026-10-12 
14:02:32.000000000 -0500
@@ -0,0 +1,113 @@
+Origin: vendor, 1.7.1-6+deb13u3 debian/patches/fix-invalid-jv-propagation.patch
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-10-08
+
+Backported to 1.6 from the Trixie patch of the same name. 1.6 has
+no stderr_cb, so that hunk is left out.
+
+---
+Index: jq-1.6/src/jv.c
+===================================================================
+--- jq-1.6.orig/src/jv.c
++++ jq-1.6/src/jv.c
+@@ -540,6 +540,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) {
+@@ -854,6 +857,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.6/src/jv_aux.c
+===================================================================
+--- jq-1.6.orig/src/jv_aux.c
++++ jq-1.6/src/jv_aux.c
+@@ -447,6 +447,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.6/src/jv_print.c
+===================================================================
+--- jq-1.6.orig/src/jv_print.c
++++ jq-1.6/src/jv_print.c
+@@ -394,6 +394,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, "<value too large>", bufsize);
++    outbuf[bufsize - 1] = 0;
++    return outbuf;
++  }
+   const char* p = jv_string_value(x);
+   const size_t len = strlen(p);
+   strncpy(outbuf, p, bufsize);
+Index: jq-1.6/src/main.c
+===================================================================
+--- jq-1.6.orig/src/main.c
++++ jq-1.6/src/main.c
+@@ -206,7 +206,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(jv_copy(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) : "<value too large>");
+     } // else no message on stderr; use --debug-trace to see a message
+     fflush(stderr);
+     jv_free(error_message);
+@@ -220,7 +221,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) : "<value too large>");
+     }
+     ret = 5;
+     jv_free(input_pos);
+Index: jq-1.6/tests/jq.test
+===================================================================
+--- jq-1.6.orig/tests/jq.test
++++ jq-1.6/tests/jq.test
+@@ -1581,6 +1581,11 @@ try ((reduce range(10001) as $_ ({}; {a:
+ null
+ "Comparison too deep"
+ 
++# 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"
++
+ # nan is parsed as a valid NaN value from JSON
+ 
+ fromjson | isnan
diff -Nru jq-1.6/debian/patches/GHSA-gf4g-95wj-4q4r.patch 
jq-1.6/debian/patches/GHSA-gf4g-95wj-4q4r.patch
--- jq-1.6/debian/patches/GHSA-gf4g-95wj-4q4r.patch     1969-12-31 
18:00:00.000000000 -0600
+++ jq-1.6/debian/patches/GHSA-gf4g-95wj-4q4r.patch     2026-10-12 
14:02:32.000000000 -0500
@@ -0,0 +1,38 @@
+Origin: 
https://github.com/jqlang/jq/commit/3985b80ce50bd75c6eb5a97cb3348c3f835ca8e0
+Reviewed-by: Simon Quigley <[email protected]>
+Last-Update: 2026-10-08
+
+Backported to 1.6. The execute.c hunk is unchanged. The jq_test.c
+tests do not apply to the 1.6 harness and are left out.
+
+From 3985b80ce50bd75c6eb5a97cb3348c3f835ca8e0 Mon Sep 17 00:00:00 2001
+From: Scott Seal <[email protected]>
+Date: Mon, 9 Mar 2026 03:30:08 -0500
+Subject: [PATCH] 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.
+---
+ src/execute.c |   2 +-
+ src/jq_test.c | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 188 insertions(+), 1 deletion(-)
+
+Index: jq-1.6/src/execute.c
+===================================================================
+--- jq-1.6.orig/src/execute.c
++++ jq-1.6/src/execute.c
+@@ -1144,7 +1144,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);
diff -Nru jq-1.6/debian/patches/series jq-1.6/debian/patches/series
--- jq-1.6/debian/patches/series        2026-07-01 03:45:02.000000000 -0500
+++ jq-1.6/debian/patches/series        2026-10-12 14:02:32.000000000 -0500
@@ -27,3 +27,7 @@
 CVE-2026-54679-1.patch
 CVE-2026-54679-2.patch
 CVE-2026-54679-3.patch
+fix-invalid-jv-propagation.patch
+fix-abort-on-string-repeat-overflow.patch
+GHSA-gf4g-95wj-4q4r.patch
+fix-f_multiply-use-after-free.patch

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature

Reply via email to