Backport low/medium CVE patches

Resolves: RHEL-150661
Resolves: CVE-2025-4877
Resolves: CVE-2025-4878
Resolves: CVE-2025-5351
Resolves: CVE-2025-8114
Resolves: CVE-2025-8277
Resolves: CVE-2026-0964
Resolves: CVE-2026-0965
Resolves: CVE-2026-0966
Resolves: CVE-2026-0967
Resolves: CVE-2026-0968
This commit is contained in:
Pavol Žáčik 2026-02-10 13:40:02 +01:00
parent b903d9f035
commit 048ffad847
No known key found for this signature in database
GPG Key ID: 4EE16C6E333F70A8
11 changed files with 3920 additions and 1 deletions

54
CVE-2025-4877.patch Normal file
View File

@ -0,0 +1,54 @@
From c9d785835f5b299c31b22be0506a658205c169bf Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Tue, 15 Apr 2025 11:41:24 +0200
Subject: [PATCH] CVE-2025-4877 base64: Prevent integer overflow and potential
OOB
Set maximum input to 256MB to have safe margin to the 1GB trigger point
for 32b arch.
The OOB should not be reachable by any internal code paths as most of
the buffers and strings we use as input for this operation already have
similar limit and none really allows this much of data.
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit 00f09acbec55962839fc7837ef14c56fb8fbaf72)
---
src/base64.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/base64.c b/src/base64.c
index 4148f49c..f42e0e80 100644
--- a/src/base64.c
+++ b/src/base64.c
@@ -29,6 +29,9 @@
#include "libssh/priv.h"
#include "libssh/buffer.h"
+/* Do not allow encoding more than 256MB of data */
+#define BASE64_MAX_INPUT_LEN 256 * 1024 * 1024
+
static
const uint8_t alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
@@ -274,7 +277,15 @@ uint8_t *bin_to_base64(const uint8_t *source, size_t len)
{
uint8_t *base64 = NULL;
uint8_t *ptr = NULL;
- size_t flen = len + (3 - (len % 3)); /* round to upper 3 multiple */
+ size_t flen = 0;
+
+ /* Set the artificial upper limit for the input. Otherwise on 32b arch, the
+ * following line could overflow for sizes larger than SIZE_MAX / 4 */
+ if (len > BASE64_MAX_INPUT_LEN) {
+ return NULL;
+ }
+
+ flen = len + (3 - (len % 3)); /* round to upper 3 multiple */
flen = (4 * flen) / 3 + 1;
base64 = malloc(flen);
--
2.53.0

2630
CVE-2025-4878.patch Normal file

File diff suppressed because it is too large Load Diff

27
CVE-2025-5351.patch Normal file
View File

@ -0,0 +1,27 @@
From f9cbbd9359dc17a0388d7b7731546dedb0c34de5 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Tue, 6 May 2025 22:43:31 +0200
Subject: [PATCH] CVE-2025-5351 pki_crypto: Avoid double-free on low-memory
conditions
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
---
src/pki_crypto.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/pki_crypto.c b/src/pki_crypto.c
index b99a6e8c..bde19238 100644
--- a/src/pki_crypto.c
+++ b/src/pki_crypto.c
@@ -2023,6 +2023,7 @@ ssh_string pki_publickey_to_blob(const ssh_key key)
bignum_safe_free(bn);
bignum_safe_free(be);
OSSL_PARAM_free(params);
+ params = NULL;
#endif /* OPENSSL_VERSION_NUMBER */
break;
}
--
2.53.0

45
CVE-2025-8114.patch Normal file
View File

