Compare commits

...

4 Commits

Author SHA1 Message Date
Bulitha Kawushika De Zoysa
18d7a3967c tests: Add interoperability tests against TinySSH
This adds a new test suite 'torture_tinyssh' that verifies interoperability with the TinySSH server using various key exchange methods.

Fixes #271

Signed-off-by: Bulitha Kawushika De Zoysa <bulithakaushika99@gmail.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-20 10:56:46 +01:00
Antoni Bertolin Monferrer
d45ce10c83 channels: Fix OOM error check after strdup
The fix allows the code to properly check if the strdup failed to allocate a
char buffer for the exit signal.

Signed-off-by: Antoni Bertolin Monferrer <antoni.monferrer@canonical.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-20 10:55:17 +01:00
Pavol Žáčik
a7fd80795e Update recently added logging to be less verbose
In 20d9642c and parent commits, log levels were
recategorized to be less verbose when using the
level INFO and lower. These levels should not
print any information redundant to the end user.

This commit fixes recently added uses of logging
that are not consistent with the abovementioned
categorization, in particular:

- logs in ssh_strict_fopen should not have
  the RARE/WARNING level since failing to open
  a file may not be an issue at all (e.g., when
  trying to open the knownhosts file).

- logging the username used in authentication
  or proxyjump-related information should be done
  at the DEBUG level, otherwise it could pollute
  the output of, e.g., curl.

Signed-off-by: Pavol Žáčik <pzacik@redhat.com>
Reviewed-by: Jakub Jelen <jjelen@redhat.com>
2026-02-20 10:54:27 +01:00
Jakub Jelen
f8cba20859 Add back Security section to 0.12.0 changelog
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Pavol Žáčik <pzacik@redhat.com>
2026-02-12 14:54:06 +01:00
12 changed files with 388 additions and 10 deletions

View File

