|
|
Message-ID: <c9bd5dbd-7b11-4637-80cd-5dec79dd491f@oracle.com>
Date: Wed, 15 Apr 2026 15:51:15 -0700
From: Alan Coopersmith <alan.coopersmith@...cle.com>
To: oss-security@...ts.openwall.com
Subject: 7 vulnerabilities disclosed & patched in jq
7 vulnerabilities have been posted to https://github.com/jqlang/jq/security -
6 this week and one in March that I don't see in the list archives. Fixes
appear to be available in their github repo, but not yet in a release.
https://github.com/jqlang/jq/security/advisories/GHSA-q3h9-m34w-h76f reports:
> Integer overflow in jvp_string_append and jvp_string_copy_replace_bad allows
> heap buffer overflow
>
> Affected versions: 1.8.1
>
> Summary
> -------
> An integer overflow in jvp_string_append() in src/jv.c causes a heap buffer
> overflow when concatenating strings whose combined length exceeds 2^31 bytes.
>
> Details
> -------
> The internal function jvp_string_append() computes the new buffer allocation
> size as:
>
> // src/jv.c:1187
> uint32_t allocsz = (currlen + len) * 2;
> if (allocsz < 32) allocsz = 32;
>
> Both currlen and len are uint32_t. When currlen + len >= 2^31, the
> multiplication by 2 overflows the 32-bit unsigned integer, wrapping allocsz to
> a small value (potentially 0, clamped to 32). The subsequent memcpy operations
> copy currlen + len bytes into the undersized buffer, causing a heap buffer
> overflow. The length_hashed field ((currlen + len) << 1) suffers the same
> overflow, corrupting the stored string length.
>
> Arrays and objects already have size limits, but strings have no equivalent
> bounds check. We have to limit the size of string so that the length does not
> overflow.
>
> Similar vulnerability exists in jvp_string_copy_replace_bad, too.
>
[See GHSA for PoC]
>
> Impact
> ------
> CWE-190 (Integer Overflow or Wraparound) leading to
> CWE-122 (Heap-based Buffer Overflow).
>
> Any user or system that evaluates untrusted jq queries is affected. Attacker
> can easily crash the jq process by sending a crafted jq query. If the system
> uses a fixed jq query (that does not concatenate the input strings many times),
> attacker needs to send a huge JSON input to make it crash. Heap corruption may
> allow further exploitation depending on the allocator and environment.
> All versions of jq through 1.8.1 and current master version are affected.
>
> Fix
> ---
> This issue was fixed by
> https://github.com/jqlang/jq/commit/e47e56d226519635768e6aab2f38f0ab037c09e5
>
> Severity: High - 8.2 / 10
> CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H
> CVE ID: CVE-2026-32316
https://github.com/jqlang/jq/security/advisories/GHSA-2hhh-px8h-355p says:
> Out-of-Bounds Read in jv_parse_sized() Error Formatting for Non-NUL-Terminated
> Counted Buffers
>
> Summary
> -------
> libjq exposes jv_parse_sized(const char *string, int length) as a counted-buffer
> JSON parsing API, but its parse-error path later treats the same buffer as a
> NUL-terminated C string. If a caller passes malformed JSON in a
> non-NUL-terminated buffer, the error construction logic can read past the
> caller-supplied length, causing an out-of-bounds read.
>
> Details
> -------
> The vulnerable path is:
>
> jv_parse_sized()
> -> jv_parse_sized_custom_flags()
> -> jv_parser_set_buf(&parser, string, length, 0)
> -> parse failure
> -> jv_string_fmt("%s (while parsing '%s')", ..., string)
>
> Relevant code:
>
> src/jv.h (line 245)
> src/jv_parse.c (line 865)
> src/jv_parse.c (line 896)
> src/jv.c (line 1528)
>
> The parser correctly accepts a (pointer, length) pair, but when building the
> error message it formats string with %s, which causes vsnprintf() to continue
> reading memory until a \0 is found. This makes the error path ignore the
> explicit buffer length and turns a counted-buffer API into an unbounded read
> sink.
>
> Reachability:
>
> Real external source: any libjq consumer calling jv_parse_sized() with a
> counted buffer.
>
> In-project internal uses such as fromjson and lexer paths also reach
> jv_parse_sized(), but those pass jq-managed strings that are already
> NUL-terminated, so the practical attack surface is the public API rather
> than the normal jq CLI path.
>
[See GHSA for PoC]
>
> Impact
> ------
>
> Only libjq is affected. A caller that uses jv_parse_sized() on untrusted
> malformed JSON in a non-NUL-terminated buffer can trigger an out-of-bounds
> read during error construction. Depending on memory layout and how the
> returned error string is logged or exposed, this can lead to memory disclosure
> or process termination.
>
> Severity: Moderate
> CVE ID: CVE-2026-39979
> Weaknesses: CWE-125 Out-of-bounds Read
> Credits: @HO-9 Reporter
https://github.com/jqlang/jq/commit/2f09060afab23fe9390cce7cb860b10416e1bf5f
states that it fixes CVE-2026-39979.
https://github.com/jqlang/jq/security/advisories/GHSA-32cx-cvvh-2wj9 advises:
> Embedded-NUL Truncation in jq CLI JSON Input Path Causes Prefix-Only Validation
> of Malformed Input
>
> Summary
> -------
> The normal jq CLI JSON input path uses fgets() and then derives the valid byte
> length with strlen() when parsing JSON input without a newline. If the input
> contains an embedded NUL byte, jq truncates the already-read buffer at the NUL
> and passes only the benign prefix to the JSON parser. As a result, jq may
> accept malformed input by validating only the prefix before the NUL.
>
> Details
> -------
> The reachable CLI path is:
>
> CLI file/stdin input
> -> jq_util_input_add_input() / stdin default
> -> jq_util_input_set_parser(..., jv_parser_new(...))
> -> jq_util_input_next_input() -> jq_util_input_read_more()
> -> strlen(state->buf)
> -> jv_parser_set_buf(state->parser, state->buf, state->buf_valid_len,
> !is_last)
>
> Relevant code:
>
> src/main.c (line 361)
> src/main.c (line 653)
> src/main.c (line 664)
> src/main.c (line 671)
> src/util.c (line 315)
> src/util.c (line 320)
> src/util.c (line 432)
>
> The flaw is that the code does not use the actual number of bytes read by
> fgets(). Instead it uses strlen(state->buf), which stops at the first embedded
> NUL. Trailing bytes after the NUL may already have been consumed from the
> input stream, but they are silently excluded from parsing.
>
> This is realistically reachable because it affects the stock jq CLI file
> and stdin parsing path used by end users.
>
[See GHSA for PoC]
>
> Observed error:
>
> jq: Bad JSON in --slurpfile ...: Invalid numeric literal at line 1, column 17
>
> Impact
>
> This issue can cause validation bypass in workflows that rely on jq to validate
> untrusted JSON before forwarding, storing, or otherwise acting on the original
> bytes. An attacker can place a benign JSON prefix before an embedded NUL and
> append malicious trailing data after it. jq may accept the prefix as valid
> JSON while silently ignoring the suffix, creating a parser differential
> between jq and downstream components that process the full input.
>
> Severity: Low
> CVE ID: CVE-2026-33948
> Weaknesses: No CWEs
> Credits: @HO-9
https://github.com/jqlang/jq/commit/6374ae0bcdfe33a18eb0ae6db28493b1f34a0a5b
says it fixes CVE-2026-33948.
https://github.com/jqlang/jq/security/advisories/GHSA-xwrw-4f8h-rjvg states:
> Unbounded Recursion in jv_setpath() / jv_getpath() / delpaths_sorted()
>
> Affected versions: <= 1.8.1
>
> Summary
> -------
> The jv_setpath(), jv_getpath(), and delpaths_sorted() functions in
> src/jv_aux.c use unbounded recursion where the recursion depth equals the
> length of a caller-supplied path array. There is no depth limit check.
> When a path array with ~60,000 or more elements is supplied — either constructed
> by a jq filter expression or provided directly in attacker-controlled JSON input
> — the C call stack is exhausted, causing a segmentation fault (SIGSEGV) and
> immediate process crash.
>
> This vulnerability bypasses the MAX_PARSING_DEPTH (10,000) limit that protects
> the JSON parser, because path arrays can be constructed programmatically to
> arbitrary lengths without being constrained by parsing depth. Critically, the
> path array can be sourced entirely from attacker-controlled JSON input, making
> this exploitable in scenarios where a trusted jq filter processes untrusted data.
>
[See GHSA for code analysis and PoC]
>
> Impact
> ------
> - Denial of Service (Crash): Any jq process that calls setpath, getpath, or
> delpaths with a sufficiently long path array will crash with SIGSEGV.
> This is an unrecoverable crash — no error handling is possible.
> - Bypass of existing depth limits: The JSON parser's MAX_PARSING_DEPTH (10,000)
> does not protect against this because path arrays are constructed at the jq
> runtime level, not during JSON parsing. An attacker can embed a flat array
> of 65,000 integers in a JSON document (only ~200 KB) that causes a crash
> when used as a path.
> - Affected real-world scenarios:
> - Web services using jq to transform or extract data from user-submitted JSON
> - CI/CD pipelines processing untrusted configuration or API responses with jq
> - Shell scripts that use setpath/getpath/delpaths on paths derived from input
> data
> - Any application embedding libjq where path arguments can be influenced by
> external input
> - Note: Unlike memory corruption vulnerabilities, stack overflow from recursion
> is generally not exploitable for code execution on modern systems with guard
> pages. The impact is limited to denial of service.
>
> Severity: Moderate - 6.2 / 10
> CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
> CVE ID: CVE-2026-33947
> Weaknesses: CWE-674 Uncontrolled Recursion
> Credits: @bg0d-glitch
https://github.com/jqlang/jq/commit/fb59f1491058d58bdc3e8dd28f1773d1ac690a1f
declares that it fixes CVE-2026-33947.
https://github.com/jqlang/jq/security/advisories/GHSA-6gc3-3g9p-xx28 announces:
> jq _strindices missing runtime type checks lead to crash and limited memory
> disclosure
>
> Affected versions: 1.8.1-dev commit 69785bf
>
> Summary
> -------
> _strindices is the C builtin used by indices / index / rindex for string inputs.
> Its wrapper passes both arguments straight to jv_string_indexes() without
> checking that they are strings, and jv_string_indexes() itself only uses
> assert(). In release builds those checks disappear under -DNDEBUG.
> _strindices(0) is enough to crash jq, and the same bug also gives an attacker
> controlled pointer dereference and a limited read/probe primitive.
>
> Details
> -------
> commit 69785bf77f86e2ea1b4a20ca86775916889e91c9, _strindices is implemented
> in src/builtin.c like this
>
> static jv f_string_indexes(jq_state *jq, jv a, jv b) {
> return jv_string_indexes(a, b);
> }
>
> jv_string_indexes() in src/jv.c then blindly assumes both given args are strings:
>
> jv jv_string_indexes(jv j, jv k) {
> assert(JVP_HAS_KIND(j, JV_KIND_STRING));
> assert(JVP_HAS_KIND(k, JV_KIND_STRING));
> const char *jstr = jv_string_value(j);
> const char *idxstr = jv_string_value(k);
> }
>
> In a debug build the assertions fire. In a normal release build they are
> compiled out, and jq dereferences j.u.ptr as if it were a valid jvp_string *.
>
> When the assertions are gone, jv_string_value() treats j.u.ptr as a string
> header and jv_string_length_bytes() reads a length from that same fake object.
> This results in either 1. an easy crash with invalid input 2. a limited read
> primitive when a crafted number is used so its bit pattern is treated as a
> pointer. The number being crafted matters because default jq builds use decnum.
> A plain numeric literal doesn't give control of u.ptr, but arithmetic such as
> this does: (<bit-cast double> + 0) reaches jv_number(double) and stores the
> IEEE-754 bits of that double in the same union field which is later read as u.ptr
>
> PoC
> ---
> Crash:
> jq -n '_strindices(0)'
>
> Controlled pointer dereference:
>
> import struct, subprocess
>
> def as_double(u64):
> return struct.unpack("<d", struct.pack("<Q", u64))[0]
>
> # because we want jstr = addr, the fake jvp_string sits 16 bytes earlier
> addr = 0x4141414141414151
> expr = f"({as_double(addr - 16)!r} + 0) | _strindices(\"\\u0000\")"
> result = subprocess.run(["jq", "-n", expr])
> print(result.returncode) # sigsegv in python
>
> Impact
> ------
>
> Anything that runs untrusted jq filters against a release build can be crashed
> very easily. If the system uses a fixed jq query (such that does not use the
> internal _strindices filter), it is not affected. The same bug also gives
> limited read behavior because values which are not strings can be treated as
> fake string objects and walked by _strindices. In a real deployment that means
> an attacker can use the exit status as a mapped/unmapped probe, and in favorable
> cases can get some bytes back through the _strindices position output.
>
> Severity: Moderate - 6.1 / 10
> CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H
> CVE ID: CVE-2026-39956
> Weaknesses:
> CWE-125 Out-of-bounds Read
> CWE-476 NULL Pointer Dereference
> CWE-843 Access of Resource Using Incompatible Type ('Type Confusion')
> Credits: @tlsbollei Reporter
https://github.com/jqlang/jq/commit/fdf8ef0f0810e3d365cdd5160de43db46f57ed03
claims to fix CVE-2026-39956.
https://github.com/jqlang/jq/security/advisories/GHSA-wwj8-gxm6-jc29 expresses:
> Algorithmic complexity DoS via hardcoded MurmurHash3 seed
>
> Summary
> -------
> jq uses MurmurHash3 with a compile-time constant seed 0x432A9843 (src/jv.c:1200)
> for all JSON object hash table operations. Since the seed is hardcoded and
> publicly visible in source, an attacker can precompute hash collisions offline
> and construct a JSON object where all keys hash to the same bucket.
> This degrades operations from O(1) to O(n), making any jq expression O(n^2).
> Only ~100KB of crafted JSON needed — far more practical than the heap overflow
> issues.
>
> Details
> -------
> File: src/jv.c, line 1200
>
> static const uint32_t HASH_SEED = 0x432A9843;
>
> Used at line 1219:
>
> static uint32_t jvp_val_hash(jv val) {
> uint32_t h1 = HASH_SEED;
> // ... MurmurHash3 body ...
> }
>
> Many languages randomize hash seeds at startup to prevent this (Python 3.3+,
> Ruby 1.9+, Perl 5.18+). jq does not.
>
[See GHSA for PoC]
>
> Severity: High - 7.5 / 10
> CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
> CVE ID: CVE-2026-40164
> Weaknesses:
> CWE-328 Use of Weak Hash
> CWE-407 Inefficient Algorithmic Complexity
> Credits: @AsafMeizner Reporter
https://github.com/jqlang/jq/commit/0c7d133c3c7e37c00b6d46b658a02244fdd3c784
appears to mitigate this, but does not list the CVE id.
https://github.com/jqlang/jq/security/advisories/GHSA-gf4g-95wj-4q4r discloses:
> jq args2obj() Heap-Use-After-Free Vulnerability Report
>
> Affected versions: 1.8.1 (commit ref:b33a763)
>
> Summary
> -------
> A potential heap-use-after-free vulnerability exists in jq's args2obj()
> function at src/execute.c:1218. The bug is in the public jq_compile_args()
> API's array argument processing path: when called with an array of 2+ named
> argument entries, freed heap memory is read (CWE-416); with 1+ entries, a
> double-free occurs (CWE-415). The standard jq CLI binary is not affected,
> as it always passes an object (not an array) to args2obj(). It appears the
> bug was introduced in commit b279713e (2017-02-26) and affects jq HEAD
> (jq-1.8.1-32-gb33a763).
>
> Details
> -------
> Root cause: The strings kk ("name") and vk ("value") are allocated once before
> the loop but passed directly to jv_object_get(), which consumes (frees) both
> its arguments. On the first iteration, kk and vk are freed. On subsequent
> iterations, they are dangling pointers — jvp_string_hash() reads from freed
> heap memory. After the loop, jv_free(kk) / jv_free(vk) trigger a double-free.
>
> // src/execute.c:1208-1223
> 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));
> jv_free(kk); // double-free: already freed by jv_object_get()
> jv_free(vk);
>
> Git history: Commit b279713e refactored inline array processing in
> jq_compile_args() into the args2obj() helper. The original code created fresh
> jv_string("name") / jv_string("value") on each iteration; the refactored
> version hoisted them into locals but failed to account for jv_object_get()
> consuming its key argument.
>
> [See GHSA for PoC]
>
> Impact
>
> CWE-416 (Use After Free) / CWE-415 (Double Free). The vulnerability is
> reachable through the public jq_compile_args() API when called with array
> arguments — not through the CLI binary. Heap corruption from the UAF and
> double-free may lead to arbitrary code execution via corrupted heap metadata,
> though no exploit has been demonstrated. All downstream consumers of
> jq_compile_args() that pass array arguments have been affected since
> commit b279713e (2017-02-26).
>
> My name is Scott Seal, and I work at Trail of Bits. Per the instructions of
> my employer, I am required to provide the following disclosure:
>
> This bug was found as part of follow-on research from DARPA's AI Cyber
> Challenge (AIXCC), where Trail of Bits built Buttercup, a Cyber Reasoning
> System that combines static analysis, fuzzing, and large language models
> to find and fix vulnerabilities.
>
> https://www.darpa.mil/research/programs/ai-cyber
> https://www.trailofbits.com/buttercup/
>
> Severity: Low
> CVE ID: No known CVE
> Weaknesses
> CWE-415 Double Free
> CWE-416 Use After Free
> Credits: @sseal Reporter
https://github.com/jqlang/jq/commit/3985b80ce50bd75c6eb5a97cb3348c3f835ca8e0
includes "Fixes GHSA-gf4g-95wj-4q4r."
--
-Alan Coopersmith- alan.coopersmith@...cle.com
Oracle Solaris Engineering - https://blogs.oracle.com/solaris
Powered by blists - more mailing lists
Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.