new file mode 100644
@@ -0,0 +1,233 @@
+From f58787c41835d9b17795730cb04925fdba25c71c Mon Sep 17 00:00:00 2001
+From: itchyny <itchyny@cybozu.co.jp>
+Date: Mon, 11 May 2026 20:41:38 +0900
+Subject: [PATCH] Detect circular module imports to prevent stack overflow
+
+jq used to recurse without bound on mutual or self-referential
+`import` declarations, exhausting the stack. Track each library's
+load state with a `loading` flag set before its dependencies are
+processed; a recursive reference to an in-progress library now
+reports "circular import of X".
+
+Fixes CVE-2026-44777.
+
+Signed-off-by: Anton Skorup <anton.skorup@axis.com>
+Upstream-Status: Backport [https://github.com/jqlang/jq/commit/f58787c41835d9b17795730cb04925fdba25c71c]
+---
+ Makefile.am | 2 ++
+ src/linker.c | 59 ++++++++++++++++++++++++-------------
+ tests/modules/cycle_a.jq | 2 ++
+ tests/modules/cycle_b.jq | 2 ++
+ tests/modules/cycle_self.jq | 2 ++
+ tests/shtest | 23 +++++++++++++++
+ 6 files changed, 70 insertions(+), 20 deletions(-)
+ create mode 100644 tests/modules/cycle_a.jq
+ create mode 100644 tests/modules/cycle_b.jq
+ create mode 100644 tests/modules/cycle_self.jq
+
+diff --git a/Makefile.am b/Makefile.am
+index acb94435f4..e2321bb196 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -232,6 +232,8 @@ EXTRA_DIST = $(DOC_FILES) $(man_MANS) $(TESTS) $(TEST_LOG_COMPILER) \
+ tests/modules/test_bind_order0.jq \
+ tests/modules/test_bind_order1.jq \
+ tests/modules/test_bind_order2.jq \
++ tests/modules/cycle_a.jq tests/modules/cycle_b.jq \
++ tests/modules/cycle_self.jq \
+ tests/onig.supp tests/local.supp \
+ tests/setup tests/torture/input0.json \
+ tests/optional.test tests/man.test tests/manonig.test \
+diff --git a/src/linker.c b/src/linker.c
+index e9027004cc..03f46db05c 100644
+--- a/src/linker.c
++++ b/src/linker.c
+@@ -20,9 +20,13 @@
+ #include "compile.h"
+ #include "jv_alloc.h"
+
++struct lib_entry {
++ char *name;
++ block def;
++ int loading;
++};
+ struct lib_loading_state {
+- char **names;
+- block *defs;
++ struct lib_entry *entries;
+ uint64_t ct;
+ };
+ static int load_library(jq_state *jq, jv lib_path,
+@@ -303,14 +307,24 @@ static int process_dependencies(jq_state *jq, jv jq_origin, jv lib_origin, block
+ } else {
+ uint64_t state_idx = 0;
+ for (; state_idx < lib_state->ct; ++state_idx) {
+- if (strcmp(lib_state->names[state_idx],jv_string_value(resolved)) == 0)
++ if (strcmp(lib_state->entries[state_idx].name, jv_string_value(resolved)) == 0)
+ break;
+ }
+
+ if (state_idx < lib_state->ct) { // Found
++ if (lib_state->entries[state_idx].loading) {
++ jq_report_error(jq, jv_string_fmt("jq: error: circular import of %s\n",
++ jv_string_value(resolved)));
++ jv_free(resolved);
++ jv_free(as);
++ jv_free(deps);
++ jv_free(jq_origin);
++ jv_free(lib_origin);
++ return 1;
++ }
+ jv_free(resolved);
+ // Bind the library to the program
+- bk = block_bind_library(lib_state->defs[state_idx], bk, OP_IS_CALL_PSEUDO, as_str);
++ bk = block_bind_library(lib_state->entries[state_idx].def, bk, OP_IS_CALL_PSEUDO, as_str);
+ } else { // Not found. Add it to the table before binding.
+ block dep_def_block = gen_noop();
+ nerrors += load_library(jq, resolved, is_data, raw, optional, as_str, &dep_def_block, lib_state);
+@@ -352,32 +366,38 @@ static int load_library(jq_state *jq, jv lib_path, int is_data, int raw, int opt
+ jq_report_error(jq, jv_string_fmt("jq: error loading data file %s: %s\n", jv_string_value(lib_path), jv_string_value(data)));
+ nerrors++;
+ }
+- goto out;
+ } else if (is_data) {
+ // import "foo" as $bar;
+ program = gen_const_global(jv_copy(data), as);
++ state_idx = lib_state->ct++;
++ lib_state->entries = jv_mem_realloc(lib_state->entries, lib_state->ct * sizeof(struct lib_entry));
++ lib_state->entries[state_idx].name = strdup(jv_string_value(lib_path));
++ lib_state->entries[state_idx].def = program;
++ lib_state->entries[state_idx].loading = 0;
+ } else {
+ // import "foo" as bar;
+ src = locfile_init(jq, jv_string_value(lib_path), jv_string_value(data), jv_string_length_bytes(jv_copy(data)));
+ nerrors += jq_parse_library(src, &program);
+ locfile_free(src);
+ if (nerrors == 0) {
++ // Register the library before processing its dependencies so that
++ // circular imports can be detected.
++ state_idx = lib_state->ct++;
++ lib_state->entries = jv_mem_realloc(lib_state->entries, lib_state->ct * sizeof(struct lib_entry));
++ lib_state->entries[state_idx].name = strdup(jv_string_value(lib_path));
++ lib_state->entries[state_idx].def = gen_noop();
++ lib_state->entries[state_idx].loading = 1;
++
+ char *lib_origin = strdup(jv_string_value(lib_path));
+ nerrors += process_dependencies(jq, jq_get_jq_origin(jq),
+ jv_string(dirname(lib_origin)),
+ &program, lib_state);
+ free(lib_origin);
+ program = block_bind_self(program, OP_IS_CALL_PSEUDO);
++ lib_state->entries[state_idx].def = program;
++ lib_state->entries[state_idx].loading = 0;
+ }
+ }
+- if (nerrors == 0) {
+- state_idx = lib_state->ct++;
+- lib_state->names = jv_mem_realloc(lib_state->names, lib_state->ct * sizeof(const char *));
+- lib_state->defs = jv_mem_realloc(lib_state->defs, lib_state->ct * sizeof(block));
+- lib_state->names[state_idx] = strdup(jv_string_value(lib_path));
+- lib_state->defs[state_idx] = program;
+- }
+-out:
+ *out_block = program;
+ jv_free(lib_path);
+ jv_free(data);
+@@ -415,7 +435,7 @@ jv load_module_meta(jq_state *jq, jv mod_relpath) {
+ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
+ int nerrors = 0;
+ block program;
+- struct lib_loading_state lib_state = {0,0,0};
++ struct lib_loading_state lib_state = {0,0};
+ nerrors = jq_parse(src, &program);
+ if (nerrors)
+ return nerrors;
+@@ -441,14 +461,13 @@ int load_program(jq_state *jq, struct locfile* src, block *out_block) {
+ nerrors = process_dependencies(jq, jq_get_jq_origin(jq), jq_get_prog_origin(jq), &program, &lib_state);
+ block libs = gen_noop();
+ for (uint64_t i = 0; i < lib_state.ct; ++i) {
+- free(lib_state.names[i]);
+- if (nerrors == 0 && !block_is_const(lib_state.defs[i]))
+- libs = block_join(libs, lib_state.defs[i]);
++ free(lib_state.entries[i].name);
++ if (nerrors == 0 && !block_is_const(lib_state.entries[i].def))
++ libs = block_join(libs, lib_state.entries[i].def);
+ else
+- block_free(lib_state.defs[i]);
++ block_free(lib_state.entries[i].def);
+ }
+- free(lib_state.names);
+- free(lib_state.defs);
++ free(lib_state.entries);
+ if (nerrors)
+ block_free(program);
+ else
+diff --git a/tests/modules/cycle_a.jq b/tests/modules/cycle_a.jq
+new file mode 100644
+index 0000000000..30c1deaedf
+--- /dev/null
++++ b/tests/modules/cycle_a.jq
+@@ -0,0 +1,2 @@
++import "cycle_b" as b;
++def f: null;
+diff --git a/tests/modules/cycle_b.jq b/tests/modules/cycle_b.jq
+new file mode 100644
+index 0000000000..3fdc360fcd
+--- /dev/null
++++ b/tests/modules/cycle_b.jq
+@@ -0,0 +1,2 @@
++import "cycle_a" as a;
++def f: null;
+diff --git a/tests/modules/cycle_self.jq b/tests/modules/cycle_self.jq
+new file mode 100644
+index 0000000000..8365eab1a4
+--- /dev/null
++++ b/tests/modules/cycle_self.jq
+@@ -0,0 +1,2 @@
++import "cycle_self" as s;
++def f: null;
+diff --git a/tests/shtest b/tests/shtest
+index fa972de870..aca82790bc 100755
+--- a/tests/shtest
++++ b/tests/shtest
+@@ -382,17 +382,40 @@ if ! HOME="$mods/home2" $VALGRIND $Q $JQ -n 'include "g"; empty'; then
+ exit 1
+ fi
+
++(
+ cd "$JQBASEDIR" # so that relative library paths are guaranteed correct
+ if ! $VALGRIND $Q $JQ -L ./tests/modules -ne 'import "test_bind_order" as check; check::check==true'; then
+ echo "Issue #817 regression?" 1>&2
+ exit 1
+ fi
++)
+
++(
+ cd "$JQBASEDIR"
+ if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; check::check==true'; then
+ echo "Issue #817 regression?" 1>&2
+ exit 1
+ fi
++)
++
++# CVE-2026-44777: Circular imports should be detected
++if $VALGRIND $JQ -L "$mods" -ne 'import "cycle_a" as a; null' 2> $d/out; then
++ echo "Mutual import should be rejected" 1>&2
++ exit 1
++fi
++if ! grep -q "circular import" $d/out; then
++ echo "Expected circular import error" 1>&2
++ exit 1
++fi
++
++if $VALGRIND $JQ -L "$mods" -ne 'import "cycle_self" as s; null' 2> $d/out; then
++ echo "Self import should be rejected" 1>&2
++ exit 1
++fi
++if ! grep -q "circular import" $d/out; then
++ echo "Expected circular import error" 1>&2
++ exit 1
++fi
+
+ ## Halt
+
@@ -18,6 +18,7 @@ SRC_URI = "git://github.com/jqlang/jq.git;protocol=https;branch=master;tag=jq-${
file://CVE-2026-33948.patch \
file://CVE-2026-39979.patch \
file://CVE-2026-41256.patch \
+ file://CVE-2026-44777.patch \
file://CVE-2026-49389.patch \
"