summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDeepak Rathore <deeratho@cisco.com>2026-04-06 04:55:22 -0700
committerAnuj Mittal <anuj.mittal@oss.qualcomm.com>2026-04-24 21:13:20 +0530
commitcdfa4084fecaa6bf99b99ad930735ccd53550b48 (patch)
tree23fd0e95f58f7ac9a9f7cc54c3a51ab8309e009c
parentf516c3f209391077cebb43439e1c3a7eba26111f (diff)
downloadmeta-openembedded-cdfa4084fecaa6bf99b99ad930735ccd53550b48.tar.gz
libssh: Fix CVE-2026-0967
Pick the patch [1] as mentioned in [2] [1] https://git.libssh.org/projects/libssh.git/commit/?id=6d74aa6138895b3662bade9bd578338b0c4f8a15 [2] https://security-tracker.debian.org/tracker/CVE-2026-0967 Signed-off-by: Deepak Rathore <deeratho@cisco.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
-rw-r--r--meta-oe/recipes-support/libssh/libssh/CVE-2026-0967.patch362
-rw-r--r--meta-oe/recipes-support/libssh/libssh_0.11.3.bb1
2 files changed, 363 insertions, 0 deletions
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 @@
1From a2de885e93b39d5d834f2e6d93bdc62dd9c0322d Mon Sep 17 00:00:00 2001
2From: Jakub Jelen <jjelen@redhat.com>
3Date: Wed, 17 Dec 2025 18:48:34 +0100
4Subject: [PATCH 3/4] CVE-2026-0967 match: Avoid recursive matching (ReDoS)
5MIME-Version: 1.0
6Content-Type: text/plain; charset=UTF-8
7Content-Transfer-Encoding: 8bit
8
9The specially crafted patterns (from configuration files) could cause
10exhaustive search or timeouts.
11
12Previous attempts to fix this by limiting recursion to depth 16 avoided
13stack overflow, but not timeouts. This is due to the backtracking,
14which caused the exponential time complexity O(N^16) of existing algorithm.
15
16This is code comes from the same function from OpenSSH, where this code
17originates from, which is not having this issue (due to not limiting the number
18of recursion), but will also easily exhaust stack due to unbound recursion:
19
20https://github.com/openssh/openssh-portable/commit/05bcd0cadf160fd4826a2284afa7cba6ec432633
21
22This is an attempt to simplify the algorithm by preventing the backtracking
23to previous wildcard, which should keep the same behavior for existing inputs
24while reducing the complexity to linear O(N*M).
25
26This fixes the long-term issue we had with fuzzing as well as recently reported
27security issue by Kang Yang.
28
29CVE: CVE-2026-0967
30Upstream-Status: Backport [https://git.libssh.org/projects/libssh.git/commit/?id=6d74aa6138895b3662bade9bd578338b0c4f8a15]
31
32Signed-off-by: Jakub Jelen <jjelen@redhat.com>
33Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
34(cherry picked from commit a411de5ce806e3ea24d088774b2f7584d6590b5f)
35(cherry picked from commit 6d74aa6138895b3662bade9bd578338b0c4f8a15)
36Signed-off-by: Deepak Rathore <deeratho@cisco.com>
37---
38 src/match.c | 111 +++++++++++++----------------
39 tests/unittests/torture_config.c | 116 +++++++++++++++++++++++--------
40 2 files changed, 135 insertions(+), 92 deletions(-)
41
42diff --git a/src/match.c b/src/match.c
43index 2c004c98..771ee63c 100644
44--- a/src/match.c
45+++ b/src/match.c
46@@ -53,85 +53,70 @@
47
48 #include "libssh/priv.h"
49
50-#define MAX_MATCH_RECURSION 16
51-
52-/*
53- * Returns true if the given string matches the pattern (which may contain ?
54- * and * as wildcards), and zero if it does not match.
55+/**
56+ * @brief Compare a string with a pattern containing wildcards `*` and `?`
57+ *
58+ * This function is an iterative replacement for the previously recursive
59+ * implementation to avoid exponential complexity (DoS) with specific patterns.
60+ *
61+ * @param[in] s The string to match.
62+ * @param[in] pattern The pattern to match against.
63+ *
64+ * @return 1 if the pattern matches, 0 otherwise.
65 */
66-static int match_pattern(const char *s, const char *pattern, size_t limit)
67+static int match_pattern(const char *s, const char *pattern)
68 {
69- bool had_asterisk = false;
70+ const char *s_star = NULL; /* Position in s when last `*` was met */
71+ const char *p_star = NULL; /* Position in pattern after last `*` */
72
73- if (s == NULL || pattern == NULL || limit <= 0) {
74+ if (s == NULL || pattern == NULL) {
75 return 0;
76 }
77
78- for (;;) {
79- /* If at end of pattern, accept if also at end of string. */
80- if (*pattern == '\0') {
81- return (*s == '\0');
82- }
83-
84- /* Skip all the asterisks and adjacent question marks */
85- while (*pattern == '*' || (had_asterisk && *pattern == '?')) {
86- if (*pattern == '*') {
87- had_asterisk = true;
88- }
89+ while (*s) {
90+ /* Case 1: Exact match or '?' wildcard */
91+ if (*pattern == *s || *pattern == '?') {
92+ s++;
93 pattern++;
94+ continue;
95 }
96
97- if (had_asterisk) {
98- /* If at end of pattern, accept immediately. */
99- if (!*pattern)
100- return 1;
101-
102- /* If next character in pattern is known, optimize. */
103- if (*pattern != '?') {
104- /*
105- * Look instances of the next character in
106- * pattern, and try to match starting from
107- * those.
108- */
109- for (; *s; s++)
110- if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) {
111- return 1;
112- }
113- /* Failed. */
114- return 0;
115- }
116- /*
117- * Move ahead one character at a time and try to
118- * match at each position.
119+ /* Case 2: '*' wildcard */
120+ if (*pattern == '*') {
121+ /* Record the position of the star and the current string position.
122+ * We optimistically assume * matches 0 characters first.
123 */
124- for (; *s; s++) {
125- if (match_pattern(s, pattern, limit - 1)) {
126- return 1;
127- }
128- }
129- /* Failed. */
130- return 0;
131- }
132- /*
133- * There must be at least one more character in the string.
134- * If we are at the end, fail.
135- */
136- if (!*s) {
137- return 0;
138+ p_star = ++pattern;
139+ s_star = s;
140+ continue;
141 }
142
143- /* Check if the next character of the string is acceptable. */
144- if (*pattern != '?' && *pattern != *s) {
145- return 0;
146+ /* Case 3: Mismatch */
147+ if (p_star) {
148+ /* If we have seen a star previously, backtrack.
149+ * We restore the pattern to just after the star,
150+ * but advance the string position (consume one more char for the
151+ * star).
152+ * No need to backtrack to previous stars as any match of the last
153+ * star could be eaten the same way by the previous star.
154+ */
155+ pattern = p_star;
156+ s = ++s_star;
157+ continue;
158 }
159
160- /* Move to the next character, both in string and in pattern. */
161- s++;
162+ /* Case 4: Mismatch and no star to backtrack to */
163+ return 0;
164+ }
165+
166+ /* Handle trailing stars in the pattern
167+ * (e.g., pattern "abc*" matching "abc") */
168+ while (*pattern == '*') {
169 pattern++;
170 }
171
172- /* NOTREACHED */
173- return 0;
174+ /* If we reached the end of the pattern, it's a match */
175+ return (*pattern == '\0');
176 }
177
178 /*
179@@ -182,7 +167,7 @@ int match_pattern_list(const char *string, const char *pattern,
180 sub[subi] = '\0';
181
182 /* Try to match the subpattern against the string. */
183- if (match_pattern(string, sub, MAX_MATCH_RECURSION)) {
184+ if (match_pattern(string, sub)) {
185 if (negated) {
186 return -1; /* Negative */
187 } else {
188diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
189index ada0ce8c..fcfe8fbc 100644
190--- a/tests/unittests/torture_config.c
191+++ b/tests/unittests/torture_config.c
192@@ -2342,80 +2342,138 @@ static void torture_config_match_pattern(void **state)
193 (void) state;
194
195 /* Simple test "a" matches "a" */
196- rv = match_pattern("a", "a", MAX_MATCH_RECURSION);
197+ rv = match_pattern("a", "a");
198 assert_int_equal(rv, 1);
199
200 /* Simple test "a" does not match "b" */
201- rv = match_pattern("a", "b", MAX_MATCH_RECURSION);
202+ rv = match_pattern("a", "b");
203 assert_int_equal(rv, 0);
204
205 /* NULL arguments are correctly handled */
206- rv = match_pattern("a", NULL, MAX_MATCH_RECURSION);
207+ rv = match_pattern("a", NULL);
208 assert_int_equal(rv, 0);
209- rv = match_pattern(NULL, "a", MAX_MATCH_RECURSION);
210+ rv = match_pattern(NULL, "a");
211 assert_int_equal(rv, 0);
212
213 /* Simple wildcard ? is handled in pattern */
214- rv = match_pattern("a", "?", MAX_MATCH_RECURSION);
215+ rv = match_pattern("a", "?");
216 assert_int_equal(rv, 1);
217- rv = match_pattern("aa", "?", MAX_MATCH_RECURSION);
218+ rv = match_pattern("aa", "?");
219 assert_int_equal(rv, 0);
220 /* Wildcard in search string */
221- rv = match_pattern("?", "a", MAX_MATCH_RECURSION);
222+ rv = match_pattern("?", "a");
223 assert_int_equal(rv, 0);
224- rv = match_pattern("?", "?", MAX_MATCH_RECURSION);
225+ rv = match_pattern("?", "?");
226 assert_int_equal(rv, 1);
227
228 /* Simple wildcard * is handled in pattern */
229- rv = match_pattern("a", "*", MAX_MATCH_RECURSION);
230+ rv = match_pattern("a", "*");
231 assert_int_equal(rv, 1);
232- rv = match_pattern("aa", "*", MAX_MATCH_RECURSION);
233+ rv = match_pattern("aa", "*");
234 assert_int_equal(rv, 1);
235 /* Wildcard in search string */
236- rv = match_pattern("*", "a", MAX_MATCH_RECURSION);
237+ rv = match_pattern("*", "a");
238 assert_int_equal(rv, 0);
239- rv = match_pattern("*", "*", MAX_MATCH_RECURSION);
240+ rv = match_pattern("*", "*");
241 assert_int_equal(rv, 1);
242
243 /* More complicated patterns */
244- rv = match_pattern("a", "*a", MAX_MATCH_RECURSION);
245+ rv = match_pattern("a", "*a");
246 assert_int_equal(rv, 1);
247- rv = match_pattern("a", "a*", MAX_MATCH_RECURSION);
248+ rv = match_pattern("a", "a*");
249 assert_int_equal(rv, 1);
250- rv = match_pattern("abababc", "*abc", MAX_MATCH_RECURSION);
251+ rv = match_pattern("abababc", "*abc");
252 assert_int_equal(rv, 1);
253- rv = match_pattern("ababababca", "*abc", MAX_MATCH_RECURSION);
254+ rv = match_pattern("ababababca", "*abc");
255 assert_int_equal(rv, 0);
256- rv = match_pattern("ababababca", "*abc*", MAX_MATCH_RECURSION);
257+ rv = match_pattern("ababababca", "*abc*");
258 assert_int_equal(rv, 1);
259
260 /* Multiple wildcards in row */
261- rv = match_pattern("aa", "??", MAX_MATCH_RECURSION);
262+ rv = match_pattern("aa", "??");
263 assert_int_equal(rv, 1);
264- rv = match_pattern("bba", "??a", MAX_MATCH_RECURSION);
265+ rv = match_pattern("bba", "??a");
266 assert_int_equal(rv, 1);
267- rv = match_pattern("aaa", "**a", MAX_MATCH_RECURSION);
268+ rv = match_pattern("aaa", "**a");
269 assert_int_equal(rv, 1);
270- rv = match_pattern("bbb", "**a", MAX_MATCH_RECURSION);
271+ rv = match_pattern("bbb", "**a");
272 assert_int_equal(rv, 0);
273
274 /* Consecutive asterisks do not make sense and do not need to recurse */
275- rv = match_pattern("hostname", "**********pattern", 5);
276+ rv = match_pattern("hostname", "**********pattern");
277 assert_int_equal(rv, 0);
278- rv = match_pattern("hostname", "pattern**********", 5);
279+ rv = match_pattern("hostname", "pattern**********");
280 assert_int_equal(rv, 0);
281- rv = match_pattern("pattern", "***********pattern", 5);
282+ rv = match_pattern("pattern", "***********pattern");
283 assert_int_equal(rv, 1);
284- rv = match_pattern("pattern", "pattern***********", 5);
285+ rv = match_pattern("pattern", "pattern***********");
286 assert_int_equal(rv, 1);
287
288- /* Limit the maximum recursion */
289- rv = match_pattern("hostname", "*p*a*t*t*e*r*n*", 5);
290+ rv = match_pattern("hostname", "*p*a*t*t*e*r*n*");
291 assert_int_equal(rv, 0);
292- /* Too much recursion */
293- rv = match_pattern("pattern", "*p*a*t*t*e*r*n*", 5);
294+ rv = match_pattern("pattern", "*p*a*t*t*e*r*n*");
295+ assert_int_equal(rv, 1);
296+
297+ /* Regular Expression Denial of Service */
298+ rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
299+ "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a");
300+ assert_int_equal(rv, 1);
301+ rv = match_pattern("ababababababababababababababababababababab",
302+ "*a*b*a*b*a*b*a*b*a*b*a*b*a*b*a*b");
303+ assert_int_equal(rv, 1);
304+
305+ /* A lot of backtracking */
306+ rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax",
307+ "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*ax");
308+ assert_int_equal(rv, 1);
309+
310+ /* Test backtracking: *a matches first 'a', fails on 'b', must backtrack */
311+ rv = match_pattern("axaxaxb", "*a*b");
312+ assert_int_equal(rv, 1);
313+
314+ /* Test greedy consumption with suffix */
315+ rv = match_pattern("foo_bar_baz_bar", "*bar");
316+ assert_int_equal(rv, 1);
317+
318+ /* Test exact suffix requirement (ensure no partial match acceptance) */
319+ rv = match_pattern("foobar_extra", "*bar");
320+ assert_int_equal(rv, 0);
321+
322+ /* Test multiple distinct wildcards */
323+ rv = match_pattern("a_very_long_string_with_a_pattern", "*long*pattern");
324+ assert_int_equal(rv, 1);
325+
326+ /* ? inside a * sequence */
327+ rv = match_pattern("abcdefg", "a*c?e*g");
328+ assert_int_equal(rv, 1);
329+
330+ /* Consecutive mixed wildcards */
331+ rv = match_pattern("abc", "*?c");
332+ assert_int_equal(rv, 1);
333+
334+ /* ? at the very end after * */
335+ rv = match_pattern("abc", "ab?");
336+ assert_int_equal(rv, 1);
337+ rv = match_pattern("abc", "ab*?");
338+ assert_int_equal(rv, 1);
339+
340+ /* Consecutive stars should be collapsed or handled gracefully */
341+ rv = match_pattern("abc", "a**c");
342+ assert_int_equal(rv, 1);
343+ rv = match_pattern("abc", "***");
344+ assert_int_equal(rv, 1);
345+
346+ /* Empty string handling */
347+ rv = match_pattern("", "*");
348+ assert_int_equal(rv, 1);
349+ rv = match_pattern("", "?");
350 assert_int_equal(rv, 0);
351+ rv = match_pattern("", "");
352+ assert_int_equal(rv, 1);
353
354+ /* Pattern longer than string */
355+ rv = match_pattern("short", "short_but_longer");
356+ assert_int_equal(rv, 0);
357 }
358
359 /* Identity file can be specified multiple times in the configuration
360--
3612.51.0
362
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
13 file://CVE-2026-3731_p2.patch \ 13 file://CVE-2026-3731_p2.patch \
14 file://CVE-2026-0968_p1.patch \ 14 file://CVE-2026-0968_p1.patch \
15 file://CVE-2026-0968_p2.patch \ 15 file://CVE-2026-0968_p2.patch \
16 file://CVE-2026-0967.patch \
16 " 17 "
17 18
18SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch" 19SRC_URI:append:toolchain-clang = " file://0001-CompilerChecks.cmake-drop-Wunused-variable-flag.patch"