@ -0,0 +1,45 @@
From 7a76f6e268e4293e64358f65d1c253de5c40f875 Mon Sep 17 00:00:00 2001
From: Andreas Schneider <asn@cryptomilk.org>
Date: Wed, 6 Aug 2025 15:17:59 +0200
Subject: [PATCH] CVE-2025-8114: Fix NULL pointer dereference after allocation
failure
Signed-off-by: Andreas Schneider <asn@cryptomilk.org>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
(cherry picked from commit 53ac23ded4cb2c5463f6c4cd1525331bd578812d)
---
src/kex.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/kex.c b/src/kex.c
index f71482fd..64aacb9e 100644
--- a/src/kex.c
+++ b/src/kex.c
@@ -1494,6 +1494,8 @@ int ssh_make_sessionid(ssh_session session)
ssh_log_hexdump("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf));
#endif
+ /* Set rc for the following switch statement in case we goto error. */
+ rc = SSH_ERROR;
switch (session->next_crypto->kex_type) {
case SSH_KEX_DH_GROUP1_SHA1:
case SSH_KEX_DH_GROUP14_SHA1:
@@ -1553,6 +1555,7 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->secret_hash);
break;
}
+
/* During the first kex, secret hash and session ID are equal. However, after
* a key re-exchange, a new secret hash is calculated. This hash will not replace
* but complement existing session id.
@@ -1561,6 +1564,7 @@ int ssh_make_sessionid(ssh_session session)
session->next_crypto->session_id = malloc(session->next_crypto->digest_len);
if (session->next_crypto->session_id == NULL) {
ssh_set_error_oom(session);
+ rc = SSH_ERROR;
goto error;
}
memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash,
--
2.53.0

184
CVE-2025-8277.patch Normal file
View File

@ -0,0 +1,184 @@
From a55ed6b67033dace401dc42c312d7b3c0ec740a3 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Tue, 5 Aug 2025 18:42:31 +0200
Subject: [PATCH 1/3] CVE-2025-8277: packet: Adjust packet filter to work when
DH-GEX is guessed wrongly
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit 4310a696f2d632c6742678077d703d9b9ff3bc0e)
---
src/packet.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/packet.c b/src/packet.c
index 891d120c..af9a55da 100644
--- a/src/packet.c
+++ b/src/packet.c
@@ -294,6 +294,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
* or session_state == SSH_SESSION_STATE_INITIAL_KEX
* - dh_handshake_state == DH_STATE_INIT
* or dh_handshake_state == DH_STATE_INIT_SENT (re-exchange)
+ * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex)
* or dh_handshake_state == DH_STATE_FINISHED (re-exchange)
*
* Transitions:
@@ -313,6 +314,7 @@ static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session se
if ((session->dh_handshake_state != DH_STATE_INIT) &&
(session->dh_handshake_state != DH_STATE_INIT_SENT) &&
+ (session->dh_handshake_state != DH_STATE_REQUEST_SENT) &&
(session->dh_handshake_state != DH_STATE_FINISHED))
{
rc = SSH_PACKET_DENIED;
--
2.53.0
From 00bdcdc96ca6e367a2a2dd05ff56ca9e6e516f45 Mon Sep 17 00:00:00 2001
From: Francesco Rollo <eferollo@gmail.com>
Date: Thu, 24 Jul 2025 16:30:07 +0300
Subject: [PATCH 2/3] CVE-2025-8277: Fix memory leak of unused ephemeral key
pair after client's wrong KEX guess
Signed-off-by: Francesco Rollo <eferollo@gmail.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit ccff22d3787c1355b3f0dcd09fe54d90acc55bf1)
---
src/dh_crypto.c | 5 +++++
src/dh_key.c | 5 +++++
src/ecdh_crypto.c | 5 +++++
src/ecdh_gcrypt.c | 6 ++++++
src/ecdh_mbedcrypto.c | 6 ++++++
5 files changed, 27 insertions(+)
diff --git a/src/dh_crypto.c b/src/dh_crypto.c
index 85249bfc..60f35912 100644
--- a/src/dh_crypto.c
+++ b/src/dh_crypto.c
@@ -407,6 +407,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto)
struct dh_ctx *ctx = NULL;
int rc;
+ /* Cleanup any previously allocated dh_ctx */
+ if (crypto->dh_ctx != NULL) {
+ ssh_dh_cleanup(crypto);
+ }
+
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
return SSH_ERROR;
diff --git a/src/dh_key.c b/src/dh_key.c
index bda54b17..637b1b44 100644
--- a/src/dh_key.c
+++ b/src/dh_key.c
@@ -237,6 +237,11 @@ int ssh_dh_init_common(struct ssh_crypto_struct *crypto)
struct dh_ctx *ctx = NULL;
int rc;
+ /* Cleanup any previously allocated dh_ctx */
+ if (crypto->dh_ctx != NULL) {
+ ssh_dh_cleanup(crypto);
+ }
+
ctx = calloc(1, sizeof(*ctx));
if (ctx == NULL) {
return SSH_ERROR;
diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c
index 178d9060..c61b6ce4 100644
--- a/src/ecdh_crypto.c
+++ b/src/ecdh_crypto.c
@@ -219,6 +219,11 @@ int ssh_client_ecdh_init(ssh_session session){
return SSH_ERROR;
}
+ if (session->next_crypto->ecdh_privkey != NULL) {
+ EC_KEY_free(session->next_crypto->ecdh_privkey);
+ session->next_crypto->ecdh_privkey = NULL;
+ }
+
session->next_crypto->ecdh_privkey = key;
session->next_crypto->ecdh_client_pubkey = client_pubkey;
diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c
index 722428c2..f49e8eb2 100644
--- a/src/ecdh_gcrypt.c
+++ b/src/ecdh_gcrypt.c
@@ -101,6 +101,12 @@ int ssh_client_ecdh_init(ssh_session session)
goto out;
}
+ /* Free any previously allocated privkey */
+ if (session->next_crypto->ecdh_privkey != NULL) {
+ gcry_sexp_release(session->next_crypto->ecdh_privkey);
+ session->next_crypto->ecdh_privkey = NULL;
+ }
+
session->next_crypto->ecdh_privkey = key;
key = NULL;
session->next_crypto->ecdh_client_pubkey = client_pubkey;
diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c
index 1d9c8f36..d31bfcc7 100644
--- a/src/ecdh_mbedcrypto.c
+++ b/src/ecdh_mbedcrypto.c
@@ -70,6 +70,12 @@ int ssh_client_ecdh_init(ssh_session session)
return SSH_ERROR;
}
+ /* Free any previously allocated privkey */
+ if (session->next_crypto->ecdh_privkey != NULL) {
+ mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey);
+ SAFE_FREE(session->next_crypto->ecdh_privkey);
+ }
+
session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair));
if (session->next_crypto->ecdh_privkey == NULL) {
return SSH_ERROR;
--
2.53.0
From 2bdddb2bd34cdd563de9227d74c5a21784c03d3d Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Wed, 6 Aug 2025 11:10:38 +0200
Subject: [PATCH 3/3] CVE-2025-8277: ecdh: Free previously allocated pubkeys
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit c9d95ab0c7a52b231bcec09afbea71944ed0d852)
---
src/ecdh_crypto.c | 1 +
src/ecdh_gcrypt.c | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c
index c61b6ce4..5694b083 100644
--- a/src/ecdh_crypto.c
+++ b/src/ecdh_crypto.c
@@ -223,6 +223,7 @@ int ssh_client_ecdh_init(ssh_session session){
EC_KEY_free(session->next_crypto->ecdh_privkey);
session->next_crypto->ecdh_privkey = NULL;
}
+ SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey);
session->next_crypto->ecdh_privkey = key;
session->next_crypto->ecdh_client_pubkey = client_pubkey;
diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c
index f49e8eb2..ee5350cd 100644
--- a/src/ecdh_gcrypt.c
+++ b/src/ecdh_gcrypt.c
@@ -106,9 +106,10 @@ int ssh_client_ecdh_init(ssh_session session)
gcry_sexp_release(session->next_crypto->ecdh_privkey);
session->next_crypto->ecdh_privkey = NULL;
}
-
session->next_crypto->ecdh_privkey = key;
key = NULL;
+
+ SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey);
session->next_crypto->ecdh_client_pubkey = client_pubkey;
client_pubkey = NULL;
--
2.53.0

