1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-12-09 15:41:10 +03:00
Files
libssh/tests/client/torture_session.c
Jakub Jelen ef50a3c0f0 tests: Remove tests of operations on freed channels
These tests are flaky because even though the care was taken to guess if
the ssh_channel_free() really freed the channel, it might not always be correct
and call to operation on the freed channel results in use after free.

Generally, no operation should be called after the channel is freed by the user.

Signed-off-by: Jakub Jelen <jjelen@redhat.com>
Reviewed-by: Andreas Schneider <asn@cryptomilk.org>
2025-08-06 11:18:45 +02:00

552 lines
16 KiB
C

/*
* This file is part of the SSH Library
*
* Copyright (c) 2012 by Aris Adamantiadis
*
* 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"
#define LIBSSH_STATIC
#include "torture.h"
#include "libssh/libssh.h"
#include "libssh/priv.h"
#include "libssh/session.h"
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>
#define BUFLEN 4096
static char buffer[BUFLEN];
static int sshd_setup(void **state)
{
torture_setup_sshd_server(state, false);
return 0;
}
static int sshd_teardown(void **state) {
torture_teardown_sshd_server(state);
return 0;
}
static int session_setup(void **state)
{
struct torture_state *s = *state;
struct passwd *pwd;
int rc;
pwd = getpwnam("bob");
assert_non_null(pwd);
rc = setuid(pwd->pw_uid);
assert_return_code(rc, errno);
s->ssh.session = torture_ssh_session(s,
TORTURE_SSH_SERVER,
NULL,
TORTURE_SSH_USER_ALICE,
NULL);
assert_non_null(s->ssh.session);
return 0;
}
static int session_teardown(void **state)
{
struct torture_state *s = *state;
ssh_disconnect(s->ssh.session);
ssh_free(s->ssh.session);
return 0;
}
static void torture_channel_read_error(void **state) {
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
int rc;
int fd;
int i;
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
rc = ssh_channel_request_exec(channel, "hexdump -C /dev/urandom");
assert_ssh_return_code(session, rc);
/* send crap and for server to send us a disconnect */
fd = ssh_get_fd(session);
assert_true(fd > 2);
rc = write(fd, "AAAA", 4);
assert_int_equal(rc, 4);
for (i=0;i<20;++i){
rc = ssh_channel_read(channel,buffer,sizeof(buffer),0);
if (rc == SSH_ERROR)
break;
}
#if OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 7
/* With openssh 6.7 this doesn't produce and error anymore */
assert_ssh_return_code(session, rc);
#else
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
#endif
ssh_channel_free(channel);
}
static void torture_channel_poll_timeout_valid(void **state) {
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
int rc;
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
rc = ssh_channel_request_exec(channel, "echo -n ABCD");
assert_ssh_return_code(session, rc);
rc = ssh_channel_poll_timeout(channel, 500, 0);
assert_int_equal(rc, strlen("ABCD"));
}
static void torture_channel_poll_timeout(void **state) {
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
int rc;
int fd;
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
fd = ssh_get_fd(session);
assert_true(fd > 2);
rc = ssh_channel_poll_timeout(channel, 500, 0);
assert_int_equal(rc, SSH_OK);
/* send crap and for server to send us a disconnect */
rc = write(fd, "AAAA", 4);
assert_int_equal(rc, 4);
rc = ssh_channel_poll_timeout(channel, 500, 0);
assert_int_equal(rc, SSH_ERROR);
ssh_channel_free(channel);
}
/*
* Check that the client can properly handle the error returned from the server
* when the maximum number of sessions is exceeded.
*
* Related: T75, T239
*
*/
static void torture_max_sessions(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
char max_session_config[32] = {0};
#define MAX_CHANNELS 10
ssh_channel channels[MAX_CHANNELS + 1];
size_t i;
int rc;
snprintf(max_session_config,
sizeof(max_session_config),
"MaxSessions %u",
MAX_CHANNELS);
/* Update server configuration to limit number of sessions */
torture_update_sshd_config(state, max_session_config);
/* Open the maximum number of channel sessions */
for (i = 0; i < MAX_CHANNELS; i++) {
channels[i] = ssh_channel_new(session);
assert_non_null(channels[i]);
rc = ssh_channel_open_session(channels[i]);
assert_ssh_return_code(session, rc);
}
/* Try to open an extra session and expect failure */
channels[i] = ssh_channel_new(session);
assert_non_null(channels[i]);
rc = ssh_channel_open_session(channels[i]);
assert_int_equal(rc, SSH_ERROR);
/* Free the unused channel */
ssh_channel_free(channels[i]);
/* Close and free channels */
for (i = 0; i < MAX_CHANNELS; i++) {
ssh_channel_close(channels[i]);
ssh_channel_free(channels[i]);
}
#undef MAX_CHANNELS
}
static void torture_no_more_sessions(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channels[2];
int rc;
/* Open a channel session */
channels[0] = ssh_channel_new(session);
assert_non_null(channels[0]);
rc = ssh_channel_open_session(channels[0]);
assert_ssh_return_code(session, rc);
/* Send no-more-sessions@openssh.com global request */
rc = ssh_request_no_more_sessions(session);
assert_ssh_return_code(session, rc);
/* Try to open an extra session and expect failure */
channels[1] = ssh_channel_new(session);
assert_non_null(channels[1]);
rc = ssh_channel_open_session(channels[1]);
assert_int_equal(rc, SSH_ERROR);
/* Free the unused channel */
ssh_channel_free(channels[1]);
/* Close and free open channel */
ssh_channel_close(channels[0]);
ssh_channel_free(channels[0]);
}
static void torture_channel_delayed_close(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
char request[256];
char buff[256] = {0};
int rc;
int fd;
snprintf(request, 256,
"dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file");
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
fd = ssh_get_fd(session);
assert_true(fd > 2);
/* Make the request, read parts with close */
rc = ssh_channel_request_exec(channel, request);
assert_ssh_return_code(session, rc);
do {
rc = ssh_channel_read(channel, buff, 256, 0);
} while(rc > 0);
assert_ssh_return_code(session, rc);
rc = ssh_channel_poll_timeout(channel, 500, 0);
assert_int_equal(rc, SSH_EOF);
ssh_channel_free(channel);
}
/* Ensure that calling 'ssh_channel_poll' on a freed channel does not lead to
* segmentation faults. */
static void torture_freed_channel_poll(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
char request[256];
int rc;
snprintf(request, 256,
"dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file");
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
/* Make the request, read parts with close */
rc = ssh_channel_request_exec(channel, request);
assert_ssh_return_code(session, rc);
ssh_channel_free(channel);
rc = ssh_channel_poll(channel, 0);
assert_int_equal(rc, SSH_ERROR);
}
/* Ensure that calling 'ssh_channel_read_nonblocking' on a freed channel does
* not lead to segmentation faults. */
static void torture_freed_channel_read_nonblocking(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
char request[256];
char buff[256] = {0};
int rc;
snprintf(request, 256,
"dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file");
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
/* Make the request, read parts with close */
rc = ssh_channel_request_exec(channel, request);
assert_ssh_return_code(session, rc);
ssh_channel_free(channel);
rc = ssh_channel_read_nonblocking(channel, buff, 256, 0);
assert_ssh_return_code_equal(session, rc, SSH_ERROR);
}
static void torture_channel_exit_status(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel = NULL;
char request[256];
uint32_t exit_status = (uint32_t)-1;
int rc;
rc = snprintf(request, sizeof(request), "true");
assert_return_code(rc, errno);
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
/* Make the request, read parts with close */
rc = ssh_channel_request_exec(channel, request);
assert_ssh_return_code(session, rc);
exit_status = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL);
assert_ssh_return_code(session, rc);
assert_int_equal(exit_status, 0);
}
static void torture_channel_exit_signal(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel = NULL;
char request[256];
uint32_t exit_status = (uint32_t)-1;
char *exit_signal = NULL;
int core_dumped = false;
int rc;
rc = snprintf(request, sizeof(request), "cat");
assert_return_code(rc, errno);
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
/* Make the request, read parts with close */
rc = ssh_channel_request_exec(channel, request);
assert_ssh_return_code(session, rc);
rc = ssh_channel_request_send_signal(channel, "TERM");
assert_ssh_return_code(session, rc);
rc = ssh_channel_get_exit_state(channel,
&exit_status,
&exit_signal,
&core_dumped);
assert_ssh_return_code(session, rc);
assert_int_equal(exit_status, (uint32_t)-1);
assert_string_equal(exit_signal, "TERM");
SAFE_FREE(exit_signal);
}
static void
torture_channel_read_stderr(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
ssh_channel channel;
int rc;
channel = ssh_channel_new(session);
assert_non_null(channel);
rc = ssh_channel_open_session(channel);
assert_ssh_return_code(session, rc);
/* This writes to standard error "pipe" */
rc = ssh_channel_request_exec(channel, "echo -n ABCD >&2");
assert_ssh_return_code(session, rc);
/* No data in stdout */
rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
assert_int_equal(rc, 0);
/* poll should say how much we can read */
rc = ssh_channel_poll(channel, 1);
assert_int_equal(rc, strlen("ABCD"));
/* Everything in stderr */
rc = ssh_channel_read(channel, buffer, sizeof(buffer), 1);
assert_int_equal(rc, strlen("ABCD"));
buffer[rc] = '\0';
assert_string_equal("ABCD", buffer);
ssh_channel_free(channel);
}
static void torture_pubkey_hash(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
char *hash = NULL;
char *hexa = NULL;
int rc = 0;
/* bad arguments */
rc = ssh_get_pubkey_hash(session, NULL);
assert_int_equal(rc, SSH_ERROR);
rc = ssh_get_pubkey_hash(NULL, (unsigned char **)&hash);
assert_int_equal(rc, SSH_ERROR);
/* deprecated, but should be covered by tests! */
rc = ssh_get_pubkey_hash(session, (unsigned char **)&hash);
if (ssh_fips_mode()) {
/* When in FIPS mode, expect the call to fail */
assert_int_equal(rc, SSH_ERROR);
} else {
assert_int_equal(rc, MD5_DIGEST_LEN);
hexa = ssh_get_hexa((unsigned char *)hash, rc);
SSH_STRING_FREE_CHAR(hash);
assert_string_equal(hexa,
"ee:80:7f:61:f9:d5:be:f1:96:86:cc:96:7a:db:7a:7b");
SSH_STRING_FREE_CHAR(hexa);
}
}
static void torture_openssh_banner_version(void **state)
{
struct torture_state *s = *state;
ssh_session session = s->ssh.session;
int openssh_version = ssh_get_openssh_version(session);
int cmake_openssh_version = SSH_VERSION_INT(OPENSSH_VERSION_MAJOR, OPENSSH_VERSION_MINOR, 0);
assert_int_equal(openssh_version, cmake_openssh_version);
}
int torture_run_tests(void) {
int rc;
struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(torture_channel_read_error,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_channel_poll_timeout_valid,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_channel_poll_timeout,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_max_sessions,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_no_more_sessions,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_channel_delayed_close,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_freed_channel_poll,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_channel_exit_status,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_channel_exit_signal,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_channel_read_stderr,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_pubkey_hash,
session_setup,
session_teardown),
cmocka_unit_test_setup_teardown(torture_openssh_banner_version,
session_setup,
session_teardown),
};
ssh_init();
torture_filter_tests(tests);
rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown);
ssh_finalize();
return rc;
}