diff -Nru libssh-0.11.2/CHANGELOG libssh-0.11.4/CHANGELOG --- libssh-0.11.2/CHANGELOG 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/CHANGELOG 2026-02-10 09:47:29.000000000 +0000 @@ -1,6 +1,29 @@ CHANGELOG ========= +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.4/CMakeLists.txt --- libssh-0.11.2/CMakeLists.txt 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/CMakeLists.txt 2026-02-10 09:47:29.000000000 +0000 @@ -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.4 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.4") 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.4/DefineOptions.cmake --- libssh-0.11.2/DefineOptions.cmake 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/DefineOptions.cmake 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/config.h.cmake --- libssh-0.11.2/config.h.cmake 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/config.h.cmake 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/debian/changelog --- libssh-0.11.2/debian/changelog 2025-11-03 23:32:14.000000000 +0000 +++ libssh-0.11.4/debian/changelog 2026-03-15 09:18:15.000000000 +0000 @@ -1,3 +1,18 @@ +libssh (0.11.4-0+deb13u1) trixie-security; urgency=medium + + * New upstream security/bug fix release: + - 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 + (Closes: #1127693) + + -- Martin Pitt Sun, 15 Mar 2026 09:18:15 +0000 + 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.4/debian/gbp.conf --- libssh-0.11.2/debian/gbp.conf 2025-06-28 05:42:47.000000000 +0000 +++ libssh-0.11.4/debian/gbp.conf 2026-03-15 09:18:15.000000000 +0000 @@ -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.4/debian/patches/CVE-2025-8114.patch --- libssh-0.11.2/debian/patches/CVE-2025-8114.patch 2025-11-03 23:32:14.000000000 +0000 +++ libssh-0.11.4/debian/patches/CVE-2025-8114.patch 1970-01-01 00:00:00.000000000 +0000 @@ -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.4/debian/patches/CVE-2025-8277.patch --- libssh-0.11.2/debian/patches/CVE-2025-8277.patch 2025-11-03 23:32:14.000000000 +0000 +++ libssh-0.11.4/debian/patches/CVE-2025-8277.patch 1970-01-01 00:00:00.000000000 +0000 @@ -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.4/debian/patches/series --- libssh-0.11.2/debian/patches/series 2025-11-03 23:32:14.000000000 +0000 +++ libssh-0.11.4/debian/patches/series 2026-03-15 09:18:15.000000000 +0000 @@ -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.4/doc/guided_tour.dox --- libssh-0.11.2/doc/guided_tour.dox 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/doc/guided_tour.dox 2026-02-10 09:47:29.000000000 +0000 @@ -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/include/libssh/crypto.h libssh-0.11.4/include/libssh/crypto.h --- libssh-0.11.2/include/libssh/crypto.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/include/libssh/crypto.h 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/include/libssh/libcrypto.h --- libssh-0.11.2/include/libssh/libcrypto.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/include/libssh/libcrypto.h 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/include/libssh/misc.h --- libssh-0.11.2/include/libssh/misc.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/include/libssh/misc.h 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/include/libssh/poll.h --- libssh-0.11.2/include/libssh/poll.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/include/libssh/poll.h 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/include/libssh/priv.h --- libssh-0.11.2/include/libssh/priv.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/include/libssh/priv.h 2026-02-10 09:47:29.000000000 +0000 @@ -473,6 +473,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/src/ABI/current libssh-0.11.4/src/ABI/current --- libssh-0.11.2/src/ABI/current 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/ABI/current 2026-02-10 09:47:29.000000000 +0000 @@ -1 +1 @@ -4.10.2 \ No newline at end of file +4.10.4 \ No newline at end of file diff -Nru libssh-0.11.2/src/ABI/libssh-4.10.3.symbols libssh-0.11.4/src/ABI/libssh-4.10.3.symbols --- libssh-0.11.2/src/ABI/libssh-4.10.3.symbols 1970-01-01 00:00:00.000000000 +0000 +++ libssh-0.11.4/src/ABI/libssh-4.10.3.symbols 2026-02-10 09:39:10.000000000 +0000 @@ -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.4/src/ABI/libssh-4.10.4.symbols --- libssh-0.11.2/src/ABI/libssh-4.10.4.symbols 1970-01-01 00:00:00.000000000 +0000 +++ libssh-0.11.4/src/ABI/libssh-4.10.4.symbols 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/src/auth.c --- libssh-0.11.2/src/auth.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/auth.c 2026-02-10 09:47:29.000000000 +0000 @@ -1294,6 +1294,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; diff -Nru libssh-0.11.2/src/bind.c libssh-0.11.4/src/bind.c --- libssh-0.11.2/src/bind.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/bind.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/bind_config.c --- libssh-0.11.2/src/bind_config.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/bind_config.c 2026-02-10 09:47:29.000000000 +0000 @@ -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/client.c libssh-0.11.4/src/client.c --- libssh-0.11.2/src/client.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/client.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/config.c --- libssh-0.11.2/src/config.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/config.c 2026-02-10 09:47:29.000000000 +0000 @@ -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/connect.c libssh-0.11.4/src/connect.c --- libssh-0.11.2/src/connect.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/connect.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/src/connector.c --- libssh-0.11.2/src/connector.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/connector.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/src/dh-gex.c --- libssh-0.11.2/src/dh-gex.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/dh-gex.c 2026-02-10 09:47:29.000000000 +0000 @@ -519,9 +519,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.4/src/dh_crypto.c --- libssh-0.11.2/src/dh_crypto.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/dh_crypto.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/dh_key.c --- libssh-0.11.2/src/dh_key.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/dh_key.c 2026-02-10 09:47:00.000000000 +0000 @@ -237,6 +237,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.4/src/ecdh_crypto.c --- libssh-0.11.2/src/ecdh_crypto.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/ecdh_crypto.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/ecdh_gcrypt.c --- libssh-0.11.2/src/ecdh_gcrypt.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/ecdh_gcrypt.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/ecdh_mbedcrypto.c --- libssh-0.11.2/src/ecdh_mbedcrypto.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/ecdh_mbedcrypto.c 2026-02-10 09:47:00.000000000 +0000 @@ -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/kex.c libssh-0.11.4/src/kex.c --- libssh-0.11.2/src/kex.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/kex.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/known_hosts.c --- libssh-0.11.2/src/known_hosts.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/known_hosts.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/src/knownhosts.c --- libssh-0.11.2/src/knownhosts.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/knownhosts.c 2026-02-10 09:47:29.000000000 +0000 @@ -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", @@ -346,6 +357,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 +414,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 +438,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 +491,7 @@ return list; error: + ssh_knownhosts_entries_free(entry_list); ssh_list_free(list); return NULL; } @@ -511,6 +543,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 +588,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 +597,7 @@ &entry_list); SAFE_FREE(host_port); if (rc != 0) { - ssh_list_free(entry_list); + ssh_knownhosts_entries_free(entry_list); return NULL; } @@ -805,7 +838,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 +898,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 +909,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 +921,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 +1108,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 +1144,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; } @@ -1202,6 +1216,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.4/src/libcrypto.c --- libssh-0.11.2/src/libcrypto.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/libcrypto.c 2026-02-10 09:47:00.000000000 +0000 @@ -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 @@ -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.4/src/match.c --- libssh-0.11.2/src/match.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/match.c 2026-02-10 09:47:29.000000000 +0000 @@ -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/misc.c libssh-0.11.4/src/misc.c --- libssh-0.11.2/src/misc.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/misc.c 2026-02-10 09:47:29.000000000 +0000 @@ -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; } @@ -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.4/src/options.c --- libssh-0.11.2/src/options.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/options.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/src/packet.c --- libssh-0.11.2/src/packet.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/packet.c 2026-02-10 09:47:00.000000000 +0000 @@ -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; diff -Nru libssh-0.11.2/src/pki.c libssh-0.11.4/src/pki.c --- libssh-0.11.2/src/pki.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/pki.c 2026-02-10 09:47:29.000000000 +0000 @@ -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); } @@ -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. + * 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[in] type The type of the cert to format. - * - * @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.4/src/pki_crypto.c --- libssh-0.11.2/src/pki_crypto.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/pki_crypto.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/pki_gcrypt.c --- libssh-0.11.2/src/pki_gcrypt.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/pki_gcrypt.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/pki_mbedcrypto.c --- libssh-0.11.2/src/pki_mbedcrypto.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/pki_mbedcrypto.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/poll.c --- libssh-0.11.2/src/poll.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/poll.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/scp.c --- libssh-0.11.2/src/scp.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/scp.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/src/server.c --- libssh-0.11.2/src/server.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/server.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/src/sftp.c --- libssh-0.11.2/src/sftp.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/sftp.c 2026-02-10 09:47:29.000000000 +0000 @@ -567,44 +567,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; - } - - if (idx > sftp->ext->count) { - 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 (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; - } - - if (idx > sftp->ext->count) { - 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 (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->data[idx]; + return sftp->ext->data[idx]; } int sftp_extension_supported(sftp_session sftp, const char *name, diff -Nru libssh-0.11.2/src/sftp_common.c libssh-0.11.4/src/sftp_common.c --- libssh-0.11.2/src/sftp_common.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/sftp_common.c 2026-02-10 09:47:29.000000000 +0000 @@ -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) { diff -Nru libssh-0.11.2/src/socket.c libssh-0.11.4/src/socket.c --- libssh-0.11.2/src/socket.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/socket.c 2026-02-10 09:47:29.000000000 +0000 @@ -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; } @@ -1096,7 +1096,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 +1205,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/wrapper.c libssh-0.11.4/src/wrapper.c --- libssh-0.11.2/src/wrapper.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/src/wrapper.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/CMakeLists.txt --- libssh-0.11.2/tests/CMakeLists.txt 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/CMakeLists.txt 2026-02-10 09:47:00.000000000 +0000 @@ -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/torture_gssapi_auth.c libssh-0.11.4/tests/client/torture_gssapi_auth.c --- libssh-0.11.2/tests/client/torture_gssapi_auth.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/client/torture_gssapi_auth.c 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/tests/client/torture_rekey.c --- libssh-0.11.2/tests/client/torture_rekey.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/client/torture_rekey.c 2026-02-10 09:47:00.000000000 +0000 @@ -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_sftp_init.c libssh-0.11.4/tests/client/torture_sftp_init.c --- libssh-0.11.2/tests/client/torture_sftp_init.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/client/torture_sftp_init.c 2026-02-10 09:47:29.000000000 +0000 @@ -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/fuzz/CMakeLists.txt libssh-0.11.4/tests/fuzz/CMakeLists.txt --- libssh-0.11.2/tests/fuzz/CMakeLists.txt 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/fuzz/CMakeLists.txt 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/fuzz/fuzzer.c --- libssh-0.11.2/tests/fuzz/fuzzer.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/fuzz/fuzzer.c 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/tests/pkcs11/setup-softhsm-tokens.sh --- libssh-0.11.2/tests/pkcs11/setup-softhsm-tokens.sh 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/pkcs11/setup-softhsm-tokens.sh 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/tests/pkd/CMakeLists.txt --- libssh-0.11.2/tests/pkd/CMakeLists.txt 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/pkd/CMakeLists.txt 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/tests/pkd/pkd_client.h --- libssh-0.11.2/tests/pkd/pkd_client.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/pkd/pkd_client.h 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/pkd/pkd_hello.c --- libssh-0.11.2/tests/pkd/pkd_hello.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/pkd/pkd_hello.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/server/test_server/CMakeLists.txt --- libssh-0.11.2/tests/server/test_server/CMakeLists.txt 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/server/test_server/CMakeLists.txt 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/server/test_server/default_cb.c --- libssh-0.11.2/tests/server/test_server/default_cb.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/server/test_server/default_cb.c 2026-02-10 09:47:00.000000000 +0000 @@ -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/test_server.c libssh-0.11.4/tests/server/test_server/test_server.c --- libssh-0.11.2/tests/server/test_server/test_server.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/server/test_server/test_server.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/server/test_server/testserver_common.c --- libssh-0.11.2/tests/server/test_server/testserver_common.c 1970-01-01 00:00:00.000000000 +0000 +++ libssh-0.11.4/tests/server/test_server/testserver_common.c 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/tests/server/test_server/testserver_common.h --- libssh-0.11.2/tests/server/test_server/testserver_common.h 1970-01-01 00:00:00.000000000 +0000 +++ libssh-0.11.4/tests/server/test_server/testserver_common.h 2026-01-15 09:22:56.000000000 +0000 @@ -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.4/tests/server/torture_gssapi_server_auth.c --- libssh-0.11.2/tests/server/torture_gssapi_server_auth.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/server/torture_gssapi_server_auth.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/server/torture_gssapi_server_auth_cb.c --- libssh-0.11.2/tests/server/torture_gssapi_server_auth_cb.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/server/torture_gssapi_server_auth_cb.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/server/torture_gssapi_server_delegation.c --- libssh-0.11.2/tests/server/torture_gssapi_server_delegation.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/server/torture_gssapi_server_delegation.c 2026-02-10 09:47:00.000000000 +0000 @@ -371,5 +371,5 @@ teardown_default_server); ssh_finalize(); - pthread_exit((void *)&rc); + return rc; } diff -Nru libssh-0.11.2/tests/ssh_ping.c libssh-0.11.4/tests/ssh_ping.c --- libssh-0.11.2/tests/ssh_ping.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/ssh_ping.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/tests_config.h.cmake --- libssh-0.11.2/tests/tests_config.h.cmake 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/tests_config.h.cmake 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/torture.c --- libssh-0.11.2/tests/torture.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/torture.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/torture.h --- libssh-0.11.2/tests/torture.h 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/torture.h 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/unittests/CMakeLists.txt --- libssh-0.11.2/tests/unittests/CMakeLists.txt 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/CMakeLists.txt 2026-02-10 09:47:29.000000000 +0000 @@ -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_config.c libssh-0.11.4/tests/unittests/torture_config.c --- libssh-0.11.2/tests/unittests/torture_config.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_config.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/tests/unittests/torture_knownhosts_parsing.c --- libssh-0.11.2/tests/unittests/torture_knownhosts_parsing.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_knownhosts_parsing.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/tests/unittests/torture_misc.c --- libssh-0.11.2/tests/unittests/torture_misc.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_misc.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/tests/unittests/torture_pki_ecdsa.c --- libssh-0.11.2/tests/unittests/torture_pki_ecdsa.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_pki_ecdsa.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/unittests/torture_pki_ed25519.c --- libssh-0.11.2/tests/unittests/torture_pki_ed25519.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_pki_ed25519.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/unittests/torture_pki_rsa.c --- libssh-0.11.2/tests/unittests/torture_pki_rsa.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_pki_rsa.c 2026-02-10 09:47:00.000000000 +0000 @@ -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.4/tests/unittests/torture_threads_pki_rsa.c --- libssh-0.11.2/tests/unittests/torture_threads_pki_rsa.c 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_threads_pki_rsa.c 2026-01-20 15:38:01.000000000 +0000 @@ -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.4/tests/unittests/torture_unit_sftp.c --- libssh-0.11.2/tests/unittests/torture_unit_sftp.c 1970-01-01 00:00:00.000000000 +0000 +++ libssh-0.11.4/tests/unittests/torture_unit_sftp.c 2026-02-10 09:47:29.000000000 +0000 @@ -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.4/tests/valgrind.supp --- libssh-0.11.2/tests/valgrind.supp 2025-06-24 13:34:42.000000000 +0000 +++ libssh-0.11.4/tests/valgrind.supp 2026-02-10 09:47:00.000000000 +0000 @@ -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,51 @@ 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:getaddrinfo ... - 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: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:krb5_gss_save_error_string ... - fun:_cmocka_run_group_tests - fun:torture_run_tests - fun:main + fun:acquire_cred_context.isra.0 + fun:acquire_cred_from.isra.0 + fun:gss_add_cred_from + fun:gss_acquire_cred_from } - -## libkrb5 -# krb5_mcc_generate_new allocates a hashtab on a static global variable -# It doesn't get freed. { - Reachable memory from libkrb5 + error string from gss init sec context Memcheck:Leak match-leak-kinds: reachable fun:malloc - fun:k5_hashtab_create ... - fun:krb5_mcc_generate_new* + fun:krb5_gss_save_error_string + ... + fun:krb5_gss_init_sec_context_ext + fun:krb5_gss_init_sec_context + fun:gss_init_sec_context }