42
CVE-2026-0964.patch Normal file
View File

@ -0,0 +1,42 @@
From 5be2d4950b4030da2f95299e12905ea9e51dbedf Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
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 <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit daa80818f89347b4d80b0c5b80659f9a9e55e8cc)
---
src/scp.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/scp.c b/src/scp.c
index 04eb4f1b..a33af16b 100644
--- 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') {
--
2.53.0

273
CVE-2026-0965.patch Normal file
View File

@ -0,0 +1,273 @@
From 63079a26261b5ea6736fcdf68077135e7f2c991d Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
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 <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit a5eb30dbfd8f3526b2d04bd9f0a3803b665f5798)
---
include/libssh/misc.h | 4 ++
include/libssh/priv.h | 3 ++
src/bind_config.c | 4 +-
src/config.c | 5 ++-
src/dh-gex.c | 4 +-
src/known_hosts.c | 2 +-
src/knownhosts.c | 2 +-
src/misc.c | 74 ++++++++++++++++++++++++++++++++
tests/unittests/torture_config.c | 19 ++++++++
9 files changed, 109 insertions(+), 8 deletions(-)
diff --git a/include/libssh/misc.h b/include/libssh/misc.h
index 47a423f2..3452251d 100644
--- a/include/libssh/misc.h
+++ b/include/libssh/misc.h
@@ -21,6 +21,8 @@
#ifndef MISC_H_
#define MISC_H_
+#include <stdio.h>
+
/* in misc.c */
/* gets the user home dir. */
char *ssh_get_user_home_dir(void);
@@ -102,4 +104,6 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *repl);
int ssh_check_hostname_syntax(const char *hostname);
+FILE *ssh_strict_fopen(const char *filename, size_t max_file_size);
+
#endif /* MISC_H_ */
diff --git a/include/libssh/priv.h b/include/libssh/priv.h
index bab761b0..876ea4ed 100644
--- a/include/libssh/priv.h
+++ b/include/libssh/priv.h
@@ -434,4 +434,7 @@ 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
+
#endif /* _LIBSSH_PRIV_H */
diff --git a/src/bind_config.c b/src/bind_config.c
index 9e4a7fd4..c12f1003 100644
--- 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 bind, const char *filename)
* 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;
}
diff --git a/src/config.c b/src/config.c
index 94b70d58..bf349812 100644
--- a/src/config.c
+++ b/src/config.c
@@ -215,7 +215,7 @@ 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);
@@ -1205,8 +1205,9 @@ int ssh_config_parse_file(ssh_session session, const char *filename)
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;
}
diff --git a/src/dh-gex.c b/src/dh-gex.c
index b022f09e..81219586 100644
--- a/src/dh-gex.c
+++ b/src/dh-gex.c
@@ -520,9 +520,9 @@ static int ssh_retrieve_dhgroup(char *moduli_file,
}
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};
diff --git a/src/known_hosts.c b/src/known_hosts.c
index f660a6f3..ba2ae4d5 100644
--- a/src/known_hosts.c
+++ b/src/known_hosts.c
@@ -83,7 +83,7 @@ static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file,
struct ssh_tokens_st *tokens = NULL;
if (*file == NULL) {
- *file = fopen(filename,"r");
+ *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (*file == NULL) {
return NULL;
}
diff --git a/src/knownhosts.c b/src/knownhosts.c
index 62777ad2..e4865ac2 100644
--- a/src/knownhosts.c
+++ b/src/knownhosts.c
@@ -232,7 +232,7 @@ static int ssh_known_hosts_read_entries(const char *match,
FILE *fp = NULL;
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_TRACE, "Failed to open the known_hosts file '%s': %s",
diff --git a/src/misc.c b/src/misc.c
index 7bb431b6..6607775e 100644
--- 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 *hostname)
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_TRACE,
+ "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_TRACE,
+ "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_TRACE,
+ "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_TRACE,
+ "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_TRACE,
+ "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;
+}
+
/** @} */
diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c
index 995a5213..70ecfb95 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -2237,6 +2237,23 @@ static void torture_config_parse_uri(void **state)
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;
@@ -2323,6 +2340,8 @@ 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),
};
--
2.53.0

