diff --git a/meta-oe/recipes-support/libssh/libssh/CVE-2026-0967.patch b/meta-oe/recipes-support/libssh/libssh/CVE-2026-0967.patch
new file mode 100644
index 0000000000..0b32dfb97f
--- /dev/null
+++ b/meta-oe/recipes-support/libssh/libssh/CVE-2026-0967.patch
@@ -0,0 +1,362 @@
+From a2de885e93b39d5d834f2e6d93bdc62dd9c0322d Mon Sep 17 00:00:00 2001
+From: Jakub Jelen <jjelen@redhat.com>
+Date: Wed, 17 Dec 2025 18:48:34 +0100
+Subject: [PATCH 3/4] CVE-2026-0967 match: Avoid recursive matching (ReDoS)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The specially crafted patterns (from configuration files) could cause
+exhaustive search or timeouts.
+
+Previous attempts to fix this by limiting recursion to depth 16 avoided
+stack overflow, but not timeouts. This is due to the backtracking,
+which caused the exponential time complexity O(N^16) of existing algorithm.
+
+This is code comes from the same function from OpenSSH, where this code
+originates from, which is not having this issue (due to not limiting the number
+of recursion), but will also easily exhaust stack due to unbound recursion:
+
+https://github.com/openssh/openssh-portable/commit/05bcd0cadf160fd4826a2284afa7cba6ec432633
+
+This is an attempt to simplify the algorithm by preventing the backtracking
+to previous wildcard, which should keep the same behavior for existing inputs
+while reducing the complexity to linear O(N*M).
+
+This fixes the long-term issue we had with fuzzing as well as recently reported
+security issue by Kang Yang.
+
+CVE: CVE-2026-0967
+Upstream-Status: Backport [https://git.libssh.org/projects/libssh.git/commit/?id=6d74aa6138895b3662bade9bd578338b0c4f8a15]
+
+Signed-off-by: Jakub Jelen <jjelen@redhat.com>
+Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
+(cherry picked from commit a411de5ce806e3ea24d088774b2f7584d6590b5f)
+(cherry picked from commit 6d74aa6138895b3662bade9bd578338b0c4f8a15)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ src/match.c                      | 111 +++++++++++++----------------
+ tests/unittests/torture_config.c | 116 +++++++++++++++++++++++--------
+ 2 files changed, 135 insertions(+), 92 deletions(-)
+
+diff --git a/src/match.c b/src/match.c
+index 2c004c98..771ee63c 100644
+--- a/src/match.c
++++ b/src/match.c
+@@ -53,85 +53,70 @@
+ 
+ #include "libssh/priv.h"
+ 
+-#define MAX_MATCH_RECURSION 16
+-
+-/*
+- * Returns true if the given string matches the pattern (which may contain ?
+- * and * as wildcards), and zero if it does not match.
++/**
++ * @brief Compare a string with a pattern containing wildcards `*` and `?`
++ *
++ * This function is an iterative replacement for the previously recursive
++ * implementation to avoid exponential complexity (DoS) with specific patterns.
++ *
++ * @param[in]  s        The string to match.
++ * @param[in]  pattern  The pattern to match against.
++ *
++ * @return              1 if the pattern matches, 0 otherwise.
+  */
+-static int match_pattern(const char *s, const char *pattern, size_t limit)
++static int match_pattern(const char *s, const char *pattern)
+ {
+-    bool had_asterisk = false;
++    const char *s_star = NULL; /* Position in s when last `*` was met */
++    const char *p_star = NULL; /* Position in pattern after last `*` */
+ 
+-    if (s == NULL || pattern == NULL || limit <= 0) {
++    if (s == NULL || pattern == NULL) {
+         return 0;
+     }
+ 
+-    for (;;) {
+-        /* If at end of pattern, accept if also at end of string. */
+-        if (*pattern == '\0') {
+-            return (*s == '\0');
+-        }
+-
+-        /* Skip all the asterisks and adjacent question marks */
+-        while (*pattern == '*' || (had_asterisk && *pattern == '?')) {
+-            if (*pattern == '*') {
+-                had_asterisk = true;
+-            }
++    while (*s) {
++        /* Case 1: Exact match or '?' wildcard */
++        if (*pattern == *s || *pattern == '?') {
++            s++;
+             pattern++;
++            continue;
+         }
+ 
+-        if (had_asterisk) {
+-            /* If at end of pattern, accept immediately. */
+-            if (!*pattern)
+-                return 1;
+-
+-            /* If next character in pattern is known, optimize. */
+-            if (*pattern != '?') {
+-                /*
+-                 * Look instances of the next character in
+-                 * pattern, and try to match starting from
+-                 * those.
+-                 */
+-                for (; *s; s++)
+-                    if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) {
+-                        return 1;
+-                    }
+-                /* Failed. */
+-                return 0;
+-            }
+-            /*
+-             * Move ahead one character at a time and try to
+-             * match at each position.
++        /* Case 2: '*' wildcard */
++        if (*pattern == '*') {
++            /* Record the position of the star and the current string position.
++             * We optimistically assume * matches 0 characters first.
+              */
+-            for (; *s; s++) {
+-                if (match_pattern(s, pattern, limit - 1)) {
+-                    return 1;
+-                }
+-            }
+-            /* Failed. */
+-            return 0;
+-        }
+-        /*
+-         * There must be at least one more character in the string.
+-         * If we are at the end, fail.
+-         */
+-        if (!*s) {
+-            return 0;
++            p_star = ++pattern;
++            s_star = s;
++            continue;
+         }
+ 
+-        /* Check if the next character of the string is acceptable. */
+-        if (*pattern != '?' && *pattern != *s) {
+-            return 0;
++        /* Case 3: Mismatch */
++        if (p_star) {
++            /* If we have seen a star previously, backtrack.
++             * We restore the pattern to just after the star,
++             * but advance the string position (consume one more char for the
++             * star).
++             * No need to backtrack to previous stars as any match of the last
++             * star could be eaten the same way by the previous star.
++             */
++            pattern = p_star;
++            s = ++s_star;
++            continue;
+         }
+ 
+-        /* Move to the next character, both in string and in pattern. */
+-        s++;
++        /* Case 4: Mismatch and no star to backtrack to */
++        return 0;
++    }
++
++    /* Handle trailing stars in the pattern
++     * (e.g., pattern "abc*" matching "abc") */
++    while (*pattern == '*') {
+         pattern++;
+     }
+ 
+-    /* NOTREACHED */
+-    return 0;
++    /* If we reached the end of the pattern, it's a match */
++    return (*pattern == '\0');
+ }
+ 
+ /*
+@@ -182,7 +167,7 @@ int match_pattern_list(const char *string, const char *pattern,
+     sub[subi] = '\0';
+ 
+     /* Try to match the subpattern against the string. */
+-    if (match_pattern(string, sub, MAX_MATCH_RECURSION)) {
++    if (match_pattern(string, sub)) {
+       if (negated) {
+         return -1;        /* Negative */
+       } else {
+diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
+index ada0ce8c..fcfe8fbc 100644
+--- a/tests/unittests/torture_config.c
++++ b/tests/unittests/torture_config.c
+@@ -2342,80 +2342,138 @@ static void torture_config_match_pattern(void **state)
+     (void) state;
+ 
+     /* Simple test "a" matches "a" */
+-    rv = match_pattern("a", "a", MAX_MATCH_RECURSION);
++    rv = match_pattern("a", "a");
+     assert_int_equal(rv, 1);
+ 
+     /* Simple test "a" does not match "b" */
+-    rv = match_pattern("a", "b", MAX_MATCH_RECURSION);
++    rv = match_pattern("a", "b");
+     assert_int_equal(rv, 0);
+ 
+     /* NULL arguments are correctly handled */
+-    rv = match_pattern("a", NULL, MAX_MATCH_RECURSION);
++    rv = match_pattern("a", NULL);
+     assert_int_equal(rv, 0);
+-    rv = match_pattern(NULL, "a", MAX_MATCH_RECURSION);
++    rv = match_pattern(NULL, "a");
+     assert_int_equal(rv, 0);
+ 
+     /* Simple wildcard ? is handled in pattern */
+-    rv = match_pattern("a", "?", MAX_MATCH_RECURSION);
++    rv = match_pattern("a", "?");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("aa", "?", MAX_MATCH_RECURSION);
++    rv = match_pattern("aa", "?");
+     assert_int_equal(rv, 0);
+     /* Wildcard in search string */
+-    rv = match_pattern("?", "a", MAX_MATCH_RECURSION);
++    rv = match_pattern("?", "a");
+     assert_int_equal(rv, 0);
+-    rv = match_pattern("?", "?", MAX_MATCH_RECURSION);
++    rv = match_pattern("?", "?");
+     assert_int_equal(rv, 1);
+ 
+     /* Simple wildcard * is handled in pattern */
+-    rv = match_pattern("a", "*", MAX_MATCH_RECURSION);
++    rv = match_pattern("a", "*");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("aa", "*", MAX_MATCH_RECURSION);
++    rv = match_pattern("aa", "*");
+     assert_int_equal(rv, 1);
+     /* Wildcard in search string */
+-    rv = match_pattern("*", "a", MAX_MATCH_RECURSION);
++    rv = match_pattern("*", "a");
+     assert_int_equal(rv, 0);
+-    rv = match_pattern("*", "*", MAX_MATCH_RECURSION);
++    rv = match_pattern("*", "*");
+     assert_int_equal(rv, 1);
+ 
+     /* More complicated patterns */
+-    rv = match_pattern("a", "*a", MAX_MATCH_RECURSION);
++    rv = match_pattern("a", "*a");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("a", "a*", MAX_MATCH_RECURSION);
++    rv = match_pattern("a", "a*");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("abababc", "*abc", MAX_MATCH_RECURSION);
++    rv = match_pattern("abababc", "*abc");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("ababababca", "*abc", MAX_MATCH_RECURSION);
++    rv = match_pattern("ababababca", "*abc");
+     assert_int_equal(rv, 0);
+-    rv = match_pattern("ababababca", "*abc*", MAX_MATCH_RECURSION);
++    rv = match_pattern("ababababca", "*abc*");
+     assert_int_equal(rv, 1);
+ 
+     /* Multiple wildcards in row */
+-    rv = match_pattern("aa", "??", MAX_MATCH_RECURSION);
++    rv = match_pattern("aa", "??");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("bba", "??a", MAX_MATCH_RECURSION);
++    rv = match_pattern("bba", "??a");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("aaa", "**a", MAX_MATCH_RECURSION);
++    rv = match_pattern("aaa", "**a");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("bbb", "**a", MAX_MATCH_RECURSION);
++    rv = match_pattern("bbb", "**a");
+     assert_int_equal(rv, 0);
+ 
+     /* Consecutive asterisks do not make sense and do not need to recurse */
+-    rv = match_pattern("hostname", "**********pattern", 5);
++    rv = match_pattern("hostname", "**********pattern");
+     assert_int_equal(rv, 0);
+-    rv = match_pattern("hostname", "pattern**********", 5);
++    rv = match_pattern("hostname", "pattern**********");
+     assert_int_equal(rv, 0);
+-    rv = match_pattern("pattern", "***********pattern", 5);
++    rv = match_pattern("pattern", "***********pattern");
+     assert_int_equal(rv, 1);
+-    rv = match_pattern("pattern", "pattern***********", 5);
++    rv = match_pattern("pattern", "pattern***********");
+     assert_int_equal(rv, 1);
+ 
+-    /* Limit the maximum recursion */
+-    rv = match_pattern("hostname", "*p*a*t*t*e*r*n*", 5);
++    rv = match_pattern("hostname", "*p*a*t*t*e*r*n*");
+     assert_int_equal(rv, 0);
+-    /* Too much recursion */
+-    rv = match_pattern("pattern", "*p*a*t*t*e*r*n*", 5);
++    rv = match_pattern("pattern", "*p*a*t*t*e*r*n*");
++    assert_int_equal(rv, 1);
++
++    /* Regular Expression Denial of Service */
++    rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
++                       "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a");
++    assert_int_equal(rv, 1);
++    rv = match_pattern("ababababababababababababababababababababab",
++                       "*a*b*a*b*a*b*a*b*a*b*a*b*a*b*a*b");
++    assert_int_equal(rv, 1);
++
++    /* A lot of backtracking */
++    rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax",
++                       "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*ax");
++    assert_int_equal(rv, 1);
++
++    /* Test backtracking: *a matches first 'a', fails on 'b', must backtrack */
++    rv = match_pattern("axaxaxb", "*a*b");
++    assert_int_equal(rv, 1);
++
++    /* Test greedy consumption with suffix */
++    rv = match_pattern("foo_bar_baz_bar", "*bar");
++    assert_int_equal(rv, 1);
++
++    /* Test exact suffix requirement (ensure no partial match acceptance) */
++    rv = match_pattern("foobar_extra", "*bar");
++    assert_int_equal(rv, 0);
++
++    /* Test multiple distinct wildcards */
++    rv = match_pattern("a_very_long_string_with_a_pattern", "*long*pattern");
++    assert_int_equal(rv, 1);
++
++    /* ? inside a * sequence */
++    rv = match_pattern("abcdefg", "a*c?e*g");
++    assert_int_equal(rv, 1);
++
++    /* Consecutive mixed wildcards */
++    rv = match_pattern("abc", "*?c");
++    assert_int_equal(rv, 1);
++
++    /* ? at the very end after * */
++    rv = match_pattern("abc", "ab?");
++    assert_int_equal(rv, 1);
++    rv = match_pattern("abc", "ab*?");
++    assert_int_equal(rv, 1);
++
++    /* Consecutive stars should be collapsed or handled gracefully */
++    rv = match_pattern("abc", "a**c");
++    assert_int_equal(rv, 1);
++    rv = match_pattern("abc", "***");
++    assert_int_equal(rv, 1);
++
++    /* Empty string handling */
++    rv = match_pattern("", "*");
++    assert_int_equal(rv, 1);
++    rv = match_pattern("", "?");
+     assert_int_equal(rv, 0);
++    rv = match_pattern("", "");
++    assert_int_equal(rv, 1);
+ 
++    /* Pattern longer than string */
++    rv = match_pattern("short", "short_but_longer");
++    assert_int_equal(rv, 0);
+ }
+ 
+ /* Identity file can be specified multiple times in the configuration
+-- 
+2.51.0
+
diff --git a/meta-oe/recipes-support/libssh/libssh_0.11.3.bb b/meta-oe/recipes-support/libssh/libssh_0.11.3.bb
index 1d4fd637d9..193ff3512d 100644
--- a/meta-oe/recipes-support/libssh/libssh_0.11.3.bb
+++ b/meta-oe/recipes-support/libssh/libssh_0.11.3.bb
@@ -13,6 +13,7 @@ SRC_URI = "git://git.libssh.org/projects/libssh.git;protocol=https;branch=stable
            file://CVE-2026-3731_p2.patch \
            file://CVE-2026-0968_p1.patch \
            file://CVE-2026-0968_p2.patch \
+           file://CVE-2026-0967.patch \
           "
 
 SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch"
