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