100
CVE-2026-0966.patch Normal file
View File

@ -0,0 +1,100 @@
From e868036a8e496e36cf986e000e050974cc30a0ae Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Thu, 8 Jan 2026 12:09:50 +0100
Subject: [PATCH 1/2] 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 <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
(cherry picked from commit 417a095e6749a1f3635e02332061edad3c6a3401)
---
src/misc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/misc.c b/src/misc.c
index 6607775e..0cca373a 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -452,7 +452,7 @@ char *ssh_get_hexa(const unsigned char *what, size_t len)
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;
}
--
2.53.0
From c112289ce14ef29f173d87b6cc507f066d6ca751 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Thu, 8 Jan 2026 12:10:16 +0100
Subject: [PATCH 2/2] CVE-2026-0966 tests: Test coverage for ssh_get_hexa
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
(cherry picked from commit 9be83584a56580da5a2f41e47137056dc0249b52)
---
tests/unittests/torture_misc.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c
index 77166759..4470c358 100644
--- a/tests/unittests/torture_misc.c
+++ b/tests/unittests/torture_misc.c
@@ -877,6 +877,36 @@ static void torture_ssh_is_ipaddr(void **state) {
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();
--
2.53.0

357
CVE-2026-0967.patch Normal file
View File

@ -0,0 +1,357 @@
From a56c4837c9e6db6cfb7e136b0e8b8d90dbc329ea Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
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 <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
(cherry picked from commit a411de5ce806e3ea24d088774b2f7584d6590b5f)
---
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 3e58f733..896d87cb 100644
--- 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 *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 70ecfb95..6dc59e48 100644
--- a/tests/unittests/torture_config.c
+++ b/tests/unittests/torture_config.c
@@ -1996,80 +1996,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.53.0

184
CVE-2026-0968.patch Normal file
View File

@ -0,0 +1,184 @@
From 7d08a51ec529edc5ccf7978d403ec4ef151e82a7 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Mon, 22 Dec 2025 20:59:11 +0100
Subject: [PATCH 1/2] CVE-2026-0968: sftp: Sanitize input handling in
sftp_parse_longname()
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit 20856f44c146468c830da61dcbbbaa8ce71e390b)
---
src/sftp.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/sftp.c b/src/sftp.c
index 60e591f0..70f9ed15 100644
--- a/src/sftp.c
+++ b/src/sftp.c
@@ -1289,13 +1289,18 @@ static char *sftp_parse_longname(const char *longname,
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 char *longname,
}
}
+ /* 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++;
}
--
2.53.0
From ccedc7907ebe2c1a977b739b85034953de582fb3 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Mon, 22 Dec 2025 21:00:03 +0100
Subject: [PATCH 2/2] CVE-2026-0968 tests: Reproducer for invalid longname data
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
(cherry picked from commit 90a5d8f47399e8db61b56793cd21476ff6a528e0)
---
tests/unittests/CMakeLists.txt | 7 +++
tests/unittests/torture_unit_sftp.c | 86 +++++++++++++++++++++++++++++
2 files changed, 93 insertions(+)
create mode 100644 tests/unittests/torture_unit_sftp.c
diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt
index 28458d49..658f14bc 100644
--- a/tests/unittests/CMakeLists.txt
+++ b/tests/unittests/CMakeLists.txt
@@ -95,6 +95,13 @@ if (UNIX AND NOT WIN32)
endif (WITH_SERVER)
endif (UNIX AND NOT WIN32)
+if (WITH_SFTP)
+ set(LIBSSH_UNIT_TESTS
+ ${LIBSSH_UNIT_TESTS}
+ torture_unit_sftp
+ )
+endif (WITH_SFTP)
+
foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS})
add_cmocka_test(${_UNIT_TEST}
SOURCES ${_UNIT_TEST}.c
diff --git a/tests/unittests/torture_unit_sftp.c b/tests/unittests/torture_unit_sftp.c
new file mode 100644
index 00000000..8cdaba8e
--- /dev/null
+++ b/tests/unittests/torture_unit_sftp.c
@@ -0,0 +1,86 @@
+#include "config.h"
+
+#include "sftp.c"
+#include "torture.h"
+
+#define LIBSSH_STATIC
+
+static void test_sftp_parse_longname(void **state)
+{
+ const char *lname = NULL;
+ char *value = NULL;
+
+ /* state not used */
+ (void)state;
+
+ /* Valid example from SFTP draft, page 18:
+ * https://datatracker.ietf.org/doc/draft-spaghetti-sshm-filexfer/
+ */
+ lname = "-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer";
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM);
+ assert_string_equal(value, "-rwxr-xr-x");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_OWNER);
+ assert_string_equal(value, "mjos");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_GROUP);
+ assert_string_equal(value, "staff");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_SIZE);
+ assert_string_equal(value, "348911");
+ free(value);
+ /* This function is broken further as the date contains space which breaks
+ * the parsing altogether */
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_DATE);
+ assert_string_equal(value, "Mar");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_TIME);
+ assert_string_equal(value, "25");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME);
+ assert_string_equal(value, "14:29");
+ free(value);
+}
+
+static void test_sftp_parse_longname_invalid(void **state)
+{
+ const char *lname = NULL;
+ char *value = NULL;
+
+ /* state not used */
+ (void)state;
+
+ /* Invalid inputs should not crash
+ */
+ lname = NULL;
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM);
+ assert_null(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME);
+ assert_null(value);
+
+ lname = "";
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM);
+ assert_string_equal(value, "");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME);
+ assert_null(value);
+
+ lname = "-rwxr-xr-x 1";
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM);
+ assert_string_equal(value, "-rwxr-xr-x");
+ free(value);
+ value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME);
+ assert_null(value);
+}
+
+int torture_run_tests(void)
+{
+ int rc;
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_sftp_parse_longname),
+ cmocka_unit_test(test_sftp_parse_longname_invalid),
+ };
+
+ rc = cmocka_run_group_tests(tests, NULL, NULL);
+ return rc;
+}
--
2.53.0

