From 7e2562074050eaff52d9f3522e1848f773168fec Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Sat, 27 Jun 2026 08:32:10 +0000 Subject: [PATCH] Patch jq for CVE-2026-49839, CVE-2026-47770 --- SPECS/jq/CVE-2026-47770.patch | 385 ++++++++++++++++++++++++++++++++++ SPECS/jq/CVE-2026-49839.patch | 34 +++ SPECS/jq/jq.spec | 7 +- 3 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 SPECS/jq/CVE-2026-47770.patch create mode 100644 SPECS/jq/CVE-2026-49839.patch diff --git a/SPECS/jq/CVE-2026-47770.patch b/SPECS/jq/CVE-2026-47770.patch new file mode 100644 index 00000000000..30c7d566579 --- /dev/null +++ b/SPECS/jq/CVE-2026-47770.patch @@ -0,0 +1,385 @@ +From 7571f793ba3b02989f47e1f1c9dea5a4e2518ed7 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Sat, 27 Jun 2026 08:26:08 +0000 +Subject: [PATCH] Guard deep structural equality and comparison recursion + (#3539) + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of None +--- + src/builtin.c | 30 +++++++++++++++++-- + src/jv.c | 37 +++++++++++++++++------ + src/jv_aux.c | 81 ++++++++++++++++++++++++++++++++++++++++++++------- + tests/jq.test | 22 ++++++++++++++ + 4 files changed, 147 insertions(+), 23 deletions(-) + +diff --git a/src/builtin.c b/src/builtin.c +index 08b94ac..5e8a9ec 100644 +--- a/src/builtin.c ++++ b/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) || +@@ -1065,6 +1081,14 @@ static jv minmax_by(jv values, jv keys, int is_min) { + 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; +@@ -2004,7 +2009,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; +@@ -2020,13 +2034,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; +@@ -2038,6 +2052,11 @@ int jv_equal(jv a, jv b) { + return r; + } + ++// Returns 1 if equal, 0 if not equal, or -1 if the comparison is too deep ++int jv_equal(jv a, jv b) { ++ return jvp_equal(a, b, 0); ++} ++ + int jv_identical(jv a, jv b) { + int r; + if (a.kind_flags != b.kind_flags +diff --git a/src/jv_aux.c b/src/jv_aux.c +index 0855053..f44ccda 100644 +--- a/src/jv_aux.c ++++ b/src/jv_aux.c +@@ -15,6 +15,24 @@ static double jv_number_get_value_and_consume(jv number) { + return value; + } + ++#ifndef MAX_CMP_DEPTH ++#define MAX_CMP_DEPTH (10000) ++#endif ++ ++struct sort_cmp_state { ++ int too_deep; ++}; ++ ++#ifdef _MSC_VER ++static __declspec(thread) struct sort_cmp_state sort_cmp_state; ++#else ++#ifdef HAVE___THREAD ++static __thread struct sort_cmp_state sort_cmp_state; ++#else ++static struct sort_cmp_state sort_cmp_state; ++#endif ++#endif ++ + static jv parse_slice(jv j, jv slice, int* pstart, int* pend) { + // Array slices + jv start_jv = jv_object_get(jv_copy(slice), jv_string("start")); +@@ -471,7 +489,7 @@ static jv delpaths_sorted(jv object, jv paths, int start) { + int delkey = jv_array_length(jv_array_get(jv_copy(paths), i)) == start + 1; + jv key = jv_array_get(jv_array_get(jv_copy(paths), i), start); + while (j < jv_array_length(jv_copy(paths)) && +- jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start))) ++ 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,12 +683,12 @@ 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); ++ r = jvp_cmp(xa, xb, depth + 1); + if (r) break; + } + } +@@ -683,19 +709,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); +diff --git a/tests/jq.test b/tests/jq.test +index 7bc6f06..bbb120f 100644 +--- a/tests/jq.test ++++ b/tests/jq.test +@@ -2173,3 +2173,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" +-- +2.45.4 + diff --git a/SPECS/jq/CVE-2026-49839.patch b/SPECS/jq/CVE-2026-49839.patch new file mode 100644 index 00000000000..fa0f5841b0e --- /dev/null +++ b/SPECS/jq/CVE-2026-49839.patch @@ -0,0 +1,34 @@ +From c7ca5b50a9a1a00a0b493c40b7c8e5d1b2112af0 Mon Sep 17 00:00:00 2001 +From: itchyny +Date: Mon, 8 Jun 2026 22:14:48 +0900 +Subject: [PATCH] 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. + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/jqlang/jq/commit/e987df0d463d85fd70825e042a082427e8275b86.patch +--- + src/jv_file.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/jv_file.c b/src/jv_file.c +index b10bcc0..40137c3 100644 +--- a/src/jv_file.c ++++ b/src/jv_file.c +@@ -57,6 +57,8 @@ jv jv_load_file(const char* filename, int raw) { + + 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; +-- +2.45.4 + diff --git a/SPECS/jq/jq.spec b/SPECS/jq/jq.spec index 3c730dc275f..e22d6c740f7 100644 --- a/SPECS/jq/jq.spec +++ b/SPECS/jq/jq.spec @@ -1,7 +1,7 @@ Summary: jq is a lightweight and flexible command-line JSON processor. Name: jq Version: 1.7.1 -Release: 6%{?dist} +Release: 7%{?dist} Group: Applications/System Vendor: Microsoft Corporation License: MIT @@ -22,6 +22,8 @@ Patch11: CVE-2026-41257.patch Patch12: CVE-2026-43895.patch Patch13: CVE-2026-43896.patch Patch14: CVE-2026-44777.patch +Patch15: CVE-2026-47770.patch +Patch16: CVE-2026-49839.patch Distribution: Azure Linux BuildRequires: bison BuildRequires: chrpath @@ -76,6 +78,9 @@ make check %{_includedir}/* %changelog +* Sat Jun 27 2026 Azure Linux Security Servicing Account - 1.7.1-7 +- Patch for CVE-2026-49839, CVE-2026-47770 + * Tue May 12 2026 Azure Linux Security Servicing Account - 1.7.1-6 - Patch for CVE-2026-43896, CVE-2026-43895, CVE-2026-41257, CVE-2026-41256, CVE-2026-40612, CVE-2026-44777