Hello, I've prepared libssh 0.10.6-0+deb12u3 for bookworm-security, and it's ready for review.
Trixie already has these fixes in DSA-6410-1 (0.11.5-0+deb13u1), and libssh isn't in dsa-needed.txt, so there is no Trixie DSA to wait for. The debdiff against deb12u2 is attached. This fixes 13 CVEs. Each one is the upstream stable-0.11 fix, ported to 0.10. - CVE-2026-0964: an SCP server can send a filename that escapes the destination directory. Names that are not a plain file in that directory are rejected. - CVE-2026-0965: a config path that is a FIFO, or something huge like /proc/kcore, could block or exhaust memory. Only a regular file under the size limit is read. The new open helper is internal, same as the hostname syntax check already was, and it is not exported. - CVE-2026-0966: ssh_get_hexa() walks off the start of the heap when the buffer is empty. - CVE-2026-0967: match_pattern() could be driven into catastrophic backtracking. The match is iterative now, and the ordinary patterns still match the same way. - CVE-2026-0968: sftp_parse_longname() reads past the end of a short listing line. - CVE-2026-3731: the SFTP extension accessors read one past the list when the index equals the count. - CVE-2026-59843: a peer can advertise a maximum packet size of 0. That value is rejected, on both the open and the confirmation. - CVE-2026-59844: SSH_FXP_READ lengths are capped at the OpenSSH limit. 0.10 has no process_read() malloc, so this is the same cap 0.11.5 applies when the request is parsed, and it is hardening here. - CVE-2026-59845: fork() for ProxyCommand was not checked, so a failure could leave pid -1 and the cleanup signalled the wrong process. - CVE-2026-59846: a username expanded through %r can carry shell metacharacters. Those names are rejected. DOMAIN\user, or a name with $ or a comma, stops working. Upstream 0.11.5 does the same. "alice" and "alice and bob" still work. The checker is internal and not exported. - CVE-2026-59847: the OpenSSL AES-GCM tag check treated a failure as success. The gcrypt build is unchanged. - CVE-2026-59848: a malicious SFTP server can answer with a request id the client never sent. 0.10 does not have sftp_common.c, so the same outstanding-id list was added in src/sftp.c. That list is one pointer added at the end of sftp_session_struct. DSA-6410-1 grew 0.11.5 the same way (0.11.2 did not have the field). The old fields stay at the same offsets, and libssh is what allocates the struct. - CVE-2026-59850: data packets that arrive after the channel is closed remotely are dropped. CVE-2026-15370 and CVE-2026-59849 are not affected. The SFTP server long-name builder and the certificate auto-login iterator both arrived in 0.11.0. I've marked both not-affected for bookworm in data/CVE/list. I built both the OpenSSL and gcrypt flavors. The unit suite passes the same on deb12u2 and deb12u3. Where 0.10 can actually hit the bug, I compared both versions: the empty hex buffer, a FIFO config, a bad username, the SFTP extension index, a short listing line, a failed ProxyCommand fork, a hostile Host pattern, and a flipped AES-GCM tag all misbehave on deb12u2 and are clean on deb12u3. I rebuilt all 23 reverse dependencies and ran the autopkgtests that could run, with no regressions. The SONAME is still libssh.so.4. The dynamic symbols match deb12u2 on both flavors, nothing added and nothing removed. Salsa fork: https://salsa.debian.org/tsimonq2/libssh/-/tree/bookworm Salsa pipeline: https://salsa.debian.org/tsimonq2/libssh/-/pipelines/1191977 Debusine work request: https://debusine.debian.net/debian/developers/work-request/1471814/ Feedback is welcome. If nothing showstopping comes up, I'll upload this and issue the DLA on Monday evening (October 19th, 2026). Best regards, Simon Quigley [email protected]
diff -Nru libssh-0.10.6/debian/changelog libssh-0.10.6/debian/changelog --- libssh-0.10.6/debian/changelog 2025-11-26 03:29:30.000000000 -0600 +++ libssh-0.10.6/debian/changelog 2026-10-15 18:24:51.000000000 -0500 @@ -1,3 +1,24 @@ +libssh (0.10.6-0+deb12u3) bookworm-security; urgency=medium + + * Non-maintainer upload by the LTS Team. + * CVE-2026-0964: reject invalid file names from an SCP server. + * CVE-2026-0965: do not read non-regular or oversized config files. + * CVE-2026-0966: fix a heap buffer underflow in ssh_get_hexa(). + * CVE-2026-0967: make match_pattern() iterative to avoid a ReDoS. + * CVE-2026-0968: stop sftp_parse_longname() reading past the string. + * CVE-2026-3731: fix an out of bounds read in the SFTP extension + accessors. + * CVE-2026-59843: fail when the peer sends a maximum packet size of 0. + * CVE-2026-59844: cap the length accepted in SSH_FXP_READ requests. + * CVE-2026-59845: check the fork() return code for ProxyCommand. + * CVE-2026-59846: reject usernames with shell metacharacters. This + brings in username validation from 0.11.0. + * CVE-2026-59847: check AES-GCM tag results correctly with OpenSSL. + * CVE-2026-59848: reject SFTP responses with unknown request IDs. + * CVE-2026-59850: drop DATA packets on remotely closed channels. + + -- Simon Quigley <[email protected]> Thu, 15 Oct 2026 18:24:51 -0500 + libssh (0.10.6-0+deb12u2) bookworm; urgency=medium [ Martin Pitt ] diff -Nru libssh-0.10.6/debian/patches/CVE-2026-0964.patch libssh-0.10.6/debian/patches/CVE-2026-0964.patch --- libssh-0.10.6/debian/patches/CVE-2026-0964.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-0964.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,43 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=a5e4b12090b0c939d85af4f29280e40c5b6600aa +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-13 + +Backported to 0.10.6. + +From a5e4b12090b0c939d85af4f29280e40c5b6600aa Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Mon, 22 Dec 2025 19:16:44 +0100 +Subject: [PATCH] CVE-2026-0964 scp: Reject invalid paths received through scp + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Andreas Schneider <[email protected]> +(cherry picked from commit daa80818f89347b4d80b0c5b80659f9a9e55e8cc) +--- + src/scp.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +--- a/src/scp.c ++++ b/src/scp.c +@@ -848,6 +848,22 @@ int ssh_scp_pull_request(ssh_scp scp) + size = strtoull(tmp, NULL, 10); + p++; + name = strdup(p); ++ /* Catch invalid name: ++ * - empty ones ++ * - containing any forward slash -- directory traversal handled ++ * differently ++ * - special names "." and ".." referring to the current and parent ++ * directories -- they are not expected either ++ */ ++ if (name == NULL || name[0] == '\0' || strchr(name, '/') || ++ strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { ++ ssh_set_error(scp->session, ++ SSH_FATAL, ++ "Received invalid filename: %s", ++ name == NULL ? "<NULL>" : name); ++ SAFE_FREE(name); ++ goto error; ++ } + SAFE_FREE(scp->request_name); + scp->request_name = name; + if (buffer[0] == 'C') { diff -Nru libssh-0.10.6/debian/patches/CVE-2026-0965.patch libssh-0.10.6/debian/patches/CVE-2026-0965.patch --- libssh-0.10.6/debian/patches/CVE-2026-0965.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-0965.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,268 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=bf390a042623e02abc8f421c4c5fadc0429a8a76 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-13 + +Backported to 0.10.6. The misc.h include and the test +registration were adjusted for this tree. + +From bf390a042623e02abc8f421c4c5fadc0429a8a76 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Thu, 11 Dec 2025 17:33:19 +0100 +Subject: [PATCH] CVE-2026-0965 config: Do not attempt to read non-regular and + too large configuration files + +Changes also the reading of known_hosts to use the new helper function + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Andreas Schneider <[email protected]> +(cherry picked from commit a5eb30dbfd8f3526b2d04bd9f0a3803b665f5798) +--- + include/libssh/misc.h | 3 ++ + include/libssh/priv.h | 3 ++ + src/bind_config.c | 4 +- + src/config.c | 8 ++-- + src/dh-gex.c | 4 +- + src/known_hosts.c | 2 +- + src/knownhosts.c | 2 +- + src/misc.c | 74 ++++++++++++++++++++++++++++++++ + tests/unittests/torture_config.c | 20 +++++++++ + 9 files changed, 110 insertions(+), 10 deletions(-) + +--- a/include/libssh/misc.h ++++ b/include/libssh/misc.h +@@ -21,6 +21,8 @@ + #ifndef MISC_H_ + #define MISC_H_ + ++#include <stdio.h> ++ + #ifdef __cplusplus + extern "C" { + #endif +@@ -106,6 +108,8 @@ char *ssh_strreplace(const char *src, co + + int ssh_check_hostname_syntax(const char *hostname); + ++FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); ++ + #ifdef __cplusplus + } + #endif +--- a/include/libssh/priv.h ++++ b/include/libssh/priv.h +@@ -438,6 +438,9 @@ bool is_ssh_initialized(void); + #define SSH_ERRNO_MSG_MAX 1024 + char *ssh_strerror(int err_num, char *buf, size_t buflen); + ++/** The default maximum file size for a configuration file */ ++#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024 ++ + #ifdef __cplusplus + } + #endif +--- a/src/bind_config.c ++++ b/src/bind_config.c +@@ -212,7 +212,7 @@ local_parse_file(ssh_bind bind, + return; + } + +- f = fopen(filename, "r"); ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", + filename); +@@ -636,7 +636,7 @@ int ssh_bind_config_parse_file(ssh_bind + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + +- f = fopen(filename, "r"); ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + return 0; + } +--- a/src/config.c ++++ b/src/config.c +@@ -215,10 +215,9 @@ local_parse_file(ssh_session session, + return; + } + +- f = fopen(filename, "r"); ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { +- SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", +- filename); ++ /* The underlying function logs the reasons */ + return; + } + +@@ -1205,8 +1204,9 @@ int ssh_config_parse_file(ssh_session se + int parsing, rv; + bool global = 0; + +- f = fopen(filename, "r"); ++ f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { ++ /* The underlying function logs the reasons */ + return 0; + } + +--- a/src/dh-gex.c ++++ b/src/dh-gex.c +@@ -520,9 +520,9 @@ static int ssh_retrieve_dhgroup(char *mo + } + + if (moduli_file != NULL) +- moduli = fopen(moduli_file, "r"); ++ moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); + else +- moduli = fopen(MODULI_FILE, "r"); ++ moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); + + if (moduli == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; +--- a/src/known_hosts.c ++++ b/src/known_hosts.c +@@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_kno + struct ssh_tokens_st *tokens; + + if (*file == NULL) { +- *file = fopen(filename,"r"); ++ *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (*file == NULL) { + return NULL; + } +--- a/src/knownhosts.c ++++ b/src/knownhosts.c +@@ -232,7 +232,7 @@ static int ssh_known_hosts_read_entries( + FILE *fp; + int rc; + +- fp = fopen(filename, "r"); ++ fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_WARN, "Failed to open the known_hosts file '%s': %s", +--- a/src/misc.c ++++ b/src/misc.c +@@ -37,6 +37,7 @@ + #endif /* _WIN32 */ + + #include <errno.h> ++#include <fcntl.h> + #include <limits.h> + #include <stdio.h> + #include <string.h> +@@ -2074,4 +2075,77 @@ int ssh_check_hostname_syntax(const char + return SSH_OK; + } + ++/** ++ * @internal ++ * ++ * @brief Safely open a file containing some configuration. ++ * ++ * Runs checks if the file can be used as some configuration file (is regular ++ * file and is not too large). If so, returns the opened file (for reading). ++ * Otherwise logs error and returns `NULL`. ++ * ++ * @param filename The path to the file to open. ++ * @param max_file_size Maximum file size that is accepted. ++ * ++ * @returns the opened file or `NULL` on error. ++ */ ++FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) ++{ ++ FILE *f = NULL; ++ struct stat sb; ++ char err_msg[SSH_ERRNO_MSG_MAX] = {0}; ++ int r, fd; ++ ++ /* open first to avoid TOCTOU */ ++ fd = open(filename, O_RDONLY); ++ if (fd == -1) { ++ SSH_LOG(SSH_LOG_RARE, ++ "Failed to open a file %s for reading: %s", ++ filename, ++ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ++ return NULL; ++ } ++ ++ /* Check the file is sensible for a configuration file */ ++ r = fstat(fd, &sb); ++ if (r != 0) { ++ SSH_LOG(SSH_LOG_RARE, ++ "Failed to stat %s: %s", ++ filename, ++ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ++ close(fd); ++ return NULL; ++ } ++ if ((sb.st_mode & S_IFMT) != S_IFREG) { ++ SSH_LOG(SSH_LOG_RARE, ++ "The file %s is not a regular file: skipping", ++ filename); ++ close(fd); ++ return NULL; ++ } ++ ++ if ((size_t)sb.st_size > max_file_size) { ++ SSH_LOG(SSH_LOG_RARE, ++ "The file %s is too large (%jd MB > %zu MB): skipping", ++ filename, ++ (intmax_t)sb.st_size / 1024 / 1024, ++ max_file_size / 1024 / 1024); ++ close(fd); ++ return NULL; ++ } ++ ++ f = fdopen(fd, "r"); ++ if (f == NULL) { ++ SSH_LOG(SSH_LOG_RARE, ++ "Failed to open a file %s for reading: %s", ++ filename, ++ ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); ++ close(fd); ++ return NULL; ++ } ++ ++ /* the flcose() will close also the underlying fd */ ++ return f; ++} ++ + /** @} */ +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -1897,6 +1897,23 @@ static void torture_config_parse_uri(voi + SAFE_FREE(hostname); + } + ++/* Invalid configuration files ++ */ ++static void torture_config_invalid(void **state) ++{ ++ ssh_session session = *state; ++ ++ ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); ++ ++ /* non-regular file -- ignored (or missing on non-unix) so OK */ ++ _parse_config(session, "/dev/random", NULL, SSH_OK); ++ ++#ifndef _WIN32 ++ /* huge file -- ignored (or missing on non-unix) so OK */ ++ _parse_config(session, "/proc/kcore", NULL, SSH_OK); ++#endif ++} ++ + int torture_run_tests(void) + { + int rc; +@@ -1971,6 +1988,9 @@ int torture_run_tests(void) + setup_no_sshdir, teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, teardown), ++ cmocka_unit_test_setup_teardown(torture_config_invalid, ++ setup, ++ teardown), + }; + + diff -Nru libssh-0.10.6/debian/patches/CVE-2026-0966.patch libssh-0.10.6/debian/patches/CVE-2026-0966.patch --- libssh-0.10.6/debian/patches/CVE-2026-0966.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-0966.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,81 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=6ba5ff1b7b1547a59f750fbc06b89737b7456117 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-13 + +Backported to 0.10.6. The documentation hunk is left out. +The test coverage is upstream b156391833c6. + +From 6ba5ff1b7b1547a59f750fbc06b89737b7456117 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Thu, 8 Jan 2026 12:09:50 +0100 +Subject: [PATCH] CVE-2026-0966 misc: Avoid heap buffer underflow in + ssh_get_hexa +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit 417a095e6749a1f3635e02332061edad3c6a3401) +--- + src/misc.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/misc.c ++++ b/src/misc.c +@@ -452,7 +452,7 @@ char *ssh_get_hexa(const unsigned char * + size_t i; + size_t hlen = len * 3; + +- if (len > (UINT_MAX - 1) / 3) { ++ if (what == NULL || len < 1 || len > (UINT_MAX - 1) / 3) { + return NULL; + } + +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -877,6 +877,36 @@ static void torture_ssh_is_ipaddr(void * + assert_int_equal(rc, 0); + } + ++static void torture_ssh_get_hexa(void **state) ++{ ++ const unsigned char *bin = NULL; ++ char *hex = NULL; ++ ++ (void)state; ++ ++ /* Null pointer should not crash */ ++ bin = NULL; ++ hex = ssh_get_hexa(bin, 0); ++ assert_null(hex); ++ ++ /* Null pointer should not crash regardless the length */ ++ bin = NULL; ++ hex = ssh_get_hexa(bin, 99); ++ assert_null(hex); ++ ++ /* Zero length input is not much useful. Just expect NULL too */ ++ bin = (const unsigned char *)""; ++ hex = ssh_get_hexa(bin, 0); ++ assert_null(hex); ++ ++ /* Valid inputs */ ++ bin = (const unsigned char *)"\x00\xFF"; ++ hex = ssh_get_hexa(bin, 2); ++ assert_non_null(hex); ++ assert_string_equal(hex, "00:ff"); ++ ssh_string_free_char(hex); ++} ++ + int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { +@@ -903,6 +933,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_strerror), + cmocka_unit_test(torture_ssh_check_hostname_syntax), + cmocka_unit_test(torture_ssh_is_ipaddr), ++ cmocka_unit_test(torture_ssh_get_hexa), + }; + + ssh_init(); diff -Nru libssh-0.10.6/debian/patches/CVE-2026-0967.patch libssh-0.10.6/debian/patches/CVE-2026-0967.patch --- libssh-0.10.6/debian/patches/CVE-2026-0967.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-0967.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,356 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=6d74aa6138895b3662bade9bd578338b0c4f8a15 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-13 + +Backported to 0.10.6. + +From 6d74aa6138895b3662bade9bd578338b0c4f8a15 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Wed, 17 Dec 2025 18:48:34 +0100 +Subject: [PATCH] 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. + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit a411de5ce806e3ea24d088774b2f7584d6590b5f) +--- + src/match.c | 111 +++++++++++++---------------- + tests/unittests/torture_config.c | 116 +++++++++++++++++++++++-------- + 2 files changed, 135 insertions(+), 92 deletions(-) + +--- a/src/match.c ++++ b/src/match.c +@@ -43,85 +43,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'); + } + + /* +@@ -172,7 +157,7 @@ int match_pattern_list(const char *strin + 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 { +--- a/tests/unittests/torture_config.c ++++ b/tests/unittests/torture_config.c +@@ -1656,80 +1656,138 @@ static void torture_config_match_pattern + (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 diff -Nru libssh-0.10.6/debian/patches/CVE-2026-0968.patch libssh-0.10.6/debian/patches/CVE-2026-0968.patch --- libssh-0.10.6/debian/patches/CVE-2026-0968.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-0968.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,59 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=796d85f786dff62bd4bcc4408d9b7bbc855841e9 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-13 + +Backported to 0.10.6's sftp_parse_longname() in src/sftp.c. +The upstream test needs sftp_common.c, which 0.10 does not have, +and is left out. + +From 796d85f786dff62bd4bcc4408d9b7bbc855841e9 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Mon, 22 Dec 2025 20:59:11 +0100 +Subject: [PATCH] CVE-2026-0968: sftp: Sanitize input handling in + sftp_parse_longname() + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Andreas Schneider <[email protected]> +(cherry picked from commit 20856f44c146468c830da61dcbbbaa8ce71e390b) +--- + src/sftp_common.c | 16 +++++++++++++--- + 1 file changed, 13 insertions(+), 3 deletions(-) + +--- a/src/sftp.c ++++ b/src/sftp.c +@@ -1289,13 +1289,18 @@ static char *sftp_parse_longname(const c + const char *p, *q; + size_t len, field = 0; + ++ if (longname == NULL || longname_field < SFTP_LONGNAME_PERM || ++ longname_field > SFTP_LONGNAME_NAME) { ++ return NULL; ++ } ++ + p = longname; + /* Find the beginning of the field which is specified by sftp_longname_field_e. */ +- while(field != longname_field) { ++ while(*p != '\0' && field != longname_field) { + if(isspace(*p)) { + field++; + p++; +- while(*p && isspace(*p)) { ++ while(*p != '\0' && isspace(*p)) { + p++; + } + } else { +@@ -1303,8 +1308,13 @@ static char *sftp_parse_longname(const c + } + } + ++ /* If we reached NULL before we got our field fail */ ++ if (field != longname_field) { ++ return NULL; ++ } ++ + q = p; +- while (! isspace(*q)) { ++ while (*q != '\0' && !isspace(*q)) { + q++; + } + diff -Nru libssh-0.10.6/debian/patches/CVE-2026-3731.patch libssh-0.10.6/debian/patches/CVE-2026-3731.patch --- libssh-0.10.6/debian/patches/CVE-2026-3731.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-3731.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,42 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=f80670a7aba86cbb442c9b115c9eaf4ca04601b8 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-14 + +Backported to 0.10.6. The client reproducer needs a running +sshd and is left out. + +From f80670a7aba86cbb442c9b115c9eaf4ca04601b8 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Thu, 11 Dec 2025 13:22:44 +0100 +Subject: [PATCH] sftp: Fix out-of-bound read from sftp extensions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit 855a0853ad3abd4a6cd85ce06fce6d8d4c7a0b60) +--- + src/sftp.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/src/sftp.c ++++ b/src/sftp.c +@@ -768,7 +768,7 @@ const char *sftp_extensions_get_name(sft + return NULL; + } + +- if (idx > sftp->ext->count) { ++ if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } +@@ -784,7 +784,7 @@ const char *sftp_extensions_get_data(sft + return NULL; + } + +- if (idx > sftp->ext->count) { ++ if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59843.patch libssh-0.10.6/debian/patches/CVE-2026-59843.patch --- libssh-0.10.6/debian/patches/CVE-2026-59843.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59843.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,81 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=687ef1c44b646b9db0b1c6e8f987edb7c9e4d919 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-14 + +Backported to 0.10.6. The confirmation hunk is placed by hand +because the 0.10 log line differs. + +From 687ef1c44b646b9db0b1c6e8f987edb7c9e4d919 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= <[email protected]> +Date: Fri, 6 Mar 2026 13:58:30 +0100 +Subject: [PATCH] CVE-2026-59843 channels: Fail when receiving max packet size + 0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Do this both for SSH2_MSG_CHANNEL_OPEN and for +SSH2_MSG_CHANNEL_OPEN_CONFIRMATION. Using the +max packet size 0 would lead to an infinite loop +in channel_write_common. + +Originally reported by Rinku Das on on 23th February. +Independently reported by Yi Lin on 26th February and +Haruto Kimura on 22nd March. + +We do not consider this as a security issue as connecting +to untrusted servers on the internet brings much worse +security consequences than hanging your clinet. + +Signed-off-by: Pavol Žáčik <[email protected]> +Reviewed-by: Jakub Jelen <[email protected]> +(cherry picked from commit 44b186fa17aff497dae420c59c003222e438103c) +--- + src/channels.c | 7 +++++++ + src/messages.c | 19 +++++++++++++++---- + 2 files changed, 22 insertions(+), 4 deletions(-) + +--- a/src/channels.c ++++ b/src/channels.c +@@ -192,6 +192,13 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_o + if (rc != SSH_OK) + goto error; + ++ if (channel->remote_maxpacket == 0) { ++ SSH_LOG(SSH_LOG_RARE, ++ "Invalid maximum packet size 0 in " ++ "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); ++ goto error; ++ } ++ + SSH_LOG(SSH_LOG_PROTOCOL, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %d:%d", + channel->local_channel, +--- a/src/messages.c ++++ b/src/messages.c +@@ -1160,10 +1160,21 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_o + SSH_LOG(SSH_LOG_PACKET, + "Clients wants to open a %s channel", type_c); + +- ssh_buffer_unpack(packet,"ddd", +- &msg->channel_request_open.sender, +- &msg->channel_request_open.window, +- &msg->channel_request_open.packet_size); ++ rc = ssh_buffer_unpack(packet, ++ "ddd", ++ &msg->channel_request_open.sender, ++ &msg->channel_request_open.window, ++ &msg->channel_request_open.packet_size); ++ if (rc != SSH_OK){ ++ goto error; ++ } ++ ++ if (msg->channel_request_open.packet_size == 0) { ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Invalid maximum packet size 0 in SSH2_MSG_CHANNEL_OPEN"); ++ goto error; ++ } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED){ + ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59844.patch libssh-0.10.6/debian/patches/CVE-2026-59844.patch --- libssh-0.10.6/debian/patches/CVE-2026-59844.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59844.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,47 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=e31f06e5380be4e714d5ad6965981fbf30738da9 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-14 + +Backported to 0.10.6. There is no built-in read handler, so the +cap is applied when the request is parsed. + +From e31f06e5380be4e714d5ad6965981fbf30738da9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= <[email protected]> +Date: Fri, 6 Mar 2026 18:05:29 +0100 +Subject: [PATCH] CVE-2026-59844 sftpserver: cap accepted values of len in + SSH_FXP_READ +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The client-provided length is directly used in +a malloc in process_read(), so not restricting it +leads to allocations bounded only by UINT32_MAX. + +The new cap is the same as the one currently used +by OpenSSH. + +Signed-off-by: Pavol Žáčik <[email protected]> +Reviewed-by: Jakub Jelen <[email protected]> +(cherry picked from commit 6dba2e06f0713c04ad5eca7d4315d0104be7e627) +--- + src/sftpserver.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/src/sftpserver.c ++++ b/src/sftpserver.c +@@ -105,6 +105,14 @@ sftp_client_message sftp_get_client_mess + sftp_client_message_free(msg); + return NULL; + } ++ if (msg->len > MAX_PACKET_LEN - 1024) { ++ ssh_set_error(session, ++ SSH_FATAL, ++ "Too large SSH_FXP_READ length: %" PRIu32, ++ msg->len); ++ sftp_client_message_free(msg); ++ return NULL; ++ } + break; + case SSH_FXP_WRITE: + rc = ssh_buffer_unpack(payload, diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59845.patch libssh-0.10.6/debian/patches/CVE-2026-59845.patch --- libssh-0.10.6/debian/patches/CVE-2026-59845.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59845.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,66 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=1afacdb430de7441c81d11a8c0ef8a36de78e960 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-14 + +Backported to 0.10.6. + +From 1afacdb430de7441c81d11a8c0ef8a36de78e960 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Thu, 26 Mar 2026 16:32:24 +0100 +Subject: [PATCH] CVE-2026-59845 socket: Properly check fork() return code +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +During execution of proxy command, when fork() fails, its return value +is stored in pid and when the parent process attempts to kill it, +it sends the kill signal to all processes the calling application has +access to (except for init). + +This caused nard to debug issues when the system under the load was hitting +fork failures, which resulted in killing of all the system processes +(of given user). + +Reported and first patch iteration provided by: Halil Oktay (oblivionsage). + +This code missing fork return value check is in libssh since 2010 +(f31a14b7932ef4cc165ddd8f1f1a5b23eb21beb3), but this issue is exploitable only +since libssh 0.9.0 as previously there was no implementation of killing +ProxyCommand children. + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit 92b6fb9c5e2d1606e8f809fd884ab6dd4d3b7d45) +--- + src/socket.c | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +--- a/src/socket.c ++++ b/src/socket.c +@@ -964,6 +964,7 @@ ssh_execute_command(const char *command, + int + ssh_socket_connect_proxycommand(ssh_socket s, const char *command) + { ++ char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socket_t pair[2]; + ssh_poll_handle h = NULL; + int pid; +@@ -982,7 +983,17 @@ ssh_socket_connect_proxycommand(ssh_sock + pid = fork(); + if (pid == 0) { + ssh_execute_command(command, pair[0], pair[0]); +- /* Does not return */ ++ /* child: Does not return */ ++ } ++ /* parent */ ++ if (pid == -1) { ++ close(pair[0]); ++ close(pair[1]); ++ ssh_set_error(s->session, ++ SSH_FATAL, ++ "fork failed: %s", ++ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); ++ return SSH_ERROR; + } + s->proxy_pid = pid; + close(pair[0]); diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59846.patch libssh-0.10.6/debian/patches/CVE-2026-59846.patch --- libssh-0.10.6/debian/patches/CVE-2026-59846.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59846.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,201 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=56ce3c193eb06af5bf3b07ec0b4c7308b5c72130 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-14 + +Backported to 0.10.6. The syntax helper comes from 0.11.0 +(ebcd6eee3cd0 and d97a5930c906). This commit is what calls it. + +From 56ce3c193eb06af5bf3b07ec0b4c7308b5c72130 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Thu, 2 Apr 2026 15:39:25 +0200 +Subject: [PATCH] CVE-2026-59846 Block shell metacharacters from usernames +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +When an attacker could sneak the dollar sign or backslash into the username +expanded for example in proxy command, it can result in printing environment +variables that might contain secrets. + +This is a fixup of CVE-2023-6004 which fixed this for hostnames, but these +two metacharacters were left out from the username filter. + +This keeps the list in one place to simplify maintenance. + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit 6309df220e3431deb41946f892f4bb5af8b59dba) +--- + include/libssh/priv.h | 2 ++ + src/misc.c | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +--- a/include/libssh/misc.h ++++ b/include/libssh/misc.h +@@ -107,6 +107,7 @@ int ssh_tmpname(char *name); + char *ssh_strreplace(const char *src, const char *pattern, const char *repl); + + int ssh_check_hostname_syntax(const char *hostname); ++int ssh_check_username_syntax(const char *username); + + FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); + +--- a/include/libssh/priv.h ++++ b/include/libssh/priv.h +@@ -227,6 +227,8 @@ int ssh_gettimeofday(struct timeval *__p + # define LIBSSH_MEM_PROTECTION + #endif + ++#define SSH_DANGEROUS_SHELL_CHARS "'`\";&<>|(){}$\\," ++ + /* forward declarations */ + struct ssh_common_struct; + struct ssh_kex_struct; +--- a/src/config_parser.c ++++ b/src/config_parser.c +@@ -194,6 +194,10 @@ int ssh_config_parse_uri(const char *tok + if (*username == NULL) { + goto error; + } ++ rc = ssh_check_username_syntax(*username); ++ if (rc != SSH_OK) { ++ goto error; ++ } + } + tok = endp + 1; + /* If there is second @ character, this does not look like our URI */ +--- a/src/misc.c ++++ b/src/misc.c +@@ -183,6 +183,7 @@ char *ssh_get_local_username(void) + { + DWORD size = 0; + char *user; ++ int rc; + + /* get the size */ + GetUserName(NULL, &size); +@@ -193,7 +194,10 @@ char *ssh_get_local_username(void) + } + + if (GetUserName(user, &size)) { +- return user; ++ rc = ssh_check_username_syntax(user); ++ if (rc == SSH_OK) { ++ return user; ++ } + } + + return NULL; +@@ -337,8 +341,10 @@ char *ssh_get_local_username(void) + } + + name = strdup(pwd.pw_name); ++ rc = ssh_check_username_syntax(name); + +- if (name == NULL) { ++ if (rc != SSH_OK) { ++ free(name); + return NULL; + } + +@@ -2148,4 +2154,36 @@ FILE *ssh_strict_fopen(const char *filen + return f; + } + ++/** ++ * @brief Checks syntax of a username ++ * ++ * This check disallows metacharacters in the username ++ * ++ * @param username The username to be checked, has to be null terminated ++ * ++ * @return SSH_OK if the username passes syntax check ++ * SSH_ERROR otherwise or if username is NULL or empty string ++ */ ++int ssh_check_username_syntax(const char *username) ++{ ++ size_t username_len; ++ ++ if (username == NULL || *username == '-') { ++ return SSH_ERROR; ++ } ++ ++ username_len = strlen(username); ++ if (username_len == 0 || username[username_len - 1] == '\\' || ++ strpbrk(username, SSH_DANGEROUS_SHELL_CHARS) != NULL) { ++ return SSH_ERROR; ++ } ++ for (size_t i = 0; i < username_len; i++) { ++ if (isspace(username[i]) != 0 && username[i + 1] == '-') { ++ return SSH_ERROR; ++ } ++ } ++ ++ return SSH_OK; ++} ++ + /** @} */ +--- a/src/options.c ++++ b/src/options.c +@@ -620,6 +620,11 @@ int ssh_options_set(ssh_session session, + ssh_set_error_oom(session); + return -1; + } ++ rc = ssh_check_username_syntax(session->opts.username); ++ if (rc != SSH_OK) { ++ ssh_set_error_invalid(session); ++ return -1; ++ } + } + break; + case SSH_OPTIONS_SSH_DIR: +--- a/tests/unittests/torture_misc.c ++++ b/tests/unittests/torture_misc.c +@@ -839,6 +839,39 @@ static void torture_ssh_check_hostname_s + assert_int_equal(rc, SSH_ERROR); + } + ++static void torture_ssh_check_username_syntax(void **state) { ++ int rc; ++ (void)state; ++ ++ rc = ssh_check_username_syntax("username"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_username_syntax("Alice"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_username_syntax("Alice and Bob"); ++ assert_int_equal(rc, SSH_OK); ++ rc = ssh_check_username_syntax("n4me?"); ++ assert_int_equal(rc, SSH_OK); ++ ++ rc = ssh_check_username_syntax("alice&bob"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("backslash\\"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("&var|()us\"<ha`r{}'"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax(" -"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("me and -"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("los -santos"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax("- who?"); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax(NULL); ++ assert_int_equal(rc, SSH_ERROR); ++ rc = ssh_check_username_syntax(""); ++ assert_int_equal(rc, SSH_ERROR); ++} ++ + static void torture_ssh_is_ipaddr(void **state) { + int rc; + char *interf = malloc(64); +@@ -932,6 +965,7 @@ int torture_run_tests(void) { + cmocka_unit_test(torture_ssh_strreplace), + cmocka_unit_test(torture_ssh_strerror), + cmocka_unit_test(torture_ssh_check_hostname_syntax), ++ cmocka_unit_test(torture_ssh_check_username_syntax), + cmocka_unit_test(torture_ssh_is_ipaddr), + cmocka_unit_test(torture_ssh_get_hexa), + }; diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59847.patch libssh-0.10.6/debian/patches/CVE-2026-59847.patch --- libssh-0.10.6/debian/patches/CVE-2026-59847.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59847.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,48 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=c7080ffcc139c3c1833c34ccaa8683efc562e5f9 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-15 + +Backported to 0.10.6, OpenSSL only. The tag check on decrypt is +upstream 06c6082df966. The gcrypt backend is unchanged. + +From c7080ffcc139c3c1833c34ccaa8683efc562e5f9 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Fri, 15 May 2026 17:01:21 +0200 +Subject: [PATCH] CVE-2026-59847 libcrypto: Fix tag verification of AES-GCM + ciphers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +EVP_DecryptFinal() returns 0 errors, which was wrongly checked since +its introduction. + +Reported by Ben Smyth [email protected] + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit 6d6cb6cb4d1684bb3f2b9fd23f3635e79d1e5495) +--- + src/libcrypto.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/libcrypto.c ++++ b/src/libcrypto.c +@@ -586,7 +586,7 @@ evp_cipher_aead_encrypt(struct ssh_ciphe + rc = EVP_EncryptFinal(cipher->ctx, + NULL, + &tmplen); +- if (rc < 0) { ++ if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, "EVP_EncryptFinal failed: Failed to create a tag"); + return; + } +@@ -674,7 +674,7 @@ evp_cipher_aead_decrypt(struct ssh_ciphe + rc = EVP_DecryptFinal(cipher->ctx, + NULL, + &outlen); +- if (rc < 0) { ++ if (rc != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_WARNING, "EVP_DecryptFinal failed: Failed authentication"); + return SSH_ERROR; + } diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59848.patch libssh-0.10.6/debian/patches/CVE-2026-59848.patch --- libssh-0.10.6/debian/patches/CVE-2026-59848.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59848.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,465 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=a30a51003205744c10ba4439306f555206ae8497 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-15 + +Backported to 0.10.6. The queue initializer is upstream +5309aefd99e1, folded into the same patch. + +From a30a51003205744c10ba4439306f555206ae8497 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= <[email protected]> +Date: Mon, 1 Jun 2026 16:33:03 +0200 +Subject: [PATCH] CVE-2026-59848 sftp: handle responses with unknown request + IDs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds a new field to sftp_session_struct, +containing a list of outstanding request IDs. +An ID is added to the list when a request +is constructed and removed when the corresponding +request is received. If a client receives a response +with an unknown request ID, it reports an error. + +Storing responses with unknown request IDs in +the response queue could be abused by a malicious +SFTP server which could deplete client memory +this way. + +Signed-off-by: Pavol Žáčik <[email protected]> +Reviewed-by: Jakub Jelen <[email protected]> +(cherry picked from commit 26147eb4767937c797f97ff3b1b1663384232417) +--- + include/libssh/sftp.h | 1 + + include/libssh/sftp_priv.h | 20 +-- + src/sftp.c | 190 +++++++++++++++++++------ + src/sftp_aio.c | 13 +- + src/sftp_common.c | 56 ++++++++ + tests/client/CMakeLists.txt | 1 + + tests/client/torture_sftp_request_id.c | 183 ++++++++++++++++++++++++ + 7 files changed, 407 insertions(+), 57 deletions(-) + create mode 100644 tests/client/torture_sftp_request_id.c + +--- a/include/libssh/sftp.h ++++ b/include/libssh/sftp.h +@@ -90,6 +90,8 @@ struct sftp_session_struct { + void **handles; + sftp_ext ext; + sftp_packet read_packet; ++ /* Appended for CVE-2026-59848; allocated by libssh only, keep last */ ++ struct ssh_list *outstanding_ids; + }; + + struct sftp_packet_struct { +--- a/src/sftp.c ++++ b/src/sftp.c +@@ -109,6 +109,22 @@ static void sftp_ext_free(sftp_ext ext) + SAFE_FREE(ext); + } + ++static void sftp_outstanding_ids_free(sftp_session sftp) ++{ ++ struct ssh_iterator *id_it = NULL; ++ ++ if (sftp->outstanding_ids == NULL) { ++ return; ++ } ++ ++ id_it = ssh_list_get_iterator(sftp->outstanding_ids); ++ for (; id_it != NULL; id_it = id_it->next) { ++ free((uint32_t *)id_it->data); ++ } ++ ssh_list_free(sftp->outstanding_ids); ++ sftp->outstanding_ids = NULL; ++} ++ + sftp_session sftp_new(ssh_session session) + { + sftp_session sftp; +@@ -149,6 +165,12 @@ sftp_session sftp_new(ssh_session sessio + goto error; + } + ++ sftp->outstanding_ids = ssh_list_new(); ++ if (sftp->outstanding_ids == NULL) { ++ ssh_set_error_oom(session); ++ goto error; ++ } ++ + if (ssh_channel_open_session(sftp->channel)) { + goto error; + } +@@ -162,6 +184,7 @@ error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } ++ sftp_outstanding_ids_free(sftp); + if (sftp->channel != NULL) { + ssh_channel_free(sftp->channel); + } +@@ -211,12 +234,19 @@ sftp_new_channel(ssh_session session, ss + sftp->session = session; + sftp->channel = channel; + ++ sftp->outstanding_ids = ssh_list_new(); ++ if (sftp->outstanding_ids == NULL) { ++ ssh_set_error_oom(session); ++ goto error; ++ } ++ + return sftp; + + error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } ++ sftp_outstanding_ids_free(sftp); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); +@@ -383,6 +413,7 @@ void sftp_free(sftp_session sftp) + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); ++ sftp_outstanding_ids_free(sftp); + + SAFE_FREE(sftp); + } +@@ -571,6 +602,8 @@ static sftp_message sftp_get_message(sft + { + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; ++ struct ssh_iterator *id_it = NULL; ++ bool id_found = false; + int rc; + + switch(packet->type) { +@@ -618,6 +651,28 @@ static sftp_message sftp_get_message(sft + msg->id, + msg->packet_type); + ++ /* Validate that this ID is in our outstanding requests list */ ++ id_it = ssh_list_get_iterator(sftp->outstanding_ids); ++ for (; id_it != NULL; id_it = id_it->next) { ++ uint32_t *stored_id = (uint32_t *)id_it->data; ++ if (*stored_id == msg->id) { ++ id_found = true; ++ ssh_list_remove(sftp->outstanding_ids, id_it); ++ free(stored_id); ++ break; ++ } ++ } ++ ++ if (!id_found) { ++ ssh_set_error(packet->sftp->session, ++ SSH_FATAL, ++ "Unknown request ID %" PRIu32, ++ msg->id); ++ sftp_message_free(msg); ++ sftp_set_error(packet->sftp, SSH_FX_FAILURE); ++ return NULL; ++ } ++ + return msg; + } + +@@ -904,11 +959,35 @@ static sftp_message sftp_dequeue(sftp_se + + /* + * Assigns a new SFTP ID for new requests and assures there is no collision +- * between them. +- * Returns a new ID ready to use in a request ++ * between them. The ID is recorded as outstanding until its response is ++ * received. ++ * Returns SSH_OK with the new ID stored in *id_out, SSH_ERROR on failure ++ * with the sftp and ssh errors set. + */ +-static inline uint32_t sftp_get_new_id(sftp_session session) { +- return ++session->id_counter; ++static int sftp_get_new_id(sftp_session sftp, uint32_t *id_out) ++{ ++ uint32_t *id = NULL; ++ int rc; ++ ++ id = malloc(sizeof(uint32_t)); ++ if (id == NULL) { ++ ssh_set_error_oom(sftp->session); ++ sftp_set_error(sftp, SSH_FX_FAILURE); ++ return SSH_ERROR; ++ } ++ ++ *id = ++sftp->id_counter; ++ rc = ssh_list_append(sftp->outstanding_ids, id); ++ if (rc != SSH_OK) { ++ free(id); ++ ssh_set_error_oom(sftp->session); ++ sftp_set_error(sftp, SSH_FX_FAILURE); ++ return SSH_ERROR; ++ } ++ ++ *id_out = *id; ++ ++ return SSH_OK; + } + + static sftp_status_message parse_status_msg(sftp_message msg){ +@@ -1029,7 +1108,11 @@ sftp_dir sftp_opendir(sftp_session sftp, + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(payload); ++ return NULL; ++ } + + rc = ssh_buffer_pack(payload, + "ds", +@@ -1571,7 +1654,11 @@ sftp_attributes sftp_readdir(sftp_sessio + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(payload); ++ return NULL; ++ } + + rc = ssh_buffer_pack(payload, + "dS", +@@ -1704,7 +1791,11 @@ static int sftp_handle_close(sftp_sessio + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dS", +@@ -1835,7 +1926,11 @@ sftp_file sftp_open(sftp_session sftp, + sftp_flags |= SSH_FXF_APPEND; + } + SSH_LOG(SSH_LOG_PACKET,"Opening file %s with sftp flags %x",file,sftp_flags); +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "dsd", +@@ -1946,7 +2041,11 @@ ssize_t sftp_read(sftp_file handle, void + return -1; + } + +- id = sftp_get_new_id(handle->sftp); ++ rc = sftp_get_new_id(handle->sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dSqd", +@@ -2047,7 +2146,11 @@ int sftp_async_read_begin(sftp_file file + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dSqd", +@@ -2173,7 +2276,11 @@ ssize_t sftp_write(sftp_file file, const + return -1; + } + +- id = sftp_get_new_id(file->sftp); ++ rc = sftp_get_new_id(file->sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dSqdP", +@@ -2291,7 +2398,11 @@ int sftp_unlink(sftp_session sftp, const + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -2366,7 +2477,11 @@ int sftp_rmdir(sftp_session sftp, const + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -2443,7 +2558,11 @@ int sftp_mkdir(sftp_session sftp, const + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -2538,7 +2657,11 @@ int sftp_rename(sftp_session sftp, const + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dss", +@@ -2622,7 +2745,11 @@ int sftp_setstat(sftp_session sftp, cons + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -2752,7 +2879,11 @@ int sftp_symlink(sftp_session sftp, cons + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + /* TODO check for version number if they ever fix it. */ + if (ssh_get_openssh_version(sftp->session)) { +@@ -2850,7 +2981,11 @@ char *sftp_readlink(sftp_session sftp, c + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -2976,7 +3111,11 @@ sftp_statvfs_t sftp_statvfs(sftp_session + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "dss", +@@ -3051,7 +3190,11 @@ int sftp_fsync(sftp_file file) + return -1; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return -1; ++ } + + rc = ssh_buffer_pack(buffer, + "dsS", +@@ -3151,7 +3294,11 @@ sftp_statvfs_t sftp_fstatvfs(sftp_file f + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "dsS", +@@ -3238,7 +3385,11 @@ char *sftp_canonicalize_path(sftp_sessio + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -3329,7 +3480,11 @@ static sftp_attributes sftp_xstat(sftp_s + return NULL; + } + +- id = sftp_get_new_id(sftp); ++ rc = sftp_get_new_id(sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "ds", +@@ -3407,7 +3562,11 @@ sftp_attributes sftp_fstat(sftp_file fil + return NULL; + } + +- id = sftp_get_new_id(file->sftp); ++ rc = sftp_get_new_id(file->sftp, &id); ++ if (rc != SSH_OK) { ++ SSH_BUFFER_FREE(buffer); ++ return NULL; ++ } + + rc = ssh_buffer_pack(buffer, + "dS", diff -Nru libssh-0.10.6/debian/patches/CVE-2026-59850.patch libssh-0.10.6/debian/patches/CVE-2026-59850.patch --- libssh-0.10.6/debian/patches/CVE-2026-59850.patch 1969-12-31 18:00:00.000000000 -0600 +++ libssh-0.10.6/debian/patches/CVE-2026-59850.patch 2026-10-15 18:24:51.000000000 -0500 @@ -0,0 +1,39 @@ +Origin: https://git.libssh.org/projects/libssh.git/commit/?id=6edfb52b3b364577d2db0334c0514a977efceed2 +Reviewed-by: Simon Quigley <[email protected]> +Last-Update: 2026-10-15 + +Backported to 0.10.6. Placed by hand because the 0.10 channel +callback is styled differently. + +From 6edfb52b3b364577d2db0334c0514a977efceed2 Mon Sep 17 00:00:00 2001 +From: Jakub Jelen <[email protected]> +Date: Wed, 1 Jul 2026 16:43:08 +0200 +Subject: [PATCH] CVE-2026-59850 channels: Avoid processing DATA packets on + closed channels +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Jakub Jelen <[email protected]> +Reviewed-by: Pavol Žáčik <[email protected]> +(cherry picked from commit a8a3fa352bb5213e08a35e4494c6e44360e2e38a) +--- + src/channels.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/src/channels.c ++++ b/src/channels.c +@@ -575,6 +575,13 @@ SSH_PACKET_CALLBACK(channel_rcv_data){ + channel->local_window, + channel->remote_window); + ++ if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { ++ SSH_LOG(SSH_LOG_WARNING, "Received data on (remotely) closed channel"); ++ ssh_set_error(session, SSH_FATAL, "Received data on (remotely) closed channel"); ++ SSH_STRING_FREE(str); ++ return SSH_PACKET_USED; ++ } ++ + /* What shall we do in this case? Let's accept it anyway */ + if (len > channel->local_window) { + SSH_LOG(SSH_LOG_RARE, diff -Nru libssh-0.10.6/debian/patches/series libssh-0.10.6/debian/patches/series --- libssh-0.10.6/debian/patches/series 2025-11-26 03:29:30.000000000 -0600 +++ libssh-0.10.6/debian/patches/series 2026-10-15 18:24:51.000000000 -0500 @@ -14,3 +14,16 @@ CVE-2025-8277-2.patch CVE-2025-8277-3.patch CVE-2025-8277-4.patch +CVE-2026-0964.patch +CVE-2026-0965.patch +CVE-2026-0966.patch +CVE-2026-0967.patch +CVE-2026-59843.patch +CVE-2026-59845.patch +CVE-2026-59847.patch +CVE-2026-59850.patch +CVE-2026-59846.patch +CVE-2026-0968.patch +CVE-2026-3731.patch +CVE-2026-59844.patch +CVE-2026-59848.patch
OpenPGP_signature.asc
Description: OpenPGP digital signature
