diff mbox series

[meta-oe] jq: patch CVE-2026-47770

Message ID 20260529070522.3737233-1-antonsk@axis.com
State New
Headers show
Series [meta-oe] jq: patch CVE-2026-47770 | expand

Commit Message

Anton Skorup May 29, 2026, 7:05 a.m. UTC
From: Anton Skorup <anton@skorup.se>

This patch adds the upstream patch for CVE-2026-47770.

Details: https://ubuntu.com/security/CVE-2026-47770
Reference: https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831
Signed-off-by: Anton Skorup <anton.skorup@axis.com>
---
 .../jq/jq/CVE-2026-47770.patch                | 483 ++++++++++++++++++
 meta-oe/recipes-devtools/jq/jq_1.8.1.bb       |   1 +
 2 files changed, 484 insertions(+)
 create mode 100644 meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch

Comments

Khem Raj May 29, 2026, 9:34 a.m. UTC | #1
Please add appropriate upstream-status tag to the patch

On Fri, May 29, 2026, 9:24 AM Anton Skorup via lists.openembedded.org
<antonsk=axis.com@lists.openembedded.org> wrote:

> From: Anton Skorup <anton@skorup.se>
>
> This patch adds the upstream patch for CVE-2026-47770.
>
> Details: https://ubuntu.com/security/CVE-2026-47770
> Reference:
> https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831
> Signed-off-by
> <https://github.com/jqlang/jq/commit/7122866869960b55cea3646bc91334ef55787831Signed-off-by>:
> Anton Skorup <anton.skorup@axis.com>
> ---
>  .../jq/jq/CVE-2026-47770.patch                | 483 ++++++++++++++++++
>  meta-oe/recipes-devtools/jq/jq_1.8.1.bb       |   1 +
>  2 files changed, 484 insertions(+)
>  create mode 100644 meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch
>
> diff --git a/meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch
> b/meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch
> new file mode 100644
> index 0000000000..2c1eb04f76
> --- /dev/null
> +++ b/meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch
> @@ -0,0 +1,483 @@
> +From b4d48d83e7fe19a31824822da1393a367a59b7ba Mon Sep 17 00:00:00 2001
> +From: Yu-Fu Fu <yufu@yfu.tw>
> +Date: Fri, 22 May 2026 04:07:16 -0700
> +Subject: [PATCH] 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".
> +
> +Fixes CVE-2026-47770.
> +
> +Change-Id: I11794879105850d5e3cd48943b1ad31bfdd2d23a
> +---
> + src/builtin.c |  36 +++++++++++++++--
> + src/jv.c      |  46 +++++++++++++++++-----
> + src/jv_aux.c  | 105 ++++++++++++++++++++++++++++++++++++++++++--------
> + tests/jq.test |  40 +++++++++++++++++++
> + 4 files changed, 198 insertions(+), 29 deletions(-)
> +
> +diff --git a/src/builtin.c b/src/builtin.c
> +index ac56f9f..ac25f90 100644
> +--- a/src/builtin.c
> ++++ b/src/builtin.c
> +@@ -295,7 +295,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;
> +         }
> +@@ -379,11 +387,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 {
> +@@ -395,6 +409,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) ||
> +@@ -845,6 +861,12 @@ static jv f_bsearch(jq_state *jq, jv input, jv
> target) {
> +   while (start < end) {
> +     int mid = start + (end - start) / 2;
> +     int result = jv_cmp(jv_copy(target), jv_array_get(jv_copy(input),
> mid));
> ++    if (result == INT_MIN) {
> ++      jv_free(answer);
> ++      jv_free(input);
> ++      jv_free(target);
> ++      return jv_invalid_with_msg(jv_string("Comparison too deep"));
> ++    }
> +     if (result == 0) {
> +       answer = jv_number(mid);
> +       break;
> +@@ -1136,6 +1158,14 @@ static jv minmax_by(jv values, jv keys, int
> is_min) {
> +   for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
> +     jv item = jv_array_get(jv_copy(keys), i);
> +     int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
> ++    if (cmp == INT_MIN) {
> ++      jv_free(item);
> ++      jv_free(values);
> ++      jv_free(keys);
> ++      jv_free(retkey);
> ++      jv_free(ret);
> ++      return jv_invalid_with_msg(jv_string("Comparison too deep"));
> ++    }
> +     if ((cmp < 0) == (is_min == 1)) {
> +       jv_free(retkey);
> +       retkey = item;
> +diff --git a/src/jv.c b/src/jv.c
> +index f701b46..e079d08 100644
> +--- a/src/jv.c
> ++++ b/src/jv.c
> +@@ -912,16 +912,20 @@ static jv* jvp_array_write(jv* a, int i) {
> +   }
> + }
> +
> +-static int jvp_array_equal(jv a, jv b) {
> ++static int jvp_equal(jv a, jv b, int depth);
> ++
> ++static int jvp_array_equal(jv a, jv b, int depth) {
> +   if (jvp_array_length(a) != jvp_array_length(b))
> +     return 0;
> +   if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
> +       jvp_array_offset(a) == jvp_array_offset(b))
> +     return 1;
> +   for (int i=0; i<jvp_array_length(a); i++) {
> +-    if (!jv_equal(jv_copy(*jvp_array_read(a, i)),
> +-                  jv_copy(*jvp_array_read(b, i))))
> +-      return 0;
> ++    int r = jvp_equal(jv_copy(*jvp_array_read(a, i)),
> ++                      jv_copy(*jvp_array_read(b, i)),
> ++                      depth);
> ++    if (r <= 0)
> ++      return r;
> +   }
> +   return 1;
> + }
> +@@ -1071,7 +1075,14 @@ jv jv_array_indexes(jv a, jv b) {
> +   int alen = jv_array_length(jv_copy(a));
> +   for (int ai = 0; ai < alen; ++ai) {
> +     jv_array_foreach(b, bi, belem) {
> +-      if (!jv_equal(jv_array_get(jv_copy(a), ai + bi), belem))
> ++      int equal = jv_equal(jv_array_get(jv_copy(a), ai + bi), belem);
> ++      if (equal < 0) {
> ++        jv_free(res);
> ++        jv_free(a);
> ++        jv_free(b);
> ++        return jv_invalid_with_msg(jv_string("Equality check too deep"));
> ++      }
> ++      if (!equal)
> +         idx = -1;
> +       else if (bi == 0 && idx == -1)
> +         idx = ai;
> +@@ -1828,7 +1839,7 @@ static int jvp_object_length(jv object) {
> +   return n;
> + }
> +
> +-static int jvp_object_equal(jv o1, jv o2) {
> ++static int jvp_object_equal(jv o1, jv o2, int depth) {
> +   int len2 = jvp_object_length(o2);
> +   int len1 = 0;
> +   for (int i=0; i<jvp_object_size(o1); i++) {
> +@@ -1837,7 +1848,8 @@ static int jvp_object_equal(jv o1, jv o2) {
> +     jv* slot2 = jvp_object_read(o2, slot->string);
> +     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;
> +@@ -2032,7 +2044,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;
> +@@ -2048,13 +2069,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;
> +@@ -2066,6 +2087,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 594a21f..a39f1f1 100644
> +--- a/src/jv_aux.c
> ++++ b/src/jv_aux.c
> +@@ -16,6 +16,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,8 +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)))
> +-      j++;
> ++           jv_equal(jv_copy(key),
> jv_array_get(jv_array_get(jv_copy(paths), j), start)) == 1);
> +     // if i <= entry < j, then entry starts with key
> +     if (delkey) {
> +       // deleting this entire key, we don't care about any more specific
> deletions
> +@@ -606,7 +623,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);
> +@@ -621,14 +644,13 @@ int jv_cmp(jv a, jv b) {
> +   case JV_KIND_FALSE:
> +   case JV_KIND_TRUE:
> +     // there's only one of each of these values
> +-    r = 0;
> +     break;
> +
> +   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);
> +     }
> +@@ -652,7 +674,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;
> +@@ -661,13 +685,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);
> +@@ -680,6 +705,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;
> +@@ -687,19 +717,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;
> +   if (n == 0) {
> +     jv_free(objects);
> +     jv_free(keys);
> +@@ -713,7 +756,13 @@ static struct sort_entry* sort_items(jv objects, jv
> keys) {
> +   }
> +   jv_free(objects);
> +   jv_free(keys);
> ++  sort_cmp_state.too_deep = 0;
> +   qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
> ++  if (sort_cmp_state.too_deep) {
> ++    sort_entry_array_free(entries, 0, n);
> ++    *too_deep = 1;
> ++    return NULL;
> ++  }
> +   return entries;
> + }
> +
> +@@ -722,7 +771,10 @@ jv jv_sort(jv objects, jv keys) {
> +   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));
> +-  struct sort_entry* entries = sort_items(objects, keys);
> ++  int too_deep = 0;
> ++  struct sort_entry* entries = sort_items(objects, keys, &too_deep);
> ++  if (too_deep)
> ++    return jv_invalid_with_msg(jv_string("Comparison too deep"));
> +   jv ret = jv_array();
> +   for (int i=0; i<n; i++) {
> +     jv_free(entries[i].key);
> +@@ -737,13 +789,24 @@ jv jv_group(jv objects, jv keys) {
> +   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));
> +-  struct sort_entry* entries = sort_items(objects, keys);
> ++  int too_deep = 0;
> ++  struct sort_entry* entries = sort_items(objects, keys, &too_deep);
> ++  if (too_deep)
> ++    return jv_invalid_with_msg(jv_string("Comparison too deep"));
> +   jv ret = jv_array();
> +   if (n > 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);
> +@@ -765,11 +828,21 @@ jv jv_unique(jv objects, jv keys) {
> +   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));
> +-  struct sort_entry* entries = sort_items(objects, keys);
> ++  int too_deep = 0;
> ++  struct sort_entry* entries = sort_items(objects, keys, &too_deep);
> ++  if (too_deep)
> ++    return jv_invalid_with_msg(jv_string("Comparison too deep"));
> +   jv ret = jv_array();
> +   jv curr_key = jv_invalid();
> +   for (int i = 0; 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);
> ++      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);
> +       jv_free(entries[i].object);
> +     } else {
> +diff --git a/tests/jq.test b/tests/jq.test
> +index 5013bce..f35ef94 100644
> +--- a/tests/jq.test
> ++++ b/tests/jq.test
> +@@ -2560,3 +2560,43 @@ null
> + try delpaths([[range(10001) | 0]]) catch .
> + null
> + "Path 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"
> ++
> ++# 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"
> ++
> ++# 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.43.0
> +
> diff --git a/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
> b/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
> index 026f6bfa71..7665ba2511 100644
> --- a/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
> +++ b/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
> @@ -17,6 +17,7 @@ SRC_URI = "git://
> github.com/jqlang/jq.git;protocol=https;branch=master;tag=jq-${
> <http://github.com/jqlang/jq.git;protocol=https;branch=master;tag=jq-$%7B>
>             file://CVE-2026-33947.patch \
>             file://CVE-2026-33948.patch \
>             file://CVE-2026-39979.patch \
> +           file://CVE-2026-47770.patch \
>             "
>
>  inherit autotools ptest
> --
> 2.43.0
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#127282):
> https://lists.openembedded.org/g/openembedded-devel/message/127282
> Mute This Topic: https://lists.openembedded.org/mt/119543594/1997914
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [
> raj.khem@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
>
diff mbox series

Patch

diff --git a/meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch b/meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch
new file mode 100644
index 0000000000..2c1eb04f76
--- /dev/null
+++ b/meta-oe/recipes-devtools/jq/jq/CVE-2026-47770.patch
@@ -0,0 +1,483 @@ 
+From b4d48d83e7fe19a31824822da1393a367a59b7ba Mon Sep 17 00:00:00 2001
+From: Yu-Fu Fu <yufu@yfu.tw>
+Date: Fri, 22 May 2026 04:07:16 -0700
+Subject: [PATCH] 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".
+
+Fixes CVE-2026-47770.
+
+Change-Id: I11794879105850d5e3cd48943b1ad31bfdd2d23a
+---
+ src/builtin.c |  36 +++++++++++++++--
+ src/jv.c      |  46 +++++++++++++++++-----
+ src/jv_aux.c  | 105 ++++++++++++++++++++++++++++++++++++++++++--------
+ tests/jq.test |  40 +++++++++++++++++++
+ 4 files changed, 198 insertions(+), 29 deletions(-)
+
+diff --git a/src/builtin.c b/src/builtin.c
+index ac56f9f..ac25f90 100644
+--- a/src/builtin.c
++++ b/src/builtin.c
+@@ -295,7 +295,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;
+         }
+@@ -379,11 +387,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 {
+@@ -395,6 +409,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) ||
+@@ -845,6 +861,12 @@ static jv f_bsearch(jq_state *jq, jv input, jv target) {
+   while (start < end) {
+     int mid = start + (end - start) / 2;
+     int result = jv_cmp(jv_copy(target), jv_array_get(jv_copy(input), mid));
++    if (result == INT_MIN) {
++      jv_free(answer);
++      jv_free(input);
++      jv_free(target);
++      return jv_invalid_with_msg(jv_string("Comparison too deep"));
++    }
+     if (result == 0) {
+       answer = jv_number(mid);
+       break;
+@@ -1136,6 +1158,14 @@ static jv minmax_by(jv values, jv keys, int is_min) {
+   for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
+     jv item = jv_array_get(jv_copy(keys), i);
+     int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
++    if (cmp == INT_MIN) {
++      jv_free(item);
++      jv_free(values);
++      jv_free(keys);
++      jv_free(retkey);
++      jv_free(ret);
++      return jv_invalid_with_msg(jv_string("Comparison too deep"));
++    }
+     if ((cmp < 0) == (is_min == 1)) {
+       jv_free(retkey);
+       retkey = item;
+diff --git a/src/jv.c b/src/jv.c
+index f701b46..e079d08 100644
+--- a/src/jv.c
++++ b/src/jv.c
+@@ -912,16 +912,20 @@ static jv* jvp_array_write(jv* a, int i) {
+   }
+ }
+ 
+-static int jvp_array_equal(jv a, jv b) {
++static int jvp_equal(jv a, jv b, int depth);
++
++static int jvp_array_equal(jv a, jv b, int depth) {
+   if (jvp_array_length(a) != jvp_array_length(b))
+     return 0;
+   if (jvp_array_ptr(a) == jvp_array_ptr(b) &&
+       jvp_array_offset(a) == jvp_array_offset(b))
+     return 1;
+   for (int i=0; i<jvp_array_length(a); i++) {
+-    if (!jv_equal(jv_copy(*jvp_array_read(a, i)),
+-                  jv_copy(*jvp_array_read(b, i))))
+-      return 0;
++    int r = jvp_equal(jv_copy(*jvp_array_read(a, i)),
++                      jv_copy(*jvp_array_read(b, i)),
++                      depth);
++    if (r <= 0)
++      return r;
+   }
+   return 1;
+ }
+@@ -1071,7 +1075,14 @@ jv jv_array_indexes(jv a, jv b) {
+   int alen = jv_array_length(jv_copy(a));
+   for (int ai = 0; ai < alen; ++ai) {
+     jv_array_foreach(b, bi, belem) {
+-      if (!jv_equal(jv_array_get(jv_copy(a), ai + bi), belem))
++      int equal = jv_equal(jv_array_get(jv_copy(a), ai + bi), belem);
++      if (equal < 0) {
++        jv_free(res);
++        jv_free(a);
++        jv_free(b);
++        return jv_invalid_with_msg(jv_string("Equality check too deep"));
++      }
++      if (!equal)
+         idx = -1;
+       else if (bi == 0 && idx == -1)
+         idx = ai;
+@@ -1828,7 +1839,7 @@ static int jvp_object_length(jv object) {
+   return n;
+ }
+ 
+-static int jvp_object_equal(jv o1, jv o2) {
++static int jvp_object_equal(jv o1, jv o2, int depth) {
+   int len2 = jvp_object_length(o2);
+   int len1 = 0;
+   for (int i=0; i<jvp_object_size(o1); i++) {
+@@ -1837,7 +1848,8 @@ static int jvp_object_equal(jv o1, jv o2) {
+     jv* slot2 = jvp_object_read(o2, slot->string);
+     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;
+@@ -2032,7 +2044,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;
+@@ -2048,13 +2069,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;
+@@ -2066,6 +2087,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 594a21f..a39f1f1 100644
+--- a/src/jv_aux.c
++++ b/src/jv_aux.c
+@@ -16,6 +16,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,8 +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)))
+-      j++;
++           jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)) == 1);
+     // if i <= entry < j, then entry starts with key
+     if (delkey) {
+       // deleting this entire key, we don't care about any more specific deletions
+@@ -606,7 +623,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);
+@@ -621,14 +644,13 @@ int jv_cmp(jv a, jv b) {
+   case JV_KIND_FALSE:
+   case JV_KIND_TRUE:
+     // there's only one of each of these values
+-    r = 0;
+     break;
+ 
+   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);
+     }
+@@ -652,7 +674,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;
+@@ -661,13 +685,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);
+@@ -680,6 +705,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;
+@@ -687,19 +717,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;
+   if (n == 0) {
+     jv_free(objects);
+     jv_free(keys);
+@@ -713,7 +756,13 @@ static struct sort_entry* sort_items(jv objects, jv keys) {
+   }
+   jv_free(objects);
+   jv_free(keys);
++  sort_cmp_state.too_deep = 0;
+   qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
++  if (sort_cmp_state.too_deep) {
++    sort_entry_array_free(entries, 0, n);
++    *too_deep = 1;
++    return NULL;
++  }
+   return entries;
+ }
+ 
+@@ -722,7 +771,10 @@ jv jv_sort(jv objects, jv keys) {
+   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));
+-  struct sort_entry* entries = sort_items(objects, keys);
++  int too_deep = 0;
++  struct sort_entry* entries = sort_items(objects, keys, &too_deep);
++  if (too_deep)
++    return jv_invalid_with_msg(jv_string("Comparison too deep"));
+   jv ret = jv_array();
+   for (int i=0; i<n; i++) {
+     jv_free(entries[i].key);
+@@ -737,13 +789,24 @@ jv jv_group(jv objects, jv keys) {
+   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));
+-  struct sort_entry* entries = sort_items(objects, keys);
++  int too_deep = 0;
++  struct sort_entry* entries = sort_items(objects, keys, &too_deep);
++  if (too_deep)
++    return jv_invalid_with_msg(jv_string("Comparison too deep"));
+   jv ret = jv_array();
+   if (n > 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);
+@@ -765,11 +828,21 @@ jv jv_unique(jv objects, jv keys) {
+   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));
+-  struct sort_entry* entries = sort_items(objects, keys);
++  int too_deep = 0;
++  struct sort_entry* entries = sort_items(objects, keys, &too_deep);
++  if (too_deep)
++    return jv_invalid_with_msg(jv_string("Comparison too deep"));
+   jv ret = jv_array();
+   jv curr_key = jv_invalid();
+   for (int i = 0; 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);
++      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);
+       jv_free(entries[i].object);
+     } else {
+diff --git a/tests/jq.test b/tests/jq.test
+index 5013bce..f35ef94 100644
+--- a/tests/jq.test
++++ b/tests/jq.test
+@@ -2560,3 +2560,43 @@ null
+ try delpaths([[range(10001) | 0]]) catch .
+ null
+ "Path 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"
++
++# 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"
++
++# 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.43.0
+
diff --git a/meta-oe/recipes-devtools/jq/jq_1.8.1.bb b/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
index 026f6bfa71..7665ba2511 100644
--- a/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
+++ b/meta-oe/recipes-devtools/jq/jq_1.8.1.bb
@@ -17,6 +17,7 @@  SRC_URI = "git://github.com/jqlang/jq.git;protocol=https;branch=master;tag=jq-${
            file://CVE-2026-33947.patch \
            file://CVE-2026-33948.patch \
            file://CVE-2026-39979.patch \
+           file://CVE-2026-47770.patch \
            "
 
 inherit autotools ptest