Summary
Unbounded recursion in jv_object_merge_recursive() allows a crafted jq program to crash the process with a segfault. The function is reachable through the * operator when both operands are objects. This is a denial of service on jq 1.8.1 and below.
Details
The jv_object_merge_recursive() function in src/jv.c recursively merges nested objects without any depth limit. When two deeply nested objects share the same key structure, the function descends into each matching key pair and calls itself again. There is no depth guard on this recursion. If the objects are nested deeply enough, the C call stack is exhausted and the process crashes with SIGSEGV.
This function is exposed to users through the * operator. When both operands of * are objects, builtin.c dispatches to jv_object_merge_recursive():
} else if (ak == JV_KIND_OBJECT && bk == JV_KIND_OBJECT) {
return jv_object_merge_recursive(a, b);
}
The recursive merge code in src/jv.c:
jv jv_object_merge_recursive(jv a, jv b) {
assert(JVP_HAS_KIND(a, JV_KIND_OBJECT));
assert(JVP_HAS_KIND(b, JV_KIND_OBJECT));
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)); // <-- UNBOUNDED RECURSION
} else {
jv_free(elem);
a = jv_object_set(a, k, v);
}
if (!jv_is_valid(a)) break;
}
jv_free(b);
return a;
}
This is a separate finding from the recently disclosed CVE-2026-33947 (unbounded recursion in path operations) and from the jv_cmp() comparison recursion issue. Different function, different file, different trigger.
PoC
Save this as poc.jq:
reduce range(75000) as $_ ({}; {a:.}) | . * . | empty
Then run:
Result: Segmentation fault
The reduce builds an object nested 75,000 levels deep (each level is {a: <previous>}). The * operator triggers jv_object_merge_recursive() to merge the object with itself, recursing into each nested a key until the stack is exhausted.
Tested on jq 1.8.1 on x86_64 Linux. Confirmed with ASan (AddressSanitizer: stack-overflow showing repeated jv_object_merge_recursive frames) and GDB.
Impact
The * operator on objects is one of the most common ways to merge JSON in jq, used for combining configs, deep-updating nested structures, and overlaying default values. Any workflow where an attacker can influence the shape of objects being merged, or the filter expression itself, can trigger this crash. A single deep object merged with itself is enough. The crash is not exploitable for code execution on modern systems with stack guard pages, so the impact is limited to process termination.
Summary
Unbounded recursion in
jv_object_merge_recursive()allows a crafted jq program to crash the process with a segfault. The function is reachable through the*operator when both operands are objects. This is a denial of service on jq 1.8.1 and below.Details
The
jv_object_merge_recursive()function insrc/jv.crecursively merges nested objects without any depth limit. When two deeply nested objects share the same key structure, the function descends into each matching key pair and calls itself again. There is no depth guard on this recursion. If the objects are nested deeply enough, the C call stack is exhausted and the process crashes with SIGSEGV.This function is exposed to users through the
*operator. When both operands of*are objects,builtin.cdispatches tojv_object_merge_recursive():The recursive merge code in
src/jv.c:This is a separate finding from the recently disclosed CVE-2026-33947 (unbounded recursion in path operations) and from the
jv_cmp()comparison recursion issue. Different function, different file, different trigger.PoC
Save this as
poc.jq:Then run:
Result:
Segmentation faultThe
reducebuilds an object nested 75,000 levels deep (each level is{a: <previous>}). The*operator triggersjv_object_merge_recursive()to merge the object with itself, recursing into each nestedakey until the stack is exhausted.Tested on jq 1.8.1 on x86_64 Linux. Confirmed with ASan (
AddressSanitizer: stack-overflowshowing repeatedjv_object_merge_recursiveframes) and GDB.Impact
The
*operator on objects is one of the most common ways to merge JSON in jq, used for combining configs, deep-updating nested structures, and overlaying default values. Any workflow where an attacker can influence the shape of objects being merged, or the filter expression itself, can trigger this crash. A single deep object merged with itself is enough. The crash is not exploitable for code execution on modern systems with stack guard pages, so the impact is limited to process termination.