View File

@ -1,6 +1,6 @@
Name: libssh
Version: 0.10.4
Release: 17%{?dist}
Release: 18%{?dist}
Summary: A library implementing the SSH protocol
License: LGPLv2+
URL: http://www.libssh.org
@ -58,6 +58,16 @@ Patch16: escape-brackets-in-proxycommand.patch
Patch17: CVE-2025-5318.patch
Patch18: CVE-2025-5987.patch
Patch19: workaround-sshd-failure-rate-limiting.patch
Patch20: CVE-2025-4877.patch
Patch21: CVE-2025-4878.patch
Patch22: CVE-2025-5351.patch
Patch23: CVE-2025-8114.patch
Patch24: CVE-2025-8277.patch
Patch25: CVE-2026-0964.patch
Patch26: CVE-2026-0965.patch
Patch27: CVE-2026-0966.patch
Patch28: CVE-2026-0967.patch
Patch29: CVE-2026-0968.patch
%description
The ssh library was designed to be used by programmers needing a working SSH
@ -150,6 +160,19 @@ popd
%attr(0644,root,root) %config(noreplace) %{_sysconfdir}/libssh/libssh_server.config
%changelog
* Fri Feb 13 2026 Pavol Žáčik <pzacik@redhat.com> - 0.10.4-18
- Resolves: RHEL-150661
- Resolves: CVE-2025-4877
- Resolves: CVE-2025-4878
- Resolves: CVE-2025-5351
- Resolves: CVE-2025-8114
- Resolves: CVE-2025-8277
- Resolves: CVE-2026-0964
- Resolves: CVE-2026-0965
- Resolves: CVE-2026-0966
- Resolves: CVE-2026-0967
- Resolves: CVE-2026-0968
* Fri Dec 12 2025 Pavol Žáčik <pzacik@redhat.com> - 0.10.4-17
- Bump spec to resolve build tagging issues