@@ -2,6 +2,17 @@ CHANGELOG
=========
version 0.12.0 (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
* Deprecations and removals:
* Bumped minimal RSA key size to 1024 bits
* New functionality:

View File

@@ -1397,7 +1397,7 @@ int ssh_userauth_publickey_auto(ssh_session session,
return SSH_AUTH_ERROR;
}
SSH_LOG(SSH_LOG_INFO,
SSH_LOG(SSH_LOG_DEBUG,
"Starting authentication as a user %s",
username ? username : session->opts.username);

View File

@@ -3517,7 +3517,7 @@ int ssh_channel_get_exit_state(ssh_channel channel,
*pexit_signal = NULL;
if (channel->exit.signal != NULL) {
*pexit_signal = strdup(channel->exit.signal);
if (pexit_signal == NULL) {
if (*pexit_signal == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}

View File

@@ -258,7 +258,9 @@ local_parse_file(ssh_session session,
f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE);
if (f == NULL) {
/* The underlying function logs the reasons */
SSH_LOG(SSH_LOG_RARE,
"Failed to open included configuration file %s",
filename);
return;
}

View File

@@ -2454,7 +2454,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
/* open first to avoid TOCTOU */
fd = open(filename, O_RDONLY);
if (fd == -1) {
SSH_LOG(SSH_LOG_RARE,
SSH_LOG(SSH_LOG_TRACE,
"Failed to open a file %s for reading: %s",
filename,
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
@@ -2464,7 +2464,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
/* Check the file is sensible for a configuration file */
r = fstat(fd, &sb);
if (r != 0) {
SSH_LOG(SSH_LOG_RARE,
SSH_LOG(SSH_LOG_TRACE,
"Failed to stat %s: %s",
filename,
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
@@ -2472,7 +2472,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
return NULL;
}
if ((sb.st_mode & S_IFMT) != S_IFREG) {
SSH_LOG(SSH_LOG_RARE,
SSH_LOG(SSH_LOG_TRACE,
"The file %s is not a regular file: skipping",
filename);
close(fd);
@@ -2480,7 +2480,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
}
if ((size_t)sb.st_size > max_file_size) {
SSH_LOG(SSH_LOG_RARE,
SSH_LOG(SSH_LOG_TRACE,
"The file %s is too large (%jd MB > %zu MB): skipping",
filename,
(intmax_t)sb.st_size / 1024 / 1024,
@@ -2491,7 +2491,7 @@ FILE *ssh_strict_fopen(const char *filename, size_t max_file_size)
f = fdopen(fd, "r");
if (f == NULL) {
SSH_LOG(SSH_LOG_RARE,
SSH_LOG(SSH_LOG_TRACE,
"Failed to open a file %s for reading: %s",
filename,
ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX));

View File

@@ -1435,7 +1435,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
session = s->session;
SSH_LOG(SSH_LOG_INFO,
SSH_LOG(SSH_LOG_DEBUG,
"Connecting to host %s port %d user %s through ProxyJump",
session->opts.host,
session->opts.port,
@@ -1515,7 +1515,7 @@ ssh_socket_connect_proxyjump(ssh_socket s)
/* transferred to the jump_thread_data */
jump_session = NULL;
SSH_LOG(SSH_LOG_INFO,
SSH_LOG(SSH_LOG_DEBUG,
"Starting proxy thread to host %s port %d user %s, callbacks=%p",
jump_thread_data->next_jump->hostname,
jump_thread_data->next_jump->port,

View File

@@ -259,6 +259,20 @@ else()
set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found")
endif()
find_program(TINYSSHD_EXECUTABLE
NAMES
tinysshd
PATHS
/sbin
/usr/sbin
/usr/local/sbin)
if (TINYSSHD_EXECUTABLE)
message(STATUS "Found TinySSH server: ${TINYSSHD_EXECUTABLE}")
else()
message(STATUS "Could NOT find TinySSH server")
endif()
find_program(SSHD_EXECUTABLE
NAME
sshd
@@ -391,6 +405,14 @@ if (CLIENT_TESTING OR SERVER_TESTING)
file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE)
# TINYSSH Key Setup
set(TINYSSH_KEY_DIR "${CMAKE_CURRENT_BINARY_DIR}/etc/tinyssh")
file(MAKE_DIRECTORY ${TINYSSH_KEY_DIR})
# Copy the TINYSSH hostkeys
file(COPY keys/ed25519.pk DESTINATION ${TINYSSH_KEY_DIR})
file(COPY keys/.ed25519.sk DESTINATION ${TINYSSH_KEY_DIR})
# Allow to auth with bob's public keys on alice and doe account
configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY)
configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY)

View File

@@ -36,6 +36,10 @@ if (WITH_PKCS11_URI)
torture_auth_pkcs11)
endif()
if (TINYSSHD_EXECUTABLE AND NCAT_EXECUTABLE)
set(LIBSSH_CLIENT_TESTS ${LIBSSH_CLIENT_TESTS} torture_tinyssh)
endif()
if (HAVE_PTHREAD)
set(LIBSSH_CLIENT_TESTS
${LIBSSH_CLIENT_TESTS}
@@ -92,6 +96,12 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS})
LINK_LIBRARIES ${TORTURE_LIBRARY} util
)
if (_CLI_TEST STREQUAL "torture_tinyssh")
target_compile_definitions(${_CLI_TEST} PRIVATE
TINYSSH_KEYS_DIR="${TINYSSH_KEY_DIR}"
)
endif()
if (OSX)
set_property(
TEST

View File

@@ -0,0 +1,328 @@
/*
* This file is part of the SSH Library
*
* Copyright (c) 2026 by Your Bulitha Kawushika De Zoysa
*
* 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"
#include "tests_config.h"
#define LIBSSH_STATIC
#include "libssh/libssh.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include "torture.h"
#include <errno.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define TINYSSH_PIDFILE "tinyssh.pid"
#define TINYSSH_PORT 22
/* TINYSSH Server Setup and Teardown */
static int tinyssh_setup(void **state)
{
struct torture_state *s = NULL;
char cmd[4096];
char pid_path[1024];
int rc;
torture_setup_socket_dir(state);
s = *state;
snprintf(pid_path,
sizeof(pid_path),
"%s/%s",
s->socket_dir,
TINYSSH_PIDFILE);
free(s->srv_pidfile);
s->srv_pidfile = strdup(pid_path);
if (s->srv_pidfile == NULL) {
return -1;
}
snprintf(cmd,
sizeof(cmd),
"%s -l %s %d -k -c \"%s %s -v %s\" "
"> %s/tinyssh.log 2>&1 & echo $! > %s",
NCAT_EXECUTABLE,
TORTURE_SSH_SERVER,
TINYSSH_PORT,
TINYSSHD_EXECUTABLE,
"",
TINYSSH_KEYS_DIR,
s->socket_dir,
s->srv_pidfile);
SSH_LOG(SSH_LOG_DEBUG, "Executing: %s\n", cmd);
rc = system(cmd);
if (rc != 0) {
return -1;
}
rc = torture_wait_for_daemon(15);
if (rc != 0) {
return -1;
}
return 0;
}
static int tinyssh_teardown(void **state)
{
struct torture_state *s = *state;
torture_terminate_process(s->srv_pidfile);
torture_teardown_socket_dir(state);
return 0;
}
/* LIBSSH Client Setup and Teardown */
static int session_setup(void **state)
{
struct torture_state *s = *state;
int verbosity = torture_libssh_verbosity();
bool process_config = false;
int port = TINYSSH_PORT;
struct passwd *pwd = NULL;
int rc;
pwd = getpwnam("bob");
assert_non_null(pwd);
rc = setuid(pwd->pw_uid);
assert_return_code(rc, errno);
s->ssh.session = ssh_new();
assert_non_null(s->ssh.session);
ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER);
ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT, &port);
ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "bob");
ssh_options_set(s->ssh.session,
SSH_OPTIONS_PROCESS_CONFIG,
&process_config);
return 0;
}
static int session_teardown(void **state)
{
struct torture_state *s = *state;
if (s->ssh.session) {
ssh_disconnect(s->ssh.session);
ssh_free(s->ssh.session);
}
return 0;
}
/* Algorithms Helper */
static void test_specific_algorithm(ssh_session session,
const char *kex,
const char *cipher,
const char *hostkey,
int expected_rc)
{
int rc;
char data[256];
size_t len_to_test[] = {1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 15,
16, 20, 31, 32, 33, 63, 64, 65, 100, 127, 128};
unsigned int i;
/* Set Key Exchange */
if (kex != NULL) {
rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex);
assert_ssh_return_code(session, rc);
}
/* Set Ciphers */
if (cipher != NULL) {
rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher);
assert_ssh_return_code(session, rc);
rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher);
assert_ssh_return_code(session, rc);
}
/* Set Hostkey */
if (hostkey != NULL) {
rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey);
assert_ssh_return_code(session, rc);
}
rc = ssh_connect(session);
if (expected_rc == SSH_OK) {
assert_ssh_return_code(session, rc);
if (cipher != NULL) {
const char *used_cipher = ssh_get_cipher_out(session);
assert_non_null(used_cipher);
assert_string_equal(used_cipher, cipher);
}
if (hostkey != NULL) {
ssh_key pubkey = NULL;
const char *type_str = NULL;
rc = ssh_get_server_publickey(session, &pubkey);
assert_int_equal(rc, SSH_OK);
assert_non_null(pubkey);
type_str = ssh_key_type_to_char(ssh_key_type(pubkey));
assert_non_null(type_str);
assert_string_equal(type_str, hostkey);
ssh_key_free(pubkey);
}
memset(data, 0, sizeof(data));
for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) {
memset(data, 'A', len_to_test[i]);
ssh_send_ignore(session, data);
ssh_handle_packets(session, 50);
}
rc = ssh_userauth_none(session, NULL);
if (rc != SSH_OK) {
rc = ssh_get_error_code(session);
assert_int_equal(rc, SSH_REQUEST_DENIED);
}
} else {
assert_int_not_equal(rc, SSH_OK);
}
}
/* Test Cases */
static void torture_tinyssh_curve25519(void **state)
{
struct torture_state *s = *state;
test_specific_algorithm(s->ssh.session,
"curve25519-sha256",
NULL,
NULL,
SSH_OK);
}
static void torture_tinyssh_curve25519_libssh(void **state)
{
struct torture_state *s = *state;
test_specific_algorithm(s->ssh.session,
"curve25519-sha256@libssh.org",
NULL,
NULL,
SSH_OK);
}
static void torture_tinyssh_sntrup761(void **state)
{
struct torture_state *s = *state;
test_specific_algorithm(s->ssh.session,
"sntrup761x25519-sha512@openssh.com",
NULL,
NULL,
SSH_OK);
}
static void torture_tinyssh_chacha20(void **state)
{
struct torture_state *s = *state;
test_specific_algorithm(s->ssh.session,
NULL,
"chacha20-poly1305@openssh.com",
NULL,
SSH_OK);
}
static void torture_tinyssh_neg_cipher(void **state)
{
struct torture_state *s = *state;
/* TinySSH does not support older ciphers like aes128-cbc.*/
test_specific_algorithm(s->ssh.session,
NULL,
"aes128-cbc",
NULL,
SSH_ERROR);
}
static void torture_tinyssh_hostkey_ed25519(void **state)
{
struct torture_state *s = *state;
test_specific_algorithm(s->ssh.session, NULL, NULL, "ssh-ed25519", SSH_OK);
}
static void torture_tinyssh_neg_kex(void **state)
{
struct torture_state *s = *state;
/* TinySSH does not support legacy Diffie-Hellman groups or NIST curves.*/
test_specific_algorithm(s->ssh.session,
"diffie-hellman-group1-sha1",
NULL,
NULL,
SSH_ERROR);
}
int torture_run_tests(void)
{
int rc;
struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_tinyssh_curve25519_libssh,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_tinyssh_sntrup761,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_tinyssh_hostkey_ed25519,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_tinyssh_chacha20,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_tinyssh_neg_cipher,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_tinyssh_neg_kex,
session_setup,
session_teardown),
};
ssh_init();
torture_filter_tests(tests);
rc = cmocka_run_group_tests(tests, tinyssh_setup, tinyssh_teardown);
ssh_finalize();
return rc;
}

BIN
tests/keys/.ed25519.sk Normal file

Binary file not shown.

1
tests/keys/ed25519.pk Normal file
View File

@@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>C<EFBFBD><EFBFBD>K<EFBFBD>ݛ<EFBFBD>1<1C><>'j &<26>e<EFBFBD><65><EFBFBD><EF8A8D>

View File

@@ -86,3 +86,7 @@
#cmakedefine PKCS11SPY "${PKCS11SPY}"
#cmakedefine HAVE_SK_DUMMY 1
#cmakedefine SK_DUMMY_LIBRARY_PATH "${SK_DUMMY_LIBRARY_PATH}"
/* TinySSH Executable */
#cmakedefine TINYSSHD_EXECUTABLE "${TINYSSHD_EXECUTABLE}"