diff -Nru libssh-0.11.2/CHANGELOG libssh-0.11.5/CHANGELOG --- libssh-0.11.2/CHANGELOG 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/CHANGELOG 2026-07-16 12:57:23.000000000 +0200 @@ -1,6 +1,54 @@ CHANGELOG ========= +version 0.11.5 (released 2026-07-21) + * Security: + * CVE-2026-15370: Stack buffer overflow in SFTP server longname construction + * CVE-2026-59843: Denial of service via zero advertised channel packet size + * CVE-2026-59844: Denial of service via oversized SFTP read length + * CVE-2026-59845: Denial of service via unchecked ProxyCommand fork() failure + * CVE-2026-59846: Information disclosure via ProxyCommand %r username expansion + * CVE-2026-59847: Integrity downgrade via OpenSSL AES-GCM tag verification + * CVE-2026-59848: Denial of service via SFTP responses with unknown request IDs + * CVE-2026-59849: Denial of service via automatic certificate authentication loop + * CVE-2026-59850: Use-after-free via data callbacks on closed channels + * Zero-initialize every ssh_string + * Compatibility: + * Fix compatibility with C23 / gcc16 + * Bugfixes: + * Fix multiple memory leaks, null checks, and error checks + * Validate peer public key in DH key exchange + * Avoid remote window overflow + * Avoid off-by-one overflow during kbdint authentication + * Avoid logging uninitialized sequence numbers + * Avoid double conversion of SFTP version number + * Send correct SFTP server version number + * Avoid handling repeated SFTP INIT messages + * Harmonize return values from SFTP server callbacks + +version 0.11.4 (released 2026-02-10) + * Security: + * CVE-2025-14821: libssh loads configuration files from the C:\etc directory + on Windows + * CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request() + * CVE-2026-0965: Possible Denial of Service when parsing unexpected + configuration files + * CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input + * CVE-2026-0967: Specially crafted patterns could cause DoS + * CVE-2026-0968: OOB Read in sftp_parse_longname() + * libssh-2026-sftp-extensions: Read buffer overrun when handling SFTP + extensions + * Stability and compatibility improvements of ProxyJump + +version 0.11.3 (released 2025-09-09) + * Security: + * CVE-2025-8114: Fix NULL pointer dereference after allocation failure + * CVE-2025-8277: Fix memory leak of ephemeral key pair during repeated wrong KEX + * Potential UAF when send() fails during key exchange + * Fix possible timeout during KEX if client sends authentication too early (#311) + * Cleanup OpenSSL PKCS#11 provider when loaded + * Zeroize buffers containing private key blobs during export + version 0.11.2 (released 2025-06-24) * Security: * CVE-2025-4877 - Write beyond bounds in binary to base64 conversion diff -Nru libssh-0.11.2/CMakeLists.txt libssh-0.11.5/CMakeLists.txt --- libssh-0.11.2/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/CMakeLists.txt 2026-07-16 12:57:23.000000000 +0200 @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12.0) +cmake_minimum_required(VERSION 3.14.0) # Specify search path for CMake modules to be loaded by include() # and find_package() @@ -9,7 +9,7 @@ include(DefineCMakeDefaults) include(DefineCompilerFlags) -project(libssh VERSION 0.11.2 LANGUAGES C) +project(libssh VERSION 0.11.5 LANGUAGES C) # global needed variable set(APPLICATION_NAME ${PROJECT_NAME}) @@ -21,7 +21,7 @@ # Increment AGE. Set REVISION to 0 # If the source code was changed, but there were no interface changes: # Increment REVISION. -set(LIBRARY_VERSION "4.10.2") +set(LIBRARY_VERSION "4.10.5") set(LIBRARY_SOVERSION "4") # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked @@ -77,7 +77,7 @@ # Disable symbol versioning in non UNIX platforms if (UNIX) - find_package(ABIMap 0.3.1) + find_package(ABIMap 0.4.0) else (UNIX) set(WITH_SYMBOL_VERSIONING OFF) endif (UNIX) @@ -168,6 +168,10 @@ set(ALLOW_ABI_BREAK "BREAK_ABI") endif() + if (WITH_FINAL) + set(FINAL "FINAL") + endif() + # Target we can depend on in 'make dist' set(_SYMBOL_TARGET "${PROJECT_NAME}.map") @@ -180,7 +184,7 @@ RELEASE_NAME_VERSION ${PROJECT_NAME}_${LIBRARY_VERSION} CURRENT_MAP ${MAP_PATH} COPY_TO ${MAP_PATH} - FINAL + ${FINAL} ${ALLOW_ABI_BREAK}) # Write the current version to the source diff -Nru libssh-0.11.2/DefineOptions.cmake libssh-0.11.5/DefineOptions.cmake --- libssh-0.11.2/DefineOptions.cmake 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/DefineOptions.cmake 2026-07-16 12:56:26.000000000 +0200 @@ -12,20 +12,35 @@ option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(WITH_PKCS11_URI "Build with PKCS#11 URI support" OFF) -option(WITH_PKCS11_PROVIDER "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) +option(WITH_PKCS11_PROVIDER + "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) option(UNIT_TESTING "Build with unit tests" OFF) option(CLIENT_TESTING "Build with client tests; requires openssh" OFF) -option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" OFF) -option(GSSAPI_TESTING "Build with GSSAPI tests; requires krb5-server,krb5-libs and krb5-workstation" OFF) -option(WITH_BENCHMARKS "Build benchmarks tools; enables unit testing and client tests" OFF) +option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" + OFF) +option( + GSSAPI_TESTING + "Build with GSSAPI tests; requires krb5-server,krb5-libs and krb5-workstation" + OFF) +option(WITH_BENCHMARKS + "Build benchmarks tools; enables unit testing and client tests" OFF) option(WITH_EXAMPLES "Build examples" ON) option(WITH_NACL "Build with libnacl (curve25519)" ON) option(WITH_SYMBOL_VERSIONING "Build with symbol versioning" ON) option(WITH_ABI_BREAK "Allow ABI break" OFF) option(WITH_GEX "Enable DH Group exchange mechanisms" ON) -option(WITH_INSECURE_NONE "Enable insecure none cipher and MAC algorithms (not suitable for production!)" OFF) -option(WITH_EXEC "Enable libssh to execute arbitrary commands from configuration files or options (match exec, proxy commands and OpenSSH-based proxy-jumps)." ON) -option(FUZZ_TESTING "Build with fuzzer for the server and client (automatically enables none cipher!)" OFF) +option( + WITH_INSECURE_NONE + "Enable insecure none cipher and MAC algorithms (not suitable for production!)" + OFF) +option( + WITH_EXEC + "Enable libssh to execute arbitrary commands from configuration files or options (match exec, proxy commands and OpenSSH-based proxy-jumps)." + ON) +option( + FUZZ_TESTING + "Build with fuzzer for the server and client (automatically enables none cipher!)" + OFF) option(PICKY_DEVELOPER "Build with picky developer flags" OFF) if (WITH_ZLIB) @@ -35,34 +50,47 @@ endif (WITH_ZLIB) if (WITH_BENCHMARKS) - set(UNIT_TESTING ON) - set(CLIENT_TESTING ON) -endif() - -if (UNIT_TESTING OR CLIENT_TESTING OR SERVER_TESTING OR GSSAPI_TESTING) - set(BUILD_STATIC_LIB ON) -endif() + set(UNIT_TESTING ON) + set(CLIENT_TESTING ON) +endif () + +if (UNIT_TESTING + OR CLIENT_TESTING + OR SERVER_TESTING + OR GSSAPI_TESTING) + set(BUILD_STATIC_LIB ON) +endif () if (WITH_NACL) - set(WITH_NACL ON) + set(WITH_NACL ON) endif (WITH_NACL) if (WITH_ABI_BREAK) - set(WITH_SYMBOL_VERSIONING ON) + set(WITH_SYMBOL_VERSIONING ON) endif (WITH_ABI_BREAK) +set(GLOBAL_CONF_DIR "/etc/ssh") +if (WIN32) + # Use PROGRAMDATA on Windows + if (DEFINED ENV{PROGRAMDATA}) + set(GLOBAL_CONF_DIR "$ENV{PROGRAMDATA}/ssh") + else () + set(GLOBAL_CONF_DIR "C:/ProgramData/ssh") + endif () +endif () + if (NOT GLOBAL_BIND_CONFIG) - set(GLOBAL_BIND_CONFIG "/etc/ssh/libssh_server_config") + set(GLOBAL_BIND_CONFIG "${GLOBAL_CONF_DIR}/libssh_server_config") endif (NOT GLOBAL_BIND_CONFIG) if (NOT GLOBAL_CLIENT_CONFIG) - set(GLOBAL_CLIENT_CONFIG "/etc/ssh/ssh_config") + set(GLOBAL_CLIENT_CONFIG "${GLOBAL_CONF_DIR}/ssh_config") endif (NOT GLOBAL_CLIENT_CONFIG) if (FUZZ_TESTING) - set(WITH_INSECURE_NONE ON) + set(WITH_INSECURE_NONE ON) endif (FUZZ_TESTING) if (WIN32) set(WITH_EXEC 0) -endif(WIN32) +endif (WIN32) diff -Nru libssh-0.11.2/config.h.cmake libssh-0.11.5/config.h.cmake --- libssh-0.11.2/config.h.cmake 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/config.h.cmake 2026-07-16 12:56:26.000000000 +0200 @@ -8,6 +8,10 @@ #cmakedefine BINARYDIR "${BINARYDIR}" #cmakedefine SOURCEDIR "${SOURCEDIR}" +/* Global configuration directory */ +#cmakedefine USR_GLOBAL_CONF_DIR "${USR_GLOBAL_CONF_DIR}" +#cmakedefine GLOBAL_CONF_DIR "${GLOBAL_CONF_DIR}" + /* Global bind configuration file path */ #cmakedefine GLOBAL_BIND_CONFIG "${GLOBAL_BIND_CONFIG}" diff -Nru libssh-0.11.2/debian/changelog libssh-0.11.5/debian/changelog --- libssh-0.11.2/debian/changelog 2025-11-04 00:32:14.000000000 +0100 +++ libssh-0.11.5/debian/changelog 2026-08-01 13:42:11.000000000 +0200 @@ -1,3 +1,38 @@ +libssh (0.11.5-0+deb13u1) trixie-security; urgency=medium + + * New upstream security/bug fix release 0.11.4: + - CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request() + - CVE-2026-0965: Possible Denial of Service when parsing unexpected + configuration files + - CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input + - CVE-2026-0967: Specially crafted patterns could cause DoS + - CVE-2026-0968: OOB Read in sftp_parse_longname() + - CVE-2026-3731: Read buffer overrun when handling SFTP extensions + - Note: CVE-2025-14821 is Windows specific, does not apply to Linux + https://www.libssh.org/2026/02/10/libssh-0-12-0-and-0-11-4-security-releases/ + (Closes: #1127693) + * New upstream security/bug fix release 0.11.5: + - CVE-2026-15370: Stack buffer overflow in SFTP server longname + construction + - CVE-2026-59843: Denial of service via zero advertised channel packet + size + - CVE-2026-59844: Denial of service via oversized SFTP read length + - CVE-2026-59845: Denial of service via unchecked ProxyCommand fork() + failure + - CVE-2026-59846: Information disclosure via ProxyCommand %r username + expansion + - CVE-2026-59847: Integrity downgrade via OpenSSL AES-GCM tag verification + - CVE-2026-59848: Denial of service via SFTP responses with unknown + request IDs + - CVE-2026-59849: Denial of service via automatic certificate + authentication loop + - CVE-2026-59850: Use-after-free via data callbacks on closed channels + - Zero-initialize every ssh_string + https://www.libssh.org/2026/07/21/libssh-0-12-1-and-0-11-5-security-releases/ + (Closes: #1142537) + + -- Martin Pitt Sat, 01 Aug 2026 13:42:11 +0200 + libssh (0.11.2-1+deb13u1) trixie; urgency=medium * CVE-2025-8277 (Closes: #1114859) diff -Nru libssh-0.11.2/debian/gbp.conf libssh-0.11.5/debian/gbp.conf --- libssh-0.11.2/debian/gbp.conf 2025-06-28 07:42:47.000000000 +0200 +++ libssh-0.11.5/debian/gbp.conf 2026-08-01 13:42:11.000000000 +0200 @@ -1,5 +1,5 @@ [DEFAULT] -debian-branch = debian +debian-branch = trixie upstream-branch = upstream pristine-tar = True diff -Nru libssh-0.11.2/debian/patches/CVE-2025-8114.patch libssh-0.11.5/debian/patches/CVE-2025-8114.patch --- libssh-0.11.2/debian/patches/CVE-2025-8114.patch 2025-11-04 00:32:14.000000000 +0100 +++ libssh-0.11.5/debian/patches/CVE-2025-8114.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,32 +0,0 @@ -From 65f363c9e3a22b90af7f74b5c439a133b1047379 Mon Sep 17 00:00:00 2001 -From: Andreas Schneider -Date: Wed, 6 Aug 2025 15:17:59 +0200 -Subject: CVE-2025-8114: Fix NULL pointer dereference after allocation failure - ---- libssh-0.11.2.orig/src/kex.c -+++ libssh-0.11.2/src/kex.c -@@ -1487,6 +1487,8 @@ int ssh_make_sessionid(ssh_session sessi - 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: -@@ -1546,6 +1548,7 @@ int ssh_make_sessionid(ssh_session sessi - 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. -@@ -1554,6 +1557,7 @@ int ssh_make_sessionid(ssh_session sessi - 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, diff -Nru libssh-0.11.2/debian/patches/CVE-2025-8277.patch libssh-0.11.5/debian/patches/CVE-2025-8277.patch --- libssh-0.11.2/debian/patches/CVE-2025-8277.patch 2025-11-04 00:32:14.000000000 +0100 +++ libssh-0.11.5/debian/patches/CVE-2025-8277.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,153 +0,0 @@ -Consists of these fixes upstream: - -From 87db2659ec608a977a63eea529f17b9168388d73 Mon Sep 17 00:00:00 2001 -From: Jakub Jelen -Date: Tue, 5 Aug 2025 18:42:31 +0200 -Subject: CVE-2025-8277: packet: Adjust packet filter to work when DH-GEX is - guessed wrongly - -From 266174a6d36687b65cf90174f06af90b8b27c65f Mon Sep 17 00:00:00 2001 -From: Francesco Rollo -Date: Thu, 24 Jul 2025 16:30:07 +0300 -Subject: CVE-2025-8277: Fix memory leak of unused ephemeral key pair after - client's wrong KEX guess - -From 8e4d67aa9eda455bfad9ac610e54b7a548d0aa08 Mon Sep 17 00:00:00 2001 -From: Jakub Jelen -Date: Wed, 6 Aug 2025 11:10:38 +0200 -Subject: CVE-2025-8277: ecdh: Free previously allocated pubkeys - -From 1c763e29d138db87665e98983f468d2dd0f286c1 Mon Sep 17 00:00:00 2001 -From: Jakub Jelen -Date: Wed, 6 Aug 2025 15:32:56 +0200 -Subject: CVE-2025-8277: mbedtls: Avoid leaking ecdh keys - ---- libssh-0.11.2.orig/src/dh_crypto.c -+++ libssh-0.11.2/src/dh_crypto.c -@@ -407,6 +407,11 @@ int ssh_dh_init_common(struct ssh_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; ---- libssh-0.11.2.orig/src/dh_key.c -+++ libssh-0.11.2/src/dh_key.c -@@ -237,6 +237,11 @@ int ssh_dh_init_common(struct ssh_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; ---- libssh-0.11.2.orig/src/ecdh_crypto.c -+++ libssh-0.11.2/src/ecdh_crypto.c -@@ -191,6 +191,17 @@ static ssh_string ssh_ecdh_generate(ssh_ - #endif /* OPENSSL_VERSION_NUMBER */ - return NULL; - } -+ -+ /* Free any previously allocated privkey */ -+ if (session->next_crypto->ecdh_privkey != NULL) { -+#if OPENSSL_VERSION_NUMBER < 0x30000000L -+ EC_KEY_free(session->next_crypto->ecdh_privkey); -+#else -+ EVP_PKEY_free(session->next_crypto->ecdh_privkey); -+#endif -+ session->next_crypto->ecdh_privkey = NULL; -+ } -+ - session->next_crypto->ecdh_privkey = key; - return pubkey_string; - } -@@ -219,6 +230,7 @@ int ssh_client_ecdh_init(ssh_session ses - return SSH_ERROR; - } - -+ ssh_string_free(session->next_crypto->ecdh_client_pubkey); - session->next_crypto->ecdh_client_pubkey = client_pubkey; - - /* register the packet callbacks */ ---- libssh-0.11.2.orig/src/ecdh_gcrypt.c -+++ libssh-0.11.2/src/ecdh_gcrypt.c -@@ -101,8 +101,15 @@ int ssh_client_ecdh_init(ssh_session ses - 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; -+ -+ SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey); - session->next_crypto->ecdh_client_pubkey = client_pubkey; - client_pubkey = NULL; - ---- libssh-0.11.2.orig/src/ecdh_mbedcrypto.c -+++ libssh-0.11.2/src/ecdh_mbedcrypto.c -@@ -70,6 +70,12 @@ int ssh_client_ecdh_init(ssh_session ses - 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; -@@ -110,6 +116,7 @@ int ssh_client_ecdh_init(ssh_session ses - goto out; - } - -+ SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey); - session->next_crypto->ecdh_client_pubkey = client_pubkey; - client_pubkey = NULL; - ---- libssh-0.11.2.orig/src/packet.c -+++ libssh-0.11.2/src/packet.c -@@ -294,6 +294,7 @@ static enum ssh_packet_filter_result_e s - * 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 s - - 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; ---- libssh-0.11.2.orig/src/wrapper.c -+++ libssh-0.11.2/src/wrapper.c -@@ -181,7 +181,10 @@ void crypto_free(struct ssh_crypto_struc - #endif /* OPENSSL_VERSION_NUMBER */ - #elif defined HAVE_GCRYPT_ECC - gcry_sexp_release(crypto->ecdh_privkey); --#endif -+#elif defined HAVE_LIBMBEDCRYPTO -+ mbedtls_ecp_keypair_free(crypto->ecdh_privkey); -+ SAFE_FREE(crypto->ecdh_privkey); -+#endif /* HAVE_LIBGCRYPT */ - crypto->ecdh_privkey = NULL; - } - #endif diff -Nru libssh-0.11.2/debian/patches/series libssh-0.11.5/debian/patches/series --- libssh-0.11.2/debian/patches/series 2025-11-04 00:32:14.000000000 +0100 +++ libssh-0.11.5/debian/patches/series 2026-08-01 13:42:11.000000000 +0200 @@ -1,5 +1,3 @@ 1003-custom-lib-names.patch 2003-disable-expand_tilde_unix-test.patch 2004-install-static-lib.patch -CVE-2025-8277.patch -CVE-2025-8114.patch diff -Nru libssh-0.11.2/doc/guided_tour.dox libssh-0.11.5/doc/guided_tour.dox --- libssh-0.11.2/doc/guided_tour.dox 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/doc/guided_tour.dox 2026-07-16 12:48:05.000000000 +0200 @@ -190,7 +190,6 @@ ssh_key srv_pubkey = NULL; size_t hlen; char buf[10]; - char *hexa = NULL; char *p = NULL; int cmp; int rc; @@ -201,7 +200,7 @@ } rc = ssh_get_publickey_hash(srv_pubkey, - SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); ssh_key_free(srv_pubkey); @@ -217,7 +216,7 @@ break; case SSH_KNOWN_HOSTS_CHANGED: fprintf(stderr, "Host key for server changed: it is now:\n"); - ssh_print_hexa("Public key hash", hash, hlen); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); fprintf(stderr, "For security reasons, connection will be stopped\n"); ssh_clean_pubkey_hash(&hash); @@ -238,10 +237,9 @@ /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ case SSH_KNOWN_HOSTS_UNKNOWN: - hexa = ssh_get_hexa(hash, hlen); fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); - fprintf(stderr, "Public key hash: %s\n", hexa); - ssh_string_free_char(hexa); + fprintf(stderr, "Public key hash: "); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); ssh_clean_pubkey_hash(&hash); p = fgets(buf, sizeof(buf), stdin); if (p == NULL) { diff -Nru libssh-0.11.2/examples/sample_sftpserver.c libssh-0.11.5/examples/sample_sftpserver.c --- libssh-0.11.2/examples/sample_sftpserver.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/examples/sample_sftpserver.c 2026-07-16 12:56:26.000000000 +0200 @@ -305,6 +305,12 @@ { struct session_data_struct *sdata = (struct session_data_struct *)userdata; + /* This server supports only one channel -- fail for repeated channel + * requests */ + if (sdata->channel != NULL) { + return NULL; + } + sdata->channel = ssh_channel_new(session); return sdata->channel; } diff -Nru libssh-0.11.2/include/libssh/crypto.h libssh-0.11.5/include/libssh/crypto.h --- libssh-0.11.2/include/libssh/crypto.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/crypto.h 2026-07-16 12:56:26.000000000 +0200 @@ -223,9 +223,6 @@ size_t requested_len); int secure_memcmp(const void *s1, const void *s2, size_t n); -#if defined(HAVE_LIBCRYPTO) && !defined(WITH_PKCS11_PROVIDER) -ENGINE *pki_get_engine(void); -#endif /* HAVE_LIBCRYPTO */ void compress_cleanup(struct ssh_crypto_struct *crypto); diff -Nru libssh-0.11.2/include/libssh/libcrypto.h libssh-0.11.5/include/libssh/libcrypto.h --- libssh-0.11.2/include/libssh/libcrypto.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/libcrypto.h 2026-07-16 12:56:26.000000000 +0200 @@ -121,6 +121,15 @@ ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p); int pki_key_ecgroup_name_to_nid(const char *group); + +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +int pki_load_pkcs11_provider(void); +#else +ENGINE *pki_get_engine(void); +#endif +#endif /* WITH_PKCS11_PROVIDER */ + #endif /* HAVE_LIBCRYPTO */ #endif /* LIBCRYPTO_H_ */ diff -Nru libssh-0.11.2/include/libssh/misc.h libssh-0.11.5/include/libssh/misc.h --- libssh-0.11.2/include/libssh/misc.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/misc.h 2026-07-16 12:56:26.000000000 +0200 @@ -36,6 +36,7 @@ #include #include #endif /* _WIN32 */ +#include #ifdef __cplusplus extern "C" { @@ -136,6 +137,8 @@ void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list); bool ssh_libssh_proxy_jumps(void); +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); + #ifdef __cplusplus } #endif diff -Nru libssh-0.11.2/include/libssh/poll.h libssh-0.11.5/include/libssh/poll.h --- libssh-0.11.2/include/libssh/poll.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/poll.h 2026-07-16 12:48:05.000000000 +0200 @@ -157,6 +157,7 @@ int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +bool ssh_poll_is_locked(ssh_poll_handle p); int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); int ssh_event_add_poll(ssh_event event, ssh_poll_handle p); diff -Nru libssh-0.11.2/include/libssh/priv.h libssh-0.11.5/include/libssh/priv.h --- libssh-0.11.2/include/libssh/priv.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/priv.h 2026-07-16 12:57:23.000000000 +0200 @@ -245,6 +245,8 @@ # define LIBSSH_MEM_PROTECTION #endif +#define SSH_DANGEROUS_SHELL_CHARS "'`\";&<>|(){}$\\," + /* forward declarations */ struct ssh_common_struct; struct ssh_kex_struct; @@ -473,6 +475,9 @@ #define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) int encode_current_tty_opts(unsigned 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 diff -Nru libssh-0.11.2/include/libssh/sftp.h libssh-0.11.5/include/libssh/sftp.h --- libssh-0.11.2/include/libssh/sftp.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/sftp.h 2026-07-16 12:57:23.000000000 +0200 @@ -93,6 +93,7 @@ sftp_ext ext; sftp_packet read_packet; sftp_limits_t limits; + struct ssh_list *outstanding_ids; }; struct sftp_packet_struct { diff -Nru libssh-0.11.2/include/libssh/sftp_priv.h libssh-0.11.5/include/libssh/sftp_priv.h --- libssh-0.11.2/include/libssh/sftp_priv.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/sftp_priv.h 2026-07-16 12:57:23.000000000 +0200 @@ -21,6 +21,8 @@ #ifndef SFTP_PRIV_H #define SFTP_PRIV_H +#include "libssh/sftp.h" + #ifdef __cplusplus extern "C" { #endif @@ -62,15 +64,17 @@ sftp_message sftp_dequeue(sftp_session sftp, uint32_t id); -/* - * 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 +/** + * @brief Assigns a new SFTP ID for new requests and assures there is no + * collision between them. + * + * @param sftp The sftp session handle. + * @param id_out Pointer to store the new ID. + * + * @returns SSH_OK on success with the new ID stored in *id + * @returns 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; -} +int sftp_get_new_id(sftp_session sftp, uint32_t *id_out); sftp_status_message parse_status_msg(sftp_message msg); diff -Nru libssh-0.11.2/include/libssh/sftpserver.h libssh-0.11.5/include/libssh/sftpserver.h --- libssh-0.11.2/include/libssh/sftpserver.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/include/libssh/sftpserver.h 2026-07-16 12:48:05.000000000 +0200 @@ -43,6 +43,15 @@ * @{ */ +/** + * @brief Macro to declare an SFTP message callback function. + * + * @param name The name of the callback function to declare. + * + * @return SSH_OK for properly processed messages (including errors reported to + * client over the channel and SSH_ERROR for protocol errors + * that the channel callback should treat as fatal. + */ #define SSH_SFTP_CALLBACK(name) \ static int name(sftp_client_message message) diff -Nru libssh-0.11.2/src/ABI/current libssh-0.11.5/src/ABI/current --- libssh-0.11.2/src/ABI/current 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/ABI/current 2026-07-16 12:57:23.000000000 +0200 @@ -1 +1 @@ -4.10.2 \ No newline at end of file +4.10.5 \ No newline at end of file diff -Nru libssh-0.11.2/src/ABI/libssh-4.10.3.symbols libssh-0.11.5/src/ABI/libssh-4.10.3.symbols --- libssh-0.11.2/src/ABI/libssh-4.10.3.symbols 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/src/ABI/libssh-4.10.3.symbols 2026-06-12 15:53:41.000000000 +0200 @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff -Nru libssh-0.11.2/src/ABI/libssh-4.10.4.symbols libssh-0.11.5/src/ABI/libssh-4.10.4.symbols --- libssh-0.11.2/src/ABI/libssh-4.10.4.symbols 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/src/ABI/libssh-4.10.4.symbols 2026-06-12 15:53:41.000000000 +0200 @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff -Nru libssh-0.11.2/src/ABI/libssh-4.10.5.symbols libssh-0.11.5/src/ABI/libssh-4.10.5.symbols --- libssh-0.11.2/src/ABI/libssh-4.10.5.symbols 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/src/ABI/libssh-4.10.5.symbols 2026-07-16 12:57:23.000000000 +0200 @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff -Nru libssh-0.11.2/src/auth.c libssh-0.11.5/src/auth.c --- libssh-0.11.2/src/auth.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/auth.c 2026-07-16 12:57:23.000000000 +0200 @@ -1188,6 +1188,7 @@ SSH_AUTH_AUTO_STATE_PUBKEY, SSH_AUTH_AUTO_STATE_KEY_IMPORTED, SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT, SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED }; @@ -1294,6 +1295,11 @@ if (session == NULL) { return SSH_AUTH_ERROR; } + + SSH_LOG(SSH_LOG_INFO, + "Starting authentication as a user %s", + username ? username : session->opts.username); + if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) { session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY; return SSH_AUTH_DENIED; @@ -1414,6 +1420,7 @@ } if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT || state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { ssh_key k = state->pubkey; if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { @@ -1464,14 +1471,15 @@ continue; } /* if the file does not exists, try configuration options */ - state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT; } /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { SSH_KEY_FREE(state->cert); - if (state->cert_it == NULL) { - state->cert_it = ssh_list_get_iterator(session->opts.certificate); - } while (state->cert_it != NULL) { const char *cert_file = state->cert_it->data; ssh_key cert = NULL; @@ -1495,10 +1503,12 @@ state->cert = cert; cert = NULL; state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + state->cert_it = state->cert_it->next; /* try to authenticate with this identity */ break; /* try this cert */ } /* continue with next identity */ + state->cert_it = state->cert_it->next; } if (state->cert != NULL) { continue; /* retry with the certificate */ @@ -2192,7 +2202,7 @@ ssh_set_error_invalid(session); return NULL; } - if (i > session->kbdint->nprompts) { + if (i >= session->kbdint->nprompts) { ssh_set_error_invalid(session); return NULL; } diff -Nru libssh-0.11.2/src/bind.c libssh-0.11.5/src/bind.c --- libssh-0.11.2/src/bind.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/bind.c 2026-07-16 12:56:26.000000000 +0200 @@ -218,7 +218,8 @@ return SSH_OK; } -int ssh_bind_listen(ssh_bind sshbind) { +int ssh_bind_listen(ssh_bind sshbind) +{ const char *host = NULL; socket_t fd; int rc; @@ -226,23 +227,6 @@ /* Apply global bind configurations, if it hasn't been applied before */ rc = ssh_bind_options_parse_config(sshbind, NULL); if (rc != 0) { - ssh_set_error(sshbind, SSH_FATAL,"Could not parse global config"); - return SSH_ERROR; - } - - /* Set default hostkey paths if no hostkey was found before */ - if (sshbind->ecdsakey == NULL && - sshbind->rsakey == NULL && - sshbind->ed25519key == NULL) { - - sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); - sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); - sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); - } - - /* Apply global bind configurations, if it hasn't been applied before */ - rc = ssh_bind_options_parse_config(sshbind, NULL); - if (rc != 0) { ssh_set_error(sshbind, SSH_FATAL, "Could not parse global config"); return SSH_ERROR; } @@ -289,10 +273,10 @@ } sshbind->bindfd = fd; - } else { - SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); - } - return 0; + } else { + SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); + } + return 0; } int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata) diff -Nru libssh-0.11.2/src/bind_config.c libssh-0.11.5/src/bind_config.c --- libssh-0.11.2/src/bind_config.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/bind_config.c 2026-07-16 12:56:26.000000000 +0200 @@ -212,7 +212,7 @@ 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 @@ * 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 -Nru libssh-0.11.2/src/channels.c libssh-0.11.5/src/channels.c --- libssh-0.11.2/src/channels.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/channels.c 2026-07-16 12:57:23.000000000 +0200 @@ -195,6 +195,13 @@ 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_DEBUG, "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 ":%" PRIu32, channel->local_channel, @@ -553,6 +560,15 @@ was_empty = channel->remote_window == 0; + if (UINT32_MAX - channel->remote_window < bytes) { + ssh_set_error(session, + SSH_FATAL, + "Window adjust %" PRIu32 " overflows remote window.", + bytes); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + channel->remote_window += bytes; /* Writing to the channel is non-blocking until the receive window is empty. @@ -628,6 +644,13 @@ 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; + } + if (len > channel->local_window) { SSH_LOG(SSH_LOG_RARE, "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")", @@ -1711,7 +1734,7 @@ */ int ssh_channel_is_open(ssh_channel channel) { - if (channel == NULL) { + if (channel == NULL || channel->session == NULL) { return 0; } return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); @@ -3456,7 +3479,7 @@ *pexit_signal = NULL; if (channel->exit.signal != NULL) { *pexit_signal = strdup(channel->exit.signal); - if (pexit_signal == NULL) { + if (*pexit_signal == NULL) { ssh_set_error_oom(session); return SSH_ERROR; } diff -Nru libssh-0.11.2/src/client.c libssh-0.11.5/src/client.c --- libssh-0.11.2/src/client.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/client.c 2026-07-16 12:56:26.000000000 +0200 @@ -836,6 +836,7 @@ session->opts.fd = SSH_INVALID_SOCKET; session->session_state = SSH_SESSION_STATE_DISCONNECTED; session->pending_call_state = SSH_PENDING_CALL_NONE; + session->packet_state = PACKET_STATE_INIT; while ((it = ssh_list_get_iterator(session->channels)) != NULL) { ssh_channel_do_free(ssh_iterator_value(ssh_channel, it)); diff -Nru libssh-0.11.2/src/config.c libssh-0.11.5/src/config.c --- libssh-0.11.2/src/config.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/config.c 2026-07-16 12:56:26.000000000 +0200 @@ -223,10 +223,9 @@ 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; } @@ -1066,13 +1065,11 @@ } break; case SOC_USERNAME: - if (session->opts.username == NULL) { - p = ssh_config_get_str_tok(&s, NULL); - if (p && *parsing) { + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_USER, p); - } - } - break; + } + break; case SOC_IDENTITY: p = ssh_config_get_str_tok(&s, NULL); if (p && *parsing) { @@ -1466,8 +1463,9 @@ 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 -Nru libssh-0.11.2/src/config_parser.c libssh-0.11.5/src/config_parser.c --- libssh-0.11.2/src/config_parser.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/config_parser.c 2026-07-16 12:48:05.000000000 +0200 @@ -174,7 +174,7 @@ char **port, bool ignore_port) { - char *endp = NULL; + const char *endp = NULL; long port_n; int rc; diff -Nru libssh-0.11.2/src/connect.c libssh-0.11.5/src/connect.c --- libssh-0.11.2/src/connect.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/connect.c 2026-07-16 12:56:26.000000000 +0200 @@ -281,6 +281,9 @@ * connection, otherwise return the first address without error or error */ if (s == -1) { s = first; + } else if (s != first && first != -1) { + /* Clean up the saved socket if any */ + ssh_connect_socket_close(first); } return s; diff -Nru libssh-0.11.2/src/connector.c libssh-0.11.5/src/connector.c --- libssh-0.11.2/src/connector.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/connector.c 2026-07-16 12:56:26.000000000 +0200 @@ -166,7 +166,7 @@ /* Fallback to default value for invalid flags */ if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { - connector->in_flags = SSH_CONNECTOR_STDOUT; + connector->out_flags = SSH_CONNECTOR_STDOUT; } return ssh_add_channel_callbacks(channel, &connector->out_channel_cb); @@ -228,6 +228,87 @@ /** * @internal * + * @brief Update the connector's flags after a read-write io + * operation + * + * This should be called after some data is successfully read from + * connector's input and written to connector's output. + * + * @param[in, out] connector Connector for which the io operation occured. + * + * @warning This does not consider the case when the io indicated failure + * + * @warning This does not consider the case when the input indicated that + * EOF was encountered. + */ +static void ssh_connector_update_flags_after_io(ssh_connector connector) +{ + /* + * With fds we can afford to mark: + * - in_available as 0 after an fd read (even if more pending data can be + * immediately read from the fd) + * + * - out_wontblock as 0 after an fd write (even if more data can + * be written to the fd without blocking) + * + * since poll events set on the fd will get raised to indicate + * possibility of read/write in case existing situation is apt + * (i.e can read/write occur right now) or if situation becomes + * apt in future (read data becomes available, write becomes + * possible) + */ + + /* + * On the other hand, with channels we need to be more careful + * before claiming read/write not possible because channel callbacks + * are called in limited scenarios. + * + * (e.g. connector callback to indicate read data available on input + * channel is called only when new data is received on channel. It is + * not called when we have some pending data in channel's buffers but + * don't receive any new data on the channel) + * + * Hence, in case of channels, blindly setting flag associated with + * read/write input/output to 0 after a read/write may not be a good + * idea as the callback that sets it back to 1 again may not be ever + * called again. + */ + + uint32_t window_size; + + /* update in_available based on input source (fd or channel) */ + if (connector->in_fd != SSH_INVALID_SOCKET) { + connector->in_available = 0; + } else if (connector->in_channel != NULL) { + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0) { + connector->in_available = 1; + } else { + connector->in_available = 0; + } + } else { + /* connector input is invalid ! */ + return; + } + + /* update out_wontblock based on output source (fd or channel) */ + if (connector->out_fd != SSH_INVALID_SOCKET) { + connector->out_wontblock = 0; + } else if (connector->out_channel != NULL) { + window_size = ssh_channel_window_size(connector->out_channel); + if (window_size > 0) { + connector->out_wontblock = 1; + } else { + connector->out_wontblock = 0; + } + } else { + /* connector output is invalid ! */ + return; + } +} + +/** + * @internal + * * @brief Callback called when a poll event is received on an input fd. */ static void ssh_connector_fd_in_cb(ssh_connector connector) @@ -306,8 +387,8 @@ ssh_set_error(connector->session, SSH_FATAL, "output socket or channel closed"); return; } - connector->out_wontblock = 0; - connector->in_available = 0; + + ssh_connector_update_flags_after_io(connector); } else { connector->in_available = 1; } @@ -359,8 +440,8 @@ "Output socket or channel closed"); return; } - connector->in_available = 0; - connector->out_wontblock = 0; + + ssh_connector_update_flags_after_io(connector); } else { connector->out_wontblock = 1; } @@ -381,15 +462,13 @@ * * @returns 0 */ -static int ssh_connector_fd_cb(ssh_poll_handle p, +static int ssh_connector_fd_cb(UNUSED_PARAM(ssh_poll_handle p), socket_t fd, int revents, void *userdata) { ssh_connector connector = userdata; - (void)p; - if (revents & POLLERR) { ssh_connector_except(connector, fd); } else if((revents & (POLLIN|POLLHUP)) && fd == connector->in_fd) { @@ -419,7 +498,7 @@ * @returns Amount of data bytes consumed */ static int ssh_connector_channel_data_cb(ssh_session session, - ssh_channel channel, + UNUSED_PARAM(ssh_channel channel), void *data, uint32_t len, int is_stderr, @@ -429,10 +508,6 @@ int w; uint32_t window; - (void) session; - (void) channel; - (void) is_stderr; - SSH_LOG(SSH_LOG_TRACE,"connector data on channel"); if (is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDERR)) { @@ -484,11 +559,7 @@ return SSH_ERROR; } - connector->out_wontblock = 0; - connector->in_available = 0; - if ((unsigned int)w < len) { - connector->in_available = 1; - } + ssh_connector_update_flags_after_io(connector); ssh_connector_reset_pollevents(connector); return w; @@ -510,10 +581,11 @@ * * @returns Amount of data bytes consumed */ -static int ssh_connector_channel_write_wontblock_cb(ssh_session session, - ssh_channel channel, - uint32_t bytes, - void *userdata) +static int +ssh_connector_channel_write_wontblock_cb(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + uint32_t bytes, + void *userdata) { ssh_connector connector = userdata; uint8_t buffer[CHUNKSIZE]; @@ -553,8 +625,8 @@ return 0; } - connector->in_available = 0; - connector->out_wontblock = 0; + + ssh_connector_update_flags_after_io(connector); } else { connector->out_wontblock = 1; } diff -Nru libssh-0.11.2/src/dh-gex.c libssh-0.11.5/src/dh-gex.c --- libssh-0.11.2/src/dh-gex.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/dh-gex.c 2026-07-16 12:56:26.000000000 +0200 @@ -167,6 +167,7 @@ if (bignum_cmp(modulus, one) <= 0) { /* p must be positive and preferably bigger than one */ ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; } if (!bignum_is_bit_set(modulus, 0)) { /* p must be a prime and therefore not divisible by 2 */ @@ -519,9 +520,9 @@ } 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 -Nru libssh-0.11.2/src/dh_crypto.c libssh-0.11.5/src/dh_crypto.c --- libssh-0.11.2/src/dh_crypto.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/dh_crypto.c 2026-07-16 12:56:26.000000000 +0200 @@ -407,6 +407,11 @@ 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 -Nru libssh-0.11.2/src/dh_key.c libssh-0.11.5/src/dh_key.c --- libssh-0.11.2/src/dh_key.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/dh_key.c 2026-07-16 12:56:26.000000000 +0200 @@ -171,6 +171,32 @@ ctx->keypair[peer].priv_key = priv; } if (pub) { + int rc; + bignum one = bignum_new(); + bignum pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + rc = bignum_set_word(one, 1); + if (rc != 1) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + bignum_sub(pmin1, ctx->modulus, one); + + /* Validate the peer public key `x` is 1 < x < (modulus - 1) */ + if (bignum_cmp(pub, one) <= 0 || + bignum_cmp(pub, pmin1) >= 0) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + bignum_safe_free(one); + bignum_safe_free(pmin1); + bignum_safe_free(ctx->keypair[peer].pub_key); ctx->keypair[peer].pub_key = pub; } @@ -237,6 +263,11 @@ 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 -Nru libssh-0.11.2/src/ecdh_crypto.c libssh-0.11.5/src/ecdh_crypto.c --- libssh-0.11.2/src/ecdh_crypto.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/ecdh_crypto.c 2026-07-16 12:56:26.000000000 +0200 @@ -191,6 +191,17 @@ #endif /* OPENSSL_VERSION_NUMBER */ return NULL; } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(session->next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(session->next_crypto->ecdh_privkey); +#endif + session->next_crypto->ecdh_privkey = NULL; + } + session->next_crypto->ecdh_privkey = key; return pubkey_string; } @@ -219,6 +230,7 @@ return SSH_ERROR; } + ssh_string_free(session->next_crypto->ecdh_client_pubkey); session->next_crypto->ecdh_client_pubkey = client_pubkey; /* register the packet callbacks */ diff -Nru libssh-0.11.2/src/ecdh_gcrypt.c libssh-0.11.5/src/ecdh_gcrypt.c --- libssh-0.11.2/src/ecdh_gcrypt.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/ecdh_gcrypt.c 2026-07-16 12:56:26.000000000 +0200 @@ -101,8 +101,15 @@ 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; + + SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey); session->next_crypto->ecdh_client_pubkey = client_pubkey; client_pubkey = NULL; diff -Nru libssh-0.11.2/src/ecdh_mbedcrypto.c libssh-0.11.5/src/ecdh_mbedcrypto.c --- libssh-0.11.2/src/ecdh_mbedcrypto.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/ecdh_mbedcrypto.c 2026-07-16 12:56:26.000000000 +0200 @@ -70,6 +70,12 @@ 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; @@ -110,6 +116,7 @@ goto out; } + SSH_STRING_FREE(session->next_crypto->ecdh_client_pubkey); session->next_crypto->ecdh_client_pubkey = client_pubkey; client_pubkey = NULL; diff -Nru libssh-0.11.2/src/gssapi.c libssh-0.11.5/src/gssapi.c --- libssh-0.11.2/src/gssapi.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/gssapi.c 2026-07-16 12:56:26.000000000 +0200 @@ -302,6 +302,11 @@ } memcpy(session->gssapi->mech.elements, oid.elements, oid.length); gss_release_oid_set(&min_stat, &selected); + if (i == n_oid) { + SSH_LOG(SSH_LOG_TRACE, "GSSAPI: no selected OID matched client OIDs"); + ssh_auth_reply_default(session, 0); + return SSH_ERROR; + } session->gssapi->user = strdup(user); session->gssapi->service = service_name; session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; diff -Nru libssh-0.11.2/src/kex.c libssh-0.11.5/src/kex.c --- libssh-0.11.2/src/kex.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/kex.c 2026-07-16 12:56:26.000000000 +0200 @@ -313,7 +313,7 @@ size_t client_kex_len; size_t server_kex_len; - char *colon = NULL; + const char *colon = NULL; int is_wrong = 1; @@ -1100,7 +1100,7 @@ * do it now. The packet is simple, but we need to do some preparations */ if (first_kex_packet_follows == 1) { char *list = kex->methods[SSH_KEX]; - char *colon = strchr(list, ','); + const char *colon = strchr(list, ','); size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); char *kex_name = calloc(kex_name_len + 1, 1); if (kex_name == NULL) { @@ -1487,6 +1487,8 @@ 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: @@ -1546,6 +1548,7 @@ 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. @@ -1554,6 +1557,7 @@ 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, diff -Nru libssh-0.11.2/src/known_hosts.c libssh-0.11.5/src/known_hosts.c --- libssh-0.11.2/src/known_hosts.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/known_hosts.c 2026-07-16 12:48:05.000000000 +0200 @@ -83,7 +83,7 @@ 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 -Nru libssh-0.11.2/src/knownhosts.c libssh-0.11.5/src/knownhosts.c --- libssh-0.11.2/src/knownhosts.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/knownhosts.c 2026-07-16 12:56:26.000000000 +0200 @@ -216,11 +216,22 @@ return 0; } -/* This method reads the known_hosts file referenced by the path +/** + * @internal + * + * @brief Read entries from filename to provided list + * + * This method reads the known_hosts file referenced by the path * in filename argument, and entries matching the match argument * will be added to the list in entries argument. * If the entries list is NULL, it will allocate a new list. Caller * is responsible to free it even if an error occurs. + * + * @param match[in] The host name (with port) to match against + * @param filename[in] The known hosts file to parse + * @param entries[in,out] The list of entries to append matching ones + * @return `SSH_OK` on missing file or success parsing, + * `SSH_ERROR` on error */ static int ssh_known_hosts_read_entries(const char *match, const char *filename, @@ -232,7 +243,7 @@ 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", @@ -299,7 +310,11 @@ } } if (entry != NULL) { - ssh_list_append(*entries, entry); + rc = ssh_list_append(*entries, entry); + if (rc != SSH_OK) { + ssh_knownhosts_entry_free(entry); + goto error; + } } } @@ -346,6 +361,33 @@ /** * @internal + * + * @brief Free known hosts entries list + * + * @param[in] entry_list The list of ssh_knownhosts_entry items + */ +static void ssh_knownhosts_entries_free(struct ssh_list *entry_list) +{ + struct ssh_iterator *it = NULL; + + if (entry_list == NULL) { + return; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); +} +/** + * @internal + * * @brief Check which host keys should be preferred for the session. * * This checks the known_hosts file to find out which algorithms should be @@ -376,25 +418,23 @@ } } - host_port = ssh_session_get_host_port(session); - if (host_port == NULL) { - return NULL; - } - list = ssh_list_new(); if (list == NULL) { ssh_set_error_oom(session); - SAFE_FREE(host_port); return NULL; } + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + goto error; + } + rc = ssh_known_hosts_read_entries(host_port, session->opts.knownhosts, &entry_list); if (rc != 0) { - ssh_list_free(entry_list); - ssh_list_free(list); - return NULL; + SAFE_FREE(host_port); + goto error; } rc = ssh_known_hosts_read_entries(host_port, @@ -402,21 +442,16 @@ &entry_list); SAFE_FREE(host_port); if (rc != 0) { - ssh_list_free(entry_list); - ssh_list_free(list); - return NULL; + goto error; } if (entry_list == NULL) { - ssh_list_free(list); - return NULL; + goto error; } count = ssh_list_count(entry_list); if (count == 0) { - ssh_list_free(list); - ssh_list_free(entry_list); - return NULL; + goto error; } for (it = ssh_list_get_iterator(entry_list); @@ -460,6 +495,7 @@ return list; error: + ssh_knownhosts_entries_free(entry_list); ssh_list_free(list); return NULL; } @@ -511,6 +547,7 @@ /** * @internal + * * @brief Get the host keys algorithms identifiers from the known_hosts files * * This expands the signatures types that can be generated from the keys types @@ -555,7 +592,7 @@ &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -564,7 +601,7 @@ &entry_list); SAFE_FREE(host_port); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -639,6 +676,7 @@ struct ssh_knownhosts_entry *e = NULL; char *known_host = NULL; char *p = NULL; + const char *cp = NULL; char *save_tok = NULL; enum ssh_keytypes_e key_type; int match = 0; @@ -772,9 +810,9 @@ /* comment */ p = strtok_r(NULL, " ", &save_tok); if (p != NULL) { - p = strstr(line, p); - if (p != NULL) { - e->comment = strdup(p); + cp = strstr(line, p); + if (cp != NULL) { + e->comment = strdup(cp); if (e->comment == NULL) { rc = SSH_ERROR; goto out; @@ -805,7 +843,6 @@ enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) { struct ssh_list *entry_list = NULL; - struct ssh_iterator *it = NULL; char *host_port = NULL; bool global_known_hosts_found = false; bool known_hosts_found = false; @@ -866,7 +903,7 @@ &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_ERROR; } } @@ -877,7 +914,7 @@ &entry_list); if (rc != 0) { SAFE_FREE(host_port); - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_ERROR; } } @@ -889,16 +926,7 @@ return SSH_KNOWN_HOSTS_UNKNOWN; } - for (it = ssh_list_get_iterator(entry_list); - it != NULL; - it = ssh_list_get_iterator(entry_list)) { - struct ssh_knownhosts_entry *entry = NULL; - - entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); - ssh_knownhosts_entry_free(entry); - ssh_list_remove(entry_list, it); - } - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_OK; } @@ -1085,13 +1113,13 @@ filename, &entry_list); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_UNKNOWN; } it = ssh_list_get_iterator(entry_list); if (it == NULL) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return SSH_KNOWN_HOSTS_UNKNOWN; } @@ -1121,16 +1149,7 @@ } } - for (it = ssh_list_get_iterator(entry_list); - it != NULL; - it = ssh_list_get_iterator(entry_list)) { - struct ssh_knownhosts_entry *entry = NULL; - - entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); - ssh_knownhosts_entry_free(entry); - ssh_list_remove(entry_list, it); - } - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return found; } @@ -1165,6 +1184,10 @@ { enum ssh_known_hosts_e old_rv, rv = SSH_KNOWN_HOSTS_UNKNOWN; + if (pentry != NULL) { + *pentry = NULL; + } + if (session->opts.knownhosts == NULL) { if (ssh_options_apply(session) < 0) { ssh_set_error(session, @@ -1202,6 +1225,8 @@ } /** + * @internal + * * @brief Get the known_hosts entry for the current connected session * from the given known_hosts file. * diff -Nru libssh-0.11.2/src/libcrypto.c libssh-0.11.5/src/libcrypto.c --- libssh-0.11.2/src/libcrypto.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/libcrypto.c 2026-07-16 12:57:23.000000000 +0200 @@ -49,8 +49,9 @@ #include #include #else -#include #include +#include +#include #endif /* OPENSSL_VERSION_NUMBER */ #include #if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) @@ -96,7 +97,37 @@ #endif } -#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +static OSSL_PROVIDER *provider = NULL; +static bool pkcs11_provider_failed = false; + +int pki_load_pkcs11_provider(void) +{ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 1) { + /* the provider is already available. + * Loaded through a configuration file? */ + return SSH_OK; + } + + if (pkcs11_provider_failed) { + /* the loading failed previously -- do not retry */ + return SSH_ERROR; + } + + provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (provider != NULL) { + return SSH_OK; + } + + SSH_LOG(SSH_LOG_TRACE, + "Failed to load the pkcs11 provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + return SSH_ERROR; +} +#else static ENGINE *engine = NULL; ENGINE *pki_get_engine(void) @@ -128,7 +159,8 @@ } return engine; } -#endif /* defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) */ +#endif /* defined(WITH_PKCS11_PROVIDER) */ +#endif /* defined(WITH_PKCS11_URI) */ #ifdef HAVE_OPENSSL_EVP_KDF_CTX #if OPENSSL_VERSION_NUMBER < 0x30000000L @@ -614,7 +646,7 @@ rc = EVP_EncryptFinal(cipher->ctx, NULL, &tmplen); - if (rc < 0) { + if (rc != 1) { SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); return; } @@ -702,7 +734,7 @@ rc = EVP_DecryptFinal(cipher->ctx, NULL, &outlen); - if (rc < 0) { + if (rc != 1 || outlen != 0) { SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptFinal failed: Failed authentication"); return SSH_ERROR; } @@ -1402,6 +1434,14 @@ engine = NULL; } #endif +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) + if (provider != NULL) { + OSSL_PROVIDER_unload(provider); + provider = NULL; + } +#endif /* WITH_PKCS11_PROVIDER */ +#endif /* WITH_PKCS11_URI */ libcrypto_initialized = 0; } diff -Nru libssh-0.11.2/src/match.c libssh-0.11.5/src/match.c --- libssh-0.11.2/src/match.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/match.c 2026-07-16 12:56:26.000000000 +0200 @@ -53,85 +53,70 @@ #include "libssh/priv.h" -#define MAX_MATCH_RECURSION 16 - -/* - * Returns true if the given string matches the pattern (which may contain ? - * and * as wildcards), and zero if it does not match. +/** + * @brief Compare a string with a pattern containing wildcards `*` and `?` + * + * This function is an iterative replacement for the previously recursive + * implementation to avoid exponential complexity (DoS) with specific patterns. + * + * @param[in] s The string to match. + * @param[in] pattern The pattern to match against. + * + * @return 1 if the pattern matches, 0 otherwise. */ -static int match_pattern(const char *s, const char *pattern, size_t limit) +static int match_pattern(const char *s, const char *pattern) { - bool had_asterisk = false; + const char *s_star = NULL; /* Position in s when last `*` was met */ + const char *p_star = NULL; /* Position in pattern after last `*` */ - if (s == NULL || pattern == NULL || limit <= 0) { + if (s == NULL || pattern == NULL) { return 0; } - for (;;) { - /* If at end of pattern, accept if also at end of string. */ - if (*pattern == '\0') { - return (*s == '\0'); - } - - /* Skip all the asterisks and adjacent question marks */ - while (*pattern == '*' || (had_asterisk && *pattern == '?')) { - if (*pattern == '*') { - had_asterisk = true; - } + while (*s) { + /* Case 1: Exact match or '?' wildcard */ + if (*pattern == *s || *pattern == '?') { + s++; pattern++; + continue; } - if (had_asterisk) { - /* If at end of pattern, accept immediately. */ - if (!*pattern) - return 1; - - /* If next character in pattern is known, optimize. */ - if (*pattern != '?') { - /* - * Look instances of the next character in - * pattern, and try to match starting from - * those. - */ - for (; *s; s++) - if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) { - return 1; - } - /* Failed. */ - return 0; - } - /* - * Move ahead one character at a time and try to - * match at each position. + /* Case 2: '*' wildcard */ + if (*pattern == '*') { + /* Record the position of the star and the current string position. + * We optimistically assume * matches 0 characters first. */ - for (; *s; s++) { - if (match_pattern(s, pattern, limit - 1)) { - return 1; - } - } - /* Failed. */ - return 0; - } - /* - * There must be at least one more character in the string. - * If we are at the end, fail. - */ - if (!*s) { - return 0; + p_star = ++pattern; + s_star = s; + continue; } - /* Check if the next character of the string is acceptable. */ - if (*pattern != '?' && *pattern != *s) { - return 0; + /* Case 3: Mismatch */ + if (p_star) { + /* If we have seen a star previously, backtrack. + * We restore the pattern to just after the star, + * but advance the string position (consume one more char for the + * star). + * No need to backtrack to previous stars as any match of the last + * star could be eaten the same way by the previous star. + */ + pattern = p_star; + s = ++s_star; + continue; } - /* Move to the next character, both in string and in pattern. */ - s++; + /* Case 4: Mismatch and no star to backtrack to */ + return 0; + } + + /* Handle trailing stars in the pattern + * (e.g., pattern "abc*" matching "abc") */ + while (*pattern == '*') { pattern++; } - /* NOTREACHED */ - return 0; + /* If we reached the end of the pattern, it's a match */ + return (*pattern == '\0'); } /* @@ -182,7 +167,7 @@ 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 -Nru libssh-0.11.2/src/messages.c libssh-0.11.5/src/messages.c --- libssh-0.11.2/src/messages.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/messages.c 2026-07-16 12:57:23.000000000 +0200 @@ -1203,10 +1203,21 @@ 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.11.2/src/misc.c libssh-0.11.5/src/misc.c --- libssh-0.11.2/src/misc.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/misc.c 2026-07-16 12:57:23.000000000 +0200 @@ -37,6 +37,7 @@ #endif /* _WIN32 */ #include +#include #include #include #include @@ -459,7 +460,7 @@ 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; } @@ -1924,7 +1925,7 @@ */ char *ssh_strreplace(const char *src, const char *pattern, const char *replace) { - char *p = NULL; + const char *p = NULL; char *src_replaced = NULL; if (src == NULL) { @@ -2191,7 +2192,7 @@ username_len = strlen(username); if (username_len == 0 || username[username_len - 1] == '\\' || - strpbrk(username, "'`\";&<>|(){}") != NULL) { + strpbrk(username, SSH_DANGEROUS_SHELL_CHARS) != NULL) { return SSH_ERROR; } for (size_t i = 0; i < username_len; i++) { @@ -2244,4 +2245,77 @@ return !(t != NULL && t[0] == '1'); } +/** + * @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; +} + /** @} */ diff -Nru libssh-0.11.2/src/options.c libssh-0.11.5/src/options.c --- libssh-0.11.2/src/options.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/options.c 2026-07-16 12:56:26.000000000 +0200 @@ -861,7 +861,7 @@ SAFE_FREE(session->opts.global_knownhosts); if (v == NULL) { session->opts.global_knownhosts = - strdup("/etc/ssh/ssh_known_hosts"); + strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); if (session->opts.global_knownhosts == NULL) { ssh_set_error_oom(session); return -1; @@ -1900,7 +1900,7 @@ if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS) == 0) { if (session->opts.global_knownhosts == NULL) { - tmp = strdup("/etc/ssh/ssh_known_hosts"); + tmp = strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); } else { tmp = ssh_path_expand_escape(session, session->opts.global_knownhosts); diff -Nru libssh-0.11.2/src/packet.c libssh-0.11.5/src/packet.c --- libssh-0.11.2/src/packet.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/packet.c 2026-07-16 12:56:26.000000000 +0200 @@ -294,6 +294,7 @@ * 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 @@ 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; @@ -1711,6 +1713,7 @@ if (rc != SSH_OK) { SSH_LOG(SSH_LOG_TRACE, "Could not unpack SSH_MSG_UNIMPLEMENTED packet"); + return SSH_PACKET_USED; } SSH_LOG(SSH_LOG_RARE, diff -Nru libssh-0.11.2/src/pki.c libssh-0.11.5/src/pki.c --- libssh-0.11.2/src/pki.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/pki.c 2026-07-16 12:56:26.000000000 +0200 @@ -61,7 +61,7 @@ enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey) { - char *start = NULL; + const char *start = NULL; start = strstr(privkey, RSA_HEADER_BEGIN); if (start != NULL) { @@ -701,8 +701,8 @@ ssh_buffer_get_len(k1->cert)); } - if (k1->type == SSH_KEYTYPE_ED25519 || - k1->type == SSH_KEYTYPE_SK_ED25519) { + if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_ED25519 || + ssh_key_type_plain(k1->type) == SSH_KEYTYPE_SK_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); } @@ -800,7 +800,7 @@ ssh_key *pkey) { ssh_key key = NULL; - char *openssh_header = NULL; + const char *openssh_header = NULL; if (b64_key == NULL || pkey == NULL) { return SSH_ERROR; @@ -1621,14 +1621,16 @@ /** * @brief Import a base64 formatted public key from a memory c-string. * - * @param[in] b64_key The base64 key to format. - * - * @param[in] type The type of the key to format. + * Note that the public key is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH public + * key file or known_hosts file). * + * @param[in] b64_key The base64 key to import. + * @param[in] type The type of the key to import. * @param[out] pkey A pointer where the allocated key can be stored. You * need to free the memory using ssh_key_free(). * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * * @see ssh_key_free() */ @@ -1929,14 +1931,16 @@ /** * @brief Import a base64 formatted certificate from a memory c-string. * - * @param[in] b64_cert The base64 cert to format. - * - * @param[in] type The type of the cert to format. + * Note that the certificate is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH certificate + * file). + * + * @param[in] b64_cert The base64 cert to import. + * @param[in] type The type of the cert to import. + * @param[out] pkey A pointer where the allocated certificate can be stored. + * You need to free the memory using ssh_key_free(). * - * @param[out] pkey A pointer where the allocated key can be stored. You - * need to free the memory using ssh_key_free(). - * - * @return SSH_OK on success, SSH_ERROR on error. + * @return `SSH_OK` on success, `SSH_ERROR` on error. * * @see ssh_key_free() */ diff -Nru libssh-0.11.2/src/pki_crypto.c libssh-0.11.5/src/pki_crypto.c --- libssh-0.11.2/src/pki_crypto.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/pki_crypto.c 2026-07-16 12:56:26.000000000 +0200 @@ -46,7 +46,6 @@ #include #if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) #include -#include #endif #endif /* OPENSSL_VERSION_NUMBER */ @@ -1429,6 +1428,8 @@ if (buffer == NULL) { return NULL; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); if (key->cert != NULL) { rc = ssh_buffer_add_buffer(buffer, key->cert); @@ -2719,9 +2720,6 @@ } #ifdef WITH_PKCS11_URI -#ifdef WITH_PKCS11_PROVIDER -static bool pkcs11_provider_failed = false; -#endif /** * @internal @@ -2787,19 +2785,10 @@ /* The provider can be either configured in openssl.cnf or dynamically * loaded, assuming it does not need any special configuration */ - if (OSSL_PROVIDER_available(NULL, "pkcs11") == 0 && - !pkcs11_provider_failed) { - OSSL_PROVIDER *pkcs11_provider = NULL; - - pkcs11_provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); - if (pkcs11_provider == NULL) { - SSH_LOG(SSH_LOG_TRACE, - "Failed to initialize provider: %s", - ERR_error_string(ERR_get_error(), NULL)); - /* Do not attempt to load it again */ - pkcs11_provider_failed = true; - goto fail; - } + rv = pki_load_pkcs11_provider(); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to load or initialize pkcs11 provider"); + goto fail; } store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); diff -Nru libssh-0.11.2/src/pki_gcrypt.c libssh-0.11.5/src/pki_gcrypt.c --- libssh-0.11.2/src/pki_gcrypt.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/pki_gcrypt.c 2026-07-16 12:56:26.000000000 +0200 @@ -1355,7 +1355,7 @@ case SSH_KEYTYPE_SK_ED25519: case SSH_KEYTYPE_SK_ED25519_CERT01: /* ed25519 keys handled globally */ - return 0; + return 1; case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384: @@ -1409,6 +1409,8 @@ if (buffer == NULL) { return NULL; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); if (key->cert != NULL) { rc = ssh_buffer_add_buffer(buffer, key->cert); diff -Nru libssh-0.11.2/src/pki_mbedcrypto.c libssh-0.11.5/src/pki_mbedcrypto.c --- libssh-0.11.2/src/pki_mbedcrypto.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/pki_mbedcrypto.c 2026-07-16 12:56:26.000000000 +0200 @@ -782,7 +782,7 @@ case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: /* ed25519 keys handled globally */ - rc = 0; + rc = 1; break; default: rc = 1; @@ -864,7 +864,12 @@ ssh_string type_s = NULL; ssh_string e = NULL; ssh_string n = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; ssh_string str = NULL; + int rc; #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi E = {0}; mbedtls_mpi N = {0}; @@ -872,18 +877,21 @@ mbedtls_mpi IQMP = {0}; mbedtls_mpi P = {0}; mbedtls_mpi Q = {0}; -#endif - int rc; -#if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi_init(&E); mbedtls_mpi_init(&N); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&IQMP); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); #endif buffer = ssh_buffer_new(); if (buffer == NULL) { return NULL; } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); if (key->cert != NULL) { rc = ssh_buffer_add_buffer(buffer, key->cert); @@ -909,279 +917,241 @@ } switch (key->type) { - case SSH_KEYTYPE_RSA: { - mbedtls_rsa_context *rsa; - if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) == 0) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa = NULL; + mbedtls_mpi *E_ptr = NULL, *N_ptr = NULL; - rsa = mbedtls_pk_rsa(*key->pk); + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) == 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + rsa = mbedtls_pk_rsa(*key->pk); #if MBEDTLS_VERSION_MAJOR > 2 - rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); - if (rc != 0) { - goto fail; - } - - e = ssh_make_bignum_string(&E); - if (e == NULL) { - goto fail; - } + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto out; + } - n = ssh_make_bignum_string(&N); - if (n == NULL) { - goto fail; - } + E_ptr = &E; + N_ptr = &N; #else - e = ssh_make_bignum_string(&rsa->E); - if (e == NULL) { - goto fail; - } - - n = ssh_make_bignum_string(&rsa->N); - if (n == NULL) { - goto fail; - } + E_ptr = &rsa->E; + N_ptr = &rsa->N; #endif - if (type == SSH_KEY_PUBLIC) { - /* The N and E parts are swapped in the public key export ! */ - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } - - rc = ssh_buffer_add_ssh_string(buffer, n); - if (rc < 0) { - goto fail; - } - } else if (type == SSH_KEY_PRIVATE) { - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string d = NULL; - ssh_string iqmp = NULL; + e = ssh_make_bignum_string(E_ptr); + if (e == NULL) { + goto out; + } - rc = ssh_buffer_add_ssh_string(buffer, n); - if (rc < 0) { - goto fail; - } + n = ssh_make_bignum_string(N_ptr); + if (n == NULL) { + goto out; + } - rc = ssh_buffer_add_ssh_string(buffer, e); - if (rc < 0) { - goto fail; - } + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } -#if MBEDTLS_VERSION_MAJOR > 2 - rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); - if (rc != 0) { - goto fail; - } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; + } + } else if (type == SSH_KEY_PRIVATE) { + mbedtls_mpi *P_ptr = NULL, *Q_ptr = NULL, *D_ptr = NULL; + mbedtls_mpi *IQMP_ptr = NULL; - p = ssh_make_bignum_string(&P); - if (p == NULL) { - goto fail; - } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; + } - q = ssh_make_bignum_string(&Q); - if (q == NULL) { - goto fail; - } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } - d = ssh_make_bignum_string(&D); - if (d == NULL) { - goto fail; - } - rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); - if (rc != 0) { - goto fail; - } +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto out; + } - iqmp = ssh_make_bignum_string(&IQMP); - if (iqmp == NULL) { - goto fail; - } + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); + if (rc != 0) { + goto out; + } + P_ptr = &P; + Q_ptr = &Q; + D_ptr = &D; + IQMP_ptr = &IQMP; #else - p = ssh_make_bignum_string(&rsa->P); - if (p == NULL) { - goto fail; - } - - q = ssh_make_bignum_string(&rsa->Q); - if (q == NULL) { - goto fail; - } - - d = ssh_make_bignum_string(&rsa->D); - if (d == NULL) { - goto fail; - } - - iqmp = ssh_make_bignum_string(&rsa->QP); - if (iqmp == NULL) { - goto fail; - } + P_ptr = &rsa->P; + Q_ptr = &rsa->Q; + D_ptr = &rsa->D; + IQMP_ptr = &rsa->QP; #endif - rc = ssh_buffer_add_ssh_string(buffer, d); - if (rc < 0) { - goto fail; - } - - rc = ssh_buffer_add_ssh_string(buffer, iqmp); - if (rc < 0) { - goto fail; - } + p = ssh_make_bignum_string(P_ptr); + if (p == NULL) { + goto out; + } - rc = ssh_buffer_add_ssh_string(buffer, p); - if (rc < 0) { - goto fail; - } + q = ssh_make_bignum_string(Q_ptr); + if (q == NULL) { + goto out; + } - rc = ssh_buffer_add_ssh_string(buffer, q); - if (rc < 0) { - goto fail; - } + d = ssh_make_bignum_string(D_ptr); + if (d == NULL) { + goto out; + } - ssh_string_burn(d); - SSH_STRING_FREE(d); - d = NULL; - ssh_string_burn(iqmp); - SSH_STRING_FREE(iqmp); - iqmp = NULL; - ssh_string_burn(p); - SSH_STRING_FREE(p); - p = NULL; - ssh_string_burn(q); - SSH_STRING_FREE(q); - q = NULL; - } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; - ssh_string_burn(n); - SSH_STRING_FREE(n); - n = NULL; - break; - } - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: - case SSH_KEYTYPE_SK_ECDSA: - type_s = - ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); - if (type_s == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + iqmp = ssh_make_bignum_string(IQMP_ptr); + if (iqmp == NULL) { + goto out; } - rc = ssh_buffer_add_ssh_string(buffer, type_s); - SSH_STRING_FREE(type_s); + rc = ssh_buffer_add_ssh_string(buffer, d); if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto out; } - e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), - &key->ecdsa->MBEDTLS_PRIVATE(Q)); + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto out; + } - if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto out; } - rc = ssh_buffer_add_ssh_string(buffer, e); + rc = ssh_buffer_add_ssh_string(buffer, q); if (rc < 0) { - goto fail; + goto out; } + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + type_s = + ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - ssh_string_burn(e); - SSH_STRING_FREE(e); - e = NULL; + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - if (type == SSH_KEY_PRIVATE) { - ssh_string d = NULL; - d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); - if (d == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; - } + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } - rc = ssh_buffer_add_ssh_string(buffer, d); - if (rc < 0) { - goto fail; - } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + + if (type == SSH_KEY_PRIVATE) { + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + goto out; + } - ssh_string_burn(d); - SSH_STRING_FREE(d); - d = NULL; - } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { - /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto out; + } + } else if (key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto out; + } + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); if (rc < 0) { - goto fail; + goto out; } - } - break; - case SSH_KEYTYPE_ED25519: - case SSH_KEYTYPE_SK_ED25519: - if (type == SSH_KEY_PUBLIC) { - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc == SSH_ERROR) { - goto fail; - } - /* public key can contain certificate sk information */ - if (key->type == SSH_KEYTYPE_SK_ED25519) { - rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); - if (rc < 0) { - goto fail; - } - } - } else { - rc = pki_ed25519_private_key_to_blob(buffer, key); - if (rc == SSH_ERROR) { - goto fail; - } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; } - break; - default: - goto fail; + } + break; + default: + goto out; } makestring: str = ssh_string_new(ssh_buffer_get_len(buffer)); if (str == NULL) { - goto fail; + goto out; } - rc = ssh_string_fill(str, ssh_buffer_get(buffer), - ssh_buffer_get_len(buffer)); + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); if (rc < 0) { - goto fail; + ssh_string_burn(str); + SSH_STRING_FREE(str); } +out: SSH_BUFFER_FREE(buffer); -#if MBEDTLS_VERSION_MAJOR > 2 - mbedtls_mpi_free(&N); - mbedtls_mpi_free(&E); -#endif - return str; -fail: - SSH_BUFFER_FREE(buffer); - ssh_string_burn(str); - SSH_STRING_FREE(str); ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi_free(&N); mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&IQMP); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); #endif - return NULL; + return str; } ssh_string pki_signature_to_blob(const ssh_signature sig) diff -Nru libssh-0.11.2/src/poll.c libssh-0.11.5/src/poll.c --- libssh-0.11.2/src/poll.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/poll.c 2026-07-16 12:56:26.000000000 +0200 @@ -422,7 +422,7 @@ { p->events = events; if (p->ctx != NULL) { - if (p->lock_cnt == 0) { + if (!ssh_poll_is_locked(p)) { p->ctx->pollfds[p->x.idx].events = events; } else if (!(p->ctx->pollfds[p->x.idx].events & POLLOUT)) { /* if locked, allow only setting POLLOUT to prevent recursive @@ -670,6 +670,20 @@ } /** + * @brief Returns if a poll object is locked. + * + * @param p Pointer to an already allocated poll object. + * @returns true if the poll object is locked; false otherwise. + */ +bool ssh_poll_is_locked(ssh_poll_handle p) +{ + if (p == NULL) { + return false; + } + return p->lock_cnt > 0; +} + +/** * @brief Poll all the sockets associated through a poll object with a * poll context. If any of the events are set after the poll, the * call back function of the socket will be called. @@ -703,7 +717,7 @@ * output buffer */ for (i = 0; i < ctx->polls_used; i++) { /* The lock allows only POLLOUT events: drop the rest */ - if (ctx->pollptrs[i]->lock_cnt > 0) { + if (ssh_poll_is_locked(ctx->pollptrs[i])) { ctx->pollfds[i].events &= POLLOUT; } } diff -Nru libssh-0.11.2/src/scp.c libssh-0.11.5/src/scp.c --- libssh-0.11.2/src/scp.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/scp.c 2026-07-16 12:56:26.000000000 +0200 @@ -862,6 +862,22 @@ 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 ? "" : name); + SAFE_FREE(name); + goto error; + } SAFE_FREE(scp->request_name); scp->request_name = name; if (buffer[0] == 'C') { diff -Nru libssh-0.11.2/src/server.c libssh-0.11.5/src/server.c --- libssh-0.11.2/src/server.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/server.c 2026-07-16 12:56:26.000000000 +0200 @@ -495,6 +495,11 @@ buffer[i] = '\0'; str = strdup(buffer); + if (str == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error_oom(session); + return 0; + } /* number of bytes read */ processed = i + 1; session->clientbanner = str; @@ -523,6 +528,7 @@ ssh_session session = s; if (session->session_state != SSH_SESSION_STATE_ERROR && session->session_state != SSH_SESSION_STATE_AUTHENTICATING && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED && session->session_state != SSH_SESSION_STATE_DISCONNECTED) return 0; else diff -Nru libssh-0.11.2/src/sftp.c libssh-0.11.5/src/sftp.c --- libssh-0.11.2/src/sftp.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/sftp.c 2026-07-16 12:57:23.000000000 +0200 @@ -140,6 +140,12 @@ 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; } @@ -156,6 +162,7 @@ if (sftp->channel != NULL) { ssh_channel_free(sftp->channel); } + ssh_list_free(sftp->outstanding_ids); if (sftp->read_packet != NULL) { if (sftp->read_packet->payload != NULL) { SSH_BUFFER_FREE(sftp->read_packet->payload); @@ -187,6 +194,12 @@ goto error; } + sftp->outstanding_ids = ssh_list_new(); + if (sftp->outstanding_ids == NULL) { + ssh_set_error_oom(session); + goto error; + } + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); if (sftp->read_packet == NULL) { ssh_set_error_oom(session); @@ -208,6 +221,7 @@ if (sftp->ext != NULL) { sftp_ext_free(sftp->ext); } + ssh_list_free(sftp->outstanding_ids); if (sftp->read_packet != NULL) { if (sftp->read_packet->payload != NULL) { SSH_BUFFER_FREE(sftp->read_packet->payload); @@ -321,7 +335,8 @@ void sftp_free(sftp_session sftp) { - sftp_request_queue ptr; + sftp_request_queue ptr = NULL; + struct ssh_iterator *id_it = NULL; if (sftp == NULL) { return; @@ -349,6 +364,12 @@ sftp_ext_free(sftp->ext); sftp_limits_free(sftp->limits); + 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); + SAFE_FREE(sftp); } @@ -567,44 +588,51 @@ return 0; } -unsigned int sftp_extensions_get_count(sftp_session sftp) { - if (sftp == NULL || sftp->ext == NULL) { - return 0; - } +unsigned int sftp_extensions_get_count(sftp_session sftp) +{ + if (sftp == NULL || sftp->ext == NULL) { + return 0; + } - return sftp->ext->count; + return sftp->ext->count; } -const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) { - if (sftp == NULL) - return NULL; - if (sftp->ext == NULL || sftp->ext->name == NULL) { - ssh_set_error_invalid(sftp->session); - return NULL; - } +const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) +{ + if (sftp == NULL) { + return NULL; + } - if (idx > sftp->ext->count) { - ssh_set_error_invalid(sftp->session); - return NULL; - } + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } - return sftp->ext->name[idx]; + return sftp->ext->name[idx]; } -const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) { - if (sftp == NULL) - return NULL; - if (sftp->ext == NULL || sftp->ext->name == NULL) { - ssh_set_error_invalid(sftp->session); - return NULL; - } +const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) +{ + if (sftp == NULL) { + return NULL; + } - if (idx > sftp->ext->count) { - ssh_set_error_invalid(sftp->session); - return NULL; - } + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } - return sftp->ext->data[idx]; + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->data[idx]; } int sftp_extension_supported(sftp_session sftp, const char *name, @@ -677,6 +705,11 @@ return NULL; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + payload = ssh_buffer_new(); if (payload == NULL) { ssh_set_error_oom(sftp->session); @@ -684,8 +717,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(payload, "ds", id, @@ -771,6 +802,11 @@ int rc; if (dir->buffer == NULL) { + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + payload = ssh_buffer_new(); if (payload == NULL) { ssh_set_error_oom(sftp->session); @@ -778,8 +814,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(payload, "dS", id, @@ -904,6 +938,11 @@ uint32_t id; int rc; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -911,8 +950,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dS", id, @@ -1019,6 +1056,11 @@ uint32_t id; int rc; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1047,7 +1089,6 @@ } SSH_LOG(SSH_LOG_PACKET, "Opening file %s with sftp flags %" PRIx32, file, sftp_flags); - id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dsd", @@ -1179,7 +1220,10 @@ return -1; } - id = sftp_get_new_id(handle->sftp); + rc = sftp_get_new_id(handle->sftp, &id); + if (rc != SSH_OK) { + return -1; + } rc = ssh_buffer_pack(buffer, "dSqd", @@ -1280,7 +1324,10 @@ return -1; } - id = sftp_get_new_id(sftp); + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } rc = ssh_buffer_pack(buffer, "dSqd", @@ -1411,7 +1458,10 @@ return -1; } - id = sftp_get_new_id(file->sftp); + rc = sftp_get_new_id(file->sftp, &id); + if (rc != SSH_OK) { + return -1; + } /* * limit the writes to the maximum specified in Section 3 of @@ -1534,6 +1584,11 @@ uint32_t id; int rc; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1541,8 +1596,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -1609,6 +1662,11 @@ uint32_t id; int rc; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1616,8 +1674,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -1682,6 +1738,11 @@ uint32_t id; int rc; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1693,8 +1754,6 @@ attr.permissions = mode; attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -1784,6 +1843,11 @@ int request_type; int rc; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1791,8 +1855,6 @@ return -1; } - id = sftp_get_new_id(sftp); - /* * posix-rename@openssh.com extension will be used * if it is supported by sftp @@ -1899,6 +1961,11 @@ return -1; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1906,8 +1973,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -1987,6 +2052,11 @@ return -1; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1994,8 +2064,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dss", id, extension_name, file); if (rc != SSH_OK) { ssh_set_error_oom(sftp->session); @@ -2119,6 +2187,11 @@ return -1; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2126,8 +2199,6 @@ return -1; } - id = sftp_get_new_id(sftp); - /* The OpenSSH sftp server has order of the arguments reversed, see the * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL @@ -2220,6 +2291,12 @@ sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2227,8 +2304,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -2310,6 +2385,11 @@ return -1; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2317,8 +2397,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dsss", id, @@ -2434,6 +2512,11 @@ return NULL; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2441,8 +2524,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dss", id, @@ -2509,6 +2590,11 @@ } sftp = file->sftp; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2516,8 +2602,6 @@ return -1; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dsS", id, @@ -2609,6 +2693,11 @@ } sftp = file->sftp; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2616,8 +2705,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dsS", id, @@ -2723,6 +2810,11 @@ if (sftp == NULL) return NULL; + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2730,8 +2822,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -2873,6 +2963,11 @@ return NULL; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2880,8 +2975,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -2964,6 +3057,11 @@ return NULL; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -2971,8 +3069,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "ds", id, @@ -3042,6 +3138,11 @@ return NULL; } + rc = sftp_get_new_id(file->sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(file->sftp->session); @@ -3049,8 +3150,6 @@ return NULL; } - id = sftp_get_new_id(file->sftp); - rc = ssh_buffer_pack(buffer, "dS", id, @@ -3121,6 +3220,11 @@ return NULL; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -3128,8 +3232,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dss", id, @@ -3208,6 +3310,11 @@ return NULL; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -3215,8 +3322,6 @@ return NULL; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dss", id, diff -Nru libssh-0.11.2/src/sftp_aio.c libssh-0.11.5/src/sftp_aio.c --- libssh-0.11.2/src/sftp_aio.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/sftp_aio.c 2026-07-16 12:57:23.000000000 +0200 @@ -86,6 +86,11 @@ return SSH_ERROR; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return SSH_ERROR; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -93,8 +98,6 @@ return SSH_ERROR; } - id = sftp_get_new_id(sftp); - rc = ssh_buffer_pack(buffer, "dSqd", id, @@ -354,6 +357,11 @@ return SSH_ERROR; } + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return SSH_ERROR; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -361,7 +369,6 @@ return SSH_ERROR; } - id = sftp_get_new_id(sftp); rc = ssh_buffer_pack(buffer, "dSqdP", id, diff -Nru libssh-0.11.2/src/sftp_common.c libssh-0.11.5/src/sftp_common.c --- libssh-0.11.2/src/sftp_common.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/sftp_common.c 2026-07-16 12:57:23.000000000 +0200 @@ -458,19 +458,24 @@ static char * sftp_parse_longname(const char *longname, enum sftp_longname_field_e longname_field) { - const char *p, *q; + const char *p = NULL, *q = NULL; 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 { @@ -478,8 +483,13 @@ } } + /* 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++; } @@ -546,17 +556,14 @@ if (rc != SSH_OK){ goto error; } - SSH_LOG(SSH_LOG_DEBUG, - "Flags: %.8" PRIx32 "\n", attr->flags); + SSH_LOG(SSH_LOG_DEBUG, "Flags: %.8" PRIx32, attr->flags); if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { rc = ssh_buffer_unpack(buf, "q", &attr->size); if(rc != SSH_OK) { goto error; } - SSH_LOG(SSH_LOG_DEBUG, - "Size: %" PRIu64 "\n", - (uint64_t) attr->size); + SSH_LOG(SSH_LOG_DEBUG, "Size: %" PRIu64, (uint64_t)attr->size); } if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { @@ -785,6 +792,8 @@ { sftp_session sftp = packet->sftp; sftp_message msg = NULL; + struct ssh_iterator *id_it = NULL; + bool id_found = false; int rc; switch (packet->type) { @@ -832,9 +841,63 @@ 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; } +int sftp_get_new_id(sftp_session sftp, uint32_t *id_out) +{ + uint32_t *id = NULL; + int rc; + + if (id_out == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + 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; +} + int sftp_read_and_dispatch(sftp_session sftp) { sftp_packet packet = NULL; diff -Nru libssh-0.11.2/src/sftpserver.c libssh-0.11.5/src/sftpserver.c --- libssh-0.11.2/src/sftpserver.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/sftpserver.c 2026-07-16 12:57:23.000000000 +0200 @@ -109,7 +109,6 @@ printf("unpack init failed!\n"); goto error; } - version = ntohl(version); sftp->client_version = version; break; case SSH_FXP_CLOSE: @@ -128,6 +127,13 @@ if (rc != SSH_OK) { goto error; } + if (msg->len > MAX_PACKET_LEN - 1024) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Too large SSH_FXP_READ length: %" PRIu32, + msg->len); + goto error; + } break; case SSH_FXP_WRITE: rc = ssh_buffer_unpack(payload, @@ -302,6 +308,18 @@ return msg->filename; } +/** + * @brief Set the filename associated with an SFTP client message. + * + * Replaces the current filename stored in the client message with a copy + * of the given @p newname string. + * + * @param[in] msg The SFTP client message to modify. + * @param[in] newname The new filename to store in the message. + * + * @warn On failure, the filename in the message is set to `NULL`. Users of + * sftp_client_message_get_filename() need to check the return value! + */ void sftp_client_message_set_filename(sftp_client_message msg, const char *newname) { @@ -605,17 +623,32 @@ ssh_buffer reply; int rc; + /* The SSH_FXP_INIT can be received only once -- repeated initialization + * is not supported */ + if (sftp->version > 0) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received duplicate INIT message"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet"); - version = sftp->client_version; + /* from draft-spaghetti-sshm-filexfer-00 Section 4: + * > The server responds with a SSH_FXP_VERSION packet, supplying the + * > lowest of its own and the client's version number. + */ + version = MIN(sftp->client_version, LIBSFTP_VERSION); + reply = ssh_buffer_new(); if (reply == NULL) { ssh_set_error_oom(session); return -1; } - rc = ssh_buffer_pack(reply, "dssssss", - LIBSFTP_VERSION, + rc = ssh_buffer_pack(reply, + "dssssss", + version, "posix-rename@openssh.com", "1", "hardlink@openssh.com", @@ -637,11 +670,7 @@ SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); - if (version > LIBSFTP_VERSION) { - sftp->version = LIBSFTP_VERSION; - } else { - sftp->version = version; - } + sftp->version = version; return SSH_OK; } @@ -879,6 +908,12 @@ int fd = -1; int status; + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing open: filename %s, mode=0%o" PRIu32, filename, mode); @@ -908,7 +943,7 @@ strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, "Write error"); - return SSH_ERROR; + return SSH_OK; } h = calloc(1, sizeof (struct sftp_handle)); @@ -967,7 +1002,7 @@ SSH_LOG(SSH_LOG_PROTOCOL, "error seeking file fd: %d at offset: %" PRIu64, fd, client_msg->offset); - return SSH_ERROR; + return SSH_OK; } buffer = malloc(client_msg->len); @@ -982,7 +1017,7 @@ sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); SSH_LOG(SSH_LOG_PROTOCOL, "read file error!"); free(buffer); - return SSH_ERROR; + return SSH_OK; } else if (readn > 0) { sftp_reply_data(client_msg, buffer, readn); } else { @@ -1028,13 +1063,13 @@ SSH_LOG(SSH_LOG_PROTOCOL, "error seeking file at offset: %" PRIu64, client_msg->offset); - return SSH_ERROR; + return SSH_OK; } written = ssh_writen(fd, msg_data, len); if (written != (ssize_t)len) { sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error"); SSH_LOG(SSH_LOG_PROTOCOL, "file write error!"); - return SSH_ERROR; + return SSH_OK; } sftp_reply_status(client_msg, SSH_FX_OK, NULL); @@ -1081,7 +1116,7 @@ sftp_reply_status(client_msg, SSH_FX_BAD_MESSAGE, "Invalid handle"); } - return SSH_OK; + return ret; } static int @@ -1092,12 +1127,18 @@ ssh_string handle_s = NULL; struct sftp_handle *h = NULL; + if (dir_name == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing dir_name from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing opendir %s", dir_name); dir = opendir(dir_name); if (dir == NULL) { sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "No such directory"); - return SSH_ERROR; + return SSH_OK; } h = calloc(1, sizeof (struct sftp_handle)); @@ -1110,6 +1151,15 @@ } h->dirp = dir; h->name = strdup(dir_name); + if (h->name == NULL) { + free(h); + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to duplicate directory name"); + sftp_reply_status(client_msg, + SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } h->type = SFTP_DIR_HANDLE; handle_s = sftp_handle_alloc(client_msg->sftp, h); @@ -1117,6 +1167,7 @@ sftp_reply_handle(client_msg, handle_s); ssh_string_free(handle_s); } else { + SAFE_FREE(h->name); free(h); closedir(dir); sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available"); @@ -1128,9 +1179,8 @@ static int readdir_long_name(char *z_file_name, struct stat *z_st, char *z_long_name) { - char tmpbuf[MAX_LONG_NAME_LEN]; char time[50]; - char *ptr = z_long_name; + char *ptr = z_long_name, *nl = NULL; int mode = z_st->st_mode; *ptr = '\0'; @@ -1194,16 +1244,19 @@ *ptr++ = ' '; *ptr = '\0'; - snprintf(tmpbuf, sizeof(tmpbuf), "%3d %d %d %d", (int)z_st->st_nlink, - (int)z_st->st_uid, (int)z_st->st_gid, (int)z_st->st_size); - strcat(z_long_name, tmpbuf); - ctime_r(&z_st->st_mtime, time); - if ((ptr = strchr(time, '\n'))) { - *ptr = '\0'; + if ((nl = strchr(time, '\n'))) { + *nl = '\0'; } - snprintf(tmpbuf, sizeof(tmpbuf), " %s %s", time + 4, z_file_name); - strcat(z_long_name, tmpbuf); + snprintf(ptr, + MAX_LONG_NAME_LEN - strlen(z_long_name), + "%3d %d %d %d %s %s", + (int)z_st->st_nlink, + (int)z_st->st_uid, + (int)z_st->st_gid, + (int)z_st->st_size, + time + 4, + z_file_name); return SSH_OK; } @@ -1295,7 +1348,6 @@ static int process_mkdir(sftp_client_message client_msg) { - int ret = SSH_OK; const char *filename = sftp_client_message_get_filename(client_msg); uint32_t msg_flags = client_msg->attr->flags; uint32_t permission = client_msg->attr->permissions; @@ -1304,51 +1356,50 @@ int status = SSH_FX_OK; int rv; - SSH_LOG(SSH_LOG_PROTOCOL, "Processing mkdir %s, mode=0%o" PRIu32, - filename, mode); - if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing mkdir %s, mode=0%o" PRIu32, + filename, mode); + rv = mkdir(filename, mode); if (rv < 0) { int saved_errno = errno; SSH_LOG(SSH_LOG_PROTOCOL, "failed to mkdir: %s", strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); - ret = SSH_ERROR; } sftp_reply_status(client_msg, status, NULL); - return ret; + return SSH_OK; } static int process_rmdir(sftp_client_message client_msg) { - int ret = SSH_OK; const char *filename = sftp_client_message_get_filename(client_msg); int status = SSH_FX_OK; int rv; - SSH_LOG(SSH_LOG_PROTOCOL, "Processing rmdir %s", filename); - if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing rmdir %s", filename); + rv = rmdir(filename); if (rv < 0) { status = unix_errno_to_ssh_stat(errno); - ret = SSH_ERROR; } sftp_reply_status(client_msg, status, NULL); - return ret; + return SSH_OK; } static int @@ -1357,6 +1408,12 @@ const char *filename = sftp_client_message_get_filename(client_msg); char *path = NULL; + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing realpath %s", filename); if (filename[0] == '\0') { @@ -1381,83 +1438,81 @@ static int process_lstat(sftp_client_message client_msg) { - int ret = SSH_OK; const char *filename = sftp_client_message_get_filename(client_msg); struct sftp_attributes_struct attr; struct stat st; int status = SSH_FX_OK; int rv; - SSH_LOG(SSH_LOG_PROTOCOL, "Processing lstat %s", filename); - if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing lstat %s", filename); + rv = lstat(filename, &st); if (rv < 0) { int saved_errno = errno; SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - ret = SSH_ERROR; } else { stat_to_filexfer_attrib(&st, &attr); sftp_reply_attr(client_msg, &attr); } - return ret; + return SSH_OK; } static int process_stat(sftp_client_message client_msg) { - int ret = SSH_OK; const char *filename = sftp_client_message_get_filename(client_msg); struct sftp_attributes_struct attr; struct stat st; int status = SSH_FX_OK; int rv; - SSH_LOG(SSH_LOG_PROTOCOL, "Processing stat %s", filename); - if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing stat %s", filename); + rv = stat(filename, &st); if (rv < 0) { int saved_errno = errno; SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - ret = SSH_ERROR; } else { stat_to_filexfer_attrib(&st, &attr); sftp_reply_attr(client_msg, &attr); } - return ret; + return SSH_OK; } static int process_setstat(sftp_client_message client_msg) { int rv; - int ret = SSH_OK; int status = SSH_FX_OK; uint32_t msg_flags = client_msg->attr->flags; const char *filename = sftp_client_message_get_filename(client_msg); - SSH_LOG(SSH_LOG_PROTOCOL, "Processing setstat %s", filename); - if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing setstat %s", filename); + if (msg_flags & SSH_FILEXFER_ATTR_SIZE) { rv = truncate(filename, client_msg->attr->size); if (rv < 0) { @@ -1467,7 +1522,7 @@ strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - return rv; + return SSH_OK; } } @@ -1480,7 +1535,7 @@ strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - return rv; + return SSH_OK; } } @@ -1493,7 +1548,7 @@ strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - return rv; + return SSH_OK; } } @@ -1514,7 +1569,7 @@ strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - return rv; + return SSH_OK; } #else struct _utimbuf tf; @@ -1530,32 +1585,32 @@ strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - return rv; + return SSH_OK; } #endif } sftp_reply_status(client_msg, status, NULL); - return ret; + return SSH_OK; } static int process_readlink(sftp_client_message client_msg) { - int ret = SSH_OK; const char *filename = sftp_client_message_get_filename(client_msg); char buf[PATH_MAX]; int len = -1; const char *err_msg = NULL; int status = SSH_FX_OK; - SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); - if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); + len = readlink(filename, buf, sizeof(buf) - 1); if (len < 0) { int saved_errno = errno; @@ -1563,13 +1618,12 @@ status = unix_errno_to_ssh_stat(saved_errno); err_msg = ssh_str_error(status); sftp_reply_status(client_msg, status, err_msg); - ret = SSH_ERROR; } else { buf[len] = '\0'; sftp_reply_name(client_msg, buf, NULL); } - return ret; + return SSH_OK; } /* Note, that this function is using reversed order of the arguments than the @@ -1580,42 +1634,46 @@ static int process_symlink(sftp_client_message client_msg) { - int ret = SSH_OK; const char *destpath = sftp_client_message_get_filename(client_msg); const char *srcpath = ssh_string_get_char(client_msg->data); int status = SSH_FX_OK; int rv; - SSH_LOG(SSH_LOG_PROTOCOL, "processing symlink: src=%s dest=%s", - srcpath, destpath); - if (srcpath == NULL || destpath == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); return SSH_ERROR; } + SSH_LOG(SSH_LOG_PROTOCOL, "processing symlink: src=%s dest=%s", + srcpath, destpath); + rv = symlink(srcpath, destpath); if (rv < 0) { int saved_errno = errno; status = unix_errno_to_ssh_stat(saved_errno); SSH_LOG(SSH_LOG_PROTOCOL, "symlink failed: %s", strerror(saved_errno)); sftp_reply_status(client_msg, status, "Write error"); - ret = SSH_ERROR; } else { sftp_reply_status(client_msg, SSH_FX_OK, "write success"); } - return ret; + return SSH_OK; } static int process_remove(sftp_client_message client_msg) { - int ret = SSH_OK; const char *filename = sftp_client_message_get_filename(client_msg); int rv; int status = SSH_FX_OK; + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "processing remove: %s", filename); rv = unlink(filename); @@ -1623,12 +1681,11 @@ int saved_errno = errno; SSH_LOG(SSH_LOG_PROTOCOL, "unlink failed: %s", strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); - ret = SSH_ERROR; } sftp_reply_status(client_msg, status, NULL); - return ret; + return SSH_OK; } static int @@ -1651,6 +1708,12 @@ int status; int rv; + if (path == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended statvfs: %s", path); rv = statvfs(path, &st); @@ -1659,7 +1722,7 @@ SSH_LOG(SSH_LOG_PROTOCOL, "statvfs failed: %s", strerror(saved_errno)); status = unix_errno_to_ssh_stat(saved_errno); sftp_reply_status(client_msg, status, NULL); - return SSH_ERROR; + return SSH_OK; } sftp_statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); @@ -1790,6 +1853,11 @@ if (strcmp(subsystem, "sftp") == 0) { sftp_session *sftp = (sftp_session *)userdata; + /* The SFTP subsystem was already initialized on this channel */ + if (*sftp != NULL) { + return SSH_ERROR; + } + /* initialize sftp session and file handler */ *sftp = sftp_server_new(session, channel); if (*sftp == NULL) { @@ -1827,8 +1895,8 @@ int decode_len; int rc; - if (sftpp == NULL) { - SSH_LOG(SSH_LOG_WARNING, "NULL userdata passed to callback"); + if (sftpp == NULL || *sftpp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "invalid userdata passed to callback"); return SSH_ERROR; } sftp = *sftpp; @@ -1840,8 +1908,13 @@ msg = sftp_get_client_message_from_packet(sftp); rc = process_client_message(msg); sftp_client_message_free(msg); - if (rc != SSH_OK) + if (rc != SSH_OK) { SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!"); + ssh_channel_send_eof(sftp->channel); + ssh_channel_close(sftp->channel); + // Leave freeing resources on caller + return rc; + } return decode_len; } diff -Nru libssh-0.11.2/src/socket.c libssh-0.11.5/src/socket.c --- libssh-0.11.2/src/socket.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/socket.c 2026-07-16 12:57:23.000000000 +0200 @@ -478,7 +478,7 @@ #endif } - if (s->poll_handle != NULL) { + if (s->poll_handle != NULL && !ssh_poll_is_locked(s->poll_handle)) { ssh_poll_free(s->poll_handle); s->poll_handle = NULL; } @@ -962,6 +962,7 @@ 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; @@ -980,7 +981,17 @@ 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]); @@ -1096,7 +1107,7 @@ cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *, jump_session->opts.proxy_jumps_user_cb); - if (cb != NULL) { + if (cb != NULL && cb->before_connection != NULL) { rc = cb->before_connection(jump_session, cb->userdata); if (rc != SSH_OK) { SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); @@ -1205,6 +1216,8 @@ ssh_event_free(event); ssh_free(jump_session); + shutdown(jump_thread_data->fd, SHUT_RDWR); + close(jump_thread_data->fd); SAFE_FREE(jump_thread_data); pthread_exit(NULL); diff -Nru libssh-0.11.2/src/string.c libssh-0.11.5/src/string.c --- libssh-0.11.2/src/string.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/string.c 2026-07-16 12:56:26.000000000 +0200 @@ -62,13 +62,12 @@ return NULL; } - str = malloc(sizeof(struct ssh_string_struct) + size); + str = calloc(1, sizeof(struct ssh_string_struct) + size); if (str == NULL) { return NULL; } str->size = htonl(size); - str->data[0] = 0; return str; } diff -Nru libssh-0.11.2/src/wrapper.c libssh-0.11.5/src/wrapper.c --- libssh-0.11.2/src/wrapper.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/src/wrapper.c 2026-07-16 12:56:26.000000000 +0200 @@ -181,7 +181,10 @@ #endif /* OPENSSL_VERSION_NUMBER */ #elif defined HAVE_GCRYPT_ECC gcry_sexp_release(crypto->ecdh_privkey); -#endif +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair_free(crypto->ecdh_privkey); + SAFE_FREE(crypto->ecdh_privkey); +#endif /* HAVE_LIBGCRYPT */ crypto->ecdh_privkey = NULL; } #endif diff -Nru libssh-0.11.2/tests/CMakeLists.txt libssh-0.11.5/tests/CMakeLists.txt --- libssh-0.11.2/tests/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/CMakeLists.txt 2026-07-16 12:56:26.000000000 +0200 @@ -23,6 +23,12 @@ ${TORTURE_LINK_LIBRARIES} pthread) endif(NOT WIN32) +if (WITH_GSSAPI AND GSSAPI_FOUND) + find_package(OpenSSL 1.1.1 REQUIRED) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + OpenSSL::Crypto) +endif (WITH_GSSAPI AND GSSAPI_FOUND) # create test library add_library(${TORTURE_LIBRARY} @@ -99,6 +105,7 @@ # OpenSSH Capabilities are required for all unit tests find_program(SSH_EXECUTABLE NAMES ssh) if (SSH_EXECUTABLE) + file(SIZE ${SSH_EXECUTABLE} SSH_EXECUTABLE_SIZE) execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) string(REGEX REPLACE "^.*OpenSSH_([0-9]+).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") string(REGEX REPLACE "^.*OpenSSH_[0-9]+.([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") @@ -163,6 +170,22 @@ endif() +find_program(DROPBEAR_EXECUTABLE NAMES dbclient) +if (DROPBEAR_EXECUTABLE) + execute_process(COMMAND ${DROPBEAR_EXECUTABLE} -V ERROR_VARIABLE DROPBEAR_VERSION_STR) + string(REGEX REPLACE "^.*Dropbear v([0-9]+)\\.([0-9]+).*$" "\\1.\\2" DROPBEAR_VERSION "${DROPBEAR_VERSION_STR}") + set(DROPBEAR_VERSION "${DROPBEAR_VERSION}") + + # HMAC-SHA1 support was removed in version 2025.87 + if("${DROPBEAR_VERSION}" VERSION_LESS "2025.87") + message("Dropbear Version less than 2025.87, enabling dropbear HMAC-SHA1 tests") + add_definitions(-DDROPBEAR_SUPPORTS_HMAC_SHA1) + endif() +else() + message(STATUS "Could NOT find Dropbear (missing: dbclient executable)") + set(DROPBEAR_EXECUTABLE "/bin/false") +endif() + find_program(SSHD_EXECUTABLE NAME sshd @@ -350,10 +373,10 @@ endif() add_custom_target(test_memcheck - # FIXME: The threads_pki_rsa test is skipped under valgrind as it times out + # FIXME: The pkd_hello_i1 test is skipped under valgrind as it times out # Passing suppression file is also stupid so lets go with override here: # https://stackoverflow.com/a/56116311 - COMMAND ${CMAKE_CTEST_COMMAND} -E torture_threads_pki_rsa -E pkd_hello_i1 + COMMAND ${CMAKE_CTEST_COMMAND} -E pkd_hello_i1 --output-on-failure --force-new-ctest-process --test-action memcheck --overwrite MemoryCheckSuppressionFile=${CMAKE_SOURCE_DIR}/tests/valgrind.supp WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff -Nru libssh-0.11.2/tests/client/CMakeLists.txt libssh-0.11.5/tests/client/CMakeLists.txt --- libssh-0.11.2/tests/client/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/client/CMakeLists.txt 2026-07-16 12:57:23.000000000 +0200 @@ -72,6 +72,7 @@ torture_sftp_home_directory torture_sftp_setstat torture_sftp_packet_read + torture_sftp_request_id ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff -Nru libssh-0.11.2/tests/client/torture_auth_cert.c libssh-0.11.5/tests/client/torture_auth_cert.c --- libssh-0.11.2/tests/client/torture_auth_cert.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/client/torture_auth_cert.c 2026-07-16 12:57:23.000000000 +0200 @@ -106,6 +106,114 @@ return 0; } +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options. It also changes the target user to one that does + * not accept authentication using this certificate and moves private key to + * different location so the default matching does not work */ +static int session_setup_bob_cert(void **state) +{ + struct torture_state *s = *state; + char doe_ssh_key[1024]; + char new_ssh_key[1024]; + char doe_ssh_cert[2048]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + const char *no_home = "~/.no_ssh"; + + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, no_home); + assert_int_equal(rc, SSH_OK); + + /* certs won't log in for bob */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(new_ssh_key, sizeof(new_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(new_ssh_key, keydata); + + /* Explicit private key and cert */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_IDENTITY, new_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options. It also changes the target user to one that does + * not accept authentication using this certificate and sets non-existing + * certificate path to trigger the right code path */ +static int session_setup_bob_cert_bad(void **state) +{ + struct torture_state *s = *state; + char doe_ssh_key[1024]; + char new_ssh_key[1024]; + char doe_ssh_cert[2048]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + const char *no_home = "~/.no_ssh"; + + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, no_home); + assert_ssh_return_code(s->ssh.session, rc); + + /* certs won't log in for bob */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(new_ssh_key, sizeof(new_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert1.pub", doe_ssh_key); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(new_ssh_key, keydata); + + /* Explicit private key and cert */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_IDENTITY, new_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + return 0; +} + static int session_teardown(void **state) { struct torture_state *s = *state; @@ -1004,6 +1112,16 @@ assert_ssh_return_code(session, rc); } +#define GROUP_TEST(TEST_NAME, SETUP) \ + { \ + #TEST_NAME "_" #SETUP, \ + TEST_NAME, \ + session_setup##_##SETUP, \ + session_teardown, \ + NULL, \ + } + + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -1019,12 +1137,12 @@ cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit_nonblocking, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_auto_fail, - session_setup_ssh_dir, - session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_auto_fail_nonblocking, - session_setup_ssh_dir, - session_teardown), + GROUP_TEST(torture_auth_auto_fail, ssh_dir), + GROUP_TEST(torture_auth_auto_fail_nonblocking, ssh_dir), + GROUP_TEST(torture_auth_auto_fail, bob_cert), + GROUP_TEST(torture_auth_auto_fail_nonblocking, bob_cert), + GROUP_TEST(torture_auth_auto_fail, bob_cert_bad), + GROUP_TEST(torture_auth_auto_fail_nonblocking, bob_cert_bad), cmocka_unit_test_setup_teardown(torture_auth_cert_options_private, session_setup_ssh_dir, session_teardown), diff -Nru libssh-0.11.2/tests/client/torture_gssapi_auth.c libssh-0.11.5/tests/client/torture_gssapi_auth.c --- libssh-0.11.2/tests/client/torture_gssapi_auth.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/client/torture_gssapi_auth.c 2026-07-16 12:56:26.000000000 +0200 @@ -272,5 +272,5 @@ rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); ssh_finalize(); - pthread_exit((void *)&rc); + return rc; } diff -Nru libssh-0.11.2/tests/client/torture_rekey.c libssh-0.11.5/tests/client/torture_rekey.c --- libssh-0.11.2/tests/client/torture_rekey.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/client/torture_rekey.c 2026-07-16 12:56:26.000000000 +0200 @@ -31,6 +31,7 @@ #include "libssh/priv.h" #include "libssh/session.h" #include "libssh/crypto.h" +#include "libssh/token.h" #include #include @@ -96,6 +97,7 @@ struct torture_state *s = *state; ssh_free(s->ssh.session); + s->ssh.session = NULL; return 0; } @@ -148,7 +150,7 @@ ssh_disconnect(s->ssh.session); } -static void sanity_check_session(void **state) +static void sanity_check_session_size(void **state, uint64_t rekey_limit) { struct torture_state *s = *state; struct ssh_crypto_struct *c = NULL; @@ -156,9 +158,9 @@ c = s->ssh.session->current_crypto; assert_non_null(c); assert_int_equal(c->in_cipher->max_blocks, - bytes / c->in_cipher->blocksize); + rekey_limit / c->in_cipher->blocksize); assert_int_equal(c->out_cipher->max_blocks, - bytes / c->out_cipher->blocksize); + rekey_limit / c->out_cipher->blocksize); /* when strict kex is used, the newkeys reset the sequence number */ if ((s->ssh.session->flags & SSH_SESSION_FLAG_KEX_STRICT) != 0) { assert_int_equal(c->out_cipher->packets, s->ssh.session->send_seq); @@ -170,6 +172,10 @@ assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); } } +static void sanity_check_session(void **state) +{ + sanity_check_session_size(state, bytes); +} /* We lower the rekey limits manually and check that the rekey * really happens when sending data @@ -275,7 +281,7 @@ /* To trigger rekey by receiving data, the easiest thing is probably to * use sftp */ -static void torture_rekey_recv(void **state) +static void torture_rekey_recv_size(void **state, uint64_t rekey_limit) { struct torture_state *s = *state; struct ssh_crypto_struct *c = NULL; @@ -290,7 +296,7 @@ mode_t mask; int rc; - sanity_check_session(state); + sanity_check_session_size(state, rekey_limit); /* Copy the initial secret hash = session_id so we know we changed keys later */ c = s->ssh.session->current_crypto; assert_non_null(c); @@ -324,8 +330,10 @@ /* The rekey limit was restored in the new crypto to the same value */ c = s->ssh.session->current_crypto; - assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); - assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); /* Check that the secret hash is different than initially */ assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); free(secret_hash); @@ -333,6 +341,11 @@ torture_sftp_close(s->ssh.tsftp); ssh_disconnect(s->ssh.session); } + +static void torture_rekey_recv(void **state) +{ + torture_rekey_recv_size(state, bytes); +} #endif /* WITH_SFTP */ /* Rekey time requires rekey after specified time and is off by default. @@ -836,6 +849,81 @@ torture_rekey_recv(state); } + +static void torture_rekey_guess_all_combinations(void **state) +{ + struct torture_state *s = *state; + char sshd_config[256] = ""; + char client_kex[256] = ""; + const char *supported = NULL; + struct ssh_tokens_st *s_tok = NULL; + uint64_t rekey_limit = 0; + int rc, i, j; + + /* The rekey limit is 1/2 of the transferred file size so we will likely get + * 2 rekeys per test, which still runs for acceptable time */ + rekey_limit = atoll(SSH_EXECUTABLE_SIZE); + rekey_limit /= 2; + + if (ssh_fips_mode()) { + supported = ssh_kex_get_fips_methods(SSH_KEX); + } else { + supported = ssh_kex_get_supported_method(SSH_KEX); + } + assert_non_null(supported); + + s_tok = ssh_tokenize(supported, ','); + assert_non_null(s_tok); + for (i = 0; s_tok->tokens[i]; i++) { + /* Skip algorithms not supported by the OpenSSH server */ + if (strstr(OPENSSH_KEX, s_tok->tokens[i]) == NULL) { + SSH_LOG(SSH_LOG_INFO, "Server: %s [skipping]", s_tok->tokens[i]); + continue; + } + SSH_LOG(SSH_LOG_INFO, "Server: %s", s_tok->tokens[i]); + snprintf(sshd_config, + sizeof(sshd_config), + "KexAlgorithms %s", + s_tok->tokens[i]); + /* This sets an only supported kex algorithm that we do not have as + * a first option in the client */ + torture_update_sshd_config(state, sshd_config); + + for (j = 0; s_tok->tokens[j]; j++) { + if (i == j) { + continue; + } + + session_setup(state); + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_REKEY_DATA, + &rekey_limit); + assert_ssh_return_code(s->ssh.session, rc); + + /* Client kex preference will have the second of the pair and the + * server one as a second to negotiate on the second attempt */ + snprintf(client_kex, + sizeof(client_kex), + "%s,%s", + s_tok->tokens[j], + s_tok->tokens[i]); + SSH_LOG(SSH_LOG_INFO, "Client: %s", client_kex); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_KEY_EXCHANGE, + client_kex); + assert_ssh_return_code(s->ssh.session, rc); + session_setup_sftp(state); + torture_rekey_recv_size(state, rekey_limit); + session_teardown(state); + } + } + + ssh_tokens_free(s_tok); +} #endif /* WITH_SFTP */ int torture_run_tests(void) { @@ -905,6 +993,7 @@ cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, session_setup, session_teardown), + cmocka_unit_test(torture_rekey_guess_all_combinations), #endif /* WITH_SFTP */ }; diff -Nru libssh-0.11.2/tests/client/torture_session.c libssh-0.11.5/tests/client/torture_session.c --- libssh-0.11.2/tests/client/torture_session.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/client/torture_session.c 2026-07-16 12:56:26.000000000 +0200 @@ -321,51 +321,6 @@ assert_int_equal(rc, SSH_ERROR); } -/* Ensure that calling 'ssh_channel_poll_timeout' on a freed channel does not - * lead to segmentation faults. */ -static void torture_freed_channel_poll_timeout(void **state) -{ - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - ssh_channel channel; - bool channel_freed = false; - char request[256]; - char buff[256] = {0}; - int rc; - - snprintf(request, 256, - "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); - - channel = ssh_channel_new(session); - assert_non_null(channel); - - rc = ssh_channel_open_session(channel); - assert_ssh_return_code(session, rc); - - /* Make the request, read parts with close */ - rc = ssh_channel_request_exec(channel, request); - assert_ssh_return_code(session, rc); - - do { - rc = ssh_channel_read(channel, buff, 256, 0); - } while(rc > 0); - assert_ssh_return_code(session, rc); - - /* when either of these conditions is met the call to ssh_channel_free will - * actually free the channel so calling poll on that channel will be - * use-after-free */ - if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || - (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { - channel_freed = true; - } - ssh_channel_free(channel); - - if (!channel_freed) { - rc = ssh_channel_poll_timeout(channel, 500, 0); - assert_int_equal(rc, SSH_ERROR); - } -} - /* Ensure that calling 'ssh_channel_read_nonblocking' on a freed channel does * not lead to segmentation faults. */ static void torture_freed_channel_read_nonblocking(void **state) @@ -461,52 +416,6 @@ SAFE_FREE(exit_signal); } - -/* Ensure that calling 'ssh_channel_get_exit_status' on a freed channel does not - * lead to segmentation faults. */ -static void torture_freed_channel_get_exit_status(void **state) -{ - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - ssh_channel channel; - bool channel_freed = false; - char request[256]; - char buff[256] = {0}; - int rc; - - snprintf(request, 256, - "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); - - channel = ssh_channel_new(session); - assert_non_null(channel); - - rc = ssh_channel_open_session(channel); - assert_ssh_return_code(session, rc); - - /* Make the request, read parts with close */ - rc = ssh_channel_request_exec(channel, request); - assert_ssh_return_code(session, rc); - - do { - rc = ssh_channel_read(channel, buff, 256, 0); - } while(rc > 0); - assert_ssh_return_code(session, rc); - - /* when either of these conditions is met the call to ssh_channel_free will - * actually free the channel so calling poll on that channel will be - * use-after-free */ - if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || - (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { - channel_freed = true; - } - SSH_CHANNEL_FREE(channel); - - if (!channel_freed) { - rc = ssh_channel_get_exit_status(channel); - assert_ssh_return_code_equal(session, rc, SSH_ERROR); - } -} - static void torture_channel_read_stderr(void **state) { @@ -611,9 +520,6 @@ cmocka_unit_test_setup_teardown(torture_freed_channel_poll, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_freed_channel_poll_timeout, - session_setup, - session_teardown), cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, session_setup, session_teardown), @@ -623,9 +529,6 @@ cmocka_unit_test_setup_teardown(torture_channel_exit_signal, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_freed_channel_get_exit_status, - session_setup, - session_teardown), cmocka_unit_test_setup_teardown(torture_channel_read_stderr, session_setup, session_teardown), diff -Nru libssh-0.11.2/tests/client/torture_sftp_init.c libssh-0.11.5/tests/client/torture_sftp_init.c --- libssh-0.11.2/tests/client/torture_sftp_init.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/client/torture_sftp_init.c 2026-06-12 15:53:41.000000000 +0200 @@ -72,6 +72,63 @@ assert_non_null(s->ssh.tsftp); } +static void session_setup_extensions(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc, count; + const char *name = NULL, *data = NULL; + sftp_session sftp = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + sftp = s->ssh.tsftp->sftp; + + /* null parameter */ + count = sftp_extensions_get_count(NULL); + assert_int_equal(count, 0); + + count = sftp_extensions_get_count(sftp); + assert_int_not_equal(count, 0); + + /* first null parameter */ + name = sftp_extensions_get_name(NULL, 0); + assert_null(name); + data = sftp_extensions_get_data(NULL, 0); + assert_null(data); + + /* First extension */ + name = sftp_extensions_get_name(sftp, 0); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, 0); + assert_non_null(data); + + /* Last extension */ + name = sftp_extensions_get_name(sftp, count - 1); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, count - 1); + assert_non_null(data); + + /* Overrun */ + name = sftp_extensions_get_name(sftp, count); + assert_null(name); + data = sftp_extensions_get_data(sftp, count); + assert_null(data); +} + static int session_teardown(void **state) { struct torture_state *s = *state; @@ -92,7 +149,10 @@ session_teardown), cmocka_unit_test_setup_teardown(session_setup_channel, NULL, - session_teardown) + session_teardown), + cmocka_unit_test_setup_teardown(session_setup_extensions, + NULL, + session_teardown), }; ssh_init(); diff -Nru libssh-0.11.2/tests/client/torture_sftp_request_id.c libssh-0.11.5/tests/client/torture_sftp_request_id.c --- libssh-0.11.2/tests/client/torture_sftp_request_id.c 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/tests/client/torture_sftp_request_id.c 2026-07-16 12:57:23.000000000 +0200 @@ -0,0 +1,182 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_request_id_null(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + int rc; + + rc = sftp_get_new_id(sftp, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_sftp_request_id_add(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + uint32_t id1, id2; + int rc; + size_t count; + + /* The list of IDs should be empty at first */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + /* Request a new ID */ + rc = sftp_get_new_id(sftp, &id1); + assert_int_equal(rc, SSH_OK); + + /* Check that the list has one ID now */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 1); + + /* Request another ID */ + rc = sftp_get_new_id(sftp, &id2); + assert_int_equal(rc, SSH_OK); + + /* Check that the IDs differ */ + assert_int_not_equal(id1, id2); + + /* Check that the list has two IDs now */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 2); +} + +static void torture_sftp_request_id_remove(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_attributes attr = NULL; + size_t count; + + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + /* We send a request and receive a response */ + attr = sftp_stat(sftp, SSH_EXECUTABLE); + assert_non_null(attr); + + /* The number of outstanding requests should be back to 0 */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + sftp_attributes_free(attr); +} + +static void torture_sftp_request_id_unknown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_buffer buffer = NULL; + uint32_t id = 0; + int rc; + size_t count; + + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = ssh_buffer_pack(buffer, "ds", id, "/tmp"); + assert_int_equal(rc, SSH_OK); + + /* Send a request without saving the request ID */ + rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, buffer); + assert_int_not_equal(rc, -1); + SSH_BUFFER_FREE(buffer); + + /* An attempt to receive the response should fail */ + rc = sftp_read_and_dispatch(sftp); + assert_int_equal(rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_request_id_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_add, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_remove, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_unknown, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff -Nru libssh-0.11.2/tests/fuzz/CMakeLists.txt libssh-0.11.5/tests/fuzz/CMakeLists.txt --- libssh-0.11.2/tests/fuzz/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/fuzz/CMakeLists.txt 2026-07-16 12:56:26.000000000 +0200 @@ -2,9 +2,7 @@ macro(fuzzer name) add_executable(${name} ${name}.c) - target_link_libraries(${name} - PRIVATE - ssh::static pthread) + target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES}) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set_target_properties(${name} PROPERTIES diff -Nru libssh-0.11.2/tests/fuzz/fuzzer.c libssh-0.11.5/tests/fuzz/fuzzer.c --- libssh-0.11.2/tests/fuzz/fuzzer.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/fuzz/fuzzer.c 2026-06-12 15:53:41.000000000 +0200 @@ -1,8 +1,14 @@ /* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */ +#include "config.h" + #include #include #include +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); __attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); @@ -35,5 +41,9 @@ free (buf); printf ("Done!\n"); + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif return 0; } diff -Nru libssh-0.11.2/tests/pkcs11/setup-softhsm-tokens.sh libssh-0.11.5/tests/pkcs11/setup-softhsm-tokens.sh --- libssh-0.11.2/tests/pkcs11/setup-softhsm-tokens.sh 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/pkcs11/setup-softhsm-tokens.sh 2026-06-12 15:53:41.000000000 +0200 @@ -28,6 +28,9 @@ directories.tokendir = $TESTDIR/db objectstore.backend = file log.level = DEBUG +# # The hashed ECDSA mechanisms wrongly do not support multi-part operations +# https://github.com/softhsm/SoftHSMv2/issues/842 +slots.mechanisms = -CKM_ECDSA_SHA1,CKM_ECDSA_SHA224,CKM_ECDSA_SHA256,CKM_ECDSA_SHA384,CKM_ECDSA_SHA512 EOF cat "$TESTDIR/softhsm.conf" diff -Nru libssh-0.11.2/tests/pkd/CMakeLists.txt libssh-0.11.5/tests/pkd/CMakeLists.txt --- libssh-0.11.2/tests/pkd/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/pkd/CMakeLists.txt 2026-06-12 15:53:41.000000000 +0200 @@ -17,10 +17,8 @@ ) set(pkd_libs - ${CMOCKA_LIBRARY} - ssh::static + ${TORTURE_LINK_LIBRARIES} ${ARGP_LIBRARIES} - pthread ) add_executable(pkd_hello ${pkd_hello_src}) diff -Nru libssh-0.11.2/tests/pkd/pkd_client.h libssh-0.11.5/tests/pkd/pkd_client.h --- libssh-0.11.2/tests/pkd/pkd_client.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/pkd/pkd_client.h 2026-07-16 12:56:26.000000000 +0200 @@ -61,7 +61,7 @@ /* Dropbear */ -#define DROPBEAR_BINARY "dbclient" +#define DROPBEAR_BINARY DROPBEAR_EXECUTABLE #define DROPBEAR_KEYGEN "dropbearkey" #define DROPBEAR_CMD_START \ diff -Nru libssh-0.11.2/tests/pkd/pkd_hello.c libssh-0.11.5/tests/pkd/pkd_hello.c --- libssh-0.11.2/tests/pkd/pkd_hello.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/pkd/pkd_hello.c 2026-07-16 12:56:26.000000000 +0200 @@ -22,6 +22,11 @@ #include "pkd_keyutil.h" #include "pkd_util.h" +#if defined(HAVE_LIBCRYPTO) +/* for OPENSSL_cleanup() of OpenSSL context */ +#include +#endif + #define DEFAULT_ITERATIONS 10 static struct pkd_daemon_args pkd_dargs; @@ -410,22 +415,32 @@ f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown) -#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ +#define PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ f(client, ecdsa_256_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_256, teardown) \ f(client, ecdsa_384_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_384, teardown) \ f(client, ecdsa_521_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_521, teardown) \ f(client, rsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_rsa, teardown) -/* TODO: Include these tests when an older version of dropbear is used. Currently, they have been removed as the latest dropbear version -does not support these MACs. - -f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown) \ -f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown) \ -f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown) \ -f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown) \ -*/ +#define PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown) \ + f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown) \ + f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown) \ + f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown) + +#ifdef DROPBEAR_SUPPORTS_HMAC_SHA1 +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) +#else +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) +#endif #define PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ f(client, ecdsa_256_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_256, teardown) \ f(client, ecdsa_256_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_256, teardown) \ f(client, ecdsa_256_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_256, teardown) \ @@ -990,6 +1005,9 @@ if (rc != 0) { fprintf(stderr, "ssh_finalize: %d\n", rc); } +#if defined(HAVE_LIBCRYPTO) + OPENSSL_cleanup(); +#endif out: return exit_code; } diff -Nru libssh-0.11.2/tests/server/test_server/CMakeLists.txt libssh-0.11.5/tests/server/test_server/CMakeLists.txt --- libssh-0.11.2/tests/server/test_server/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/test_server/CMakeLists.txt 2026-07-16 12:56:26.000000000 +0200 @@ -11,7 +11,8 @@ add_library(testserver STATIC test_server.c default_cb.c - sftpserver_cb.c) + sftpserver_cb.c + testserver_common.c) if (WITH_COVERAGE) append_coverage_compiler_flags_to_target(testserver) endif (WITH_COVERAGE) @@ -32,7 +33,7 @@ add_executable(test_server ${server_SRCS}) target_compile_options(test_server PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(test_server - PRIVATE testserver ssh::ssh ${ARGP_LIBRARIES} util) + PRIVATE testserver ${TORTURE_LINK_LIBRARIES} ${ARGP_LIBRARIES} util) if (WITH_COVERAGE) append_coverage_compiler_flags_to_target(test_server) endif (WITH_COVERAGE) diff -Nru libssh-0.11.2/tests/server/test_server/default_cb.c libssh-0.11.5/tests/server/test_server/default_cb.c --- libssh-0.11.2/tests/server/test_server/default_cb.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/test_server/default_cb.c 2026-07-16 12:56:26.000000000 +0200 @@ -21,9 +21,11 @@ * MA 02111-1307, USA. */ + #include "config.h" #include "test_server.h" #include "default_cb.h" +#include "testserver_common.h" #include #include @@ -448,9 +450,11 @@ case 0: close(cdata->pty_master); if (login_tty(cdata->pty_slave) != 0) { + finalize_openssl(); exit(1); } execl("/bin/sh", "sh", mode, command, NULL); + finalize_openssl(); exit(0); default: close(cdata->pty_slave); @@ -500,6 +504,7 @@ close(err[1]); /* exec the requested command. */ execl("/bin/sh", "sh", "-c", command, NULL); + finalize_openssl(); exit(0); } diff -Nru libssh-0.11.2/tests/server/test_server/sftpserver_cb.c libssh-0.11.5/tests/server/test_server/sftpserver_cb.c --- libssh-0.11.2/tests/server/test_server/sftpserver_cb.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/test_server/sftpserver_cb.c 2026-07-16 12:56:26.000000000 +0200 @@ -41,6 +41,12 @@ #include #include +#include // for cmocka +#include // for cmocka +#include // for cmocka + +#include + #ifdef HAVE_LIBUTIL_H #include #endif @@ -129,6 +135,11 @@ goto end; } + if (sdata->channel != NULL) { + fprintf(stderr, "Only one channel is supported\n"); + goto end; + } + chan = ssh_channel_new(session); if (chan == NULL) { fprintf(stderr, "Error creating channel: %s\n", @@ -211,6 +222,33 @@ return cb; } +/* Default SFTP channel data callback with some additional checks */ +int sftp_channel_data_callback(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + int rv; + + rv = sftp_channel_default_data_callback(session, + channel, + data, + len, + is_stderr, + userdata); + + if (sftpp != NULL && *sftpp != NULL) { + sftp_session sftp = *sftpp; + /* NOTE that this expects both server and clieng being libssh with this + * same version number */ + assert_true(sftp->client_version <= LIBSFTP_VERSION); + } + return rv; +} + /* The caller is responsible to set the userdata to be provided to the callback * The caller is responsible to free the allocated structure * */ @@ -225,7 +263,7 @@ goto end; } - cb->channel_data_function = sftp_channel_default_data_callback; + cb->channel_data_function = sftp_channel_data_callback; cb->channel_subsystem_request_function = sftp_channel_default_subsystem_request; end: diff -Nru libssh-0.11.2/tests/server/test_server/test_server.c libssh-0.11.5/tests/server/test_server/test_server.c --- libssh-0.11.2/tests/server/test_server/test_server.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/test_server/test_server.c 2026-07-16 12:56:26.000000000 +0200 @@ -22,6 +22,7 @@ */ #include "test_server.h" +#include "testserver_common.h" #include #include @@ -288,6 +289,7 @@ free_server_state(state); SAFE_FREE(state); + finalize_openssl(); exit(0); case -1: fprintf(stderr, "Failed to fork\n"); @@ -355,11 +357,8 @@ /* The child process starts a server which will listen for connections */ rc = run_server(state); - if (rc != 0) { - exit(rc); - } - - exit(0); + finalize_openssl(); + exit(rc); case -1: strerror_r(errno, err_str, 1024); fprintf(stderr, "Failed to fork: %s\n", diff -Nru libssh-0.11.2/tests/server/test_server/testserver_common.c libssh-0.11.5/tests/server/test_server/testserver_common.c --- libssh-0.11.2/tests/server/test_server/testserver_common.c 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/tests/server/test_server/testserver_common.c 2026-06-12 15:53:41.000000000 +0200 @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "testserver_common.h" + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +void finalize_openssl(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} diff -Nru libssh-0.11.2/tests/server/test_server/testserver_common.h libssh-0.11.5/tests/server/test_server/testserver_common.h --- libssh-0.11.2/tests/server/test_server/testserver_common.h 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/tests/server/test_server/testserver_common.h 2026-06-12 15:53:41.000000000 +0200 @@ -0,0 +1,26 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +void finalize_openssl(void); diff -Nru libssh-0.11.2/tests/server/torture_gssapi_server_auth.c libssh-0.11.5/tests/server/torture_gssapi_server_auth.c --- libssh-0.11.2/tests/server/torture_gssapi_server_auth.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/torture_gssapi_server_auth.c 2026-07-16 12:56:26.000000000 +0200 @@ -451,5 +451,5 @@ teardown_default_server); ssh_finalize(); - pthread_exit((void *)&rc); + return rc; } diff -Nru libssh-0.11.2/tests/server/torture_gssapi_server_auth_cb.c libssh-0.11.5/tests/server/torture_gssapi_server_auth_cb.c --- libssh-0.11.2/tests/server/torture_gssapi_server_auth_cb.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/torture_gssapi_server_auth_cb.c 2026-07-16 12:56:26.000000000 +0200 @@ -475,5 +475,5 @@ teardown_default_server); ssh_finalize(); - pthread_exit((void *)&rc); + return rc; } diff -Nru libssh-0.11.2/tests/server/torture_gssapi_server_delegation.c libssh-0.11.5/tests/server/torture_gssapi_server_delegation.c --- libssh-0.11.2/tests/server/torture_gssapi_server_delegation.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/torture_gssapi_server_delegation.c 2026-07-16 12:56:26.000000000 +0200 @@ -371,5 +371,5 @@ teardown_default_server); ssh_finalize(); - pthread_exit((void *)&rc); + return rc; } diff -Nru libssh-0.11.2/tests/server/torture_server_auth_kbdint.c libssh-0.11.5/tests/server/torture_server_auth_kbdint.c --- libssh-0.11.2/tests/server/torture_server_auth_kbdint.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/torture_server_auth_kbdint.c 2026-07-16 12:56:26.000000000 +0200 @@ -698,9 +698,12 @@ static void torture_server_auth_kbdint(void **state) { struct test_server_st *tss = *state; - struct torture_state *s; - ssh_session session; + struct torture_state *s = NULL; + ssh_session session = NULL; + int nprompts = 0; int rc; + const char *prompt = NULL; + char echo; assert_non_null(tss); @@ -726,7 +729,19 @@ rc = ssh_userauth_kbdint(session, NULL, NULL); assert_int_equal(rc, SSH_AUTH_INFO); - assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 2); + + prompt = ssh_userauth_kbdint_getprompt(NULL, 0, &echo); + assert_null(prompt); + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_string_equal(prompt, "username: "); + assert_int_equal(echo, 1); + prompt = ssh_userauth_kbdint_getprompt(session, 1, &echo); + assert_string_equal(prompt, "password: "); + assert_int_equal(echo, 0); + prompt = ssh_userauth_kbdint_getprompt(session, 2, &echo); + assert_null(prompt); /* Reply the first 2 prompts using the username and password */ rc = ssh_userauth_kbdint_setanswer(session, 0, @@ -740,7 +755,12 @@ /* Resend the password */ rc = ssh_userauth_kbdint(session, NULL, NULL); assert_int_equal(rc, SSH_AUTH_INFO); - assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 1); + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_string_equal(prompt, "retype password: "); + assert_int_equal(echo, 0); rc = ssh_userauth_kbdint_setanswer(session, 0, TORTURE_SSH_USER_BOB_PASSWORD); @@ -749,8 +769,11 @@ rc = ssh_userauth_kbdint(session, NULL, NULL); /* Sometimes, SSH server send an empty query at the end of exchange */ - if(rc == SSH_AUTH_INFO) { - assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + if (rc == SSH_AUTH_INFO) { + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 0); + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_null(prompt); rc = ssh_userauth_kbdint(session, NULL, NULL); } diff -Nru libssh-0.11.2/tests/server/torture_sftpserver.c libssh-0.11.5/tests/server/torture_sftpserver.c --- libssh-0.11.2/tests/server/torture_sftpserver.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/server/torture_sftpserver.c 2026-07-16 12:57:23.000000000 +0200 @@ -36,6 +36,8 @@ #include "libssh/libssh.h" #include "libssh/priv.h" #include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/sftp_priv.h" #include "test_server.h" #include "default_cb.h" @@ -462,6 +464,9 @@ rc = sftp_init(sftp); assert_int_equal(rc, SSH_OK); + /* Assert some information about the connected session */ + assert_int_equal(sftp->server_version, LIBSFTP_VERSION); + /* symbol link */ rc = sftp_symlink(sftp, "/tmp/this_is_the_link", "/tmp/sftp_symlink_test"); assert_int_equal(rc, SSH_OK); @@ -501,6 +506,74 @@ assert_int_equal(rc, SSH_OK); } +static void torture_server_sftp_init_repeat(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + ssh_session session = NULL; + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_packet packet = NULL; + uint32_t version; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + /* send one version N-1 */ + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION - 1); + assert_int_equal(rc, SSH_OK); + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + SSH_BUFFER_FREE(buffer); + assert_int_equal(rc, 9); + + packet = sftp_packet_read(sftp); + assert_non_null(packet); + assert_int_equal(packet->type, SSH_FXP_VERSION); + + /* Make sure we get the expected version N-1 */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + assert_int_equal(rc, SSH_OK); + assert_int_equal(version, LIBSFTP_VERSION - 1); + + /* Repeated INIT will fail on server */ + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_ERROR); +} + static void torture_server_sftp_open_read_write(void **state) { struct test_server_st *tss = *state; @@ -1057,6 +1130,93 @@ sftp_attributes_free(tmp_attr); } +static void +torture_server_sftp_readdir(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + int num_files = 0; + sftp_dir dir; + sftp_attributes a = NULL; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + dir = sftp_opendir(sftp, tsftp->testdir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, "server_setstat_test") == 0) { + /* verify long name is in the expected format */ + assert_string_equal(a->longname, + "-rw-r----- 1 5001 9000 10 Jan 1 03:44:27 1970 server_setstat_test"); + } else if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 3); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); +} + + /* The max number of handles is 256 in sftpserver.h -- keep in sync! */ #define SFTP_HANDLES 256 static void torture_server_sftp_handles_exhaustion(void **state) @@ -1100,6 +1260,86 @@ } } +static void torture_server_sftp_opendir_handles_exhaustion(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_file handles[SFTP_HANDLES] = {0}; + sftp_dir dir = NULL; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Occupy all handles with files */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handles[i]); + } + + /* Opening a directory should fail gracefully without leaking h->name */ + dir = sftp_opendir(sftp, tsftp->testdir); + assert_null(dir); + + /* cleanup */ + for (int i = 0; i < SFTP_HANDLES; i++) { + rc = sftp_close(handles[i]); + assert_int_equal(rc, SSH_OK); + } +} + +static void torture_server_sftp_after_channel_close(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char tmp_file[PATH_MAX] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + struct stat sb; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* Close the channel */ + ssh_channel_close(sftp->channel); + /* Reset the flags so the channel looks open for the caller so we do not + * have to reimplement sending the message in the test */ + sftp->channel->local_eof = 0; + sftp->channel->state = SSH_CHANNEL_STATE_OPEN; + sftp->channel->flags &= ~SSH_CHANNEL_FLAG_CLOSED_LOCAL; + + /* Create a new file */ + handle = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_null(handle); + + /* Should not be created */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, -1); +} int torture_run_tests(void) { int rc; @@ -1110,6 +1350,9 @@ cmocka_unit_test_setup_teardown(torture_server_test_sftp_function, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_init_repeat, + session_setup, + session_teardown), cmocka_unit_test_setup_teardown(torture_server_sftp_open_read_write, session_setup_sftp, session_teardown), @@ -1128,11 +1371,22 @@ cmocka_unit_test_setup_teardown(torture_server_sftp_setstat, session_setup_sftp, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_readdir, + session_setup_sftp, + session_teardown), cmocka_unit_test_setup_teardown(torture_server_sftp_handles_exhaustion, session_setup_sftp, session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_opendir_handles_exhaustion, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_after_channel_close, + session_setup_sftp, + session_teardown), }; + setenv("TZ", "UTC", 1); + ssh_init(); torture_filter_tests(tests); diff -Nru libssh-0.11.2/tests/ssh_ping.c libssh-0.11.5/tests/ssh_ping.c --- libssh-0.11.2/tests/ssh_ping.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/ssh_ping.c 2026-07-16 12:56:26.000000000 +0200 @@ -27,6 +27,7 @@ const char *banner = NULL; ssh_session session = NULL; const char *hostkeys = NULL; + const char *kex = NULL; int rc = 1; bool process_config = false; @@ -66,6 +67,13 @@ if (rc < 0) { goto out; } + + /* Enable all supported kex algorithms */ + kex = ssh_kex_get_supported_method(SSH_KEX); + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + if (rc < 0) { + goto out; + } rc = ssh_connect(session); if (rc != SSH_OK) { diff -Nru libssh-0.11.2/tests/tests_config.h.cmake libssh-0.11.5/tests/tests_config.h.cmake --- libssh-0.11.2/tests/tests_config.h.cmake 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/tests_config.h.cmake 2026-07-16 12:56:26.000000000 +0200 @@ -65,6 +65,8 @@ #cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" #cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" #cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE_SIZE "${SSH_EXECUTABLE_SIZE}" +#cmakedefine DROPBEAR_EXECUTABLE "${DROPBEAR_EXECUTABLE}" #cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT} #cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" #cmakedefine SOFTHSM2_LIBRARY "${SOFTHSM2_LIBRARY}" diff -Nru libssh-0.11.2/tests/torture.c libssh-0.11.5/tests/torture.c --- libssh-0.11.2/tests/torture.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/torture.c 2026-07-16 12:56:26.000000000 +0200 @@ -52,6 +52,11 @@ #include #endif +#ifdef WITH_GSSAPI +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + #define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" /* socket wrapper IPv6 prefix fd00::5357:5fxx */ #define TORTURE_SSHD_SRV_IPV6 "fd00::5357:5f0a" @@ -1848,9 +1853,31 @@ } #endif /* defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) */ -int main(int argc, char **argv) { +/** + * Finalize the torture context. No-op except for OpenSSL or GSSAPI + * + * When OpenSSL is built without the at-exit handlers, it won't call the + * OPENSSL_cleanup() from destructor or at-exit handler, which means we need to + * do it manually in the tests. + * + * It is never a good idea to call this function from the library context as we + * can not be sure the libssh is really the last one using the OpenSSL. + * + * This needs to be called at the end of the main function or any time before + * any forked process (servers) exits. + */ +void torture_finalize(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} + +int main(int argc, char **argv) +{ struct argument_s arguments; char *env = getenv("LIBSSH_VERBOSITY"); + int rv; arguments.verbose=0; arguments.pattern=NULL; @@ -1868,5 +1895,9 @@ cmocka_set_test_filter(pattern); #endif - return torture_run_tests(); + rv = torture_run_tests(); + + torture_finalize(); + + return rv; } diff -Nru libssh-0.11.2/tests/torture.h libssh-0.11.5/tests/torture.h --- libssh-0.11.2/tests/torture.h 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/torture.h 2026-07-16 12:56:26.000000000 +0200 @@ -178,4 +178,6 @@ void torture_setenv(char const* variable, char const* value); void torture_unsetenv(char const* variable); +void torture_finalize(void); + #endif /* _TORTURE_H */ diff -Nru libssh-0.11.2/tests/unittests/CMakeLists.txt libssh-0.11.5/tests/unittests/CMakeLists.txt --- libssh-0.11.2/tests/unittests/CMakeLists.txt 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/CMakeLists.txt 2026-07-16 12:56:26.000000000 +0200 @@ -98,6 +98,13 @@ 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 -Nru libssh-0.11.2/tests/unittests/torture_channel.c libssh-0.11.5/tests/unittests/torture_channel.c --- libssh-0.11.2/tests/unittests/torture_channel.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_channel.c 2026-07-13 16:55:26.000000000 +0200 @@ -39,10 +39,29 @@ close(fd); } +static void torture_channel_null_session(void **state) +{ + ssh_channel channel = NULL; + + (void)state; + + channel = calloc(1, sizeof(struct ssh_channel_struct)); + + assert_non_null(channel); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->session = NULL; + + assert_int_equal(ssh_channel_is_open(channel), 0); + + free(channel); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { cmocka_unit_test(torture_channel_select), + cmocka_unit_test(torture_channel_null_session), }; ssh_init(); diff -Nru libssh-0.11.2/tests/unittests/torture_config.c libssh-0.11.5/tests/unittests/torture_config.c --- libssh-0.11.2/tests/unittests/torture_config.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_config.c 2026-07-16 12:56:26.000000000 +0200 @@ -53,6 +53,8 @@ #define LIBSSH_TEST_NONEWLINEONELINE "libssh_test_NoNewLineOneline.tmp" #define LIBSSH_TEST_RECURSIVE_INCLUDE "libssh_test_recursive_include.tmp" #define LIBSSH_TESTCONFIG_MATCH_COMPLEX "libssh_test_match_complex.tmp" +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING "libssh_test_loglevel_missing.tmp" +#define LIBSSH_TESTCONFIG_JUMP "libssh_test_jump.tmp" #define LIBSSH_TESTCONFIG_STRING1 \ "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" @@ -243,6 +245,26 @@ "\tForwardAgent yes\n" \ "\tHostName complex-match\n" +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING "LogLevel\n" +#define LIBSSH_TESTCONFIG_JUMP_STRING \ + "# The jump host\n" \ + "Host ub-jumphost\n" \ + " HostName 1xxxxxx\n" \ + " User ubuntu\n" \ + " IdentityFile ~/of/temp-libssh.pem\n" \ + " Port 23\n" \ + " LogLevel DEBUG3\n" \ + "\n" \ + "# Cisco Router through Jump Host\n" \ + "Host cisco-router\n" \ + " HostName xx.xxxxxxxxx\n" \ + " User username\n" \ + " ProxyJump ub-jumphost\n" \ + " Port 5555\n" \ + " #RequiredRSASize 512\n" \ + " PasswordAuthentication yes\n" \ + " LogLevel DEBUG3\n" + /** * @brief helper function loading configuration from either file or string */ @@ -293,6 +315,8 @@ unlink(LIBSSH_TEST_NONEWLINEEND); unlink(LIBSSH_TEST_NONEWLINEONELINE); unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); torture_write_file(LIBSSH_TESTCONFIG1, LIBSSH_TESTCONFIG_STRING1); @@ -361,6 +385,10 @@ /* Match complex combinations */ torture_write_file(LIBSSH_TESTCONFIG_MATCH_COMPLEX, LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING); + torture_write_file(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, + LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING); + torture_write_file(LIBSSH_TESTCONFIG_JUMP, + LIBSSH_TESTCONFIG_JUMP_STRING); return 0; } @@ -390,6 +418,8 @@ unlink(LIBSSH_TEST_NONEWLINEEND); unlink(LIBSSH_TEST_NONEWLINEONELINE); unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); return 0; } @@ -2342,80 +2372,138 @@ (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 @@ -2468,9 +2556,9 @@ char *home = NULL; struct passwd *pw = getpwuid(getuid()); assert_non_null(pw); - user = pw->pw_name; + user = strdup(pw->pw_name); assert_non_null(user); - home = pw->pw_dir; + home = strdup(pw->pw_dir); assert_non_null(home); #endif @@ -2528,6 +2616,8 @@ snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); assert_string_equal(result, h); free(result); + free(home); + free(user); #endif } @@ -2615,6 +2705,129 @@ ssh_string_free_char(v); } +/* Missing value to LogLevel configuration option + */ +static void torture_config_loglevel_missing_value(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, NULL, SSH_OK); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + char *v = NULL; + int ret; + + (void)user; + + /* During the connection, we force parsing the same configuration file + * (would be normally parsed automatically during the connection itself) + */ + ret = ssh_config_parse_file(jump_session, LIBSSH_TESTCONFIG_JUMP); + assert_return_code(ret, errno); + + /* Test the variable presence */ + ret = ssh_options_get(jump_session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "1xxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(jump_session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "ubuntu"); + ssh_string_free_char(v); + + assert_int_equal(jump_session->opts.port, 23); + + /* Fail the connection -- we are in unit tests so it would fail anyway */ + return 1; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} +/* Reproducer for complex proxy jump + */ +static void torture_config_jump(void **state) +{ + ssh_session session = *state; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "cisco-router"); + + _parse_config(session, LIBSSH_TESTCONFIG_JUMP, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "xx.xxxxxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "username"); + ssh_string_free_char(v); + + assert_int_equal(session->opts.port, 5555); + + /* At this point, the configuration file is not parsed for the jump host so + * we are getting just the the hostname -- the port and username will get + * pulled during the session connecting to this host */ + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 1); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "ub-jumphost", + NULL, + NULL); + + /* Set up the callbacks -- they should verify we are going to connect to the + * right host */ + ret = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, ret); + + ret = ssh_connect(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + printf("%s: EOF\n", __func__); +} + +/* 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; @@ -2711,6 +2924,15 @@ setup, teardown), cmocka_unit_test_setup_teardown(torture_config_match_complex, setup, teardown), + cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_jump, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_invalid, + setup, + teardown), }; diff -Nru libssh-0.11.2/tests/unittests/torture_knownhosts_parsing.c libssh-0.11.5/tests/unittests/torture_knownhosts_parsing.c --- libssh-0.11.2/tests/unittests/torture_knownhosts_parsing.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_knownhosts_parsing.c 2026-06-12 15:53:41.000000000 +0200 @@ -696,6 +696,82 @@ ssh_free(session); } +static int setup_bad_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DEFAULT_ED25519, + sizeof(char), + strlen(LOCALHOST_DEFAULT_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_DEFAULT_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +#define LOCALHOST_BAD_LINE "localhost \n" + nwritten = fwrite(LOCALHOST_BAD_LINE, + sizeof(char), + strlen(LOCALHOST_BAD_LINE), + fp); + if (nwritten != strlen(LOCALHOST_BAD_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static void torture_knownhosts_has_entry(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + bool process_config = false; + struct ssh_knownhosts_entry *entry = NULL; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* Error is expected -- this tests the memory is not leaked from this + * test case */ + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + + found = ssh_session_get_known_hosts_entry(session, &entry); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + assert_null(entry); + + ssh_free(session); +} #endif /* _WIN32 There is no /dev/null on Windows */ int torture_run_tests(void) { @@ -738,6 +814,9 @@ cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_global, setup_knownhosts_file, teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_has_entry, + setup_bad_knownhosts_file, + teardown_knownhosts_file), #endif }; diff -Nru libssh-0.11.2/tests/unittests/torture_misc.c libssh-0.11.5/tests/unittests/torture_misc.c --- libssh-0.11.2/tests/unittests/torture_misc.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_misc.c 2026-07-16 12:56:26.000000000 +0200 @@ -1129,6 +1129,36 @@ 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[] = { @@ -1158,6 +1188,7 @@ 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), }; ssh_init(); diff -Nru libssh-0.11.2/tests/unittests/torture_pki_ecdsa.c libssh-0.11.5/tests/unittests/torture_pki_ecdsa.c --- libssh-0.11.2/tests/unittests/torture_pki_ecdsa.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_pki_ecdsa.c 2026-07-16 12:56:26.000000000 +0200 @@ -367,10 +367,14 @@ static void torture_pki_ecdsa_import_cert_file(void **state) { int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; ssh_key cert = NULL; - enum ssh_keytypes_e type; + enum ssh_keytypes_e type, exp_cert_type; struct pki_st *test_state = *((struct pki_st **)state); + exp_cert_type = test_state->type + 3; + /* Importing public key as cert should fail */ rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY ".pub", &cert); assert_int_equal(rc, SSH_ERROR); @@ -380,13 +384,78 @@ assert_int_equal(rc, 0); assert_non_null(cert); + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + type = ssh_key_type(cert); - assert_int_equal(type, test_state->type+3); + assert_int_equal(type, exp_cert_type); rc = ssh_key_is_public(cert); assert_int_equal(rc, 1); + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == test_state->type); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == exp_cert_type); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate(test_state->type, 256, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); } static void torture_pki_ecdsa_publickey_base64(void **state) diff -Nru libssh-0.11.2/tests/unittests/torture_pki_ed25519.c libssh-0.11.5/tests/unittests/torture_pki_ed25519.c --- libssh-0.11.2/tests/unittests/torture_pki_ed25519.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_pki_ed25519.c 2026-07-16 12:56:26.000000000 +0200 @@ -312,6 +312,8 @@ static void torture_pki_ed25519_import_cert_file(void **state) { int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; ssh_key cert = NULL; enum ssh_keytypes_e type; @@ -323,16 +325,88 @@ assert_null(cert); rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(cert); + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + type = ssh_key_type(cert); assert_true(type == SSH_KEYTYPE_ED25519_CERT01); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + SSH_KEY_FREE(cert); + SSH_KEY_FREE(pubkey); + skip(); + } + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_ED25519); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_ED25519_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); } static void torture_pki_ed25519_publickey_base64(void **state) diff -Nru libssh-0.11.2/tests/unittests/torture_pki_rsa.c libssh-0.11.5/tests/unittests/torture_pki_rsa.c --- libssh-0.11.2/tests/unittests/torture_pki_rsa.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_pki_rsa.c 2026-07-16 12:56:26.000000000 +0200 @@ -373,6 +373,7 @@ ssh_key pubkey = NULL; ssh_key privkey = NULL; ssh_key cert = NULL; + enum ssh_keytypes_e type; (void)state; /* unused */ @@ -389,6 +390,13 @@ assert_return_code(rc, errno); assert_non_null(pubkey); + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), passphrase, NULL, @@ -397,6 +405,9 @@ assert_return_code(rc, errno); assert_non_null(privkey); + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_RSA); + /* Basic sanity. */ rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); assert_int_equal(rc, SSH_ERROR); @@ -416,6 +427,10 @@ rc = ssh_pki_copy_cert_to_privkey(cert, privkey); assert_return_code(rc, errno); assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_RSA_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); /* The private key's cert is already set, another copy should fail. */ rc = ssh_pki_copy_cert_to_privkey(cert, privkey); @@ -437,6 +452,9 @@ rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); assert_int_equal(rc, SSH_ERROR); + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); diff -Nru libssh-0.11.2/tests/unittests/torture_threads_pki_rsa.c libssh-0.11.5/tests/unittests/torture_threads_pki_rsa.c --- libssh-0.11.2/tests/unittests/torture_threads_pki_rsa.c 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/unittests/torture_threads_pki_rsa.c 2026-07-16 12:48:05.000000000 +0200 @@ -58,14 +58,8 @@ } for (i = 0; i < NUM_THREADS; ++i) { - void *p = NULL; - uint64_t *result; - - rc = pthread_join(threads[i], &p); + rc = pthread_join(threads[i], NULL); assert_int_equal(rc, 0); - - result = (uint64_t *)p; - assert_null(result); } return rc; @@ -133,10 +127,9 @@ return 0; } -static int disable_secmem(void **state) +static void +disable_secmem(void) { - (void) state; /*unused*/ - #if defined(HAVE_LIBGCRYPT) /* gcrypt currently is configured to use only 4kB of locked secmem * (see ssh_crypto_init() in src/libcrypt.c) @@ -145,23 +138,10 @@ * To avoid the expected warning, disable the secure memory. * */ - gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); #endif - - return 0; -} - -static int enable_secmem(void **state) -{ - (void) state; /*unused*/ - -#if defined(HAVE_LIBGCRYPT) - /* Re-enable secmem */ - gcry_control(GCRYCTL_INIT_SECMEM, 4096); - gcry_control(GCRYCTL_RESUME_SECMEM_WARN); -#endif - return 0; } static void *thread_pki_rsa_import_pubkey_file(void *threadid) @@ -178,7 +158,7 @@ SSH_KEY_FREE(pubkey); - pthread_exit(NULL); + return NULL; } static void torture_pki_rsa_import_pubkey_file(void **state) @@ -211,8 +191,7 @@ NULL, NULL); assert_true(rc == -1); - - pthread_exit(NULL); + return NULL; } static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state){ @@ -239,7 +218,8 @@ assert_true(rc == -1); SSH_KEY_FREE(key); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state){ @@ -281,7 +261,7 @@ free(key_str); SSH_KEY_FREE(key); - pthread_exit(NULL); + return NULL; } static void torture_pki_rsa_import_privkey_base64(void **state) @@ -324,7 +304,8 @@ SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_publickey_from_privatekey(void **state) @@ -397,7 +378,8 @@ SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_copy_cert_to_privkey(void **state) @@ -430,7 +412,8 @@ assert_true(rc == 1); SSH_KEY_FREE(cert); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_import_cert_file(void **state) @@ -481,7 +464,8 @@ free(b64_key); free(key_buf); SSH_KEY_FREE(key); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_publickey_base64(void **state) @@ -545,7 +529,8 @@ SSH_KEY_FREE(privkey_dup); SSH_STRING_FREE_CHAR(b64_key); SSH_STRING_FREE_CHAR(b64_key_gen); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_duplicate_key(void **state) @@ -628,7 +613,8 @@ SSH_KEY_FREE(pubkey); ssh_free(session); - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_generate_key(void **state) @@ -686,7 +672,8 @@ assert_true(rc == -1); SSH_KEY_FREE(key); #endif - pthread_exit(NULL); + + return NULL; } static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) @@ -737,14 +724,8 @@ for (f = 0; f < NUM_TESTS; f++) { for (i = 0; i < NUM_THREADS; ++i) { - void *p = NULL; - uint64_t *result = NULL; - - rc = pthread_join(threads[f][i], &p); + rc = pthread_join(threads[f][i], NULL); assert_int_equal(rc, 0); - - result = (uint64_t *)p; - assert_null(result); } } } @@ -756,18 +737,21 @@ cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_key, - setup_rsa_key, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_NULL_str, - setup_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, - setup_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, setup_rsa_key, @@ -781,12 +765,8 @@ cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_generate_key, - disable_secmem, - enable_secmem), - cmocka_unit_test_setup_teardown(torture_mixed, - setup_rsa_key, - teardown), + cmocka_unit_test(torture_pki_rsa_generate_key), + cmocka_unit_test_setup_teardown(torture_mixed, setup_rsa_key, teardown), }; /* @@ -801,6 +781,7 @@ * If the library is statically linked, ssh_init() is not called * automatically */ + disable_secmem(); ssh_init(); torture_filter_tests(tests); rc = cmocka_run_group_tests(tests, NULL, NULL); diff -Nru libssh-0.11.2/tests/unittests/torture_unit_sftp.c libssh-0.11.5/tests/unittests/torture_unit_sftp.c --- libssh-0.11.2/tests/unittests/torture_unit_sftp.c 1970-01-01 01:00:00.000000000 +0100 +++ libssh-0.11.5/tests/unittests/torture_unit_sftp.c 2026-06-12 15:53:41.000000000 +0200 @@ -0,0 +1,86 @@ +#include "config.h" + +#include "sftp_common.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; +} diff -Nru libssh-0.11.2/tests/valgrind.supp libssh-0.11.5/tests/valgrind.supp --- libssh-0.11.2/tests/valgrind.supp 2025-06-24 15:34:42.000000000 +0200 +++ libssh-0.11.5/tests/valgrind.supp 2026-07-16 12:56:26.000000000 +0200 @@ -140,6 +140,40 @@ fun:FIPS_mode_set fun:OPENSSL_init_library } +{ + Threads + Failed PEM decoder do not play well openssl/openssl#29077 + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:CRYPTO_malloc + fun:CRYPTO_zalloc + fun:ossl_rcu_read_lock + fun:module_find + fun:module_run + fun:CONF_modules_load + fun:CONF_modules_load_file_ex + fun:ossl_config_int + fun:ossl_config_int + fun:ossl_init_config + fun:ossl_init_config_ossl_ + fun:__pthread_once_slow.isra.0 + fun:pthread_once@@GLIBC_2.34 + fun:CRYPTO_THREAD_run_once + fun:OPENSSL_init_crypto + fun:ossl_provider_doall_activated + fun:ossl_algorithm_do_all + fun:ossl_method_construct.constprop.0 + fun:inner_evp_generic_fetch.constprop.0 + fun:evp_generic_do_all + fun:EVP_KEYMGMT_do_all_provided + fun:ossl_decoder_ctx_setup_for_pkey + fun:OSSL_DECODER_CTX_new_for_pkey + fun:pem_read_bio_key_decoder + fun:pem_read_bio_key + fun:PEM_read_bio_PrivateKey_ex + fun:pki_private_key_from_base64 + ... +} # Cmocka { This looks like leak from cmocka when the forked server is not properly terminated @@ -207,94 +241,143 @@ Memcheck:Leak match-leak-kinds: reachable fun:malloc - fun:malloc - fun:strdup - fun:_dl_load_cache_lookup - fun:_dl_map_object - fun:dl_open_worker_begin - fun:_dl_catch_exception - fun:dl_open_worker - fun:_dl_catch_exception - fun:_dl_open - fun:do_dlopen - fun:_dl_catch_exception - fun:_dl_catch_error - fun:dlerror_run - fun:__libc_dlopen_mode - fun:module_load + ... fun:__nss_module_get_function + ... fun:getaddrinfo ... - fun:krb5_sname_to_principal - ... - fun:gss_init_sec_context - fun:ssh_packet_userauth_gssapi_response - fun:ssh_packet_process - fun:ssh_packet_socket_callback - fun:ssh_socket_pollcallback - fun:ssh_poll_ctx_dopoll - fun:ssh_handle_packets - fun:ssh_handle_packets_termination - fun:ssh_userauth_get_response - fun:ssh_userauth_gssapi - fun:torture_gssapi_auth_server_identity + fun:torture_* ... fun:_cmocka_run_group_tests fun:torture_run_tests fun:main } - +## libkrb5 +# krb5_mcc_generate_new allocates a hashtab on a static global variable +# It doesn't get freed. { - Reachable memory from getaddrinfo + Reachable memory from libkrb5 Memcheck:Leak match-leak-kinds: reachable fun:malloc - fun:UnknownInlinedFun - fun:_dl_new_object - fun:_dl_map_object_from_fd - fun:_dl_map_object - fun:dl_open_worker_begin - fun:_dl_catch_exception - fun:dl_open_worker - fun:_dl_catch_exception - fun:_dl_open - fun:do_dlopen - fun:_dl_catch_exception - fun:_dl_catch_error - fun:dlerror_run - fun:__libc_dlopen_mode - fun:module_load - fun:__nss_module_get_function - fun:getaddrinfo + fun:k5_hashtab_create ... - fun:krb5_sname_to_principal + fun:krb5_mcc_generate_new* +} +{ + Error string from acquire creds in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:acquire_cred_context.isra.0 + fun:acquire_cred_from.isra.0 + fun:gss_add_cred_from + fun:gss_acquire_cred_from +} +{ + error string from gss init sec context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string ... + fun:krb5_gss_init_sec_context_ext + fun:krb5_gss_init_sec_context fun:gss_init_sec_context - fun:ssh_packet_userauth_gssapi_response - fun:ssh_packet_process - fun:ssh_packet_socket_callback - fun:ssh_socket_pollcallback - fun:ssh_poll_ctx_dopoll - fun:ssh_handle_packets - fun:ssh_handle_packets_termination - fun:ssh_userauth_get_response - fun:ssh_userauth_gssapi - fun:torture_gssapi_auth_server_identity +} + +# Function mecherror_copy called in various +# functions of the krb5 library copies entries +# to the global error mapping table (mecherrmap m). +{ + Global error mapping table in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:mecherror_copy +} + +# Function add_error_table called in various +# functions of the krb5 library adds entries +# to a global list of error tables et_list. +{ + Global list of error tables in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:add_error_table +} + +# Function build_mechSet builds the global +# gss_OID_set_desc g_mechSet which is only +# free'd when initialized again. +{ + Global OID set in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc ... - fun:_cmocka_run_group_tests - fun:torture_run_tests - fun:main + fun:build_mechSet } -## libkrb5 -# krb5_mcc_generate_new allocates a hashtab on a static global variable -# It doesn't get freed. +# Function gssint_register_mechinfo() +# called from gssint_mechglue_init() adds +# entries to a global linked list g_mechList. { - Reachable memory from libkrb5 + Global list of gss_mech_info in krb5 Memcheck:Leak match-leak-kinds: reachable fun:malloc - fun:k5_hashtab_create ... - fun:krb5_mcc_generate_new* + fun:gssint_register_mechinfo* + ... + fun:gssint_mechglue_init +} + +# Function addConfigEntry() called during +# updateMechList() adds entries to +# a global linked list g_mechList. +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:addConfigEntry + ... + fun:updateMechList +} + +# Function loadInterMech() called during +# updateMechList() loops through the global +# linked list g_mechList and updates its entries +# with heap-alloced "interposer fields". +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:loadInterMech + ... + fun:updateMechList +} + +# Multiple krb5 functions call krb5int_open_plugin +# which opens shared libraries using dlopen. +# The plugin handle then seems to be stored in the +# main krb5 context. +{ + Plugin handles stored in the krb5 context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:dlopen* + ... + fun:krb5int_open_plugin }