From d68108f3a47f9244166ba8c06130a55ee27e7422 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 16 Aug 2023 13:29:30 +0200 Subject: [PATCH 001/795] build: Bump the minimal cmake version to 3.5 We use string(APPEND) from version 3.4 for 5 years and CMake is deprecating support for versions before 3.5 so bumping one more version. Fixes: #209 Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider Reviewed-by: Aris Adamantiadis --- CMakeLists.txt | 2 +- INSTALL | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ba50f70..44e08edb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.3.0) +cmake_minimum_required(VERSION 3.5.0) cmake_policy(SET CMP0048 NEW) # Specify search path for CMake modules to be loaded by include() diff --git a/INSTALL b/INSTALL index 7ba53c50..2c457f4b 100644 --- a/INSTALL +++ b/INSTALL @@ -7,7 +7,7 @@ In order to build libssh, you need to install several components: - A C compiler -- [CMake](https://www.cmake.org) >= 3.3.0 +- [CMake](https://www.cmake.org) >= 3.5.0 - [openssl](https://www.openssl.org) >= 1.0.1 or - [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.4 From 3e748512c7a159180d66df500f645769f345ab82 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 16 Aug 2023 13:30:05 +0200 Subject: [PATCH 002/795] doc: Update minimal OpenSSL and gcrypt version and mention Mbed TLS Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider Reviewed-by: Aris Adamantiadis --- INSTALL | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/INSTALL b/INSTALL index 2c457f4b..7ed4ff20 100644 --- a/INSTALL +++ b/INSTALL @@ -8,10 +8,12 @@ In order to build libssh, you need to install several components: - A C compiler - [CMake](https://www.cmake.org) >= 3.5.0 -- [openssl](https://www.openssl.org) >= 1.0.1 -or -- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.4 - [libz](https://www.zlib.net) >= 1.2 +- [openssl](https://www.openssl.org) >= 1.1.1 +or +- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.5 +or +- [Mbed TLS](https://www.trustedfirmware.org/projects/mbed-tls/) optional: - [cmocka](https://cmocka.org/) >= 1.1.0 From 60db508054e32466c7eeb8283401765282c4045e Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Wed, 26 Jul 2023 21:58:40 +0200 Subject: [PATCH 003/795] channel: use a larger window size to increase receive throughput. The window size controls how much data the peer can send before we send back a message to to increase the window. This changes the default window from 1.28MB to 2MiB. 2MiB matches the OpenSSH default session size. The code is also refactored to grow the windows on code paths where data is consumed, and move the condition that checks if the growing the window is needed into the grow method. Signed-off-by: Tom Deseyn Reviewed-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/channels.c | 108 +++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/src/channels.c b/src/channels.c index b54af3bb..208645da 100644 --- a/src/channels.c +++ b/src/channels.c @@ -52,16 +52,19 @@ #include "libssh/server.h" #endif -#define WINDOWBASE 1280000 -#define WINDOWLIMIT (WINDOWBASE/2) - /* * All implementations MUST be able to process packets with an * uncompressed payload length of 32768 bytes or less and a total packet * size of 35000 bytes or less. */ #define CHANNEL_MAX_PACKET 32768 -#define CHANNEL_INITIAL_WINDOW 64000 + +/* + * WINDOW_DEFAULT matches the default OpenSSH session window size. + * This controls how much data the peer can send before needing to receive + * a round-trip SSH2_MSG_CHANNEL_WINDOW_ADJUST message that increases the window. + */ +#define WINDOW_DEFAULT (64*CHANNEL_MAX_PACKET) /** * @defgroup libssh_channel The SSH channel functions @@ -422,32 +425,43 @@ ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) { * @brief grows the local window and sends a packet to the other party * @param session SSH session * @param channel SSH channel - * @param minimumsize The minimum acceptable size for the new window. * @return SSH_OK if successful; SSH_ERROR otherwise. */ static int grow_window(ssh_session session, - ssh_channel channel, - uint32_t minimumsize) + ssh_channel channel) { - uint32_t new_window = minimumsize > WINDOWBASE ? minimumsize : WINDOWBASE; + uint32_t used; + uint32_t increment; int rc; - if (new_window <= channel->local_window) { + /* Calculate the increment taking into account what the peer may still send + * (local_window) and what we've already buffered (stdout_buffer and + * stderr_buffer). + */ + used = channel->local_window; + if (channel->stdout_buffer != NULL) { + used += ssh_buffer_get_len(channel->stdout_buffer); + } + if (channel->stderr_buffer != NULL) { + used += ssh_buffer_get_len(channel->stderr_buffer); + } + /* Avoid a negative increment in case the peer sent more than the window allowed */ + increment = WINDOW_DEFAULT > used ? WINDOW_DEFAULT - used : 0; + /* Don't grow until we can request at least half a window */ + if (increment < (WINDOW_DEFAULT / 2)) { SSH_LOG(SSH_LOG_DEBUG, "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes : not needed (%" PRIu32 " bytes)", - channel->local_channel, channel->remote_channel, new_window, + channel->local_channel, channel->remote_channel, WINDOW_DEFAULT, channel->local_window); return SSH_OK; } - /* WINDOW_ADJUST packet needs a relative increment rather than an absolute - * value, so we give here the missing bytes needed to reach new_window - */ + rc = ssh_buffer_pack(session->out_buffer, "bdd", SSH2_MSG_CHANNEL_WINDOW_ADJUST, channel->remote_channel, - new_window - channel->local_window); + increment); if (rc != SSH_OK) { ssh_set_error_oom(session); goto error; @@ -458,12 +472,12 @@ static int grow_window(ssh_session session, } SSH_LOG(SSH_LOG_DEBUG, - "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes", + "growing window (channel %" PRIu32 ":%" PRIu32 ") by %" PRIu32 " bytes", channel->local_channel, channel->remote_channel, - new_window); + increment); - channel->local_window = new_window; + channel->local_window += increment; return SSH_OK; error: @@ -614,12 +628,17 @@ SSH_PACKET_CALLBACK(channel_rcv_data) channel->local_window, channel->remote_window); - /* What shall we do in this case? Let's accept it anyway */ if (len > channel->local_window) { SSH_LOG(SSH_LOG_RARE, "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")", len, channel->local_window); + + SSH_STRING_FREE(str); + + ssh_set_error(session, SSH_FATAL, "Window exceeded"); + + return SSH_PACKET_USED; } data = ssh_string_data(str); @@ -629,11 +648,7 @@ SSH_PACKET_CALLBACK(channel_rcv_data) return SSH_PACKET_USED; } - if (len <= channel->local_window) { - channel->local_window -= len; - } else { - channel->local_window = 0; /* buggy remote */ - } + channel->local_window -= len; SSH_LOG(SSH_LOG_PACKET, "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")", @@ -661,19 +676,20 @@ SSH_PACKET_CALLBACK(channel_rcv_data) ssh_buffer_get_len(buf), is_stderr); if (rest > 0) { + int rc; if (channel->counter != NULL) { channel->counter->in_bytes += rest; } ssh_buffer_pass_bytes(buf, rest); + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } } } ssh_callbacks_iterate_end(); - if (channel->local_window + ssh_buffer_get_len(buf) < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } - } return SSH_PACKET_USED; } @@ -1025,7 +1041,7 @@ int ssh_channel_open_session(ssh_channel channel) return channel_open(channel, "session", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } @@ -1053,7 +1069,7 @@ int ssh_channel_open_auth_agent(ssh_channel channel) return channel_open(channel, "auth-agent@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, NULL); } @@ -1122,7 +1138,7 @@ int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, rc = channel_open(channel, "direct-tcpip", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -1205,7 +1221,7 @@ int ssh_channel_open_forward_unix(ssh_channel channel, rc = channel_open(channel, "direct-streamlocal@openssh.com", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -2967,14 +2983,13 @@ int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, struct ssh_channel_read_termination_struct { ssh_channel channel; - uint32_t count; ssh_buffer buffer; }; static int ssh_channel_read_termination(void *s) { struct ssh_channel_read_termination_struct *ctx = s; - if (ssh_buffer_get_len(ctx->buffer) >= ctx->count || + if (ssh_buffer_get_len(ctx->buffer) >= 1 || ctx->channel->remote_eof || ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) return 1; @@ -3063,28 +3078,17 @@ int ssh_channel_read_timeout(ssh_channel channel, stdbuf=channel->stderr_buffer; } - /* - * We may have problem if the window is too small to accept as much data - * as asked - */ SSH_LOG(SSH_LOG_PACKET, "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32, count, ssh_buffer_get_len(stdbuf), channel->local_window); - if (count > ssh_buffer_get_len(stdbuf) + channel->local_window) { - if (grow_window(session, channel, count - ssh_buffer_get_len(stdbuf)) < 0) { - return -1; - } - } - /* block reading until at least one byte has been read * and ignore the trivial case count=0 */ ctx.channel = channel; ctx.buffer = stdbuf; - ctx.count = 1; if (timeout_ms < SSH_TIMEOUT_DEFAULT) { timeout_ms = SSH_TIMEOUT_INFINITE; @@ -3126,11 +3130,10 @@ int ssh_channel_read_timeout(ssh_channel channel, if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) { channel->state = SSH_CHANNEL_STATE_CLOSED; } - /* Authorize some buffering while userapp is busy */ - if (channel->local_window < WINDOWLIMIT) { - if (grow_window(session, channel, 0) < 0) { - return -1; - } + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; } return len; @@ -3290,7 +3293,6 @@ int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) } ctx.buffer = stdbuf; ctx.channel = channel; - ctx.count = 1; rc = ssh_handle_packets_termination(channel->session, timeout, ssh_channel_read_termination, @@ -3708,7 +3710,7 @@ int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost pending: rc = channel_open(channel, "forwarded-tcpip", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); @@ -3771,7 +3773,7 @@ int ssh_channel_open_x11(ssh_channel channel, pending: rc = channel_open(channel, "x11", - CHANNEL_INITIAL_WINDOW, + WINDOW_DEFAULT, CHANNEL_MAX_PACKET, payload); From 6a64f5a11a7b7a6c84e5e276212351339152c66e Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Tue, 18 Jul 2023 07:48:50 +0200 Subject: [PATCH 004/795] Allow sending data payloads of remote_maxpacket length. Signed-off-by: Tom Deseyn Reviewed-by: Jakub Jelen --- src/channels.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/channels.c b/src/channels.c index 208645da..7e3cc9ad 100644 --- a/src/channels.c +++ b/src/channels.c @@ -1502,7 +1502,6 @@ static int channel_write_common(ssh_channel channel, ssh_session session; uint32_t origlen = len; size_t effectivelen; - size_t maxpacketlen; int rc; if(channel == NULL) { @@ -1520,12 +1519,6 @@ static int channel_write_common(ssh_channel channel, return SSH_ERROR; } - /* - * Handle the max packet len from remote side, be nice - * 10 bytes for the headers - */ - maxpacketlen = channel->remote_maxpacket - 10; - if (channel->local_eof) { ssh_set_error(session, SSH_REQUEST_DENIED, "Can't write to channel %" PRIu32 ":%" PRIu32 " after EOF was sent", @@ -1576,7 +1569,11 @@ static int channel_write_common(ssh_channel channel, effectivelen = len; } - effectivelen = MIN(effectivelen, maxpacketlen); + /* + * Like OpenSSH, don't subtract bytes for the header fields + * and allow to send a payload of remote_maxpacket length. + */ + effectivelen = MIN(effectivelen, channel->remote_maxpacket); rc = ssh_buffer_pack(session->out_buffer, "bd", From adfb2bcc756db17ba147f9f606e0ddf7b7cef04a Mon Sep 17 00:00:00 2001 From: Sahana Prasad Date: Mon, 14 Aug 2023 10:42:15 +0200 Subject: [PATCH 005/795] Revert the control flow callback in commit https://gitlab.com/libssh/libssh-mirror/-/commit/6f029598c78dd999b3773ce1bc54e390d5b7ec57 Signed-off-by: Sahana Prasad Reviewed-by: Jakub Jelen --- src/packet.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/packet.c b/src/packet.c index 5c811292..2b4a4e78 100644 --- a/src/packet.c +++ b/src/packet.c @@ -1399,12 +1399,37 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) return processed; } +static void ssh_packet_socket_controlflow_callback(int code, void *userdata) +{ + ssh_session session = userdata; + struct ssh_iterator *it; + ssh_channel channel; + + if (code == SSH_SOCKET_FLOW_WRITEWONTBLOCK) { + SSH_LOG(SSH_LOG_TRACE, "sending channel_write_wontblock callback"); + + /* the out pipe is empty so we can forward this to channels */ + it = ssh_list_get_iterator(session->channels); + while (it != NULL) { + channel = ssh_iterator_value(ssh_channel, it); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + it = it->next; + } + } +} + void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) { struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; callbacks->data = ssh_packet_socket_callback; callbacks->connected = NULL; + callbacks->controlflow = ssh_packet_socket_controlflow_callback; callbacks->userdata = session; ssh_socket_set_callbacks(s, callbacks); } From 8ed50ea6edc26623e435965c55e21602fcac3a11 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 17 Dec 2020 16:55:30 +0100 Subject: [PATCH 006/795] Update header files parser to match mutli-line function declarations Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- cmake/Modules/ExtractSymbols.cmake | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cmake/Modules/ExtractSymbols.cmake b/cmake/Modules/ExtractSymbols.cmake index f7829334..95c850b6 100644 --- a/cmake/Modules/ExtractSymbols.cmake +++ b/cmake/Modules/ExtractSymbols.cmake @@ -50,15 +50,28 @@ file(READ ${HEADERS_LIST_FILE} HEADERS_LIST) set(symbols) foreach(header ${HEADERS_LIST}) + file(READ ${header} header_content) # Filter only lines containing the FILTER_PATTERN - file(STRINGS ${header} contain_filter - REGEX "^.*${FILTER_PATTERN}.*[(]" + # separated from the function name with one optional newline + string(REGEX MATCHALL + "${FILTER_PATTERN}[^(\n]*\n?[^(\n]*[(]" + contain_filter + "${header_content}" + ) + + # Remove the optional newline now + string(REGEX REPLACE + "(.+)\n?(.*)" + "\\1\\2" + oneline + "${contain_filter}" ) # Remove function-like macros - foreach(line ${contain_filter}) - if (NOT ${line} MATCHES ".*#[ ]*define") + # and anything with two underscores that sounds suspicious + foreach(line ${oneline}) + if (NOT ${line} MATCHES ".*(#[ ]*define|__)") list(APPEND not_macro ${line}) endif() endforeach() From 7645892ca2abad2c8e5f34d29281c9b0b961e1ab Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 14 Dec 2020 16:37:14 +0100 Subject: [PATCH 007/795] Try to describe our coding style using clang-format Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .clang-format | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index 78ca1364..1da4f35d 100644 --- a/.clang-format +++ b/.clang-format @@ -10,7 +10,7 @@ BraceWrapping: AfterFunction: true AfterStruct: false AfterUnion: false - AfterExternBlock: true + AfterExternBlock: false BeforeElse: false BeforeWhile: false IndentCaseLabels: false @@ -22,6 +22,6 @@ BinPackArguments: false BinPackParameters: false AllowAllArgumentsOnNextLine: false AllowShortFunctionsOnASingleLine: Empty -AlwaysBreakAfterReturnType: None +AlwaysBreakAfterReturnType: AllDefinitions AlignEscapedNewlines: Left ForEachMacros: ['ssh_callbacks_iterate'] From f86bec735b012116971bc5e9c2e47e7fd8245471 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 4 Jul 2023 17:06:11 +0200 Subject: [PATCH 008/795] poll: Drop all events except POLLOUT when called recursively The FD locking was modified in 30b5a2e33bf260062dd31c9c0e98cf9982b08961 but it caused some weird issues on s390x in Debian tests, which were getting POLLHUP, causing infinite recursion while the callback tried to close socket. Previously, the lock blocked only the POLLIN events as we believed these were the only events we could get recursively that could cause issues. But it looks like more sane behavior will be blocking everything but POLLOUT to allow the buffers to be flushed. Fixes #202 Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/poll.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/poll.c b/src/poll.c index 828f5e0a..b5d0a662 100644 --- a/src/poll.c +++ b/src/poll.c @@ -698,13 +698,13 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) return SSH_ERROR; } - /* Ignore any pollin events on locked sockets as that means we are called + /* Allow only POLLOUT events on locked sockets as that means we are called * recursively and we only want process the POLLOUT events here to flush * output buffer */ for (i = 0; i < ctx->polls_used; i++) { - /* The lock prevents invoking POLLIN events: drop them now */ + /* The lock allows only POLLOUT events: drop the rest */ if (ctx->pollptrs[i]->lock_cnt > 0) { - ctx->pollfds[i].events &= ~POLLIN; + ctx->pollfds[i].events &= POLLOUT; } } ssh_timestamp_init(&ts); From 4e56c5c956fbac7319592c959980a10f9109979f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 4 Aug 2023 21:39:46 +0200 Subject: [PATCH 009/795] poll: Avoid passing other events to callbacks when called recursively Some architectures (s390x) provide different poll events such as POLLHUP in case the remote end closed the connection (and they keep reporting this forever). This is an issue when the user provided callback registering this event as an error and tries to send some reply (for example EOF) using `ssh_channel_send_eof()` which will lead to infinite recursion and sefgaults. This was not solved by the 30b5a2e33bf260062dd31c9c0e98cf9982b08961 because the POLLHUP event is not provided by the poll in events bitfield, but only reported by the poll in revents bit field thus we need to filter these events later on when the poll is recursively. Fixes #202 Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/poll.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/poll.c b/src/poll.c index b5d0a662..8f81c11c 100644 --- a/src/poll.c +++ b/src/poll.c @@ -722,14 +722,21 @@ int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) used = ctx->polls_used; for (i = 0; i < used && rc > 0; ) { - if (ctx->pollfds[i].revents == 0) { + revents = ctx->pollfds[i].revents; + /* Do not pass any other events except for POLLOUT to callback when + * called recursively more than 2 times. On s390x the poll will be + * spammed with POLLHUP events causing infinite recursion when the user + * callback issues some write/flush/poll calls. */ + if (ctx->pollptrs[i]->lock_cnt > 2) { + revents &= POLLOUT; + } + if (revents == 0) { i++; } else { int ret; p = ctx->pollptrs[i]; fd = ctx->pollfds[i].fd; - revents = ctx->pollfds[i].revents; /* avoid having any event caught during callback */ ctx->pollfds[i].events = 0; p->lock_cnt++; From 6cf5f0e340370ecdc80e300a2622f47fe04c5682 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Sat, 9 Sep 2023 08:49:43 +0200 Subject: [PATCH 010/795] sftp: Cap maximum SFTP write The curl does not do any (or enough) chunking when writing large files using the sftp_write() function which causes some servers to choke [1]. The simplest solution is to limit the SFTP packet size according the SFTP specification recommendation which is 32768 B and not write more. This means the function will not write the whole amount of data it was asked to write and the calling applications are required to handle the return values correctly. More complicated solution would be to send several SFTP packet from the single sftp_write() function by iterating over the all data passed. The next improvement in the long term should be respecting the value reported by the server in the limits@openssh.com extension, which specifies the maximum packet size and reads/writes explicitly (if supported). [1] https://github.com/curl/curl/pull/11804 Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/sftp.h | 4 ++++ src/sftp.c | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index e210a1be..4b4f8481 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -550,6 +550,10 @@ LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_ /** * @brief Write to a file using an opened sftp file handle. * + * The maximum size of the SFTP packet payload is 32768 bytes so the count + * parameter is capped at this value. This is low-level function so it does not + * try to send more than this amount of data. + * * @param file Open sftp file handle to write to. * * @param buf Pointer to buffer to write data. diff --git a/src/sftp.c b/src/sftp.c index d95af819..c1f29449 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -2211,6 +2211,17 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { id = sftp_get_new_id(file->sftp); + + /* limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * + * FIXME: This value should be adjusted to the value from the + * limits@openssh.com extension if supported + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + */ + count = count > 32768 ? 32768 : count; + rc = ssh_buffer_pack(buffer, "dSqdP", id, From 5d792a3b5a5fc2331cb52d6febdd886c5df580e3 Mon Sep 17 00:00:00 2001 From: anfanite396 Date: Thu, 14 Sep 2023 22:11:48 +0530 Subject: [PATCH 011/795] Adding support for limits@openssh.com on client side Signed-off-by: anfanite396 Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 29 ++++++++ src/sftp.c | 116 +++++++++++++++++++++++++++++ tests/client/CMakeLists.txt | 1 + tests/client/torture_sftp_limits.c | 97 ++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 tests/client/torture_sftp_limits.c diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index 4b4f8481..ef069a37 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -77,6 +77,7 @@ typedef struct sftp_request_queue_struct* sftp_request_queue; typedef struct sftp_session_struct* sftp_session; typedef struct sftp_status_message_struct* sftp_status_message; typedef struct sftp_statvfs_struct* sftp_statvfs_t; +typedef struct sftp_limits_struct* sftp_limits_t; struct sftp_session_struct { ssh_session session; @@ -200,6 +201,16 @@ struct sftp_statvfs_struct { uint64_t f_namemax; /** maximum filename length */ }; +/** + * @brief SFTP limits structure. + */ +struct sftp_limits_struct { + uint64_t max_packet_length; /** maximum number of bytes in a single sftp packet */ + uint64_t max_read_length; /** maximum length in a SSH_FXP_READ packet */ + uint64_t max_write_length; /** maximum length in a SSH_FXP_WRITE packet */ + uint64_t max_open_handles; /** maximum number of active handles allowed by server */ +}; + /** * @brief Creates a new sftp session. * @@ -846,6 +857,24 @@ LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); */ LIBSSH_API int sftp_fsync(sftp_file file); +/** + * @brief Get information about the various limits the server might impose. + * + * @param sftp The sftp session handle. + * + * @return A limits structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_limits_t sftp_limits(sftp_session sftp); + +/** + * @brief Free the memory of an allocated limits. + * + * @param limits The limits to free. + */ +LIBSSH_API void sftp_limits_free(sftp_limits_t limits); + /** * @brief Canonicalize a sftp path. * diff --git a/src/sftp.c b/src/sftp.c index c1f29449..14a07bca 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -3383,6 +3383,122 @@ void sftp_statvfs_free(sftp_statvfs_t statvfs) { SAFE_FREE(statvfs); } +static sftp_limits_t +sftp_parse_limits(sftp_session sftp, ssh_buffer buf) +{ + sftp_limits_t limits = NULL; + int rc; + + limits = calloc(1, sizeof(struct sftp_limits_struct)); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqq", + &limits->max_packet_length, /** maximum number of bytes in a single sftp packet */ + &limits->max_read_length, /** maximum length in a SSH_FXP_READ packet */ + &limits->max_write_length, /** maximum length in a SSH_FXP_WRITE packet */ + &limits->max_open_handles /** maximum number of active handles allowed by server */ + ); + if (rc != SSH_OK) { + SAFE_FREE(limits); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid limits structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return limits; +} + +sftp_limits_t +sftp_limits(sftp_session sftp) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "ds", + id, + "limits@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_limits_t limits = sftp_parse_limits(sftp, msg->payload); + sftp_message_free(msg); + if (limits == NULL) { + return NULL; + } + + return limits; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get limits", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +void +sftp_limits_free(sftp_limits_t limits) +{ + if (limits == NULL) { + return; + } + + SAFE_FREE(limits); +} + /* another code written by Nick */ char *sftp_canonicalize_path(sftp_session sftp, const char *path) { diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 6bb1762a..f977b35d 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -51,6 +51,7 @@ if (WITH_SFTP) torture_sftp_read torture_sftp_fsync torture_sftp_hardlink + torture_sftp_limits torture_sftp_rename ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff --git a/tests/client/torture_sftp_limits.c b/tests/client/torture_sftp_limits.c new file mode 100644 index 00000000..f8b682f7 --- /dev/null +++ b/tests/client/torture_sftp_limits.c @@ -0,0 +1,97 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_limits(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_limits_t li; + + if (!sftp_extension_supported(t->sftp, "limits@openssh.com", "1")) + skip(); + + li = sftp_limits(t->sftp); + assert_non_null(li); + + assert_int_not_equal(li->max_packet_length, 0); + assert_int_not_equal(li->max_read_length, 0); + assert_int_not_equal(li->max_write_length, 0); + assert_int_not_equal(li->max_open_handles, 0); + + sftp_limits_free(li); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_limits, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} From 66144f6f60074772c096889498e972a5d5eeb85d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 20 Sep 2023 09:43:54 +0200 Subject: [PATCH 012/795] Add missing function to header file on windows Fixes: #214 Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/libssh.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 03c3a93f..b2c68a7a 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -775,10 +775,8 @@ LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, LIBSSH_API int ssh_userauth_publickey(ssh_session session, const char *username, const ssh_key privkey); -#ifndef _WIN32 LIBSSH_API int ssh_userauth_agent(ssh_session session, const char *username); -#endif LIBSSH_API int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, char** value); LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, From 2df232463802203e2d675d4f99c65776a6f6b3c4 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 31 Oct 2023 17:12:52 +0100 Subject: [PATCH 013/795] session: Free agent state on windows Fixes: #220 Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/session.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/session.c b/src/session.c index 742ac8bb..098f94a0 100644 --- a/src/session.c +++ b/src/session.c @@ -237,9 +237,7 @@ void ssh_free(ssh_session session) crypto_free(session->current_crypto); crypto_free(session->next_crypto); -#ifndef _WIN32 ssh_agent_free(session->agent); -#endif /* _WIN32 */ ssh_key_free(session->srv.rsa_key); session->srv.rsa_key = NULL; @@ -296,9 +294,7 @@ void ssh_free(ssh_session session) } ssh_list_free(session->out_queue); -#ifndef _WIN32 - ssh_agent_state_free (session->agent_state); -#endif + ssh_agent_state_free(session->agent_state); session->agent_state = NULL; SAFE_FREE(session->auth.auto_state); From 19ced21adb4596ee1102aad592e48a04e59ad7bd Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Tue, 21 Nov 2023 19:47:33 +0530 Subject: [PATCH 014/795] torture_session.c: Append a '\0' before string comparison ssh_channel_read() reads the data into the buffer, but doesn't append a '\0' after it. When the buffer is asserted to be equal to a string further in the test, the assertion could fail if the byte after the data stored in the buffer doesn't contain '\0' (and it mayn't) This commit appends a '\0' after the data read into the buffer before comparing it with a string. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/client/torture_session.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index 37ed573a..d10b328d 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -471,6 +471,8 @@ torture_channel_read_stderr(void **state) /* 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); From 0e938ebcf4f358e5c8d50ad1e16e64449b237813 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 2 Nov 2023 10:16:12 +0100 Subject: [PATCH 015/795] ci: Build fuzzers also for normal testing Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad Reviewed-by: Eshan Kelkar --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c3ee5286..d942ae3e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ stages: variables: CMAKE_DEFAULT_OPTIONS: "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DPICKY_DEVELOPER=ON" CMAKE_BUILD_OPTIONS: "-DWITH_BLOWFISH_CIPHER=ON -DWITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_DEBUG_PACKET=ON -DWITH_DEBUG_CALLTRACE=ON" - CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DWITH_BENCHMARKS=ON" + CMAKE_TEST_OPTIONS: "-DUNIT_TESTING=ON -DCLIENT_TESTING=ON -DSERVER_TESTING=ON -DWITH_BENCHMARKS=ON -DFUZZ_TESTING=ON" CMAKE_OPTIONS: $CMAKE_DEFAULT_OPTIONS $CMAKE_BUILD_OPTIONS $CMAKE_TEST_OPTIONS before_script: &build - uname -a From edb04af5be778a7b6d1e5292eae39b0856931e56 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 2 Nov 2023 11:14:16 +0100 Subject: [PATCH 016/795] fuzz: Add key files fuzzers Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad Reviewed-by: Eshan Kelkar --- tests/fuzz/CMakeLists.txt | 2 + tests/fuzz/ssh_privkey_fuzzer.c | 52 +++++++++++++++ .../855ce609b52aec530bf631a78da7038bed99040a | 8 +++ tests/fuzz/ssh_pubkey_fuzzer.c | 66 +++++++++++++++++++ .../b2c9f01394a2835b2cd7c520395a4977143e8d23 | 1 + 5 files changed, 129 insertions(+) create mode 100644 tests/fuzz/ssh_privkey_fuzzer.c create mode 100644 tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a create mode 100644 tests/fuzz/ssh_pubkey_fuzzer.c create mode 100644 tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 8d9b2bea..40a6eb13 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -32,3 +32,5 @@ fuzzer(ssh_server_fuzzer) fuzzer(ssh_client_config_fuzzer) fuzzer(ssh_bind_config_fuzzer) fuzzer(ssh_known_hosts_fuzzer) +fuzzer(ssh_privkey_fuzzer) +fuzzer(ssh_pubkey_fuzzer) diff --git a/tests/fuzz/ssh_privkey_fuzzer.c b/tests/fuzz/ssh_privkey_fuzzer.c new file mode 100644 index 00000000..b65d680d --- /dev/null +++ b/tests/fuzz/ssh_privkey_fuzzer.c @@ -0,0 +1,52 @@ +/* + * Copyright 2023 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/priv.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + uint8_t *input = NULL; + int rc; + + input = bin_to_base64(data, size); + if (input == NULL) { + return 1; + } + + ssh_init(); + + rc = ssh_pki_import_privkey_base64((char *)input, NULL, NULL, NULL, &pkey); + free(input); + if (rc != SSH_OK) { + return 1; + } + ssh_key_free(pkey); + + ssh_finalize(); + + return 0; +} + diff --git a/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a b/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a new file mode 100644 index 00000000..2759f43e --- /dev/null +++ b/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVgAAAKg+IRNSPiET +UgAAAAtzc2gtZWQyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVg +AAAED2zFg52qYItoZaSUnir4VKubTxJveL9D2oWK7Prg/O24ujq/HWVfplmh7TmVZOS7Cs +lRk3RPyER6vlQX0o+E5WAAAAHmpqZWxlbkB0NDcwcy5qamVsZW4ucmVkaGF0LmNvbQECAw +QFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/fuzz/ssh_pubkey_fuzzer.c b/tests/fuzz/ssh_pubkey_fuzzer.c new file mode 100644 index 00000000..01b08449 --- /dev/null +++ b/tests/fuzz/ssh_pubkey_fuzzer.c @@ -0,0 +1,66 @@ +/* + * Copyright 2023 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + const char *template = "/tmp/libssh_pubkey_XXXXXX"; + char *filename = strdup(template); + int fd; + int rc; + ssize_t sz; + + ssh_init(); + + if (filename == NULL) { + return -1; + } + fd = mkstemp(filename); + if (fd == -1) { + free(filename); + close(fd); + return -1; + } + sz = write(fd, data, size); + close(fd); + if ((size_t)sz != size) { + unlink(filename); + free(filename); + return -1; + } + + rc = ssh_pki_import_pubkey_file(filename, &pkey); + if (rc != SSH_OK) { + unlink(filename); + free(filename); + return 1; + } + ssh_key_free(pkey); + unlink(filename); + free(filename); + + ssh_finalize(); + + return 0; +} + diff --git a/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 b/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 new file mode 100644 index 00000000..accd5b65 --- /dev/null +++ b/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com From 9f2b42382cf6088c35cfb14fbe4b4533bc005625 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 9 Nov 2023 09:35:51 +0100 Subject: [PATCH 017/795] fuzz: Use ssh_writen to avoid short reads Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad Reviewed-by: Eshan Kelkar --- tests/fuzz/ssh_pubkey_fuzzer.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/fuzz/ssh_pubkey_fuzzer.c b/tests/fuzz/ssh_pubkey_fuzzer.c index 01b08449..70c94948 100644 --- a/tests/fuzz/ssh_pubkey_fuzzer.c +++ b/tests/fuzz/ssh_pubkey_fuzzer.c @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "config.h" #include #include @@ -20,18 +21,19 @@ #define LIBSSH_STATIC 1 #include "libssh/libssh.h" +#include "libssh/misc.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { ssh_key pkey = NULL; - const char *template = "/tmp/libssh_pubkey_XXXXXX"; - char *filename = strdup(template); + char *filename = NULL; int fd; int rc; ssize_t sz; ssh_init(); + filename = strdup("/tmp/libssh_pubkey_XXXXXX"); if (filename == NULL) { return -1; } @@ -41,9 +43,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) close(fd); return -1; } - sz = write(fd, data, size); + sz = ssh_writen(fd, data, size); close(fd); - if ((size_t)sz != size) { + if (sz == SSH_ERROR) { unlink(filename); free(filename); return -1; From 6e834b8df2c01f4f45c19094a09e28b28fa62fcf Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 9 Nov 2023 09:49:35 +0100 Subject: [PATCH 018/795] pki: Initialize pointers and avoid buffer overrun Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad Reviewed-by: Eshan Kelkar --- src/pki.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pki.c b/src/pki.c index 6863c90c..bfdcb311 100644 --- a/src/pki.c +++ b/src/pki.c @@ -1668,10 +1668,10 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) { enum ssh_keytypes_e type; struct stat sb; - char *key_buf, *p; + char *key_buf = NULL, *p = NULL; size_t buflen, i; - const char *q; - FILE *file; + const char *q = NULL; + FILE *file = NULL; off_t size; int rc, cmp; char err_msg[SSH_ERRNO_MSG_MAX] = {0}; @@ -1777,6 +1777,10 @@ int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) return SSH_ERROR; } + if (i >= buflen) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } q = &p[i + 1]; for (; i < buflen; i++) { if (isspace((int)p[i])) { From a8fe05cc4015bb0e0e6697bf9a1f7544939c27ef Mon Sep 17 00:00:00 2001 From: anshul agrawal Date: Sat, 11 Nov 2023 12:34:44 +0530 Subject: [PATCH 019/795] Adding expand-path@openssh.com extension for client Signed-off-by: anshul agrawal Reviewed-by: Sahana Prasad Reviewed-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 13 +++ src/sftp.c | 94 ++++++++++++++++++ tests/client/CMakeLists.txt | 1 + tests/client/torture_sftp_expand_path.c | 125 ++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 tests/client/torture_sftp_expand_path.c diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index ef069a37..a1aa783a 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -897,6 +897,19 @@ LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); */ LIBSSH_API int sftp_server_version(sftp_session sftp); +/** + * @brief Canonicalize path using expand-path@openssh.com extension + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_expand_path(sftp_session sftp, const char *path); + #ifdef WITH_SERVER /** * @brief Create a new sftp server session. diff --git a/src/sftp.c b/src/sftp.c index 14a07bca..83abf6f2 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -3744,4 +3744,98 @@ sftp_attributes sftp_fstat(sftp_file file) return NULL; } +char *sftp_expand_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "NULL received as an argument instead of the path to expand"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "expand-path@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + rc = sftp_read_and_dispatch(sftp); + if (rc < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse expanded path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to expand path", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + #endif /* WITH_SFTP */ diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index f977b35d..4c4b1d8e 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -53,6 +53,7 @@ if (WITH_SFTP) torture_sftp_hardlink torture_sftp_limits torture_sftp_rename + torture_sftp_expand_path ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff --git a/tests/client/torture_sftp_expand_path.c b/tests/client/torture_sftp_expand_path.c new file mode 100644 index 00000000..85ef0108 --- /dev/null +++ b/tests/client/torture_sftp_expand_path.c @@ -0,0 +1,125 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_expand_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *expanded_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "expand-path@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for a absolute path */ + expanded_path = sftp_expand_path(t->sftp, "~/."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* testing for a relative path */ + expanded_path = sftp_expand_path(t->sftp, "."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* passing a NULL sftp session */ + expanded_path = sftp_expand_path(NULL, "~/."); + assert_null(expanded_path); + + /* passing an invalid path */ + expanded_path = sftp_expand_path(t->sftp, "/...//"); + assert_null(expanded_path); + + /* passing null path */ + expanded_path = sftp_expand_path(t->sftp, NULL); + assert_null(expanded_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_expand_path, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} From c3e03ab4651e4f3382e3a51c0273ade894f0c48a Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sun, 2 Jul 2023 01:27:33 +0530 Subject: [PATCH 020/795] Move certain functions from sftp.c to a new file sftp_common.c Currently the sftp api code is limited to sftp.c, sftpserver.c In future it can be required to add new sftp related APIs which are present in their own separate source files instead of adding their code to the already large sftp.c file. Those new hypothetical or existing (in sftpserver.c) sftp API functions present in the source files other than sftp.c will need to call certain functions present in sftp.c which are not provided in the public api as they are for internal use (by other sftp related functions) only. Some of these sftp.c functions have external linkage, some of them don't and cannot be currently accessed outside sftp.c This commit : 1. Moves such functions along with the functions they depend on from sftp.c to a new file sftp_common.c, to seperate them out from other sftp api functions. 2. Makes necessary changes to make required functions visible outside sftp_common.c 3. Uses the header file sftp_priv.h for necessary declarations (and not sftp.h) since these functions are not to be provided in the public sftp api. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- include/libssh/sftp_priv.h | 22 + src/CMakeLists.txt | 1 + src/sftp.c | 867 ------------------------------------ src/sftp_common.c | 890 +++++++++++++++++++++++++++++++++++++ 4 files changed, 913 insertions(+), 867 deletions(-) create mode 100644 src/sftp_common.c diff --git a/include/libssh/sftp_priv.h b/include/libssh/sftp_priv.h index 70987b1e..8470a8ad 100644 --- a/include/libssh/sftp_priv.h +++ b/include/libssh/sftp_priv.h @@ -54,6 +54,28 @@ int sftp_reply_version(sftp_client_message client_msg); */ int sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len); +void sftp_set_error(sftp_session sftp, int errnum); + +void sftp_message_free(sftp_message msg); + +int sftp_read_and_dispatch(sftp_session sftp); + +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id); + +/* + * Assigns a new SFTP ID for new requests and assures there is no collision + * between them. + * Returns a new ID ready to use in a request + */ +static inline uint32_t sftp_get_new_id(sftp_session session) +{ + return ++session->id_counter; +} + +sftp_status_message parse_status_msg(sftp_message msg); + +void status_msg_free(sftp_status_message status); + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 24f85dc0..4c54824d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -252,6 +252,7 @@ if (WITH_SFTP) set(libssh_SRCS ${libssh_SRCS} sftp.c + sftp_common.c ) if (WITH_SERVER) diff --git a/src/sftp.c b/src/sftp.c index 83abf6f2..56236252 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -56,21 +56,12 @@ #ifdef WITH_SFTP -/* Buffer size maximum is 256M */ -#define SFTP_PACKET_SIZE_MAX 0x10000000 - struct sftp_ext_struct { uint32_t count; char **name; char **data; }; -/* functions */ -static int sftp_enqueue(sftp_session session, sftp_message msg); -static void sftp_message_free(sftp_message msg); -static void sftp_set_error(sftp_session sftp, int errnum); -static void status_msg_free(sftp_status_message status); - static sftp_ext sftp_ext_new(void) { sftp_ext ext; @@ -413,167 +404,6 @@ sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) return payload_len + sizeof(uint32_t); } -int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) -{ - uint8_t header[5] = {0}; - uint32_t payload_size; - int size; - int rc; - - /* Add size of type */ - payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); - PUSH_BE_U32(header, 0, payload_size); - PUSH_BE_U8(header, 4, type); - - rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); - if (rc < 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - size = ssh_channel_write(sftp->channel, - ssh_buffer_get(payload), - ssh_buffer_get_len(payload)); - if (size < 0) { - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - if ((uint32_t)size != ssh_buffer_get_len(payload)) { - SSH_LOG(SSH_LOG_PACKET, - "Had to write %" PRIu32 " bytes, wrote only %d", - ssh_buffer_get_len(payload), - size); - } - - return size; -} - -sftp_packet sftp_packet_read(sftp_session sftp) -{ - uint8_t tmpbuf[4]; - uint8_t *buffer = NULL; - sftp_packet packet = sftp->read_packet; - uint32_t size; - int nread; - bool is_eof; - int rc; - - packet->sftp = sftp; - - /* - * If the packet has a payload, then just reinit the buffer, otherwise - * allocate a new one. - */ - if (packet->payload != NULL) { - rc = ssh_buffer_reinit(packet->payload); - if (rc != 0) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - } else { - packet->payload = ssh_buffer_new(); - if (packet->payload == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - } - - nread = 0; - do { - int s; - - // read from channel until 4 bytes have been read or an error occurs - s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); - if (s < 0) { - goto error; - } else if (s == 0) { - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received EOF while reading sftp packet size"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } else { - nread += s; - } - } while (nread < 4); - - size = PULL_BE_U32(tmpbuf, 0); - if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { - ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; - } - - do { - nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); - if (nread < 0) { - goto error; - } else if (nread == 0) { - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_FATAL, - "Received EOF while reading sftp packet type"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } - } while (nread < 1); - - packet->type = tmpbuf[0]; - - /* Remove the packet type size */ - size -= sizeof(uint8_t); - - /* Allocate the receive buffer from payload */ - buffer = ssh_buffer_allocate(packet->payload, size); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - goto error; - } - while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { - nread = ssh_channel_read(sftp->channel, buffer, size, 0); - if (nread < 0) { - /* TODO: check if there are cases where an error needs to be set here */ - goto error; - } - - if (nread > 0) { - buffer += nread; - size -= nread; - } else { /* nread == 0 */ - /* Retry the reading unless the remote was closed */ - is_eof = ssh_channel_is_eof(sftp->channel); - if (is_eof) { - ssh_set_error(sftp->session, - SSH_REQUEST_DENIED, - "Received EOF while reading sftp packet"); - sftp_set_error(sftp, SSH_FX_EOF); - goto error; - } - } - } - - return packet; -error: - ssh_buffer_reinit(packet->payload); - return NULL; -} - -static void sftp_set_error(sftp_session sftp, int errnum) { - if (sftp != NULL) { - sftp->errnum = errnum; - } -} - /* Get the last sftp error */ int sftp_get_error(sftp_session sftp) { if (sftp == NULL) { @@ -583,104 +413,6 @@ int sftp_get_error(sftp_session sftp) { return sftp->errnum; } -static void sftp_message_free(sftp_message msg) -{ - if (msg == NULL) { - return; - } - - SSH_BUFFER_FREE(msg->payload); - SAFE_FREE(msg); -} - -static sftp_message sftp_get_message(sftp_packet packet) -{ - sftp_session sftp = packet->sftp; - sftp_message msg = NULL; - int rc; - - switch(packet->type) { - case SSH_FXP_STATUS: - case SSH_FXP_HANDLE: - case SSH_FXP_DATA: - case SSH_FXP_ATTRS: - case SSH_FXP_NAME: - case SSH_FXP_EXTENDED_REPLY: - break; - default: - ssh_set_error(packet->sftp->session, - SSH_FATAL, - "Unknown packet type %d", - packet->type); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; - } - - msg = calloc(1, sizeof(struct sftp_message_struct)); - if (msg == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; - } - - msg->sftp = packet->sftp; - msg->packet_type = packet->type; - - /* Move the payload from the packet to the message */ - msg->payload = packet->payload; - packet->payload = NULL; - - rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); - if (rc != SSH_OK) { - ssh_set_error(packet->sftp->session, SSH_FATAL, - "Invalid packet %d: no ID", packet->type); - sftp_message_free(msg); - sftp_set_error(packet->sftp, SSH_FX_FAILURE); - return NULL; - } - - SSH_LOG(SSH_LOG_PACKET, - "Packet with id %" PRIu32 " type %d", - msg->id, - msg->packet_type); - - return msg; -} - -static int sftp_read_and_dispatch(sftp_session sftp) -{ - sftp_packet packet = NULL; - sftp_message msg = NULL; - - packet = sftp_packet_read(sftp); - if (packet == NULL) { - /* something nasty happened reading the packet */ - return -1; - } - - msg = sftp_get_message(packet); - if (msg == NULL) { - return -1; - } - - if (sftp_enqueue(sftp, msg) < 0) { - sftp_message_free(msg); - return -1; - } - - return 0; -} - -void sftp_packet_free(sftp_packet packet) -{ - if (packet == NULL) { - return; - } - - SSH_BUFFER_FREE(packet->payload); - free(packet); -} - /* Initialize the sftp session with the server. */ int sftp_init(sftp_session sftp) { sftp_packet packet = NULL; @@ -841,166 +573,6 @@ int sftp_extension_supported(sftp_session sftp, const char *name, return 0; } -static sftp_request_queue request_queue_new(sftp_message msg) { - sftp_request_queue queue = NULL; - - queue = calloc(1, sizeof(struct sftp_request_queue_struct)); - if (queue == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - queue->message = msg; - - return queue; -} - -static void request_queue_free(sftp_request_queue queue) { - if (queue == NULL) { - return; - } - - ZERO_STRUCTP(queue); - SAFE_FREE(queue); -} - -static int sftp_enqueue(sftp_session sftp, sftp_message msg) { - sftp_request_queue queue = NULL; - sftp_request_queue ptr; - - queue = request_queue_new(msg); - if (queue == NULL) { - return -1; - } - - SSH_LOG(SSH_LOG_PACKET, - "Queued msg id %" PRIu32 " type %d", - msg->id, msg->packet_type); - - if(sftp->queue == NULL) { - sftp->queue = queue; - } else { - ptr = sftp->queue; - while(ptr->next) { - ptr=ptr->next; /* find end of linked list */ - } - ptr->next = queue; /* add it on bottom */ - } - - return 0; -} - -/* - * Pulls a message from the queue based on the ID. - * Returns NULL if no message has been found. - */ -static sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ - sftp_request_queue prev = NULL; - sftp_request_queue queue; - sftp_message msg; - - if(sftp->queue == NULL) { - return NULL; - } - - queue = sftp->queue; - while (queue) { - if(queue->message->id == id) { - /* remove from queue */ - if (prev == NULL) { - sftp->queue = queue->next; - } else { - prev->next = queue->next; - } - msg = queue->message; - request_queue_free(queue); - SSH_LOG(SSH_LOG_PACKET, - "Dequeued msg id %" PRIu32 " type %d", - msg->id, - msg->packet_type); - return msg; - } - prev = queue; - queue = queue->next; - } - - return NULL; -} - -/* - * Assigns a new SFTP ID for new requests and assures there is no collision - * between them. - * Returns a new ID ready to use in a request - */ -static inline uint32_t sftp_get_new_id(sftp_session session) { - return ++session->id_counter; -} - -static sftp_status_message parse_status_msg(sftp_message msg){ - sftp_status_message status; - int rc; - - if (msg->packet_type != SSH_FXP_STATUS) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Not a ssh_fxp_status message passed in!"); - sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); - return NULL; - } - - status = calloc(1, sizeof(struct sftp_status_message_struct)); - if (status == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - - status->id = msg->id; - rc = ssh_buffer_unpack(msg->payload, "d", - &status->status); - if (rc != SSH_OK){ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - rc = ssh_buffer_unpack(msg->payload, "ss", - &status->errormsg, - &status->langmsg); - - if(rc != SSH_OK && msg->sftp->version >=3){ - /* These are mandatory from version 3 */ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - if (status->errormsg == NULL) - status->errormsg = strdup("No error message in packet"); - if (status->langmsg == NULL) - status->langmsg = strdup(""); - if (status->errormsg == NULL || status->langmsg == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - status_msg_free(status); - return NULL; - } - - return status; -} - -static void status_msg_free(sftp_status_message status){ - if (status == NULL) { - return; - } - - SAFE_FREE(status->errormsg); - SAFE_FREE(status->langmsg); - SAFE_FREE(status); -} - static sftp_file parse_handle_msg(sftp_message msg){ sftp_file file; @@ -1126,445 +698,6 @@ sftp_dir sftp_opendir(sftp_session sftp, const char *path) return NULL; } -/* - * Parse the attributes from a payload from some messages. It is coded on - * baselines from the protocol version 4. - * This code is more or less dead but maybe we will need it in the future. - */ -static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, - int expectnames) { - sftp_attributes attr; - ssh_string owner = NULL; - ssh_string group = NULL; - uint32_t flags = 0; - int ok = 0; - - /* unused member variable */ - (void) expectnames; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - /* This isn't really a loop, but it is like a try..catch.. */ - do { - if (ssh_buffer_get_u32(buf, &flags) != 4) { - break; - } - - flags = ntohl(flags); - attr->flags = flags; - - if (flags & SSH_FILEXFER_ATTR_SIZE) { - if (ssh_buffer_get_u64(buf, &attr->size) != 8) { - break; - } - attr->size = ntohll(attr->size); - } - - if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { - owner = ssh_buffer_get_ssh_string(buf); - if (owner == NULL) { - break; - } - attr->owner = ssh_string_to_char(owner); - SSH_STRING_FREE(owner); - if (attr->owner == NULL) { - break; - } - - group = ssh_buffer_get_ssh_string(buf); - if (group == NULL) { - break; - } - attr->group = ssh_string_to_char(group); - SSH_STRING_FREE(group); - if (attr->group == NULL) { - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { - break; - } - attr->permissions = ntohl(attr->permissions); - - /* FIXME on windows! */ - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { - if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { - break; - } - attr->atime64 = ntohll(attr->atime64); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { - break; - } - attr->atime_nseconds = ntohl(attr->atime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_CREATETIME) { - if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { - break; - } - attr->createtime = ntohll(attr->createtime); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { - break; - } - attr->createtime_nseconds = ntohl(attr->createtime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { - if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { - break; - } - attr->mtime64 = ntohll(attr->mtime64); - - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { - break; - } - attr->mtime_nseconds = ntohl(attr->mtime_nseconds); - } - } - - if (flags & SSH_FILEXFER_ATTR_ACL) { - if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { - break; - } - } - - if (flags & SSH_FILEXFER_ATTR_EXTENDED) { - if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { - break; - } - attr->extended_count = ntohl(attr->extended_count); - - while(attr->extended_count && - (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && - (attr->extended_data = ssh_buffer_get_ssh_string(buf))){ - attr->extended_count--; - } - - if (attr->extended_count) { - break; - } - } - ok = 1; - } while (0); - - if (ok == 0) { - /* break issued somewhere */ - SSH_STRING_FREE(attr->acl); - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); - - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - - return NULL; - } - - return attr; -} - -enum sftp_longname_field_e { - SFTP_LONGNAME_PERM = 0, - SFTP_LONGNAME_FIXME, - SFTP_LONGNAME_OWNER, - SFTP_LONGNAME_GROUP, - SFTP_LONGNAME_SIZE, - SFTP_LONGNAME_DATE, - SFTP_LONGNAME_TIME, - SFTP_LONGNAME_NAME, -}; - -static char *sftp_parse_longname(const char *longname, - enum sftp_longname_field_e longname_field) { - const char *p, *q; - size_t len, field = 0; - - p = longname; - /* Find the beginning of the field which is specified by sftp_longname_field_e. */ - while(field != longname_field) { - if(isspace(*p)) { - field++; - p++; - while(*p && isspace(*p)) { - p++; - } - } else { - p++; - } - } - - q = p; - while (! isspace(*q)) { - q++; - } - - len = q - p; - - return strndup(p, len); -} - -/* sftp version 0-3 code. It is different from the v4 */ -/* maybe a paste of the draft is better than the code */ -/* - uint32 flags - uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE - uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID - uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID - uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS - uint32 atime present only if flag SSH_FILEXFER_ACMODTIME - uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME - uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED - string extended_type - string extended_data - ... more extended data (extended_type - extended_data pairs), - so that number of pairs equals extended_count */ -static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, - int expectname) { - sftp_attributes attr; - int rc; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } - - if (expectname) { - rc = ssh_buffer_unpack(buf, "ss", - &attr->name, - &attr->longname); - if (rc != SSH_OK){ - goto error; - } - SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); - - /* Set owner and group if we talk to openssh and have the longname */ - if (ssh_get_openssh_version(sftp->session)) { - attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); - if (attr->owner == NULL) { - goto error; - } - - attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); - if (attr->group == NULL) { - goto error; - } - } - } - - rc = ssh_buffer_unpack(buf, "d", &attr->flags); - if (rc != SSH_OK){ - goto error; - } - SSH_LOG(SSH_LOG_DEBUG, - "Flags: %.8" PRIx32 "\n", 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); - } - - if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_unpack(buf, "dd", - &attr->uid, - &attr->gid); - if (rc != SSH_OK){ - goto error; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_unpack(buf, "d", &attr->permissions); - if (rc != SSH_OK){ - goto error; - } - - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_unpack(buf, "dd", - &attr->atime, - &attr->mtime); - if (rc != SSH_OK){ - goto error; - } - } - - if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { - rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); - if (rc != SSH_OK){ - goto error; - } - - if (attr->extended_count > 0){ - rc = ssh_buffer_unpack(buf, "ss", - &attr->extended_type, - &attr->extended_data); - if (rc != SSH_OK){ - goto error; - } - attr->extended_count--; - } - /* just ignore the remaining extensions */ - - while (attr->extended_count > 0){ - ssh_string tmp1,tmp2; - rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); - if (rc != SSH_OK){ - goto error; - } - SAFE_FREE(tmp1); - SAFE_FREE(tmp2); - attr->extended_count--; - } - } - - return attr; - - error: - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->name); - SAFE_FREE(attr->longname); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - sftp_set_error(sftp, SSH_FX_FAILURE); - - return NULL; -} - -int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) -{ - uint32_t flags = (attr ? attr->flags : 0); - int rc; - - flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); - - rc = ssh_buffer_pack(buffer, "d", flags); - if (rc != SSH_OK) { - return -1; - } - - if (attr != NULL) { - if (flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_pack(buffer, "q", attr->size); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_pack(buffer, "d", attr->permissions); - if (rc != SSH_OK) { - return -1; - } - } - - if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); - if (rc != SSH_OK) { - return -1; - } - } - } - return 0; -} - - -sftp_attributes sftp_parse_attr(sftp_session session, - ssh_buffer buf, - int expectname) -{ - switch(session->version) { - case 4: - return sftp_parse_attr_4(session, buf, expectname); - case 3: - case 2: - case 1: - case 0: - return sftp_parse_attr_3(session, buf, expectname); - default: - ssh_set_error(session->session, SSH_FATAL, - "Version %d unsupported by client", session->server_version); - return NULL; - } - - return NULL; -} - /* Get the version of the SFTP protocol supported by the server */ int sftp_server_version(sftp_session sftp) { return sftp->server_version; diff --git a/src/sftp_common.c b/src/sftp_common.c new file mode 100644 index 00000000..6fa137de --- /dev/null +++ b/src/sftp_common.c @@ -0,0 +1,890 @@ +/* + * sftp_common.c - Secure FTP functions which are private and are used + * internally by other sftp api functions spread across + * various source files. + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * + * 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 + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +/* Buffer size maximum is 256M */ +#define SFTP_PACKET_SIZE_MAX 0x10000000 + +sftp_packet sftp_packet_read(sftp_session sftp) +{ + uint8_t tmpbuf[4]; + uint8_t *buffer = NULL; + sftp_packet packet = sftp->read_packet; + uint32_t size; + int nread; + bool is_eof; + int rc; + + packet->sftp = sftp; + + /* + * If the packet has a payload, then just reinit the buffer, otherwise + * allocate a new one. + */ + if (packet->payload != NULL) { + rc = ssh_buffer_reinit(packet->payload); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } else { + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + + nread = 0; + do { + int s; + + // read from channel until 4 bytes have been read or an error occurs + s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); + if (s < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } else { + nread += s; + } + } while (nread < 4); + + size = PULL_BE_U32(tmpbuf, 0); + if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { + ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + + do { + nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); + if (nread < 0) { + goto error; + } else if (nread == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } + } while (nread < 1); + + packet->type = tmpbuf[0]; + + /* Remove the packet type size */ + size -= sizeof(uint8_t); + + /* Allocate the receive buffer from payload */ + buffer = ssh_buffer_allocate(packet->payload, size); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { + nread = ssh_channel_read(sftp->channel, buffer, size, 0); + if (nread < 0) { + /* TODO: check if there are cases where an error needs to be set here */ + goto error; + } + + if (nread > 0) { + buffer += nread; + size -= nread; + } else { /* nread == 0 */ + /* Retry the reading unless the remote was closed */ + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "Received EOF while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } + } + } + + return packet; +error: + ssh_buffer_reinit(packet->payload); + return NULL; +} + +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +{ + uint8_t header[5] = {0}; + uint32_t payload_size; + int size; + int rc; + + /* Add size of type */ + payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); + PUSH_BE_U32(header, 0, payload_size); + PUSH_BE_U8(header, 4, type); + + rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + size = ssh_channel_write(sftp->channel, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (size < 0) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if ((uint32_t)size != ssh_buffer_get_len(payload)) { + SSH_LOG(SSH_LOG_PACKET, + "Had to write %" PRIu32 " bytes, wrote only %d", + ssh_buffer_get_len(payload), + size); + } + + return size; +} + +void sftp_packet_free(sftp_packet packet) +{ + if (packet == NULL) { + return; + } + + SSH_BUFFER_FREE(packet->payload); + free(packet); +} + +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) +{ + uint32_t flags = (attr ? attr->flags : 0); + int rc; + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { + return -1; + } + + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } + } + return 0; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we will need it in the future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, + int expectnames) { + sftp_attributes attr; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + SSH_STRING_FREE(owner); + if (attr->owner == NULL) { + break; + } + + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + SSH_STRING_FREE(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while(attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))){ + attr->extended_count--; + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + SSH_STRING_FREE(attr->acl); + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char *sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) { + const char *p, *q; + size_t len, field = 0; + + p = longname; + /* Find the beginning of the field which is specified by sftp_longname_field_e. */ + while(field != longname_field) { + if(isspace(*p)) { + field++; + p++; + while(*p && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + q = p; + while (! isspace(*q)) { + q++; + } + + len = q - p; + + return strndup(p, len); +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, + int expectname) { + sftp_attributes attr; + int rc; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (expectname) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->name, + &attr->longname); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + goto error; + } + + attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + goto error; + } + } + } + + rc = ssh_buffer_unpack(buf, "d", &attr->flags); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, + "Flags: %.8" PRIx32 "\n", 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); + } + + if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->uid, + &attr->gid); + if (rc != SSH_OK){ + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_unpack(buf, "d", &attr->permissions); + if (rc != SSH_OK){ + goto error; + } + + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->atime, + &attr->mtime); + if (rc != SSH_OK){ + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { + rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); + if (rc != SSH_OK){ + goto error; + } + + if (attr->extended_count > 0){ + rc = ssh_buffer_unpack(buf, "ss", + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK){ + goto error; + } + attr->extended_count--; + } + /* just ignore the remaining extensions */ + + while (attr->extended_count > 0){ + ssh_string tmp1,tmp2; + rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); + if (rc != SSH_OK){ + goto error; + } + SAFE_FREE(tmp1); + SAFE_FREE(tmp2); + attr->extended_count--; + } + } + + return attr; + + error: + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + + return NULL; +} + +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname) +{ + switch(session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", session->server_version); + return NULL; + } + + return NULL; +} + +void sftp_set_error(sftp_session sftp, int errnum) { + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +void sftp_message_free(sftp_message msg) +{ + if (msg == NULL) { + return; + } + + SSH_BUFFER_FREE(msg->payload); + SAFE_FREE(msg); +} + +static sftp_request_queue request_queue_new(sftp_message msg) { + sftp_request_queue queue = NULL; + + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) { + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int sftp_enqueue(sftp_session sftp, sftp_message msg) { + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %" PRIu32 " type %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if(queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +static sftp_message sftp_get_message(sftp_packet packet) +{ + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; + int rc; + + switch(packet->type) { + case SSH_FXP_STATUS: + case SSH_FXP_HANDLE: + case SSH_FXP_DATA: + case SSH_FXP_ATTRS: + case SSH_FXP_NAME: + case SSH_FXP_EXTENDED_REPLY: + break; + default: + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown packet type %d", + packet->type); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg = calloc(1, sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg->sftp = packet->sftp; + msg->packet_type = packet->type; + + /* Move the payload from the packet to the message */ + msg->payload = packet->payload; + packet->payload = NULL; + + rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + if (rc != SSH_OK) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Packet with id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + + return msg; +} + +int sftp_read_and_dispatch(sftp_session sftp) +{ + sftp_packet packet = NULL; + sftp_message msg = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + /* something nasty happened reading the packet */ + return -1; + } + + msg = sftp_get_message(packet); + if (msg == NULL) { + return -1; + } + + if (sftp_enqueue(sftp, msg) < 0) { + sftp_message_free(msg); + return -1; + } + + return 0; +} + +sftp_status_message parse_status_msg(sftp_message msg){ + sftp_status_message status; + int rc; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); + return NULL; + } + + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK){ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if(rc != SSH_OK && msg->sftp->version >=3){ + /* These are mandatory from version 3 */ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + if (status->langmsg == NULL) + status->langmsg = strdup(""); + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + status_msg_free(status); + return NULL; + } + + return status; +} + +void status_msg_free(sftp_status_message status){ + if (status == NULL) { + return; + } + + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +#endif /* WITH_SFTP */ From 7455b6ae641b4af49270ee43c75c28be94a7b733 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sun, 2 Jul 2023 17:30:50 +0530 Subject: [PATCH 021/795] Reformat sftp_common.c according to current coding style. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- src/sftp_common.c | 717 ++++++++++++++++++++++++---------------------- 1 file changed, 370 insertions(+), 347 deletions(-) diff --git a/src/sftp_common.c b/src/sftp_common.c index 6fa137de..005fa9da 100644 --- a/src/sftp_common.c +++ b/src/sftp_common.c @@ -75,7 +75,7 @@ sftp_packet sftp_packet_read(sftp_session sftp) do { int s; - // read from channel until 4 bytes have been read or an error occurs + /* read from channel until 4 bytes have been read or an error occurs */ s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); if (s < 0) { goto error; @@ -196,57 +196,58 @@ int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) void sftp_packet_free(sftp_packet packet) { - if (packet == NULL) { - return; - } + if (packet == NULL) { + return; + } - SSH_BUFFER_FREE(packet->payload); - free(packet); + SSH_BUFFER_FREE(packet->payload); + free(packet); } int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) { - uint32_t flags = (attr ? attr->flags : 0); - int rc; - - flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | - SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + uint32_t flags = (attr ? attr->flags : 0); + int rc; - rc = ssh_buffer_pack(buffer, "d", flags); - if (rc != SSH_OK) { - return -1; - } + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); - if (attr != NULL) { - if (flags & SSH_FILEXFER_ATTR_SIZE) { - rc = ssh_buffer_pack(buffer, "q", attr->size); - if (rc != SSH_OK) { + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { return -1; - } } - if (flags & SSH_FILEXFER_ATTR_UIDGID) { - rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); - if (rc != SSH_OK) { - return -1; - } - } + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - rc = ssh_buffer_pack(buffer, "d", attr->permissions); - if (rc != SSH_OK) { - return -1; - } - } + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } - if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { - rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); - if (rc != SSH_OK) { - return -1; - } + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } } - } - return 0; + + return 0; } /* @@ -254,198 +255,204 @@ int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) * baselines from the protocol version 4. * This code is more or less dead but maybe we will need it in the future. */ -static sftp_attributes sftp_parse_attr_4(sftp_session sftp, ssh_buffer buf, - int expectnames) { - sftp_attributes attr; - ssh_string owner = NULL; - ssh_string group = NULL; - uint32_t flags = 0; - int ok = 0; - - /* unused member variable */ - (void) expectnames; - - attr = calloc(1, sizeof(struct sftp_attributes_struct)); - if (attr == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return NULL; - } +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, + ssh_buffer buf, + int expectnames) +{ + sftp_attributes attr = NULL; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; - /* This isn't really a loop, but it is like a try..catch.. */ - do { - if (ssh_buffer_get_u32(buf, &flags) != 4) { - break; + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; } - flags = ntohl(flags); - attr->flags = flags; + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } - if (flags & SSH_FILEXFER_ATTR_SIZE) { - if (ssh_buffer_get_u64(buf, &attr->size) != 8) { - break; - } - attr->size = ntohll(attr->size); - } + flags = ntohl(flags); + attr->flags = flags; - if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { - owner = ssh_buffer_get_ssh_string(buf); - if (owner == NULL) { - break; - } - attr->owner = ssh_string_to_char(owner); - SSH_STRING_FREE(owner); - if (attr->owner == NULL) { - break; - } + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } - group = ssh_buffer_get_ssh_string(buf); - if (group == NULL) { - break; - } - attr->group = ssh_string_to_char(group); - SSH_STRING_FREE(group); - if (attr->group == NULL) { - break; - } - } + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + SSH_STRING_FREE(owner); + if (attr->owner == NULL) { + break; + } - if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { - if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { - break; - } - attr->permissions = ntohl(attr->permissions); + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + SSH_STRING_FREE(group); + if (attr->group == NULL) { + break; + } + } - /* FIXME on windows! */ - switch (attr->permissions & SSH_S_IFMT) { - case SSH_S_IFSOCK: - case SSH_S_IFBLK: - case SSH_S_IFCHR: - case SSH_S_IFIFO: - attr->type = SSH_FILEXFER_TYPE_SPECIAL; - break; - case SSH_S_IFLNK: - attr->type = SSH_FILEXFER_TYPE_SYMLINK; - break; - case SSH_S_IFREG: - attr->type = SSH_FILEXFER_TYPE_REGULAR; - break; - case SSH_S_IFDIR: - attr->type = SSH_FILEXFER_TYPE_DIRECTORY; - break; - default: - attr->type = SSH_FILEXFER_TYPE_UNKNOWN; - break; - } - } + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } - if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { - if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { - break; - } - attr->atime64 = ntohll(attr->atime64); + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { - break; + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } } - attr->atime_nseconds = ntohl(attr->atime_nseconds); - } - } - if (flags & SSH_FILEXFER_ATTR_CREATETIME) { - if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { - break; - } - attr->createtime = ntohll(attr->createtime); + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { - break; + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } } - attr->createtime_nseconds = ntohl(attr->createtime_nseconds); - } - } - if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { - if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { - break; - } - attr->mtime64 = ntohll(attr->mtime64); + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); - if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { - if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { - break; + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } } - attr->mtime_nseconds = ntohl(attr->mtime_nseconds); - } - } - if (flags & SSH_FILEXFER_ATTR_ACL) { - if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { - break; - } - } + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } - if (flags & SSH_FILEXFER_ATTR_EXTENDED) { - if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { - break; - } - attr->extended_count = ntohl(attr->extended_count); + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); - while(attr->extended_count && - (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && - (attr->extended_data = ssh_buffer_get_ssh_string(buf))){ - attr->extended_count--; - } + while (attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))) { + attr->extended_count--; + } - if (attr->extended_count) { - break; - } - } - ok = 1; - } while (0); + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); - if (ok == 0) { - /* break issued somewhere */ - SSH_STRING_FREE(attr->acl); - SSH_STRING_FREE(attr->extended_type); - SSH_STRING_FREE(attr->extended_data); - SAFE_FREE(attr->owner); - SAFE_FREE(attr->group); - SAFE_FREE(attr); + if (ok == 0) { + /* break issued somewhere */ + SSH_STRING_FREE(attr->acl); + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); - ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); - return NULL; - } + return NULL; + } - return attr; + return attr; } enum sftp_longname_field_e { - SFTP_LONGNAME_PERM = 0, - SFTP_LONGNAME_FIXME, - SFTP_LONGNAME_OWNER, - SFTP_LONGNAME_GROUP, - SFTP_LONGNAME_SIZE, - SFTP_LONGNAME_DATE, - SFTP_LONGNAME_TIME, - SFTP_LONGNAME_NAME, + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, }; -static char *sftp_parse_longname(const char *longname, - enum sftp_longname_field_e longname_field) { +static char * sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) +{ const char *p, *q; size_t len, field = 0; p = longname; - /* Find the beginning of the field which is specified by sftp_longname_field_e. */ - while(field != longname_field) { - if(isspace(*p)) { + /* + * Find the beginning of the field which is specified + * by sftp_longname_field_e. + */ + while (field != longname_field) { + if (isspace(*p)) { field++; p++; - while(*p && isspace(*p)) { + while (*p && isspace(*p)) { p++; } } else { @@ -478,8 +485,10 @@ static char *sftp_parse_longname(const char *longname, string extended_data ... more extended data (extended_type - extended_data pairs), so that number of pairs equals extended_count */ -static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, - int expectname) { +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, + ssh_buffer buf, + int expectname) +{ sftp_attributes attr; int rc; @@ -492,8 +501,8 @@ static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, if (expectname) { rc = ssh_buffer_unpack(buf, "ss", - &attr->name, - &attr->longname); + &attr->name, + &attr->longname); if (rc != SSH_OK){ goto error; } @@ -501,12 +510,14 @@ static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, /* Set owner and group if we talk to openssh and have the longname */ if (ssh_get_openssh_version(sftp->session)) { - attr->owner = sftp_parse_longname(attr->longname, SFTP_LONGNAME_OWNER); + attr->owner = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_OWNER); if (attr->owner == NULL) { goto error; } - attr->group = sftp_parse_longname(attr->longname, SFTP_LONGNAME_GROUP); + attr->group = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_GROUP); if (attr->group == NULL) { goto error; } @@ -532,16 +543,16 @@ static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { rc = ssh_buffer_unpack(buf, "dd", - &attr->uid, - &attr->gid); - if (rc != SSH_OK){ + &attr->uid, + &attr->gid); + if (rc != SSH_OK) { goto error; } } if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { rc = ssh_buffer_unpack(buf, "d", &attr->permissions); - if (rc != SSH_OK){ + if (rc != SSH_OK) { goto error; } @@ -569,31 +580,31 @@ static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { rc = ssh_buffer_unpack(buf, "dd", - &attr->atime, - &attr->mtime); - if (rc != SSH_OK){ + &attr->atime, + &attr->mtime); + if (rc != SSH_OK) { goto error; } } if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); - if (rc != SSH_OK){ + if (rc != SSH_OK) { goto error; } - if (attr->extended_count > 0){ + if (attr->extended_count > 0) { rc = ssh_buffer_unpack(buf, "ss", - &attr->extended_type, - &attr->extended_data); - if (rc != SSH_OK){ + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK) { goto error; } attr->extended_count--; } /* just ignore the remaining extensions */ - while (attr->extended_count > 0){ + while (attr->extended_count > 0) { ssh_string tmp1,tmp2; rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); if (rc != SSH_OK){ @@ -607,7 +618,7 @@ static sftp_attributes sftp_parse_attr_3(sftp_session sftp, ssh_buffer buf, return attr; - error: +error: SSH_STRING_FREE(attr->extended_type); SSH_STRING_FREE(attr->extended_data); SAFE_FREE(attr->name); @@ -625,27 +636,29 @@ sftp_attributes sftp_parse_attr(sftp_session session, ssh_buffer buf, int expectname) { - switch(session->version) { + switch (session->version) { case 4: - return sftp_parse_attr_4(session, buf, expectname); + return sftp_parse_attr_4(session, buf, expectname); case 3: case 2: case 1: case 0: - return sftp_parse_attr_3(session, buf, expectname); + return sftp_parse_attr_3(session, buf, expectname); default: - ssh_set_error(session->session, SSH_FATAL, - "Version %d unsupported by client", session->server_version); - return NULL; - } + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", + session->server_version); + return NULL; + } - return NULL; + return NULL; } -void sftp_set_error(sftp_session sftp, int errnum) { - if (sftp != NULL) { - sftp->errnum = errnum; - } +void sftp_set_error(sftp_session sftp, int errnum) +{ + if (sftp != NULL) { + sftp->errnum = errnum; + } } void sftp_message_free(sftp_message msg) @@ -658,91 +671,95 @@ void sftp_message_free(sftp_message msg) SAFE_FREE(msg); } -static sftp_request_queue request_queue_new(sftp_message msg) { - sftp_request_queue queue = NULL; +static sftp_request_queue request_queue_new(sftp_message msg) +{ + sftp_request_queue queue = NULL; - queue = calloc(1, sizeof(struct sftp_request_queue_struct)); - if (queue == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } - queue->message = msg; + queue->message = msg; - return queue; + return queue; } -static void request_queue_free(sftp_request_queue queue) { - if (queue == NULL) { - return; - } +static void request_queue_free(sftp_request_queue queue) +{ + if (queue == NULL) { + return; + } - ZERO_STRUCTP(queue); - SAFE_FREE(queue); + ZERO_STRUCTP(queue); + SAFE_FREE(queue); } -static int sftp_enqueue(sftp_session sftp, sftp_message msg) { - sftp_request_queue queue = NULL; - sftp_request_queue ptr; +static int sftp_enqueue(sftp_session sftp, sftp_message msg) +{ + sftp_request_queue queue = NULL; + sftp_request_queue ptr; - queue = request_queue_new(msg); - if (queue == NULL) { - return -1; - } + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } - SSH_LOG(SSH_LOG_PACKET, - "Queued msg id %" PRIu32 " type %d", - msg->id, msg->packet_type); + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %" PRIu32 " type %d", + msg->id, msg->packet_type); - if(sftp->queue == NULL) { - sftp->queue = queue; - } else { - ptr = sftp->queue; - while(ptr->next) { - ptr=ptr->next; /* find end of linked list */ + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ } - ptr->next = queue; /* add it on bottom */ - } - return 0; + return 0; } /* * Pulls a message from the queue based on the ID. * Returns NULL if no message has been found. */ -sftp_message sftp_dequeue(sftp_session sftp, uint32_t id){ - sftp_request_queue prev = NULL; - sftp_request_queue queue; - sftp_message msg; +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id) +{ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if (queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } - if(sftp->queue == NULL) { return NULL; - } - - queue = sftp->queue; - while (queue) { - if(queue->message->id == id) { - /* remove from queue */ - if (prev == NULL) { - sftp->queue = queue->next; - } else { - prev->next = queue->next; - } - msg = queue->message; - request_queue_free(queue); - SSH_LOG(SSH_LOG_PACKET, - "Dequeued msg id %" PRIu32 " type %d", - msg->id, - msg->packet_type); - return msg; - } - prev = queue; - queue = queue->next; - } - - return NULL; } static sftp_message sftp_get_message(sftp_packet packet) @@ -751,7 +768,7 @@ static sftp_message sftp_get_message(sftp_packet packet) sftp_message msg = NULL; int rc; - switch(packet->type) { + switch (packet->type) { case SSH_FXP_STATUS: case SSH_FXP_HANDLE: case SSH_FXP_DATA: @@ -823,68 +840,74 @@ int sftp_read_and_dispatch(sftp_session sftp) return 0; } -sftp_status_message parse_status_msg(sftp_message msg){ - sftp_status_message status; - int rc; +sftp_status_message parse_status_msg(sftp_message msg) +{ + sftp_status_message status = NULL; + int rc; - if (msg->packet_type != SSH_FXP_STATUS) { - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Not a ssh_fxp_status message passed in!"); - sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); - return NULL; - } + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); + return NULL; + } - status = calloc(1, sizeof(struct sftp_status_message_struct)); - if (status == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } - status->id = msg->id; - rc = ssh_buffer_unpack(msg->payload, "d", - &status->status); - if (rc != SSH_OK){ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - rc = ssh_buffer_unpack(msg->payload, "ss", - &status->errormsg, - &status->langmsg); - - if(rc != SSH_OK && msg->sftp->version >=3){ - /* These are mandatory from version 3 */ - SAFE_FREE(status); - ssh_set_error(msg->sftp->session, SSH_FATAL, - "Invalid SSH_FXP_STATUS message"); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - return NULL; - } - if (status->errormsg == NULL) - status->errormsg = strdup("No error message in packet"); - if (status->langmsg == NULL) - status->langmsg = strdup(""); - if (status->errormsg == NULL || status->langmsg == NULL) { - ssh_set_error_oom(msg->sftp->session); - sftp_set_error(msg->sftp, SSH_FX_FAILURE); - status_msg_free(status); - return NULL; - } + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK) { + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if (rc != SSH_OK && msg->sftp->version >= 3) { + /* These are mandatory from version 3 */ + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + + if (status->langmsg == NULL) + status->langmsg = strdup(""); + + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + status_msg_free(status); + return NULL; + } - return status; + return status; } -void status_msg_free(sftp_status_message status){ - if (status == NULL) { - return; - } +void status_msg_free(sftp_status_message status) +{ + if (status == NULL) { + return; + } - SAFE_FREE(status->errormsg); - SAFE_FREE(status->langmsg); - SAFE_FREE(status); + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); } #endif /* WITH_SFTP */ From c1606da45099e6be4f3652d5f64ebfd409fd9dfc Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Fri, 26 May 2023 13:21:48 +0530 Subject: [PATCH 022/795] Introduce sftp async i/o (aio) api The existing sftp async read api has two problems : 1. sftp_async_read() assumes that the value of the third parameter count is same as the number of bytes requested to read in the corresponding call to sftp_async_read_begin(). But the documentation of sftp_async_read() allows the value of count parameter to be more than that requested length. If value of count parameter is more than that requested length then sftp_async_read() updates the file->offset incorrectly which leads to further read/writes occuring from incorrect offsets. The problem here is that sftp_async_read() doesn't know about the number of bytes requested to read specified in the call to sftp_async_read_begin(), and it wrongly assumes the value of its count parameter (which is actually the size of the buffer to store the read data) to be the same as the number of bytes requested to read. 2. sftp_async_read_begin() returns an uint32_t type value type casted to int as a request identifier, whereas sftp_async_read() expects an uint32_t type value as a request identifier. Due to this the user has to typecast the identifier returned by sftp_async_read_begin() from int to uint32_t and then pass it to sftp_async_read(). This type casting is cumbersome for the user and hence the approach is not user-friendly. This commit solves the above two problems by introducing a new sftp aio api. The sftp_aio_begin_*() functions in the api send an i/o request to the sftp server and provide the caller a dynamically allocated structure storing information about the sent request. Information like number of bytes requested for i/o, id of sent request etc is stored in the structure. That structure should be provided to the sftp_aio_wait_*() functions in the api which wait for the response corresponding to the request whose info is stored in the provided structure. The libssh user is supposed to handle that structure through an opaque type sftp_aio. Since the structure stores the number of bytes requested for i/o, sftp_aio_wait_*() knows about the number of bytes requested for i/o (specified in the call to sftp_aio_begin_*()) and hence updates the file->offset correctly solving problem #1 present in the existing async api. Since the structure provided by sftp_aio_begin_*() (containing the request id) is supplied to sftp_aio_wait_*(), no casting of id's needs to be done by the user solving problem #2 of the existing async api. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 211 ++++++++++++++++++ src/CMakeLists.txt | 1 + src/libssh.map | 5 + src/sftp_aio.c | 490 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 707 insertions(+) create mode 100644 src/sftp_aio.c diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index a1aa783a..209973a8 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -78,6 +78,7 @@ typedef struct sftp_session_struct* sftp_session; typedef struct sftp_status_message_struct* sftp_status_message; typedef struct sftp_statvfs_struct* sftp_statvfs_t; typedef struct sftp_limits_struct* sftp_limits_t; +typedef struct sftp_aio_struct* sftp_aio; struct sftp_session_struct { ssh_session session; @@ -580,6 +581,216 @@ LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_ */ LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); +/** + * @brief Deallocate memory corresponding to a sftp aio handle. + * + * This function deallocates memory corresponding to the aio handle returned + * by the sftp_aio_begin_*() functions. Users can use this function to free + * memory corresponding to an aio handle for an outstanding async i/o request + * on encountering some error. + * + * @param aio sftp aio handle corresponding to which memory has + * to be deallocated. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_wait_read() + * @see sftp_aio_begin_write() + * @see sftp_aio_wait_write() + */ +LIBSSH_API void sftp_aio_free(sftp_aio aio); +#define SFTP_AIO_FREE(x) \ + do { if(x != NULL) {sftp_aio_free(x); x = NULL;} } while(0) + +/** + * @brief Start an asynchronous read from a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions : + * + * sftp_aio_begin_read() and sftp_aio_wait_read(). + * + * - The first step is to call sftp_aio_begin_read(). This function sends a + * read request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_read() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_read(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_read() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_read() must not be closed before the + * corresponding call to sftp_aio_wait_read(). After sftp_aio_wait_read() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Number of bytes to read. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns SSH_OK on success, SSH_ERROR on error with sftp and ssh + * errors set. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_aio_begin_read() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_read() is called. + * Not calling sftp_aio_wait_read() will lead to memory + * leaks. + * + * @see sftp_aio_wait_read() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API int sftp_aio_begin_read(sftp_file file, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous read to complete and store the read data + * in the supplied buffer. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't been + * executed yet, this function returns SSH_AGAIN and must be called again + * using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_read(). + * + * @param buf Pointer to the buffer in which read data will be stored. + * + * @param buf_size Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_aio_begin_read() call. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid sftp aio handle + * may never return. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size); + +/** + * @brief Start an asynchronous write to a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous write. To do so, you must call 2 functions : + * + * sftp_aio_begin_write() and sftp_aio_wait_write(). + * + * - The first step is to call sftp_aio_begin_write(). This function sends a + * write request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_write() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_write(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_write() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_write() must not be closed before the + * corresponding call to sftp_aio_wait_write(). After sftp_aio_wait_write() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * @param file The opened sftp file handle to write to. + * + * @param buf Pointer to the buffer containing data to write. + * + * @param len Number of bytes to write. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns SSH_OK on success, SSH_ERROR with sftp and ssh errors + * set. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_aio_begin_write() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_write() is + * called. Not calling sftp_aio_wait_write() will lead to + * memory leaks. + * + * @see sftp_aio_wait_write() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API int sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous write to complete. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't + * been executed yet, this function returns SSH_AGAIN and must be called + * again using the same sftp aio handle. + * + * On success, this function returns the number of bytes written. + * The SFTP protocol doesn't support partial writes to remote files, + * hence on success this returned value will always be equal to the + * len passed in the previous corresponding call to sftp_aio_begin_write(). + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_write(). + * + * @return Number of bytes written on success, SSH_ERROR + * if an error occurred, SSH_AGAIN if the file is + * opened in nonblocking mode and the request hasn't + * been executed yet. + * + * @warning A call to this function with an invalid sftp aio handle + * may never return. + * + * @see sftp_aio_begin_write() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_write(sftp_aio *aio); + /** * @brief Seek to a specific location in a file. * diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c54824d..c06cf697 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -253,6 +253,7 @@ if (WITH_SFTP) ${libssh_SRCS} sftp.c sftp_common.c + sftp_aio.c ) if (WITH_SERVER) diff --git a/src/libssh.map b/src/libssh.map index f81d8abe..5797261a 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -464,5 +464,10 @@ LIBSSH_AFTER_4_9_0 global: sftp_channel_default_data_callback; sftp_channel_default_subsystem_request; + sftp_aio_begin_read; + sftp_aio_begin_write; + sftp_aio_free; + sftp_aio_wait_read; + sftp_aio_wait_write; } LIBSSH_4_9_0; diff --git a/src/sftp_aio.c b/src/sftp_aio.c new file mode 100644 index 00000000..d0c0d874 --- /dev/null +++ b/src/sftp_aio.c @@ -0,0 +1,490 @@ +/* + * sftp_aio.c - Secure FTP functions for asynchronous i/o + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * Copyright (c) 2023 by Eshan Kelkar + * + * 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 "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +#ifdef WITH_SFTP + +struct sftp_aio_struct { + sftp_file file; + uint32_t id; + size_t len; +}; + +static sftp_aio sftp_aio_new(void) +{ + sftp_aio aio = NULL; + aio = calloc(1, sizeof(struct sftp_aio_struct)); + return aio; +} + +void sftp_aio_free(sftp_aio aio) +{ + SAFE_FREE(aio); +} + +int sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number of " + "bytes to read"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + file->handle, + file->offset, + len); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = len; + + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we read len bytes from the file */ + file->offset += len; + *aio = aio_handle; + return SSH_OK; +} + +ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size) +{ + sftp_file file = NULL; + size_t bytes_requested; + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + uint32_t string_len, host_len; + int rc, err; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed " + "instead of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf_size < bytes_requested) { + ssh_set_error(sftp->session, SSH_FATAL, + "Buffer size (%zu, passed by the caller) is " + "smaller than the number of bytes requested " + "to read (%zu, as per the supplied sftp aio)", + buf_size, bytes_requested); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + /* handle an existing request */ + while (msg == NULL) { + if (file->nonblocking) { + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp, (*aio)->id); + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server : %s", status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + /* Update the offset correctly */ + file->offset = file->offset - bytes_requested; + err = SSH_OK; + } + + status_msg_free(status); + return err; + + case SSH_FXP_DATA: + rc = ssh_buffer_get_u32(msg->payload, &string_len); + if (rc == 0) { + /* Insufficient data in the buffer */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + host_len = ntohl(string_len); + if (host_len > buf_size) { + /* + * This should never happen, as according to the + * SFTP protocol the server reads bytes less than + * or equal to the number of bytes requested to read. + * + * And we have checked before that the buffer size is + * greater than or equal to the number of bytes requested + * to read, hence code of this if block should never + * get executed. + */ + ssh_set_error(sftp->session, SSH_FATAL, + "DATA packet (%u bytes) received from sftp server " + "cannot fit into the supplied buffer (%zu bytes)", + host_len, buf_size); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return SSH_ERROR; + } + + string_len = ssh_buffer_get_data(msg->payload, buf, host_len); + if (string_len != host_len) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + /* Update the offset with the correct value */ + file->offset = file->offset - (bytes_requested - string_len); + sftp_message_free(msg); + return string_len; + + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + return SSH_ERROR; /* not reached */ +} + +int sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead " + "of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number " + "of bytes to write"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = sftp_get_new_id(sftp); + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + len, /* len of datastring */ + len, buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = len; + + rc = sftp_packet_write(sftp, SSH_FXP_WRITE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we wrote len bytes to the file */ + file->offset += len; + *aio = aio_handle; + return SSH_OK; +} + +ssize_t sftp_aio_wait_write(sftp_aio *aio) +{ + sftp_file file = NULL; + size_t bytes_requested; + + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* This should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + while (msg == NULL) { + if (file->nonblocking) { + if (ssh_channel_poll(sftp->channel, 0) == 0) { + /* we cannot block */ + return SSH_AGAIN; + } + } + + if (sftp_read_and_dispatch(sftp) < 0) { + /* something nasty has happened */ + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + msg = sftp_dequeue(sftp, (*aio)->id); + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status == SSH_FX_OK) { + status_msg_free(status); + return bytes_requested; + } + + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return SSH_ERROR; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; +} + +#endif /* WITH_SFTP */ From 4768d2970a05044ac4ace45b5f46093b9f5b46e5 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sat, 27 May 2023 12:45:39 +0530 Subject: [PATCH 023/795] Add tests for sftp aio api torture_sftp_aio.c has been added in tests/client/ directory. It contains torture_sftp_aio_read(), torture_sftp_aio_write() and torture_sftp_aio_negative(). torture_sftp_aio_read() tests sftp_aio_begin_read() and sftp_aio_wait_read() to perform async reads. torture_sftp_aio_write() tests sftp_aio_begin_write() and sftp_aio_wait_write() to perform async writes. torture_sftp_aio_negative() performs negative tests on the sftp aio read/write API. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/client/CMakeLists.txt | 1 + tests/client/torture_sftp_aio.c | 369 ++++++++++++++++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 tests/client/torture_sftp_aio.c diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 4c4b1d8e..cde4838f 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -54,6 +54,7 @@ if (WITH_SFTP) torture_sftp_limits torture_sftp_rename torture_sftp_expand_path + torture_sftp_aio ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff --git a/tests/client/torture_sftp_aio.c b/tests/client/torture_sftp_aio.c new file mode 100644 index 00000000..4f62fa38 --- /dev/null +++ b/tests/client/torture_sftp_aio.c @@ -0,0 +1,369 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_aio_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + struct { + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytes_read; + } a = {0}, b = {0}; + + sftp_file file = NULL; + sftp_attributes file_attr = NULL; + int fd; + + size_t chunk_size = MAX_XFER_BUF_SIZE; + int in_flight_requests = 20; + + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + + size_t file_size; + size_t bytes_requested; + size_t to_read, total_bytes_read; + int i, rc; + + aio_queue = ssh_list_new(); + assert_non_null(aio_queue); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Get the file size */ + file_attr = sftp_stat(t->sftp, SSH_EXECUTABLE); + assert_non_null(file_attr); + file_size = file_attr->size; + + bytes_requested = 0; + for (i = 0; + i < in_flight_requests && bytes_requested < file_size; + ++i) { + to_read = file_size - bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + rc = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(rc, SSH_OK); + bytes_requested += to_read; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + total_bytes_read = 0; + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, sizeof(a.buf)); + assert_int_not_equal(a.bytes_read, SSH_ERROR); + + total_bytes_read += (size_t)a.bytes_read; + if (total_bytes_read != file_size) { + assert_int_equal((size_t)a.bytes_read, chunk_size); + /* + * Failure of this assertion means that a short + * read is encountered but we have not reached + * the end of file yet. A short read before reaching + * the end of file should not occur for disk files + * according to the SFTP protocol. (In our code the + * file SSH_EXECUTABLE being read is a disk file) + */ + } + + /* + * Check whether the bytes read above are bytes + * present in the file or some garbage was stored + * in the buffer supplied to sftp_aio_wait_read(). + */ + b.bytes_read = read(fd, b.buf, a.bytes_read); + assert_int_equal(a.bytes_read, b.bytes_read); + + rc = memcmp(a.buf, b.buf, (size_t)a.bytes_read); + assert_int_equal(rc, 0); + + /* Issue more read requests if needed */ + if (bytes_requested == file_size) { + continue; + } + + /* else issue more requests */ + to_read = file_size - bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + rc = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(rc, SSH_OK); + bytes_requested += to_read; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + /* + * Check whether sftp server responds with an + * eof for more requests. + */ + rc = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(rc, SSH_OK); + + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, sizeof(a.buf)); + assert_int_equal(a.bytes_read, 0); + + /* Cleanup */ + sftp_attributes_free(file_attr); + close(fd); + sftp_close(file); + ssh_list_free(aio_queue); +} + +static void torture_sftp_aio_write(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + int fd; + + struct { + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytes; + } wr = {0}, rd = {0}; + + int in_flight_requests; + sftp_aio *aio_queue = NULL; + int rc, i; + + in_flight_requests = 2; + aio_queue = malloc(sizeof(sftp_aio) * in_flight_requests); + assert_non_null(aio_queue); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + for (i = 0; i < in_flight_requests; ++i) { + rc = sftp_aio_begin_write(file, wr.buf, sizeof(wr.buf), &aio_queue[i]); + assert_int_equal(rc, SSH_OK); + } + + for (i = 0; i < in_flight_requests; ++i) { + wr.bytes = sftp_aio_wait_write(&aio_queue[i]); + assert_int_equal(wr.bytes, sizeof(wr.buf)); + + /* + * Check whether the bytes written to the file + * by SFTP AIO write api were the bytes present + * in the buffer to write or some garbage was + * written to the file. + */ + rd.bytes = read(fd, rd.buf, wr.bytes); + assert_int_equal(rd.bytes, wr.bytes); + + rc = memcmp(rd.buf, wr.buf, wr.bytes); + assert_int_equal(rc, 0); + } + + /* Cleanup */ + close(fd); + sftp_close(file); + free(aio_queue); + + rc = unlink(file_path); + assert_int_equal(rc, 0); +} + +static void torture_sftp_aio_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char buf[MAX_XFER_BUF_SIZE] = {0}; + sftp_file file = NULL; + char file_path[128] = {0}; + sftp_aio aio = NULL; + + size_t chunk_size = MAX_XFER_BUF_SIZE; + ssize_t bytes_read, bytes_written; + int rc; + + /* Open a file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Negative tests for reading start */ + + /* Passing NULL as the sftp file handle */ + rc = sftp_aio_begin_read(NULL, chunk_size, &aio); + assert_int_equal(rc, SSH_ERROR); + + /* Passing 0 as the number of bytes to read */ + rc = sftp_aio_begin_read(file, 0, &aio); + assert_int_equal(rc, SSH_ERROR); + + /* Passing NULL instead of a pointer to a location to store an aio handle */ + rc = sftp_aio_begin_read(file, chunk_size, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes_read = sftp_aio_wait_read(NULL, buf, sizeof(buf)); + assert_int_equal(bytes_read, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + rc = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(rc, SSH_OK); + + bytes_read = sftp_aio_wait_read(&aio, NULL, sizeof(buf)); + assert_int_equal(bytes_read, SSH_ERROR); + + /* Passing 0 as the buffer size */ + rc = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(rc, SSH_OK); + + bytes_read = sftp_aio_wait_read(&aio, buf, 0); + assert_int_equal(bytes_read, SSH_ERROR); + + /* + * Test for the scenario when the number + * of bytes read exceed the buffer size. + */ + rc = sftp_seek(file, 0); /* Seek to the start of file */ + assert_int_equal(rc, 0); + + rc = sftp_aio_begin_read(file, 2, &aio); + assert_int_equal(rc, SSH_OK); + + bytes_read = sftp_aio_wait_read(&aio, buf, 1); + assert_int_equal(bytes_read, SSH_ERROR); + + sftp_close(file); + + /* Open a file for writing */ + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Negative tests for writing start */ + + /* Passing NULL as the sftp file handle */ + rc = sftp_aio_begin_write(NULL, buf, MAX_XFER_BUF_SIZE, &aio); + assert_int_equal(rc, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + rc = sftp_aio_begin_write(file, NULL, MAX_XFER_BUF_SIZE, &aio); + assert_int_equal(rc, SSH_ERROR); + + /* Passing 0 as the size of buffer */ + rc = sftp_aio_begin_write(file, buf, 0, &aio); + assert_int_equal(rc, SSH_ERROR); + + /* Passing NULL instead of a pointer to a location to store an aio handle */ + rc = sftp_aio_begin_write(file, buf, MAX_XFER_BUF_SIZE, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes_written = sftp_aio_wait_write(NULL); + assert_int_equal(bytes_written, SSH_ERROR); + + sftp_close(file); + rc = unlink(file_path); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_aio_read, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_write, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_negative, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} From d8790d06c46cb6624a279a27406ac1e1e5216535 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Thu, 1 Jun 2023 20:18:05 +0530 Subject: [PATCH 024/795] Reformat tests/benchmarks/benchmarks.c tests/benchmarks/benchmarks.c has been reformatted according to current coding style. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/benchmarks/benchmarks.c | 612 ++++++++++++++++++---------------- 1 file changed, 321 insertions(+), 291 deletions(-) diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 58f3ac37..178ac7f9 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -27,42 +27,42 @@ #include #include -struct benchmark benchmarks[]= { +struct benchmark benchmarks[] = { { - .name="benchmark_raw_upload", - .fct=benchmarks_raw_up, - .enabled=0 + .name = "benchmark_raw_upload", + .fct = benchmarks_raw_up, + .enabled = 0 }, { - .name="benchmark_raw_download", - .fct=benchmarks_raw_down, - .enabled=0 + .name = "benchmark_raw_download", + .fct = benchmarks_raw_down, + .enabled = 0 }, { - .name="benchmark_scp_upload", - .fct=benchmarks_scp_up, - .enabled=0 + .name = "benchmark_scp_upload", + .fct = benchmarks_scp_up, + .enabled = 0 }, { - .name="benchmark_scp_download", - .fct=benchmarks_scp_down, - .enabled=0 + .name = "benchmark_scp_download", + .fct = benchmarks_scp_down, + .enabled = 0 }, #ifdef WITH_SFTP { - .name="benchmark_sync_sftp_upload", - .fct=benchmarks_sync_sftp_up, - .enabled=0 + .name = "benchmark_sync_sftp_upload", + .fct = benchmarks_sync_sftp_up, + .enabled = 0 }, { - .name="benchmark_sync_sftp_download", - .fct=benchmarks_sync_sftp_down, - .enabled=0 + .name = "benchmark_sync_sftp_download", + .fct = benchmarks_sync_sftp_down, + .enabled = 0 }, { - .name="benchmark_async_sftp_download", - .fct=benchmarks_async_sftp_down, - .enabled=0 + .name = "benchmark_async_sftp_download", + .fct = benchmarks_async_sftp_down, + .enabled = 0 } #endif /* WITH_SFTP */ }; @@ -73,7 +73,7 @@ struct benchmark benchmarks[]= { const char *argp_program_version = "libssh benchmarks 2011-08-28"; const char *argp_program_bug_address = "Aris Adamantiadis "; -static char **cmdline; +static char **cmdline = NULL; /* Program documentation. */ static char doc[] = "libssh benchmarks"; @@ -81,128 +81,126 @@ static char doc[] = "libssh benchmarks"; /* The options we understand. */ static struct argp_option options[] = { - { - .name = "verbose", - .key = 'v', - .arg = NULL, - .flags = 0, - .doc = "Make libssh benchmark more verbose", - .group = 0 - }, - { - .name = "raw-upload", - .key = '1', - .arg = NULL, - .flags = 0, - .doc = "Upload raw data using channel", - .group = 0 - }, - { - .name = "raw-download", - .key = '2', - .arg = NULL, - .flags = 0, - .doc = "Download raw data using channel", - .group = 0 - }, - { - .name = "scp-upload", - .key = '3', - .arg = NULL, - .flags = 0, - .doc = "Upload data using SCP", - .group = 0 - }, - { - .name = "scp-download", - .key = '4', - .arg = NULL, - .flags = 0, - .doc = "Download data using SCP", - .group = 0 - }, - { - .name = "sync-sftp-upload", - .key = '5', - .arg = NULL, - .flags = 0, - .doc = "Upload data using synchronous SFTP", - .group = 0 - - }, - { - .name = "sync-sftp-download", - .key = '6', - .arg = NULL, - .flags = 0, - .doc = "Download data using synchronous SFTP (slow)", - .group = 0 - - }, - { - .name = "async-sftp-download", - .key = '7', - .arg = NULL, - .flags = 0, - .doc = "Download data using asynchronous SFTP (fast)", - .group = 0 - - }, - { - .name = "host", - .key = 'h', - .arg = "HOST", - .flags = 0, - .doc = "Add a host to connect for benchmark (format user@hostname)", - .group = 0 - }, - { - .name = "size", - .key = 's', - .arg = "MBYTES", - .flags = 0, - .doc = "MBytes of data to send/receive per test", - .group = 0 - }, - { - .name = "chunk", - .key = 'c', - .arg = "bytes", - .flags = 0, - .doc = "size of data chunks to send/receive", - .group = 0 - }, - { - .name = "prequests", - .key = 'p', - .arg = "number [20]", - .flags = 0, - .doc = "[async SFTP] number of concurrent requests", - .group = 0 - }, - { - .name = "cipher", - .key = 'C', - .arg = "cipher", - .flags = 0, - .doc = "Cryptographic cipher to be used", - .group = 0 - }, - - {NULL, 0, NULL, 0, NULL, 0} + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh benchmark more verbose", + .group = 0 + }, + { + .name = "raw-upload", + .key = '1', + .arg = NULL, + .flags = 0, + .doc = "Upload raw data using channel", + .group = 0 + }, + { + .name = "raw-download", + .key = '2', + .arg = NULL, + .flags = 0, + .doc = "Download raw data using channel", + .group = 0 + }, + { + .name = "scp-upload", + .key = '3', + .arg = NULL, + .flags = 0, + .doc = "Upload data using SCP", + .group = 0 + }, + { + .name = "scp-download", + .key = '4', + .arg = NULL, + .flags = 0, + .doc = "Download data using SCP", + .group = 0 + }, + { + .name = "sync-sftp-upload", + .key = '5', + .arg = NULL, + .flags = 0, + .doc = "Upload data using synchronous SFTP", + .group = 0 + }, + { + .name = "sync-sftp-download", + .key = '6', + .arg = NULL, + .flags = 0, + .doc = "Download data using synchronous SFTP (slow)", + .group = 0 + }, + { + .name = "async-sftp-download", + .key = '7', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP (fast)", + .group = 0 + }, + { + .name = "host", + .key = 'h', + .arg = "HOST", + .flags = 0, + .doc = "Add a host to connect for benchmark (format user@hostname)", + .group = 0 + }, + { + .name = "size", + .key = 's', + .arg = "MBYTES", + .flags = 0, + .doc = "MBytes of data to send/receive per test", + .group = 0 + }, + { + .name = "chunk", + .key = 'c', + .arg = "bytes", + .flags = 0, + .doc = "size of data chunks to send/receive", + .group = 0 + }, + { + .name = "prequests", + .key = 'p', + .arg = "number [20]", + .flags = 0, + .doc = "[async SFTP] number of concurrent requests", + .group = 0 + }, + { + .name = "cipher", + .key = 'C', + .arg = "cipher", + .flags = 0, + .doc = "Cryptographic cipher to be used", + .group = 0 + }, + + {NULL, 0, NULL, 0, NULL, 0} }; /* Parse a single option. */ -static error_t parse_opt (int key, char *arg, struct argp_state *state) { - /* Get the input argument from argp_parse, which we - * know is a pointer to our arguments structure. - */ - struct argument_s *arguments = state->input; +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; - /* arg is currently not used */ - (void) arg; + /* arg is currently not used */ + (void) arg; - switch (key) { + switch (key) { case '1': case '2': case '3': @@ -210,42 +208,43 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { case '5': case '6': case '7': - benchmarks[key - '1'].enabled = 1; - arguments->ntests ++; - break; + benchmarks[key - '1'].enabled = 1; + arguments->ntests++; + break; case 'v': - arguments->verbose++; - break; + arguments->verbose++; + break; case 's': - arguments->datasize = atoi(arg); - break; + arguments->datasize = atoi(arg); + break; case 'p': - arguments->concurrent_requests = atoi(arg); - break; + arguments->concurrent_requests = atoi(arg); + break; case 'c': - arguments->chunksize = atoi(arg); - break; + arguments->chunksize = atoi(arg); + break; case 'C': - arguments->cipher = arg; - break; + arguments->cipher = arg; + break; case 'h': - if(arguments->nhosts >= MAX_HOSTS_CONNECT){ - fprintf(stderr, "Too much hosts\n"); - return ARGP_ERR_UNKNOWN; - } - arguments->hosts[arguments->nhosts]=arg; - arguments->nhosts++; - break; + if (arguments->nhosts >= MAX_HOSTS_CONNECT) { + fprintf(stderr, "Too much hosts\n"); + return ARGP_ERR_UNKNOWN; + } + + arguments->hosts[arguments->nhosts] = arg; + arguments->nhosts++; + break; case ARGP_KEY_ARG: - /* End processing here. */ - cmdline = &state->argv [state->next - 1]; - state->next = state->argc; - break; + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; default: - return ARGP_ERR_UNKNOWN; - } + return ARGP_ERR_UNKNOWN; + } - return 0; + return 0; } /* Our argp parser. */ @@ -253,150 +252,181 @@ static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; #endif /* HAVE_ARGP_H */ -static void cmdline_parse(int argc, char **argv, struct argument_s *arguments) { - /* - * Parse our arguments; every option seen by parse_opt will - * be reflected in arguments. - */ +static void cmdline_parse(int argc, char **argv, struct argument_s *arguments) +{ + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ #ifdef HAVE_ARGP_H - argp_parse(&argp, argc, argv, 0, 0, arguments); + argp_parse(&argp, argc, argv, 0, 0, arguments); #else /* HAVE_ARGP_H */ - (void) argc; - (void) argv; - arguments->hosts[0]="localhost"; - arguments->nhosts=1; + (void) argc; + (void) argv; + arguments->hosts[0] = "localhost"; + arguments->nhosts = 1; #endif /* HAVE_ARGP_H */ } -static void arguments_init(struct argument_s *arguments){ - memset(arguments,0,sizeof(*arguments)); - arguments->chunksize=32758; - arguments->concurrent_requests=20; - arguments->datasize = 10; +static void arguments_init(struct argument_s *arguments) +{ + memset(arguments, 0, sizeof(*arguments)); + arguments->chunksize = 32758; + arguments->concurrent_requests = 20; + arguments->datasize = 10; } -static ssh_session connect_host(const char *host, int verbose, char *cipher){ - ssh_session session=ssh_new(); - if(session==NULL) - goto error; - if(ssh_options_set(session,SSH_OPTIONS_HOST, host)<0) - goto error; - ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); - if(cipher != NULL){ - if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) || - ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher)){ - goto error; +static ssh_session connect_host(const char *host, int verbose, char *cipher) +{ + ssh_session session = ssh_new(); + if (session == NULL) + goto error; + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) + goto error; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if (cipher != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) || + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher)) { + goto error; + } } - } - ssh_options_parse_config(session, NULL); - if(ssh_connect(session)==SSH_ERROR) - goto error; - if(ssh_userauth_autopubkey(session,NULL) != SSH_AUTH_SUCCESS) - goto error; - return session; + + ssh_options_parse_config(session, NULL); + if (ssh_connect(session) == SSH_ERROR) + goto error; + if (ssh_userauth_autopubkey(session, NULL) != SSH_AUTH_SUCCESS) + goto error; + return session; + error: - fprintf(stderr,"Error connecting to \"%s\": %s\n",host,ssh_get_error(session)); - ssh_free(session); - return NULL; + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, ssh_get_error(session)); + ssh_free(session); + return NULL; } -static char *network_speed(float bps){ - static char buf[128]; - if(bps > 1000*1000*1000){ - /* Gbps */ - snprintf(buf,sizeof(buf),"%f Gbps",bps/(1000*1000*1000)); - } else if(bps > 1000*1000){ - /* Mbps */ - snprintf(buf,sizeof(buf),"%f Mbps",bps/(1000*1000)); - } else if(bps > 1000){ - snprintf(buf,sizeof(buf),"%f Kbps",bps/1000); - } else { - snprintf(buf,sizeof(buf),"%f bps",bps); - } - return buf; +static char *network_speed(float bps) +{ + static char buf[128]; + if (bps > 1000 * 1000 * 1000) { + /* Gbps */ + snprintf(buf, sizeof(buf), "%f Gbps", bps / (1000 * 1000 * 1000)); + } else if (bps > 1000 * 1000) { + /* Mbps */ + snprintf(buf, sizeof(buf), "%f Mbps", bps / (1000 * 1000)); + } else if (bps > 1000) { + snprintf(buf, sizeof(buf), "%f Kbps", bps / 1000); + } else { + snprintf(buf, sizeof(buf), "%f bps", bps); + } + + return buf; } static void do_benchmarks(ssh_session session, struct argument_s *arguments, - const char *hostname){ - float ping_rtt=0.0; - float ssh_rtt=0.0; - float bps=0.0; - int i; - int err; - struct benchmark *b; - - if(arguments->verbose>0) - fprintf(stdout,"Testing ICMP RTT\n"); - err=benchmarks_ping_latency(hostname, &ping_rtt); - if(err == 0){ - fprintf(stdout,"ping RTT : %f ms\n",ping_rtt); - } - err=benchmarks_ssh_latency(session, &ssh_rtt); - if(err==0){ - fprintf(stdout, "SSH RTT : %f ms. Theoretical max BW (win=128K) : %s\n",ssh_rtt,network_speed(128000.0/(ssh_rtt / 1000.0))); - } - for (i=0 ; ienabled){ - err=b->fct(session,arguments,&bps); - if(err==0){ - fprintf(stdout, "%s : %s : %s\n",hostname, b->name, network_speed(bps)); - } + const char *hostname) +{ + float ping_rtt = 0.0; + float ssh_rtt = 0.0; + float bps = 0.0; + int i; + int err; + struct benchmark *b = NULL; + + if (arguments->verbose > 0) + fprintf(stdout, "Testing ICMP RTT\n"); + + err = benchmarks_ping_latency(hostname, &ping_rtt); + if (err == 0) { + fprintf(stdout, "ping RTT : %f ms\n", ping_rtt); + } + + err = benchmarks_ssh_latency(session, &ssh_rtt); + if (err == 0) { + fprintf(stdout, + "SSH RTT : %f ms. Theoretical max BW (win=128K) : %s\n", + ssh_rtt, network_speed(128000.0 / (ssh_rtt / 1000.0))); + } + + for (i=0; i < BENCHMARK_NUMBER; ++i){ + b = &benchmarks[i]; + if (b->enabled) { + err=b->fct(session, arguments, &bps); + + if (err == 0) { + fprintf(stdout, + "%s : %s : %s\n", + hostname, b->name, network_speed(bps)); + } + } } - } } -char *buffer; - -int main(int argc, char **argv){ - struct argument_s arguments; - ssh_session session; - int i; - - arguments_init(&arguments); - cmdline_parse(argc, argv, &arguments); - if (arguments.nhosts==0){ - fprintf(stderr,"At least one host (-h) must be specified\n"); - return EXIT_FAILURE; - } - if (arguments.ntests==0){ - for(i=0; i < BENCHMARK_NUMBER ; ++i){ - benchmarks[i].enabled=1; +char *buffer = NULL; + +int main(int argc, char **argv) +{ + struct argument_s arguments; + ssh_session session = NULL; + int i; + + arguments_init(&arguments); + cmdline_parse(argc, argv, &arguments); + if (arguments.nhosts == 0) { + fprintf(stderr, "At least one host (-h) must be specified\n"); + return EXIT_FAILURE; } - arguments.ntests=BENCHMARK_NUMBER; - } - buffer=malloc(arguments.chunksize > 1024 ? arguments.chunksize : 1024); - if(buffer == NULL){ - fprintf(stderr,"Allocation of chunk buffer failed\n"); - return EXIT_FAILURE; - } - if (arguments.verbose > 0){ - fprintf(stdout, "Will try hosts "); - for(i=0;i 1024 ? arguments.chunksize : 1024); + if (buffer == NULL) { + fprintf(stderr, "Allocation of chunk buffer failed\n"); + return EXIT_FAILURE; + } + + if (arguments.verbose > 0) { + fprintf(stdout, "Will try hosts "); + for (i=0; i < arguments.nhosts; ++i) { + fprintf(stdout, "\"%s\" ", arguments.hosts[i]); + } + + fprintf(stdout, "with benchmarks "); + for (i = 0; i < BENCHMARK_NUMBER; ++i) { + if (benchmarks[i].enabled) + fprintf(stdout, "\"%s\" ", benchmarks[i].name); + } + + fprintf(stdout,"\n"); } - fprintf(stdout,"\n"); - } - - for(i=0; i 0) - fprintf(stdout,"Connecting to \"%s\"...\n",arguments.hosts[i]); - session=connect_host(arguments.hosts[i], arguments.verbose, arguments.cipher); - if(session != NULL && arguments.verbose > 0) - fprintf(stdout,"Success\n"); - if(session == NULL){ - fprintf(stderr,"Errors occurred, stopping\n"); - return EXIT_FAILURE; + + for (i = 0; i < arguments.nhosts; ++i) { + if (arguments.verbose > 0) + fprintf(stdout, "Connecting to \"%s\"...\n", arguments.hosts[i]); + + session = connect_host(arguments.hosts[i], + arguments.verbose, + arguments.cipher); + if (session != NULL && arguments.verbose > 0) + fprintf(stdout, "Success\n"); + + if (session == NULL) { + fprintf(stderr, "Errors occurred, stopping\n"); + return EXIT_FAILURE; + } + + do_benchmarks(session, &arguments, arguments.hosts[i]); + ssh_disconnect(session); + ssh_free(session); } - do_benchmarks(session, &arguments, arguments.hosts[i]); - ssh_disconnect(session); - ssh_free(session); - } - return EXIT_SUCCESS; + + return EXIT_SUCCESS; } From 4e239484fe21681b9fb24c246433f8a25bdccac3 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Wed, 7 Jun 2023 13:19:54 +0530 Subject: [PATCH 025/795] Use helper variable in connect_host() According to libssh coding conventions, function return values must not be directly passed to if- or while- conditions. This rule was not being followed in connect_host(). A helper variable has been introduced which stores the return code of the functions which is then passed to the if- conditions. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/benchmarks/benchmarks.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 178ac7f9..5b7c498c 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -278,26 +278,43 @@ static void arguments_init(struct argument_s *arguments) static ssh_session connect_host(const char *host, int verbose, char *cipher) { - ssh_session session = ssh_new(); + ssh_session session = NULL; + int rc; + + session = ssh_new(); if (session == NULL) goto error; - if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) + rc = ssh_options_set(session, SSH_OPTIONS_HOST, host); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if (rc < 0) goto error; - ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); if (cipher != NULL) { - if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) || - ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher)) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + if (rc < 0) goto error; - } } - ssh_options_parse_config(session, NULL); - if (ssh_connect(session) == SSH_ERROR) + rc = ssh_options_parse_config(session, NULL); + if (rc < 0) goto error; - if (ssh_userauth_autopubkey(session, NULL) != SSH_AUTH_SUCCESS) + + rc = ssh_connect(session); + if (rc == SSH_ERROR) goto error; + + rc = ssh_userauth_autopubkey(session, NULL); + if (rc != SSH_AUTH_SUCCESS) + goto error; + return session; error: From 08a8bd936ce6529df08320ed709587c704405355 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Wed, 14 Jun 2023 00:20:11 +0530 Subject: [PATCH 026/795] Fix error reporting in connect_host() This commit fixes connect_host() such that if ssh_new() fails, connect_host() fails and provides the reason for failure. Prior to this commit if ssh_new() failed, connect_host() failed but did not provide the reason for failure to connect to the host. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/benchmarks/benchmarks.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 5b7c498c..0dedffe3 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -282,8 +282,11 @@ static ssh_session connect_host(const char *host, int verbose, char *cipher) int rc; session = ssh_new(); - if (session == NULL) - goto error; + if (session == NULL) { + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, "Unable to create a new ssh session"); + return NULL; + } rc = ssh_options_set(session, SSH_OPTIONS_HOST, host); if (rc < 0) From be0c558bcccc28255b2130d454e23ceac5cd2897 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Thu, 1 Jun 2023 21:10:19 +0530 Subject: [PATCH 027/795] Link benchmark code statically with libssh benchmark code present in tests/benchmarks/ directory was linked with libssh dynamically due to which it could use only the functions exposed in the public API of libssh. To be able to use those functions in the benchmark code which are a part of libssh api but not a part of the public api for libssh (examples of such functions are ssh_list api functions), the benchmark code needs to be linked statically to libssh. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/benchmarks/CMakeLists.txt | 2 +- tests/benchmarks/benchmarks.c | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/benchmarks/CMakeLists.txt b/tests/benchmarks/CMakeLists.txt index ca4f0006..4074b8f4 100644 --- a/tests/benchmarks/CMakeLists.txt +++ b/tests/benchmarks/CMakeLists.txt @@ -14,4 +14,4 @@ include_directories(${libssh_BINARY_DIR}) add_executable(benchmarks ${benchmarks_SRCS}) -target_link_libraries(benchmarks ssh::ssh) +target_link_libraries(benchmarks ssh::static) diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 0dedffe3..0091fc22 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -19,6 +19,8 @@ * MA 02111-1307, USA. */ +#define LIBSSH_STATIC + #include "config.h" #include "benchmarks.h" #include @@ -390,7 +392,7 @@ int main(int argc, char **argv) { struct argument_s arguments; ssh_session session = NULL; - int i; + int i, r; arguments_init(&arguments); cmdline_parse(argc, argv, &arguments); @@ -427,6 +429,12 @@ int main(int argc, char **argv) fprintf(stdout,"\n"); } + r = ssh_init(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to initialize libssh\n"); + return EXIT_FAILURE; + } + for (i = 0; i < arguments.nhosts; ++i) { if (arguments.verbose > 0) fprintf(stdout, "Connecting to \"%s\"...\n", arguments.hosts[i]); @@ -447,6 +455,12 @@ int main(int argc, char **argv) ssh_free(session); } + r = ssh_finalize(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to finalize libssh\n"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } From 710ce11cf0978ebeac900257fd2a64e068d5805e Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 29 May 2023 11:45:49 +0530 Subject: [PATCH 028/795] Add benchmark code for download using the async sftp aio api benchmarks_async_sftp_aio_down() has been added in tests/benchmarks/bench_sftp.c to obtain the performance metrics of a download using the low level async sftp aio api. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/benchmarks/bench_sftp.c | 148 ++++++++++++++++++++++++++++++++++ tests/benchmarks/benchmarks.c | 18 ++++- tests/benchmarks/benchmarks.h | 3 + 3 files changed, 167 insertions(+), 2 deletions(-) diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c index d5766879..5548c1c0 100644 --- a/tests/benchmarks/bench_sftp.c +++ b/tests/benchmarks/bench_sftp.c @@ -23,6 +23,7 @@ #include "benchmarks.h" #include #include +#include #include #include #include @@ -240,3 +241,150 @@ int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, free(ids); return -1; } + +int benchmarks_async_sftp_aio_down(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + + int concurrent_downloads = args->concurrent_requests; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t bytes_requested = 0, total_bytes_read = 0; + size_t to_read; + ssize_t bytes_read; + int warned = 0, i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + return -1; + } + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, O_RDONLY, 0); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting download of %zu bytes now, " + "using %d concurrent downloads.\n", + total_bytes, concurrent_downloads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_downloads && bytes_requested < total_bytes; + ++i) { + to_read = total_bytes - bytes_requested; + if (to_read > args->chunksize) { + to_read = args->chunksize; + } + + rc = sftp_aio_begin_read(file, to_read, &aio); + if (rc == SSH_ERROR) { + goto error; + } + + bytes_requested += to_read; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_read = sftp_aio_wait_read(&aio, buffer, args->chunksize); + if (bytes_read == -1) { + goto error; + } + + total_bytes_read += (size_t)bytes_read; + if (bytes_read == 0) { + fprintf(stdout , + "File smaller than expected : %zu bytes (expected %zu).\n", + total_bytes_read, total_bytes); + break; + } + + if (total_bytes_read != total_bytes && + (size_t)bytes_read != args->chunksize && + warned != 1) { + fprintf(stderr, + "async_sftp_aio_download : Receiving short reads " + "(%zu, expected %u) before encountering eof, " + "the received file will be corrupted and shorted. " + "Adapt chunksize to %zu.\n", + bytes_read, args->chunksize, bytes_read); + warned = 1; + } + + if (bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_read = total_bytes - bytes_requested; + if (to_read > args->chunksize) { + to_read = args->chunksize; + } + + rc = sftp_aio_begin_read(file, to_read, &aio); + if (rc == SSH_ERROR) { + goto error; + } + + bytes_requested += to_read; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + sftp_aio_free(aio); + goto error; + } + } + + ssh_list_free(aio_queue); + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes_read) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Download took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes_read, *bps); + } + + sftp_free(sftp); + return 0; + +error: + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_free(sftp); + return -1; +} diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 0091fc22..56dc7dce 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -62,8 +62,13 @@ struct benchmark benchmarks[] = { .enabled = 0 }, { - .name = "benchmark_async_sftp_download", - .fct = benchmarks_async_sftp_down, + .name="benchmark_async_sftp_download", + .fct=benchmarks_async_sftp_down, + .enabled=0 + }, + { + .name = "benchmark_async_sftp_aio_download", + .fct = benchmarks_async_sftp_aio_down, .enabled = 0 } #endif /* WITH_SFTP */ @@ -147,6 +152,14 @@ static struct argp_option options[] = { .doc = "Download data using asynchronous SFTP (fast)", .group = 0 }, + { + .name = "async-sftp-aio-download", + .key = '8', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, { .name = "host", .key = 'h', @@ -210,6 +223,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) case '5': case '6': case '7': + case '8': benchmarks[key - '1'].enabled = 1; arguments->ntests++; break; diff --git a/tests/benchmarks/benchmarks.h b/tests/benchmarks/benchmarks.h index 26da09bb..073e2834 100644 --- a/tests/benchmarks/benchmarks.h +++ b/tests/benchmarks/benchmarks.h @@ -37,6 +37,7 @@ enum libssh_benchmarks { BENCHMARK_SYNC_SFTP_UPLOAD, BENCHMARK_SYNC_SFTP_DOWNLOAD, BENCHMARK_ASYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_DOWNLOAD, BENCHMARK_NUMBER }; @@ -96,4 +97,6 @@ int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, float *bps); int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, float *bps); +int benchmarks_async_sftp_aio_down(ssh_session session, struct argument_s *args, + float *bps); #endif /* BENCHMARKS_H_ */ From f4fe781f65b08e06b6a7bda48a7b6310fc686993 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 29 May 2023 14:42:10 +0530 Subject: [PATCH 029/795] Add benchmark code for upload using the async sftp aio api benchmarks_async_sftp_aio_up() has been added in tests/benchmarks/bench_sftp.c to obtain the performance metrics of a upload using the low level async sftp aio api. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- tests/benchmarks/bench_sftp.c | 128 ++++++++++++++++++++++++++++++++++ tests/benchmarks/benchmarks.c | 14 ++++ tests/benchmarks/benchmarks.h | 3 + 3 files changed, 145 insertions(+) diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c index 5548c1c0..64ad5efc 100644 --- a/tests/benchmarks/bench_sftp.c +++ b/tests/benchmarks/bench_sftp.c @@ -388,3 +388,131 @@ int benchmarks_async_sftp_aio_down(ssh_session session, sftp_free(sftp); return -1; } + +int benchmarks_async_sftp_aio_up(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + + int concurrent_uploads = args->concurrent_requests; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t bytes_requested = 0; + size_t to_write; + ssize_t bytes_written; + int i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + return -1; + } + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, + O_RDWR | O_CREAT | O_TRUNC, 0777); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting upload of %zu bytes now, " + "using %d concurrent uploads.\n", + total_bytes, concurrent_uploads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_uploads && bytes_requested < total_bytes; + ++i) { + to_write = total_bytes - bytes_requested; + if (to_write > args->chunksize) { + to_write = args->chunksize; + } + + rc = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (rc == SSH_ERROR) { + goto error; + } + + bytes_requested += to_write; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + goto error; + } + + if (bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_write = total_bytes - bytes_requested; + if (to_write > args->chunksize) { + to_write = args->chunksize; + } + + rc = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (rc == SSH_ERROR) { + goto error; + } + + bytes_requested += to_write; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + sftp_aio_free(aio); + goto error; + } + } + + ssh_list_free(aio_queue); + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Upload took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes, *bps); + } + + sftp_free(sftp); + return 0; + +error: + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_free(sftp); + return -1; +} diff --git a/tests/benchmarks/benchmarks.c b/tests/benchmarks/benchmarks.c index 56dc7dce..4229d5a5 100644 --- a/tests/benchmarks/benchmarks.c +++ b/tests/benchmarks/benchmarks.c @@ -70,6 +70,11 @@ struct benchmark benchmarks[] = { .name = "benchmark_async_sftp_aio_download", .fct = benchmarks_async_sftp_aio_down, .enabled = 0 + }, + { + .name = "benchmark_async_sftp_aio_upload", + .fct = benchmarks_async_sftp_aio_up, + .enabled = 0 } #endif /* WITH_SFTP */ }; @@ -160,6 +165,14 @@ static struct argp_option options[] = { .doc = "Download data using asynchronous SFTP AIO api (fast)", .group = 0 }, + { + .name = "async-sftp-aio-upload", + .key = '9', + .arg = NULL, + .flags = 0, + .doc = "Upload data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, { .name = "host", .key = 'h', @@ -224,6 +237,7 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) case '6': case '7': case '8': + case '9': benchmarks[key - '1'].enabled = 1; arguments->ntests++; break; diff --git a/tests/benchmarks/benchmarks.h b/tests/benchmarks/benchmarks.h index 073e2834..5f54a950 100644 --- a/tests/benchmarks/benchmarks.h +++ b/tests/benchmarks/benchmarks.h @@ -38,6 +38,7 @@ enum libssh_benchmarks { BENCHMARK_SYNC_SFTP_DOWNLOAD, BENCHMARK_ASYNC_SFTP_DOWNLOAD, BENCHMARK_ASYNC_SFTP_AIO_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_UPLOAD, BENCHMARK_NUMBER }; @@ -99,4 +100,6 @@ int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, float *bps); int benchmarks_async_sftp_aio_down(ssh_session session, struct argument_s *args, float *bps); +int benchmarks_async_sftp_aio_up(ssh_session session, struct argument_s *args, + float *bps); #endif /* BENCHMARKS_H_ */ From 12f28a519be09eaaf15b1de505f286c3d3e00d78 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 4 Sep 2023 16:03:24 +0530 Subject: [PATCH 030/795] introduction.dox : Add pkcs11 tutorial to the table of contents Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- doc/introduction.dox | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/introduction.dox b/doc/introduction.dox index f2f3d3dd..9c6cd06e 100644 --- a/doc/introduction.dox +++ b/doc/introduction.dox @@ -44,6 +44,8 @@ Table of contents: @subpage libssh_tutor_threads +@subpage libssh_tutor_pkcs11 + @subpage libssh_tutor_todo */ From d1960cb9a285f7743af833c8faf35dbea9e50dfd Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 4 Sep 2023 15:58:02 +0530 Subject: [PATCH 031/795] Add tutorial for the sftp aio API Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- doc/introduction.dox | 2 + doc/sftp_aio.dox | 575 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 577 insertions(+) create mode 100644 doc/sftp_aio.dox diff --git a/doc/introduction.dox b/doc/introduction.dox index 9c6cd06e..8d2aa1d5 100644 --- a/doc/introduction.dox +++ b/doc/introduction.dox @@ -46,6 +46,8 @@ Table of contents: @subpage libssh_tutor_pkcs11 +@subpage libssh_tutor_sftp_aio + @subpage libssh_tutor_todo */ diff --git a/doc/sftp_aio.dox b/doc/sftp_aio.dox new file mode 100644 index 00000000..51960c86 --- /dev/null +++ b/doc/sftp_aio.dox @@ -0,0 +1,575 @@ +/** + +@page libssh_tutor_sftp_aio Chapter 10: The SFTP asynchronous I/O + +@section sftp_aio_api The SFTP asynchronous I/O + +NOTE : Please read @ref libssh_tutor_sftp before reading this page. The +synchronous sftp_read() and sftp_write() have been described there. + +SFTP AIO stands for "SFTP Asynchronous Input/Output". This API contains +functions which perform async read/write operations on remote files. + +File transfers performed using the asynchronous sftp aio API can be +significantly faster than the file transfers performed using the synchronous +sftp read/write API (see sftp_read() and sftp_write()). + +The sftp aio API functions are divided into two categories : + - sftp_aio_begin_*() [see sftp_aio_begin_read(), sftp_aio_begin_write()]: + These functions send a request for an i/o operation to the server and + provide the caller an sftp aio handle corresponding to the sent request. + + - sftp_aio_wait_*() [see sftp_aio_wait_read(), sftp_aio_wait_write()]: + These functions wait for the server response corresponding to a previously + issued request. Which request ? the request corresponding to the sftp aio + handle supplied by the caller to these functions. + +Conceptually, you can think of the sftp aio handle as a request identifier. + +Technically, the sftp_aio_begin_*() functions dynamically allocate memory to +store information about the i/o request they send and provide the caller a +handle to this memory, we call this handle an sftp aio handle. + +sftp_aio_wait_*() functions use the information stored in that memory (handled +by the caller supplied sftp aio handle) to identify a request, and then they +wait for that request's response. These functions also release the memory +handled by the caller supplied sftp aio handle (except when they return +SSH_AGAIN). + +sftp_aio_free() can also be used to release the memory handled by an sftp aio +handle but unlike the sftp_aio_wait_*() functions, it doesn't wait for a +response. This should be used to release the memory corresponding to an sftp +aio handle when some failure occurs. An example has been provided at the +end of this page to show the usage of sftp_aio_free(). + +To begin with, this tutorial will provide basic examples that describe the +usage of sftp aio API to perform a single read/write operation. + +The later sections describe the usage of the sftp aio API to obtain faster file +transfers as compared to the transfers performed using the synchronous sftp +read/write API. + +On encountering an error, the sftp aio API functions set the sftp and ssh +errors just like any other libssh sftp API function. These errors can be +obtained using sftp_get_error(), ssh_get_error() and ssh_get_error_code(). +The code examples provided on this page ignore error handling for the sake of +brevity. + +@subsection sftp_aio_read Using the sftp aio API for reading (a basic example) + +For performing an async read operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_read() to send a read request to the +server. The caller is provided an sftp aio handle corresponding to the sent +read request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_read(), this function waits for the server response which +indicates the success/failure of the read request. On success, the response +indicates EOF or contains the data read from the sftp file. + +The following code example shows how a read operation can be performed +on an sftp file using the sftp aio API. + +@code +ssize_t read_chunk(sftp_file file, void *buf, size_t to_read) +{ + ssize_t bytes_read; + int rc; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a read request to the sftp server + rc = sftp_aio_begin_read(file, to_read, &aio); + if (rc == SSH_ERROR) { + // handle error + } + + // Wait for the response of the read request corresponding to the + // sftp aio handle stored in the aio variable. + bytes_read = sftp_aio_wait_read(&aio, buf, to_read); + if (bytes_read == SSH_ERROR) { + // handle error + } + + return bytes_read; +} +@endcode + +@subsection sftp_aio_write Using the sftp aio API for writing (a basic example) + +For performing an async write operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_write() to send a write request to +the server. The caller is provided an sftp aio handle corresponding to the +sent write request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_write(), this function waits for the server response which +indicates the success/failure of the write request. + +The following code example shows how a write operation can be performed on an +sftp file using the sftp aio API. + +@code +ssize_t write_chunk(sftp_file file, void *buf, size_t to_write) +{ + ssize_t bytes_written; + int rc; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a write request to the sftp server + rc = sftp_aio_begin_write(file, buf, to_write, &aio); + if (rc == SSH_ERROR) { + // handle error + } + + // Wait for the response of the write request corresponding to + // the sftp aio handle stored in the aio variable. + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + return bytes_written; +} +@endcode + +@subsection sftp_aio_actual_use Using the sftp aio API to speed up a transfer + +The above examples were provided to introduce the sftp aio API. +This is not how the sftp aio API is intended to be used, because the +above usage offers no advantage over the synchronous sftp read/write API +which does the same thing i.e issue a request and then immediately wait for +its response. + +The facility that the sftp aio API provides is that the user can do +anything between issuing a request and getting the corresponding response. +Any number of operations can be performed after calling sftp_aio_begin_*() +[which issues a request] and before calling sftp_aio_wait_*() [which waits +for a response] + +The code can leverage this feature by calling sftp_aio_begin_*() multiple times +to issue multiple requests before calling sftp_aio_wait_*() to wait for the +response of an earlier issued request. This approach will keep a certain number +of requests outstanding at the client side. + +After issuing those requests, while the client code does something else (for +example waiting for an outstanding request's response, processing an obtained +response, issuing another request or any other operation the client wants +to perform), at the same time : + + - Some of those outstanding requests may be travelling over the + network towards the server. + + - Some of the outstanding requests may have reached the server and may + be queued for processing at the server side. + + - Some of the outstanding requests may have been processed and the + corresponding responses may be travelling over the network towards the + client. + + - Some of the responses corresponding to the outstanding requests may + have already reached the client side. + +Clearly in this case, operations that the client performs and operations +involved in transfer/processing of a outstanding request can occur in +parallel. Also, operations involved in transfer/processing of two or more +outstanding requests may also occur in parallel (for example when one request +travels to the server, another request's response may be incoming towards the +client). Such kind of parallelism makes the overall transfer faster as compared +to a transfer performed using the synchronous sftp read/write API. + +When the synchronous sftp read/write API is used to perform a transfer, +a strict sequence is followed: + + - The client issues a single read/write request. + - Then waits for its response. + - On obtaining the response, the client processes it. + - After the processing ends, the client issues the next read/write request. + +A file transfer performed in this manner would be slower than the case where +multiple read/write requests are kept outstanding at the client side. Because +here at any given time, operations related to transfer/processing of only one +request/response pair occurs. This is in contrast to the multiple outstanding +requests scenario where operations related to transfer/processing of multiple +request/response pairs may occur at the same time. + +Although it's true that keeping multiple requests outstanding can speed up a +transfer, those outstanding requests come at a cost of increased memory +consumption both at the client side and the server side. Hence care must be +taken to use a reasonable limit for the number of requests kept outstanding. + +The further sections provide code examples to show how uploads/downloads +can be performed using the sftp aio API and the concept of outstanding requests +discussed in this section. In those code examples, error handling has been +ignored and at some places pseudo code has been used for the sake of brevity. + +The complete code for performing uploads/downloads using the sftp aio API, +can be found at https://gitlab.com/libssh/libssh-mirror/-/tree/master. + + - libssh benchmarks for uploads performed using the sftp aio API [See + tests/benchmarks/bench_sftp.c] + - libssh benchmarks for downloads performed using the sftp aio API. [See + tests/benchmarks/bench_sftp.c] + - libssh sftp ft API code for performing a local to remote transfer (upload). + [See src/sftp_ft.c] + - libssh sftp ft API code for performing a remote to local transfer + (download). [See src/sftp_ft.c] + +@subsection sftp_aio_download_example Performing a download using the sftp aio API + +Terminologies used in the following code snippets : + + - file : The sftp file handle of the remote file to download data + from. (See sftp_open()) + + - file_size : the size of the sftp file to download. This size can be obtained + by statting the remote file to download (e.g by using sftp_stat()) + + - We will need to maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the read requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested don't exceed the size of the file to download. + +@code +sftp_aio aio = NULL; + +// Using a chunk size of 16 KB +size_t chunk_size = 16 * 1024; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Number of bytes for which requests have been sent +size_t bytes_requested = 0; + +// Number of bytes which have been downloaded +size_t bytes_downloaded = 0; + +// Buffer to use for the download +char *buffer = NULL; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the remote file (to download) using sftp_open(). +... // Code to stat the remote file's file size. +... // Code to open the local file in which downloaded data is to be stored. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && bytes_requested < file_size; + ++i) { + to_read = file_size - bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + // Issue a read request + rc = sftp_aio_begin_read(file, to_read, &aio); + if (rc == SSH_ERROR) { + // handle error + } + + bytes_requested += to_read; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another read request if there are +still some bytes in the sftp file (to download) for which we haven't sent the +read request. (This happens when bytes_requested < file_size) + +This issuing of another read request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_read = sftp_aio_wait_read(&aio, buffer, chunk_size); + if (bytes_read == SSH_ERROR) { + //handle error + } + + bytes_downloaded += bytes_read; + if (bytes_read != chunk_size && bytes_downloaded != file_size) { + // A short read encountered on the remote file before reaching EOF, + // handle it. + } + + // Pseudo code + WRITE bytes_read bytes from the buffer into the local file + in which downloaded data is to be stored ; + + if (bytes_requested == file_size) { + // no need to issue more read requests + continue; + } + + // else issue a read request + to_read = file_size - bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + rc = sftp_aio_begin_read(file, to_read, &aio); + if (rc == SSH_ERROR) { + // handle error + } + + bytes_requested += to_read; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the download +would've been complete (assuming no error occurs). + +@subsection sftp_aio_upload_example Performing an upload using the sftp aio API + +Terminologies used in the following code snippets : + + - file : The sftp file handle of the remote file in which uploaded data + is to be stored. (See sftp_open()) + + - file_size : The size of the local file to upload. This size can be + obtained by statting the local file to upload (e.g by using stat()) + + - We will need maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the write requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested to write don't exceed the size of the file to upload. + +@code +sftp_aio aio = NULL; + +// Using a chunk size of 16 KB +size_t chunk_size = 16 * 1024; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Number of bytes for which write requests have been sent +size_t bytes_requested = 0; + +// Buffer to use for the upload +char *buffer = NULL; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the local file (to upload) [e.g using open(), fopen()]. +... // Code to stat the local file's file size [e.g using stat()]. +... // Code to open the remote file in which uploaded data will be stored [see + // sftp_open()]. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && bytes_requested < file_size; + ++i) { + to_write = file_size - bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into the buffer; + + rc = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (rc == SSH_ERROR) { + // handle error + } + + bytes_requested += to_write; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another write request if there are +still some bytes in the local file (to upload) for which we haven't sent +the write request. (This happens when bytes_requested < file_size) + +This issuing of another write request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + // sftp_aio_wait_write() won't report a short write, so no need + // to check for a short write here. + + if (bytes_requested == file_size) { + // no need to issue more write requests + continue; + } + + // else issue a write request + to_write = file_size - bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into a buffer; + + rc = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (rc == SSH_ERROR) { + // handle error + } + + bytes_requested += to_write; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the upload +would've been complete (assuming no error occurs). + +@subsection sftp_aio_free Example showing the usage of sftp_aio_free() + +The purpose of sftp_aio_free() was discussed at the beginning of this page, +the following code example shows how it can be used during cleanup. + +@code +void print_sftp_error(sftp_session sftp) +{ + if (sftp == NULL) { + return; + } + + fprintf(stderr, "sftp error : %d\n", sftp_get_error(sftp)); + fprintf(stderr, "ssh error : %s\n", ssh_get_error(sftp->session)); +} + +// Returns 0 on success, -1 on error +int write_strings(sftp_file file) +{ + const char * strings[] = { + "This is the first string", + "This is the second string", + "This is the third string", + "This is the fourth string" + }; + + size_t string_count = sizeof(strings) / sizeof(strings[0]); + size_t i; + + sftp_session sftp = NULL; + sftp_aio aio = NULL; + + int rc; + + if (file == NULL) { + return -1; + } + + ... // Code to initialize the queue which will be used to store sftp aio + // handles + + sftp = file->sftp; + for (i = 0; i < string_count; ++i) { + rc = sftp_aio_begin_write(file, + strings[i], + strlen(strings[i]), + &aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + + // Pseudo code + ENQUEUE aio in the queue of sftp aio handles + } + + for (i = 0; i < string_count; ++i) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + rc = sftp_aio_wait_write(&aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + } + + + ... // Code to destroy the queue in which sftp aio handles were + // stored + + return 0; + +err: + + while (queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + sftp_aio_free(aio); + } + + ... // Code to destroy the queue in which sftp aio handles were + // stored. + + return -1; +} + +@endcode + +*/ From d0c76b5baa1d514f3eca9707e2b5228b7bae6bf8 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sat, 9 Sep 2023 16:29:15 +0530 Subject: [PATCH 032/795] sftp.h : Deprecate the old sftp async API for reading Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index 209973a8..b4e0e18b 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -532,7 +532,8 @@ LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); * @see sftp_async_read() * @see sftp_open() */ -LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); +SSH_DEPRECATED LIBSSH_API int sftp_async_read_begin(sftp_file file, + uint32_t len); /** * @brief Wait for an asynchronous read to complete and save the data. @@ -557,7 +558,10 @@ LIBSSH_API int sftp_async_read_begin(sftp_file file, uint32_t len); * * @see sftp_async_read_begin() */ -LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_t id); +SSH_DEPRECATED LIBSSH_API int sftp_async_read(sftp_file file, + void *data, + uint32_t len, + uint32_t id); /** * @brief Write to a file using an opened sftp file handle. From c0a76cf9b1d838c968280a998644622eb373f47e Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Fri, 15 Sep 2023 19:34:56 +0530 Subject: [PATCH 033/795] sftp.dox: Change a subsection heading to a more suitable heading. "Copying a file to the remote computer" is not an appropriate heading for a subsection that describes how to open a remote file and write "Hello World" to it. That heading is not appropriate as the subsection does not show how to copy a file from local to remote computer. Hence, this commit changes that heading to a more suitable heading. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- doc/sftp.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/sftp.dox b/doc/sftp.dox index 1f99cfdf..8839bd30 100644 --- a/doc/sftp.dox +++ b/doc/sftp.dox @@ -139,7 +139,7 @@ Unlike its equivalent in the SCP subsystem, this function does NOT change the current directory to the newly created subdirectory. -@subsection sftp_write Copying a file to the remote computer +@subsection sftp_write Writing to a file on the remote computer You handle the contents of a remote file just like you would do with a local file: you open the file in a given mode, move the file pointer in it, From 677d1e1d10f31bbc6f5be976a54eab0a1accc4bd Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sat, 16 Sep 2023 19:43:22 +0530 Subject: [PATCH 034/795] sftp.dox: Remove references of old sftp async API This commit removes the references of the old async sftp API from the libssh sftp tutorial because the old async API is to be deprecated and replaced by the sftp aio API. Signed-off-by: Eshan Kelkar Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- doc/sftp.dox | 112 ++++++++++++++------------------------------------- 1 file changed, 31 insertions(+), 81 deletions(-) diff --git a/doc/sftp.dox b/doc/sftp.dox index 8839bd30..4c176a4b 100644 --- a/doc/sftp.dox +++ b/doc/sftp.dox @@ -203,16 +203,14 @@ int sftp_helloworld(ssh_session session, sftp_session sftp) @subsection sftp_read Reading a file from the remote computer -The nice thing with reading a file over the network through SFTP is that it -can be done both in a synchronous way or an asynchronous way. If you read the file -asynchronously, your program can do something else while it waits for the -results to come. - -Synchronous read is done with sftp_read(). +A synchronous read from a remote file is done using sftp_read(). This +section describes how to download a remote file using sftp_read(). The +next section will discuss more about synchronous/asynchronous read/write +operations using libssh sftp API. Files are normally transferred in chunks. A good chunk size is 16 KB. The following example transfers the remote file "/etc/profile" in 16 KB chunks. For each chunk we -request, sftp_read blocks till the data has been received: +request, sftp_read() blocks till the data has been received: @code // Good chunk size @@ -273,87 +271,39 @@ int sftp_read_sync(ssh_session session, sftp_session sftp) } @endcode -Asynchronous read is done in two steps, first sftp_async_read_begin(), which -returns a "request handle", and then sftp_async_read(), which uses that request handle. -If the file has been opened in nonblocking mode, then sftp_async_read() -might return SSH_AGAIN, which means that the request hasn't completed yet -and that the function should be called again later on. Otherwise, -sftp_async_read() waits for the data to come. To open a file in nonblocking mode, -call sftp_file_set_nonblocking() right after you opened it. Default is blocking mode. +@subsection sftp_aio Performing an asynchronous read/write on a file on the remote computer -The example below reads a very big file in asynchronous, nonblocking, mode. Each -time the data is not ready yet, a counter is incremented. +sftp_read() performs a "synchronous" read operation on a remote file. +This means that sftp_read() will first request the server to read some +data from the remote file and then would wait until the server response +containing data to read (or an error) arrives at the client side. -@code -// Good chunk size -#define MAX_XFER_BUF_SIZE 16384 +sftp_write() performs a "synchronous" write operation on a remote file. +This means that sftp_write() will first request the server to write some +data to the remote file and then would wait until the server response +containing information about the status of the write operation arrives at the +client side. -int sftp_read_async(ssh_session session, sftp_session sftp) -{ - int access_type; - sftp_file file; - char buffer[MAX_XFER_BUF_SIZE]; - int async_request; - int nbytes; - long counter; - int rc; +If your client program wants to do something other than waiting for the +response after requesting a read/write, the synchronous sftp_read() and +sftp_write() can't be used. In such a case the "asynchronous" sftp aio API +should be used. - access_type = O_RDONLY; - file = sftp_open(sftp, "some_very_big_file", - access_type, 0); - if (file == NULL) { - fprintf(stderr, "Can't open file for reading: %s\n", - ssh_get_error(session)); - return SSH_ERROR; - } - sftp_file_set_nonblocking(file); - - async_request = sftp_async_read_begin(file, sizeof(buffer)); - counter = 0L; - usleep(10000); - if (async_request >= 0) { - nbytes = sftp_async_read(file, buffer, sizeof(buffer), - async_request); - } else { - nbytes = -1; - } +Please go through @ref libssh_tutor_sftp_aio for a detailed description +of the sftp aio API. - while (nbytes > 0 || nbytes == SSH_AGAIN) { - if (nbytes > 0) { - write(1, buffer, nbytes); - async_request = sftp_async_read_begin(file, sizeof(buffer)); - } else { - counter++; - } - usleep(10000); +The sftp aio API provides two categories of functions : + - sftp_aio_begin_*() : For requesting a read/write from the server. + - sftp_aio_wait_*() : For waiting for the response of a previously + issued read/write request from the server. - if (async_request >= 0) { - nbytes = sftp_async_read(file, buffer, sizeof(buffer), - async_request); - } else { - nbytes = -1; - } - } - - if (nbytes < 0) { - fprintf(stderr, "Error while reading file: %s\n", - ssh_get_error(session)); - sftp_close(file); - return SSH_ERROR; - } - - printf("The counter has reached value: %ld\n", counter); - - rc = sftp_close(file); - if (rc != SSH_OK) { - fprintf(stderr, "Can't close the read file: %s\n", - ssh_get_error(session)); - return rc; - } +Hence, the client program can call sftp_aio_begin_*() to request a read/write +and then can perform any number of operations (other than waiting) before +calling sftp_aio_wait_*() for waiting for the response of the previously +issued request. - return SSH_OK; -} -@endcode +We call read/write operations performed in the manner described above as +"asynchronous" read/write operations on a remote file. @subsection sftp_ls Listing the contents of a directory From 46ab527bbeb8a54566aa474b4247b24b88e4d50f Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Mon, 4 Dec 2023 22:13:56 +0100 Subject: [PATCH 035/795] Fix typo Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen --- src/pki.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pki.c b/src/pki.c index bfdcb311..6b0ef76e 100644 --- a/src/pki.c +++ b/src/pki.c @@ -586,7 +586,7 @@ enum ssh_keytypes_e ssh_key_type_from_name(const char *name) } /** - * @brief Get the pubic key type corresponding to a certificate type. + * @brief Get the public key type corresponding to a certificate type. * * @param[in] type The certificate or public key type. * From 54ac7c95e8809054cd25e21836ad5993aac934a6 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 23 Nov 2023 15:03:14 +0100 Subject: [PATCH 036/795] examples: Avoid accessing list before acquiring lock Thanks coverity CID 1526592 Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- examples/ssh_X11_client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/ssh_X11_client.c b/examples/ssh_X11_client.c index b3fa0e2d..e3386813 100644 --- a/examples/ssh_X11_client.c +++ b/examples/ssh_X11_client.c @@ -343,10 +343,11 @@ static void delete_item(ssh_channel channel) static node_t *search_item(ssh_channel channel) { - node_t *current = node; + node_t *current = NULL; pthread_mutex_lock(&mutex); + current = node; while (current != NULL) { if (current->channel == channel) { pthread_mutex_unlock(&mutex); From 19439fcfd889387a326116a92f4e180d084061ac Mon Sep 17 00:00:00 2001 From: Sven Fischer Date: Fri, 17 Nov 2023 18:16:46 +0100 Subject: [PATCH 037/795] Add binary dir to target include directories Build binary dir contains the libssh_version.h file. By adding the binary dir to the target include path, the include file can be found by projects which use libssh as a sub-project by add_subdirectory(). Signed-off-by: Sven Fischer Reviewed-by: Jakub Jelen --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c06cf697..6ad4fe72 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -346,6 +346,7 @@ endif () target_include_directories(ssh PUBLIC $ + $ $ PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) @@ -409,6 +410,7 @@ if (BUILD_STATIC_LIB) target_include_directories(ssh-static PUBLIC $ + $ $ PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) target_link_libraries(ssh-static From 12b1fcdfcf217a88c839cf18d9c75368c5b727f5 Mon Sep 17 00:00:00 2001 From: Sven Fischer Date: Tue, 28 Nov 2023 00:51:23 +0100 Subject: [PATCH 038/795] Remove binary include dir from PRIVATE_INCLUDE_DIRS Signed-off-by: Sven Fischer Reviewed-by: Jakub Jelen --- src/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ad4fe72..b6b9cd7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ set(LIBSSH_PUBLIC_INCLUDE_DIRS ${libssh_SOURCE_DIR}/include) set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} ) From ae4040a7eb9345b8f534e3b304e97ac8e21cfdc1 Mon Sep 17 00:00:00 2001 From: Sven Fischer Date: Tue, 5 Dec 2023 11:58:01 +0100 Subject: [PATCH 039/795] Make compile-commands generation conditional To not "pollute" projects with the compile-commands.json link if they include libssh as a subproject (e.g. with add_subdirectory()), check if libssh is the root project and only create the link in this case. Signed-off-by: Sven Fischer Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 44e08edb..5885770d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,10 +225,13 @@ endif (CMAKE_BUILD_TYPE STREQUAL "Coverage") add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source DEPENDS ${_SYMBOL_TARGET} VERBATIM) -# Link compile database for clangd -execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink - "${CMAKE_BINARY_DIR}/compile_commands.json" - "${CMAKE_SOURCE_DIR}/compile_commands.json") +get_directory_property(hasParent PARENT_DIRECTORY) +if(NOT(hasParent)) + # Link compile database for clangd if we are the master project + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + "${CMAKE_BINARY_DIR}/compile_commands.json" + "${CMAKE_SOURCE_DIR}/compile_commands.json") +endif() message(STATUS "********************************************") message(STATUS "********** ${PROJECT_NAME} build options : **********") From 2c026e43149f98798535a9fb0e845f2f336e06a9 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 29 Sep 2023 18:47:18 +0200 Subject: [PATCH 040/795] bignum: Avoid trailing newline in log message Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/bignum.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bignum.c b/src/bignum.c index bee55d67..72429460 100644 --- a/src/bignum.c +++ b/src/bignum.c @@ -70,7 +70,7 @@ bignum ssh_make_string_bn(ssh_string string) #ifdef DEBUG_CRYPTO SSH_LOG(SSH_LOG_TRACE, - "Importing a %zu bits, %zu bytes object ...\n", + "Importing a %zu bits, %zu bytes object ...", len * 8, len); #endif /* DEBUG_CRYPTO */ From dd11d469dcb572daef9bd25c4cda2aa39a3bb3e9 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Sep 2023 23:26:18 +0200 Subject: [PATCH 041/795] tests: replace assert_true Mechanical edit in vim: %s/assert_true(rc == 0)/assert_return_code(rc, errno)/g %s/assert_true(rc == SSH_OK)/assert_return_code(rc, errno)/g %s/assert_true(rc == \(-*\d*\))/assert_int_equal(rc, \1)/g %s/assert_true(rc == \(.*\))/assert_int_equal(rc, \1)/g %s/assert_true(type == \(.*\))/assert_int_equal(type, \1)/g Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/unittests/torture_pki_ecdsa.c | 116 ++++++++++++------------ tests/unittests/torture_pki_rsa.c | 136 ++++++++++++++-------------- 2 files changed, 126 insertions(+), 126 deletions(-) diff --git a/tests/unittests/torture_pki_ecdsa.c b/tests/unittests/torture_pki_ecdsa.c index d8a1432b..6c6e3012 100644 --- a/tests/unittests/torture_pki_ecdsa.c +++ b/tests/unittests/torture_pki_ecdsa.c @@ -231,11 +231,11 @@ static void torture_pki_ecdsa_import_privkey_base64(void **state) assert_non_null(key_str); rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); SSH_KEY_FREE(key); @@ -261,11 +261,11 @@ static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) assert_int_equal(rc, file_str_len - 1); rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -292,11 +292,11 @@ static void torture_pki_ecdsa_import_privkey_base64_whitespace(void **state) assert_int_equal(rc, file_str_len - 1); rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -318,11 +318,11 @@ static void torture_pki_ecdsa_publickey_from_privatekey(void **state) assert_non_null(key_str); rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); free(key_str); @@ -338,14 +338,14 @@ static void torture_pki_ecdsa_import_cert_file(void **state) struct pki_st *test_state = *((struct pki_st **)state); rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(cert); type = ssh_key_type(cert); - assert_true(type == test_state->type+3); + assert_int_equal(type, test_state->type+3); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(cert); } @@ -369,7 +369,7 @@ static void torture_pki_ecdsa_publickey_base64(void **state) } type = ssh_key_type_from_name(q); - assert_true(type == test_state->type); + assert_int_equal(type, test_state->type); q = ++p; while (p != NULL && *p != '\0' && *p != ' ') p++; @@ -378,11 +378,11 @@ static void torture_pki_ecdsa_publickey_base64(void **state) } rc = ssh_pki_import_pubkey_base64(q, type, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); rc = ssh_pki_export_pubkey_base64(key, &b64_key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(b64_key); assert_string_equal(q, b64_key); @@ -406,7 +406,7 @@ static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", pubkey_original, sizeof(pubkey_original)); - assert_true(rc == 0); + assert_int_equal(rc, 0); /* remove the public key, generate it from the private key and write it. */ unlink(LIBSSH_ECDSA_TESTKEY ".pub"); @@ -416,20 +416,20 @@ static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ECDSA_TESTKEY ".pub"); - assert_true(rc == 0); + assert_int_equal(rc, 0); rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", pubkey_generated, sizeof(pubkey_generated)); - assert_true(rc == 0); + assert_int_equal(rc, 0); len = torture_pubkey_len(pubkey_original); assert_int_equal(len, torture_pubkey_len(pubkey_generated)); assert_memory_equal(pubkey_original, pubkey_generated, len); @@ -451,11 +451,11 @@ static void torture_pki_ecdsa_duplicate_key(void **state) (void) state; rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(b64_key); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, @@ -463,27 +463,27 @@ static void torture_pki_ecdsa_duplicate_key(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); privkey_dup = ssh_key_dup(privkey); assert_non_null(privkey_dup); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey_dup); rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(b64_key_gen); assert_string_equal(b64_key, b64_key_gen); rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(pubkey); SSH_KEY_FREE(pubkey_dup); @@ -540,7 +540,7 @@ static void torture_pki_generate_key_ecdsa(void **state) (void) state; rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -548,9 +548,9 @@ static void torture_pki_generate_key_ecdsa(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); type_char = ssh_key_type_to_char(type); assert_string_equal(type_char, "ecdsa-sha2-nistp256"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -562,7 +562,7 @@ static void torture_pki_generate_key_ecdsa(void **state) /* deprecated */ rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -570,9 +570,9 @@ static void torture_pki_generate_key_ecdsa(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); type_char = ssh_key_type_to_char(type); assert_string_equal(type_char, "ecdsa-sha2-nistp256"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -583,7 +583,7 @@ static void torture_pki_generate_key_ecdsa(void **state) SSH_KEY_FREE(pubkey); rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P384, 0, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -591,9 +591,9 @@ static void torture_pki_generate_key_ecdsa(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P384); type_char = ssh_key_type_to_char(type); assert_string_equal(type_char, "ecdsa-sha2-nistp384"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -605,7 +605,7 @@ static void torture_pki_generate_key_ecdsa(void **state) /* deprecated */ rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 384, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -613,9 +613,9 @@ static void torture_pki_generate_key_ecdsa(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P384); type_char = ssh_key_type_to_char(type); assert_string_equal(type_char, "ecdsa-sha2-nistp384"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -626,7 +626,7 @@ static void torture_pki_generate_key_ecdsa(void **state) SSH_KEY_FREE(pubkey); rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P521, 0, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -634,9 +634,9 @@ static void torture_pki_generate_key_ecdsa(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); type_char = ssh_key_type_to_char(type); assert_string_equal(type_char, "ecdsa-sha2-nistp521"); etype_char =ssh_pki_key_ecdsa_name(key); @@ -648,7 +648,7 @@ static void torture_pki_generate_key_ecdsa(void **state) /* deprecated */ rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 521, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -656,9 +656,9 @@ static void torture_pki_generate_key_ecdsa(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); type_char = ssh_key_type_to_char(type); assert_string_equal(type_char, "ecdsa-sha2-nistp521"); etype_char = ssh_pki_key_ecdsa_name(key); @@ -685,11 +685,11 @@ static void torture_pki_ecdsa_cert_verify(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(cert); /* Get the hash type to be used in the signature based on the key type */ @@ -698,7 +698,7 @@ static void torture_pki_ecdsa_cert_verify(void **state) sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), hash_type); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(privkey); SSH_KEY_FREE(cert); @@ -837,7 +837,7 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(origkey); unlink(LIBSSH_ECDSA_TESTKEY); @@ -847,18 +847,18 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, LIBSSH_ECDSA_TESTKEY); - assert_true(rc == 0); + assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); @@ -869,7 +869,7 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(origkey); unlink(LIBSSH_ECDSA_TESTKEY_PASSPHRASE); @@ -878,7 +878,7 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, LIBSSH_ECDSA_TESTKEY_PASSPHRASE); - assert_true(rc == 0); + assert_int_equal(rc, 0); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, @@ -886,7 +886,7 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); assert_null(privkey); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, @@ -894,11 +894,11 @@ static void torture_pki_ecdsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); @@ -914,10 +914,10 @@ static void torture_pki_ecdsa_name(void **state, const char *expected_name) (void) state; /* unused */ rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &key); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(key); - etype_char =ssh_pki_key_ecdsa_name(key); + etype_char = ssh_pki_key_ecdsa_name(key); assert_string_equal(etype_char, expected_name); SSH_KEY_FREE(key); diff --git a/tests/unittests/torture_pki_rsa.c b/tests/unittests/torture_pki_rsa.c index 96fcb4e7..c1cca6ae 100644 --- a/tests/unittests/torture_pki_rsa.c +++ b/tests/unittests/torture_pki_rsa.c @@ -164,7 +164,7 @@ static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state) NULL, NULL, NULL); - assert_true(rc == -1); + assert_int_equal(rc, -1); } @@ -178,7 +178,7 @@ static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state) /* test if it returns -1 if key_str is NULL */ rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); } @@ -197,17 +197,17 @@ static void torture_pki_rsa_import_privkey_base64(void **state) assert_non_null(key_str); rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_key_is_public(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); SSH_KEY_FREE(key); @@ -234,17 +234,17 @@ static void torture_pki_rsa_import_privkey_base64_comment(void **state) assert_int_equal(rc, file_str_len - 1); rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_key_is_public(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -272,17 +272,17 @@ static void torture_pki_rsa_import_privkey_base64_whitespace(void **state) assert_int_equal(rc, file_str_len - 1); rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_key_is_public(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); free(key_str); free(file_str); @@ -303,14 +303,14 @@ static void torture_pki_rsa_publickey_from_privatekey(void **state) NULL, NULL, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); SSH_KEY_FREE(key); @@ -333,11 +333,11 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) (void) state; /* unused */ rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(cert); rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), @@ -345,32 +345,32 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(privkey); /* Basic sanity. */ rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); - assert_true(rc == SSH_ERROR); + 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_true(rc == SSH_ERROR); + 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_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey->cert); rc = ssh_pki_copy_cert_to_privkey(cert, privkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(privkey->cert); /* The private key's cert is already set, another copy should fail. */ rc = ssh_pki_copy_cert_to_privkey(cert, privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); @@ -385,14 +385,14 @@ static void torture_pki_rsa_import_cert_file(void **state) { (void) state; /* unused */ rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(cert); type = ssh_key_type(cert); - assert_true(type == SSH_KEYTYPE_RSA_CERT01); + assert_int_equal(type, SSH_KEYTYPE_RSA_CERT01); rc = ssh_key_is_public(cert); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(cert); } @@ -417,7 +417,7 @@ static void torture_pki_rsa_publickey_base64(void **state) } type = ssh_key_type_from_name(q); - assert_true(type == SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_KEYTYPE_RSA); q = ++p; while (p != NULL && *p != '\0' && *p != ' ') p++; @@ -426,11 +426,11 @@ static void torture_pki_rsa_publickey_base64(void **state) } rc = ssh_pki_import_pubkey_base64(q, type, &key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_pubkey_base64(key, &b64_key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(b64_key); assert_string_equal(q, b64_key); @@ -457,20 +457,20 @@ static void torture_pki_rsa_generate_pubkey_from_privkey(void **state) { NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_RSA_TESTKEY ".pub"); - assert_true(rc == 0); + assert_return_code(rc, errno); rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_generated, sizeof(pubkey_generated)); - assert_true(rc == 0); + assert_return_code(rc, errno); len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_RSA), @@ -494,11 +494,11 @@ static void torture_pki_rsa_duplicate_key(void **state) (void) state; rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(pubkey); rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(b64_key); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, @@ -506,27 +506,27 @@ static void torture_pki_rsa_duplicate_key(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); privkey_dup = ssh_key_dup(privkey); assert_non_null(privkey_dup); rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(pubkey_dup); rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(b64_key_gen); assert_string_equal(b64_key, b64_key_gen); rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_return_code(rc, errno); rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(pubkey); SSH_KEY_FREE(pubkey_dup); @@ -550,7 +550,7 @@ static void torture_pki_rsa_generate_key(void **state) if (!ssh_fips_mode()) { rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -558,7 +558,7 @@ static void torture_pki_rsa_generate_key(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); @@ -567,7 +567,7 @@ static void torture_pki_rsa_generate_key(void **state) } rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -575,7 +575,7 @@ static void torture_pki_rsa_generate_key(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); @@ -583,7 +583,7 @@ static void torture_pki_rsa_generate_key(void **state) pubkey = NULL; rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -591,7 +591,7 @@ static void torture_pki_rsa_generate_key(void **state) sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); assert_non_null(sign); rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); ssh_signature_free(sign); SSH_KEY_FREE(key); SSH_KEY_FREE(pubkey); @@ -613,11 +613,11 @@ static void torture_pki_rsa_sha2(void **state) /* Setup */ rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(cert); /* Get the public key to verify signature */ @@ -680,7 +680,7 @@ static void torture_pki_rsa_key_size(void **state) (void) state; rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); - assert_true(rc == SSH_OK); + assert_return_code(rc, errno); assert_non_null(key); rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); assert_int_equal(rc, SSH_OK); @@ -696,7 +696,7 @@ static void torture_pki_rsa_key_size(void **state) /* the verification should fail now */ rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); ssh_signature_free(sign); SSH_KEY_FREE(key); @@ -821,7 +821,7 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(origkey); unlink(LIBSSH_RSA_TESTKEY); @@ -831,18 +831,18 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, LIBSSH_RSA_TESTKEY); - assert_true(rc == 0); + assert_return_code(rc, errno); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); @@ -853,7 +853,7 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(origkey); unlink(LIBSSH_RSA_TESTKEY_PASSPHRASE); @@ -862,7 +862,7 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, LIBSSH_RSA_TESTKEY_PASSPHRASE); - assert_true(rc == 0); + assert_return_code(rc, errno); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, @@ -870,7 +870,7 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); assert_null(privkey); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, @@ -878,11 +878,11 @@ static void torture_pki_rsa_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == 0); + assert_return_code(rc, errno); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_return_code(rc, errno); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); @@ -907,7 +907,7 @@ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(key); @@ -917,7 +917,7 @@ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); #ifndef HAVE_LIBCRYPTO @@ -928,7 +928,7 @@ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); #endif } @@ -955,7 +955,7 @@ torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) assert_non_null(key); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_KEY_FREE(key); @@ -965,7 +965,7 @@ torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); /* test if it returns -1 if passphrase is NULL */ @@ -975,7 +975,7 @@ torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) NULL, NULL, &key); - assert_true(rc == -1); + assert_int_equal(rc, -1); SSH_KEY_FREE(key); } From 16ebd4597ecfd3cd472636f3e83a4e6b82273480 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Sep 2023 22:58:00 +0200 Subject: [PATCH 042/795] pki: Avoid needless cast to void Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/pki.h | 2 +- src/pki.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/libssh/pki.h b/include/libssh/pki.h index 096a645f..575d442d 100644 --- a/include/libssh/pki.h +++ b/include/libssh/pki.h @@ -74,7 +74,7 @@ struct ssh_key_struct { ed25519_privkey *ed25519_privkey; #endif /* HAVE_LIBCRYPTO */ ssh_string sk_application; - void *cert; + ssh_buffer cert; enum ssh_keytypes_e cert_type; }; diff --git a/src/pki.c b/src/pki.c index 6b0ef76e..259a8a2f 100644 --- a/src/pki.c +++ b/src/pki.c @@ -1476,7 +1476,7 @@ static int pki_import_cert_buffer(ssh_buffer buffer, key->type = type; key->type_c = type_c; - key->cert = (void*) cert; + key->cert = cert; *pkey = key; return SSH_OK; From 44de06e8db145e56d676954feff5c060f6c30ebc Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Sep 2023 22:57:32 +0200 Subject: [PATCH 043/795] pki: Add support for comparing certificates Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/libssh.h | 3 ++- src/pki.c | 16 ++++++++++++++++ src/pki_ed25519_common.c | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index b2c68a7a..35ce2be5 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -295,7 +295,8 @@ enum ssh_keytypes_e{ enum ssh_keycmp_e { SSH_KEY_CMP_PUBLIC = 0, - SSH_KEY_CMP_PRIVATE + SSH_KEY_CMP_PRIVATE = 1, + SSH_KEY_CMP_CERTIFICATE = 2, }; #define SSH_ADDRSTRLEN 46 diff --git a/src/pki.c b/src/pki.c index 259a8a2f..eed79f0c 100644 --- a/src/pki.c +++ b/src/pki.c @@ -685,6 +685,22 @@ int ssh_key_cmp(const ssh_key k1, } } + if (what == SSH_KEY_CMP_CERTIFICATE) { + if (!is_cert_type(k1->type) || + !is_cert_type(k2->type)) { + return 1; + } + if (k1->cert == NULL || k2->cert == NULL) { + return 1; + } + if (ssh_buffer_get_len(k1->cert) != ssh_buffer_get_len(k2->cert)) { + return 1; + } + return memcmp(ssh_buffer_get(k1->cert), + ssh_buffer_get(k2->cert), + ssh_buffer_get_len(k1->cert)); + } + if (k1->type == SSH_KEYTYPE_ED25519 || k1->type == SSH_KEYTYPE_SK_ED25519) { return pki_ed25519_key_cmp(k1, k2, what); diff --git a/src/pki_ed25519_common.c b/src/pki_ed25519_common.c index f9f69649..3b165e2c 100644 --- a/src/pki_ed25519_common.c +++ b/src/pki_ed25519_common.c @@ -121,6 +121,10 @@ int pki_ed25519_key_cmp(const ssh_key k1, if (cmp != 0) { return 1; } + break; + case SSH_KEY_CMP_CERTIFICATE: + /* handled globally */ + return 1; } return 0; From de8f36c93c22f4683bed4bb20037613a3a55bbd3 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Sep 2023 23:20:09 +0200 Subject: [PATCH 044/795] pki: Support comparing keys with certificates Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki.c | 2 +- src/pki_crypto.c | 5 +++-- src/pki_gcrypt.c | 18 +++++++++--------- src/pki_mbedcrypto.c | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pki.c b/src/pki.c index eed79f0c..96e128c2 100644 --- a/src/pki.c +++ b/src/pki.c @@ -664,7 +664,7 @@ int ssh_key_cmp(const ssh_key k1, return 1; } - if (k1->type != k2->type) { + if (ssh_key_type_plain(k1->type) != ssh_key_type_plain(k2->type)) { SSH_LOG(SSH_LOG_DEBUG, "key types don't match!"); return 1; } diff --git a/src/pki_crypto.c b/src/pki_crypto.c index c31ef928..dae1686b 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -814,9 +814,10 @@ int pki_key_compare(const ssh_key k1, enum ssh_keycmp_e what) { int rc; - (void) what; - switch (k1->type) { + (void)what; + + switch (ssh_key_type_plain(k1->type)) { case SSH_KEYTYPE_ECDSA_P256: case SSH_KEYTYPE_ECDSA_P384: case SSH_KEYTYPE_ECDSA_P521: diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index a1674900..0a864493 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -1298,6 +1298,7 @@ int pki_key_compare(const ssh_key k1, { switch (k1->type) { case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { return 1; } @@ -1325,13 +1326,19 @@ int pki_key_compare(const ssh_key k1, } break; case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: /* ed25519 keys handled globally */ return 0; case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: #ifdef HAVE_GCRYPT_ECC if (k1->ecdsa_nid != k2->ecdsa_nid) { return 1; @@ -1348,16 +1355,9 @@ int pki_key_compare(const ssh_key k1, } break; #endif - case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS: /* deprecated */ case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ - case SSH_KEYTYPE_RSA_CERT01: - case SSH_KEYTYPE_ECDSA: - case SSH_KEYTYPE_ECDSA_P256_CERT01: - case SSH_KEYTYPE_ECDSA_P384_CERT01: - case SSH_KEYTYPE_ECDSA_P521_CERT01: - case SSH_KEYTYPE_SK_ECDSA_CERT01: - case SSH_KEYTYPE_ED25519_CERT01: - case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_ECDSA: /* deprecated */ case SSH_KEYTYPE_RSA1: case SSH_KEYTYPE_UNKNOWN: return 1; diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index 86717ca7..e047239e 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -638,7 +638,7 @@ int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) mbedtls_mpi_init(&E2); #endif - switch (k1->type) { + switch (ssh_key_type_plain(k1->type)) { case SSH_KEYTYPE_RSA: { mbedtls_rsa_context *rsa1, *rsa2; if (!mbedtls_pk_can_do(k1->rsa, MBEDTLS_PK_RSA) || From a8c844c9c2e0edf63543f5164d76ca7e6fbba286 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 21 Sep 2023 18:00:00 +0200 Subject: [PATCH 045/795] pki: Make sure imported certificate is certificate Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pki.c b/src/pki.c index 96e128c2..92224664 100644 --- a/src/pki.c +++ b/src/pki.c @@ -1868,7 +1868,18 @@ int ssh_pki_import_cert_blob(const ssh_string cert_blob, */ int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) { - return ssh_pki_import_pubkey_file(filename, pkey); + int rc; + + rc = ssh_pki_import_pubkey_file(filename, pkey); + if (rc == SSH_OK) { + /* check the key is a cert type. */ + if (!is_cert_type((*pkey)->type)) { + SSH_KEY_FREE(*pkey); + return SSH_ERROR; + } + } + + return rc; } /** From d604d7f872cf1da32b38fdcb8ab405df02e88259 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 21 Sep 2023 18:02:08 +0200 Subject: [PATCH 046/795] pki: Make sure public keys match when adding certificate data Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pki.c b/src/pki.c index 92224664..b07a5f67 100644 --- a/src/pki.c +++ b/src/pki.c @@ -2162,7 +2162,7 @@ int ssh_pki_export_pubkey_file(const ssh_key key, **/ int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { ssh_buffer cert_buffer; - int rc; + int rc, cmp; if (certkey == NULL || privkey == NULL) { return SSH_ERROR; @@ -2176,6 +2176,12 @@ int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) { return SSH_ERROR; } + /* make sure the public keys match */ + cmp = ssh_key_cmp(certkey, privkey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return SSH_ERROR; + } + cert_buffer = ssh_buffer_new(); if (cert_buffer == NULL) { return SSH_ERROR; From 4f903812e6a4c2aba09d325adbfbcc4a2269a869 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 21 Sep 2023 18:02:54 +0200 Subject: [PATCH 047/795] auth: Reformat ssh_userauth_agent Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/auth.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/auth.c b/src/auth.c index c29a7106..d630b7a6 100644 --- a/src/auth.c +++ b/src/auth.c @@ -1255,35 +1255,36 @@ int ssh_userauth_publickey_auto(ssh_session session, rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to import public key: %s", - pubkey_file); + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } else if (rc == SSH_EOF) { /* Read the private key and save the public key to file */ rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); - state->it=state->it->next; + state->it = state->it->next; continue; } - rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, + &state->pubkey); if (rc == SSH_ERROR) { ssh_key_free(state->privkey); SAFE_FREE(session->auth.auto_state); From 7d4f210234b62975f61fb231a239d4f17b70c09b Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 25 Sep 2023 16:39:52 +0200 Subject: [PATCH 048/795] tests: Cover recent changes for importing certs to keys Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/unittests/torture_pki_ecdsa.c | 5 +++++ tests/unittests/torture_pki_ed25519.c | 5 +++++ tests/unittests/torture_pki_rsa.c | 23 ++++++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/unittests/torture_pki_ecdsa.c b/tests/unittests/torture_pki_ecdsa.c index 6c6e3012..0995a14b 100644 --- a/tests/unittests/torture_pki_ecdsa.c +++ b/tests/unittests/torture_pki_ecdsa.c @@ -337,6 +337,11 @@ static void torture_pki_ecdsa_import_cert_file(void **state) enum ssh_keytypes_e type; struct pki_st *test_state = *((struct pki_st **)state); + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); assert_int_equal(rc, 0); assert_non_null(cert); diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c index 764df04e..cf83ed0b 100644 --- a/tests/unittests/torture_pki_ed25519.c +++ b/tests/unittests/torture_pki_ed25519.c @@ -317,6 +317,11 @@ static void torture_pki_ed25519_import_cert_file(void **state) (void) state; /* unused */ + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); assert_true(rc == 0); assert_non_null(cert); diff --git a/tests/unittests/torture_pki_rsa.c b/tests/unittests/torture_pki_rsa.c index c1cca6ae..1700270e 100644 --- a/tests/unittests/torture_pki_rsa.c +++ b/tests/unittests/torture_pki_rsa.c @@ -330,7 +330,12 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) ssh_key privkey = NULL; ssh_key cert = NULL; - (void) state; /* unused */ + (void)state; /* unused */ + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); assert_return_code(rc, errno); @@ -372,6 +377,22 @@ static void torture_pki_rsa_copy_cert_to_privkey(void **state) 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_RSA, 2048, &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); + SSH_KEY_FREE(cert); SSH_KEY_FREE(privkey); SSH_KEY_FREE(pubkey); From c1630fa0973c41c82ab39b6ad2cad53703f0b847 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 27 Sep 2023 17:14:35 +0200 Subject: [PATCH 049/795] Reformat auth.c Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/auth.c | 112 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/src/auth.c b/src/auth.c index d630b7a6..068a24f1 100644 --- a/src/auth.c +++ b/src/auth.c @@ -754,16 +754,17 @@ static int ssh_userauth_agent_publickey(ssh_session session, bool allowed; int rc; - switch(session->pending_call_state) { - case SSH_PENDING_CALL_NONE: - break; - case SSH_PENDING_CALL_AUTH_AGENT: - goto pending; - default: - ssh_set_error(session, - SSH_FATAL, - "Bad call during pending SSH call in ssh_userauth_try_publickey"); - return SSH_ERROR; + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in %s", + __func__); + return SSH_ERROR; } rc = ssh_userauth_request_service(session); @@ -807,14 +808,14 @@ static int ssh_userauth_agent_publickey(ssh_session session, /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", - SSH2_MSG_USERAUTH_REQUEST, - username ? username : session->opts.username, - "ssh-connection", - "publickey", - 1, /* private key */ - sig_type_c, /* algo */ - pubkey_s /* public key */ - ); + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "publickey", + 1, /* private key */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); SSH_STRING_FREE(pubkey_s); if (rc < 0) { goto fail; @@ -905,7 +906,7 @@ int ssh_userauth_agent(ssh_session session, const char *username) { int rc = SSH_AUTH_ERROR; - struct ssh_agent_state_struct *state; + struct ssh_agent_state_struct *state = NULL; ssh_key *configKeys = NULL; size_t configKeysCount = 0; size_t i; @@ -925,7 +926,7 @@ int ssh_userauth_agent(ssh_session session, return SSH_AUTH_ERROR; } ZERO_STRUCTP(session->agent_state); - session->agent_state->state=SSH_AGENT_STATE_NONE; + session->agent_state->state = SSH_AGENT_STATE_NONE; } state = session->agent_state; @@ -953,12 +954,12 @@ int ssh_userauth_agent(ssh_session session, while (it != NULL && configKeysCount < identityLen) { const char *privkeyFile = it->data; + ssh_key pubkey = NULL; /* * Read the private key file listed in the config, but we're only * interested in the public key. Don't try to decrypt private key. */ - ssh_key pubkey = NULL; rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey); if (rc == SSH_OK) { configKeys[configKeysCount++] = pubkey; @@ -996,13 +997,16 @@ int ssh_userauth_agent(ssh_session session, while (state->pubkey != NULL) { if (state->state == SSH_AGENT_STATE_NONE) { SSH_LOG(SSH_LOG_DEBUG, - "Trying identity %s", state->comment); + "Trying identity %s", + state->comment); if (session->opts.identities_only) { /* Check if this key is one of the keys listed in the config */ bool found_key = false; for (i = 0; i < configKeysCount; i++) { - if (ssh_key_cmp(state->pubkey, configKeys[i], - SSH_KEY_CMP_PUBLIC) == 0) { + int cmp = ssh_key_cmp(state->pubkey, + configKeys[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { found_key = true; break; } @@ -1011,7 +1015,8 @@ int ssh_userauth_agent(ssh_session session, if (!found_key) { SSH_LOG(SSH_LOG_DEBUG, "Identities only is enabled and identity %s was " - "not listed in config, skipping", state->comment); + "not listed in config, skipping", + state->comment); SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; SSH_KEY_FREE(state->pubkey); @@ -1026,10 +1031,10 @@ int ssh_userauth_agent(ssh_session session, } } if (state->state == SSH_AGENT_STATE_NONE || - state->state == SSH_AGENT_STATE_PUBKEY) { + state->state == SSH_AGENT_STATE_PUBKEY) { rc = ssh_userauth_try_publickey(session, username, state->pubkey); if (rc == SSH_AUTH_ERROR) { - ssh_agent_state_free (state); + ssh_agent_state_free(state); session->agent_state = NULL; goto done; } else if (rc == SSH_AUTH_AGAIN) { @@ -1037,17 +1042,20 @@ int ssh_userauth_agent(ssh_session session, goto done; } else if (rc != SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s refused by server", state->comment); + "Public key of %s refused by server", + state->comment); SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; SSH_KEY_FREE(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } SSH_LOG(SSH_LOG_DEBUG, - "Public key of %s accepted by server", state->comment); + "Public key of %s accepted by server", + state->comment); state->state = SSH_AGENT_STATE_AUTH; } if (state->state == SSH_AGENT_STATE_AUTH) { @@ -1058,14 +1066,15 @@ int ssh_userauth_agent(ssh_session session, SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) { - ssh_agent_state_free (session->agent_state); + ssh_agent_state_free(session->agent_state); session->agent_state = NULL; goto done; } else if (rc != SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, "Server accepted public key but refused the signature"); SSH_KEY_FREE(state->pubkey); - state->pubkey = ssh_agent_get_next_ident(session, &state->comment); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); state->state = SSH_AGENT_STATE_NONE; continue; } @@ -1132,7 +1141,8 @@ int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, return SSH_ERROR; } - if (session->auth.auto_state != NULL && session->auth.auto_state->it != NULL) { + if (session->auth.auto_state != NULL && + session->auth.auto_state->it != NULL) { id = session->auth.auto_state->it->data; } @@ -1183,7 +1193,7 @@ int ssh_userauth_publickey_auto(ssh_session session, { ssh_auth_callback auth_fn = NULL; void *auth_data = NULL; - struct ssh_auth_auto_state_struct *state; + struct ssh_auth_auto_state_struct *state = NULL; int rc; if (session == NULL) { @@ -1214,7 +1224,7 @@ int ssh_userauth_publickey_auto(ssh_session session, rc = ssh_userauth_agent(session, username); if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL || - rc == SSH_AUTH_AGAIN ) { + rc == SSH_AUTH_AGAIN) { return rc; } state->state = SSH_AUTH_AUTO_STATE_PUBKEY; @@ -1229,7 +1239,8 @@ int ssh_userauth_publickey_auto(ssh_session session, if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { SSH_LOG(SSH_LOG_DEBUG, - "Trying to authenticate with %s", privkey_file); + "Trying to authenticate with %s", + privkey_file); state->privkey = NULL; state->pubkey = NULL; @@ -1242,14 +1253,19 @@ int ssh_userauth_publickey_auto(ssh_session session, if (pub_uri_from_priv == NULL) { return SSH_ERROR; } else { - snprintf(pubkey_file, sizeof(pubkey_file), "%s", + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s", pub_uri_from_priv); SAFE_FREE(pub_uri_from_priv); } } else #endif /* WITH_PKCS11_URI */ { - snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file); + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s.pub", + privkey_file); } rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); @@ -1322,7 +1338,7 @@ int ssh_userauth_publickey_auto(ssh_session session, state->privkey = NULL; ssh_key_free(state->pubkey); state->pubkey = NULL; - state->it=state->it->next; + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } @@ -1332,18 +1348,18 @@ int ssh_userauth_publickey_auto(ssh_session session, /* Public key has been accepted by the server */ if (state->privkey == NULL) { rc = ssh_pki_import_privkey_file(privkey_file, - passphrase, - auth_fn, - auth_data, - &state->privkey); + passphrase, + auth_fn, + auth_data, + &state->privkey); if (rc == SSH_ERROR) { ssh_key_free(state->pubkey); - state->pubkey=NULL; + state->pubkey = NULL; ssh_set_error(session, - SSH_FATAL, - "Failed to read private key: %s", - privkey_file); - state->it=state->it->next; + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; } else if (rc == SSH_EOF) { From aae1bc10588787e1fe15c84b9f8f76176ac8e603 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 21 Sep 2023 18:03:17 +0200 Subject: [PATCH 050/795] Handle automatic certificate authentication This involves reading the certificates from configuration files through options and handling them similarly as the OpenSSH does when doing the auto pubkey authentication, also in combination with agent or identities only. Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/config.h | 1 + include/libssh/libssh.h | 1 + include/libssh/session.h | 2 + src/auth.c | 236 ++++++++++++++++++++++++++++++++++----- src/config.c | 9 +- src/options.c | 76 ++++++++++++- src/session.c | 33 ++++++ 7 files changed, 325 insertions(+), 33 deletions(-) diff --git a/include/libssh/config.h b/include/libssh/config.h index 21702391..4f8a9b6f 100644 --- a/include/libssh/config.h +++ b/include/libssh/config.h @@ -65,6 +65,7 @@ enum ssh_config_opcode_e { SOC_IDENTITIESONLY, SOC_CONTROLMASTER, SOC_CONTROLPATH, + SOC_CERTIFICATE, SOC_MAX /* Keep this one last in the list */ }; diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 35ce2be5..5348370e 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -416,6 +416,7 @@ enum ssh_options_e { SSH_OPTIONS_IDENTITIES_ONLY, SSH_OPTIONS_CONTROL_MASTER, SSH_OPTIONS_CONTROL_PATH, + SSH_OPTIONS_CERTIFICATE, }; enum { diff --git a/include/libssh/session.h b/include/libssh/session.h index b3b3e4e6..cb55db95 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -231,6 +231,8 @@ struct ssh_session_struct { struct { struct ssh_list *identity; struct ssh_list *identity_non_exp; + struct ssh_list *certificate; + struct ssh_list *certificate_non_exp; char *username; char *host; char *bindaddr; /* bind the client to an ip addr */ diff --git a/src/auth.c b/src/auth.c index 068a24f1..820d82fc 100644 --- a/src/auth.c +++ b/src/auth.c @@ -557,6 +557,7 @@ int ssh_userauth_try_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -690,6 +691,7 @@ int ssh_userauth_publickey(ssh_session session, goto fail; } + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); /* request */ rc = ssh_buffer_pack(session->out_buffer, "bsssbsS", SSH2_MSG_USERAUTH_REQUEST, @@ -859,6 +861,7 @@ static int ssh_userauth_agent_publickey(ssh_session session, enum ssh_agent_state_e { SSH_AGENT_STATE_NONE = 0, SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, SSH_AGENT_STATE_AUTH }; @@ -908,7 +911,9 @@ int ssh_userauth_agent(ssh_session session, int rc = SSH_AUTH_ERROR; struct ssh_agent_state_struct *state = NULL; ssh_key *configKeys = NULL; + ssh_key *configCerts = NULL; size_t configKeysCount = 0; + size_t configCertsCount = 0; size_t i; if (session == NULL) { @@ -944,17 +949,24 @@ int ssh_userauth_agent(ssh_session session, * is in there. */ size_t identityLen = ssh_list_count(session->opts.identity); + size_t certsLen = ssh_list_count(session->opts.certificate); struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); - configKeys = malloc(identityLen * sizeof(configKeys[0])); - if (!configKeys) { + configKeys = malloc(identityLen * sizeof(ssh_key)); + configCerts = malloc((certsLen + identityLen) * sizeof(ssh_key)); + if (configKeys == NULL || configCerts == NULL) { + free(configKeys); + free(configCerts); ssh_set_error_oom(session); return SSH_AUTH_ERROR; } while (it != NULL && configKeysCount < identityLen) { const char *privkeyFile = it->data; + size_t certPathLen; + char *certFile = NULL; ssh_key pubkey = NULL; + ssh_key cert = NULL; /* * Read the private key file listed in the config, but we're only @@ -967,9 +979,7 @@ int ssh_userauth_agent(ssh_session session, char *pubkeyFile = NULL; size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub"); - if (pubkey) { - SSH_KEY_FREE(pubkey); - } + SSH_KEY_FREE(pubkey); /* * If we couldn't get the public key from the private key file, @@ -983,13 +993,47 @@ int ssh_userauth_agent(ssh_session session, } snprintf(pubkeyFile, pubkeyPathLen, "%s.pub", privkeyFile); rc = ssh_pki_import_pubkey_file(pubkeyFile, &pubkey); + free(pubkeyFile); if (rc == SSH_OK) { configKeys[configKeysCount++] = pubkey; } else if (pubkey) { SSH_KEY_FREE(pubkey); } - free(pubkeyFile); } + /* Now try to see if there is a certificate with default name + * do not merge it yet with the key as we need to try first the + * non-certified key */ + certPathLen = strlen(privkeyFile) + sizeof("-cert.pub"); + certFile = malloc(certPathLen); + if (!certFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(certFile, certPathLen, "%s-cert.pub", privkeyFile); + rc = ssh_pki_import_cert_file(certFile, &cert); + free(certFile); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + /* And now load separately-listed certificates. */ + it = ssh_list_get_iterator(session->opts.certificate); + while (it != NULL && configCertsCount < certsLen + identityLen) { + const char *certFile = it->data; + ssh_key cert = NULL; + + rc = ssh_pki_import_cert_file(certFile, &cert); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + it = it->next; } } @@ -1011,6 +1055,16 @@ int ssh_userauth_agent(ssh_session session, break; } } + /* or in separate certificates */ + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } if (!found_key) { SSH_LOG(SSH_LOG_DEBUG, @@ -1031,19 +1085,36 @@ int ssh_userauth_agent(ssh_session session, } } if (state->state == SSH_AGENT_STATE_NONE || - state->state == SSH_AGENT_STATE_PUBKEY) { + state->state == SSH_AGENT_STATE_PUBKEY || + state->state == SSH_AGENT_STATE_CERT) { rc = ssh_userauth_try_publickey(session, username, state->pubkey); if (rc == SSH_AUTH_ERROR) { ssh_agent_state_free(state); session->agent_state = NULL; goto done; } else if (rc == SSH_AUTH_AGAIN) { - state->state = SSH_AGENT_STATE_PUBKEY; + state->state = (state->state == SSH_AGENT_STATE_NONE ? + SSH_AGENT_STATE_PUBKEY : state->state); goto done; } else if (rc != SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, "Public key of %s refused by server", state->comment); + if (state->state == SSH_AGENT_STATE_PUBKEY) { + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Retry with matching certificate"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_key_dup(configCerts[i]); + state->state = SSH_AGENT_STATE_CERT; + continue; + } + } + } SSH_STRING_FREE_CHAR(state->comment); state->comment = NULL; SSH_KEY_FREE(state->pubkey); @@ -1092,6 +1163,10 @@ int ssh_userauth_agent(ssh_session session, ssh_key_free(configKeys[i]); } free(configKeys); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); return rc; } @@ -1099,6 +1174,8 @@ enum ssh_auth_auto_state_e { SSH_AUTH_AUTO_STATE_NONE = 0, SSH_AUTH_AUTO_STATE_PUBKEY, SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED }; @@ -1107,6 +1184,8 @@ struct ssh_auth_auto_state_struct { struct ssh_iterator *it; ssh_key privkey; ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; }; /** @@ -1186,6 +1265,9 @@ int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, * @note Most server implementations do not permit changing the username during * authentication. The username should only be set with ssh_options_set() only * before you connect to the server. + * + * The OpenSSH iterates over the identities and first try the plain public key + * and then the certificate if it is in place. */ int ssh_userauth_publickey_auto(ssh_session session, const char *username, @@ -1241,6 +1323,7 @@ int ssh_userauth_publickey_auto(ssh_session session, SSH_LOG(SSH_LOG_DEBUG, "Trying to authenticate with %s", privkey_file); + state->cert = NULL; state->privkey = NULL; state->pubkey = NULL; @@ -1302,7 +1385,7 @@ int ssh_userauth_publickey_auto(ssh_session session, rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey); if (rc == SSH_ERROR) { - ssh_key_free(state->privkey); + SSH_KEY_FREE(state->privkey); SAFE_FREE(session->auth.auto_state); return SSH_AUTH_ERROR; } @@ -1316,28 +1399,101 @@ int ssh_userauth_publickey_auto(ssh_session session, } state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; } - if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { - rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + ssh_key k = state->pubkey; + if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + k = state->cert; + } + rc = ssh_userauth_try_publickey(session, username, k); if (rc == SSH_AUTH_ERROR) { SSH_LOG(SSH_LOG_TRACE, "Public key authentication error for %s", privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); return rc; } else if (rc == SSH_AUTH_AGAIN) { return rc; } else if (rc != SSH_AUTH_SUCCESS) { + int r; /* do not reuse `rc` as it is used to return from here */ + SSH_KEY_FREE(state->cert); SSH_LOG(SSH_LOG_DEBUG, - "Public key for %s refused by server", - privkey_file); - ssh_key_free(state->privkey); - state->privkey = NULL; - ssh_key_free(state->pubkey); - state->pubkey = NULL; + "Public key for %s%s refused by server", + privkey_file, + (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED + ? " (with certificate)" : "")); + /* Try certificate file by appending -cert.pub (if present) */ + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + char cert_file[PATH_MAX] = {0}; + ssh_key cert = NULL; + + snprintf(cert_file, + sizeof(cert_file), + "%s-cert.pub", + privkey_file); + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (default path)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + /* TODO check the pubkey and certs match */ + SSH_LOG(SSH_LOG_TRACE, + "Certificate loaded %s. Retry the authentication.", + cert_file); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE; + SSH_KEY_FREE(state->cert); + state->cert = cert; + /* try to authenticate with this certificate */ + continue; + } + /* if the file does not exists, try configuration options */ + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } + /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + SSH_KEY_FREE(state->cert); + if (state->cert_it == NULL) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + } + while (state->cert_it != NULL) { + const char *cert_file = state->cert_it->data; + ssh_key cert = NULL; + + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (options)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + int cmp = ssh_key_cmp(cert, + state->pubkey, + SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + state->cert_it = state->cert_it->next; + SSH_KEY_FREE(cert); + continue; /* with next cert */ + } + SSH_LOG(SSH_LOG_TRACE, + "Found matching certificate %s in options. Retry the authentication.", + cert_file); + state->cert = cert; + cert = NULL; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + /* try to authenticate with this identity */ + break; /* try this cert */ + } + /* continue with next identity */ + } + if (state->cert != NULL) { + continue; /* retry with the certificate */ + } + } + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); state->it = state->it->next; state->state = SSH_AUTH_AUTO_STATE_PUBKEY; continue; @@ -1353,8 +1509,8 @@ int ssh_userauth_publickey_auto(ssh_session session, auth_data, &state->privkey); if (rc == SSH_ERROR) { - ssh_key_free(state->pubkey); - state->pubkey = NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); ssh_set_error(session, SSH_FATAL, "Failed to read private key: %s", @@ -1364,8 +1520,8 @@ int ssh_userauth_publickey_auto(ssh_session session, continue; } else if (rc == SSH_EOF) { /* If the file doesn't exist, continue */ - ssh_key_free(state->pubkey); - state->pubkey = NULL; + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); SSH_LOG(SSH_LOG_DEBUG, "Private key %s doesn't exist.", privkey_file); @@ -1374,16 +1530,33 @@ int ssh_userauth_publickey_auto(ssh_session session, continue; } } + if (state->cert != NULL && !is_cert_type(state->privkey->cert_type)) { + rc = ssh_pki_copy_cert_to_privkey(state->cert, state->privkey); + if (rc != SSH_OK) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to copy cert to private key"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } rc = ssh_userauth_publickey(session, username, state->privkey); if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + bool cert_used = (state->cert != NULL); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SAFE_FREE(session->auth.auto_state); if (rc == SSH_AUTH_SUCCESS) { SSH_LOG(SSH_LOG_DEBUG, - "Successfully authenticated using %s", - privkey_file); + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); } return rc; } @@ -1391,8 +1564,9 @@ int ssh_userauth_publickey_auto(ssh_session session, return rc; } - ssh_key_free(state->privkey); - ssh_key_free(state->pubkey); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); SSH_LOG(SSH_LOG_DEBUG, "The server accepted the public key but refused the signature"); diff --git a/src/config.c b/src/config.c index 156b146e..5eedbce9 100644 --- a/src/config.c +++ b/src/config.c @@ -92,7 +92,7 @@ static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { { "canonicalizehostname", SOC_UNSUPPORTED}, { "canonicalizemaxdots", SOC_UNSUPPORTED}, { "canonicalizepermittedcnames", SOC_UNSUPPORTED}, - { "certificatefile", SOC_UNSUPPORTED}, + { "certificatefile", SOC_CERTIFICATE}, { "kbdinteractiveauthentication", SOC_UNSUPPORTED}, { "checkhostip", SOC_UNSUPPORTED}, { "connectionattempts", SOC_UNSUPPORTED}, @@ -623,6 +623,7 @@ ssh_config_parse_line(ssh_session session, opcode != SOC_MATCH && opcode != SOC_INCLUDE && opcode != SOC_IDENTITY && + opcode != SOC_CERTIFICATE && opcode > SOC_UNSUPPORTED && opcode < SOC_MAX) { /* Ignore all unknown types here */ /* Skip all the options that were already applied */ @@ -1218,6 +1219,12 @@ ssh_config_parse_line(ssh_session session, ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); } break; + case SOC_CERTIFICATE: + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); + } + break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", opcode); diff --git a/src/options.c b/src/options.c index f0bb476a..f144bbb4 100644 --- a/src/options.c +++ b/src/options.c @@ -118,7 +118,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) while (it) { int rc; - id = strdup((char *) it->data); + id = strdup((char *)it->data); if (id == NULL) { ssh_free(new); return -1; @@ -138,6 +138,32 @@ int ssh_options_copy(ssh_session src, ssh_session *dest) it = ssh_list_get_iterator(src->opts.identity); } + list = new->opts.certificate_non_exp; + it = ssh_list_get_iterator(src->opts.certificate_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the certificate list if there is any already */ + list = new->opts.certificate; + it = ssh_list_get_iterator(src->opts.certificate); + } + if (src->opts.sshdir != NULL) { new->opts.sshdir = strdup(src->opts.sshdir); if (new->opts.sshdir == NULL) { @@ -353,6 +379,21 @@ int ssh_options_set_algo(ssh_session session, * It may include "%s" which will be replaced by the * user home directory. * + * - SSH_OPTIONS_CERTIFICATE: + * Add a new certificate file (const char *, format string) to + * the certificate list.\n + * \n + * By default id_rsa-cert.pub, id_ecdsa-cert.pub and + * id_ed25519-cert.pub files are used, when the underlying + * private key is present.\n + * \n + * The certificate itself can not be used to authenticate to + * remote server so it needs to be paired with private key + * (aka identity file) provided with separate option, from agent + * or from PKCS#11 token. + * It may include "%s" which will be replaced by the + * user home directory. + * * - SSH_OPTIONS_TIMEOUT: * Set a timeout for the connection in seconds (long). * @@ -753,6 +794,22 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } break; + case SSH_OPTIONS_CERTIFICATE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + rc = ssh_list_append(session->opts.certificate_non_exp, q); + if (rc < 0) { + free(q); + return -1; + } + break; case SSH_OPTIONS_KNOWNHOSTS: v = value; SAFE_FREE(session->opts.knownhosts); @@ -1753,6 +1810,23 @@ int ssh_options_apply(ssh_session session) } session->opts.exp_flags |= SSH_OPT_EXP_FLAG_IDENTITY; + for (tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + char *id = tmp; + + tmp = ssh_path_expand_escape(session, id); + if (tmp == NULL) { + return -1; + } + free(id); + + rc = ssh_list_append(session->opts.certificate, tmp); + if (rc != SSH_OK) { + return -1; + } + } + return 0; } diff --git a/src/session.c b/src/session.c index 098f94a0..c3aaf32f 100644 --- a/src/session.c +++ b/src/session.c @@ -127,6 +127,17 @@ ssh_session ssh_new(void) goto err; } + session->opts.certificate = ssh_list_new(); + if (session->opts.certificate == NULL) { + goto err; + } + session->opts.certificate_non_exp = ssh_list_new(); + if (session->opts.certificate_non_exp == NULL) { + goto err; + } + /* the default certificates are loaded automatically from the default + * identities later */ + id = strdup("%d/id_ed25519"); if (id == NULL) { goto err; @@ -288,6 +299,28 @@ void ssh_free(ssh_session session) ssh_list_free(session->opts.identity_non_exp); } + if (session->opts.certificate) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate); + } + + if (session->opts.certificate_non_exp) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate_non_exp); + } + while ((b = ssh_list_pop_head(struct ssh_buffer_struct *, session->out_queue)) != NULL) { SSH_BUFFER_FREE(b); From 14c7b6a3fb7f262322e4af8bfe83a824ba703fc8 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 29 Sep 2023 11:12:08 +0200 Subject: [PATCH 051/795] tests: Coverage for certificate files config and options Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/unittests/torture_config.c | 25 +++++++++++++++++++++---- tests/unittests/torture_options.c | 16 ++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index db33152b..544623fe 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -184,7 +184,9 @@ extern LIBSSH_THREAD int ssh_log_level; /* Multiple IdentityFile settings all are applied */ #define LIBSSH_TESTCONFIG_STRING13 \ "IdentityFile id_rsa_one\n" \ - "IdentityFile id_ecdsa_two\n" + "CertificateFile id_rsa_one-cert.pub\n" \ + "IdentityFile id_ecdsa_two\n" \ + "CertificateFile id_ecdsa_two-cert.pub\n" \ /* +,-,^ features for all supported list */ /* kex won't work in fips */ @@ -1913,10 +1915,10 @@ static void torture_config_parser_get_cmd(void **state) } else if (pid == 0) { ssh_execute_command(tok, fileno(outfile), fileno(outfile)); /* Does not return */ - } else { - /* parent + } else { + /* parent * wait child process */ - wait(NULL); + wait(NULL); infile = fopen("output.log", "r"); assert_non_null(infile); p = fgets(buffer, sizeof(buffer), infile); @@ -2198,6 +2200,7 @@ static void torture_config_match_pattern(void **state) static void torture_config_identity(void **state) { const char *id = NULL; + const char *cert = NULL; struct ssh_iterator *it = NULL; ssh_session session = *state; @@ -2214,6 +2217,20 @@ static void torture_config_identity(void **state) assert_non_null(it); id = it->data; assert_string_equal(id, "id_rsa_one"); + + /* The certs are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + cert = it->data; + /* The certs are coming as listed in the configuration file */ + assert_string_equal(cert, "id_rsa_one-cert.pub"); + + it = it->next; + assert_non_null(it); + cert = it->data; + assert_string_equal(cert, "id_ecdsa_two-cert.pub"); + /* and that is all */ + assert_null(it->next); } /* Make absolute path for config include diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index b1c416dd..5ba3bdc6 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -900,6 +900,9 @@ static void torture_options_copy(void **state) config = fopen("test_config", "w"); assert_non_null(config); fputs("IdentityFile ~/.ssh/id_ecdsa\n" + "IdentityFile ~/.ssh/my_rsa\n" + "CertificateFile ~/.ssh/my_rsa-cert.pub\n" + "CertificateFile ~/.ssh/id_ecdsa-cert.pub\n" "User tester\n" "Hostname example.com\n" "BindAddress 127.0.0.2\n" @@ -947,6 +950,19 @@ static void torture_options_copy(void **state) assert_null(it); assert_null(it2); + /* Check the certificates match */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.certificate_non_exp); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + assert_string_equal(session->opts.username, new->opts.username); assert_string_equal(session->opts.host, new->opts.host); assert_string_equal(session->opts.bindaddr, new->opts.bindaddr); From baa4eb12325b2feeb990207e80467988722c9dc2 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 29 Sep 2023 15:27:50 +0200 Subject: [PATCH 052/795] tests: Move tests with certificates to separate user This avoids very-long test and false positives when using some auto-pubkey authentication from picking up default keys, which are available in bob's home directory when we want to test the certificate authentication. The separate file is also needed because once we change to bob's UID, we can not simply go back different UID and this sounds cleaner than setting up SSH_DIR to different users ... Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/CMakeLists.txt | 11 +- tests/client/CMakeLists.txt | 1 + tests/client/torture_auth.c | 211 +----------------- tests/client/torture_auth_cert.c | 338 +++++++++++++++++++++++++++++ tests/client/torture_auth_common.c | 90 ++++++++ tests/etc/pam_matrix_passdb.in | 1 + tests/etc/passwd.in | 1 + tests/etc/shadow.in | 1 + 8 files changed, 446 insertions(+), 208 deletions(-) create mode 100644 tests/client/torture_auth_cert.c create mode 100644 tests/client/torture_auth_common.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d91c2211..34e6cf81 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -303,10 +303,13 @@ if (CLIENT_TESTING OR SERVER_TESTING) file(READ keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub CONTENTS) file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") - # Copy the signed key to an alternative directory in bob's homedir. - file(COPY keys/certauth/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) - file(COPY keys/certauth/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) - file(COPY keys/certauth/id_rsa-cert.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh_cert/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Copy the signed key to an doe's homedir. + file(COPY keys/certauth/id_rsa DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa-cert.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) endif () if (WITH_PKCS11_URI) diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index cde4838f..0e7aa288 100644 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -9,6 +9,7 @@ set(LIBSSH_CLIENT_TESTS torture_connect torture_hostkey torture_auth + torture_auth_cert torture_rekey torture_forward torture_knownhosts diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index 5a9bc6fa..617afc69 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -32,8 +32,7 @@ #include #include -/* agent_is_running */ -#include "agent.c" +#include "torture_auth_common.c" static int sshd_setup(void **state) { @@ -112,7 +111,7 @@ static int agent_setup(void **state) char ssh_agent_cmd[4096]; char ssh_agent_sock[1024]; char ssh_agent_pidfile[1024]; - char bob_ssh_key[1024]; + char ssh_key_add[1024]; struct passwd *pwd; int rc; @@ -149,38 +148,12 @@ static int agent_setup(void **state) setenv("SSH_AUTH_SOCK", ssh_agent_sock, 1); setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile, 1); - snprintf(bob_ssh_key, - sizeof(bob_ssh_key), + snprintf(ssh_key_add, + sizeof(ssh_key_add), "ssh-add %s/.ssh/id_rsa", pwd->pw_dir); - rc = system(bob_ssh_key); - assert_return_code(rc, errno); - - return 0; -} - -static int agent_cert_setup(void **state) -{ - char bob_alt_ssh_key[1024]; - struct passwd *pwd; - int rc; - - rc = agent_setup(state); - if (rc != 0) { - return rc; - } - - pwd = getpwnam("bob"); - assert_non_null(pwd); - - /* remove all keys, load alternative key + cert */ - snprintf(bob_alt_ssh_key, - sizeof(bob_alt_ssh_key), - "ssh-add -D && ssh-add %s/.ssh_cert/id_rsa", - pwd->pw_dir); - - rc = system(bob_alt_ssh_key); + rc = system(ssh_key_add); assert_return_code(rc, errno); return 0; @@ -659,73 +632,15 @@ static void torture_auth_password_nonblocking(void **state) { assert_int_equal(rc, SSH_AUTH_SUCCESS); } -static void torture_auth_agent(void **state) { - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; - - if (!ssh_agent_is_running(session)){ - print_message("*** Agent not running. Test ignored\n"); - return; - } - rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); - assert_int_equal(rc, SSH_OK); - - rc = ssh_connect(session); - assert_int_equal(rc, SSH_OK); - - rc = ssh_userauth_none(session,NULL); - /* This request should return a SSH_REQUEST_DENIED error */ - if (rc == SSH_ERROR) { - assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); - } - rc = ssh_userauth_list(session, NULL); - assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); - - rc = ssh_userauth_agent(session, NULL); - assert_ssh_return_code(session, rc); -} - -static void torture_auth_agent_nonblocking(void **state) { - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; - - if (!ssh_agent_is_running(session)){ - print_message("*** Agent not running. Test ignored\n"); - return; - } - rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); - assert_int_equal(rc, SSH_OK); - - rc = ssh_connect(session); - assert_int_equal(rc, SSH_OK); - - rc = ssh_userauth_none(session,NULL); - /* This request should return a SSH_REQUEST_DENIED error */ - if (rc == SSH_ERROR) { - assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); - } - rc = ssh_userauth_list(session, NULL); - assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); - - ssh_set_blocking(session,0); - - do { - rc = ssh_userauth_agent(session, NULL); - } while (rc == SSH_AUTH_AGAIN); - assert_ssh_return_code(session, rc); -} - static void torture_auth_agent_identities_only(void **state) { struct torture_state *s = *state; ssh_session session = s->ssh.session; char bob_ssh_key[1024]; - struct passwd *pwd; + struct passwd *pwd = NULL; int rc; int identities_only = 1; - char *id; + char *id = NULL; pwd = getpwnam("bob"); assert_non_null(pwd); @@ -831,109 +746,6 @@ static void torture_auth_agent_identities_only_protected(void **state) assert_ssh_return_code(session, rc); } -static void torture_auth_cert(void **state) { - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - ssh_key privkey = NULL; - ssh_key cert = NULL; - char bob_ssh_key[1024]; - char bob_ssh_cert[2048]; - struct passwd *pwd; - int rc; - - pwd = getpwnam("bob"); - assert_non_null(pwd); - - snprintf(bob_ssh_key, - sizeof(bob_ssh_key), - "%s/.ssh_cert/id_rsa", - pwd->pw_dir); - snprintf(bob_ssh_cert, - sizeof(bob_ssh_cert), - "%s-cert.pub", - bob_ssh_key); - - /* cert has been signed for login as alice */ - rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); - assert_int_equal(rc, SSH_OK); - - rc = ssh_connect(session); - assert_int_equal(rc, SSH_OK); - - rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); - assert_int_equal(rc, SSH_OK); - - rc = ssh_pki_import_cert_file(bob_ssh_cert, &cert); - assert_int_equal(rc, SSH_OK); - - rc = ssh_pki_copy_cert_to_privkey(cert, privkey); - assert_int_equal(rc, SSH_OK); - - rc = ssh_userauth_try_publickey(session, NULL, cert); - assert_ssh_return_code(session, rc); - - rc = ssh_userauth_publickey(session, NULL, privkey); - assert_int_equal(rc, SSH_AUTH_SUCCESS); - - SSH_KEY_FREE(privkey); - SSH_KEY_FREE(cert); -} - -static void torture_auth_agent_cert(void **state) -{ -#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; - - /* Skip this test if in FIPS mode. - * - * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when - * using certificates. It always uses SHA1. - * - * This should be removed as soon as OpenSSH agent bug is fixed. - * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ - if (ssh_fips_mode()) { - skip(); - } else { - /* After the bug is solved, this also should be removed */ - rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, - "ssh-rsa-cert-v01@openssh.com"); - assert_int_equal(rc, SSH_OK); - } -#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ - - /* Setup loads a different key, tests are exactly the same. */ - torture_auth_agent(state); -} - -static void torture_auth_agent_cert_nonblocking(void **state) -{ -#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; - - /* Skip this test if in FIPS mode. - * - * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when - * using certificates. It always uses SHA1. - * - * This should be removed as soon as OpenSSH agent bug is fixed. - * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ - if (ssh_fips_mode()) { - skip(); - } else { - /* After the bug is solved, this also should be removed */ - rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, - "ssh-rsa-cert-v01@openssh.com"); - assert_int_equal(rc, SSH_OK); - } -#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ - - torture_auth_agent_nonblocking(state); -} - static void torture_auth_pubkey_types(void **state) { struct torture_state *s = *state; @@ -1396,15 +1208,6 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected, agent_setup, agent_teardown), - cmocka_unit_test_setup_teardown(torture_auth_cert, - pubkey_setup, - session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_agent_cert, - agent_cert_setup, - agent_teardown), - cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, - agent_cert_setup, - agent_teardown), cmocka_unit_test_setup_teardown(torture_auth_pubkey_types, pubkey_setup, session_teardown), diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c new file mode 100644 index 00000000..06507e1a --- /dev/null +++ b/tests/client/torture_auth_cert.c @@ -0,0 +1,338 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2023 by 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" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "torture_auth_common.c" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + 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; + int verbosity = torture_libssh_verbosity(); + const char *all_keytypes = NULL; + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("doe"); + 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); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Enable all hostkeys */ + all_keytypes = ssh_kex_get_supported_method(SSH_HOSTKEYS); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, all_keytypes); + assert_ssh_return_code(s->ssh.session, rc); + + 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 int cert_setup(void **state) +{ + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int agent_setup(void **state) +{ + struct torture_state *s = *state; + char ssh_agent_cmd[4096]; + char ssh_agent_sock[1024]; + char ssh_agent_pidfile[1024]; + char ssh_key_add[1024]; + struct passwd *pwd; + int rc; + + rc = cert_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(ssh_agent_sock, + sizeof(ssh_agent_sock), + "%s/agent.sock", + s->socket_dir); + + snprintf(ssh_agent_pidfile, + sizeof(ssh_agent_pidfile), + "%s/agent.pid", + s->socket_dir); + + /* Production ready code!!! */ + snprintf(ssh_agent_cmd, + sizeof(ssh_agent_cmd), + "eval `ssh-agent -a %s`; echo $SSH_AGENT_PID > %s", + ssh_agent_sock, ssh_agent_pidfile); + + /* run ssh-agent and ssh-add as the normal user */ + unsetenv("UID_WRAPPER_ROOT"); + + rc = system(ssh_agent_cmd); + assert_return_code(rc, errno); + + setenv("SSH_AUTH_SOCK", ssh_agent_sock, 1); + setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile, 1); + + snprintf(ssh_key_add, + sizeof(ssh_key_add), + "ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(ssh_key_add); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_cert_setup(void **state) +{ + char doe_alt_ssh_key[1024]; + struct passwd *pwd; + int rc; + + rc = agent_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + /* remove all keys, load alternative key + cert */ + snprintf(doe_alt_ssh_key, + sizeof(doe_alt_ssh_key), + "ssh-add -D && ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(doe_alt_ssh_key); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_teardown(void **state) +{ + const char *ssh_agent_pidfile; + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + ssh_agent_pidfile = getenv("TORTURE_SSH_AGENT_PIDFILE"); + assert_non_null(ssh_agent_pidfile); + + /* kill agent pid */ + rc = torture_terminate_process(ssh_agent_pidfile); + assert_return_code(rc, errno); + + unlink(ssh_agent_pidfile); + + unsetenv("TORTURE_SSH_AGENT_PIDFILE"); + unsetenv("SSH_AUTH_SOCK"); + + return 0; +} + +static void torture_auth_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + /* cert has been signed for login as alice */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_try_publickey(session, NULL, cert); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +static void torture_auth_agent_cert(void **state) +{ +#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Skip this test if in FIPS mode. + * + * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when + * using certificates. It always uses SHA1. + * + * This should be removed as soon as OpenSSH agent bug is fixed. + * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ + if (ssh_fips_mode()) { + skip(); + } else { + /* After the bug is solved, this also should be removed */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } +#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ + + /* Setup loads a different key, tests are exactly the same. */ + torture_auth_agent(state); +} + +static void torture_auth_agent_cert_nonblocking(void **state) +{ +#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Skip this test if in FIPS mode. + * + * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when + * using certificates. It always uses SHA1. + * + * This should be removed as soon as OpenSSH agent bug is fixed. + * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ + if (ssh_fips_mode()) { + skip(); + } else { + /* After the bug is solved, this also should be removed */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } +#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ + + torture_auth_agent_nonblocking(state); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_cert, + cert_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, + agent_cert_setup, + agent_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/tests/client/torture_auth_common.c b/tests/client/torture_auth_common.c new file mode 100644 index 00000000..15e407ab --- /dev/null +++ b/tests/client/torture_auth_common.c @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 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" + +#include "torture.h" +#include "libssh/libssh.h" + +/* agent_is_running */ +#include "agent.c" + +void torture_auth_agent(void **state); +void torture_auth_agent(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +void torture_auth_agent_nonblocking(void **state); +void torture_auth_agent_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} diff --git a/tests/etc/pam_matrix_passdb.in b/tests/etc/pam_matrix_passdb.in index c0aa54e5..9404bc0e 100644 --- a/tests/etc/pam_matrix_passdb.in +++ b/tests/etc/pam_matrix_passdb.in @@ -1,3 +1,4 @@ bob:secret:sshd alice:secret:sshd charlie:secret:sshd +doe:secret:sshd diff --git a/tests/etc/passwd.in b/tests/etc/passwd.in index 85e20c6d..cae364b7 100644 --- a/tests/etc/passwd.in +++ b/tests/etc/passwd.in @@ -1,6 +1,7 @@ bob:x:5000:9000:bob gecos:@HOMEDIR@/bob:/bin/sh alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh charlie:x:5002:9000:charlie gecos:@HOMEDIR@/charlie:/bin/sh +doe:x:5003:9000:doe gecos:@HOMEDIR@/doe:/bin/sh sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false root:x:65534:65532:root gecos:@HOMEDIR@:/bin/false diff --git a/tests/etc/shadow.in b/tests/etc/shadow.in index a0b2b9d6..0f03b149 100644 --- a/tests/etc/shadow.in +++ b/tests/etc/shadow.in @@ -1,3 +1,4 @@ alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: charlie:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +doe:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: From e179675f2c3ce2b1f15915f81792ba6009711abc Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 29 Sep 2023 15:38:32 +0200 Subject: [PATCH 053/795] tests: Verify the certs in default location are used for authentication Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth_cert.c | 344 +++++++++++++++++++++++++++++-- 1 file changed, 324 insertions(+), 20 deletions(-) diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c index 06507e1a..bb8c9f8f 100644 --- a/tests/client/torture_auth_cert.c +++ b/tests/client/torture_auth_cert.c @@ -53,7 +53,7 @@ static int session_setup(void **state) struct torture_state *s = *state; int verbosity = torture_libssh_verbosity(); const char *all_keytypes = NULL; - struct passwd *pwd; + struct passwd *pwd = NULL; bool b = false; int rc; @@ -77,31 +77,40 @@ static int session_setup(void **state) rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, all_keytypes); assert_ssh_return_code(s->ssh.session, rc); + /* certs have been signed for login as alice */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + return 0; } -static int session_teardown(void **state) +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options or from agent explicitly. */ +static int session_setup_ssh_dir(void **state) { struct torture_state *s = *state; + const char *no_home = "~/.no_ssh"; + int rc; - ssh_disconnect(s->ssh.session); - ssh_free(s->ssh.session); + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, &no_home); + assert_ssh_return_code(s->ssh.session, rc); return 0; } -static int cert_setup(void **state) +static int session_teardown(void **state) { - int rc; - - rc = session_setup(state); - if (rc != 0) { - return rc; - } + struct torture_state *s = *state; - /* Make sure we do not interfere with another ssh-agent */ - unsetenv("SSH_AUTH_SOCK"); - unsetenv("SSH_AGENT_PID"); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); return 0; } @@ -116,7 +125,7 @@ static int agent_setup(void **state) struct passwd *pwd; int rc; - rc = cert_setup(state); + rc = session_setup(state); if (rc != 0) { return rc; } @@ -234,10 +243,6 @@ static void torture_auth_cert(void **state) "%s-cert.pub", doe_ssh_key); - /* cert has been signed for login as alice */ - rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); - assert_int_equal(rc, SSH_OK); - rc = ssh_connect(session); assert_int_equal(rc, SSH_OK); @@ -260,6 +265,278 @@ static void torture_auth_cert(void **state) SSH_KEY_FREE(cert); } +static void torture_auth_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, cert); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +/* Same as torture_auth_cert, but without explicitly loading certificate to the + * private key file, keeping libssh to use default cert path when done with + * _auto(). */ +static void torture_auth_cert_default_non_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert_nonblocking, but without explicitly loading + * certificate to the private key file, keeping libssh to use default cert path + * when done with _auto(). + * Non-blocking version */ +static void torture_auth_cert_default_non_explicit_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities */ +static void torture_auth_auto_fail(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities + * Non-blocking version */ +static void torture_auth_auto_fail_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. */ +static void torture_auth_cert_options_private(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_private_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, also the certificate file + */ +static void torture_auth_cert_options_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s-cert.pub", + doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + static void torture_auth_agent_cert(void **state) { #if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) @@ -319,7 +596,34 @@ int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(torture_auth_cert, - cert_setup, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_auto_fail, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_auto_fail_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_private, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_private_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_cert, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_cert_nonblocking, + session_setup_ssh_dir, session_teardown), cmocka_unit_test_setup_teardown(torture_auth_agent_cert, agent_cert_setup, From 1a5ff139e2d59088bdd63465a0cc0548cec0b842 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 3 Oct 2023 14:57:36 +0200 Subject: [PATCH 054/795] tests: Cover failed logins with password/kbdint Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth.c | 128 +++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index 617afc69..d3e71d9e 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -499,7 +499,11 @@ static void torture_auth_autopubkey_nonblocking(void **state) { assert_int_equal(rc, SSH_AUTH_SUCCESS); } -static void torture_auth_kbdint(void **state) { +static void +torture_auth_kbdint(void **state, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -522,19 +526,35 @@ static void torture_auth_kbdint(void **state) { assert_int_equal(rc, SSH_AUTH_INFO); assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); - rc = ssh_userauth_kbdint_setanswer(session, 0, TORTURE_SSH_USER_BOB_PASSWORD); + rc = ssh_userauth_kbdint_setanswer(session, 0, password); assert_false(rc < 0); rc = ssh_userauth_kbdint(session, NULL, NULL); /* Sometimes, SSH server send an empty query at the end of exchange */ - if(rc == SSH_AUTH_INFO) { + if (rc == SSH_AUTH_INFO) { assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); rc = ssh_userauth_kbdint(session, NULL, NULL); } - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_good(void **state) +{ + torture_auth_kbdint(state, TORTURE_SSH_USER_BOB_PASSWORD, SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_bad(void **state) +{ + torture_auth_kbdint(state, "bad password stample", SSH_AUTH_DENIED); } -static void torture_auth_kbdint_nonblocking(void **state) { +static void +torture_auth_kbdint_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -545,9 +565,9 @@ static void torture_auth_kbdint_nonblocking(void **state) { rc = ssh_connect(session); assert_int_equal(rc, SSH_OK); - ssh_set_blocking(session,0); + ssh_set_blocking(session, 0); do { - rc = ssh_userauth_none(session, NULL); + rc = ssh_userauth_none(session, NULL); } while (rc == SSH_AUTH_AGAIN); /* This request should return a SSH_REQUEST_DENIED error */ @@ -562,23 +582,41 @@ static void torture_auth_kbdint_nonblocking(void **state) { } while (rc == SSH_AUTH_AGAIN); assert_int_equal(rc, SSH_AUTH_INFO); assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); - rc = ssh_userauth_kbdint_setanswer(session, 0, TORTURE_SSH_USER_BOB_PASSWORD); + rc = ssh_userauth_kbdint_setanswer(session, 0, password); assert_false(rc < 0); do { rc = ssh_userauth_kbdint(session, NULL, NULL); } while (rc == SSH_AUTH_AGAIN); /* Sometimes, SSH server send an empty query at the end of exchange */ - if(rc == SSH_AUTH_INFO) { + if (rc == SSH_AUTH_INFO) { assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); do { rc = ssh_userauth_kbdint(session, NULL, NULL); } while (rc == SSH_AUTH_AGAIN); } - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_nonblocking_good(void **state) +{ + torture_auth_kbdint_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); } -static void torture_auth_password(void **state) { +static void +torture_auth_kbdint_nonblocking_bad(void **state) +{ + torture_auth_kbdint_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); +} + +static void +torture_auth_password(void **state, const char *password, enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -597,11 +635,29 @@ static void torture_auth_password(void **state) { rc = ssh_userauth_list(session, NULL); assert_true(rc & SSH_AUTH_METHOD_PASSWORD); - rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + rc = ssh_userauth_password(session, NULL, password); + assert_int_equal(rc, res); } -static void torture_auth_password_nonblocking(void **state) { +static void +torture_auth_password_good(void **state) +{ + torture_auth_password(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_password_bad(void **state) +{ + torture_auth_password(state, "bad password stample", SSH_AUTH_DENIED); +} + +static void +torture_auth_password_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ struct torture_state *s = *state; ssh_session session = s->ssh.session; int rc; @@ -614,7 +670,7 @@ static void torture_auth_password_nonblocking(void **state) { ssh_set_blocking(session,0); do { - rc = ssh_userauth_none(session, NULL); + rc = ssh_userauth_none(session, NULL); } while (rc == SSH_AUTH_AGAIN); /* This request should return a SSH_REQUEST_DENIED error */ @@ -626,10 +682,26 @@ static void torture_auth_password_nonblocking(void **state) { assert_true(rc & SSH_AUTH_METHOD_PASSWORD); do { - rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); - } while(rc==SSH_AUTH_AGAIN); + rc = ssh_userauth_password(session, NULL, password); + } while (rc == SSH_AUTH_AGAIN); - assert_int_equal(rc, SSH_AUTH_SUCCESS); + assert_int_equal(rc, res); +} + +static void +torture_auth_password_nonblocking_good(void **state) +{ + torture_auth_password_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_password_nonblocking_bad(void **state) +{ + torture_auth_password_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); } static void torture_auth_agent_identities_only(void **state) @@ -1169,16 +1241,28 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_none_max_tries, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_password, + cmocka_unit_test_setup_teardown(torture_auth_password_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_good, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking, + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_good, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_kbdint, + cmocka_unit_test_setup_teardown(torture_auth_kbdint_bad, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking, + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_bad, session_setup, session_teardown), cmocka_unit_test_setup_teardown(torture_auth_pubkey, From bac71d1e9cd63b2bfd179e163cb3535f01800ea9 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 3 Oct 2023 16:02:44 +0200 Subject: [PATCH 055/795] tests: Convert key to PEM so we can not access public key directly There are several tests that depended in the past on the fact that we can not read public key from private encrypted keys. This is no longer the case for some time as the OpenSSH file format has public key in plaintext. This change just converts the same key into the PEM Format, which should still be opaque for us and trigger code paths that enforce opening of the accompanied public key file. Converted using the following command: $ ssh-keygen -m PEM -p -N secret -P secret -f tests/keys/id_rsa_protected Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/keys/id_rsa_protected | 58 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/tests/keys/id_rsa_protected b/tests/keys/id_rsa_protected index cdf5c2b8..034cb287 100644 --- a/tests/keys/id_rsa_protected +++ b/tests/keys/id_rsa_protected @@ -1,28 +1,30 @@ ------BEGIN OPENSSH PRIVATE KEY----- -b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBjmItEMS -YKDxy/7xvsZY+uAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz -1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0a -Kclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDB -UhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN -8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzy -TmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrvAAADwCLre52Jer2DTQ -TJi91b/xNm5NRuW9366ZdoOC5NdWtbQFk4YJmdImEDo8k1t3Re24rVNxLMQwHwZX4ZLISl -/e49RtSd6TDP44FkQF4NgtCjLUdmEWRTQj0mtENGto+wdLpL25HkmmI5WGrQU9SufVhhvj -TxKi6ediSXIXEA5bSrWNvUaw084TT3ZfP9g98/6wr9tAYL1jVfTFUabvZzCR6+wRVoJIVc -/+uN1bubj+IdOzYSm9Dhj4kUlK+KvI4GtouCzjuEZosjvn0ino3du1vgyT7SPdjmDxtIds -YI7YiB1Xy3QcWdWFk+SoXhDizf9pupo2r1+G50GoBuXg2ELdsKBLXtxQ9bh37JyAcLzagq -iVMCJjk3XMZvNXhdELRqLeWyhQ7U1BCtUBatbem0VsH6hQZ/pHReX2We8/GAUQkh4ZN8U2 -lkta9v5cb7XaBm49JjzIa3WeOS+tFHIUAWqd7MQ4f2FCTMhBssLAM7EJDOUXyo6938pa75 -+LvdLZRUycE8d/PWG9SuFWSe4CJJrRlBQqPEwx9OPtKNNKgsXIGVKAFLXe+nJ4z6RXTR3R -IGe0uaf8v9Jra5j22rq/dbQG1fP1fZNcCnIZQQo6olLaoyQmGCboC8CiCz1PNTsC1+r4pB -oaRiCx5/qLF6EXQ03mdEqL1L/R+KMDa2+Ncw2hCSRU3GBby4wXmSqFsboRy5uxJB5sK0Ut -sI3FW48k9zijiqVpdysRkalVVSQj8ymTG9LbjjEEmE7qxRf2dZCEnS/iPFUIu7iO9ISiOm -4ThpROBspNyHMXKFR6mKArJX1vIwjehlaLAXA3UMY9PEFRDrWQcbatGWj4f/L6e3Tq+n7a -t0djAgKlh40IvVL+Xf+Bsv8vUr7HAbKnOxpX69nEShiJqR5YWlEPXba+JCOjryE2ycoRB6 -d2d0SgDlB1M04uUmv2sy2Kw/CcSNHPLKGiYqqv8DAZ4GiKH2rI4oWvH9z2uRuQni98/Gw1 -1D5/QwJOHpqrUnVat4JXPBeTqiHYYtbTtqJLeIPX+Dsa6tbdjEOVgx2FkH3104xMwJyUKb -Ccip4AbWsTwfM4GVPnJE6WCBcXC5WR6AOzuEEDQjhyzLs5K7RVb7irfhHa4Vs1/2LvxnRT -dmTzdv/mhUNqS9RIPmFttfsSveDqY0P6WOn+K6FcCHQjpFJ3pK08glD+Sx4cbFv3lUQLfw -hsjL0P+p+M+gTqeJ1kb2z87fiS03mHMV15lmb7nzoqyeJLIukV1jidWdGxf0efnQfmUVfX -Wa+ehGaw== ------END OPENSSH PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,0B181CC88F75C33B7DEBE5C18B481F18 + +rYtUw8FhEv48JmNTm3i1TEqEgElC629iaMQu/YzRV5zL/n83HwMKbRpAZ31Cch2a +8thZRQ6YsL/56vr+fKKVgDF7y3wmStp5sVkOQXMeZ9D746ZEGcYGnYH8JQhibDDB +sTK2kQrmBERg7H8rOoNCzxxoK+VZl2Z+S+yLVq0//qxBfuluZwCdk9Tie69Cd/Dk +PeBjOVPnFCavCKCWpUs/So8VQq9jXG84hRltwC0htSTEq+xfgNtS64f63WL6gEnB +WZ5KSO1gyzKC5/YAB6LXPxIIVzfZYXiuOWV/t8DXZK/lvhqQ3gSyPZezSrX8wEMC +xQeX41etQGjCcgxWH41iPCNTuoIKo2t/BPlfLJilJotmUSnYOxDmkZbLabuyS+0p +WGtnEwFSrxQosx6u9GBHX94Ikex0bf00KzNpKExzAIRqTdesaviJ1QX/pRsvT/Xp +TtH2aWV5kYNc+B+BrCQU7mlx/eEtXR2H5zJQxLSrTVKb1vUIHytufnPePk2BkcQ2 +CTE1xT+ZkUaY1WiCBxWgVTflL5FY9E6BerKEGVSfloso8tGCgsoO/Fch0Ho5/bXp +T+3nQEY780KduKJ8xCJJDQgD8GbjNR6sCtcPrewqEsgrpAbJUKyXhU7klGC09zzI +/JnNmdd10w2l/5A92GGrCgXnTYb8/w9J/qa6qyAAYU9/8rPo7ErGb7mKclmzz63j +cksImoExfrr9CIr7wjrXFO0OoupmMegNOZtgwsN7i0FI8vWYc6a3IaFWSWfE29Ux +rw9TK9L9pDvhCqS/WjW86S25muqnTSMQ/bhmiPw8z8tOjdi2YRqNcU2TyWoB2Mct +W+w9G5dSukMwkXQ2RNjDo2GfuXLXpUe5zCVixI2wxYGvIqTGkDZn/u1Jdxy1IxNc +qEsEZAOCVnJU1cQpB9ENsyrRUIsdQVWNQSvsUZz2XSELULwIFTcCTHr2PAJ5xzZ6 +VQy3DGEpZf7+yGACoi8LY8f5Ve5C9NciyA4/C/uvOUd7PhAf4g41mKw8+bAr8NFt +ubeXTo0iI29FkmmebfM1sRBHvomGT7qYsHBW2pgqBrm3X9kFcQ9EFhr6S2ULMcIn +4iX1mbqvC0c1CUmZakkNg94FQp2zbUclAuDkg3BTA0gwbyudvx0ccBmzQ43/6AJ5 +xz1hrfusX5Vcjz6+i5WHJDK/mlUDwTV5GAhcmar9eEcFXJEosD+mrAalflz3Vc2X +5A9plGfKkaFdth8YUGjLr+O2O5ggkDpCMbjYo4HQ6/dslYvqvnavJYrRKrEZbtvj +8fR5E11tPrK1aKzPHO0VLKf4UHs57JNqicSlYGy78FSCPG4d17KQlFyzbXsfbsvp +9EQK4N2jwRNZAOHuTuoqQ8TNzDahdlmbBS2Akd3rVV9H1/eNeN3r6Demww+yixoy +uPhjofn0P28eH7Gqiyhh20QYYqG7aky9IYMPnIBtA1hJp9MtMa1m8aHGxxZrUigj +S62Q34JzA8A6Rwc2kTHRzXG2o6oQ3vCQfy0JGlmDlG2yofcn7YgrMCv+srTniuiA +YBnOeic5cllYnDB9bpF2kufJT6CigoxP18HIw+jhYabuOTHO67MYf2En+is8vlQS +-----END RSA PRIVATE KEY----- From 1fcaac9a35b1e8e097a72e685ab517cb057d9764 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 3 Oct 2023 17:44:40 +0200 Subject: [PATCH 056/795] tests: Implement more negative auth tests Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth.c | 12 ++++++++++++ tests/client/torture_auth_common.c | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index d3e71d9e..fa58c14a 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -296,9 +296,21 @@ static void torture_auth_pubkey(void **state) { rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); assert_int_equal(rc, SSH_OK); + /* negative tests */ + rc = ssh_userauth_try_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_try_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_try_publickey(session, NULL, privkey); assert_int_equal(rc, SSH_AUTH_SUCCESS); + /* negative tests */ + rc = ssh_userauth_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_publickey(session, NULL, privkey); assert_int_equal(rc, SSH_AUTH_SUCCESS); diff --git a/tests/client/torture_auth_common.c b/tests/client/torture_auth_common.c index 15e407ab..8a4f2854 100644 --- a/tests/client/torture_auth_common.c +++ b/tests/client/torture_auth_common.c @@ -52,6 +52,10 @@ void torture_auth_agent(void **state) rc = ssh_userauth_list(session, NULL); assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + /* negative test case */ + rc = ssh_userauth_agent(NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_agent(session, NULL); assert_ssh_return_code(session, rc); } From 0ff6adeb80f927ae49e981146745b08bb13e4d15 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 3 Oct 2023 17:45:19 +0200 Subject: [PATCH 057/795] tests: Implement more certificate tests Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth.c | 6 +- tests/client/torture_auth_cert.c | 431 +++++++++++++++++++++++++++++++ 2 files changed, 434 insertions(+), 3 deletions(-) diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index fa58c14a..0fcc2642 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -768,7 +768,7 @@ static void torture_auth_agent_identities_only(void **state) rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); assert_int_equal(rc, SSH_OK); - /* Should succeed as key now in config */ + /* Should succeed as key now in config/options */ rc = ssh_userauth_agent(session, NULL); assert_ssh_return_code(session, rc); } @@ -781,7 +781,7 @@ static void torture_auth_agent_identities_only_protected(void **state) struct passwd *pwd; int rc; int identities_only = 1; - char *id; + char *id = NULL; pwd = getpwnam("bob"); assert_non_null(pwd); @@ -1024,7 +1024,7 @@ static void torture_auth_pubkey_types_ecdsa_nonblocking(void **state) ssh_set_blocking(session, 0); do { - rc = ssh_userauth_none(session, NULL); + rc = ssh_userauth_none(session, NULL); } while (rc == SSH_AUTH_AGAIN); /* This request should return a SSH_REQUEST_DENIED error */ diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c index bb8c9f8f..958c8945 100644 --- a/tests/client/torture_auth_cert.c +++ b/tests/client/torture_auth_cert.c @@ -30,6 +30,7 @@ #include "libssh/session.h" #include +#include #include #include @@ -592,6 +593,418 @@ static void torture_auth_agent_cert_nonblocking(void **state) torture_auth_agent_nonblocking(state); } +static void +torture_auth_agent_cert_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static int agent_cert_setup_explicit(void **state) +{ + char orig_doe_ssh_key[1024]; + char doe_ssh_key[1024]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd ; + int rc; + + agent_cert_setup(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(orig_doe_ssh_key, + sizeof(orig_doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/my_rsa", + pwd->pw_dir); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(orig_doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(doe_ssh_key, keydata); + + return 0; +} + +static void +torture_auth_agent_cert_identities_only_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/my_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_identities_only_nonblocking_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, + sizeof(doe_ssh_key), + "%s/.ssh/my_rsa", + pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_only_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_only_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + int identities_only = 1; + char *id = NULL; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -631,6 +1044,24 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, agent_cert_setup, agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only_nonblocking_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_only_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_only_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), }; ssh_init(); From f41f0492e47af3921b2981851f1b7d0892cc7277 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 3 Oct 2023 15:33:57 +0200 Subject: [PATCH 058/795] Comments Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth.c | 6 ++++++ tests/torture.c | 1 + 2 files changed, 7 insertions(+) diff --git a/tests/client/torture_auth.c b/tests/client/torture_auth.c index 0fcc2642..77b7bd1f 100644 --- a/tests/client/torture_auth.c +++ b/tests/client/torture_auth.c @@ -716,6 +716,12 @@ torture_auth_password_nonblocking_bad(void **state) SSH_AUTH_DENIED); } +/* TODO cover the case: + * * when there is accompanying certificate (identities only + agent) + * * export private key to public key during _auto() authentication. + * this needs to be a encrypted private key in PEM format without + * accompanying public key. + */ static void torture_auth_agent_identities_only(void **state) { struct torture_state *s = *state; diff --git a/tests/torture.c b/tests/torture.c index 2641b2bf..3072edf3 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -76,6 +76,7 @@ static const char *pattern = NULL; #ifndef _WIN32 +/* TODO missing code coverage */ static int _torture_auth_kbdint(ssh_session session, const char *password) { const char *prompt; From c925907917ed911920b6a7611e5c72a595f58717 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 3 Oct 2023 18:04:03 +0200 Subject: [PATCH 059/795] tests: Move the workaround to separate function Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth_cert.c | 43 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c index 958c8945..18766b93 100644 --- a/tests/client/torture_auth_cert.c +++ b/tests/client/torture_auth_cert.c @@ -538,7 +538,7 @@ static void torture_auth_cert_options_cert_nonblocking(void **state) assert_int_equal(rc, SSH_AUTH_SUCCESS); } -static void torture_auth_agent_cert(void **state) +static void workaround_old_openssh_bug(void **state) { #if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) struct torture_state *s = *state; @@ -560,7 +560,14 @@ static void torture_auth_agent_cert(void **state) "ssh-rsa-cert-v01@openssh.com"); assert_int_equal(rc, SSH_OK); } +#else + (void)state; #endif /* OPENSSH_VERSION_MAJOR < 8.1 */ +} + +static void torture_auth_agent_cert(void **state) +{ + workaround_old_openssh_bug(state); /* Setup loads a different key, tests are exactly the same. */ torture_auth_agent(state); @@ -568,27 +575,7 @@ static void torture_auth_agent_cert(void **state) static void torture_auth_agent_cert_nonblocking(void **state) { -#if OPENSSH_VERSION_MAJOR < 8 || (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) - struct torture_state *s = *state; - ssh_session session = s->ssh.session; - int rc; - - /* Skip this test if in FIPS mode. - * - * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when - * using certificates. It always uses SHA1. - * - * This should be removed as soon as OpenSSH agent bug is fixed. - * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ - if (ssh_fips_mode()) { - skip(); - } else { - /* After the bug is solved, this also should be removed */ - rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, - "ssh-rsa-cert-v01@openssh.com"); - assert_int_equal(rc, SSH_OK); - } -#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ + workaround_old_openssh_bug(state); torture_auth_agent_nonblocking(state); } @@ -604,6 +591,8 @@ torture_auth_agent_cert_identities_only(void **state) char *id = NULL; int rc; + workaround_old_openssh_bug(state); + pwd = getpwnam("doe"); assert_non_null(pwd); @@ -661,6 +650,8 @@ torture_auth_agent_cert_identities_only_nonblocking(void **state) char *id = NULL; int rc; + workaround_old_openssh_bug(state); + pwd = getpwnam("doe"); assert_non_null(pwd); @@ -763,6 +754,8 @@ torture_auth_agent_cert_identities_only_explicit(void **state) char *id = NULL; int rc; + workaround_old_openssh_bug(state); + pwd = getpwnam("doe"); assert_non_null(pwd); @@ -826,6 +819,8 @@ torture_auth_agent_cert_identities_only_nonblocking_explicit(void **state) char *id = NULL; int rc; + workaround_old_openssh_bug(state); + pwd = getpwnam("doe"); assert_non_null(pwd); @@ -896,6 +891,8 @@ torture_auth_agent_cert_only_identities_only(void **state) char *id = NULL; int rc; + workaround_old_openssh_bug(state); + pwd = getpwnam("doe"); assert_non_null(pwd); @@ -952,6 +949,8 @@ torture_auth_agent_cert_only_identities_only_nonblocking(void **state) char *id = NULL; int rc; + workaround_old_openssh_bug(state); + pwd = getpwnam("doe"); assert_non_null(pwd); From d22194f0b119f7415723a13b531bbe14226c14dd Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 2 Aug 2023 17:17:07 +0200 Subject: [PATCH 060/795] packet_cb: Reformat remaining functions Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/packet_cb.c | 269 ++++++++++++++++++++++++++---------------------- 1 file changed, 144 insertions(+), 125 deletions(-) diff --git a/src/packet_cb.c b/src/packet_cb.c index c3b36700..ee32dcfd 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -45,43 +45,50 @@ * * @brief Handle a SSH_DISCONNECT packet. */ -SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ - int rc; - uint32_t code = 0; - char *error = NULL; - ssh_string error_s; - (void)user; - (void)type; - - rc = ssh_buffer_get_u32(packet, &code); - if (rc != 0) { - code = ntohl(code); - } - - error_s = ssh_buffer_get_ssh_string(packet); - if (error_s != NULL) { - error = ssh_string_to_char(error_s); - SSH_STRING_FREE(error_s); - } - - if (error != NULL) { - session->peer_discon_msg = strdup(error); - } - - SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", - code, error != NULL ? error : "no error"); - ssh_set_error(session, SSH_FATAL, - "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", - code, error != NULL ? error : "no error"); - SAFE_FREE(error); - - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state = SSH_SESSION_STATE_ERROR; - /* correctly handle disconnect during authorization */ - session->auth.state = SSH_AUTH_STATE_FAILED; - /* TODO: handle a graceful disconnect */ - return SSH_PACKET_USED; +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) +{ + int rc; + uint32_t code = 0; + char *error = NULL; + ssh_string error_s = NULL; + + (void)user; + (void)type; + + rc = ssh_buffer_get_u32(packet, &code); + if (rc != 0) { + code = ntohl(code); + } + + error_s = ssh_buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + SSH_STRING_FREE(error_s); + } + + if (error != NULL) { + session->peer_discon_msg = strdup(error); + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; + /* correctly handle disconnect during authorization */ + session->auth.state = SSH_AUTH_STATE_FAILED; + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; } /** @@ -89,102 +96,113 @@ SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback){ * * @brief Handle a SSH_IGNORE and SSH_DEBUG packet. */ -SSH_PACKET_CALLBACK(ssh_packet_ignore_callback){ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback) +{ (void)session; /* unused */ - (void)user; - (void)type; - (void)packet; - SSH_LOG(SSH_LOG_DEBUG,"Received %s packet",type==SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); - /* TODO: handle a graceful disconnect */ - return SSH_PACKET_USED; + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG, + "Received %s packet", + type == SSH2_MSG_IGNORE ? "SSH_MSG_IGNORE" : "SSH_MSG_DEBUG"); + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; } -SSH_PACKET_CALLBACK(ssh_packet_newkeys){ - ssh_string sig_blob = NULL; - ssh_signature sig = NULL; - int rc; - (void)packet; - (void)user; - (void)type; - SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); - - if (session->session_state != SSH_SESSION_STATE_DH || - session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { - ssh_set_error(session, - SSH_FATAL, - "ssh_packet_newkeys called in wrong state : %d:%d", - session->session_state,session->dh_handshake_state); - goto error; - } - - if(session->server){ - /* server things are done in server.c */ - session->dh_handshake_state=DH_STATE_FINISHED; - } else { - ssh_key server_key; - - /* client */ - - /* Verify the host's signature. FIXME do it sooner */ - sig_blob = session->next_crypto->dh_server_signature; - session->next_crypto->dh_server_signature = NULL; - - /* get the server public key */ - server_key = ssh_dh_get_next_server_publickey(session); - if (server_key == NULL) { - goto error; - } +SSH_PACKET_CALLBACK(ssh_packet_newkeys) +{ + ssh_string sig_blob = NULL; + ssh_signature sig = NULL; + int rc; - rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); - if (rc != SSH_OK) { + (void)packet; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); + + if (session->session_state != SSH_SESSION_STATE_DH || + session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + ssh_set_error(session, + SSH_FATAL, + "ssh_packet_newkeys called in wrong state : %d:%d", + session->session_state, + session->dh_handshake_state); goto error; } - /* Check if signature from server matches user preferences */ - if (session->opts.wanted_methods[SSH_HOSTKEYS]) { - if (!ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], - sig->type_c)) { + if (session->server) { + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { + ssh_key server_key = NULL; + + /* client */ + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + server_key = ssh_dh_get_next_server_publickey(session); + if (server_key == NULL) { + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc != SSH_OK) { + goto error; + } + + /* Check if signature from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + rc = ssh_match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + sig->type_c); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + sig->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + goto error; + } + } + + rc = ssh_pki_signature_verify(session, + sig, + server_key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { ssh_set_error(session, SSH_FATAL, - "Public key from server (%s) doesn't match user " - "preference (%s)", - sig->type_c, - session->opts.wanted_methods[SSH_HOSTKEYS]); + "Failed to verify server hostkey signature"); goto error; } - } + SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); - rc = ssh_pki_signature_verify(session, - sig, - server_key, - session->next_crypto->secret_hash, - session->next_crypto->digest_len); - SSH_SIGNATURE_FREE(sig); - if (rc == SSH_ERROR) { - ssh_set_error(session, - SSH_FATAL, - "Failed to verify server hostkey signature"); - goto error; + /* When receiving this packet, we switch on the incoming crypto. */ + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } } - SSH_LOG(SSH_LOG_DEBUG,"Signature verified and valid"); + session->dh_handshake_state = DH_STATE_FINISHED; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; - /* When receiving this packet, we switch on the incoming crypto. */ - rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); - if (rc != SSH_OK) { - goto error; - } - } - session->dh_handshake_state = DH_STATE_FINISHED; - session->ssh_connection_callback(session); - return SSH_PACKET_USED; error: - SSH_SIGNATURE_FREE(sig); - ssh_string_burn(sig_blob); - SSH_STRING_FREE(sig_blob); - session->session_state = SSH_SESSION_STATE_ERROR; - return SSH_PACKET_USED; + SSH_SIGNATURE_FREE(sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; } /** @@ -192,16 +210,16 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys){ * @brief handles a SSH_SERVICE_ACCEPT packet * */ -SSH_PACKET_CALLBACK(ssh_packet_service_accept){ - (void)packet; - (void)type; - (void)user; +SSH_PACKET_CALLBACK(ssh_packet_service_accept) +{ + (void)packet; + (void)type; + (void)user; session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED; - SSH_LOG(SSH_LOG_PACKET, - "Received SSH_MSG_SERVICE_ACCEPT"); + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT"); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } /** @@ -214,6 +232,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info) int rc; uint32_t nr_extensions = 0; uint32_t i; + (void)type; (void)user; From ad458c4633e7b129e32cd371a75ace275abe72a7 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 15 Aug 2023 12:08:54 +0200 Subject: [PATCH 061/795] tests: Do not use assert_true Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/unittests/torture_pki_ed25519.c | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c index cf83ed0b..fe56c3f3 100644 --- a/tests/unittests/torture_pki_ed25519.c +++ b/tests/unittests/torture_pki_ed25519.c @@ -247,10 +247,10 @@ static void torture_pki_ed25519_import_export_privkey_base64(void **state) assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ED25519); + assert_int_equal(type, SSH_KEYTYPE_ED25519); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); rc = ssh_pki_export_privkey_base64(key, passphrase, @@ -270,10 +270,10 @@ static void torture_pki_ed25519_import_export_privkey_base64(void **state) assert_non_null(key); type = ssh_key_type(key); - assert_true(type == SSH_KEYTYPE_ED25519); + assert_int_equal(type, SSH_KEYTYPE_ED25519); rc = ssh_key_is_private(key); - assert_true(rc == 1); + assert_int_equal(rc, 1); SSH_STRING_FREE_CHAR(b64_key); SSH_KEY_FREE(key); @@ -521,7 +521,7 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(origkey); unlink(LIBSSH_ED25519_TESTKEY); @@ -531,18 +531,18 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, LIBSSH_ED25519_TESTKEY); - assert_true(rc == 0); + assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, NULL, NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); unlink(LIBSSH_ED25519_TESTKEY); SSH_KEY_FREE(privkey); @@ -552,7 +552,7 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, LIBSSH_ED25519_TESTKEY); - assert_true(rc == 0); + assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, NULL, @@ -560,18 +560,18 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, &privkey); /* opening without passphrase should fail */ - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, torture_get_testkey_passphrase(), NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); unlink(LIBSSH_ED25519_TESTKEY); SSH_KEY_FREE(origkey); @@ -583,7 +583,7 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, &origkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(origkey); unlink(LIBSSH_ED25519_TESTKEY_PASSPHRASE); @@ -592,7 +592,7 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, LIBSSH_ED25519_TESTKEY_PASSPHRASE); - assert_true(rc == 0); + assert_int_equal(rc, 0); /* Test with invalid passphrase */ rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, @@ -600,18 +600,18 @@ static void torture_pki_ed25519_write_privkey(void **state) NULL, NULL, &privkey); - assert_true(rc == SSH_ERROR); + assert_int_equal(rc, SSH_ERROR); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, torture_get_testkey_passphrase(), NULL, NULL, &privkey); - assert_true(rc == 0); + assert_int_equal(rc, 0); assert_non_null(privkey); rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); - assert_true(rc == 0); + assert_int_equal(rc, 0); SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); From 0cfd4d8ec71b6f2344918d23767341531a9c9e5c Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 11 Aug 2023 16:21:10 +0200 Subject: [PATCH 062/795] examples: Reformat and fix typos in keygen Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- examples/keygen.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/keygen.c b/examples/keygen.c index 2ab00113..99f8c98c 100644 --- a/examples/keygen.c +++ b/examples/keygen.c @@ -27,14 +27,14 @@ int main(void) rv = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key); if (rv != SSH_OK) { fprintf(stderr, "Failed to generate private key"); - return -1; + return -1; } - /* Write it to a file testkey in the current dirrectory */ + /* Write it to a file testkey in the current directory */ rv = ssh_pki_export_privkey_file(key, NULL, NULL, NULL, "testkey"); if (rv != SSH_OK) { fprintf(stderr, "Failed to write private key file"); - return -1; + return -1; } return 0; From 04acf9a8ab4fcadfdd240736b3f2c501a13ee98e Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 16 Aug 2023 13:20:46 +0200 Subject: [PATCH 063/795] pki: Unbreak key comparison of Ed25519 keys imported from PEM or OpenSSH container Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/pki_crypto.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/pki_crypto.c b/src/pki_crypto.c index dae1686b..08e59533 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -1003,6 +1003,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, EC_KEY *ecdsa = NULL; #endif /* OPENSSL_VERSION_NUMBER */ uint8_t *ed25519 = NULL; + uint8_t *ed25519_pubkey = NULL; ssh_key key = NULL; enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; EVP_PKEY *pkey = NULL; @@ -1092,6 +1093,22 @@ ssh_key pki_private_key_from_base64(const char *b64_key, ERR_error_string(ERR_get_error(), NULL)); goto fail; } + + /* length matches the private key length */ + ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + evp_rc = EVP_PKEY_get_raw_public_key(pkey, (uint8_t *)ed25519_pubkey, + &key_len); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } type = SSH_KEYTYPE_ED25519; } @@ -1113,6 +1130,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; key->key = pkey; key->ed25519_privkey = ed25519; + key->ed25519_pubkey = ed25519_pubkey; #ifdef HAVE_OPENSSL_ECC if (is_ecdsa_key_type(key->type)) { #if OPENSSL_VERSION_NUMBER < 0x30000000L @@ -1128,6 +1146,7 @@ ssh_key pki_private_key_from_base64(const char *b64_key, EVP_PKEY_free(pkey); ssh_key_free(key); SAFE_FREE(ed25519); + SAFE_FREE(ed25519_pubkey); return NULL; } From 63be7f76510b7cf2e91ad75f8e82b134c4e84a76 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 24 Aug 2023 11:08:12 +0200 Subject: [PATCH 064/795] libcrypto: Report errors from OpenSSL key import and export Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/libcrypto.c | 3 +++ src/pki_crypto.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/libcrypto.c b/src/libcrypto.c index 2e635e66..d30c1ac5 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -1481,6 +1481,9 @@ int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, rc = EVP_PKEY_fromdata(ctx, pkey, selection, params); if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); OSSL_PARAM_free(params); EVP_PKEY_CTX_free(ctx); return -1; diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 08e59533..d21a76dd 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -967,6 +967,9 @@ ssh_string pki_private_key_to_pem(const ssh_key key, pkey = NULL; if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to write private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); goto err; } From baa773d1cd6838af33fedcd65ddbb4e46e2b06c0 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 24 Aug 2023 11:11:37 +0200 Subject: [PATCH 065/795] pki: Calculate missing CRT parameters when building RSA Key The OpenSSL claims that these parameters are not mandatory and just speed up calculations. But in reality, if they are missing, we can not export this key into PEM files or if we export them, they are not readable/valid. This was discussed in the following OpenSSL issue even with some proposed fix, but it will take time before this will be implemented so in the meantime, we back down to calculating the parameters manually as done in OpenSSH. https://github.com/openssl/openssl/issues/21826 Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/pki_crypto.c | 84 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/src/pki_crypto.c b/src/pki_crypto.c index d21a76dd..4b60a709 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -1157,12 +1157,17 @@ int pki_privkey_build_rsa(ssh_key key, ssh_string n, ssh_string e, ssh_string d, - UNUSED_PARAM(ssh_string iqmp), + ssh_string iqmp, ssh_string p, ssh_string q) { int rc; - BIGNUM *be, *bn, *bd/*, *biqmp*/, *bp, *bq; + BIGNUM *be = NULL, *bn = NULL, *bd = NULL; + BIGNUM *biqmp = NULL, *bp = NULL, *bq = NULL; + BIGNUM *aux = NULL, *d_consttime = NULL; + BIGNUM *bdmq1 = NULL, *bdmp1 = NULL; + BN_CTX *ctx = NULL; + #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); if (param_bld == NULL) { @@ -1178,7 +1183,7 @@ int pki_privkey_build_rsa(ssh_key key, bn = ssh_make_string_bn(n); be = ssh_make_string_bn(e); bd = ssh_make_string_bn(d); - /*biqmp = ssh_make_string_bn(iqmp);*/ + biqmp = ssh_make_string_bn(iqmp); bp = ssh_make_string_bn(p); bq = ssh_make_string_bn(q); if (be == NULL || bn == NULL || bd == NULL || @@ -1187,6 +1192,33 @@ int pki_privkey_build_rsa(ssh_key key, goto fail; } + /* Calculate remaining CRT parameters for OpenSSL to be happy + * taken from OpenSSH */ + if ((ctx = BN_CTX_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + if ((aux = BN_new()) == NULL || + (bdmq1 = BN_new()) == NULL || + (bdmp1 = BN_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + if ((d_consttime = BN_dup(bd)) == NULL) { + rc = SSH_ERROR; + goto fail; + } + BN_set_flags(aux, BN_FLG_CONSTTIME); + BN_set_flags(d_consttime, BN_FLG_CONSTTIME); + + if ((BN_sub(aux, bq, BN_value_one()) == 0) || + (BN_mod(bdmq1, d_consttime, aux, ctx) == 0) || + (BN_sub(aux, bp, BN_value_one()) == 0) || + (BN_mod(bdmp1, d_consttime, aux, ctx) == 0)) { + rc = SSH_ERROR; + goto fail; + } + #if OPENSSL_VERSION_NUMBER < 0x30000000L /* Memory management of be, bn and bd is transferred to RSA object */ rc = RSA_set0_key(key_rsa, bn, be, bd); @@ -1203,9 +1235,15 @@ int pki_privkey_build_rsa(ssh_key key, /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA * operations are much faster when these values are available. * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html + * And OpenSSL fails to export these keys to PEM if these are missing: + * https://github.com/openssl/openssl/issues/21826 */ - /* RSA_set0_crt_params(key->rsa, biqmp, NULL, NULL); - TODO calculate missing crt_params */ + rc = RSA_set0_crt_params(key_rsa, bdmp1, bdmq1, biqmp); + if (rc == 0) { + goto fail; + } + bignum_safe_free(aux); + bignum_safe_free(d_consttime); key->key = EVP_PKEY_new(); if (key->key == NULL) { @@ -1239,6 +1277,36 @@ int pki_privkey_build_rsa(ssh_key key, goto fail; } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); if (rc != SSH_OK) { rc = SSH_ERROR; @@ -1264,7 +1332,13 @@ int pki_privkey_build_rsa(ssh_key key, bignum_safe_free(bd); bignum_safe_free(bp); bignum_safe_free(bq); + bignum_safe_free(biqmp); + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + bignum_safe_free(bdmp1); + bignum_safe_free(bdmq1); + BN_CTX_free(ctx); return rc; #endif /* OPENSSL_VERSION_NUMBER */ } From 30d5ab431373d5d8b5b026dacce19eac9f0e7f06 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 24 Aug 2023 11:14:15 +0200 Subject: [PATCH 066/795] pki: Fix indentation Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/pki.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pki.c b/src/pki.c index b07a5f67..f43f835a 100644 --- a/src/pki.c +++ b/src/pki.c @@ -1163,8 +1163,9 @@ int pki_import_privkey_buffer(enum ssh_keytypes_e type, ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); - ssh_log_hexdump("iqmp", ssh_string_data(iqmp), - ssh_string_len(iqmp)); + ssh_log_hexdump("iqmp", + ssh_string_data(iqmp), + ssh_string_len(iqmp)); ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); #endif /* DEBUG_CRYPTO */ From 3fa28aaf49ec8b1c2b7eca9a850ed90cd42ea1c3 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 11 Aug 2023 16:22:01 +0200 Subject: [PATCH 067/795] pki: New API functions exporting (also ed25519 keys in different formats) This also adds an fallback to OpenSSH file format in non-OpenSSL backends and OpenSSH-compatible private key export for writing OpenSSH private keys. Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/libssh.h | 20 +++ include/libssh/pki.h | 4 + include/libssh/pki_priv.h | 4 +- src/libssh.map | 2 + src/pki.c | 208 ++++++++++++++++++++++++++----- src/pki_container_openssh.c | 66 +++------- src/pki_crypto.c | 236 ++++++++++++++++++++++++++++++------ src/pki_ed25519_common.c | 28 +++++ src/pki_gcrypt.c | 147 ++++++++++++++++++---- src/pki_mbedcrypto.c | 175 +++++++++++++++++++++++--- 10 files changed, 727 insertions(+), 163 deletions(-) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 5348370e..c8107706 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -686,6 +686,12 @@ typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, /** @} */ +enum ssh_file_format_e { + SSH_FILE_FORMAT_DEFAULT = 0, + SSH_FILE_FORMAT_OPENSSH, + SSH_FILE_FORMAT_PEM, +}; + LIBSSH_API ssh_key ssh_key_new(void); #define SSH_KEY_FREE(x) \ do { if ((x) != NULL) { ssh_key_free(x); x = NULL; } } while(0) @@ -712,6 +718,13 @@ LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data, char **b64_key); +LIBSSH_API int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format); LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, const char *passphrase, ssh_auth_callback auth_fn, @@ -722,6 +735,13 @@ LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data, const char *filename); +LIBSSH_API int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format); LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, ssh_key privkey); diff --git a/include/libssh/pki.h b/include/libssh/pki.h index 575d442d..efb9bdbf 100644 --- a/include/libssh/pki.h +++ b/include/libssh/pki.h @@ -153,6 +153,10 @@ int ssh_pki_import_pubkey_blob(const ssh_string key_blob, int ssh_pki_import_cert_blob(const ssh_string cert_blob, ssh_key *pkey); +/* SSH Private Key Functions */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob); + /* SSH Signing Functions */ ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, diff --git a/include/libssh/pki_priv.h b/include/libssh/pki_priv.h index c63e129d..2061ebd7 100644 --- a/include/libssh/pki_priv.h +++ b/include/libssh/pki_priv.h @@ -92,7 +92,7 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n); int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e); -ssh_string pki_publickey_to_blob(const ssh_key key); +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type); /* SSH Private Key Functions */ int pki_privkey_build_rsa(ssh_key key, @@ -106,7 +106,6 @@ int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp); -ssh_string pki_publickey_to_blob(const ssh_key key); /* SSH Signature Functions */ ssh_signature pki_sign_data(const ssh_key privkey, @@ -143,6 +142,7 @@ int pki_ed25519_key_cmp(const ssh_key k1, enum ssh_keycmp_e what); int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key); int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key); +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey); ssh_string pki_ed25519_signature_to_blob(ssh_signature sig); int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob); int pki_privkey_build_ed25519(ssh_key key, diff --git a/src/libssh.map b/src/libssh.map index 5797261a..e0d310dd 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -469,5 +469,7 @@ LIBSSH_AFTER_4_9_0 sftp_aio_free; sftp_aio_wait_read; sftp_aio_wait_write; + ssh_pki_export_privkey_base64_format; + ssh_pki_export_privkey_file_format; } LIBSSH_4_9_0; diff --git a/src/pki.c b/src/pki.c index f43f835a..b5d423a2 100644 --- a/src/pki.c +++ b/src/pki.c @@ -836,9 +836,10 @@ int ssh_pki_import_privkey_base64(const char *b64_key, return SSH_OK; } + + /** - * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for - * keytype ssh-ed25519 + * @brief Convert a private key to a base64 encoded key in given format * * @param[in] privkey The private key to export. * @@ -852,15 +853,19 @@ int ssh_pki_import_privkey_base64(const char *b64_key, * @param[out] b64_key A pointer to store the allocated base64 encoded key. You * need to free the buffer using ssh_string_from_char(). * + * @param[in] format The file format (OpenSSH, PEM, or default) + * * @return SSH_OK on success, SSH_ERROR on error. * * @see ssh_string_free_char() */ -int ssh_pki_export_privkey_base64(const ssh_key privkey, - const char *passphrase, - ssh_auth_callback auth_fn, - void *auth_data, - char **b64_key) +int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format) { ssh_string blob = NULL; char *b64 = NULL; @@ -869,16 +874,34 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, return SSH_ERROR; } - if (privkey->type == SSH_KEYTYPE_ED25519){ - blob = ssh_pki_openssh_privkey_export(privkey, - passphrase, - auth_fn, - auth_data); - } else { + /* The PEM export is supported only with OpenSSL. We fall back to + * OpenSSH key format elsewhere */ + if (format == SSH_FILE_FORMAT_DEFAULT) { +#ifdef HAVE_LIBCRYPTO + if (privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; + } else { +#else + if (1) { +#endif /* HAVE_LIBCRYPTO */ + format = SSH_FILE_FORMAT_OPENSSH; + } + } + + switch (format) { + case SSH_FILE_FORMAT_DEFAULT: + case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); + break; + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; } if (blob == NULL) { return SSH_ERROR; @@ -895,6 +918,42 @@ int ssh_pki_export_privkey_base64(const ssh_key privkey, return SSH_OK; } + /** + * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key) +{ + return ssh_pki_export_privkey_base64_format(privkey, + passphrase, + auth_fn, + auth_data, + b64_key, + SSH_FILE_FORMAT_DEFAULT); +} + + + /** * @brief Import a private key from a file or a PKCS #11 device. * @@ -1002,8 +1061,7 @@ int ssh_pki_import_privkey_file(const char *filename, } /** - * @brief Export a private key to a pem file on disk, or OpenSSH format for - * keytype ssh-ed25519 + * @brief Export a private key to a file in format specified in the argument * * @param[in] privkey The private key to export. * @@ -1016,16 +1074,21 @@ int ssh_pki_import_privkey_file(const char *filename, * * @param[in] filename The path where to store the pem file. * + * @param[in] format The file format (OpenSSH, PEM, or default) + * * @return SSH_OK on success, SSH_ERROR on error. */ -int ssh_pki_export_privkey_file(const ssh_key privkey, - const char *passphrase, - ssh_auth_callback auth_fn, - void *auth_data, - const char *filename) + +int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format) { - ssh_string blob; - FILE *fp; + ssh_string blob = NULL; + FILE *fp = NULL; int rc; if (privkey == NULL || !ssh_key_is_private(privkey)) { @@ -1040,16 +1103,34 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, return SSH_EOF; } - if (privkey->type == SSH_KEYTYPE_ED25519){ - blob = ssh_pki_openssh_privkey_export(privkey, - passphrase, - auth_fn, - auth_data); - } else { + /* The PEM export is supported only with OpenSSL. We fall back to + * OpenSSH key format elsewhere */ + if (format == SSH_FILE_FORMAT_DEFAULT) { +#ifdef HAVE_LIBCRYPTO + if (privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; + } else { +#else + if (1) { +#endif /* HAVE_LIBCRYPTO */ + format = SSH_FILE_FORMAT_OPENSSH; + } + } + + switch (format) { + case SSH_FILE_FORMAT_DEFAULT: + case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); + break; + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; } if (blob == NULL) { fclose(fp); @@ -1068,6 +1149,38 @@ int ssh_pki_export_privkey_file(const ssh_key privkey, return SSH_OK; } +/** + * @brief Export a private key to a pem file on disk, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int +ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename) +{ + return ssh_pki_export_privkey_file_format(privkey, + passphrase, + auth_fn, + auth_data, + filename, + SSH_FILE_FORMAT_DEFAULT); +} + /* temporary function to migrate seamlessly to ssh_key */ ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) { @@ -2035,7 +2148,42 @@ int ssh_pki_export_pubkey_blob(const ssh_key key, return SSH_OK; } - blob = pki_publickey_to_blob(key); + blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a private key. + * + * The "key_blob" is encoded as per draft-miller-ssh-agent-08 section 4.2 + * "Adding keys to the agent" for any of the supported key types. + * + * @param[in] key A private key to create the private ssh_string from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PRIVATE); if (blob == NULL) { return SSH_ERROR; } @@ -2066,7 +2214,7 @@ int ssh_pki_export_pubkey_base64(const ssh_key key, return SSH_ERROR; } - key_blob = pki_publickey_to_blob(key); + key_blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); if (key_blob == NULL) { return SSH_ERROR; } diff --git a/src/pki_container_openssh.c b/src/pki_container_openssh.c index 92101c4e..82afdf8e 100644 --- a/src/pki_container_openssh.c +++ b/src/pki_container_openssh.c @@ -394,37 +394,6 @@ ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) } -/** @internal - * @brief exports a private key to a string blob. - * @param[in] privkey private key to convert - * @param[out] buffer buffer to write the blob in. - * @returns SSH_OK on success - * @warning only supports ed25519 key type at the moment. - */ -static int pki_openssh_export_privkey_blob(const ssh_key privkey, - ssh_buffer buffer) -{ - int rc; - - if (privkey->type != SSH_KEYTYPE_ED25519) { - SSH_LOG(SSH_LOG_TRACE, "Type %s not supported", privkey->type_c); - return SSH_ERROR; - } - if (privkey->ed25519_privkey == NULL || - privkey->ed25519_pubkey == NULL) { - return SSH_ERROR; - } - rc = ssh_buffer_pack(buffer, - "sdPdPP", - privkey->type_c, - (uint32_t)ED25519_KEY_LEN, - (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey, - (uint32_t)(2 * ED25519_KEY_LEN), - (size_t)ED25519_KEY_LEN, privkey->ed25519_privkey, - (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey); - return rc; -} - /** @internal * @brief encrypts an ed25519 private key blob * @@ -536,8 +505,8 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, ssh_auth_callback auth_fn, void *auth_data) { - ssh_buffer buffer; - ssh_string str = NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL, blob = NULL; ssh_string pubkey_s=NULL; ssh_buffer privkey_buffer = NULL; uint32_t rnd; @@ -554,17 +523,13 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, if (privkey == NULL) { return NULL; } - if (privkey->type != SSH_KEYTYPE_ED25519){ - SSH_LOG(SSH_LOG_TRACE, "Unsupported key type %s", privkey->type_c); - return NULL; - } if (passphrase != NULL || auth_fn != NULL){ SSH_LOG(SSH_LOG_DEBUG, "Enabling encryption for private key export"); to_encrypt = 1; } buffer = ssh_buffer_new(); - pubkey_s = pki_publickey_to_blob(privkey); - if(buffer == NULL || pubkey_s == NULL){ + rc = ssh_pki_export_pubkey_blob(privkey, &pubkey_s); + if (buffer == NULL || rc != SSH_OK) { goto error; } @@ -578,22 +543,17 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, goto error; } - /* checkint1 & 2 */ - rc = ssh_buffer_pack(privkey_buffer, - "dd", - rnd, - rnd); - if (rc == SSH_ERROR){ - goto error; - } - - rc = pki_openssh_export_privkey_blob(privkey, privkey_buffer); - if (rc == SSH_ERROR){ + rc = ssh_pki_export_privkey_blob(privkey, &blob); + if (rc != SSH_OK) { goto error; } - /* comment */ - rc = ssh_buffer_pack(privkey_buffer, "s", "" /* comment */); + rc = ssh_buffer_pack(privkey_buffer, + "ddPs", + rnd, /* checkint 1 & 2 */ + rnd, + ssh_string_len(blob), ssh_string_data(blob), + "" /* comment */); if (rc == SSH_ERROR){ goto error; } @@ -710,6 +670,8 @@ ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, } error: + ssh_string_burn(blob); + ssh_string_free(blob); if (privkey_buffer != NULL) { void *bufptr = ssh_buffer_get(privkey_buffer); explicit_bzero(bufptr, ssh_buffer_get_len(privkey_buffer)); diff --git a/src/pki_crypto.c b/src/pki_crypto.c index 4b60a709..f4ce8bdf 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -1309,18 +1309,9 @@ int pki_privkey_build_rsa(ssh_key key, rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); if (rc != SSH_OK) { - rc = SSH_ERROR; - goto fail; - } - - rc = EVP_PKEY_set_bn_param(key->key, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); - if (rc != 1) { - rc = SSH_ERROR; - goto fail; - } - - rc = EVP_PKEY_set_bn_param(key->key, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); - if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); rc = SSH_ERROR; goto fail; } @@ -1412,7 +1403,7 @@ int pki_pubkey_build_rsa(ssh_key key, #endif /* OPENSSL_VERSION_NUMBER */ } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer; ssh_string type_s; @@ -1422,10 +1413,13 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string p = NULL; ssh_string g = NULL; ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; int rc; #if OPENSSL_VERSION_NUMBER >= 0x30000000L BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL, - *bn = NULL, *be = NULL; + *bn = NULL, *be = NULL, + *bd = NULL, *biqmp = NULL; OSSL_PARAM *params = NULL; #endif /* OPENSSL_VERSION_NUMBER */ @@ -1460,7 +1454,7 @@ ssh_string pki_publickey_to_blob(const ssh_key key) case SSH_KEYTYPE_RSA: case SSH_KEYTYPE_RSA1: { #if OPENSSL_VERSION_NUMBER < 0x30000000L - const BIGNUM *be, *bn; + const BIGNUM *be = NULL, *bn = NULL; const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); RSA_get0_key(key_rsa, &bn, &be, NULL); #else @@ -1498,13 +1492,133 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - if (ssh_buffer_add_ssh_string(buffer, e) < 0) { - goto fail; - } - if (ssh_buffer_add_ssh_string(buffer, n) < 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 fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *bd, *biqmp, *bp, *bq; + RSA_get0_key(key_rsa, NULL, NULL, &bd); + RSA_get0_factors(key_rsa, &bp, &bq); + RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param D has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bd); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bp); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bq); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_COEFFICIENT1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param IQMP has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &biqmp); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string((BIGNUM *)biqmp); + if (iqmp == NULL) { + goto fail; + } + p = ssh_make_bignum_string((BIGNUM *)bp); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string((BIGNUM *)bq); + if (q == NULL) { + goto fail; + } + + 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; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + 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; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + bignum_safe_free(biqmp); + bignum_safe_free(bp); + bignum_safe_free(bq); +#endif /* OPENSSL_VERSION_NUMBER */ + } ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; @@ -1520,13 +1634,23 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc == SSH_ERROR){ - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + 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; + } } break; case SSH_KEYTYPE_ECDSA_P256: @@ -1544,6 +1668,7 @@ ssh_string pki_publickey_to_blob(const ssh_key key) #else const EC_GROUP *group = NULL; const EC_POINT *point = NULL; + const BIGNUM *exp = NULL; EC_KEY *ec = NULL; #endif /* OPENSSL_VERSION_NUMBER */ @@ -1628,15 +1753,52 @@ ssh_string pki_publickey_to_blob(const ssh_key key) ssh_string_burn(e); SSH_STRING_FREE(e); e = NULL; + if (type == SSH_KEY_PRIVATE) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L - OSSL_PARAM_free(params); -#endif /* OPENSSL_VERSION_NUMBER */ + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc < 0) { + goto fail; + } - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY); + rc = OSSL_PARAM_get_BN(locate_param, &bd); + if (rc != 1) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, d) < 0) { + goto fail; + } +#else + exp = EC_KEY_get0_private_key(ec); + if (exp == NULL) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)exp); + if (d == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + 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, key->sk_application); + if (rc < 0) { + goto fail; + } } - +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ break; } #endif /* HAVE_OPENSSL_ECC */ @@ -1672,6 +1834,10 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(q); ssh_string_burn(n); SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); #if OPENSSL_VERSION_NUMBER >= 0x30000000L bignum_safe_free(bp); bignum_safe_free(bq); @@ -1679,6 +1845,8 @@ ssh_string pki_publickey_to_blob(const ssh_key key) bignum_safe_free(bpub_key); bignum_safe_free(bn); bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(biqmp); OSSL_PARAM_free(params); #endif /* OPENSSL_VERSION_NUMBER */ diff --git a/src/pki_ed25519_common.c b/src/pki_ed25519_common.c index 3b165e2c..03859f7c 100644 --- a/src/pki_ed25519_common.c +++ b/src/pki_ed25519_common.c @@ -206,6 +206,34 @@ int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) return rc; } +/** @internal + * @brief exports a ed25519 private key to a string blob. + * @param[in] privkey private key to convert + * @param[out] buffer buffer to write the blob in. + * @returns SSH_OK on success + */ +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey) +{ + int rc; + + if (privkey->type != SSH_KEYTYPE_ED25519) { + SSH_LOG(SSH_LOG_TRACE, "Type %s not supported", privkey->type_c); + return SSH_ERROR; + } + if (privkey->ed25519_privkey == NULL || + privkey->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + rc = ssh_buffer_pack(buffer, + "dPdPP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey, + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, privkey->ed25519_privkey, + (size_t)ED25519_KEY_LEN, privkey->ed25519_pubkey); + return rc; +} + /** * @internal * diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 0a864493..65bb77e6 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -1366,16 +1366,18 @@ int pki_key_compare(const ssh_key k1, return 0; } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer; ssh_string type_s; ssh_string str = NULL; ssh_string e = NULL; ssh_string n = NULL; + ssh_string d = NULL; ssh_string p = NULL; ssh_string g = NULL; ssh_string q = NULL; + ssh_string u = NULL; int rc; buffer = ssh_buffer_new(); @@ -1423,30 +1425,108 @@ ssh_string pki_publickey_to_blob(const ssh_key key) goto fail; } - 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; - } + 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) { + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_sexp_extract_mpi(key->rsa, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + p = ssh_sexp_extract_mpi(key->rsa, + "p", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (p == NULL) { + goto fail; + } + + q = ssh_sexp_extract_mpi(key->rsa, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (q == NULL) { + goto fail; + } + u = ssh_sexp_extract_mpi(key->rsa, + "u", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (u == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, u); + if (rc < 0) { + goto fail; + } + /* Swap the P and Q as the iqmp in gcrypt is ipmq ... */ + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(u); + SSH_STRING_FREE(u); + } ssh_string_burn(e); SSH_STRING_FREE(e); ssh_string_burn(n); SSH_STRING_FREE(n); - break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK){ - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + 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; + } } break; case SSH_KEYTYPE_ECDSA_P256: @@ -1457,22 +1537,19 @@ ssh_string pki_publickey_to_blob(const ssh_key key) 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; + goto fail; } rc = ssh_buffer_add_ssh_string(buffer, type_s); SSH_STRING_FREE(type_s); if (rc < 0) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } e = ssh_sexp_extract_mpi(key->ecdsa, "q", GCRYMPI_FMT_STD, GCRYMPI_FMT_STD); if (e == NULL) { - SSH_BUFFER_FREE(buffer); - return NULL; + goto fail; } rc = ssh_buffer_add_ssh_string(buffer, e); @@ -1484,9 +1561,27 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + if (type == SSH_KEY_PRIVATE) { + d = ssh_sexp_extract_mpi(key->ecdsa, "d", GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + 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, key->sk_application); + if (rc < 0) { + goto fail; + } } break; diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index e047239e..d3fda0ae 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -878,7 +878,7 @@ static const char* pki_key_ecdsa_nid_to_char(int nid) return "unknown"; } -ssh_string pki_publickey_to_blob(const ssh_key key) +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { ssh_buffer buffer = NULL; ssh_string type_s = NULL; @@ -888,6 +888,10 @@ ssh_string pki_publickey_to_blob(const ssh_key key) #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi E; mbedtls_mpi N; + mbedtls_mpi D; + mbedtls_mpi IQMP; + mbedtls_mpi P; + mbedtls_mpi Q; #endif int rc; @@ -961,21 +965,124 @@ ssh_string pki_publickey_to_blob(const ssh_key key) } #endif - if (ssh_buffer_add_ssh_string(buffer, e) < 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 fail; + } - if (ssh_buffer_add_ssh_string(buffer, n) < 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; + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto fail; + } + + p = ssh_make_bignum_string(&P); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string(&Q); + if (q == NULL) { + goto fail; + } + + 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; + } + + iqmp = ssh_make_bignum_string(&IQMP); + if (iqmp == NULL) { + goto fail; + } + +#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; + } +#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; + } + + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + 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: @@ -1013,21 +1120,51 @@ ssh_string pki_publickey_to_blob(const ssh_key key) SSH_STRING_FREE(e); e = NULL; - if (key->type == SSH_KEYTYPE_SK_ECDSA && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; - } + if (type == SSH_KEY_PRIVATE) { + ssh_string d = NULL; + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + 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, key->sk_application); + if (rc < 0) { + goto fail; + } + + } break; case SSH_KEYTYPE_ED25519: case SSH_KEYTYPE_SK_ED25519: - rc = pki_ed25519_public_key_to_blob(buffer, key); - if (rc != SSH_OK) { - goto fail; - } - if (key->type == SSH_KEYTYPE_SK_ED25519 && - ssh_buffer_add_ssh_string(buffer, key->sk_application) < 0) { - goto fail; + 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; + } } break; default: From 417a0f01f840a01747c45e685c1612b2a500d81f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 11 Aug 2023 16:53:40 +0200 Subject: [PATCH 068/795] examples: Demonstrate export of different key formats Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- examples/keygen2.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/examples/keygen2.c b/examples/keygen2.c index bbe15e0e..466e075b 100644 --- a/examples/keygen2.c +++ b/examples/keygen2.c @@ -38,6 +38,7 @@ struct arguments_st { unsigned long bits; char *file; char *passphrase; + char *format; int action_list; }; @@ -96,6 +97,16 @@ static struct argp_option options[] = { .doc = "List the Fingerprint of the given key\n", .group = 0 }, + { + .name = "format", + .key = 'm', + .arg = "FORMAT", + .flags = 0, + .doc = "Write the file in specific format. The supported values are " + "'PEM'and 'OpenSSH' file format. By default Ed25519 " + "keys are exported in OpenSSH format and others in PEM.\n", + .group = 0 + }, { /* End of the options */ 0 @@ -168,6 +179,9 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) case 'l': arguments->action_list = 1; break; + case 'm': + arguments->format = strdup(arg); + break; case ARGP_KEY_ARG: if (state->arg_num > 0) { /* Too many arguments. */ @@ -382,8 +396,36 @@ int main(int argc, char *argv[]) } /* Write the private key */ - rc = ssh_pki_export_privkey_file(key, arguments.passphrase, NULL, NULL, - arguments.file); + if (arguments.format != NULL) { + if (strcasecmp(arguments.format, "PEM") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_PEM); + } else if (strcasecmp(arguments.format, "OpenSSH") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_OPENSSH); + } else { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_DEFAULT); + } + } else { + rc = ssh_pki_export_privkey_file(key, + arguments.passphrase, + NULL, + NULL, + arguments.file); + } if (rc != SSH_OK) { fprintf(stderr, "Error: Failed to write private key file"); goto end; From 75a177f8d60c0e053ccc30651738c4ac704f1ee6 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 15 Aug 2023 18:59:20 +0200 Subject: [PATCH 069/795] Test coverage for file export and for PEM and OpenSSH formats Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/unittests/torture_pki_ecdsa.c | 188 +++++++++++++++++++++----- tests/unittests/torture_pki_ed25519.c | 113 +++++++++++----- tests/unittests/torture_pki_rsa.c | 134 +++++++++++++++--- 3 files changed, 344 insertions(+), 91 deletions(-) diff --git a/tests/unittests/torture_pki_ecdsa.c b/tests/unittests/torture_pki_ecdsa.c index 0995a14b..f6b17e8a 100644 --- a/tests/unittests/torture_pki_ecdsa.c +++ b/tests/unittests/torture_pki_ecdsa.c @@ -1,4 +1,5 @@ #include "config.h" +#include "libssh/libssh.h" #define LIBSSH_STATIC @@ -218,14 +219,16 @@ static void torture_pki_ecdsa_import_pubkey_from_openssh_privkey(void **state) SSH_KEY_FREE(pubkey); } -static void torture_pki_ecdsa_import_privkey_base64(void **state) +static void +torture_pki_ecdsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) { int rc; - char *key_str = NULL; - ssh_key key = NULL; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; const char *passphrase = torture_get_testkey_passphrase(); - (void) state; /* unused */ + (void)state; /* unused */ key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); assert_non_null(key_str); @@ -237,8 +240,39 @@ static void torture_pki_ecdsa_import_privkey_base64(void **state) rc = ssh_key_is_private(key); assert_int_equal(rc, 1); + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, passphrase, NULL, NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE | SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + free(key_str); + free(new_key_str); SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_default(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); } static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) @@ -828,8 +862,9 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state) SSH_KEY_FREE(key); } -#ifdef HAVE_LIBCRYPTO -static void torture_pki_ecdsa_write_privkey(void **state) +static void +torture_pki_ecdsa_write_privkey_format(void **state, + enum ssh_file_format_e format) { ssh_key origkey = NULL; ssh_key privkey = NULL; @@ -847,11 +882,12 @@ static void torture_pki_ecdsa_write_privkey(void **state) unlink(LIBSSH_ECDSA_TESTKEY); - rc = ssh_pki_export_privkey_file(origkey, - NULL, - NULL, - NULL, - LIBSSH_ECDSA_TESTKEY); + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY, + format); assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, @@ -878,11 +914,12 @@ static void torture_pki_ecdsa_write_privkey(void **state) assert_non_null(origkey); unlink(LIBSSH_ECDSA_TESTKEY_PASSPHRASE); - rc = ssh_pki_export_privkey_file(origkey, - torture_get_testkey_passphrase(), - NULL, - NULL, - LIBSSH_ECDSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + format); assert_int_equal(rc, 0); /* Test with invalid passphrase */ @@ -908,6 +945,39 @@ static void torture_pki_ecdsa_write_privkey(void **state) SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); } + +static void +torture_pki_ecdsa_write_privkey(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ecdsa_write_privkey_pem(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_write_privkey_openssh(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} #endif /* HAVE_LIBCRYPTO */ static void torture_pki_ecdsa_name(void **state, const char *expected_name) @@ -964,15 +1034,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, setup_openssh_ecdsa_key_521, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_ecdsa_key_521, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_521, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_comment, setup_ecdsa_key_256, teardown), @@ -991,15 +1064,18 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64_whitespace, setup_ecdsa_key_521, teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_openssh_ecdsa_key_256, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_openssh_ecdsa_key_384, - teardown), - cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_privkey_base64, - setup_openssh_ecdsa_key_521, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_521, + teardown), cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_from_privatekey, setup_ecdsa_key_256, teardown), @@ -1064,7 +1140,6 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, setup_ecdsa_key_521, teardown), -#ifdef HAVE_LIBCRYPTO cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, setup_ecdsa_key_256, teardown), @@ -1074,6 +1149,49 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, setup_ecdsa_key_521, teardown), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_521, + teardown), #endif /* HAVE_LIBCRYPTO */ cmocka_unit_test(torture_pki_sign_data_ecdsa), cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), diff --git a/tests/unittests/torture_pki_ed25519.c b/tests/unittests/torture_pki_ed25519.c index fe56c3f3..0142fbe3 100644 --- a/tests/unittests/torture_pki_ed25519.c +++ b/tests/unittests/torture_pki_ed25519.c @@ -508,36 +508,44 @@ static void torture_pki_ed25519_cert_verify(void **state) ssh_free(session); } -static void torture_pki_ed25519_write_privkey(void **state) +static void +torture_pki_ed25519_write_privkey_format(void **state, + enum ssh_file_format_e format) { ssh_key origkey = NULL; ssh_key privkey = NULL; int rc; - (void) state; /* unused */ + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - NULL, - NULL, - NULL, - &origkey); + NULL, + NULL, + NULL, + &origkey); assert_int_equal(rc, 0); assert_non_null(origkey); unlink(LIBSSH_ED25519_TESTKEY); - rc = ssh_pki_export_privkey_file(origkey, - NULL, - NULL, - NULL, - LIBSSH_ED25519_TESTKEY); + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); assert_int_equal(rc, 0); rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - NULL, - NULL, - NULL, - &privkey); + NULL, + NULL, + NULL, + &privkey); assert_int_equal(rc, 0); assert_non_null(privkey); @@ -547,26 +555,31 @@ static void torture_pki_ed25519_write_privkey(void **state) unlink(LIBSSH_ED25519_TESTKEY); SSH_KEY_FREE(privkey); /* do the same with passphrase */ - rc = ssh_pki_export_privkey_file(origkey, - torture_get_testkey_passphrase(), - NULL, - NULL, - LIBSSH_ED25519_TESTKEY); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); assert_int_equal(rc, 0); - rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - NULL, - NULL, - NULL, - &privkey); - /* opening without passphrase should fail */ - assert_int_equal(rc, SSH_ERROR); + /* Opening passphrase protected key will prompt for the pin interactively, + * which would hang in the test */ + if (format != SSH_FILE_FORMAT_PEM) { + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + /* opening without passphrase should fail */ + assert_int_equal(rc, SSH_ERROR); + } rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, - torture_get_testkey_passphrase(), - NULL, - NULL, - &privkey); + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); assert_int_equal(rc, 0); assert_non_null(privkey); @@ -587,11 +600,12 @@ static void torture_pki_ed25519_write_privkey(void **state) assert_non_null(origkey); unlink(LIBSSH_ED25519_TESTKEY_PASSPHRASE); - rc = ssh_pki_export_privkey_file(origkey, - torture_get_testkey_passphrase(), - NULL, - NULL, - LIBSSH_ED25519_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY_PASSPHRASE, + format); assert_int_equal(rc, 0); /* Test with invalid passphrase */ @@ -617,6 +631,26 @@ static void torture_pki_ed25519_write_privkey(void **state) SSH_KEY_FREE(privkey); } +static void +torture_pki_ed25519_write_privkey(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ed25519_write_privkey_pem(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ed25519_write_privkey_openssh(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} +#endif + static void torture_pki_ed25519_sign(void **state) { ssh_key privkey = NULL; @@ -1023,6 +1057,13 @@ int torture_run_tests(void) { #ifdef HAVE_LIBCRYPTO cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey), cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey_pem, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_write_privkey_openssh, + setup_ed25519_key, + teardown), #endif cmocka_unit_test(torture_pki_ed25519_verify), cmocka_unit_test(torture_pki_ed25519_verify_bad), diff --git a/tests/unittests/torture_pki_rsa.c b/tests/unittests/torture_pki_rsa.c index 1700270e..4da6b148 100644 --- a/tests/unittests/torture_pki_rsa.c +++ b/tests/unittests/torture_pki_rsa.c @@ -1,5 +1,6 @@ #include "config.h" +#include "libssh/libssh.h" #define LIBSSH_STATIC @@ -183,11 +184,13 @@ static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state) SSH_KEY_FREE(key); } -static void torture_pki_rsa_import_privkey_base64(void **state) +static void +torture_pki_rsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) { int rc; - char *key_str = NULL; - ssh_key key = NULL; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; const char *passphrase = torture_get_testkey_passphrase(); enum ssh_keytypes_e type; @@ -196,6 +199,7 @@ static void torture_pki_rsa_import_privkey_base64(void **state) key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); assert_non_null(key_str); + /* Import test key */ rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); assert_return_code(rc, errno); assert_non_null(key); @@ -209,8 +213,48 @@ static void torture_pki_rsa_import_privkey_base64(void **state) rc = ssh_key_is_public(key); assert_int_equal(rc, 1); + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, + passphrase, + NULL, + NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + type = ssh_key_type(new_key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE|SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + free(key_str); + free(new_key_str); SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_rsa_import_export_privkey_base64(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); } static void torture_pki_rsa_import_privkey_base64_comment(void **state) @@ -828,8 +872,9 @@ static void torture_pki_fail_sign_with_incompatible_hash(void **state) SSH_KEY_FREE(key); } -#ifdef HAVE_LIBCRYPTO -static void torture_pki_rsa_write_privkey(void **state) +static void +torture_pki_rsa_write_privkey_format(void **state, + enum ssh_file_format_e format) { ssh_key origkey = NULL; ssh_key privkey = NULL; @@ -847,11 +892,12 @@ static void torture_pki_rsa_write_privkey(void **state) unlink(LIBSSH_RSA_TESTKEY); - rc = ssh_pki_export_privkey_file(origkey, - NULL, - NULL, - NULL, - LIBSSH_RSA_TESTKEY); + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_RSA_TESTKEY, + format); assert_return_code(rc, errno); rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, @@ -878,11 +924,12 @@ static void torture_pki_rsa_write_privkey(void **state) assert_non_null(origkey); unlink(LIBSSH_RSA_TESTKEY_PASSPHRASE); - rc = ssh_pki_export_privkey_file(origkey, - torture_get_testkey_passphrase(), - NULL, - NULL, - LIBSSH_RSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_RSA_TESTKEY_PASSPHRASE, + format); assert_return_code(rc, errno); /* Test with invalid passphrase */ @@ -908,6 +955,38 @@ static void torture_pki_rsa_write_privkey(void **state) SSH_KEY_FREE(origkey); SSH_KEY_FREE(privkey); } + +static void +torture_pki_rsa_write_privkey(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#if defined(HAVE_LIBCRYPTO) +static void +torture_pki_rsa_write_privkey_pem(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_rsa_write_privkey_openssh(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_rsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} +static void +torture_pki_rsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} #endif /* HAVE_LIBCRYPTO */ static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) @@ -1015,7 +1094,7 @@ int torture_run_tests(void) { 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, + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_export_privkey_base64, setup_rsa_key, teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_comment, @@ -1024,9 +1103,10 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64_whitespace, setup_rsa_key, teardown), - cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, - setup_openssh_rsa_key, - teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_openssh_rsa_key, + teardown), cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_from_privatekey, setup_rsa_key, teardown), @@ -1049,10 +1129,24 @@ int torture_run_tests(void) { teardown), cmocka_unit_test(torture_pki_rsa_generate_key), cmocka_unit_test(torture_pki_rsa_key_size), -#if defined(HAVE_LIBCRYPTO) cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey, setup_rsa_key, teardown), +#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_pem, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_openssh, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_pem, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_openssh, + setup_openssh_rsa_key, + teardown), #endif /* HAVE_LIBCRYPTO */ cmocka_unit_test(torture_pki_sign_data_rsa), cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), From 57ec9a35c612d416bfc045c48ccb69a5e9b57008 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Sun, 5 Nov 2023 13:12:47 +0100 Subject: [PATCH 070/795] CVE-2023-6004: torture_config: Allow multiple '@' in usernames Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/unittests/torture_config.c | 50 +++++++++++++++++--------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 544623fe..bc6b08f9 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -1049,23 +1049,22 @@ static void torture_config_proxyjump(void **state, assert_string_equal(session->opts.ProxyCommand, "ssh -W '[%h]:%p' 2620:52:0::fed"); - /* In this part, we try various other config files and strings. */ - - /* Try to create some invalid configurations */ - /* Non-numeric port */ - config = "Host bad-port\n" - "\tProxyJump jumpbox:22bad22\n"; + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; if (file != NULL) { torture_write_file(file, config); } else { string = config; } torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); - _parse_config(session, file, string, SSH_ERROR); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -J user@principal.com@jumpbox:22 -W '[%h]:%p' localhost"); - /* Too many @ */ - config = "Host bad-hostname\n" + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" "\tProxyJump user@principal.com@jumpbox:22\n"; if (file != NULL) { torture_write_file(file, config); @@ -1073,7 +1072,24 @@ static void torture_config_proxyjump(void **state, string = config; } torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user@principal.com -p 22 -W '[%h]:%p' jumpbox"); + + /* In this part, we try various other config files and strings. */ + + /* Try to create some invalid configurations */ + /* Non-numeric port */ + config = "Host bad-port\n" + "\tProxyJump jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); _parse_config(session, file, string, SSH_ERROR); /* Braces mismatch in hostname */ @@ -1148,18 +1164,6 @@ static void torture_config_proxyjump(void **state, ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); _parse_config(session, file, string, SSH_ERROR); - /* Too many @ in second jump */ - config = "Host bad-hostname\n" - "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; - if (file != NULL) { - torture_write_file(file, config); - } else { - string = config; - } - torture_reset_config(session); - ssh_options_set(session, SSH_OPTIONS_HOST, "bad-hostname"); - _parse_config(session, file, string, SSH_ERROR); - /* Braces mismatch in second jump */ config = "Host mismatch\n" "\tProxyJump localhost,[::1:20\n"; From 1dfde16f49076b255e6370f30abf9f03d48997be Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Wed, 1 Nov 2023 11:24:43 +0100 Subject: [PATCH 071/795] CVE-2023-6004: config_parser: Allow multiple '@' in usernames Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index 10cc614f..ec58e873 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -180,7 +180,7 @@ int ssh_config_parse_uri(const char *tok, } /* Username part (optional) */ - endp = strchr(tok, '@'); + endp = strrchr(tok, '@'); if (endp != NULL) { /* Zero-length username is not valid */ if (tok == endp) { From b83368b2ed10a3d14344f374d9765d47d1d9f3f7 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 31 Oct 2023 09:48:52 +0100 Subject: [PATCH 072/795] CVE-2023-6004: options: Simplify the hostname parsing in ssh_options_set Using ssh_config_parse_uri can simplify the parsing of the host parsing inside the function of ssh_options_set Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/options.c | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/options.c b/src/options.c index f144bbb4..2e73be46 100644 --- a/src/options.c +++ b/src/options.c @@ -37,6 +37,7 @@ #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/options.h" +#include "libssh/config_parser.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/bind.h" @@ -633,33 +634,24 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(session); + char *username = NULL, *hostname = NULL, *port = NULL; + rc = ssh_config_parse_uri(value, &username, &hostname, &port); + if (rc != SSH_OK) { return -1; } - p = strrchr(q, '@'); - - SAFE_FREE(session->opts.host); - - if (p) { - *p = '\0'; - session->opts.host = strdup(p + 1); - if (session->opts.host == NULL) { - SAFE_FREE(q); - ssh_set_error_oom(session); - return -1; - } - + if (port != NULL) { + SAFE_FREE(username); + SAFE_FREE(hostname); + SAFE_FREE(port); + return -1; + } + if (username != NULL) { SAFE_FREE(session->opts.username); - session->opts.username = strdup(q); - SAFE_FREE(q); - if (session->opts.username == NULL) { - ssh_set_error_oom(session); - return -1; - } - } else { - session->opts.host = q; + session->opts.username = username; + } + if (hostname != NULL) { + SAFE_FREE(session->opts.host); + session->opts.host = hostname; } } break; From 0ff85b034a04d45e79a79cd5666b348b5e27800d Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 10 Oct 2023 12:44:16 +0200 Subject: [PATCH 073/795] CVE-2023-6004: misc: Add function to check allowed characters of a hostname The hostname can be a domain name or an ip address. The colon has to be allowed because of IPv6 even it is prohibited in domain names. Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- include/libssh/misc.h | 2 ++ src/misc.c | 68 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/include/libssh/misc.h b/include/libssh/misc.h index fe86d251..dfbba14c 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -122,6 +122,8 @@ char *ssh_strreplace(const char *src, const char *pattern, const char *repl); ssize_t ssh_readn(int fd, void *buf, size_t nbytes); ssize_t ssh_writen(int fd, const void *buf, size_t nbytes); +int ssh_check_hostname_syntax(const char *hostname); + #ifdef __cplusplus } #endif diff --git a/src/misc.c b/src/misc.c index 8bdd568e..0c0a2fe3 100644 --- a/src/misc.c +++ b/src/misc.c @@ -94,6 +94,8 @@ #define ZLIB_STRING "" #endif +#define ARPA_DOMAIN_MAX_LEN 63 + /** * @defgroup libssh_misc The SSH helper functions * @ingroup libssh @@ -2087,4 +2089,70 @@ ssize_t ssh_writen(int fd, const void *buf, size_t nbytes) return total_bytes_written; } +/** + * @brief Checks syntax of a domain name + * + * The check is made based on the RFC1035 section 2.3.1 + * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) + * + * The label should be no longer than 63 characters + * The label should start with a letter and end with a letter or number + * The label in this implementation can start with a number to allow virtual + * URLs to pass. Note that this will make IPv4 addresses to pass + * this check too. + * + * @param hostname The domain name to be checked, has to be null terminated + * + * @return SSH_OK if the hostname passes syntax check + * SSH_ERROR otherwise or if hostname is NULL or empty string + */ +int ssh_check_hostname_syntax(const char *hostname) +{ + char *it = NULL, *s = NULL, *buf = NULL; + size_t it_len; + char c; + + if (hostname == NULL || strlen(hostname) == 0) { + return SSH_ERROR; + } + + /* strtok_r writes into the string, keep the input clean */ + s = strdup(hostname); + if (s == NULL) { + return SSH_ERROR; + } + + it = strtok_r(s, ".", &buf); + /* if the token has 0 length */ + if (it == NULL) { + free(s); + return SSH_ERROR; + } + do { + it_len = strlen(it); + if (it_len > ARPA_DOMAIN_MAX_LEN || + /* the first char must be a letter, but some virtual urls start + * with a number */ + isalnum(it[0]) == 0 || + isalnum(it[it_len - 1]) == 0) { + free(s); + return SSH_ERROR; + } + while (*it != '\0') { + c = *it; + /* the "." is allowed too, but tokenization removes it from the + * string */ + if (isalnum(c) == 0 && c != '-') { + free(s); + return SSH_ERROR; + } + it++; + } + } while ((it = strtok_r(NULL, ".", &buf)) != NULL); + + free(s); + + return SSH_OK; +} + /** @} */ From 2cd971e10e6244c6ffbfadbeba626ef998b4f78e Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 10 Oct 2023 12:45:28 +0200 Subject: [PATCH 074/795] CVE-2023-6004: torture_misc: Add test for ssh_check_hostname_syntax Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/unittests/torture_misc.c | 75 +++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index b8f871a8..72655b3c 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -979,6 +979,78 @@ static void torture_ssh_writen(void **state) free(write_buf); } +static void torture_ssh_check_hostname_syntax(void **state) +{ + int rc; + (void)state; + + rc = ssh_check_hostname_syntax("duckduckgo.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("www.libssh.org"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("Some-Thing.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.ok"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("lavabo-inter.innocentes-manus-meas"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("localhost"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a-0.b-b"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("libssh."); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_hostname_syntax(NULL); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("/"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("@"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("["); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("{"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("&"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("|"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("\""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(" "); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("*the+giant&\"rooks\".c0m"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("!www.libssh.org"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("--.--"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh-"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("fe80::9656:d028:8652:66b6"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("."); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(".."); + assert_int_equal(rc, SSH_ERROR); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -1004,7 +1076,8 @@ int torture_run_tests(void) { cmocka_unit_test(torture_ssh_strreplace), cmocka_unit_test(torture_ssh_strerror), cmocka_unit_test(torture_ssh_readn), - cmocka_unit_test(torture_ssh_writen) + cmocka_unit_test(torture_ssh_writen), + cmocka_unit_test(torture_ssh_check_hostname_syntax), }; ssh_init(); From 95c6f880ef1539635bb82a134f7b8a06a46887ca Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 10 Oct 2023 18:33:56 +0200 Subject: [PATCH 075/795] CVE-2023-6004: config_parser: Check for valid syntax of a hostname if it is a domain name This prevents code injection. The domain name syntax checker is based on RFC1035. Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/config_parser.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/config_parser.c b/src/config_parser.c index ec58e873..9ffc8b8b 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -30,6 +30,7 @@ #include "libssh/config_parser.h" #include "libssh/priv.h" +#include "libssh/misc.h" /* Returns the original string after skipping the leading whitespace * until finding LF. @@ -47,7 +48,7 @@ char *ssh_config_get_cmd(char **str) break; } } - + for (r = c; *c; c++) { if (*c == '\n') { *c = '\0'; @@ -167,6 +168,7 @@ int ssh_config_parse_uri(const char *tok, { char *endp = NULL; long port_n; + int rc; /* Sanitize inputs */ if (username != NULL) { @@ -224,6 +226,14 @@ int ssh_config_parse_uri(const char *tok, if (*hostname == NULL) { goto error; } + /* if not an ip, check syntax */ + rc = ssh_is_ipaddr(*hostname); + if (rc == 0) { + rc = ssh_check_hostname_syntax(*hostname); + if (rc != SSH_OK) { + goto error; + } + } } /* Skip also the closing bracket */ if (*endp == ']') { From 7b697d711e2c8b88ca6e15e349caae2dff9cb442 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 10 Oct 2023 10:28:47 +0200 Subject: [PATCH 076/795] CVE-2023-6004: torture_proxycommand: Add test for proxycommand injection Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/client/torture_proxycommand.c | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/client/torture_proxycommand.c b/tests/client/torture_proxycommand.c index 9b8019ca..1bad4ccc 100644 --- a/tests/client/torture_proxycommand.c +++ b/tests/client/torture_proxycommand.c @@ -166,6 +166,56 @@ static void torture_options_set_proxycommand_ssh_stderr(void **state) assert_int_equal(rc & O_RDWR, O_RDWR); } +static void torture_options_proxycommand_injection(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + const char *malicious_host = "`echo foo > mfile`"; + const char *command = "nc %h %p"; + char *current_dir = NULL; + char *malicious_file_path = NULL; + int mfp_len; + int verbosity = torture_libssh_verbosity(); + struct stat sb; + 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); + // if we would be checking the rc, this should fail + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, malicious_host); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code_equal(s->ssh.session, rc, SSH_ERROR); + + current_dir = torture_get_current_working_dir(); + assert_non_null(current_dir); + mfp_len = strlen(current_dir) + 6; + malicious_file_path = malloc(mfp_len); + assert_non_null(malicious_file_path); + rc = snprintf(malicious_file_path, mfp_len, + "%s/mfile", current_dir); + assert_int_equal(rc, mfp_len); + free(current_dir); + rc = stat(malicious_file_path, &sb); + assert_int_not_equal(rc, 0); + + // cleanup + remove(malicious_file_path); + free(malicious_file_path); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -181,6 +231,9 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand_injection, + NULL, + session_teardown), }; From 92e35c291c9a5c6dbe742a2677bf377597f69cd7 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Mon, 6 Nov 2023 20:11:38 +0100 Subject: [PATCH 077/795] CVE-2023-6004: torture_misc: Add test for ssh_is_ipaddr Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/unittests/torture_misc.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index 72655b3c..a53d640f 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -1051,6 +1051,31 @@ static void torture_ssh_check_hostname_syntax(void **state) assert_int_equal(rc, SSH_ERROR); } +static void torture_ssh_is_ipaddr(void **state) { + int rc; + (void)state; + + rc = ssh_is_ipaddr("201.255.3.69"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("::1"); + assert_int_equal(rc, 1); + rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); + assert_int_equal(rc, 1); + + rc = ssh_is_ipaddr(".."); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr(":::"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("1.1.1.1.1"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("1.1"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("caesar"); + assert_int_equal(rc, 0); + rc = ssh_is_ipaddr("::xa:1"); + assert_int_equal(rc, 0); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -1078,6 +1103,7 @@ int torture_run_tests(void) { cmocka_unit_test(torture_ssh_readn), cmocka_unit_test(torture_ssh_writen), cmocka_unit_test(torture_ssh_check_hostname_syntax), + cmocka_unit_test(torture_ssh_is_ipaddr), }; ssh_init(); From 2c92e8ce930a428a6fd150ae1ae55c5a365543f5 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 28 Nov 2023 15:26:45 +0100 Subject: [PATCH 078/795] CVE-2023-6004: misc: Add ipv6 link-local check for an ip address Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/CMakeLists.txt | 3 ++- src/connect.c | 2 +- src/misc.c | 44 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b6b9cd7c..216e149a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,11 +85,12 @@ if (MINGW AND Threads_FOUND) ) endif() -# This needs to be last for mingw to build +# The ws2_32 needs to be last for mingw to build # https://gitlab.com/libssh/libssh-mirror/-/issues/84 if (WIN32) set(LIBSSH_LINK_LIBRARIES ${LIBSSH_LINK_LIBRARIES} + iphlpapi ws2_32 ) endif (WIN32) diff --git a/src/connect.c b/src/connect.c index fd54c511..dd3bbcf5 100644 --- a/src/connect.c +++ b/src/connect.c @@ -131,7 +131,7 @@ static int getai(const char *host, int port, struct addrinfo **ai) #endif } - if (ssh_is_ipaddr(host)) { + if (ssh_is_ipaddr(host) == 1) { /* this is an IP address */ SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); hints.ai_flags |= AI_NUMERICHOST; diff --git a/src/misc.c b/src/misc.c index 0c0a2fe3..5cab9d96 100644 --- a/src/misc.c +++ b/src/misc.c @@ -32,6 +32,7 @@ #include #include #include +#include #endif /* _WIN32 */ @@ -59,6 +60,7 @@ #include #include #include +#include #ifdef HAVE_IO_H #include @@ -222,22 +224,37 @@ int ssh_is_ipaddr_v4(const char *str) int ssh_is_ipaddr(const char *str) { int rc = SOCKET_ERROR; + char *s = strdup(str); - if (strchr(str, ':')) { + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { struct sockaddr_storage ss; int sslen = sizeof(ss); + char *network_interface = strchr(s, '%'); - /* TODO link-local (IP:v6:addr%ifname). */ - rc = WSAStringToAddressA((LPSTR) str, + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = WSAStringToAddressA((LPSTR) s, AF_INET6, NULL, (struct sockaddr*)&ss, &sslen); if (rc == 0) { + free(s); return 1; } } + free(s); return ssh_is_ipaddr_v4(str); } #else /* _WIN32 */ @@ -343,17 +360,32 @@ int ssh_is_ipaddr_v4(const char *str) int ssh_is_ipaddr(const char *str) { int rc = -1; + char *s = strdup(str); - if (strchr(str, ':')) { + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { struct in6_addr dest6; + char *network_interface = strchr(s, '%'); - /* TODO link-local (IP:v6:addr%ifname). */ - rc = inet_pton(AF_INET6, str, &dest6); + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = inet_pton(AF_INET6, s, &dest6); if (rc > 0) { + free(s); return 1; } } + free(s); return ssh_is_ipaddr_v4(str); } From f353b39ff2c0e0db51f978f035ac976ff5377413 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 28 Nov 2023 15:27:31 +0100 Subject: [PATCH 079/795] CVE-2023-6004: torture_misc: Add tests for ipv6 link-local Signed-off-by: Norbert Pocs Reviewed-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/unittests/torture_misc.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index a53d640f..e35d2eac 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -18,7 +18,14 @@ #include "torture.h" #include "error.c" +#ifdef _WIN32 +#include +#else +#include +#endif + #define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." +#define TORTURE_IPV6_LOCAL_LINK "fe80::98e1:82ff:fe8d:28b3%%%s" const char template[] = "temp_dir_XXXXXX"; @@ -1053,14 +1060,27 @@ static void torture_ssh_check_hostname_syntax(void **state) static void torture_ssh_is_ipaddr(void **state) { int rc; + char *interf = malloc(64); + char *test_interf = malloc(128); (void)state; + assert_non_null(interf); + assert_non_null(test_interf); rc = ssh_is_ipaddr("201.255.3.69"); assert_int_equal(rc, 1); rc = ssh_is_ipaddr("::1"); assert_int_equal(rc, 1); rc = ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"); assert_int_equal(rc, 1); + if_indextoname(1, interf); + assert_non_null(interf); + rc = sprintf(test_interf, TORTURE_IPV6_LOCAL_LINK, interf); + /* the "%%s" is not written */ + assert_int_equal(rc, strlen(interf) + strlen(TORTURE_IPV6_LOCAL_LINK) - 3); + rc = ssh_is_ipaddr(test_interf); + assert_int_equal(rc, 1); + free(interf); + free(test_interf); rc = ssh_is_ipaddr(".."); assert_int_equal(rc, 0); From 7ecc6a704ba30ef65a928742f140e0ee977c9dc4 Mon Sep 17 00:00:00 2001 From: Aris Adamantiadis Date: Tue, 12 Dec 2023 23:09:57 +0100 Subject: [PATCH 080/795] CVE-2023-48795: client side mitigation Signed-off-by: Aris Adamantiadis Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/packet.h | 1 + include/libssh/session.h | 6 +++++ src/curve25519.c | 19 +++---------- src/dh-gex.c | 7 +---- src/dh.c | 17 +++--------- src/ecdh.c | 8 +----- src/ecdh_crypto.c | 12 +++------ src/ecdh_gcrypt.c | 10 +++---- src/ecdh_mbedcrypto.c | 11 +++----- src/kex.c | 33 +++++++++++++++++++---- src/packet.c | 58 ++++++++++++++++++++++++++++++++++++++++ src/packet_cb.c | 12 +++++++++ 12 files changed, 125 insertions(+), 69 deletions(-) diff --git a/include/libssh/packet.h b/include/libssh/packet.h index 7f10a709..f0c8cb20 100644 --- a/include/libssh/packet.h +++ b/include/libssh/packet.h @@ -67,6 +67,7 @@ SSH_PACKET_CALLBACK(ssh_packet_ext_info); SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); #endif +int ssh_packet_send_newkeys(ssh_session session); int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); int ssh_packet_parse_type(ssh_session session); //int packet_flush(ssh_session session, int enforce_blocking); diff --git a/include/libssh/session.h b/include/libssh/session.h index cb55db95..27da7a83 100644 --- a/include/libssh/session.h +++ b/include/libssh/session.h @@ -84,6 +84,12 @@ enum ssh_pending_call_e { * sending it twice during key exchange to simplify the state machine. */ #define SSH_SESSION_FLAG_KEXINIT_SENT 0x0008 +/* The current SSH2 session implements the "strict KEX" feature and should behave + * differently on SSH2_MSG_NEWKEYS. */ +#define SSH_SESSION_FLAG_KEX_STRICT 0x0010 +/* Unexpected packets have been sent while the session was still unencrypted */ +#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020 + /* codes to use with ssh_handle_packets*() */ /* Infinite timeout */ #define SSH_TIMEOUT_INFINITE -1 diff --git a/src/curve25519.c b/src/curve25519.c index 26603681..3f57f25d 100644 --- a/src/curve25519.c +++ b/src/curve25519.c @@ -335,16 +335,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; @@ -502,18 +496,13 @@ static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init){ return SSH_ERROR; } - /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - goto error; - } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: diff --git a/src/dh-gex.c b/src/dh-gex.c index ea30e4e9..4e59073f 100644 --- a/src/dh-gex.c +++ b/src/dh-gex.c @@ -297,15 +297,10 @@ static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc = ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; diff --git a/src/dh.c b/src/dh.c index cd2876cb..e19e43d1 100644 --- a/src/dh.c +++ b/src/dh.c @@ -398,16 +398,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; error: @@ -551,15 +545,12 @@ int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) } SSH_LOG(SSH_LOG_DEBUG, "Sent KEX_DH_[GEX]_REPLY"); - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - ssh_buffer_reinit(session->out_buffer); - goto error; - } session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; - if (ssh_packet_send(session) == SSH_ERROR) { + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_NEWKEYS sent"); return SSH_OK; error: diff --git a/src/ecdh.c b/src/ecdh.c index fa5e08c5..af80beec 100644 --- a/src/ecdh.c +++ b/src/ecdh.c @@ -93,16 +93,10 @@ SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ } /* Send the MSG_NEWKEYS */ - if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { - goto error; - } - - rc=ssh_packet_send(session); + rc = ssh_packet_send_newkeys(session); if (rc == SSH_ERROR) { goto error; } - - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; return SSH_PACKET_USED; diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c index e31cc0aa..817f066a 100644 --- a/src/ecdh_crypto.c +++ b/src/ecdh_crypto.c @@ -522,18 +522,12 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) goto error; } - /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - goto error; - } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - if (rc == SSH_ERROR){ + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto error; } - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); return SSH_PACKET_USED; error: diff --git a/src/ecdh_gcrypt.c b/src/ecdh_gcrypt.c index 76e24873..86d15f72 100644 --- a/src/ecdh_gcrypt.c +++ b/src/ecdh_gcrypt.c @@ -372,17 +372,13 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto out; } - + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; /* Send the MSG_NEWKEYS */ - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc != SSH_OK) { + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto out; } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); - out: gcry_sexp_release(param); gcry_sexp_release(key); diff --git a/src/ecdh_mbedcrypto.c b/src/ecdh_mbedcrypto.c index 1c930fb5..1d9c8f36 100644 --- a/src/ecdh_mbedcrypto.c +++ b/src/ecdh_mbedcrypto.c @@ -318,16 +318,13 @@ SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ goto out; } - rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); - if (rc < 0) { - rc = SSH_ERROR; + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { goto out; } - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; - rc = ssh_packet_send(session); - SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); - out: mbedtls_ecp_group_free(&grp); if (rc == SSH_ERROR) { diff --git a/src/kex.c b/src/kex.c index 42d94ad7..b78ee27d 100644 --- a/src/kex.c +++ b/src/kex.c @@ -178,6 +178,9 @@ /* RFC 8308 */ #define KEX_EXTENSION_CLIENT "ext-info-c" +/* Strict kex mitigation against CVE-2023-48795 */ +#define KEX_STRICT_CLIENT "kex-strict-c-v00@openssh.com" +#define KEX_STRICT_SERVER "kex-strict-s-v00@openssh.com" /* Allowed algorithms in FIPS mode */ #define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ @@ -509,6 +512,26 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit) session->first_kex_follows_guess_wrong ? "wrong" : "right"); } + /* + * handle the "strict KEX" feature. If supported by peer, then set up the + * flag and verify packet sequence numbers. + */ + if (server_kex) { + ok = ssh_match_group(crypto->client_kex.methods[SSH_KEX], + KEX_STRICT_CLIENT); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Client supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } else { + /* client kex */ + ok = ssh_match_group(crypto->server_kex.methods[SSH_KEX], + KEX_STRICT_SERVER); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Server supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } #ifdef WITH_SERVER if (server_kex) { /* @@ -789,21 +812,21 @@ int ssh_set_client_kex(ssh_session session) return SSH_OK; } - /* Here we append ext-info-c to the list of kex algorithms */ + /* Here we append ext-info-c and kex-strict-c-v00@openssh.com to the list of kex algorithms */ kex = client->methods[SSH_KEX]; len = strlen(kex); - if (len + strlen(KEX_EXTENSION_CLIENT) + 2 < len) { + /* Comma, comma, nul byte */ + kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + strlen(KEX_STRICT_CLIENT ) + 1; + if (kex_len >= MAX_PACKET_LEN) { /* Overflow */ return SSH_ERROR; } - kex_len = len + strlen(KEX_EXTENSION_CLIENT) + 2; /* comma, NULL */ kex_tmp = realloc(kex, kex_len); if (kex_tmp == NULL) { - free(kex); ssh_set_error_oom(session); return SSH_ERROR; } - snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_EXTENSION_CLIENT); + snprintf(kex_tmp + len, kex_len - len, ",%s,%s", KEX_EXTENSION_CLIENT, KEX_STRICT_CLIENT); client->methods[SSH_KEX] = kex_tmp; return SSH_OK; diff --git a/src/packet.c b/src/packet.c index 2b4a4e78..8508c731 100644 --- a/src/packet.c +++ b/src/packet.c @@ -1314,6 +1314,19 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) } #endif /* WITH_ZLIB */ payloadsize = ssh_buffer_get_len(session->in_buffer); + if (session->recv_seq == UINT32_MAX) { + /* Overflowing sequence numbers is always fishy */ + if (crypto == NULL) { + /* don't allow sequence number overflow when unencrypted */ + ssh_set_error(session, + SSH_FATAL, + "Incoming sequence number overflow"); + goto error; + } else { + SSH_LOG(SSH_LOG_WARNING, + "Incoming sequence number overflow"); + } + } session->recv_seq++; if (crypto != NULL) { struct ssh_cipher_struct *cipher = NULL; @@ -1338,7 +1351,19 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) "comp=%" PRIu32 ",payload=%" PRIu32 "]", session->in_packet.type, packet_len, padding, compsize, payloadsize); + if (crypto == NULL) { + /* In strict kex, only a few packets are allowed. Taint the session + * if we received packets that are normally allowed but to be + * refused if we are in strict kex when KEX is over. + */ + uint8_t type = session->in_packet.type; + if (type != SSH2_MSG_KEXINIT && type != SSH2_MSG_NEWKEYS && + (type < SSH2_MSG_KEXDH_INIT || + type > SSH2_MSG_KEX_DH_GEX_REQUEST)) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } /* Check if the packet is expected */ filter_result = ssh_packet_incoming_filter(session); @@ -1354,6 +1379,9 @@ ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) session->in_packet.type); goto error; case SSH_PACKET_UNKNOWN: + if (crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } ssh_packet_send_unimplemented(session, session->recv_seq - 1); break; } @@ -1529,7 +1557,33 @@ void ssh_packet_process(ssh_session session, uint8_t type) SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", ssh_get_error(session)); } + if (session->current_crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } +} + +/** @internal + * @brief sends a SSH_MSG_NEWKEYS when enabling the new negotiated ciphers + * @param session the SSH session + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_newkeys(ssh_session session) +{ + int rc; + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + return rc; } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + return rc; } /** @internal @@ -1842,6 +1896,10 @@ int ssh_packet_send(ssh_session session) if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { struct ssh_iterator *it; + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->send_seq = 0; + } for (it = ssh_list_get_iterator(session->out_queue); it != NULL; it = ssh_list_get_iterator(session->out_queue)) { diff --git a/src/packet_cb.c b/src/packet_cb.c index ee32dcfd..363c605f 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -133,6 +133,18 @@ SSH_PACKET_CALLBACK(ssh_packet_newkeys) goto error; } + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->recv_seq = 0; + /* Check that we aren't tainted */ + if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { + ssh_set_error(session, + SSH_FATAL, + "Received unexpected packets in strict KEX mode."); + goto error; + } + } + if (session->server) { /* server things are done in server.c */ session->dh_handshake_state=DH_STATE_FINISHED; From 3876976cedb93450e0e2a4fc8125d05b99c7fe5a Mon Sep 17 00:00:00 2001 From: Aris Adamantiadis Date: Tue, 12 Dec 2023 23:30:26 +0100 Subject: [PATCH 081/795] CVE-2023-48795: Server side mitigations Signed-off-by: Aris Adamantiadis Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/kex.h | 1 + src/kex.c | 46 ++++++++++++++++++++++++++++++++++---------- src/server.c | 8 +++++++- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/include/libssh/kex.h b/include/libssh/kex.h index 6da6693c..4a2ecb99 100644 --- a/include/libssh/kex.h +++ b/include/libssh/kex.h @@ -40,6 +40,7 @@ SSH_PACKET_CALLBACK(ssh_packet_kexinit); int ssh_send_kex(ssh_session session); void ssh_list_kex(struct ssh_kex_struct *kex); int ssh_set_client_kex(ssh_session session); +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex); int ssh_kex_select_methods(ssh_session session); int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); diff --git a/src/kex.c b/src/kex.c index b78ee27d..86b42785 100644 --- a/src/kex.c +++ b/src/kex.c @@ -759,11 +759,8 @@ int ssh_set_client_kex(ssh_session session) { struct ssh_kex_struct *client = &session->next_crypto->client_kex; const char *wanted; - char *kex = NULL; - char *kex_tmp = NULL; int ok; int i; - size_t kex_len, len; /* Skip if already set, for example for the rekey or when we do the guessing * it could have been already used to make some protocol decisions. */ @@ -812,11 +809,33 @@ int ssh_set_client_kex(ssh_session session) return SSH_OK; } - /* Here we append ext-info-c and kex-strict-c-v00@openssh.com to the list of kex algorithms */ - kex = client->methods[SSH_KEX]; + ok = ssh_kex_append_extensions(session, client); + if (ok != SSH_OK){ + return ok; + } + + return SSH_OK; +} + +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex) +{ + char *kex = NULL; + char *kex_tmp = NULL; + size_t kex_len, len; + + /* Here we append ext-info-c and kex-strict-c-v00@openssh.com for client + * and kex-strict-s-v00@openssh.com for server to the list of kex algorithms + */ + kex = pkex->methods[SSH_KEX]; len = strlen(kex); - /* Comma, comma, nul byte */ - kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + strlen(KEX_STRICT_CLIENT ) + 1; + if (session->server) { + /* Comma, nul byte */ + kex_len = len + 1 + strlen(KEX_STRICT_SERVER) + 1; + } else { + /* Comma, comma, nul byte */ + kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + + strlen(KEX_STRICT_CLIENT) + 1; + } if (kex_len >= MAX_PACKET_LEN) { /* Overflow */ return SSH_ERROR; @@ -826,9 +845,16 @@ int ssh_set_client_kex(ssh_session session) ssh_set_error_oom(session); return SSH_ERROR; } - snprintf(kex_tmp + len, kex_len - len, ",%s,%s", KEX_EXTENSION_CLIENT, KEX_STRICT_CLIENT); - client->methods[SSH_KEX] = kex_tmp; - + if (session->server){ + snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_STRICT_SERVER); + } else { + snprintf(kex_tmp + len, + kex_len - len, + ",%s,%s", + KEX_EXTENSION_CLIENT, + KEX_STRICT_CLIENT); + } + pkex->methods[SSH_KEX] = kex_tmp; return SSH_OK; } diff --git a/src/server.c b/src/server.c index 290b4250..28c3c015 100644 --- a/src/server.c +++ b/src/server.c @@ -187,7 +187,13 @@ int server_set_kex(ssh_session session) } } - return 0; + /* Do not append the extensions during rekey */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + rc = ssh_kex_append_extensions(session, server); + return rc; } int ssh_server_init_kex(ssh_session session) { From bdcdf920965f2fffc8e4ff8fc5675992eacf3891 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 14 Dec 2023 12:22:01 +0100 Subject: [PATCH 082/795] CVE-2023-48795: Strip extensions from both kex lists for matching Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/kex.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/kex.c b/src/kex.c index 86b42785..0df4d3eb 100644 --- a/src/kex.c +++ b/src/kex.c @@ -957,11 +957,19 @@ int ssh_kex_select_methods (ssh_session session) enum ssh_key_exchange_e kex_type; int i; - /* Here we should drop the ext-info-c from the list so we avoid matching. + /* Here we should drop the extensions from the list so we avoid matching. * it. We added it to the end, so we can just truncate the string here */ - ext_start = strstr(client->methods[SSH_KEX], ","KEX_EXTENSION_CLIENT); - if (ext_start != NULL) { - ext_start[0] = '\0'; + if (session->client) { + ext_start = strstr(client->methods[SSH_KEX], "," KEX_EXTENSION_CLIENT); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + if (session->server) { + ext_start = strstr(server->methods[SSH_KEX], "," KEX_STRICT_SERVER); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } } for (i = 0; i < SSH_KEX_METHODS; i++) { From a8b9d1368724cb237743ebc98218b7fe713459c8 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 14 Dec 2023 12:47:48 +0100 Subject: [PATCH 083/795] CVE-2023-48795: tests: Adjust calculation to strict kex Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/client/torture_rekey.c | 55 ++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/tests/client/torture_rekey.c b/tests/client/torture_rekey.c index 0fc13b8b..f6a633fc 100644 --- a/tests/client/torture_rekey.c +++ b/tests/client/torture_rekey.c @@ -148,6 +148,29 @@ static void torture_rekey_default(void **state) ssh_disconnect(s->ssh.session); } +static void sanity_check_session(void **state) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + + c = s->ssh.session->current_crypto; + assert_non_null(c); + 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); + /* 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); + assert_int_equal(c->in_cipher->packets, s->ssh.session->recv_seq); + } else { + /* Otherwise we have less encrypted packets than transferred + * (first are not encrypted) */ + assert_true(c->out_cipher->packets < s->ssh.session->send_seq); + assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + } +} + /* We lower the rekey limits manually and check that the rekey * really happens when sending data */ @@ -166,16 +189,10 @@ static void torture_rekey_send(void **state) rc = ssh_connect(s->ssh.session); assert_ssh_return_code(s->ssh.session, rc); - /* The blocks limit is set correctly */ - 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); - /* We should have less encrypted packets than transferred (first are not encrypted) */ - assert_true(c->out_cipher->packets < s->ssh.session->send_seq); - assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + sanity_check_session(state); /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); secret_hash = malloc(c->digest_len); assert_non_null(secret_hash); memcpy(secret_hash, c->secret_hash, c->digest_len); @@ -273,15 +290,10 @@ static void torture_rekey_recv(void **state) mode_t mask; int rc; - /* The blocks limit is set correctly */ + sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ c = s->ssh.session->current_crypto; assert_non_null(c); - 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); - /* We should have less encrypted packets than transferred (first are not encrypted) */ - assert_true(c->out_cipher->packets < s->ssh.session->send_seq); - assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); - /* Copy the initial secret hash = session_id so we know we changed keys later */ secret_hash = malloc(c->digest_len); assert_non_null(secret_hash); memcpy(secret_hash, c->secret_hash, c->digest_len); @@ -468,15 +480,10 @@ static void torture_rekey_different_kex(void **state) assert_ssh_return_code(s->ssh.session, rc); /* The blocks limit is set correctly */ - 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); - /* We should have less encrypted packets than transferred (first are not encrypted) */ - assert_true(c->out_cipher->packets < s->ssh.session->send_seq); - assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + sanity_check_session(state); /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); secret_hash = malloc(c->digest_len); assert_non_null(secret_hash); memcpy(secret_hash, c->secret_hash, c->digest_len); From a16f34c57a4034f940c557936fd9434976adabcf Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 15 Dec 2023 10:30:09 +0100 Subject: [PATCH 084/795] CVE-2023-6918: kdf: Reformat Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/kdf.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/kdf.c b/src/kdf.c index 44f06631..987ae972 100644 --- a/src/kdf.c +++ b/src/kdf.c @@ -58,7 +58,7 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) } ctx->digest_type = type; - switch(type){ + switch (type) { case SSH_KDF_SHA1: ctx->ctx.sha1_ctx = sha1_init(); return ctx; @@ -79,7 +79,7 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) { - switch(ctx->digest_type){ + switch (ctx->digest_type) { case SSH_KDF_SHA1: sha1_update(ctx->ctx.sha1_ctx, data, len); break; @@ -97,26 +97,28 @@ static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) { - switch(ctx->digest_type){ + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_final(md,ctx->ctx.sha1_ctx); + sha1_final(md, ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_final(md,ctx->ctx.sha256_ctx); + sha256_final(md, ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_final(md,ctx->ctx.sha384_ctx); + sha384_final(md, ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_final(md,ctx->ctx.sha512_ctx); + sha512_final(md, ctx->ctx.sha512_ctx); break; } SAFE_FREE(ctx); } int sshkdf_derive_key(struct ssh_crypto_struct *crypto, - unsigned char *key, size_t key_len, - uint8_t key_type, unsigned char *output, + unsigned char *key, + size_t key_len, + uint8_t key_type, + unsigned char *output, size_t requested_len) { /* Can't use VLAs with Visual Studio, so allocate the biggest From 10c200037a82218d43c30ff2fcda0af7fbe7168e Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 15 Dec 2023 12:55:27 +0100 Subject: [PATCH 085/795] CVE-2023-6918: Remove unused evp functions and types Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/libcrypto.h | 5 --- include/libssh/libgcrypt.h | 1 - include/libssh/libmbedcrypto.h | 1 - include/libssh/wrapper.h | 4 -- src/libcrypto.c | 54 ------------------------- src/libgcrypt.c | 53 ------------------------ src/libmbedcrypto.c | 74 ---------------------------------- 7 files changed, 192 deletions(-) diff --git a/include/libssh/libcrypto.h b/include/libssh/libcrypto.h index 79a5fd5c..2f6bdc0a 100644 --- a/include/libssh/libcrypto.h +++ b/include/libssh/libcrypto.h @@ -40,11 +40,6 @@ typedef EVP_MD_CTX* SHA384CTX; typedef EVP_MD_CTX* SHA512CTX; typedef EVP_MD_CTX* MD5CTX; typedef EVP_MD_CTX* HMACCTX; -#ifdef HAVE_ECC -typedef EVP_MD_CTX *EVPCTX; -#else -typedef void *EVPCTX; -#endif #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH #define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH diff --git a/include/libssh/libgcrypt.h b/include/libssh/libgcrypt.h index 966fb044..a8044545 100644 --- a/include/libssh/libgcrypt.h +++ b/include/libssh/libgcrypt.h @@ -32,7 +32,6 @@ typedef gcry_md_hd_t SHA384CTX; typedef gcry_md_hd_t SHA512CTX; typedef gcry_md_hd_t MD5CTX; typedef gcry_md_hd_t HMACCTX; -typedef gcry_md_hd_t EVPCTX; #define SHA_DIGEST_LENGTH 20 #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH #define MD5_DIGEST_LEN 16 diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h index a4ee010b..918fe293 100644 --- a/include/libssh/libmbedcrypto.h +++ b/include/libssh/libmbedcrypto.h @@ -42,7 +42,6 @@ typedef mbedtls_md_context_t *SHA384CTX; typedef mbedtls_md_context_t *SHA512CTX; typedef mbedtls_md_context_t *MD5CTX; typedef mbedtls_md_context_t *HMACCTX; -typedef mbedtls_md_context_t *EVPCTX; #define SHA_DIGEST_LENGTH 20 #define SHA_DIGEST_LEN SHA_DIGEST_LENGTH diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h index 36589cff..07e64018 100644 --- a/include/libssh/wrapper.h +++ b/include/libssh/wrapper.h @@ -95,10 +95,6 @@ void sha512_update(SHA512CTX c, const void *data, size_t len); void sha512_final(unsigned char *md,SHA512CTX c); void sha512(const unsigned char *digest, size_t len, unsigned char *hash); -void evp(int nid, unsigned char *digest, size_t len, unsigned char *hash, unsigned int *hlen); -EVPCTX evp_init(int nid); -void evp_update(EVPCTX ctx, const void *data, size_t len); -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen); HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); int hmac_update(HMACCTX c, const void *data, size_t len); diff --git a/src/libcrypto.c b/src/libcrypto.c index d30c1ac5..f45ffa96 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -127,60 +127,6 @@ ENGINE *pki_get_engine(void) } #endif /* WITH_PKCS11_PROVIDER */ -#ifdef HAVE_OPENSSL_ECC -static const EVP_MD *nid_to_evpmd(int nid) -{ - switch (nid) { - case NID_X9_62_prime256v1: - return EVP_sha256(); - case NID_secp384r1: - return EVP_sha384(); - case NID_secp521r1: - return EVP_sha512(); - default: - return NULL; - } - - return NULL; -} - -void evp(int nid, unsigned char *digest, size_t len, unsigned char *hash, unsigned int *hlen) -{ - const EVP_MD *evp_md = nid_to_evpmd(nid); - EVP_MD_CTX *md = EVP_MD_CTX_new(); - - EVP_DigestInit(md, evp_md); - EVP_DigestUpdate(md, digest, len); - EVP_DigestFinal(md, hash, hlen); - EVP_MD_CTX_free(md); -} - -EVPCTX evp_init(int nid) -{ - const EVP_MD *evp_md = nid_to_evpmd(nid); - - EVPCTX ctx = EVP_MD_CTX_new(); - if (ctx == NULL) { - return NULL; - } - - EVP_DigestInit(ctx, evp_md); - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, size_t len) -{ - EVP_DigestUpdate(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - EVP_DigestFinal(ctx, md, mdlen); - EVP_MD_CTX_free(ctx); -} -#endif /* HAVE_OPENSSL_ECC */ - #ifdef HAVE_OPENSSL_EVP_KDF_CTX #if OPENSSL_VERSION_NUMBER < 0x30000000L static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 58f51095..4feda00c 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -69,59 +69,6 @@ static int alloc_key(struct ssh_cipher_struct *cipher) { void ssh_reseed(void){ } -#ifdef HAVE_GCRYPT_ECC -static int nid_to_md_algo(int nid) -{ - switch (nid) { - case NID_gcrypt_nistp256: - return GCRY_MD_SHA256; - case NID_gcrypt_nistp384: - return GCRY_MD_SHA384; - case NID_gcrypt_nistp521: - return GCRY_MD_SHA512; - } - return GCRY_MD_NONE; -} - -void evp(int nid, unsigned char *digest, size_t len, - unsigned char *hash, unsigned int *hlen) -{ - int algo = nid_to_md_algo(nid); - - /* Note: What gcrypt calls 'hash' is called 'digest' here and - vice-versa. */ - gcry_md_hash_buffer(algo, hash, digest, len); - *hlen = gcry_md_get_algo_dlen(algo); -} - -EVPCTX evp_init(int nid) -{ - gcry_error_t err; - int algo = nid_to_md_algo(nid); - EVPCTX ctx; - - err = gcry_md_open(&ctx, algo, 0); - if (err) { - return NULL; - } - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, size_t len) -{ - gcry_md_write(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - int algo = gcry_md_get_algo(ctx); - *mdlen = gcry_md_get_algo_dlen(algo); - memcpy(md, gcry_md_read(ctx, algo), *mdlen); - gcry_md_close(ctx); -} -#endif - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, uint8_t key_type, unsigned char *output, diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index bc12a820..e1056174 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -51,80 +51,6 @@ void ssh_reseed(void) mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); } -static mbedtls_md_type_t nid_to_md_algo(int nid) -{ - switch (nid) { - case NID_mbedtls_nistp256: - return MBEDTLS_MD_SHA256; - case NID_mbedtls_nistp384: - return MBEDTLS_MD_SHA384; - case NID_mbedtls_nistp521: - return MBEDTLS_MD_SHA512; - } - return MBEDTLS_MD_NONE; -} - -void evp(int nid, unsigned char *digest, size_t len, - unsigned char *hash, unsigned int *hlen) -{ - mbedtls_md_type_t algo = nid_to_md_algo(nid); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(algo); - - - if (md_info != NULL) { - *hlen = mbedtls_md_get_size(md_info); - mbedtls_md(md_info, digest, len, hash); - } -} - -EVPCTX evp_init(int nid) -{ - EVPCTX ctx = NULL; - int rc; - mbedtls_md_type_t algo = nid_to_md_algo(nid); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(algo); - - if (md_info == NULL) { - return NULL; - } - - ctx = malloc(sizeof(mbedtls_md_context_t)); - if (ctx == NULL) { - return NULL; - } - - mbedtls_md_init(ctx); - - rc = mbedtls_md_setup(ctx, md_info, 0); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - rc = mbedtls_md_starts(ctx); - if (rc != 0) { - SAFE_FREE(ctx); - return NULL; - } - - return ctx; -} - -void evp_update(EVPCTX ctx, const void *data, size_t len) -{ - mbedtls_md_update(ctx, data, len); -} - -void evp_final(EVPCTX ctx, unsigned char *md, unsigned int *mdlen) -{ - *mdlen = mbedtls_md_get_size(ctx->MBEDTLS_PRIVATE(md_info)); - mbedtls_md_finish(ctx, md); - mbedtls_md_free(ctx); - SAFE_FREE(ctx); -} - int ssh_kdf(struct ssh_crypto_struct *crypto, unsigned char *key, size_t key_len, uint8_t key_type, unsigned char *output, From 5c407d2f16ab76c3dbc8324b4138f405177219b6 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 15 Dec 2023 12:55:54 +0100 Subject: [PATCH 086/795] CVE-2023-6918: Systematically check return values when calculating digests with all crypto backends Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/wrapper.h | 34 ++++---- src/kdf.c | 96 ++++++++++++++++++----- src/md_crypto.c | 161 ++++++++++++++++++++++++++++++-------- src/md_gcrypt.c | 107 +++++++++++++++++++++---- src/md_mbedcrypto.c | 165 +++++++++++++++++++++++++++++++-------- src/session.c | 72 ++++++++++++----- 6 files changed, 504 insertions(+), 131 deletions(-) diff --git a/include/libssh/wrapper.h b/include/libssh/wrapper.h index 07e64018..b3e28eac 100644 --- a/include/libssh/wrapper.h +++ b/include/libssh/wrapper.h @@ -72,29 +72,33 @@ struct ssh_crypto_struct; typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; MD5CTX md5_init(void); -void md5_update(MD5CTX c, const void *data, size_t len); -void md5_final(unsigned char *md,MD5CTX c); +void md5_ctx_free(MD5CTX); +int md5_update(MD5CTX c, const void *data, size_t len); +int md5_final(unsigned char *md, MD5CTX c); SHACTX sha1_init(void); -void sha1_update(SHACTX c, const void *data, size_t len); -void sha1_final(unsigned char *md,SHACTX c); -void sha1(const unsigned char *digest,size_t len,unsigned char *hash); +void sha1_ctx_free(SHACTX); +int sha1_update(SHACTX c, const void *data, size_t len); +int sha1_final(unsigned char *md,SHACTX c); +int sha1(const unsigned char *digest,size_t len, unsigned char *hash); SHA256CTX sha256_init(void); -void sha256_update(SHA256CTX c, const void *data, size_t len); -void sha256_final(unsigned char *md,SHA256CTX c); -void sha256(const unsigned char *digest, size_t len, unsigned char *hash); +void sha256_ctx_free(SHA256CTX); +int sha256_update(SHA256CTX c, const void *data, size_t len); +int sha256_final(unsigned char *md,SHA256CTX c); +int sha256(const unsigned char *digest, size_t len, unsigned char *hash); SHA384CTX sha384_init(void); -void sha384_update(SHA384CTX c, const void *data, size_t len); -void sha384_final(unsigned char *md,SHA384CTX c); -void sha384(const unsigned char *digest, size_t len, unsigned char *hash); +void sha384_ctx_free(SHA384CTX); +int sha384_update(SHA384CTX c, const void *data, size_t len); +int sha384_final(unsigned char *md,SHA384CTX c); +int sha384(const unsigned char *digest, size_t len, unsigned char *hash); SHA512CTX sha512_init(void); -void sha512_update(SHA512CTX c, const void *data, size_t len); -void sha512_final(unsigned char *md,SHA512CTX c); -void sha512(const unsigned char *digest, size_t len, unsigned char *hash); - +void sha512_ctx_free(SHA512CTX); +int sha512_update(SHA512CTX c, const void *data, size_t len); +int sha512_final(unsigned char *md,SHA512CTX c); +int sha512(const unsigned char *digest, size_t len, unsigned char *hash); HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); int hmac_update(HMACCTX c, const void *data, size_t len); diff --git a/src/kdf.c b/src/kdf.c index 987ae972..a8e534e5 100644 --- a/src/kdf.c +++ b/src/kdf.c @@ -77,41 +77,64 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) } } -static void ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +static void ssh_mac_ctx_free(ssh_mac_ctx ctx) { + if (ctx == NULL) { + return; + } + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_update(ctx->ctx.sha1_ctx, data, len); + sha1_ctx_free(ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_update(ctx->ctx.sha256_ctx, data, len); + sha256_ctx_free(ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_update(ctx->ctx.sha384_ctx, data, len); + sha384_ctx_free(ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_update(ctx->ctx.sha512_ctx, data, len); + sha512_ctx_free(ctx->ctx.sha512_ctx); break; } + SAFE_FREE(ctx); +} + +static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +{ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + return sha1_update(ctx->ctx.sha1_ctx, data, len); + case SSH_KDF_SHA256: + return sha256_update(ctx->ctx.sha256_ctx, data, len); + case SSH_KDF_SHA384: + return sha384_update(ctx->ctx.sha384_ctx, data, len); + case SSH_KDF_SHA512: + return sha512_update(ctx->ctx.sha512_ctx, data, len); + } + return SSH_ERROR; } -static void ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) { + int rc = SSH_ERROR; + switch (ctx->digest_type) { case SSH_KDF_SHA1: - sha1_final(md, ctx->ctx.sha1_ctx); + rc = sha1_final(md, ctx->ctx.sha1_ctx); break; case SSH_KDF_SHA256: - sha256_final(md, ctx->ctx.sha256_ctx); + rc = sha256_final(md, ctx->ctx.sha256_ctx); break; case SSH_KDF_SHA384: - sha384_final(md, ctx->ctx.sha384_ctx); + rc = sha384_final(md, ctx->ctx.sha384_ctx); break; case SSH_KDF_SHA512: - sha512_final(md, ctx->ctx.sha512_ctx); + rc = sha512_final(md, ctx->ctx.sha512_ctx); break; } SAFE_FREE(ctx); + return rc; } int sshkdf_derive_key(struct ssh_crypto_struct *crypto, @@ -126,6 +149,7 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, unsigned char digest[DIGEST_MAX_LEN]; size_t output_len = crypto->digest_len; ssh_mac_ctx ctx; + int rc; if (DIGEST_MAX_LEN < crypto->digest_len) { return -1; @@ -136,11 +160,30 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, return -1; } - ssh_mac_update(ctx, key, key_len); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, &key_type, 1); - ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); - ssh_mac_final(digest, ctx); + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, &key_type, 1); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } if (requested_len < output_len) { output_len = requested_len; @@ -152,10 +195,25 @@ int sshkdf_derive_key(struct ssh_crypto_struct *crypto, if (ctx == NULL) { return -1; } - ssh_mac_update(ctx, key, key_len); - ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); - ssh_mac_update(ctx, output, output_len); - ssh_mac_final(digest, ctx); + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, output, output_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } if (requested_len < output_len + crypto->digest_len) { memcpy(output + output_len, digest, requested_len - output_len); } else { diff --git a/src/md_crypto.c b/src/md_crypto.c index f5104f04..f7cda8dd 100644 --- a/src/md_crypto.c +++ b/src/md_crypto.c @@ -25,6 +25,7 @@ #include "libssh/crypto.h" #include "libssh/wrapper.h" +#include #include #include #include @@ -46,28 +47,49 @@ sha1_init(void) } void +sha1_ctx_free(SHACTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha1_update(SHACTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1_final(unsigned char *md, SHACTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1(const unsigned char *digest, size_t len, unsigned char *hash) { SHACTX c = sha1_init(); - if (c != NULL) { - sha1_update(c, digest, len); - sha1_final(hash, c); + int rc; + + if (c == NULL) { + return SSH_ERROR; } + rc = sha1_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha1_final(hash, c); } SHA256CTX @@ -87,28 +109,49 @@ sha256_init(void) } void +sha256_ctx_free(SHA256CTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha256_update(SHA256CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256_final(unsigned char *md, SHA256CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256(const unsigned char *digest, size_t len, unsigned char *hash) { SHA256CTX c = sha256_init(); - if (c != NULL) { - sha256_update(c, digest, len); - sha256_final(hash, c); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha256_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; } + return sha256_final(hash, c); } SHA384CTX @@ -128,28 +171,49 @@ sha384_init(void) } void +sha384_ctx_free(SHA384CTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha384_update(SHA384CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384_final(unsigned char *md, SHA384CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384(const unsigned char *digest, size_t len, unsigned char *hash) { SHA384CTX c = sha384_init(); - if (c != NULL) { - sha384_update(c, digest, len); - sha384_final(hash, c); + int rc; + + if (c == NULL) { + return SSH_ERROR; } + rc = sha384_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha384_final(hash, c); } SHA512CTX @@ -169,28 +233,49 @@ sha512_init(void) } void +sha512_ctx_free(SHA512CTX c) +{ + EVP_MD_CTX_free(c); +} + +int sha512_update(SHA512CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512_final(unsigned char *md, SHA512CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512(const unsigned char *digest, size_t len, unsigned char *hash) { SHA512CTX c = sha512_init(); - if (c != NULL) { - sha512_update(c, digest, len); - sha512_final(hash, c); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha512_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; } + return sha512_final(hash, c); } MD5CTX @@ -210,16 +295,30 @@ md5_init(void) } void +md5_ctx_free(MD5CTX c) +{ + EVP_MD_CTX_free(c); +} + +int md5_update(MD5CTX c, const void *data, size_t len) { - EVP_DigestUpdate(c, data, len); + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } -void +int md5_final(unsigned char *md, MD5CTX c) { unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); - EVP_DigestFinal(c, md, &mdlen); EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; } diff --git a/src/md_gcrypt.c b/src/md_gcrypt.c index 1f0a71f3..93c7b0d9 100644 --- a/src/md_gcrypt.c +++ b/src/md_gcrypt.c @@ -36,24 +36,40 @@ sha1_init(void) return ctx; } -void +int sha1_update(SHACTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return SSH_OK; } void +sha1_ctx_free(SHACTX c) +{ + gcry_md_close(c); +} + +int sha1_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA_DIGEST_LEN); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA_DIGEST_LEN); gcry_md_close(c); + return SSH_OK; } -void +int sha1(const unsigned char *digest, size_t len, unsigned char *hash) { gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + return SSH_OK; } SHA256CTX @@ -66,23 +82,39 @@ sha256_init(void) } void +sha256_ctx_free(SHA256CTX c) +{ + gcry_md_close(c); +} + +int sha256_update(SHACTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return SSH_OK; } -void +int sha256_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA256_DIGEST_LEN); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA256_DIGEST_LEN); gcry_md_close(c); + return SSH_OK; } -void +int sha256(const unsigned char *digest, size_t len, unsigned char *hash) { gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); + return SSH_OK; } SHA384CTX @@ -95,23 +127,39 @@ sha384_init(void) } void +sha384_ctx_free(SHA384CTX c) +{ + gcry_md_close(c); +} + +int sha384_update(SHACTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return SSH_OK; } -void +int sha384_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA384_DIGEST_LEN); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA384_DIGEST_LEN); gcry_md_close(c); + return SSH_OK; } -void +int sha384(const unsigned char *digest, size_t len, unsigned char *hash) { gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); + return SSH_OK; } SHA512CTX @@ -124,23 +172,39 @@ sha512_init(void) } void +sha512_ctx_free(SHA512CTX c) +{ + gcry_md_close(c); +} + +int sha512_update(SHACTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return SSH_OK; } -void +int sha512_final(unsigned char *md, SHACTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), SHA512_DIGEST_LEN); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA512_DIGEST_LEN); gcry_md_close(c); + return SSH_OK; } -void +int sha512(const unsigned char *digest, size_t len, unsigned char *hash) { gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); + return SSH_OK; } MD5CTX @@ -153,15 +217,30 @@ md5_init(void) } void +md5_ctx_free(MD5CTX c) +{ + gcry_md_close(c); +} + +int md5_update(MD5CTX c, const void *data, size_t len) { gcry_md_write(c, data, len); + return SSH_OK; } -void +int md5_final(unsigned char *md, MD5CTX c) { + unsigned char *tmp = NULL; + gcry_md_final(c); - memcpy(md, gcry_md_read(c, 0), MD5_DIGEST_LEN); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, MD5_DIGEST_LEN); gcry_md_close(c); + return SSH_OK; } diff --git a/src/md_mbedcrypto.c b/src/md_mbedcrypto.c index 227e20ab..b3529b4b 100644 --- a/src/md_mbedcrypto.c +++ b/src/md_mbedcrypto.c @@ -64,27 +64,48 @@ sha1_init(void) } void +sha1_ctx_free(SHACTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha1_update(SHACTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1_final(unsigned char *md, SHACTX c) { - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); + int rc = mbedtls_md_finish(c, md); + sha1_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha1(const unsigned char *digest, size_t len, unsigned char *hash) { const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; } + return SSH_OK; } SHA256CTX @@ -122,27 +143,48 @@ sha256_init(void) } void +sha256_ctx_free(SHA256CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha256_update(SHA256CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256_final(unsigned char *md, SHA256CTX c) { - mbedtls_md_finish(c, md); + int rc = mbedtls_md_finish(c, md); mbedtls_md_free(c); SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha256(const unsigned char *digest, size_t len, unsigned char *hash) { + int rc; const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; } + return SSH_OK; } SHA384CTX @@ -180,27 +222,48 @@ sha384_init(void) } void +sha384_ctx_free(SHA384CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha384_update(SHA384CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384_final(unsigned char *md, SHA384CTX c) { - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); + int rc = mbedtls_md_finish(c, md); + sha384_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha384(const unsigned char *digest, size_t len, unsigned char *hash) { const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; } + return SSH_OK; } SHA512CTX @@ -237,27 +300,48 @@ sha512_init(void) } void +sha512_ctx_free(SHA512CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int sha512_update(SHA512CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512_final(unsigned char *md, SHA512CTX c) { - mbedtls_md_finish(c, md); - mbedtls_md_free(c); - SAFE_FREE(c); + int rc = mbedtls_md_finish(c, md); + sha512_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int sha512(const unsigned char *digest, size_t len, unsigned char *hash) { const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - if (md_info != NULL) { - mbedtls_md(md_info, digest, len, hash); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } MD5CTX @@ -294,15 +378,30 @@ md5_init(void) } void +md5_ctx_free(MD5CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int md5_update(MD5CTX c, const void *data, size_t len) { - mbedtls_md_update(c, data, len); + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } -void +int md5_final(unsigned char *md, MD5CTX c) { - mbedtls_md_finish(c, md); + int rc = mbedtls_md_finish(c, md); mbedtls_md_free(c); SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; } diff --git a/src/session.c b/src/session.c index c3aaf32f..6b87fe46 100644 --- a/src/session.c +++ b/src/session.c @@ -1047,7 +1047,18 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) *hash = NULL; if (session->current_crypto == NULL || session->current_crypto->server_pubkey == NULL) { - ssh_set_error(session,SSH_FATAL,"No current cryptographic context"); + ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { return SSH_ERROR; } @@ -1062,25 +1073,21 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) return SSH_ERROR; } - rc = ssh_get_server_publickey(session, &pubkey); + rc = md5_update(ctx, + ssh_string_data(pubkey_blob), + ssh_string_len(pubkey_blob)); if (rc != SSH_OK) { - md5_final(h, ctx); + md5_ctx_free(ctx); SAFE_FREE(h); - return SSH_ERROR; + return rc; } - - rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); - ssh_key_free(pubkey); + SSH_STRING_FREE(pubkey_blob); + rc = md5_final(h, ctx); if (rc != SSH_OK) { - md5_final(h, ctx); SAFE_FREE(h); - return SSH_ERROR; + return rc; } - md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); - SSH_STRING_FREE(pubkey_blob); - md5_final(h, ctx); - *hash = h; return MD5_DIGEST_LEN; @@ -1200,8 +1207,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha1_final(h, ctx); + rc = sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + sha1_ctx_free(ctx); + goto out; + } + rc = sha1_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = SHA_DIGEST_LEN; } @@ -1223,8 +1239,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - sha256_final(h, ctx); + rc = sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + sha256_ctx_free(ctx); + goto out; + } + rc = sha256_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = SHA256_DIGEST_LEN; } @@ -1254,8 +1279,17 @@ int ssh_get_publickey_hash(const ssh_key key, goto out; } - md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); - md5_final(h, ctx); + rc = md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob)); + if (rc != SSH_OK) { + free(h); + md5_ctx_free(ctx); + goto out; + } + rc = md5_final(h, ctx); + if (rc != SSH_OK) { + free(h); + goto out; + } *hlen = MD5_DIGEST_LEN; } From 59c00c66c4466bacaddf73dcd853ac1dac95ba39 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 15 Dec 2023 13:35:14 +0100 Subject: [PATCH 087/795] CVE-2023-6918: kdf: Detect context init failures Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/kdf.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/kdf.c b/src/kdf.c index a8e534e5..6bc477ce 100644 --- a/src/kdf.c +++ b/src/kdf.c @@ -61,20 +61,32 @@ static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) switch (type) { case SSH_KDF_SHA1: ctx->ctx.sha1_ctx = sha1_init(); + if (ctx->ctx.sha1_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA256: ctx->ctx.sha256_ctx = sha256_init(); + if (ctx->ctx.sha256_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA384: ctx->ctx.sha384_ctx = sha384_init(); + if (ctx->ctx.sha384_ctx == NULL) { + goto err; + } return ctx; case SSH_KDF_SHA512: ctx->ctx.sha512_ctx = sha512_init(); + if (ctx->ctx.sha512_ctx == NULL) { + goto err; + } return ctx; - default: - SAFE_FREE(ctx); - return NULL; } +err: + SAFE_FREE(ctx); + return NULL; } static void ssh_mac_ctx_free(ssh_mac_ctx ctx) From b3de3a33352a78214a534005e3e4f0576dcc9e17 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 15 Dec 2023 15:39:12 +0100 Subject: [PATCH 088/795] CVE-2023-6918: tests: Code coverage for ssh_get_pubkey_hash() Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/client/torture_session.c | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index d10b328d..8a43e586 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -478,6 +478,38 @@ torture_channel_read_stderr(void **state) 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); + } +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { @@ -514,6 +546,9 @@ int torture_run_tests(void) { 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), }; ssh_init(); From 4f997aee7c7d7ea346b3e8ba505da0b7601ff318 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Dec 2023 10:32:40 +0100 Subject: [PATCH 089/795] Fix regression in IPv6 addresses in hostname parsing Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- include/libssh/config_parser.h | 11 ++++++++--- src/config.c | 4 ++-- src/config_parser.c | 16 +++++++++++----- src/options.c | 10 ++-------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h index a7dd42a2..ca353432 100644 --- a/include/libssh/config_parser.h +++ b/include/libssh/config_parser.h @@ -30,6 +30,8 @@ extern "C" { #endif +#include + char *ssh_config_get_cmd(char **str); char *ssh_config_get_token(char **str); @@ -49,14 +51,17 @@ int ssh_config_get_yesno(char **str, int notfound); * be stored or NULL if we do not care about the result. * @param[out] port Pointer to the location, where the new port will * be stored or NULL if we do not care about the result. + * @param[in] ignore_port Set to true if the we should not attempt to parse + * port number. * * @returns SSH_OK if the provided string is in format of SSH URI, * SSH_ERROR on failure */ int ssh_config_parse_uri(const char *tok, - char **username, - char **hostname, - char **port); + char **username, + char **hostname, + char **port, + bool ignore_port); #ifdef __cplusplus } diff --git a/src/config.c b/src/config.c index 5eedbce9..7135c3b1 100644 --- a/src/config.c +++ b/src/config.c @@ -464,7 +464,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } if (parse_entry) { /* We actually care only about the first item */ - rv = ssh_config_parse_uri(cp, &username, &hostname, &port); + rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); /* The rest of the list needs to be passed on */ if (endp != NULL) { next = strdup(endp + 1); @@ -475,7 +475,7 @@ ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) } } else { /* The rest is just sanity-checked to avoid failures later */ - rv = ssh_config_parse_uri(cp, NULL, NULL, NULL); + rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); } if (rv != SSH_OK) { goto out; diff --git a/src/config_parser.c b/src/config_parser.c index 9ffc8b8b..5f30cd3e 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -162,9 +162,10 @@ int ssh_config_get_yesno(char **str, int notfound) } int ssh_config_parse_uri(const char *tok, - char **username, - char **hostname, - char **port) + char **username, + char **hostname, + char **port, + bool ignore_port) { char *endp = NULL; long port_n; @@ -210,12 +211,17 @@ int ssh_config_parse_uri(const char *tok, if (endp == NULL) { goto error; } - } else { - /* Hostnames or aliases expand to the last colon or to the end */ + } else if (!ignore_port) { + /* Hostnames or aliases expand to the last colon (if port is requested) + * or to the end */ endp = strrchr(tok, ':'); if (endp == NULL) { endp = strchr(tok, '\0'); } + } else { + /* If no port is requested, expand to the end of line + * (to accommodate the IPv6 addresses) */ + endp = strchr(tok, '\0'); } if (tok == endp) { /* Zero-length hostnames are not valid */ diff --git a/src/options.c b/src/options.c index 2e73be46..676c49e7 100644 --- a/src/options.c +++ b/src/options.c @@ -634,17 +634,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_invalid(session); return -1; } else { - char *username = NULL, *hostname = NULL, *port = NULL; - rc = ssh_config_parse_uri(value, &username, &hostname, &port); + char *username = NULL, *hostname = NULL; + rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); if (rc != SSH_OK) { return -1; } - if (port != NULL) { - SAFE_FREE(username); - SAFE_FREE(hostname); - SAFE_FREE(port); - return -1; - } if (username != NULL) { SAFE_FREE(session->opts.username); session->opts.username = username; From 6f6e453d7b0ad4ee6a6f6a1c96a9a6b27821410d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Dec 2023 09:52:18 +0100 Subject: [PATCH 090/795] tests: Increase test coverage for IPv6 address parsing as hostnames This was an issue in cockpit: https://github.com/cockpit-project/cockpit/issues/19772 Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/unittests/torture_config.c | 49 +++++++++++++++++++++++++++++++ tests/unittests/torture_options.c | 16 ++++++++++ 2 files changed, 65 insertions(+) diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index bc6b08f9..751aa126 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -2332,6 +2332,53 @@ static void torture_config_make_absolute_no_sshdir(void **state) torture_config_make_absolute_int(state, 1); } +static void torture_config_parse_uri(void **state) +{ + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "localhost"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + /* do not want port */ + rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); +} + int torture_run_tests(void) { int rc; @@ -2424,6 +2471,8 @@ int torture_run_tests(void) setup, teardown), cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, setup_no_sshdir, teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, teardown), }; diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 5ba3bdc6..b07712d8 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -57,6 +57,20 @@ static void torture_options_set_host(void **state) { assert_non_null(session->opts.host); assert_string_equal(session->opts.host, "localhost"); + /* IPv4 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "127.1.1.1"); + assert_null(session->opts.username); + + /* IPv6 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "::1"); + assert_null(session->opts.username); + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); assert_true(rc == 0); assert_non_null(session->opts.host); @@ -64,12 +78,14 @@ static void torture_options_set_host(void **state) { assert_non_null(session->opts.username); assert_string_equal(session->opts.username, "guru"); + /* more @ in uri is OK -- it should go to the username */ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname"); assert_true(rc == 0); assert_non_null(session->opts.host); assert_string_equal(session->opts.host, "hostname"); assert_non_null(session->opts.username); assert_string_equal(session->opts.username, "at@login"); + } static void torture_options_set_ciphers(void **state) { From d53236d69faa1bec0aea9d1c7d27e5db81cd739b Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 2 Jan 2024 16:13:36 +0100 Subject: [PATCH 091/795] Fix typos detected with new codespell Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- config.h.cmake | 2 +- examples/ssh_server.c | 2 +- src/libmbedcrypto.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.h.cmake b/config.h.cmake index 5d0afdd7..391ee162 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -91,7 +91,7 @@ /* Define to 1 if you have elliptic curve cryptography */ #cmakedefine HAVE_ECC 1 -/* Define to 1 if you have gl_flags as a glob_t sturct member */ +/* Define to 1 if you have gl_flags as a glob_t struct member */ #cmakedefine HAVE_GLOB_GL_FLAGS_MEMBER 1 /* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */ diff --git a/examples/ssh_server.c b/examples/ssh_server.c index ef444458..fab8f96e 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -512,7 +512,7 @@ static int shell_request(ssh_session session, ssh_channel channel, static int subsystem_request(ssh_session session, ssh_channel channel, const char *subsystem, void *userdata) { - /* subsystem requests behave simillarly to exec requests. */ + /* subsystem requests behave similarly to exec requests. */ if (strcmp(subsystem, "sftp") == 0) { return exec_request(session, channel, SFTP_SERVER_PATH, userdata); } diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index e1056174..55951764 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -213,7 +213,7 @@ cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, goto error; } - /* libssh only encypts and decrypts packets that are multiples of a block + /* libssh only encrypts and decrypts packets that are multiples of a block * size, and no padding is used */ rc = mbedtls_cipher_set_padding_mode(&cipher->encrypt_ctx, MBEDTLS_PADDING_NONE); From 71c47b464aa6eecccf88ab0ff7b4388cc8c312dd Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Sun, 24 Dec 2023 01:10:06 +0100 Subject: [PATCH 092/795] Generate a tagfile with Doxygen This creates an XML file with information about each symbol, including the anchors used in the URL. It's useful to have this to generate links to the documentation from other documentation systems. Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen --- .gitignore | 1 + doc/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 47eb46ac..831f32f4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ compile_commands.json tags /build /obj* +doc/tags.xml diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index fcc15b90..c8b5a7e5 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -14,6 +14,7 @@ if (DOXYGEN_FOUND) set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) set(DOXYGEN_MARKDOWN_SUPPORT YES) set(DOXYGEN_FULL_PATH_NAMES NO) + set(DOXYGEN_GENERATE_TAGFILE "tags.xml") set(DOXYGEN_PREDEFINED DOXYGEN WITH_SERVER From 283d75802d419694dcec3dae95c0c8b6a64d33bf Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 20 Dec 2023 09:43:18 +0100 Subject: [PATCH 093/795] session: Avoid memory leaks Thanks coverity CID 1531417 Signed-off-by: Jakub Jelen Reviewed-by: Norbert Pocs --- src/session.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/session.c b/src/session.c index 6b87fe46..58b879ac 100644 --- a/src/session.c +++ b/src/session.c @@ -1026,8 +1026,8 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) { ssh_key pubkey = NULL; ssh_string pubkey_blob = NULL; - MD5CTX ctx; - unsigned char *h; + MD5CTX ctx = NULL; + unsigned char *h = NULL; int rc; if (session == NULL || hash == NULL) { @@ -1064,11 +1064,13 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); if (h == NULL) { + SSH_STRING_FREE(pubkey_blob); return SSH_ERROR; } ctx = md5_init(); if (ctx == NULL) { + SSH_STRING_FREE(pubkey_blob); SAFE_FREE(h); return SSH_ERROR; } @@ -1077,6 +1079,7 @@ int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob)); if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey_blob); md5_ctx_free(ctx); SAFE_FREE(h); return rc; From 24dfc5926473c82f0d0d2e957a8e5296c52c7f8d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 20 Dec 2023 09:58:21 +0100 Subject: [PATCH 094/795] pki: Rewrite default key format handling to improve readability ... and make coerity happy avoiding dead code CID 1531320 CID 1531321 Signed-off-by: Jakub Jelen Reviewed-by: Norbert Pocs --- src/pki.c | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/pki.c b/src/pki.c index b5d423a2..816b7e6f 100644 --- a/src/pki.c +++ b/src/pki.c @@ -874,28 +874,26 @@ ssh_pki_export_privkey_base64_format(const ssh_key privkey, return SSH_ERROR; } - /* The PEM export is supported only with OpenSSL. We fall back to - * OpenSSH key format elsewhere */ - if (format == SSH_FILE_FORMAT_DEFAULT) { + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + */ #ifdef HAVE_LIBCRYPTO - if (privkey->type != SSH_KEYTYPE_ED25519) { - format = SSH_FILE_FORMAT_PEM; - } else { -#else - if (1) { -#endif /* HAVE_LIBCRYPTO */ - format = SSH_FILE_FORMAT_OPENSSH; - } + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; } +#endif /* HAVE_LIBCRYPTO */ switch (format) { - case SSH_FILE_FORMAT_DEFAULT: case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ case SSH_FILE_FORMAT_OPENSSH: blob = ssh_pki_openssh_privkey_export(privkey, passphrase, @@ -1103,28 +1101,26 @@ ssh_pki_export_privkey_file_format(const ssh_key privkey, return SSH_EOF; } - /* The PEM export is supported only with OpenSSL. We fall back to - * OpenSSH key format elsewhere */ - if (format == SSH_FILE_FORMAT_DEFAULT) { + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + */ #ifdef HAVE_LIBCRYPTO - if (privkey->type != SSH_KEYTYPE_ED25519) { - format = SSH_FILE_FORMAT_PEM; - } else { -#else - if (1) { -#endif /* HAVE_LIBCRYPTO */ - format = SSH_FILE_FORMAT_OPENSSH; - } + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519) { + format = SSH_FILE_FORMAT_PEM; } +#endif /* HAVE_LIBCRYPTO */ switch (format) { - case SSH_FILE_FORMAT_DEFAULT: case SSH_FILE_FORMAT_PEM: blob = pki_private_key_to_pem(privkey, passphrase, auth_fn, auth_data); break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ case SSH_FILE_FORMAT_OPENSSH: blob = ssh_pki_openssh_privkey_export(privkey, passphrase, From a5cc515f02b9467488fd426a95458567781230c7 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Mon, 11 Dec 2023 18:21:17 +0100 Subject: [PATCH 095/795] Document that ssh_channel_read_nonblocking() may return SSH_EOF The current documentation incorrectly states that it will return 0 on EOF, but the function calls ssh_channel_poll() internally, which will return SSH_EOF, which will then be returned by ssh_channel_read_nonblocking(). Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen Reviewed-by: Norbert Pocs --- src/channels.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/channels.c b/src/channels.c index 7e3cc9ad..fe040c1f 100644 --- a/src/channels.c +++ b/src/channels.c @@ -3150,10 +3150,8 @@ int ssh_channel_read_timeout(ssh_channel channel, * * @param[in] is_stderr A boolean to select the stderr stream. * - * @return The number of bytes read, 0 if nothing is available or - * SSH_ERROR on error. - * - * @warning Don't forget to check for EOF as it would return 0 here. + * @return The number of bytes read (0 if nothing is available), + * SSH_ERROR on error, and SSH_EOF if the channel is EOF. * * @see ssh_channel_is_eof() */ From 8fbb12eddfbfcbf37db4fd7e4be10a1f8bb7d991 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Mon, 11 Dec 2023 18:42:37 +0100 Subject: [PATCH 096/795] Document that ssh_channel_read_nonblocking() will trigger callbacks Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen Reviewed-by: Norbert Pocs --- src/channels.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/channels.c b/src/channels.c index fe040c1f..4e3de321 100644 --- a/src/channels.c +++ b/src/channels.c @@ -3140,7 +3140,7 @@ int ssh_channel_read_timeout(ssh_channel channel, * @brief Do a nonblocking read on the channel. * * A nonblocking read on the specified channel. it will return <= count bytes of - * data read atomically. + * data read atomically. It will also trigger any callbacks set on the channel. * * @param[in] channel The channel to read from. * From 804e283c8b9a8ce544ef4f7b258fe5146e18adae Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Tue, 5 Dec 2023 17:55:25 +0100 Subject: [PATCH 097/795] Document that options set on a bind will be free'd by ssh_bind_free Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen --- include/libssh/server.h | 3 +++ src/options.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/libssh/server.h b/include/libssh/server.h index 4033dac7..12210883 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -222,6 +222,9 @@ LIBSSH_API int ssh_server_init_kex(ssh_session session); /** * @brief Free a ssh servers bind. * + * Note that this will also free options that have been set on the bind, + * including keys set with SSH_BIND_OPTIONS_IMPORT_KEY. + * * @param ssh_bind_o The ssh server bind to free. */ LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); diff --git a/src/options.c b/src/options.c index 676c49e7..2727b873 100644 --- a/src/options.c +++ b/src/options.c @@ -1926,7 +1926,8 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * This is DEPRECATED, please do not use. * * - SSH_BIND_OPTIONS_IMPORT_KEY: - * Set the Private Key for the server directly (ssh_key) + * Set the Private Key for the server directly + * (ssh_key). It will be free'd by ssh_bind_free(). * * - SSH_BIND_OPTIONS_CIPHERS_C_S: * Set the symmetric cipher client to server (const char *, From 22c41e6784ba3d41dd7678c96f0942fa8b3bf1d1 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 9 Jan 2024 08:11:28 +0100 Subject: [PATCH 098/795] Happy new year 2024! Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- include/libssh/libssh.h | 2 +- src/client.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index c8107706..77ecb4f3 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -1,7 +1,7 @@ /* * This file is part of the SSH Library * - * Copyright (c) 2003-2023 by Aris Adamantiadis and the libssh team + * Copyright (c) 2003-2024 by Aris Adamantiadis and the libssh team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public diff --git a/src/client.c b/src/client.c index a54bac60..9e1606a3 100644 --- a/src/client.c +++ b/src/client.c @@ -873,7 +873,7 @@ ssh_disconnect(ssh_session session) */ const char *ssh_copyright(void) { - return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2023 " + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2024 " "Aris Adamantiadis, Andreas Schneider " "and libssh contributors. " "Distributed under the LGPL, please refer to COPYING " From 3fa6c1639e6d1653022c6f13b70d1c2f6ca72498 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Sat, 6 Jan 2024 01:27:23 +0100 Subject: [PATCH 099/795] Remove logging functions from the threads Doxygen group The closing brace of the @addtogroup command was too low, causing some logging functions to be added to the threads group. Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen Reviewed-by: Norbert Pocs --- include/libssh/callbacks.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libssh/callbacks.h b/include/libssh/callbacks.h index 34016595..29c1d391 100644 --- a/include/libssh/callbacks.h +++ b/include/libssh/callbacks.h @@ -1056,6 +1056,7 @@ LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); * @see ssh_threads_set_callbacks */ LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); +/** @} */ /** * @brief Set the logging callback function. @@ -1073,7 +1074,6 @@ LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); */ LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); -/** @} */ #ifdef __cplusplus } #endif From 9cf3d79abc8720f19aef42b98770e6ab0bfdff88 Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Fri, 5 Jan 2024 12:53:11 +0100 Subject: [PATCH 100/795] Fix docstring for ssh_userauth_kbdint_getanswer() This incorrectly stated that it would return an integer value instead of a string. Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen --- src/auth.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/auth.c b/src/auth.c index 820d82fc..0acd9686 100644 --- a/src/auth.c +++ b/src/auth.c @@ -2210,7 +2210,8 @@ int ssh_userauth_kbdint_getnanswers(ssh_session session) * * @param[in] i index The number of the ith answer. * - * @return 0 on success, < 0 on error. + * @return The answer string, or NULL if the answer is not + * available. Do not free the string. */ const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) { From 99e8f34142b7984f17ff810821daed47bcbdaf6e Mon Sep 17 00:00:00 2001 From: JamesWrigley Date: Thu, 4 Jan 2024 22:13:57 +0100 Subject: [PATCH 101/795] Fix docstring for ssh_message_auth_password() Signed-off-by: James Wrigley Reviewed-by: Jakub Jelen --- include/libssh/server.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libssh/server.h b/include/libssh/server.h index 12210883..885ef576 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -295,7 +295,7 @@ LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); * * @param[in] msg The message to get the password from. * - * @return The username or NULL if an error occurred. + * @return The password or NULL if an error occurred. * * @see ssh_message_get() * @see ssh_message_type() From 63ee84862befb1acf0c142a6080cad9336377bd0 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Wed, 6 Dec 2023 11:39:35 +0530 Subject: [PATCH 102/795] sftp.c: Reformat sftp_init() according to the current coding style Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- src/sftp.c | 163 ++++++++++++++++++++++++++++------------------------- 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/src/sftp.c b/src/sftp.c index 56236252..8613de59 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -414,100 +414,107 @@ int sftp_get_error(sftp_session sftp) { } /* Initialize the sftp session with the server. */ -int sftp_init(sftp_session sftp) { - sftp_packet packet = NULL; - ssh_buffer buffer = NULL; - char *ext_name = NULL; - char *ext_data = NULL; - uint32_t version; - int rc; - - buffer = ssh_buffer_new(); - if (buffer == NULL) { - ssh_set_error_oom(sftp->session); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - - rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); - if (rc != SSH_OK) { - ssh_set_error_oom(sftp->session); - SSH_BUFFER_FREE(buffer); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - if (sftp_packet_write(sftp, SSH_FXP_INIT, buffer) < 0) { - SSH_BUFFER_FREE(buffer); - return -1; - } - SSH_BUFFER_FREE(buffer); +int sftp_init(sftp_session sftp) +{ + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; + int rc; - packet = sftp_packet_read(sftp); - if (packet == NULL) { - return -1; - } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - if (packet->type != SSH_FXP_VERSION) { - ssh_set_error(sftp->session, SSH_FATAL, - "Received a %d messages instead of SSH_FXP_VERSION", packet->type); - return -1; - } + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } - /* TODO: are we sure there are 4 bytes ready? */ - rc = ssh_buffer_unpack(packet->payload, "d", &version); - if (rc != SSH_OK){ - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; - } - SSH_LOG(SSH_LOG_DEBUG, - "SFTP server version %" PRIu32, - version); - rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - while (rc == SSH_OK) { - uint32_t count = sftp->ext->count; - char **tmp; - - rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); if (rc == SSH_ERROR) { - break; + SSH_BUFFER_FREE(buffer); + return -1; } - SSH_LOG(SSH_LOG_DEBUG, - "SFTP server extension: %s, version: %s", - ext_name, ext_data); + SSH_BUFFER_FREE(buffer); - count++; - tmp = realloc(sftp->ext->name, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; } - tmp[count - 1] = ext_name; - sftp->ext->name = tmp; - tmp = realloc(sftp->ext->data, count * sizeof(char *)); - if (tmp == NULL) { - ssh_set_error_oom(sftp->session); - SAFE_FREE(ext_name); - SAFE_FREE(ext_data); - sftp_set_error(sftp, SSH_FX_FAILURE); - return -1; + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", + packet->type); + return -1; } - tmp[count - 1] = ext_data; - sftp->ext->data = tmp; - sftp->ext->count = count; + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK){ + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server version %" PRIu32, + version); rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); - } + while (rc == SSH_OK) { + uint32_t count = sftp->ext->count; + char **tmp; - sftp->version = sftp->server_version = (int)version; + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); - return 0; + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; + + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; + + sftp->ext->count = count; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + } + + sftp->version = sftp->server_version = (int)version; + + return 0; } unsigned int sftp_extensions_get_count(sftp_session sftp) { From 5ea247df8e38f777863a0fc0415dfca57ccff057 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Fri, 8 Dec 2023 11:39:12 +0530 Subject: [PATCH 103/795] sftp.c: Reformat sftp limits API accoding to the current coding style Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- src/sftp.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/sftp.c b/src/sftp.c index 8613de59..edc138f3 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -2523,8 +2523,7 @@ void sftp_statvfs_free(sftp_statvfs_t statvfs) { SAFE_FREE(statvfs); } -static sftp_limits_t -sftp_parse_limits(sftp_session sftp, ssh_buffer buf) +static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) { sftp_limits_t limits = NULL; int rc; @@ -2552,8 +2551,7 @@ sftp_parse_limits(sftp_session sftp, ssh_buffer buf) return limits; } -sftp_limits_t -sftp_limits(sftp_session sftp) +sftp_limits_t sftp_limits(sftp_session sftp) { sftp_status_message status = NULL; sftp_message msg = NULL; @@ -2629,8 +2627,7 @@ sftp_limits(sftp_session sftp) return NULL; } -void -sftp_limits_free(sftp_limits_t limits) +void sftp_limits_free(sftp_limits_t limits) { if (limits == NULL) { return; From 4f24fbd3a06ffc9acf591092c017af77d5c40797 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Thu, 7 Dec 2023 11:39:36 +0530 Subject: [PATCH 104/795] sftp.c, sftp.h: Store the limits in the sftp_session In the sftp_init() call, the limits are stored in the sftp_sesssion. If the limits@openssh.com extension is supported the limits are retrieved from the server, else libssh uses the default limits. The sftp library functions that require the limits can access them using the sftp session. The library user can call sftp_limits() to get a copy of the limits stored in the sftp session. Since the limits were already retrieved from the server during sftp_init(), this sftp_limits() call requires no communication with the server. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 1 + src/sftp.c | 113 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index b4e0e18b..5624e8ca 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -92,6 +92,7 @@ struct sftp_session_struct { void **handles; sftp_ext ext; sftp_packet read_packet; + sftp_limits_t limits; }; struct sftp_packet_struct { diff --git a/src/sftp.c b/src/sftp.c index edc138f3..29341d55 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -347,6 +347,7 @@ void sftp_free(sftp_session sftp) SAFE_FREE(sftp->read_packet); sftp_ext_free(sftp->ext); + sftp_limits_free(sftp->limits); SAFE_FREE(sftp); } @@ -413,6 +414,9 @@ int sftp_get_error(sftp_session sftp) { return sftp->errnum; } +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); +static sftp_limits_t sftp_limits_use_default(sftp_session sftp); + /* Initialize the sftp session with the server. */ int sftp_init(sftp_session sftp) { @@ -514,6 +518,47 @@ int sftp_init(sftp_session sftp) sftp->version = sftp->server_version = (int)version; + /* Set the limits */ + rc = sftp_extension_supported(sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* Get the ssh and sftp errors */ + const char *static_ssh_err_msg = ssh_get_error(sftp->session); + int ssh_err_code = ssh_get_error_code(sftp->session); + int sftp_err_code = sftp_get_error(sftp); + char *ssh_err_msg = strdup(static_ssh_err_msg); + if (ssh_err_msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + sftp->limits = sftp_limits_use_extension(sftp); + if (sftp->limits == NULL) { + /* fallback and use the default limits on failure */ + SSH_LOG(SSH_LOG_TRACE, + "Failed to get the limits from a server claiming to " + "support the limits@openssh.com extension, falling back " + "and using the default limits"); + + /* Restore the sftp and ssh errors to their previous state */ + ssh_set_error(sftp->session, ssh_err_code, "%s", ssh_err_msg); + sftp_set_error(sftp, sftp_err_code); + SAFE_FREE(ssh_err_msg); + + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } else { + SAFE_FREE(ssh_err_msg); + } + } else { + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } + return 0; } @@ -2523,12 +2568,17 @@ void sftp_statvfs_free(sftp_statvfs_t statvfs) { SAFE_FREE(statvfs); } +static sftp_limits_t sftp_limits_new(void) +{ + return calloc(1, sizeof(struct sftp_limits_struct)); +} + static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) { sftp_limits_t limits = NULL; int rc; - limits = calloc(1, sizeof(struct sftp_limits_struct)); + limits = sftp_limits_new(); if (limits == NULL) { ssh_set_error_oom(sftp->session); sftp_set_error(sftp, SSH_FX_FAILURE); @@ -2551,7 +2601,7 @@ static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) return limits; } -sftp_limits_t sftp_limits(sftp_session sftp) +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp) { sftp_status_message status = NULL; sftp_message msg = NULL; @@ -2627,6 +2677,65 @@ sftp_limits_t sftp_limits(sftp_session sftp) return NULL; } +static sftp_limits_t sftp_limits_use_default(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits->max_packet_length = 34000; + limits->max_read_length = 32768; + limits->max_write_length = 32768; + + /* + * For max-open-handles field openssh says : + * If the server doesn't enforce a specific limit, then the field may + * be set to 0. This implies the server relies on the OS to enforce + * limits (e.g. available memory or file handles), and such limits + * might be dynamic. The client SHOULD take care to not try to exceed + * reasonable limits. + */ + limits->max_open_handles = 0; + + return limits; +} + +sftp_limits_t sftp_limits(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + if (sftp->limits == NULL) { + ssh_set_error(sftp, SSH_FATAL, + "Uninitialized sftp session, " + "sftp_init() was not called or failed"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + memcpy(limits, sftp->limits, sizeof(struct sftp_limits_struct)); + return limits; +} + void sftp_limits_free(sftp_limits_t limits) { if (limits == NULL) { From d2d5e717f3d15516cf9756cea66e449cbe517c76 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sun, 10 Dec 2023 09:22:27 +0530 Subject: [PATCH 105/795] torture_sftp_limits.c: Change the test Test has been changed such that sftp_limits() is called when the limits@openssh.com extension is supported as well as when it is not supported. Also, a simple negative test has been added for NULL argument. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- tests/client/torture_sftp_limits.c | 96 +++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 8 deletions(-) diff --git a/tests/client/torture_sftp_limits.c b/tests/client/torture_sftp_limits.c index f8b682f7..07ef9928 100644 --- a/tests/client/torture_sftp_limits.c +++ b/tests/client/torture_sftp_limits.c @@ -6,9 +6,14 @@ #include "sftp.c" #include +#include #include #include +#if HAVE_VALGRIND_VALGRIND_H + #include +#endif + static int sshd_setup(void **state) { torture_setup_sshd_server(state, false); @@ -62,27 +67,102 @@ static void torture_sftp_limits(void **state) { struct torture_state *s = *state; struct torture_sftp *t = s->ssh.tsftp; - sftp_limits_t li; - - if (!sftp_extension_supported(t->sftp, "limits@openssh.com", "1")) - skip(); + sftp_limits_t li = NULL; + int rc; li = sftp_limits(t->sftp); assert_non_null(li); - assert_int_not_equal(li->max_packet_length, 0); - assert_int_not_equal(li->max_read_length, 0); - assert_int_not_equal(li->max_write_length, 0); - assert_int_not_equal(li->max_open_handles, 0); + rc = sftp_extension_supported(t->sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* + * Tests are run against the OpenSSH server, hence we check for the + * specific limits used by OpenSSH. + */ + uint64_t openssh_max_packet_length = 256 * 1024; + uint64_t openssh_max_read_length = openssh_max_packet_length - 1024; + uint64_t openssh_max_write_length = openssh_max_packet_length - 1024; + size_t vg = 0; + + assert_int_equal(li->max_packet_length, openssh_max_packet_length); + assert_int_equal(li->max_read_length, openssh_max_read_length); + assert_int_equal(li->max_write_length, openssh_max_write_length); + + /* + * fds - File descriptors, w.r.to - With respect to + * + * Valgrind reserves some fds for itself and changes the rlimits + * w.r.to fds for the process its inspecting. Due to this reservation + * the rlimits w.r.to fds for our test may not be the same as the + * rlimits w.r.to fds seen by OpenSSH server (which Valgrind isn't + * inspecting). + * + * Valgrind changes the limits in such a way that after seeing the + * changed limits, the test cannot predict the original unchanged + * limits (which OpenSSH would be using). Hence, the test cannot + * determine the correct value of "max_open_handles" that the OpenSSH + * server should've sent. + * + * So if Valgrind is running our test, we don't provide any kind of + * check for max_open_handles. Check for >= 0 is also not provided in + * this case since that's always true for an uint64_t (an unsigned type) + */ +#if HAVE_VALGRIND_VALGRIND_H + vg = RUNNING_ON_VALGRIND; +#endif + + if (vg == 0) { + struct rlimit rlim = {0}; + uint64_t openssh_max_open_handles = 0; + + /* + * Get the resource limit for max file descriptors that a process + * can open. Since the client and the server run on the same machine + * in case of tests, this limit should be same for both (except the + * case when Valgrind runs the test) + */ + rc = getrlimit(RLIMIT_NOFILE, &rlim); + assert_int_equal(rc, 0); + if (rlim.rlim_cur > 5) { + /* + * Leaving file handles for stdout, stdin, stderr, syslog and + * a spare file handle, OpenSSH server allows the client to open + * at max (rlim.rlim_cur - 5) handles. + */ + openssh_max_open_handles = rlim.rlim_cur - 5; + } + + assert_int_equal(li->max_open_handles, openssh_max_open_handles); + } + } else { + /* Check for the default limits */ + assert_int_equal(li->max_packet_length, 34000); + assert_int_equal(li->max_read_length, 32768); + assert_int_equal(li->max_write_length, 32768); + assert_int_equal(li->max_open_handles, 0); + } sftp_limits_free(li); } +static void torture_sftp_limits_negative(void **state) +{ + sftp_limits_t li = NULL; + + (void)state; + li = sftp_limits(NULL); + assert_null(li); +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(torture_sftp_limits, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_limits_negative, session_setup, session_teardown) }; From 91990f9dfa6a9a79f59f23a591ae667351091606 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Thu, 7 Dec 2023 10:16:48 +0530 Subject: [PATCH 106/795] sftp_aio.c, sftp.h: Add capping to the sftp aio read API Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 21 +++++++++++++++------ src/sftp_aio.c | 9 +++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index 5624e8ca..78c0e743 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -634,6 +634,12 @@ LIBSSH_API void sftp_aio_free(sftp_aio aio); * calling sftp_close() or to keep it open and perform some more operations * on it. * + * This function caps the length a user is allowed to read from an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * * @param file The opened sftp file handle to be read from. * * @param len Number of bytes to read. @@ -641,11 +647,14 @@ LIBSSH_API void sftp_aio_free(sftp_aio aio); * @param aio Pointer to a location where the sftp aio handle * (corresponding to the sent request) should be stored. * - * @returns SSH_OK on success, SSH_ERROR on error with sftp and ssh + * @returns On success, the number of bytes the server is + * requested to read (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh * errors set. * - * @warning When calling this function, the internal offset is - * updated corresponding to the len parameter. + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to read. * * @warning A call to sftp_aio_begin_read() sends a request to * the server. When the server answers, libssh allocates @@ -660,9 +669,9 @@ LIBSSH_API void sftp_aio_free(sftp_aio aio); * @see sftp_get_error() * @see ssh_get_error() */ -LIBSSH_API int sftp_aio_begin_read(sftp_file file, - size_t len, - sftp_aio *aio); +LIBSSH_API ssize_t sftp_aio_begin_read(sftp_file file, + size_t len, + sftp_aio *aio); /** * @brief Wait for an asynchronous read to complete and store the read data diff --git a/src/sftp_aio.c b/src/sftp_aio.c index d0c0d874..243d17c1 100644 --- a/src/sftp_aio.c +++ b/src/sftp_aio.c @@ -50,7 +50,7 @@ void sftp_aio_free(sftp_aio aio) SAFE_FREE(aio); } -int sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) +ssize_t sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) { sftp_session sftp = NULL; ssh_buffer buffer = NULL; @@ -73,6 +73,11 @@ int sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) return SSH_ERROR; } + /* Apply a cap on the length a user is allowed to read */ + if (len > sftp->limits->max_read_length) { + len = sftp->limits->max_read_length; + } + if (aio == NULL) { ssh_set_error(sftp->session, SSH_FATAL, "Invalid argument, NULL passed instead of a pointer to " @@ -126,7 +131,7 @@ int sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) /* Assume we read len bytes from the file */ file->offset += len; *aio = aio_handle; - return SSH_OK; + return len; } ssize_t sftp_aio_wait_read(sftp_aio *aio, From 188a9cf68f905e71d17f95eb002f31fe85ed7600 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Thu, 7 Dec 2023 10:44:58 +0530 Subject: [PATCH 107/795] sftp_aio.c, sftp.h: Add capping to sftp aio write API Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 28 ++++++++++++++++------------ src/sftp_aio.c | 15 ++++++++++----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index 78c0e743..ffa315c3 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -738,6 +738,12 @@ LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, * calling sftp_close() or to keep it open and perform some more operations * on it. * + * This function caps the length a user is allowed to write to an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * * @param file The opened sftp file handle to write to. * * @param buf Pointer to the buffer containing data to write. @@ -747,11 +753,14 @@ LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, * @param aio Pointer to a location where the sftp aio handle * (corresponding to the sent request) should be stored. * - * @returns SSH_OK on success, SSH_ERROR with sftp and ssh errors + * @returns On success, the number of bytes the server is + * requested to write (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh errors * set. * - * @warning When calling this function, the internal offset is - * updated corresponding to the len parameter. + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to write. * * @warning A call to sftp_aio_begin_write() sends a request to * the server. When the server answers, libssh allocates @@ -766,10 +775,10 @@ LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, * @see sftp_get_error() * @see ssh_get_error() */ -LIBSSH_API int sftp_aio_begin_write(sftp_file file, - const void *buf, - size_t len, - sftp_aio *aio); +LIBSSH_API ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio); /** * @brief Wait for an asynchronous write to complete. @@ -784,11 +793,6 @@ LIBSSH_API int sftp_aio_begin_write(sftp_file file, * been executed yet, this function returns SSH_AGAIN and must be called * again using the same sftp aio handle. * - * On success, this function returns the number of bytes written. - * The SFTP protocol doesn't support partial writes to remote files, - * hence on success this returned value will always be equal to the - * len passed in the previous corresponding call to sftp_aio_begin_write(). - * * @param aio Pointer to the sftp aio handle returned by * sftp_aio_begin_write(). * diff --git a/src/sftp_aio.c b/src/sftp_aio.c index 243d17c1..c1c54561 100644 --- a/src/sftp_aio.c +++ b/src/sftp_aio.c @@ -307,10 +307,10 @@ ssize_t sftp_aio_wait_read(sftp_aio *aio, return SSH_ERROR; /* not reached */ } -int sftp_aio_begin_write(sftp_file file, - const void *buf, - size_t len, - sftp_aio *aio) +ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio) { sftp_session sftp = NULL; ssh_buffer buffer = NULL; @@ -341,6 +341,11 @@ int sftp_aio_begin_write(sftp_file file, return SSH_ERROR; } + /* Apply a cap on the length a user is allowed to write */ + if (len > sftp->limits->max_write_length) { + len = sftp->limits->max_write_length; + } + if (aio == NULL) { ssh_set_error(sftp->session, SSH_FATAL, "Invalid argument, NULL passed instead of a pointer to " @@ -394,7 +399,7 @@ int sftp_aio_begin_write(sftp_file file, /* Assume we wrote len bytes to the file */ file->offset += len; *aio = aio_handle; - return SSH_OK; + return len; } ssize_t sftp_aio_wait_write(sftp_aio *aio) From d73a0acef768e3006bd4c1342e400cd48e476766 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 11 Dec 2023 12:44:30 +0530 Subject: [PATCH 108/795] torture_sftp_aio.c: Change the tests according to aio api changes The tests have been changed such that the return value of sftp_aio_begin_*() functions is expected to be a ssize_t which indicates the number of bytes for which the function sent a read/write request or error. Tests for trying to read/write bytes more than the max limit enforced by the API have also been added. The negative tests for reading and writing have also been seperated for the sake of clarity. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- tests/client/torture_sftp_aio.c | 317 ++++++++++++++++++++++++-------- 1 file changed, 241 insertions(+), 76 deletions(-) diff --git a/tests/client/torture_sftp_aio.c b/tests/client/torture_sftp_aio.c index 4f62fa38..4cbb5793 100644 --- a/tests/client/torture_sftp_aio.c +++ b/tests/client/torture_sftp_aio.c @@ -60,13 +60,13 @@ static int session_teardown(void **state) return 0; } -static void torture_sftp_aio_read(void **state) +static void torture_sftp_aio_read_file(void **state) { struct torture_state *s = *state; struct torture_sftp *t = s->ssh.tsftp; struct { - char buf[MAX_XFER_BUF_SIZE]; + char *buf; ssize_t bytes_read; } a = {0}, b = {0}; @@ -74,17 +74,31 @@ static void torture_sftp_aio_read(void **state) sftp_attributes file_attr = NULL; int fd; - size_t chunk_size = MAX_XFER_BUF_SIZE; + size_t chunk_size; int in_flight_requests = 20; sftp_aio aio = NULL; struct ssh_list *aio_queue = NULL; + sftp_limits_t li = NULL; size_t file_size; - size_t bytes_requested; + size_t total_bytes_requested; size_t to_read, total_bytes_read; + ssize_t bytes_requested; + int i, rc; + /* Get the max limit for reading, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + a.buf = calloc(chunk_size, 1); + assert_non_null(a.buf); + + b.buf = calloc(chunk_size, 1); + assert_non_null(b.buf); + aio_queue = ssh_list_new(); assert_non_null(aio_queue); @@ -99,18 +113,18 @@ static void torture_sftp_aio_read(void **state) assert_non_null(file_attr); file_size = file_attr->size; - bytes_requested = 0; + total_bytes_requested = 0; for (i = 0; - i < in_flight_requests && bytes_requested < file_size; + i < in_flight_requests && total_bytes_requested < file_size; ++i) { - to_read = file_size - bytes_requested; + to_read = file_size - total_bytes_requested; if (to_read > chunk_size) { to_read = chunk_size; } - rc = sftp_aio_begin_read(file, to_read, &aio); - assert_int_equal(rc, SSH_OK); - bytes_requested += to_read; + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; /* enqueue */ rc = ssh_list_append(aio_queue, aio); @@ -119,7 +133,7 @@ static void torture_sftp_aio_read(void **state) total_bytes_read = 0; while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { - a.bytes_read = sftp_aio_wait_read(&aio, a.buf, sizeof(a.buf)); + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); assert_int_not_equal(a.bytes_read, SSH_ERROR); total_bytes_read += (size_t)a.bytes_read; @@ -129,9 +143,8 @@ static void torture_sftp_aio_read(void **state) * Failure of this assertion means that a short * read is encountered but we have not reached * the end of file yet. A short read before reaching - * the end of file should not occur for disk files - * according to the SFTP protocol. (In our code the - * file SSH_EXECUTABLE being read is a disk file) + * the end of file should not occur for our test where + * the chunk size respects the max limit for reading. */ } @@ -147,19 +160,19 @@ static void torture_sftp_aio_read(void **state) assert_int_equal(rc, 0); /* Issue more read requests if needed */ - if (bytes_requested == file_size) { + if (total_bytes_requested == file_size) { continue; } /* else issue more requests */ - to_read = file_size - bytes_requested; + to_read = file_size - total_bytes_requested; if (to_read > chunk_size) { to_read = chunk_size; } - rc = sftp_aio_begin_read(file, to_read, &aio); - assert_int_equal(rc, SSH_OK); - bytes_requested += to_read; + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; /* enqueue */ rc = ssh_list_append(aio_queue, aio); @@ -170,20 +183,59 @@ static void torture_sftp_aio_read(void **state) * Check whether sftp server responds with an * eof for more requests. */ - rc = sftp_aio_begin_read(file, chunk_size, &aio); - assert_int_equal(rc, SSH_OK); + bytes_requested = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes_requested, chunk_size); - a.bytes_read = sftp_aio_wait_read(&aio, a.buf, sizeof(a.buf)); + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); assert_int_equal(a.bytes_read, 0); - /* Cleanup */ + /* Clean up */ sftp_attributes_free(file_attr); close(fd); sftp_close(file); ssh_list_free(aio_queue); + free(b.buf); + free(a.buf); + sftp_limits_free(li); } -static void torture_sftp_aio_write(void **state) +static void torture_sftp_aio_read_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + char *buf = NULL; + ssize_t bytes; + + /* Get the max limit for reading */ + li = sftp_limits(t->sftp); + assert_non_null(li); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Try reading more than the max limit */ + bytes = sftp_aio_begin_read(file, + li->max_read_length * 2, + &aio); + assert_int_equal(bytes, li->max_read_length); + + buf = calloc(li->max_read_length, 1); + assert_non_null(buf); + + bytes = sftp_aio_wait_read(&aio, buf, li->max_read_length); + assert_int_not_equal(bytes, SSH_ERROR); + + free(buf); + sftp_close(file); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_file(void **state) { struct torture_state *s = *state; struct torture_sftp *t = s->ssh.tsftp; @@ -193,15 +245,29 @@ static void torture_sftp_aio_write(void **state) int fd; struct { - char buf[MAX_XFER_BUF_SIZE]; + char *buf; ssize_t bytes; } wr = {0}, rd = {0}; - int in_flight_requests; + size_t chunk_size; + ssize_t bytes_requested; + int in_flight_requests = 2; + + sftp_limits_t li = NULL; sftp_aio *aio_queue = NULL; int rc, i; - in_flight_requests = 2; + /* Get the max limit for writing, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + rd.buf = calloc(chunk_size, 1); + assert_non_null(rd.buf); + + wr.buf = calloc(chunk_size, 1); + assert_non_null(wr.buf); + aio_queue = malloc(sizeof(sftp_aio) * in_flight_requests); assert_non_null(aio_queue); @@ -214,13 +280,16 @@ static void torture_sftp_aio_write(void **state) assert_int_not_equal(fd, -1); for (i = 0; i < in_flight_requests; ++i) { - rc = sftp_aio_begin_write(file, wr.buf, sizeof(wr.buf), &aio_queue[i]); - assert_int_equal(rc, SSH_OK); + bytes_requested = sftp_aio_begin_write(file, + wr.buf, + chunk_size, + &aio_queue[i]); + assert_int_equal(bytes_requested, chunk_size); } for (i = 0; i < in_flight_requests; ++i) { wr.bytes = sftp_aio_wait_write(&aio_queue[i]); - assert_int_equal(wr.bytes, sizeof(wr.buf)); + assert_int_equal(wr.bytes, chunk_size); /* * Check whether the bytes written to the file @@ -235,64 +304,121 @@ static void torture_sftp_aio_write(void **state) assert_int_equal(rc, 0); } - /* Cleanup */ + /* Clean up */ close(fd); sftp_close(file); free(aio_queue); rc = unlink(file_path); assert_int_equal(rc, 0); + + free(wr.buf); + free(rd.buf); + sftp_limits_free(li); } -static void torture_sftp_aio_negative(void **state) +static void torture_sftp_aio_write_more_than_cap(void **state) { struct torture_state *s = *state; struct torture_sftp *t = s->ssh.tsftp; - char buf[MAX_XFER_BUF_SIZE] = {0}; - sftp_file file = NULL; + sftp_limits_t li = NULL; + char *buf = NULL; + size_t buf_size; + char file_path[128] = {0}; + sftp_file file = NULL; + + sftp_aio aio = NULL; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + buf_size = li->max_write_length * 2; + buf = calloc(buf_size, 1); + assert_non_null(buf); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_cap", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Try writing more than the max limit for writing */ + bytes = sftp_aio_begin_write(file, buf, buf_size, &aio); + assert_int_equal(bytes, li->max_write_length); + + bytes = sftp_aio_wait_write(&aio); + assert_int_equal(bytes, li->max_write_length); + + /* Clean up */ + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + sftp_file file = NULL; sftp_aio aio = NULL; + sftp_limits_t li = NULL; - size_t chunk_size = MAX_XFER_BUF_SIZE; - ssize_t bytes_read, bytes_written; + size_t chunk_size; + ssize_t bytes; int rc; + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + /* Open a file for reading */ file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); assert_non_null(file); - /* Negative tests for reading start */ - /* Passing NULL as the sftp file handle */ - rc = sftp_aio_begin_read(NULL, chunk_size, &aio); - assert_int_equal(rc, SSH_ERROR); + bytes = sftp_aio_begin_read(NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); /* Passing 0 as the number of bytes to read */ - rc = sftp_aio_begin_read(file, 0, &aio); - assert_int_equal(rc, SSH_ERROR); + bytes = sftp_aio_begin_read(file, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); - /* Passing NULL instead of a pointer to a location to store an aio handle */ - rc = sftp_aio_begin_read(file, chunk_size, NULL); - assert_int_equal(rc, SSH_ERROR); + /* + * Passing NULL instead of a pointer to a location to + * store an aio handle. + */ + bytes = sftp_aio_begin_read(file, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); /* Passing NULL instead of a pointer to an aio handle */ - bytes_read = sftp_aio_wait_read(NULL, buf, sizeof(buf)); - assert_int_equal(bytes_read, SSH_ERROR); + bytes = sftp_aio_wait_read(NULL, buf, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); /* Passing NULL as the buffer's address */ - rc = sftp_aio_begin_read(file, chunk_size, &aio); - assert_int_equal(rc, SSH_OK); + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); - bytes_read = sftp_aio_wait_read(&aio, NULL, sizeof(buf)); - assert_int_equal(bytes_read, SSH_ERROR); + bytes = sftp_aio_wait_read(&aio, NULL, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); /* Passing 0 as the buffer size */ - rc = sftp_aio_begin_read(file, chunk_size, &aio); - assert_int_equal(rc, SSH_OK); + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); - bytes_read = sftp_aio_wait_read(&aio, buf, 0); - assert_int_equal(bytes_read, SSH_ERROR); + bytes = sftp_aio_wait_read(&aio, buf, 0); + assert_int_equal(bytes, SSH_ERROR); /* * Test for the scenario when the number @@ -301,62 +427,101 @@ static void torture_sftp_aio_negative(void **state) rc = sftp_seek(file, 0); /* Seek to the start of file */ assert_int_equal(rc, 0); - rc = sftp_aio_begin_read(file, 2, &aio); - assert_int_equal(rc, SSH_OK); + bytes = sftp_aio_begin_read(file, 2, &aio); + assert_int_equal(bytes, 2); - bytes_read = sftp_aio_wait_read(&aio, buf, 1); - assert_int_equal(bytes_read, SSH_ERROR); + bytes = sftp_aio_wait_read(&aio, buf, 1); + assert_int_equal(bytes, SSH_ERROR); sftp_close(file); + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); /* Open a file for writing */ snprintf(file_path, sizeof(file_path), - "%s/libssh_sftp_aio_write_test", t->testdir); + "%s/libssh_sftp_aio_write_test_negative", t->testdir); file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); assert_non_null(file); - /* Negative tests for writing start */ - /* Passing NULL as the sftp file handle */ - rc = sftp_aio_begin_write(NULL, buf, MAX_XFER_BUF_SIZE, &aio); - assert_int_equal(rc, SSH_ERROR); + bytes = sftp_aio_begin_write(NULL, buf, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); /* Passing NULL as the buffer's address */ - rc = sftp_aio_begin_write(file, NULL, MAX_XFER_BUF_SIZE, &aio); - assert_int_equal(rc, SSH_ERROR); + bytes = sftp_aio_begin_write(file, NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); /* Passing 0 as the size of buffer */ - rc = sftp_aio_begin_write(file, buf, 0, &aio); - assert_int_equal(rc, SSH_ERROR); + bytes = sftp_aio_begin_write(file, buf, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); /* Passing NULL instead of a pointer to a location to store an aio handle */ - rc = sftp_aio_begin_write(file, buf, MAX_XFER_BUF_SIZE, NULL); - assert_int_equal(rc, SSH_ERROR); + bytes = sftp_aio_begin_write(file, buf, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); /* Passing NULL instead of a pointer to an aio handle */ - bytes_written = sftp_aio_wait_write(NULL); - assert_int_equal(bytes_written, SSH_ERROR); + bytes = sftp_aio_wait_write(NULL); + assert_int_equal(bytes, SSH_ERROR); sftp_close(file); rc = unlink(file_path); assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); } int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_sftp_aio_read, + cmocka_unit_test_setup_teardown(torture_sftp_aio_read_file, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_read_more_than_cap, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_sftp_aio_write, + cmocka_unit_test_setup_teardown(torture_sftp_aio_write_file, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_sftp_aio_negative, + cmocka_unit_test_setup_teardown(torture_sftp_aio_write_more_than_cap, session_setup, - session_teardown) + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_read_negative, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_aio_write_negative, + session_setup, + session_teardown), }; ssh_init(); From 47d8bcf9a5f45ba91e75c2a2115bbb4987c1d674 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Wed, 13 Dec 2023 10:33:07 +0530 Subject: [PATCH 109/795] bench_sftp.c: Change sftp aio download benchmark Following changes have been made : 1. The benchmark now expects sftp_aio_begin_read() to return an ssize_t indicating an error (or) the number of bytes for which it sent a read request. 2. If the user sets a chunk size > max limit for the reading via CLI, the benchmark does not use the set chunk size and instead uses the max limit for reading as the chunk size for download. 3. fprintf calls have been introduced to print the reason for the failure if the benchmark fails. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- tests/benchmarks/bench_sftp.c | 107 ++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c index 64ad5efc..70409345 100644 --- a/tests/benchmarks/bench_sftp.c +++ b/tests/benchmarks/bench_sftp.c @@ -247,30 +247,59 @@ int benchmarks_async_sftp_aio_down(ssh_session session, float *bps) { sftp_session sftp = NULL; + sftp_limits_t li = NULL; sftp_file file = NULL; sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; int concurrent_downloads = args->concurrent_requests; + size_t chunksize; struct timestamp_struct ts = {0}; float ms = 0.0f; size_t total_bytes = args->datasize * 1024 * 1024; - size_t bytes_requested = 0, total_bytes_read = 0; + size_t total_bytes_requested = 0, total_bytes_read = 0; + size_t bufsize = args->chunksize; size_t to_read; - ssize_t bytes_read; + ssize_t bytes_read, bytes_requested; int warned = 0, i, rc; sftp = sftp_new(session); if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); return -1; } + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + rc = sftp_init(sftp); if (rc == SSH_ERROR) { goto error; } + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_read_length) { + chunksize = li->max_read_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for read packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + file = sftp_open(sftp, SFTPDIR SFTPFILE, O_RDONLY, 0); if (file == NULL) { goto error; @@ -278,6 +307,8 @@ int benchmarks_async_sftp_aio_down(ssh_session session, aio_queue = ssh_list_new(); if (aio_queue == NULL) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); goto error; } @@ -291,30 +322,41 @@ int benchmarks_async_sftp_aio_down(ssh_session session, timestamp_init(&ts); for (i = 0; - i < concurrent_downloads && bytes_requested < total_bytes; + i < concurrent_downloads && total_bytes_requested < total_bytes; ++i) { - to_read = total_bytes - bytes_requested; - if (to_read > args->chunksize) { - to_read = args->chunksize; + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; } - rc = sftp_aio_begin_read(file, to_read, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); goto error; } - bytes_requested += to_read; + total_bytes_requested += (size_t)bytes_requested; /* enqueue */ rc = ssh_list_append(aio_queue, aio); if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory"); sftp_aio_free(aio); goto error; } } while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { - bytes_read = sftp_aio_wait_read(&aio, buffer, args->chunksize); + bytes_read = sftp_aio_wait_read(&aio, buffer, bufsize); if (bytes_read == -1) { goto error; } @@ -322,50 +364,60 @@ int benchmarks_async_sftp_aio_down(ssh_session session, total_bytes_read += (size_t)bytes_read; if (bytes_read == 0) { fprintf(stdout , - "File smaller than expected : %zu bytes (expected %zu).\n", + "File smaller than expected: %zu bytes (expected %zu).\n", total_bytes_read, total_bytes); break; } if (total_bytes_read != total_bytes && - (size_t)bytes_read != args->chunksize && + (size_t)bytes_read != chunksize && warned != 1) { fprintf(stderr, - "async_sftp_aio_download : Receiving short reads " - "(%zu, expected %u) before encountering eof, " + "async_sftp_aio_download: Receiving short reads " + "(%zu, expected %zu) before encountering eof, " "the received file will be corrupted and shorted. " "Adapt chunksize to %zu.\n", - bytes_read, args->chunksize, bytes_read); + bytes_read, chunksize, bytes_read); warned = 1; } - if (bytes_requested == total_bytes) { + if (total_bytes_requested == total_bytes) { /* No need to issue more requests */ continue; } /* else issue a request */ - to_read = total_bytes - bytes_requested; - if (to_read > args->chunksize) { - to_read = args->chunksize; + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; } - rc = sftp_aio_begin_read(file, to_read, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { goto error; } - bytes_requested += to_read; + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; /* enqueue */ rc = ssh_list_append(aio_queue, aio); if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); sftp_aio_free(aio); goto error; } } - ssh_list_free(aio_queue); sftp_close(file); ms = elapsed_time(&ts); *bps = (float)(8000 * total_bytes_read) / ms; @@ -374,10 +426,18 @@ int benchmarks_async_sftp_aio_down(ssh_session session, ms, total_bytes_read, *bps); } + ssh_list_free(aio_queue); + sftp_limits_free(li); sftp_free(sftp); return 0; error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + } + /* Release aio structures corresponding to outstanding requests */ while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { sftp_aio_free(aio); @@ -385,6 +445,7 @@ int benchmarks_async_sftp_aio_down(ssh_session session, ssh_list_free(aio_queue); sftp_close(file); + sftp_limits_free(li); sftp_free(sftp); return -1; } From 9857a5ef594938169540254c1cb132e7b59a437e Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Thu, 14 Dec 2023 10:35:20 +0530 Subject: [PATCH 110/795] bench_sftp.c: Change sftp aio upload benchmark Following changes have been made: 1. The benchmark now expects sftp_aio_begin_write() to return a ssize_t indicating an error (or) the number of bytes for which it sent a write request. 2. If the user sets the chunk size > max limit for writing via CLI, the benchmark does not use the set chunk size and instead uses the max limit for writing as the chunk size 3. fprintf calls have been added to print the reason for failure if the benchmark fails. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- tests/benchmarks/bench_sftp.c | 95 ++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/tests/benchmarks/bench_sftp.c b/tests/benchmarks/bench_sftp.c index 70409345..e6c68248 100644 --- a/tests/benchmarks/bench_sftp.c +++ b/tests/benchmarks/bench_sftp.c @@ -455,38 +455,65 @@ int benchmarks_async_sftp_aio_up(ssh_session session, float *bps) { sftp_session sftp = NULL; + sftp_limits_t li = NULL; sftp_file file = NULL; sftp_aio aio = NULL; struct ssh_list *aio_queue = NULL; int concurrent_uploads = args->concurrent_requests; + size_t chunksize; struct timestamp_struct ts = {0}; float ms = 0.0f; size_t total_bytes = args->datasize * 1024 * 1024; - size_t bytes_requested = 0; - size_t to_write; - ssize_t bytes_written; + size_t to_write, total_bytes_requested = 0; + ssize_t bytes_written, bytes_requested; int i, rc; sftp = sftp_new(session); if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); return -1; } + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + rc = sftp_init(sftp); if (rc == SSH_ERROR) { goto error; } + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_write_length) { + chunksize = li->max_write_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for write packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + file = sftp_open(sftp, SFTPDIR SFTPFILE, - O_RDWR | O_CREAT | O_TRUNC, 0777); + O_WRONLY | O_CREAT | O_TRUNC, 0777); if (file == NULL) { goto error; } aio_queue = ssh_list_new(); if (aio_queue == NULL) { + fprintf(stderr, "Error during sftp aio upload: Insufficient memory\n"); goto error; } @@ -500,23 +527,34 @@ int benchmarks_async_sftp_aio_up(ssh_session session, timestamp_init(&ts); for (i = 0; - i < concurrent_uploads && bytes_requested < total_bytes; + i < concurrent_uploads && total_bytes_requested < total_bytes; ++i) { - to_write = total_bytes - bytes_requested; - if (to_write > args->chunksize) { - to_write = args->chunksize; + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; } - rc = sftp_aio_begin_write(file, buffer, to_write, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); goto error; } - bytes_requested += to_write; + total_bytes_requested += (size_t)bytes_requested; /* enqueue */ rc = ssh_list_append(aio_queue, aio); if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); sftp_aio_free(aio); goto error; } @@ -528,33 +566,43 @@ int benchmarks_async_sftp_aio_up(ssh_session session, goto error; } - if (bytes_requested == total_bytes) { + if (total_bytes_requested == total_bytes) { /* No need to issue more requests */ continue; } /* else issue a request */ - to_write = total_bytes - bytes_requested; - if (to_write > args->chunksize) { - to_write = args->chunksize; + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; } - rc = sftp_aio_begin_write(file, buffer, to_write, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); goto error; } - bytes_requested += to_write; + total_bytes_requested += bytes_requested; /* enqueue */ rc = ssh_list_append(aio_queue, aio); if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); sftp_aio_free(aio); goto error; } } - ssh_list_free(aio_queue); sftp_close(file); ms = elapsed_time(&ts); *bps = (float)(8000 * total_bytes) / ms; @@ -563,10 +611,18 @@ int benchmarks_async_sftp_aio_up(ssh_session session, ms, total_bytes, *bps); } + ssh_list_free(aio_queue); + sftp_limits_free(li); sftp_free(sftp); return 0; error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + } + /* Release aio structures corresponding to outstanding requests */ while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { sftp_aio_free(aio); @@ -574,6 +630,7 @@ int benchmarks_async_sftp_aio_up(ssh_session session, ssh_list_free(aio_queue); sftp_close(file); + sftp_limits_free(li); sftp_free(sftp); return -1; } From d7f7c952f20f77b85926812812469496f379c05c Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Sat, 30 Dec 2023 14:02:11 +0530 Subject: [PATCH 111/795] sftp_aio.dox: Change the sftp aio tutorial to incorporate capping A section has been added to explain the capping applied by the sftp aio API. Also the example codes have been changed such that they expect sftp_aio_begin_*() functions to return an ssize_t indicating the number of bytes it requested the server to read/write. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- doc/sftp_aio.dox | 206 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 168 insertions(+), 38 deletions(-) diff --git a/doc/sftp_aio.dox b/doc/sftp_aio.dox index 51960c86..9c26f5e1 100644 --- a/doc/sftp_aio.dox +++ b/doc/sftp_aio.dox @@ -73,18 +73,20 @@ on an sftp file using the sftp aio API. @code ssize_t read_chunk(sftp_file file, void *buf, size_t to_read) { - ssize_t bytes_read; - int rc; + ssize_t bytes_requested, bytes_read; // Variable to store an sftp aio handle sftp_aio aio = NULL; // Send a read request to the sftp server - rc = sftp_aio_begin_read(file, to_read, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { // handle error } + // Here its possible that (bytes_requested < to_read) as specified in + // the function documentation of sftp_aio_begin_read() + // Wait for the response of the read request corresponding to the // sftp aio handle stored in the aio variable. bytes_read = sftp_aio_wait_read(&aio, buf, to_read); @@ -113,18 +115,20 @@ sftp file using the sftp aio API. @code ssize_t write_chunk(sftp_file file, void *buf, size_t to_write) { - ssize_t bytes_written; - int rc; + ssize_t bytes_requested, bytes_written; // Variable to store an sftp aio handle sftp_aio aio = NULL; // Send a write request to the sftp server - rc = sftp_aio_begin_write(file, buf, to_write, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_write(file, buf, to_write, &aio); + if (bytes_requested == SSH_ERROR) { // handle error } + // Here its possible that (bytes_requested < to_write) as specified in + // the function documentation of sftp_aio_begin_write() + // Wait for the response of the write request corresponding to // the sftp aio handle stored in the aio variable. bytes_written = sftp_aio_wait_write(&aio); @@ -218,10 +222,60 @@ can be found at https://gitlab.com/libssh/libssh-mirror/-/tree/master. - libssh sftp ft API code for performing a remote to local transfer (download). [See src/sftp_ft.c] +@subsection sftp_aio_cap Capping applied by the sftp aio API + +Before the code examples for uploads and downloads, its important +to know about the capping applied by the sftp aio API. + +sftp_aio_begin_read() caps the number of bytes the caller can request +to read from the remote file. That cap is the value of the max_read_length +field of the sftp_limits_t returned by sftp_limits(). Say that cap is LIM +and the caller passes x as the number of bytes to read to +sftp_aio_begin_read(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_read() will request the server + to read x bytes from the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_read() will request the server + to read LIM bytes from the remote file and will return LIM. + +Hence to request server to read x bytes (> LIM), the caller would have +to call sftp_aio_begin_read() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_read() calls becomes equal to x. + +For the sake of simplicity, the code example for download in the upcoming +section would always ask sftp_aio_begin_read() to read x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +Similarly, sftp_aio_begin_write() caps the number of bytes the caller +can request to write to the remote file. That cap is the value of +max_write_length field of the sftp_limits_t returned by sftp_limits(). +Say that cap is LIM and the caller passes x as the number of bytes to +write to sftp_aio_begin_write(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_write() will request the server + to write x bytes to the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_write() will request the server + to write LIM bytes to the remote file and will return LIM. + +Hence to request server to write x bytes (> LIM), the caller would have +to call sftp_aio_begin_write() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_write() calls becomes equal to x. + +For the sake of simplicity, the code example for upload in the upcoming +section would always ask sftp_aio_begin_write() to write x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + @subsection sftp_aio_download_example Performing a download using the sftp aio API Terminologies used in the following code snippets : + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + - file : The sftp file handle of the remote file to download data from. (See sftp_open()) @@ -238,14 +292,18 @@ requested don't exceed the size of the file to download. @code sftp_aio aio = NULL; -// Using a chunk size of 16 KB -size_t chunk_size = 16 * 1024; +// Chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used +// by the code to set the chunk size +sftp_limits_t lim = NULL; // Max number of requests to keep outstanding at a time size_t in_flight_requests = 5; // Number of bytes for which requests have been sent -size_t bytes_requested = 0; +size_t total_bytes_requested = 0; // Number of bytes which have been downloaded size_t bytes_downloaded = 0; @@ -253,6 +311,25 @@ size_t bytes_downloaded = 0; // Buffer to use for the download char *buffer = NULL; +// Helper variables +size_t to_read; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for download = the max limit for reading +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_read_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_read_length; + buffer = malloc(chunk_size); if (buffer == NULL) { // handle error @@ -265,20 +342,26 @@ if (buffer == NULL) { // handles. for (i = 0; - i < in_flight_requests && bytes_requested < file_size; + i < in_flight_requests && total_bytes_requested < file_size; ++i) { - to_read = file_size - bytes_requested; + to_read = file_size - total_bytes_requested; if (to_read > chunk_size) { to_read = chunk_size; } // Issue a read request - rc = sftp_aio_begin_read(file, to_read, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { // handle error } - bytes_requested += to_read; + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; // Pseudo code ENQUEUE aio in the queue; @@ -292,7 +375,7 @@ issued outstanding request. On getting that response, we issue another read request if there are still some bytes in the sftp file (to download) for which we haven't sent the -read request. (This happens when bytes_requested < file_size) +read request. (This happens when total_bytes_requested < file_size) This issuing of another read request (under a condition) is done to keep the number of outstanding requests equal to the value of the @@ -314,36 +397,45 @@ while (the queue is not empty) { bytes_downloaded += bytes_read; if (bytes_read != chunk_size && bytes_downloaded != file_size) { // A short read encountered on the remote file before reaching EOF, - // handle it. + // short read before reaching EOF should never happen for the sftp aio + // API which respects the max limit for reading. This probably + // indicates a bad server. } // Pseudo code WRITE bytes_read bytes from the buffer into the local file in which downloaded data is to be stored ; - if (bytes_requested == file_size) { + if (total_bytes_requested == file_size) { // no need to issue more read requests continue; } // else issue a read request - to_read = file_size - bytes_requested; + to_read = file_size - total_bytes_requested; if (to_read > chunk_size) { to_read = chunk_size; } - rc = sftp_aio_begin_read(file, to_read, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { // handle error } - bytes_requested += to_read; + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += bytes_requested; // Pseudo code ENQUEUE aio in the queue; } free(buffer); +sftp_limits_free(lim); ... // Code to destroy the queue which was used to store the sftp aio // handles. @@ -356,6 +448,9 @@ would've been complete (assuming no error occurs). Terminologies used in the following code snippets : + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + - file : The sftp file handle of the remote file in which uploaded data is to be stored. (See sftp_open()) @@ -372,18 +467,41 @@ requested to write don't exceed the size of the file to upload. @code sftp_aio aio = NULL; -// Using a chunk size of 16 KB -size_t chunk_size = 16 * 1024; +// The chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used by +// the code to set the chunk size +sftp_limits_t lim = NULL; // Max number of requests to keep outstanding at a time size_t in_flight_requests = 5; -// Number of bytes for which write requests have been sent -size_t bytes_requested = 0; +// Total number of bytes for which write requests have been sent +size_t total_bytes_requested = 0; // Buffer to use for the upload char *buffer = NULL; +// Helper variables +size_t to_write; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for upload = the max limit for writing. +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_write_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_write_length; + buffer = malloc(chunk_size); if (buffer == NULL) { // handle error @@ -397,9 +515,9 @@ if (buffer == NULL) { // handles. for (i = 0; - i < in_flight_requests && bytes_requested < file_size; + i < in_flight_requests && total_bytes_requested < file_size; ++i) { - to_write = file_size - bytes_requested; + to_write = file_size - total_bytes_requested; if (to_write > chunk_size) { to_write = chunk_size; } @@ -407,12 +525,18 @@ for (i = 0; // Pseudo code READ to_write bytes from the local file (to upload) into the buffer; - rc = sftp_aio_begin_write(file, buffer, to_write, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { // handle error } - bytes_requested += to_write; + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; // Pseudo code ENQUEUE aio in the queue; @@ -426,7 +550,7 @@ issued outstanding request. On getting that response, we issue another write request if there are still some bytes in the local file (to upload) for which we haven't sent -the write request. (This happens when bytes_requested < file_size) +the write request. (This happens when total_bytes_requested < file_size) This issuing of another write request (under a condition) is done to keep the number of outstanding requests equal to the value of the @@ -448,13 +572,13 @@ while (the queue is not empty) { // sftp_aio_wait_write() won't report a short write, so no need // to check for a short write here. - if (bytes_requested == file_size) { + if (total_bytes_requested == file_size) { // no need to issue more write requests continue; } // else issue a write request - to_write = file_size - bytes_requested; + to_write = file_size - total_bytes_requested; if (to_write > chunk_size) { to_write = chunk_size; } @@ -462,12 +586,18 @@ while (the queue is not empty) { // Pseudo code READ to_write bytes from the local file (to upload) into a buffer; - rc = sftp_aio_begin_write(file, buffer, to_write, &aio); - if (rc == SSH_ERROR) { + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { // handle error } - bytes_requested += to_write; + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; // Pseudo code ENQUEUE aio in the queue; From ebcd6eee3cd04babd9e8f5efbe2377317c95bcc8 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Wed, 27 Dec 2023 20:32:18 +0100 Subject: [PATCH 112/795] misc: Add function to check username syntax Malicious code can be injected using the username with metacharacters, therefore the username must be validated before using it with any %u. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- include/libssh/misc.h | 1 + src/misc.c | 32 ++++++++++++++++++++++++++++++++ tests/unittests/torture_misc.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/include/libssh/misc.h b/include/libssh/misc.h index dfbba14c..fc8596f7 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -123,6 +123,7 @@ ssize_t ssh_readn(int fd, void *buf, size_t nbytes); ssize_t ssh_writen(int fd, const void *buf, size_t nbytes); int ssh_check_hostname_syntax(const char *hostname); +int ssh_check_username_syntax(const char *username); #ifdef __cplusplus } diff --git a/src/misc.c b/src/misc.c index 5cab9d96..c5213561 100644 --- a/src/misc.c +++ b/src/misc.c @@ -2187,4 +2187,36 @@ int ssh_check_hostname_syntax(const char *hostname) return SSH_OK; } +/** + * @brief Checks syntax of a username + * + * This check disallows metacharacters in the username + * + * @param username The username to be checked, has to be null terminated + * + * @return SSH_OK if the username passes syntax check + * SSH_ERROR otherwise or if username is NULL or empty string + */ +int ssh_check_username_syntax(const char *username) +{ + size_t username_len; + + if (username == NULL || *username == '-') { + return SSH_ERROR; + } + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || + strpbrk(username, "'`\";&<>|(){}") != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { + if (isspace(username[i]) != 0 && username[i + 1] == '-') { + return SSH_ERROR; + } + } + + return SSH_OK; +} + /** @} */ diff --git a/tests/unittests/torture_misc.c b/tests/unittests/torture_misc.c index e35d2eac..5a3b4805 100644 --- a/tests/unittests/torture_misc.c +++ b/tests/unittests/torture_misc.c @@ -1058,6 +1058,39 @@ static void torture_ssh_check_hostname_syntax(void **state) assert_int_equal(rc, SSH_ERROR); } +static void torture_ssh_check_username_syntax(void **state) { + int rc; + (void)state; + + rc = ssh_check_username_syntax("username"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice and Bob"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("n4me?"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_username_syntax("alice&bob"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("backslash\\"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("&var|()us\" Date: Thu, 28 Dec 2023 12:16:29 +0100 Subject: [PATCH 113/795] Check any input username for validity Check possible inputs of username for malicious code. Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- src/config_parser.c | 4 ++++ src/misc.c | 10 ++++++++-- src/options.c | 5 +++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/config_parser.c b/src/config_parser.c index 5f30cd3e..bd6ab9d7 100644 --- a/src/config_parser.c +++ b/src/config_parser.c @@ -194,6 +194,10 @@ int ssh_config_parse_uri(const char *tok, if (*username == NULL) { goto error; } + rc = ssh_check_username_syntax(*username); + if (rc != SSH_OK) { + goto error; + } } tok = endp + 1; /* If there is second @ character, this does not look like our URI */ diff --git a/src/misc.c b/src/misc.c index c5213561..76a77357 100644 --- a/src/misc.c +++ b/src/misc.c @@ -182,6 +182,7 @@ char *ssh_get_local_username(void) { DWORD size = 0; char *user; + int rc; /* get the size */ GetUserName(NULL, &size); @@ -192,7 +193,10 @@ char *ssh_get_local_username(void) } if (GetUserName(user, &size)) { - return user; + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } } return NULL; @@ -336,8 +340,10 @@ char *ssh_get_local_username(void) } name = strdup(pwd.pw_name); + rc = ssh_check_username_syntax(name); - if (name == NULL) { + if (rc != SSH_OK) { + free(name); return NULL; } diff --git a/src/options.c b/src/options.c index 2727b873..961aba4e 100644 --- a/src/options.c +++ b/src/options.c @@ -738,6 +738,11 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, ssh_set_error_oom(session); return -1; } + rc = ssh_check_username_syntax(session->opts.username); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } } break; case SSH_OPTIONS_SSH_DIR: From 2be44b4c5a010f5a0d0252dcf2a9828dfe849121 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Thu, 28 Dec 2023 12:27:31 +0100 Subject: [PATCH 114/795] torture: Add cases for username checks Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- tests/unittests/torture_config.c | 3 +++ tests/unittests/torture_options.c | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index 751aa126..ebc2cdbd 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -2377,6 +2377,9 @@ static void torture_config_parse_uri(void **state) assert_null(username); assert_string_equal(hostname, "1:2:3::4"); SAFE_FREE(hostname); + + rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); + assert_int_equal(rc, SSH_ERROR); } int torture_run_tests(void) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index b07712d8..e41c15da 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -86,6 +86,9 @@ static void torture_options_set_host(void **state) { assert_non_null(session->opts.username); assert_string_equal(session->opts.username, "at@login"); + /* disallow metacharacters in the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); } static void torture_options_set_ciphers(void **state) { @@ -393,6 +396,9 @@ static void torture_options_set_user(void **state) { assert_true(rc == 0); #endif /* _WIN32 */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "&shallN()tP4ss"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); assert_true(rc == 0); assert_string_equal(session->opts.username, "guru"); From c0354c468993cac33276295425d17e9ac6f07d1e Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Tue, 9 Jan 2024 09:24:17 +0100 Subject: [PATCH 115/795] misc.c: Initialize pointers and free it Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- src/misc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/misc.c b/src/misc.c index 76a77357..c7a9706c 100644 --- a/src/misc.c +++ b/src/misc.c @@ -181,7 +181,7 @@ int ssh_gettimeofday(struct timeval *__p, void *__t) char *ssh_get_local_username(void) { DWORD size = 0; - char *user; + char *user = NULL; int rc; /* get the size */ @@ -199,6 +199,8 @@ char *ssh_get_local_username(void) } } + free(user); + return NULL; } @@ -331,7 +333,7 @@ char *ssh_get_local_username(void) struct passwd pwd; struct passwd *pwdbuf = NULL; char buf[NSS_BUFLEN_PASSWD]; - char *name; + char *name = NULL; int rc; rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); From 1176a71d612d74de8e8fc09b25a9f0b949611026 Mon Sep 17 00:00:00 2001 From: Gauravsingh Sisodia Date: Wed, 10 Jan 2024 10:42:23 +0000 Subject: [PATCH 116/795] examples: server check all keys in authorized_keys instead of one Fix read file with fgets and remove memory leaks Remove use of ssh_pki_import_pubkey_file in ssh server and update max line size Fix example server line no. and formatting Fix check for leading whitespace in line Reformat to avoid nesting Remove setting sdata->authenticated to 0, the default is 0 Better error messages and handle case for fgets failing Increment lineno at start Signed-off-by: Gauravsingh Sisodia Reviewed-by: Jakub Jelen --- examples/ssh_server.c | 93 +++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/examples/ssh_server.c b/examples/ssh_server.c index fab8f96e..4b91807e 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -55,6 +55,7 @@ The goal is to show the API in action. #define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) #define SFTP_SERVER_PATH "/usr/lib/sftp-server" +#define AUTH_KEYS_MAX_LINE_SIZE 2048 static void set_default_keys(ssh_bind sshbind, int rsa_already_set, @@ -541,6 +542,15 @@ static int auth_publickey(ssh_session session, void *userdata) { struct session_data_struct *sdata = (struct session_data_struct *) userdata; + ssh_key key = NULL; + FILE *fp = NULL; + char line[AUTH_KEYS_MAX_LINE_SIZE] = {0}; + char *p = NULL; + const char *q = NULL; + unsigned int lineno = 0; + int result; + int i; + enum ssh_keytypes_e type; (void) user; (void) session; @@ -553,31 +563,72 @@ static int auth_publickey(ssh_session session, return SSH_AUTH_DENIED; } - // valid so far. Now look through authorized keys for a match - if (authorizedkeys[0]) { - ssh_key key = NULL; - int result; - struct stat buf; - - if (stat(authorizedkeys, &buf) == 0) { - result = ssh_pki_import_pubkey_file( authorizedkeys, &key ); - if ((result != SSH_OK) || (key==NULL)) { - fprintf(stderr, - "Unable to import public key file %s\n", - authorizedkeys); - } else { - result = ssh_key_cmp( key, pubkey, SSH_KEY_CMP_PUBLIC ); - ssh_key_free(key); - if (result == 0) { - sdata->authenticated = 1; - return SSH_AUTH_SUCCESS; - } + fp = fopen(authorizedkeys, "r"); + if (fp == NULL) { + fprintf(stderr, "Error: opening authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); + return SSH_AUTH_DENIED; + } + + while (fgets(line, sizeof(line), fp)) { + lineno++; + + /* Skip leading whitespace and ignore comments */ + p = line; + + for (i = 0; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (!isspace((int)p[i])) { + break; + } + } + + if (p[i] == '#' || p[i] == '\0' || p[i] == '\n') { + continue; + } + + q = &p[i]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + + q = &p[i + 1]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; } } + + result = ssh_pki_import_pubkey_base64(q, type, &key); + if (result != SSH_OK) { + fprintf(stderr, + "Warning: Cannot import key on line no. %d in authorized keys file: %s\n", + lineno, + authorizedkeys); + continue; + } + + result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC); + ssh_key_free(key); + if (result == 0) { + sdata->authenticated = 1; + fclose(fp); + return SSH_AUTH_SUCCESS; + } + } + if (ferror(fp) != 0) { + fprintf(stderr, + "Error: Reading from authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); } + fclose(fp); - // no matches - sdata->authenticated = 0; + /* no matches */ return SSH_AUTH_DENIED; } From 2c918aad6763754bdffb84796b410e21f24bb7ec Mon Sep 17 00:00:00 2001 From: Clemens Lang Date: Fri, 19 Jan 2024 11:37:40 +0000 Subject: [PATCH 117/795] tests: Use /tmp for tmpdirs that contain sockets Socket paths have a length limit, and depending on the working directory of the source code, these tests occasionally fail if the path is too long. Avoid this by using a template string that is absolute and in /tmp, which should avoid the socket path length issues. This fixes building libssh with pkcs11 provider support in 'fedpkg mockbuild'. Signed-off-by: Clemens Lang Reviewed-by: Jakub Jelen --- tests/client/torture_auth_pkcs11.c | 2 +- tests/unittests/torture_pki_ecdsa_uri.c | 2 +- tests/unittests/torture_pki_rsa_uri.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/client/torture_auth_pkcs11.c b/tests/client/torture_auth_pkcs11.c index f0658484..2537d2d8 100644 --- a/tests/client/torture_auth_pkcs11.c +++ b/tests/client/torture_auth_pkcs11.c @@ -40,7 +40,7 @@ #define LIBSSH_ECDSA_384_TESTKEY "id_pkcs11_ecdsa_384" #define LIBSSH_ECDSA_521_TESTKEY "id_pkcs11_ecdsa_521" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; struct pki_st { char *temp_dir; diff --git a/tests/unittests/torture_pki_ecdsa_uri.c b/tests/unittests/torture_pki_ecdsa_uri.c index fe38c6c6..fd3088b8 100644 --- a/tests/unittests/torture_pki_ecdsa_uri.c +++ b/tests/unittests/torture_pki_ecdsa_uri.c @@ -27,7 +27,7 @@ #define PUB_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=public" #define PUB_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=public" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; const unsigned char INPUT[] = "1234567890123456789012345678901234567890" "123456789012345678901234"; struct pki_st { diff --git a/tests/unittests/torture_pki_rsa_uri.c b/tests/unittests/torture_pki_rsa_uri.c index a13e470c..46c9a083 100644 --- a/tests/unittests/torture_pki_rsa_uri.c +++ b/tests/unittests/torture_pki_rsa_uri.c @@ -16,7 +16,7 @@ #define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" #define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" -const char template[] = "temp_dir_XXXXXX"; +const char template[] = "/tmp/temp_dir_XXXXXX"; const unsigned char INPUT[] = "1234567890123456789012345678901234567890" "123456789012345678901234"; struct pki_st { From 172f6bfb47cf91544e32ca12b612bac41ba0bd0e Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 8 Feb 2024 11:28:00 +0100 Subject: [PATCH 118/795] tests:pkd: Add missing includes for cmocka Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/pkd/pkd_hello.c | 1 + tests/pkd/pkd_keyutil.c | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/pkd/pkd_hello.c b/tests/pkd/pkd_hello.c index a64124d3..069ed8df 100644 --- a/tests/pkd/pkd_hello.c +++ b/tests/pkd/pkd_hello.c @@ -8,6 +8,7 @@ #include #include // for cmocka #include // for cmocka +#include // for cmocka #include #include #include diff --git a/tests/pkd/pkd_keyutil.c b/tests/pkd/pkd_keyutil.c index 4e032f17..daaab134 100644 --- a/tests/pkd/pkd_keyutil.c +++ b/tests/pkd/pkd_keyutil.c @@ -8,6 +8,7 @@ #include // for cmocka #include // for cmocka +#include // for cmocka #include // for cmocka #include From 4172752b4bcdc6d85b840786daf2104e8bf98d93 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 15 Jan 2024 11:05:59 +0100 Subject: [PATCH 119/795] sftp: Handle read/write limits in the old low-level SFTP API Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/sftp.h | 12 +++++++++--- src/sftp.c | 23 ++++++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index ffa315c3..dc1ad4de 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -489,6 +489,11 @@ LIBSSH_API void sftp_file_set_blocking(sftp_file handle); /** * @brief Read from a file using an opened sftp file handle. * + * This function caps the length a user is allowed to read from an sftp file. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * * @param file The opened sftp file handle to be read from. * * @param buf Pointer to buffer to receive read data. @@ -567,9 +572,10 @@ SSH_DEPRECATED LIBSSH_API int sftp_async_read(sftp_file file, /** * @brief Write to a file using an opened sftp file handle. * - * The maximum size of the SFTP packet payload is 32768 bytes so the count - * parameter is capped at this value. This is low-level function so it does not - * try to send more than this amount of data. + * This function caps the length a user is allowed to write to an sftp file. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). * * @param file Open sftp file handle to write to. * diff --git a/src/sftp.c b/src/sftp.c index 29341d55..2bfe04e4 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -1156,6 +1156,18 @@ ssize_t sftp_read(sftp_file handle, void *buf, size_t count) { return 0; } + /* + * limit the reads to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + */ + if (count > sftp->limits->max_read_length) { + count = sftp->limits->max_read_length; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1396,16 +1408,17 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) { id = sftp_get_new_id(file->sftp); - - /* limit the writes to the maximum specified in Section 3 of + /* + * limit the writes to the maximum specified in Section 3 of * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. * - * FIXME: This value should be adjusted to the value from the - * limits@openssh.com extension if supported * TODO: We should iterate over the blocks rather than writing less than * requested to provide less surprises to the calling applications. */ - count = count > 32768 ? 32768 : count; + if (count > sftp->limits->max_write_length) { + count = sftp->limits->max_write_length; + } rc = ssh_buffer_pack(buffer, "dSqdP", From 3e2bbbc96a81909c0949b89fb14361122248ff28 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 16 Jan 2024 14:05:03 +0100 Subject: [PATCH 120/795] sftp: Fix copy&paste error in the doxygen comment Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/sftp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index dc1ad4de..d7924390 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -500,7 +500,7 @@ LIBSSH_API void sftp_file_set_blocking(sftp_file handle); * * @param count Size of the buffer in bytes. * - * @return Number of bytes written, < 0 on error with ssh and sftp + * @return Number of bytes read, < 0 on error with ssh and sftp * error set. * * @see sftp_get_error() From fbfc9b359560bd2265b71ee6324c8b66575f1d45 Mon Sep 17 00:00:00 2001 From: renmingshuai Date: Mon, 19 Feb 2024 16:18:34 +0800 Subject: [PATCH 121/795] Fix a syntax error Signed-off-by: renmingshuai Reviewed-by: Jakub Jelen --- include/libssh/config_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libssh/config_parser.h b/include/libssh/config_parser.h index ca353432..4648614c 100644 --- a/include/libssh/config_parser.h +++ b/include/libssh/config_parser.h @@ -51,7 +51,7 @@ int ssh_config_get_yesno(char **str, int notfound); * be stored or NULL if we do not care about the result. * @param[out] port Pointer to the location, where the new port will * be stored or NULL if we do not care about the result. - * @param[in] ignore_port Set to true if the we should not attempt to parse + * @param[in] ignore_port Set to true if we should not attempt to parse * port number. * * @returns SSH_OK if the provided string is in format of SSH URI, From ff111a4a8b7d12e3b83ab6663d5fb2952861fa01 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 15 Feb 2024 13:58:58 +0100 Subject: [PATCH 122/795] cmake: Use Python find_package Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- CMakeLists.txt | 2 +- cmake/Modules/FindABIMap.cmake | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5885770d..57bc8b6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5.0) +cmake_minimum_required(VERSION 3.12.0) cmake_policy(SET CMP0048 NEW) # Specify search path for CMake modules to be loaded by include() diff --git a/cmake/Modules/FindABIMap.cmake b/cmake/Modules/FindABIMap.cmake index 5117b498..e7f725d2 100644 --- a/cmake/Modules/FindABIMap.cmake +++ b/cmake/Modules/FindABIMap.cmake @@ -220,13 +220,12 @@ # Search for python which is required if (ABIMap_FIND_REQURIED) - find_package(PythonInterp REQUIRED) + find_package(Python REQUIRED) else() - find_package(PythonInterp) + find_package(Python) endif() - -if (PYTHONINTERP_FOUND) +if (TARGET Python::Interpreter) # Search for abimap tool used to generate the map files find_program(ABIMAP_EXECUTABLE NAMES abimap DOC "path to the abimap executable") mark_as_advanced(ABIMAP_EXECUTABLE) From 486d2289faeaf79566d9b6331f041e5c5b457230 Mon Sep 17 00:00:00 2001 From: Gregor Jasny Date: Wed, 5 Apr 2023 16:31:20 +0200 Subject: [PATCH 123/795] cmake: remove fallback for crypto lib lookup because if a fallback happens, the WITH_(GCRYPT|MBEDTLS) variables do not match the selection, anymore. Also a silent fallback is pretty bad if it is unnoticed. Signed-off-by: Gregor Jasny Reviewed-by: Andreas Schneider --- CMakeLists.txt | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 57bc8b6b..9e3ab039 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,32 +49,16 @@ endif (WITH_ZLIB) if (WITH_GCRYPT) find_package(GCrypt 1.5.0 REQUIRED) - if (NOT GCRYPT_FOUND) - message(FATAL_ERROR "Could not find GCrypt") - endif (NOT GCRYPT_FOUND) elseif(WITH_MBEDTLS) find_package(MbedTLS REQUIRED) - if (NOT MBEDTLS_FOUND) - message(FATAL_ERROR "Could not find mbedTLS") - endif (NOT MBEDTLS_FOUND) -else (WITH_GCRYPT) - find_package(OpenSSL 1.1.1) - if (OPENSSL_FOUND) - # On CMake < 3.16, OPENSSL_CRYPTO_LIBRARIES is usually a synonym for OPENSSL_CRYPTO_LIBRARY, but is not defined - # when building on Windows outside of Cygwin. We provide the synonym here, if FindOpenSSL didn't define it already. - if (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) - set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) - endif (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) - else (OPENSSL_FOUND) - find_package(GCrypt) - if (NOT GCRYPT_FOUND) - find_package(MbedTLS) - if (NOT MBEDTLS_FOUND) - message(FATAL_ERROR "Could not find OpenSSL, GCrypt or mbedTLS") - endif (NOT MBEDTLS_FOUND) - endif (NOT GCRYPT_FOUND) - endif (OPENSSL_FOUND) -endif(WITH_GCRYPT) +else() + find_package(OpenSSL 1.1.1 REQUIRED) + # On CMake < 3.16, OPENSSL_CRYPTO_LIBRARIES is usually a synonym for OPENSSL_CRYPTO_LIBRARY, but is not defined + # when building on Windows outside of Cygwin. We provide the synonym here, if FindOpenSSL didn't define it already. + if (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) + set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + endif (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) +endif() if (UNIT_TESTING) find_package(CMocka REQUIRED) From 6ad455a8acfe6032c2a87cf83f2d20463c30f8af Mon Sep 17 00:00:00 2001 From: Gregor Jasny Date: Wed, 5 Apr 2023 16:36:19 +0200 Subject: [PATCH 124/795] cmake: use imported targets for OpenSSL and zlib Imported targets are highly preferred over the individual variables for includes and libs because they will be used in a coherent way and any spelling mistakes or unavailability won't go unnoticed. Also it will prevent bugs like conan-io/conan-center-index#16900 or using mismatching header/libs combinations. Signed-off-by: Gregor Jasny Reviewed-by: Andreas Schneider --- CMakeLists.txt | 6 ------ ConfigureChecks.cmake | 25 ++----------------------- src/CMakeLists.txt | 24 ++++-------------------- tests/CMakeLists.txt | 4 +--- tests/external_override/CMakeLists.txt | 2 +- tests/pkd/CMakeLists.txt | 1 - tests/unittests/CMakeLists.txt | 2 -- 7 files changed, 8 insertions(+), 56 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e3ab039..9c7b8c50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,4 @@ cmake_minimum_required(VERSION 3.12.0) -cmake_policy(SET CMP0048 NEW) # Specify search path for CMake modules to be loaded by include() # and find_package() @@ -53,11 +52,6 @@ elseif(WITH_MBEDTLS) find_package(MbedTLS REQUIRED) else() find_package(OpenSSL 1.1.1 REQUIRED) - # On CMake < 3.16, OPENSSL_CRYPTO_LIBRARIES is usually a synonym for OPENSSL_CRYPTO_LIBRARY, but is not defined - # when building on Windows outside of Cygwin. We provide the synonym here, if FindOpenSSL didn't define it already. - if (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) - set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) - endif (NOT DEFINED OPENSSL_CRYPTO_LIBRARIES) endif() if (UNIT_TESTING) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 334695f1..64aaea07 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -76,53 +76,32 @@ if (WIN32) endif (WIN32) if (OPENSSL_FOUND) - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) + check_include_file(openssl/des.h HAVE_OPENSSL_DES_H) if (NOT HAVE_OPENSSL_DES_H) message(FATAL_ERROR "Could not detect openssl/des.h") endif() - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) check_include_file(openssl/aes.h HAVE_OPENSSL_AES_H) if (NOT HAVE_OPENSSL_AES_H) message(FATAL_ERROR "Could not detect openssl/aes.h") endif() if (WITH_BLOWFISH_CIPHER) - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) check_include_file(openssl/blowfish.h HAVE_OPENSSL_BLOWFISH_H) endif() - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) check_include_file(openssl/ecdh.h HAVE_OPENSSL_ECDH_H) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) check_include_file(openssl/ec.h HAVE_OPENSSL_EC_H) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) check_include_file(openssl/ecdsa.h HAVE_OPENSSL_ECDSA_H) - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(EVP_KDF_CTX_new_id HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(EVP_KDF_CTX_new HAVE_OPENSSL_EVP_KDF_CTX_NEW) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(FIPS_mode HAVE_OPENSSL_FIPS_MODE) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) - - set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${OPENSSL_CRYPTO_LIBRARIES}) check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) - unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 216e149a..09390082 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,17 +8,9 @@ set(LIBSSH_LINK_LIBRARIES ${LIBSSH_REQUIRED_LIBRARIES} ) -if (OPENSSL_CRYPTO_LIBRARIES) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${OPENSSL_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${OPENSSL_CRYPTO_LIBRARIES} - ) -endif (OPENSSL_CRYPTO_LIBRARIES) +if (TARGET OpenSSL::Crypto) + list(APPEND LIBSSH_LINK_LIBRARIES OpenSSL::Crypto) +endif () if (MBEDTLS_CRYPTO_LIBRARY) set(LIBSSH_PRIVATE_INCLUDE_DIRS @@ -43,15 +35,7 @@ if (GCRYPT_LIBRARIES) endif() if (WITH_ZLIB) - set(LIBSSH_PRIVATE_INCLUDE_DIRS - ${LIBSSH_PRIVATE_INCLUDE_DIRS} - ${ZLIB_INCLUDE_DIR} - ) - - set(LIBSSH_LINK_LIBRARIES - ${LIBSSH_LINK_LIBRARIES} - ${ZLIB_LIBRARY} - ) + list(APPEND LIBSSH_LINK_LIBRARIES ZLIB::ZLIB) endif (WITH_ZLIB) if (WITH_GSSAPI AND GSSAPI_FOUND) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 34e6cf81..c4f97fc6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,9 +6,7 @@ endif (BSD OR SOLARIS OR OSX) set(TORTURE_LIBRARY torture) -include_directories(${OPENSSL_INCLUDE_DIR} - ${CMOCKA_INCLUDE_DIR} - ${ZLIB_INCLUDE_DIR} +include_directories(${CMOCKA_INCLUDE_DIR} ${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR} ${libssh_SOURCE_DIR}/src diff --git a/tests/external_override/CMakeLists.txt b/tests/external_override/CMakeLists.txt index 7c34b8c8..365a1083 100644 --- a/tests/external_override/CMakeLists.txt +++ b/tests/external_override/CMakeLists.txt @@ -44,7 +44,7 @@ else () ${libssh_SOURCE_DIR}/src/md_crypto.c ) set(override_libs - ${OPENSSL_CRYPTO_LIBRARIES} + OpenSSL::Crypto ) endif (WITH_GCRYPT) diff --git a/tests/pkd/CMakeLists.txt b/tests/pkd/CMakeLists.txt index 3dc72c75..9a7038cc 100644 --- a/tests/pkd/CMakeLists.txt +++ b/tests/pkd/CMakeLists.txt @@ -5,7 +5,6 @@ if (WITH_SERVER AND UNIX AND NOT WIN32) include_directories(${libssh_SOURCE_DIR}/include ${libssh_BINARY_DIR}/include ${CMOCKA_INCLUDE_DIR} - ${ZLIB_INCLUDE_DIR} ${CMAKE_BINARY_DIR} ${libssh_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 04fcba11..e4ee846a 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -1,7 +1,5 @@ project(unittests C) -include_directories(${OPENSSL_INCLUDE_DIR}) - set(LIBSSH_UNIT_TESTS torture_bignum torture_buffer From 1291ceb17d6abd97dd487635cfeef1aac36f2ff4 Mon Sep 17 00:00:00 2001 From: Daniel Evers Date: Wed, 11 Oct 2023 19:01:06 +0200 Subject: [PATCH 125/795] Fix #157: Allow to set terminal modes for PTYs Added the new function `ssh_channel_request_pty_size_modes` which allows to pass additional encoded SSH terminal modes (see opcodes in RFC 4245). Signed-off-by: Daniel Evers (daniel.evers@utimaco.com) Reviewed-by: Jakub Jelen --- include/libssh/libssh.h | 2 + include/libssh/libsshpp.hpp | 7 +- src/channels.c | 22 +++- src/libssh.map | 1 + tests/client/CMakeLists.txt | 1 + tests/client/torture_request_pty_modes.c | 145 +++++++++++++++++++++++ 6 files changed, 171 insertions(+), 7 deletions(-) mode change 100644 => 100755 tests/client/CMakeLists.txt create mode 100755 tests/client/torture_request_pty_modes.c diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 77ecb4f3..13f1b812 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -479,6 +479,8 @@ LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, int cols, int rows); +LIBSSH_API int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *term, + int cols, int rows, const unsigned char* modes, size_t modes_len); LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); LIBSSH_API int ssh_channel_request_send_break(ssh_channel channel, uint32_t length); diff --git a/include/libssh/libsshpp.hpp b/include/libssh/libsshpp.hpp index 602c7aec..e0f21e85 100644 --- a/include/libssh/libsshpp.hpp +++ b/include/libssh/libsshpp.hpp @@ -587,9 +587,12 @@ class Channel { ssh_throw(err); return_throwable; } - void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0){ + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0, + const unsigned char* modes=NULL, size_t modes_len=0){ int err; - if(term != NULL && cols != 0 && rows != 0) + if(term != NULL && cols != 0 && rows != 0 && modes != NULL) + err=ssh_channel_request_pty_size_modes(channel,term,cols,rows,modes,modes_len); + else if(term != NULL && cols != 0 && rows != 0) err=ssh_channel_request_pty_size(channel,term,cols,rows); else err=ssh_channel_request_pty(channel); diff --git a/src/channels.c b/src/channels.c index 4e3de321..ee3c8eca 100644 --- a/src/channels.c +++ b/src/channels.c @@ -1927,13 +1927,17 @@ static int channel_request(ssh_channel channel, const char *request, * * @param[in] row The number of rows. * + * @param[in] modes Encoded SSH terminal modes for the PTY + * + * @param[in] modes_len Number of bytes in 'modes' + * * @return SSH_OK on success, * SSH_ERROR if an error occurred, * SSH_AGAIN if in nonblocking mode and call has * to be done again. */ -int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, - int col, int row) +int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal, + int col, int row, const unsigned char* modes, size_t modes_len) { ssh_session session; ssh_buffer buffer = NULL; @@ -1963,14 +1967,14 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, } rc = ssh_buffer_pack(buffer, - "sdddddb", + "sdddddP", terminal, col, row, 0, /* pix */ 0, /* pix */ - 1, /* add a 0byte string */ - 0); + (uint32_t)modes_len, + modes_len, modes); if (rc != SSH_OK) { ssh_set_error_oom(session); @@ -1984,6 +1988,14 @@ int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, return rc; } +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) +{ + /* default modes/options: none */ + const unsigned char modes[1] = {0}; + return ssh_channel_request_pty_size_modes(channel, terminal, col, row, modes, sizeof(modes)); +} + /** * @brief Request a PTY. * diff --git a/src/libssh.map b/src/libssh.map index e0d310dd..558f921d 100644 --- a/src/libssh.map +++ b/src/libssh.map @@ -471,5 +471,6 @@ LIBSSH_AFTER_4_9_0 sftp_aio_wait_write; ssh_pki_export_privkey_base64_format; ssh_pki_export_privkey_file_format; + ssh_channel_request_pty_size_modes; } LIBSSH_4_9_0; diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt old mode 100644 new mode 100755 index 0e7aa288..adfc0e74 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -17,6 +17,7 @@ set(LIBSSH_CLIENT_TESTS torture_proxycommand torture_session torture_request_env + torture_request_pty_modes torture_client_global_requests) find_program(SCP_EXECUTABLE NAMES scp) diff --git a/tests/client/torture_request_pty_modes.c b/tests/client/torture_request_pty_modes.c new file mode 100755 index 00000000..8004c52c --- /dev/null +++ b/tests/client/torture_request_pty_modes.c @@ -0,0 +1,145 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * 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 + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + 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_request_pty_modes_translate_ocrnl(void **state) +{ + const unsigned char modes[] = { + /* enable OCRNL */ + 73, 0, 0, 0, 1, + /* disable all other CR/NL handling */ + 34, 0, 0, 0, 0, + 35, 0, 0, 0, 0, + 36, 0, 0, 0, 0, + 72, 0, 0, 0, 0, + 74, 0, 0, 0, 0, + 75, 0, 0, 0, 0, + 0, /* TTY_OP_END */ + }; + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + char buffer[4096] = {0}; + int nbytes; + int rc; + int string_found = 0; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_pty_size_modes(c, "xterm", 80, 25, modes, sizeof(modes)); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[nbytes]='\0'; + /* expect 2 newline characters */ + if (strstr(buffer, ">TEST\n\n<") != NULL) { + string_found = 1; + break; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_pty_modes_translate_ocrnl, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} + From b5daac6772be9dd52a60fa5a0c22ddd9a63d2e92 Mon Sep 17 00:00:00 2001 From: Daniel Evers Date: Wed, 11 Oct 2023 20:18:09 +0200 Subject: [PATCH 126/795] Issue #157: Added documentation Signed-off-by: Daniel Evers (daniel.evers@utimaco.com) Reviewed-by: Jakub Jelen --- doc/shell.dox | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/shell.dox b/doc/shell.dox index d770f27a..2cf400a5 100644 --- a/doc/shell.dox +++ b/doc/shell.dox @@ -65,8 +65,14 @@ to as a "pty", for "pseudo-teletype". The remote processes won't see the difference with a real text-oriented terminal. If needed, you request the pty with the function ssh_channel_request_pty(). -Then you define its dimensions (number of rows and columns) -with ssh_channel_change_pty_size(). +If you want define its dimensions (number of rows and columns), +call ssh_channel_request_pty_size() instead. It's also possible to change +the dimensions after creating the pty with ssh_channel_change_pty_size(). + +If you want to change the terminal modes used by the pty (e.g. to change +CRLF handling), use ssh_channel_request_pty_size_modes(). This function +accepts an additional "modes" buffer that is expected to contain encoded +terminal modes according to RFC 4254 section 8. Be your session interactive or not, the next step is to request a shell with ssh_channel_request_shell(). From cd6e84a6c3602cd75fc9bfa4d66c5a5cbfb3dc10 Mon Sep 17 00:00:00 2001 From: Daniel Evers Date: Sat, 28 Oct 2023 16:16:11 +0200 Subject: [PATCH 127/795] Issue #157: Use the current TTY's settings by default. When opening a PTY on the server, try to use the current TTY's settings (i.e. based on STDIN). If that fails or STDIN isn't a TTY, use default modes that avoid any character translation. Don't rely on stdin to be a TTY (breaks CI). Instead, open a PTY and temporarily use that as "fake" stdin. Signed-off-by: Daniel Evers (daniel.evers@utimaco.com) Reviewed-by: Jakub Jelen --- include/libssh/priv.h | 8 + src/CMakeLists.txt | 1 + src/channels.c | 15 +- src/ttyopts.c | 442 +++++++++++++++++++++++ tests/client/CMakeLists.txt | 2 +- tests/client/torture_request_pty_modes.c | 138 ++++++- 6 files changed, 589 insertions(+), 17 deletions(-) create mode 100644 src/ttyopts.c diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 4ec3b2a7..596cc2d6 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -47,6 +47,10 @@ # endif #endif /* !defined(HAVE_STRTOULL) */ +#ifdef HAVE_TERMIOS_H +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -452,6 +456,10 @@ bool is_ssh_initialized(void); #define SSH_ERRNO_MSG_MAX 1024 char *ssh_strerror(int err_num, char *buf, size_t buflen); +/** 55 defined options (5 bytes each) + terminator */ +#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) +int encode_current_tty_opts(unsigned char *buf, size_t buflen); + #ifdef __cplusplus } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09390082..748bb7a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,6 +127,7 @@ set(libssh_SRCS socket.c string.c threads.c + ttyopts.c wrapper.c external/bcrypt_pbkdf.c external/blowfish.c diff --git a/src/channels.c b/src/channels.c index ee3c8eca..9e613715 100644 --- a/src/channels.c +++ b/src/channels.c @@ -1991,9 +1991,18 @@ int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, int col, int row) { - /* default modes/options: none */ - const unsigned char modes[1] = {0}; - return ssh_channel_request_pty_size_modes(channel, terminal, col, row, modes, sizeof(modes)); + /* use modes from the current TTY */ + unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + if (rc < 0) { + return rc; + } + return ssh_channel_request_pty_size_modes(channel, + terminal, + col, + row, + modes_buf, + (size_t)rc); } /** diff --git a/src/ttyopts.c b/src/ttyopts.c new file mode 100644 index 00000000..d30d62d0 --- /dev/null +++ b/src/ttyopts.c @@ -0,0 +1,442 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH + * + * 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 +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif + +/** Terminal mode opcodes */ +enum { + TTY_OP_END = 0, + TTY_OP_VINTR = 1, + TTY_OP_VQUIT = 2, + TTY_OP_VERASE = 3, + TTY_OP_VKILL = 4, + TTY_OP_VEOF = 5, + TTY_OP_VEOL = 6, + TTY_OP_VEOL2 = 7, + TTY_OP_VSTART = 8, + TTY_OP_VSTOP = 9, + TTY_OP_VSUSP = 10, + TTY_OP_VDSUSP = 11, + TTY_OP_VREPRINT = 12, + TTY_OP_VWERASE = 13, + TTY_OP_VLNEXT = 14, + TTY_OP_VFLUSH = 15, + TTY_OP_VSWTC = 16, + TTY_OP_VSTATUS = 17, + TTY_OP_VDISCARD = 18, + TTY_OP_IGNPAR = 30, + TTY_OP_PARMRK = 31, + TTY_OP_INPCK = 32, + TTY_OP_ISTRIP = 33, + TTY_OP_INLCR = 34, + TTY_OP_IGNCR = 35, + TTY_OP_ICRNL = 36, + TTY_OP_IUCLC = 37, + TTY_OP_IXON = 38, + TTY_OP_IXANY = 39, + TTY_OP_IXOFF = 40, + TTY_OP_IMAXBEL = 41, + TTY_OP_IUTF8 = 42, + TTY_OP_ISIG = 50, + TTY_OP_ICANON = 51, + TTY_OP_XCASE = 52, + TTY_OP_ECHO = 53, + TTY_OP_ECHOE = 54, + TTY_OP_ECHOK = 55, + TTY_OP_ECHONL = 56, + TTY_OP_NOFLSH = 57, + TTY_OP_TOSTOP = 58, + TTY_OP_IEXTEN = 59, + TTY_OP_ECHOCTL = 60, + TTY_OP_ECHOKE = 61, + TTY_OP_PENDIN = 62, + TTY_OP_OPOST = 70, + TTY_OP_OLCUC = 71, + TTY_OP_ONLCR = 72, + TTY_OP_OCRNL = 73, + TTY_OP_ONOCR = 74, + TTY_OP_ONLRET = 75, + TTY_OP_CS7 = 90, + TTY_OP_CS8 = 91, + TTY_OP_PARENB = 92, + TTY_OP_PARODD = 93, + TTY_OP_ISPEED = 128, + TTY_OP_OSPEED = 129, +}; + +/** + * Encodes a single SSH terminal mode option into the buffer. + * + * @param[in] attr The mode's opcode value. + * + * @param[in] value The mode's value. + * + * @param[out] buf Destination buffer to encode into. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes written to the buffer on success, -1 on + * error. + */ +static int +encode_termios_opt(unsigned char opcode, + uint32_t value, + unsigned char *buf, + size_t buflen) +{ + int offset = 0; + + /* always need 5 bytes */ + if (buflen < 5) { + return -1; + } + + /* 1 byte opcode */ + buf[offset++] = opcode; + + /* 4 bytes value (big endian) */ + value = htonl(value); + memcpy(buf + offset, &value, sizeof(value)); + offset += sizeof(value); + + return offset; +} + +#ifdef HAVE_TERMIOS_H +/** Converts a baudrate constant (Bxxxx) to a numeric value. */ +static int +baud2speed(int baudrate) +{ + switch (baudrate) { + default: + case B0: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + case B230400: + return 230400; + } +} + +/** + * Encodes all terminal options from the given \c termios structure + * into the buffer. + * + * @param[in] attr The terminal options to encode. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + +#define SSH_ENCODE_INPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0) + SSH_ENCODE_INPUT_OPT(IGNPAR) + SSH_ENCODE_INPUT_OPT(PARMRK) + SSH_ENCODE_INPUT_OPT(INPCK) + SSH_ENCODE_INPUT_OPT(ISTRIP) + SSH_ENCODE_INPUT_OPT(INLCR) + SSH_ENCODE_INPUT_OPT(IGNCR) + SSH_ENCODE_INPUT_OPT(ICRNL) + SSH_ENCODE_INPUT_OPT(IUCLC) + SSH_ENCODE_INPUT_OPT(IXON) + SSH_ENCODE_INPUT_OPT(IXANY) + SSH_ENCODE_INPUT_OPT(IXOFF) + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#ifdef IUTF8 + SSH_ENCODE_INPUT_OPT(IUTF8) +#endif +#undef SSH_ENCODE_INPUT_OPT + +#define SSH_ENCODE_OUTPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) + SSH_ENCODE_OUTPUT_OPT(OPOST) + SSH_ENCODE_OUTPUT_OPT(OLCUC) + SSH_ENCODE_OUTPUT_OPT(ONLCR) + SSH_ENCODE_OUTPUT_OPT(OCRNL) + SSH_ENCODE_OUTPUT_OPT(ONOCR) + SSH_ENCODE_OUTPUT_OPT(ONLRET) +#undef SSH_ENCODE_OUTPUT_OPT + +#define SSH_ENCODE_CONTROL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0) + SSH_ENCODE_CONTROL_OPT(CS7) + SSH_ENCODE_CONTROL_OPT(CS8) + SSH_ENCODE_CONTROL_OPT(PARENB) + SSH_ENCODE_CONTROL_OPT(PARODD) +#undef SSH_ENCODE_CONTROL_OPT + +#define SSH_ENCODE_LOCAL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) + SSH_ENCODE_LOCAL_OPT(ISIG) + SSH_ENCODE_LOCAL_OPT(ICANON) + SSH_ENCODE_LOCAL_OPT(XCASE) + SSH_ENCODE_LOCAL_OPT(ECHO) + SSH_ENCODE_LOCAL_OPT(ECHOE) + SSH_ENCODE_LOCAL_OPT(ECHOK) + SSH_ENCODE_LOCAL_OPT(ECHONL) + SSH_ENCODE_LOCAL_OPT(NOFLSH) + SSH_ENCODE_LOCAL_OPT(TOSTOP) + SSH_ENCODE_LOCAL_OPT(IEXTEN) + SSH_ENCODE_LOCAL_OPT(ECHOCTL) + SSH_ENCODE_LOCAL_OPT(ECHOKE) + SSH_ENCODE_LOCAL_OPT(PENDIN) +#undef SSH_ENCODE_LOCAL_OPT + +#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt]) + SSH_ENCODE_CC_OPT(VINTR) + SSH_ENCODE_CC_OPT(VQUIT) + SSH_ENCODE_CC_OPT(VERASE) + SSH_ENCODE_CC_OPT(VKILL) + SSH_ENCODE_CC_OPT(VEOF) + SSH_ENCODE_CC_OPT(VEOL) + SSH_ENCODE_CC_OPT(VEOL2) + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif + SSH_ENCODE_CC_OPT(VREPRINT) + SSH_ENCODE_CC_OPT(VWERASE) + SSH_ENCODE_CC_OPT(VLNEXT) +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif + SSH_ENCODE_CC_OPT(VDISCARD) +#undef SSH_ENCODE_CC_OPT + + SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr))) + SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr))) +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} +#endif + +/** + * Encodes a set of default options to ensure "sane" PTY behavior. + * This function intentionally doesn't use the \c termios structure + * to allow it to work on Windows as well. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_default_opts(unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + + SSH_ENCODE_OPT(TTY_OP_VINTR, 003) + SSH_ENCODE_OPT(TTY_OP_VQUIT, 034) + SSH_ENCODE_OPT(TTY_OP_VERASE, 0177) + SSH_ENCODE_OPT(TTY_OP_VKILL, 025) + SSH_ENCODE_OPT(TTY_OP_VEOF, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL2, 0) + SSH_ENCODE_OPT(TTY_OP_VSTART, 021) + SSH_ENCODE_OPT(TTY_OP_VSTOP, 023) + SSH_ENCODE_OPT(TTY_OP_VSUSP, 032) + SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031) + SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022) + SSH_ENCODE_OPT(TTY_OP_VWERASE, 027) + SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026) + SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017) + SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0) + SSH_ENCODE_OPT(TTY_OP_PARMRK, 0) + SSH_ENCODE_OPT(TTY_OP_INPCK, 0) + SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0) + SSH_ENCODE_OPT(TTY_OP_INLCR, 0) + SSH_ENCODE_OPT(TTY_OP_IGNCR, 0) + SSH_ENCODE_OPT(TTY_OP_ICRNL, 0) + SSH_ENCODE_OPT(TTY_OP_IUCLC, 0) + SSH_ENCODE_OPT(TTY_OP_IXON, 1) + SSH_ENCODE_OPT(TTY_OP_IXANY, 0) + SSH_ENCODE_OPT(TTY_OP_IXOFF, 0) + SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0) + SSH_ENCODE_OPT(TTY_OP_IUTF8, 1) + SSH_ENCODE_OPT(TTY_OP_ISIG, 1) + SSH_ENCODE_OPT(TTY_OP_ICANON, 1) + SSH_ENCODE_OPT(TTY_OP_XCASE, 0) + SSH_ENCODE_OPT(TTY_OP_ECHO, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOE, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOK, 1) + SSH_ENCODE_OPT(TTY_OP_ECHONL, 0) + SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0) + SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0) + SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 0) + SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1) + SSH_ENCODE_OPT(TTY_OP_PENDIN, 0) + SSH_ENCODE_OPT(TTY_OP_OPOST, 1) + SSH_ENCODE_OPT(TTY_OP_OLCUC, 0) + SSH_ENCODE_OPT(TTY_OP_ONLCR, 0) + SSH_ENCODE_OPT(TTY_OP_OCRNL, 0) + SSH_ENCODE_OPT(TTY_OP_ONOCR, 0) + SSH_ENCODE_OPT(TTY_OP_ONLRET, 0) + SSH_ENCODE_OPT(TTY_OP_CS7, 1) + SSH_ENCODE_OPT(TTY_OP_CS8, 1) + SSH_ENCODE_OPT(TTY_OP_PARENB, 0) + SSH_ENCODE_OPT(TTY_OP_PARODD, 0) + SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400); + SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400); + +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} + +/** + * @ingroup libssh_misc + * + * @brief Encode the current TTY options as SSH modes. + * + * Call this function to determine the settings of the process' TTY and + * encode them as SSH Terminal Modes according to RFC 4254 section 8. + * + * If STDIN isn't connected to a TTY, this function fills the buffer with + * "sane" default modes. + * + * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes . + * + * @code + * unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + * encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + * @endcode + * + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +int +encode_current_tty_opts(unsigned char *buf, size_t buflen) +{ +#ifdef HAVE_TERMIOS_H + struct termios attr; + ZERO_STRUCT(attr); + + if (isatty(STDIN_FILENO)) { + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + return encode_termios_opts(&attr, buf, buflen); + } +#endif + + /* use "sane" default attributes */ + return encode_default_opts(buf, buflen); +} diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index adfc0e74..c99c94ef 100755 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -67,7 +67,7 @@ foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) add_cmocka_test(${_CLI_TEST} SOURCES ${_CLI_TEST}.c COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} - LINK_LIBRARIES ${TORTURE_LIBRARY} + LINK_LIBRARIES ${TORTURE_LIBRARY} util ) if (OSX) diff --git a/tests/client/torture_request_pty_modes.c b/tests/client/torture_request_pty_modes.c index 8004c52c..a00be2a7 100755 --- a/tests/client/torture_request_pty_modes.c +++ b/tests/client/torture_request_pty_modes.c @@ -27,8 +27,13 @@ #include #include +#include #include #include +#include +#include +#include +#include static int sshd_setup(void **state) { @@ -75,6 +80,26 @@ static int session_teardown(void **state) return 0; } +/* reads from the channel, expecting the given output */ +static int check_channel_output(ssh_channel c, const char *expected) +{ + char buffer[4096] = {0}; + int nbytes; + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[nbytes]='\0'; + if (strstr(buffer, expected) != NULL) + { + return 1; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + return 0; +} + +/* set explicit TTY modes and validate that the server uses them */ static void torture_request_pty_modes_translate_ocrnl(void **state) { const unsigned char modes[] = { @@ -92,8 +117,6 @@ static void torture_request_pty_modes_translate_ocrnl(void **state) struct torture_state *s = *state; ssh_session session = s->ssh.session; ssh_channel c; - char buffer[4096] = {0}; - int nbytes; int rc; int string_found = 0; @@ -106,20 +129,103 @@ static void torture_request_pty_modes_translate_ocrnl(void **state) rc = ssh_channel_request_pty_size_modes(c, "xterm", 80, 25, modes, sizeof(modes)); assert_ssh_return_code(session, rc); - rc = ssh_channel_request_exec(c, "echo -e '>TEST\\r\\n<'"); + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); assert_ssh_return_code(session, rc); - nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); - while (nbytes > 0) { - buffer[nbytes]='\0'; - /* expect 2 newline characters */ - if (strstr(buffer, ">TEST\n\n<") != NULL) { - string_found = 1; - break; - } + /* expect 2 newline characters */ + string_found = check_channel_output(c, ">TEST\n\n<"); + assert_int_equal(string_found, 1); - nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); - } + ssh_channel_close(c); +} + +/* if stdin is a TTY, its modes are passed to the server */ +static void torture_request_pty_modes_use_stdin_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + struct termios modes; + int stdin_backup_fd = -1; + int master_fd, slave_fd; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must be a TTY, so open one and replace the FD */ + stdin_backup_fd = dup(STDIN_FILENO); + rc = openpty(&master_fd, &slave_fd, NULL, NULL, NULL); + assert_int_equal(rc, 0); + dup2(master_fd, STDIN_FILENO); + assert_true(isatty(STDIN_FILENO)); + /* translate NL to CRNL on output to see a noticeable effect */ + memset(&modes, 0, sizeof(modes)); + tcgetattr(STDIN_FILENO, &modes); + modes.c_oflag |= ONLCR; + modes.c_iflag &= ~(ICRNL | INLCR | IGNCR); + tcsetattr(STDIN_FILENO, TCSANOW, &modes); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + close(master_fd); + close(slave_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 carriage return characters + newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is NOT a TTY, default modes are passed to the server */ +static void torture_request_pty_modes_use_default_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + int stdin_backup_fd = -1; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must not a TTY - change the FD to something else */ + stdin_backup_fd = dup(STDIN_FILENO); + close(STDIN_FILENO); + rc = open("/dev/null", O_RDONLY); // reuses FD 0 now + assert_int_equal(rc, STDIN_FILENO); + assert_false(isatty(STDIN_FILENO)); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect the input unmodified */ + string_found = check_channel_output(c, ">TEST\r\n<"); assert_int_equal(string_found, 1); ssh_channel_close(c); @@ -132,6 +238,12 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_request_pty_modes_translate_ocrnl, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_stdin_modes, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_default_modes, + session_setup, + session_teardown), }; ssh_init(); From 6e5eb4ed2debe56281b4a5eb3e16c4329a95510f Mon Sep 17 00:00:00 2001 From: Daniel Evers Date: Wed, 8 Nov 2023 16:38:34 +0100 Subject: [PATCH 128/795] Issue #157: Adapted documentation to the latest code changes. Signed-off-by: Daniel Evers (daniel.evers@utimaco.com) Reviewed-by: Jakub Jelen --- doc/shell.dox | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/shell.dox b/doc/shell.dox index 2cf400a5..f51c489c 100644 --- a/doc/shell.dox +++ b/doc/shell.dox @@ -69,6 +69,9 @@ If you want define its dimensions (number of rows and columns), call ssh_channel_request_pty_size() instead. It's also possible to change the dimensions after creating the pty with ssh_channel_change_pty_size(). +These two functions configure the pty using the same terminal modes that +stdin has. If stdin isn't a TTY, they use default modes that configure +the pty with in canonical mode and e.g. preserving CR and LF characters. If you want to change the terminal modes used by the pty (e.g. to change CRLF handling), use ssh_channel_request_pty_size_modes(). This function accepts an additional "modes" buffer that is expected to contain encoded From a7d212cd7d58214607efdc3d9194f32b52f5d2f0 Mon Sep 17 00:00:00 2001 From: Daniel Evers Date: Fri, 2 Feb 2024 08:07:29 +0100 Subject: [PATCH 129/795] Issue #157: Added author Signed-off-by: Daniel Evers (daniel.evers@utimaco.com) Reviewed-by: Jakub Jelen --- src/ttyopts.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ttyopts.c b/src/ttyopts.c index d30d62d0..a37ab7c2 100644 --- a/src/ttyopts.c +++ b/src/ttyopts.c @@ -4,6 +4,7 @@ * This file is part of the SSH Library * * Copyright (c) 2023 by Utimaco TS GmbH + * Author: Daniel Evers * * 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 From b2fcef3fad96703715b70cfed59c109858d36cdb Mon Sep 17 00:00:00 2001 From: Abdelrahman Yossef Date: Wed, 28 Feb 2024 03:44:40 +0200 Subject: [PATCH 130/795] updated documentation of sftp_tell64 Signed-off-by: Abdelrahman Youssef Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index d7924390..754a54c3 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -855,8 +855,7 @@ LIBSSH_API unsigned long sftp_tell(sftp_file file); * @param file Open sftp file handle. * * @return The offset of the current byte relative to the beginning - * of the file associated with the file descriptor. < 0 on - * error. + * of the file associated with the file descriptor. */ LIBSSH_API uint64_t sftp_tell64(sftp_file file); From 3b7095acbbddc945c6c18de3c86532fcaeda0ca8 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 7 Mar 2024 12:50:13 +0100 Subject: [PATCH 131/795] Conditionalize TTY options that are not available on freebsd Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/ttyopts.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ttyopts.c b/src/ttyopts.c index a37ab7c2..f6557884 100644 --- a/src/ttyopts.c +++ b/src/ttyopts.c @@ -216,7 +216,9 @@ encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) SSH_ENCODE_INPUT_OPT(INLCR) SSH_ENCODE_INPUT_OPT(IGNCR) SSH_ENCODE_INPUT_OPT(ICRNL) +#ifdef IUCLC SSH_ENCODE_INPUT_OPT(IUCLC) +#endif SSH_ENCODE_INPUT_OPT(IXON) SSH_ENCODE_INPUT_OPT(IXANY) SSH_ENCODE_INPUT_OPT(IXOFF) @@ -229,7 +231,9 @@ encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) #define SSH_ENCODE_OUTPUT_OPT(opt) \ SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) SSH_ENCODE_OUTPUT_OPT(OPOST) +#ifdef OLCUC SSH_ENCODE_OUTPUT_OPT(OLCUC) +#endif SSH_ENCODE_OUTPUT_OPT(ONLCR) SSH_ENCODE_OUTPUT_OPT(OCRNL) SSH_ENCODE_OUTPUT_OPT(ONOCR) @@ -248,7 +252,9 @@ encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) SSH_ENCODE_LOCAL_OPT(ISIG) SSH_ENCODE_LOCAL_OPT(ICANON) +#ifdef XCASE SSH_ENCODE_LOCAL_OPT(XCASE) +#endif SSH_ENCODE_LOCAL_OPT(ECHO) SSH_ENCODE_LOCAL_OPT(ECHOE) SSH_ENCODE_LOCAL_OPT(ECHOK) From 9ee8d8cd204570548328e089b92c1bc5f0e4135a Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 7 Mar 2024 13:18:20 +0100 Subject: [PATCH 132/795] tests: Print content of channels to investigate random failures Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/client/torture_request_pty_modes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/client/torture_request_pty_modes.c b/tests/client/torture_request_pty_modes.c index a00be2a7..0e8cdc0d 100755 --- a/tests/client/torture_request_pty_modes.c +++ b/tests/client/torture_request_pty_modes.c @@ -89,6 +89,7 @@ static int check_channel_output(ssh_channel c, const char *expected) nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); while (nbytes > 0) { buffer[nbytes]='\0'; + ssh_log_hexdump("Read bytes:", (unsigned char *)buffer, nbytes); if (strstr(buffer, expected) != NULL) { return 1; From 6a03f6cefec7cefc5151a165e7824d712a0f5e14 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 7 Mar 2024 13:51:27 +0100 Subject: [PATCH 133/795] tests: Introduce chown wrapper to avoid OpenSSH touching PTY ownership The OpenSSH as part of the new test torture_request_pty_modes attempts to chown the pty to the faked user, which is obviously not permitted when the test does not run as a root. But since all the permissions for SSH are faked, just ignoring these requests should be safe enough giving expected results. Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/CMakeLists.txt | 13 ++++++++++++- tests/chown_wrapper.c | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/chown_wrapper.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c4f97fc6..e8c77883 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -234,6 +234,16 @@ if (CLIENT_TESTING OR SERVER_TESTING) set(CHROOT_WRAPPER "${CHROOT_WRAPPER_LIBRARY}") endif() + # chown wrapper + add_library(chown_wrapper SHARED chown_wrapper.c) + set(CHOWN_WRAPPER_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chown_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + chown_wrapper + ) + set(CHOWN_WRAPPER "${CHOWN_WRAPPER_LIBRARY}") + # ssh_ping add_executable(ssh_ping ssh_ping.c) target_compile_options(ssh_ping PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) @@ -253,7 +263,8 @@ if (CLIENT_TESTING OR SERVER_TESTING) configure_file(etc/pam.d/sshd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d/sshd @ONLY) - set(TORTURE_ENVIRONMENT "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}") + set(TORTURE_ENVIRONMENT + "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}:${CHOWN_WRAPPER}") if (priv_wrapper_FOUND) list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER=1 PRIV_WRAPPER_CHROOT_DISABLE=1) list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER_PRCTL_DISABLE="ALL" PRIV_WRAPPER_SETRLIMIT_DISABLE="ALL") diff --git a/tests/chown_wrapper.c b/tests/chown_wrapper.c new file mode 100644 index 00000000..ee6910ed --- /dev/null +++ b/tests/chown_wrapper.c @@ -0,0 +1,21 @@ +#define _GNU_SOURCE +#include +#include +#include + +typedef int (*__libc_chown)(const char *pathname, uid_t owner, gid_t group); + +/* silent gcc */ +int chown(const char *pathname, uid_t owner, gid_t group); + +int chown(const char *pathname, uid_t owner, gid_t group) +{ + __libc_chown original_chown; + if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { + /* fake it! */ + return 0; + } + + original_chown = (__libc_chown)dlsym(RTLD_NEXT, "chown"); + return (*original_chown)(pathname, owner, group); +} From 64ef3fefb4d1ab61ba9dadf69eccb631142016c2 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 8 Mar 2024 10:04:07 +0100 Subject: [PATCH 134/795] Rework the coverage build This reworks it to avoid a need to special build type and adding the flags only to the targets that need it (skipping testing wrappers which break with them). It also updates the CodeCoverage module from the following URL: https://github.com/bilke/cmake-modules/blob/master/CodeCoverage.cmake Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 2 +- CMakeLists.txt | 7 ++- cmake/Modules/AddCMockaTest.cmake | 4 ++ cmake/Modules/CodeCoverage.cmake | 76 +++++++++++++++++-------- cmake/Modules/DefineCompilerFlags.cmake | 12 ---- src/CMakeLists.txt | 7 +++ tests/CMakeLists.txt | 12 +++- tests/pkd/CMakeLists.txt | 5 +- tests/server/test_server/CMakeLists.txt | 11 ++-- 9 files changed, 89 insertions(+), 47 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d942ae3e..344a91dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -148,7 +148,7 @@ fedora/coverage: extends: .fedora image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD variables: - CMAKE_ADDITIONAL_OPTIONS: -DCMAKE_BUILD_TYPE=Coverage + CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_BUILD_TYPE=Debug -DWITH_COVERAGE=ON" script: - cmake $CMAKE_OPTIONS $CMAKE_ADDITIONAL_OPTIONS .. && make -j$(nproc) && diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c7b8c50..acc1e606 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,18 +188,18 @@ if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) endif (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) # Coverage -if (CMAKE_BUILD_TYPE STREQUAL "Coverage") +if (WITH_COVERAGE) include(CodeCoverage) setup_target_for_coverage_lcov( NAME "coverage" EXECUTABLE make test DEPENDENCIES ssh tests) - set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary --gcov-ignore-parse-errors) + set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary) setup_target_for_coverage_gcovr_xml( NAME "coverage_xml" EXECUTABLE make test DEPENDENCIES ssh tests) -endif (CMAKE_BUILD_TYPE STREQUAL "Coverage") +endif (WITH_COVERAGE) add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source DEPENDS ${_SYMBOL_TARGET} VERBATIM) @@ -215,6 +215,7 @@ message(STATUS "********************************************") message(STATUS "********** ${PROJECT_NAME} build options : **********") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Coverage: ${WITH_COVERAGE}") message(STATUS "zlib support: ${WITH_ZLIB}") message(STATUS "libgcrypt support: ${WITH_GCRYPT}") message(STATUS "libmbedTLS support: ${WITH_MBEDTLS}") diff --git a/cmake/Modules/AddCMockaTest.cmake b/cmake/Modules/AddCMockaTest.cmake index 4b0c2dad..79178183 100644 --- a/cmake/Modules/AddCMockaTest.cmake +++ b/cmake/Modules/AddCMockaTest.cmake @@ -116,5 +116,9 @@ function(ADD_CMOCKA_TEST _TARGET_NAME) add_test(${_TARGET_NAME} ${TARGET_SYSTEM_EMULATOR} ${_TARGET_NAME} ) + if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(${_TARGET_NAME}) + endif (WITH_COVERAGE) endfunction (ADD_CMOCKA_TEST) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake index 3cf81b98..c500cfa4 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -83,6 +83,10 @@ # - Change gcovr output from -o for --xml and --html output respectively. # This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". # +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# # USAGE: # # 1. Copy this file into your cmake modules path. @@ -147,30 +151,34 @@ if(NOT GCOV_PATH) message(FATAL_ERROR "gcov not found! Aborting...") endif() # NOT GCOV_PATH +# Check supported compiler (Clang, GNU and Flang) get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) -list(GET LANGUAGES 0 LANG) - -if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) - message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") - endif() -elseif(NOT CMAKE_COMPILER_IS_GNUCXX) - if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") - # Do nothing; exit conditional without error if true - elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") - # Do nothing; exit conditional without error if true - else() - message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") endif() -endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() -set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage" +set(COVERAGE_COMPILER_FLAGS "-g --coverage" CACHE INTERNAL "") + if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) - if(HAVE_fprofile_abs_path) - set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") endif() endif() @@ -202,7 +210,7 @@ mark_as_advanced( CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT (CMAKE_BUILD_TYPE STREQUAL "Coverage" OR CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) @@ -228,7 +236,7 @@ endif() # ) function(setup_target_for_coverage_lcov) - set(options NO_DEMANGLE) + set(options NO_DEMANGLE SONARQUBE) set(oneValueArgs BASE_DIRECTORY NAME) set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -298,6 +306,18 @@ function(setup_target_for_coverage_lcov) ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o ${Coverage_NAME} ${Coverage_NAME}.info ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() if(CODE_COVERAGE_VERBOSE) @@ -329,6 +349,12 @@ function(setup_target_for_coverage_lcov) message(STATUS "Command to generate lcov HTML output: ") string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() endif() # Setup target @@ -340,6 +366,7 @@ function(setup_target_for_coverage_lcov) COMMAND ${LCOV_BASELINE_COUNT_CMD} COMMAND ${LCOV_FILTER_CMD} COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} # Set output files as GENERATED (will be removed on 'make clean') BYPRODUCTS @@ -347,6 +374,7 @@ function(setup_target_for_coverage_lcov) ${Coverage_NAME}.capture ${Coverage_NAME}.total ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} ${Coverage_NAME}/index.html WORKING_DIRECTORY ${PROJECT_BINARY_DIR} DEPENDS ${Coverage_DEPENDENCIES} @@ -358,6 +386,7 @@ function(setup_target_for_coverage_lcov) add_custom_command(TARGET ${Coverage_NAME} POST_BUILD COMMAND ; COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} ) # Show info where to find the report @@ -621,7 +650,6 @@ function(setup_target_for_coverage_fastcov) --process-gcno --output ${Coverage_NAME}.json --exclude ${FASTCOV_EXCLUDES} - --exclude ${FASTCOV_EXCLUDES} ) set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} @@ -714,7 +742,9 @@ endfunction() # append_coverage_compiler_flags # Setup coverage for specific library function(append_coverage_compiler_flags_to_target name) - target_compile_options(${name} - PRIVATE ${COVERAGE_COMPILER_FLAGS}) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() endfunction() - diff --git a/cmake/Modules/DefineCompilerFlags.cmake b/cmake/Modules/DefineCompilerFlags.cmake index c6c07ede..39378a10 100644 --- a/cmake/Modules/DefineCompilerFlags.cmake +++ b/cmake/Modules/DefineCompilerFlags.cmake @@ -46,16 +46,4 @@ if (UNIX AND NOT WIN32) CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") set(CMAKE_EXEC_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" CACHE STRING "Flags used by the linker during UNDEFINEDSANITIZER builds.") - - # Activate with: -DCMAKE_BUILD_TYPE=Coverage - set(CMAKE_C_FLAGS_COVERAGE "-O0 -g -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the C compiler during Coverage builds.") - set(CMAKE_CXX_FLAGS_COVERAGE "-O0 -g -fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the CXX compiler during Coverage builds.") - set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "-fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the linker during the creation of shared libraries during Coverage builds.") - set(CMAKE_MODULE_LINKER_FLAGS_COVERAGE "-fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the linker during the creation of shared libraries during Coverage builds.") - set(CMAKE_EXEC_LINKER_FLAGS_COVERAGE "-fprofile-arcs -ftest-coverage" - CACHE STRING "Flags used by the linker during Coverage builds.") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 748bb7a9..93ecb5e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -371,6 +371,10 @@ if (MINGW) target_link_libraries(ssh PRIVATE "-Wl,--enable-stdcall-fixup") target_compile_definitions(ssh PRIVATE "_POSIX_SOURCE") endif () +if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(ssh) +endif (WITH_COVERAGE) install(TARGETS ssh @@ -423,6 +427,9 @@ if (BUILD_STATIC_LIB) if (WIN32) target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC") endif (WIN32) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(ssh-static) + endif (WITH_COVERAGE) endif (BUILD_STATIC_LIB) message(STATUS "Threads_FOUND=${Threads_FOUND}") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e8c77883..d795b4f6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,10 +26,13 @@ add_library(${TORTURE_LIBRARY} torture_key.c torture_pki.c torture_cmocka.c) -target_link_libraries(${TORTURE_LIBRARY} ${TORTURE_LINK_LIBRARIES}) +target_link_libraries(${TORTURE_LIBRARY} PRIVATE ${TORTURE_LINK_LIBRARIES}) target_compile_options(${TORTURE_LIBRARY} PRIVATE -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" ) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_LIBRARY}) +endif (WITH_COVERAGE) # The shared version of the library is only useful when client testing is # enabled @@ -60,7 +63,7 @@ if (CLIENT_TESTING) torture_pki.c torture_cmocka.c ) - target_link_libraries(${TORTURE_SHARED_LIBRARY} + target_link_libraries(${TORTURE_SHARED_LIBRARY} PUBLIC ${CMOCKA_LIBRARY} ssh::static ${WRAP_SYMBOLS} @@ -69,11 +72,14 @@ if (CLIENT_TESTING) -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" -DTORTURE_SHARED ) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_SHARED_LIBRARY}) + endif (WITH_COVERAGE) endif () if (ARGP_LIBRARIES) target_link_libraries(${TORTURE_LIBRARY} - ${ARGP_LIBRARIES} + PUBLIC ${ARGP_LIBRARIES} ) endif() diff --git a/tests/pkd/CMakeLists.txt b/tests/pkd/CMakeLists.txt index 9a7038cc..f5a62653 100644 --- a/tests/pkd/CMakeLists.txt +++ b/tests/pkd/CMakeLists.txt @@ -25,7 +25,10 @@ set(pkd_libs add_executable(pkd_hello ${pkd_hello_src}) target_compile_options(pkd_hello PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) -target_link_libraries(pkd_hello ${pkd_libs}) +target_link_libraries(pkd_hello PRIVATE ${pkd_libs}) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(pkd_hello) +endif (WITH_COVERAGE) # # pkd_hello_i1 runs only one iteration per algorithm combination for diff --git a/tests/server/test_server/CMakeLists.txt b/tests/server/test_server/CMakeLists.txt index f1453d43..7e0f88c1 100644 --- a/tests/server/test_server/CMakeLists.txt +++ b/tests/server/test_server/CMakeLists.txt @@ -12,6 +12,9 @@ add_library(testserver STATIC test_server.c default_cb.c sftpserver_cb.c) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(testserver) +endif (WITH_COVERAGE) set(LIBSSH_SERVER_TESTS # torture_server_kbdint @@ -29,10 +32,10 @@ if (UNIX AND NOT WIN32) add_executable(test_server ${server_SRCS}) target_compile_options(test_server PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) target_link_libraries(test_server - testserver - ssh::ssh - ${ARGP_LIBRARIES} - util) + PRIVATE testserver ssh::ssh ${ARGP_LIBRARIES} util) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(test_server) + endif (WITH_COVERAGE) endif () endif (WITH_SERVER AND UNIX AND NOT WIN32) From 81f9b000054d64a9a80617caa7ace463272fa06f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 8 Mar 2024 10:20:38 +0100 Subject: [PATCH 135/795] cmake: Use -fprofile-update=atomic to avoid coverage files corruption from threads The gcc should be able to select this automatically based on the presence of -pthread is present on the commandline, but given that we link the tests static, we do not have this? Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- cmake/Modules/CodeCoverage.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake index c500cfa4..0fd70ae2 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -164,7 +164,7 @@ foreach(LANG ${LANGUAGES}) endif() endforeach() -set(COVERAGE_COMPILER_FLAGS "-g --coverage" +set(COVERAGE_COMPILER_FLAGS "-g --coverage -fprofile-update=atomic" CACHE INTERNAL "") if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") From fcd63abb6a074e43c048d522bbc90d7a855ea896 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 7 Mar 2024 11:08:04 +0100 Subject: [PATCH 136/795] tests: Avoid hardcoding 64b arch path to pkcs11-spy Find the path to the library using cmake and enable this sort of logging only with TORTURE_PKCS11 environment variable. Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/CMakeLists.txt | 4 ++++ tests/tests_config.h.cmake | 1 + tests/torture.c | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d795b4f6..46c19ff7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -187,6 +187,10 @@ if (CLIENT_TESTING OR SERVER_TESTING) if (NOT SOFTHSM_FOUND) message(SEND_ERROR "Could not find softhsm module!") endif (NOT SOFTHSM_FOUND) + find_library(PKCS11SPY + NAMES + pkcs11-spy.so + ) if (WITH_PKCS11_PROVIDER) find_package(PkgConfig) if (PKG_CONFIG_FOUND) diff --git a/tests/tests_config.h.cmake b/tests/tests_config.h.cmake index bb9164bd..3b6bde0e 100644 --- a/tests/tests_config.h.cmake +++ b/tests/tests_config.h.cmake @@ -69,3 +69,4 @@ #cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" #cmakedefine SOFTHSM2_LIBRARY "${SOFTHSM2_LIBRARY}" #cmakedefine P11_KIT_CLIENT "${P11_KIT_CLIENT}" +#cmakedefine PKCS11SPY "${PKCS11SPY}" diff --git a/tests/torture.c b/tests/torture.c index 3072edf3..78edaae1 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -1207,6 +1207,7 @@ void torture_setup_tokens(const char *temp_dir, char token_setup_start_cmd[1024] = {0}; char socket_path[1204] = {0}; char conf_path[1024] = {0}; + char *env = NULL; int rc; rc = snprintf(token_setup_start_cmd, @@ -1239,9 +1240,19 @@ void torture_setup_tokens(const char *temp_dir, setenv("PKCS11_PROVIDER_MODULE", P11_KIT_CLIENT, 1); /* This is useful for debugging PKCS#11 calls */ - setenv("PKCS11SPY", P11_KIT_CLIENT, 1); - setenv("PKCS11_PROVIDER_MODULE", "/usr/lib64/pkcs11-spy.so", 1); + + env = getenv("TORTURE_PKCS11"); + if (env != NULL && env[0] != '\0') { +#ifdef PKCS11SPY + setenv("PKCS11SPY", P11_KIT_CLIENT, 1); + setenv("PKCS11_PROVIDER_MODULE", PKCS11SPY, 1); #else + fprintf(stderr, "[ TORTURE ] >>> pkcs11-spy not found\n"); +#endif + } +#else + (void)env; + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", temp_dir); setenv("SOFTHSM2_CONF", conf_path, 1); #endif /* WITH_PKCS11_PROVIDER */ From b9d4e11456a7f65d739239f4d92eb618cbe02037 Mon Sep 17 00:00:00 2001 From: Gauravsingh Sisodia Date: Fri, 8 Mar 2024 17:41:55 +0000 Subject: [PATCH 137/795] reformat: bind.c reformat: remove unneeded free Signed-off-by: Gauravsingh Sisodia Reviewed-by: Sahana Prasad --- src/bind.c | 167 +++++++++++++++++++++++++++++------------------------ 1 file changed, 93 insertions(+), 74 deletions(-) diff --git a/src/bind.c b/src/bind.c index d150933c..6580b776 100644 --- a/src/bind.c +++ b/src/bind.c @@ -225,90 +225,105 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { return SSH_OK; } -int ssh_bind_listen(ssh_bind sshbind) { - const char *host; - socket_t fd; - int rc; +int ssh_bind_listen(ssh_bind sshbind) +{ + const char *host = NULL; + socket_t fd; + int rc; - if (sshbind->rsa == NULL && - sshbind->ecdsa == NULL && - sshbind->ed25519 == NULL) { - rc = ssh_bind_import_keys(sshbind); - if (rc != SSH_OK) { - return SSH_ERROR; - } - } + /* 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; + } - if (sshbind->bindfd == SSH_INVALID_SOCKET) { - host = sshbind->bindaddr; - if (host == NULL) { - host = "0.0.0.0"; - } + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { - fd = bind_socket(sshbind, host, sshbind->bindport); - if (fd == SSH_INVALID_SOCKET) { - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + 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"); + } - if (listen(fd, 10) < 0) { - char err_msg[SSH_ERRNO_MSG_MAX] = {0}; - ssh_set_error(sshbind, SSH_FATAL, - "Listening to socket %d: %s", - fd, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); - CLOSE_SOCKET(fd); - ssh_key_free(sshbind->rsa); - sshbind->rsa = NULL; - /* XXX should this clear also other structures that were allocated */ - return -1; - } + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + host = sshbind->bindaddr; + if (host == NULL) { + host = "0.0.0.0"; + } + + fd = bind_socket(sshbind, host, sshbind->bindport); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + + if (listen(fd, 10) < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(sshbind, + SSH_FATAL, + "Listening to socket %d: %s", + fd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } - sshbind->bindfd = fd; + sshbind->bindfd = fd; } 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){ - if (sshbind == NULL) { - return SSH_ERROR; - } - if (callbacks == NULL) { - ssh_set_error_invalid(sshbind); - return SSH_ERROR; - } - if(callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)){ - ssh_set_error(sshbind,SSH_FATAL, - "Invalid callback passed in (badly initialized)"); - return SSH_ERROR; - } - sshbind->bind_callbacks = callbacks; - sshbind->bind_callbacks_userdata=userdata; - return 0; +int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata) +{ + if (sshbind == NULL) { + return SSH_ERROR; + } + if (callbacks == NULL) { + ssh_set_error_invalid(sshbind); + return SSH_ERROR; + } + if (callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)) { + ssh_set_error(sshbind, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + } + sshbind->bind_callbacks = callbacks; + sshbind->bind_callbacks_userdata = userdata; + return 0; } /** @internal * @brief callback being called by poll when an event happens * */ -static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, - socket_t fd, int revents, void *user){ - ssh_bind sshbind=(ssh_bind)user; - (void)sshpoll; - (void)fd; - - if(revents & POLLIN){ - /* new incoming connection */ - if(ssh_callbacks_exists(sshbind->bind_callbacks,incoming_connection)){ - sshbind->bind_callbacks->incoming_connection(sshbind, - sshbind->bind_callbacks_userdata); +static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int revents, void *user) +{ + ssh_bind sshbind = (ssh_bind)user; + (void)sshpoll; + (void)fd; + + if (revents & POLLIN) { + /* new incoming connection */ + if (ssh_callbacks_exists(sshbind->bind_callbacks, incoming_connection)) { + sshbind->bind_callbacks->incoming_connection(sshbind, + sshbind->bind_callbacks_userdata); + } } - } - return 0; + return 0; } /** @internal @@ -336,20 +351,24 @@ ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind) return sshbind->poll; } -void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) { - sshbind->blocking = blocking ? 1 : 0; +void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) +{ + sshbind->blocking = blocking ? 1 : 0; } -socket_t ssh_bind_get_fd(ssh_bind sshbind) { - return sshbind->bindfd; +socket_t ssh_bind_get_fd(ssh_bind sshbind) +{ + return sshbind->bindfd; } -void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) { - sshbind->bindfd = fd; +void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) +{ + sshbind->bindfd = fd; } -void ssh_bind_fd_toaccept(ssh_bind sshbind) { - sshbind->toaccept = 1; +void ssh_bind_fd_toaccept(ssh_bind sshbind) +{ + sshbind->toaccept = 1; } void ssh_bind_free(ssh_bind sshbind){ From a9d1cfa9e233601269f1ca20a952f7b9504d1a5c Mon Sep 17 00:00:00 2001 From: Gauravsingh Sisodia Date: Mon, 19 Feb 2024 12:45:23 +0000 Subject: [PATCH 138/795] feat: Handle hostkeys like OpenSSH fix: memory leak fix: add defaults after parsing fix: set defaults in ssh_bind_listen tests: add test for checking default hostkey paths remove: null check for hostkey paths, can't happen since we set defaults now examples: ssh_server remove "no default keys", default hostkeys set in ssh_bind_listen Signed-off-by: Gauravsingh Sisodia Reviewed-by: Sahana Prasad --- examples/ssh_server.c | 55 +-------------------------- src/bind.c | 37 +++++++++--------- tests/unittests/CMakeLists.txt | 11 ++---- tests/unittests/torture_unit_server.c | 39 ++++++++++++++++++- 4 files changed, 62 insertions(+), 80 deletions(-) diff --git a/examples/ssh_server.c b/examples/ssh_server.c index 4b91807e..3e9f344b 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -45,32 +45,10 @@ The goal is to show the API in action. #define BUF_SIZE 1048576 #endif -#ifndef KEYS_FOLDER -#ifdef _WIN32 -#define KEYS_FOLDER -#else -#define KEYS_FOLDER "/etc/ssh/" -#endif -#endif - #define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) #define SFTP_SERVER_PATH "/usr/lib/sftp-server" #define AUTH_KEYS_MAX_LINE_SIZE 2048 -static void set_default_keys(ssh_bind sshbind, - int rsa_already_set, - int ecdsa_already_set) { - if (!rsa_already_set) { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, - KEYS_FOLDER "ssh_host_rsa_key"); - } - if (!ecdsa_already_set) { - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, - KEYS_FOLDER "ssh_host_ecdsa_key"); - } - ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, - KEYS_FOLDER "ssh_host_ed25519_key"); -} #define DEF_STR_SIZE 1024 char authorizedkeys[DEF_STR_SIZE] = {0}; char username[128] = "myuser"; @@ -145,14 +123,6 @@ static struct argp_option options[] = { .doc = "Set expected password.", .group = 0 }, - { - .name = "no-default-keys", - .key = 'n', - .arg = NULL, - .flags = 0, - .doc = "Do not set default key locations.", - .group = 0 - }, { .name = "verbose", .key = 'v', @@ -169,30 +139,19 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { /* Get the input argument from argp_parse, which we * know is a pointer to our arguments structure. */ ssh_bind sshbind = state->input; - static int no_default_keys = 0; - static int rsa_already_set = 0, ecdsa_already_set = 0; switch (key) { - case 'n': - no_default_keys = 1; - break; case 'p': ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); break; case 'k': ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); - /* We can't track the types of keys being added with this - option, so let's ensure we keep the keys we're adding - by just not setting the default keys */ - no_default_keys = 1; break; case 'r': ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); - rsa_already_set = 1; break; case 'e': ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); - ecdsa_already_set = 1; break; case 'a': strncpy(authorizedkeys, arg, DEF_STR_SIZE-1); @@ -219,13 +178,6 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) { /* Not enough arguments. */ argp_usage (state); } - - if (!no_default_keys) { - set_default_keys(sshbind, - rsa_already_set, - ecdsa_already_set); - } - break; default: return ARGP_ERR_UNKNOWN; @@ -242,10 +194,8 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { int ecdsa_already_set = 0; int key; - while((key = getopt(argc, argv, "a:e:k:np:P:r:u:v")) != -1) { - if (key == 'n') { - no_default_keys = 1; - } else if (key == 'p') { + while((key = getopt(argc, argv, "a:e:k:p:P:r:u:v")) != -1) { + if (key == 'p') { ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg); } else if (key == 'k') { ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); @@ -281,7 +231,6 @@ static int parse_opt(int argc, char **argv, ssh_bind sshbind) { " -e, --ecdsakey=FILE Set the ecdsa key (deprecated alias for 'k').\n" " -k, --hostkey=FILE Set a host key. Can be used multiple times.\n" " Implies no default keys.\n" - " -n, --no-default-keys Do not set default key locations.\n" " -p, --port=PORT Set the port to bind.\n" " -P, --pass=PASSWORD Set expected password.\n" " -r, --rsakey=FILE Set the rsa key (deprecated alias for 'k').\n" diff --git a/src/bind.c b/src/bind.c index 6580b776..c2917865 100644 --- a/src/bind.c +++ b/src/bind.c @@ -149,14 +149,6 @@ ssh_bind ssh_bind_new(void) { static int ssh_bind_import_keys(ssh_bind sshbind) { int rc; - if (sshbind->ecdsakey == NULL && - sshbind->rsakey == NULL && - sshbind->ed25519key == NULL) { - ssh_set_error(sshbind, SSH_FATAL, - "ECDSA, ED25519, or RSA host key file must be set"); - return SSH_ERROR; - } - #ifdef HAVE_ECC if (sshbind->ecdsa == NULL && sshbind->ecdsakey != NULL) { rc = ssh_pki_import_privkey_file(sshbind->ecdsakey, @@ -225,12 +217,28 @@ static int ssh_bind_import_keys(ssh_bind sshbind) { return SSH_OK; } -int ssh_bind_listen(ssh_bind sshbind) -{ - const char *host = NULL; +int ssh_bind_listen(ssh_bind sshbind) { + const char *host; socket_t fd; int rc; + /* 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) { @@ -424,13 +432,6 @@ int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) return SSH_ERROR; } - /* 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; - } - session->server = 1; /* Copy options from bind to session */ diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index e4ee846a..c053e5b8 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -84,13 +84,10 @@ if (UNIX AND NOT WIN32) torture_threads_pki_rsa ) if (WITH_SERVER) - # Not working correctly - # add_cmocka_test(torture_server_x11 torture_server_x11.c ${TEST_TARGET_LIBRARIES}) - # the signals are not testable under cmocka - # set(LIBSSH_THREAD_UNIT_TESTS - # ${LIBSSH_THREAD_UNIT_TESTS} - # torture_unit_server - # ) + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + torture_unit_server + ) endif (WITH_SERVER) endif (UNIX AND NOT WIN32) diff --git a/tests/unittests/torture_unit_server.c b/tests/unittests/torture_unit_server.c index 3e7e69f7..2fd4be72 100644 --- a/tests/unittests/torture_unit_server.c +++ b/tests/unittests/torture_unit_server.c @@ -10,11 +10,13 @@ #include #include +#include #include "torture.h" #include "torture_key.h" #define TEST_SERVER_PORT 2222 +#if 0 struct test_state { const char *hostkey; char *hostkey_path; @@ -107,7 +109,7 @@ static void test_ssh_accept_interrupt(void **state) struct test_state *ts = (struct test_state *)*state; int rc; pthread_t client_pthread, interrupt_pthread; - ssh_bind sshbind; + ssh_bind sshbind = NULL; ssh_session server; /* Create server */ @@ -145,14 +147,47 @@ static void test_ssh_accept_interrupt(void **state) rc = pthread_join(client_pthread, NULL); assert_int_equal(rc, 0); } +#endif + + +static void test_default_hostkey_paths(void **state) +{ + int rc; + ssh_bind sshbind = NULL; + + /* state not used */ + (void)state; + + /* Create server */ + rc = ssh_init(); + assert_int_equal(rc, 0); + + sshbind = ssh_bind_new(); + assert_non_null(sshbind); + + /* This will fail because we don't have permission to import keys unless we run as root + * TODO: Implement some filesystem wrapper, that would allow this check to pass by + * reading the keys from some accessible test location */ + ssh_bind_listen(sshbind); + + assert_string_equal(sshbind->rsakey, "/etc/ssh/ssh_host_rsa_key"); + assert_string_equal(sshbind->ecdsakey, "/etc/ssh/ssh_host_ecdsa_key"); + assert_string_equal(sshbind->ed25519key, "/etc/ssh/ssh_host_ed25519_key"); + + /* Cleanup */ + ssh_bind_free(sshbind); + ssh_finalize(); +} int torture_run_tests(void) { int rc; const struct CMUnitTest tests[] = { + cmocka_unit_test(test_default_hostkey_paths), + /* Not working correctly the signals are not testable under cmocka cmocka_unit_test_setup_teardown(test_ssh_accept_interrupt, setup, - teardown) + teardown) */ }; rc = cmocka_run_group_tests(tests, NULL, NULL); From 9cee4fa0546d6f5d8832c6df5bc05d36baa96910 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 14 Dec 2020 16:50:29 +0100 Subject: [PATCH 139/795] Add review stage to the CI checking formatting Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 14 ++++++++++++++ .gitlab-ci/clang-format-check.sh | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100755 .gitlab-ci/clang-format-check.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 344a91dc..7cbec438 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -82,6 +82,20 @@ stages: - update-crypto-policies --set FIPS - update-crypto-policies --show +############################################################################### +# Review # +############################################################################### +clang-format: + variables: + GIT_DEPTH: 100 + stage: review + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - ./.gitlab-ci/clang-format-check.sh + only: + - merge_requests + # the format is not always matching our intentions + allow_failure: true ############################################################################### # CentOS builds # diff --git a/.gitlab-ci/clang-format-check.sh b/.gitlab-ci/clang-format-check.sh new file mode 100755 index 00000000..1089c969 --- /dev/null +++ b/.gitlab-ci/clang-format-check.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# Based on Github Action +# https://github.com/yshui/git-clang-format-lint + +diff=`git-clang-format --diff --commit $CI_MERGE_REQUEST_DIFF_BASE_SHA` +[ "$diff" = "no modified files to format" ] && exit 0 +[ "$diff" = "clang-format did not modify any files" ] && exit 0 + +printf "You have introduced coding style breakages, suggested changes:\n\n" + +echo "$diff" | colordiff +exit 1 From ed68fdaa61c79c08eddd5423af5d3bd2b929843d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 17 Dec 2020 15:29:10 +0100 Subject: [PATCH 140/795] Run CI in merge requests and in branches This should avoid duplicate pipelines as suggested in (gitlab-org/gitlab!230928) Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7cbec438..76d26347 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,6 +15,17 @@ stages: - test - analysis +# This is some black magic to select between branch pipelines and +# merge request pipelines to avoid running same pipelines in twice +workflow: + rules: + - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && $CI_PIPELINE_SOURCE == "push"' + when: never + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS' + when: never + - if: '$CI_COMMIT_BRANCH' + .build: stage: build variables: @@ -37,6 +48,10 @@ stages: # Do not use after_script as it does not make the targets fail tags: - shared + only: + - merge_requests + - branches + except: - tags artifacts: @@ -316,6 +331,11 @@ fedora/mingw32: image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD before_script: - | + # for merge requests + if [[ -n "$CI_MERGE_REQUEST_DIFF_BASE_SHA" ]]; then + export CI_COMMIT_BEFORE_SHA="$CI_MERGE_REQUEST_DIFF_BASE_SHA" + fi + # for branches run if [[ -z "$CI_COMMIT_BEFORE_SHA" ]]; then export CI_COMMIT_BEFORE_SHA=$(git rev-parse "${CI_COMMIT_SHA}~20") fi @@ -329,6 +349,8 @@ fedora/mingw32: - shared except: - tags + only: + - merge_requests artifacts: expire_in: 1 week when: on_failure From 46e6804c8935da19ba006fcaabc3b520a300fb56 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Sat, 4 Apr 2020 08:33:23 +0200 Subject: [PATCH 141/795] gitlab-ci: Check merge requests for Signed-off-by trailers Based on Andreas work in https://gitlab.com/libssh/libssh-mirror/-/merge_requests/104/ Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 10 +++++++ .gitlab-ci/git-check-signoff-trailer.sh | 36 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100755 .gitlab-ci/git-check-signoff-trailer.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76d26347..12381813 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -112,6 +112,16 @@ clang-format: # the format is not always matching our intentions allow_failure: true +git-log-check: + variables: + GIT_DEPTH: 100 + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + stage: review + script: + - ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} + only: + - merge_requests + ############################################################################### # CentOS builds # ############################################################################### diff --git a/.gitlab-ci/git-check-signoff-trailer.sh b/.gitlab-ci/git-check-signoff-trailer.sh new file mode 100755 index 00000000..ef21eeec --- /dev/null +++ b/.gitlab-ci/git-check-signoff-trailer.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +if [ $# != 1 ]; then + echo "Usage: $0 UPSTREAM_COMMIT_SHA" + exit 1 +fi + +failed=0 + +if [ -z "$CI_COMMIT_SHA" ]; then + echo "CI_COMMIT_SHA is not set" + exit 1 +fi + +CI_COMMIT_RANGE="$1..$CI_COMMIT_SHA" + +red='\033[0;31m' +blue='\033[0;34m' + +echo -e "${blue}Checking commit range: $CI_COMMIT_RANGE" +echo +echo + +for commit in `git rev-list $CI_COMMIT_RANGE`; do + git show -s --format=%B $commit | grep "^Signed-off-by: " 2>&1 >/dev/null + ret=$? + if [ $ret -eq 1 ]; then + echo -e "${red} >>> Missing Signed-off-by trailer in commit $commit" + failed=`expr $failed + 1` + fi +done + +echo +echo + +exit $failed From c5a0d0fc0961726e2f073639d621d48c2c85ae25 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 6 Mar 2024 17:48:11 +0100 Subject: [PATCH 142/795] ci: Move codespell to the review stage in file Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 12381813..b0335cfa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,6 +100,16 @@ workflow: ############################################################################### # Review # ############################################################################### +codespell: + stage: review + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD + script: + - codespell --ignore-words-list=keypair,sorce,ned,nd,ue + tags: + - shared + only: + - merge_requests + clang-format: variables: GIT_DEPTH: 100 @@ -538,6 +548,9 @@ freebsd/openssl_1.1.1/x86_64: tags: - windows - shared-windows + only: + - merge_requests + - branches except: - tags artifacts: @@ -615,14 +628,3 @@ coverity: when: on_failure paths: - obj/cov-int/*.txt - -############################################################################### -# Codespell # -############################################################################### -codespell: - stage: review - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - script: - - codespell --ignore-words-list=keypair,sorce,ned,nd,ue - tags: - - shared From 2fc77d90cfb8ea13fc020c9995b3797a6ff854ad Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 20 Mar 2024 11:26:43 +0100 Subject: [PATCH 143/795] Run all reviews in single job Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0335cfa..76796323 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,35 +100,21 @@ workflow: ############################################################################### # Review # ############################################################################### -codespell: - stage: review - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - script: - - codespell --ignore-words-list=keypair,sorce,ned,nd,ue - tags: - - shared - only: - - merge_requests - -clang-format: +review: variables: GIT_DEPTH: 100 stage: review image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD script: - - ./.gitlab-ci/clang-format-check.sh - only: - - merge_requests + - ERROR=0 + codespell --ignore-words-list=keypair,sorce,ned,nd,ue || ERROR=1; + ./.gitlab-ci/clang-format-check.sh || ERROR=1; + ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; + exit $ERROR # the format is not always matching our intentions allow_failure: true - -git-log-check: - variables: - GIT_DEPTH: 100 - image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$FEDORA_BUILD - stage: review - script: - - ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} + tags: + - shared only: - merge_requests From 45334b6736ca63673632fc1f4c2f8e81878f65c1 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 20 Mar 2024 11:30:27 +0100 Subject: [PATCH 144/795] clang-format: Note about line break after short type Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .clang-format | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.clang-format b/.clang-format index 1da4f35d..2880be88 100644 --- a/.clang-format +++ b/.clang-format @@ -22,6 +22,8 @@ BinPackArguments: false BinPackParameters: false AllowAllArgumentsOnNextLine: false AllowShortFunctionsOnASingleLine: Empty +# TODO with Clang 19, replace the below with +# BreakAfterReturnType: ExceptShortType AlwaysBreakAfterReturnType: AllDefinitions AlignEscapedNewlines: Left ForEachMacros: ['ssh_callbacks_iterate'] From b6fd4912d7a61ec1228a1b3edb41fece1c35c57f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 21 Mar 2024 17:48:46 +0100 Subject: [PATCH 145/795] Fix shellcheck issues Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci/clang-format-check.sh | 4 ++-- .gitlab-ci/git-check-signoff-trailer.sh | 6 +++--- tests/benchmarks/bench1.sh | 11 ++++++----- tests/benchmarks/bench2.sh | 11 ++++++----- tests/pkcs11/setup-softhsm-tokens.sh | 6 +++--- tests/unittests/hello world.sh | 3 ++- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.gitlab-ci/clang-format-check.sh b/.gitlab-ci/clang-format-check.sh index 1089c969..261918ae 100755 --- a/.gitlab-ci/clang-format-check.sh +++ b/.gitlab-ci/clang-format-check.sh @@ -2,11 +2,11 @@ # Based on Github Action # https://github.com/yshui/git-clang-format-lint -diff=`git-clang-format --diff --commit $CI_MERGE_REQUEST_DIFF_BASE_SHA` +diff=$(git-clang-format --diff --commit "$CI_MERGE_REQUEST_DIFF_BASE_SHA") [ "$diff" = "no modified files to format" ] && exit 0 [ "$diff" = "clang-format did not modify any files" ] && exit 0 printf "You have introduced coding style breakages, suggested changes:\n\n" -echo "$diff" | colordiff +echo "${diff}" | colordiff exit 1 diff --git a/.gitlab-ci/git-check-signoff-trailer.sh b/.gitlab-ci/git-check-signoff-trailer.sh index ef21eeec..e3819662 100755 --- a/.gitlab-ci/git-check-signoff-trailer.sh +++ b/.gitlab-ci/git-check-signoff-trailer.sh @@ -21,12 +21,12 @@ echo -e "${blue}Checking commit range: $CI_COMMIT_RANGE" echo echo -for commit in `git rev-list $CI_COMMIT_RANGE`; do - git show -s --format=%B $commit | grep "^Signed-off-by: " 2>&1 >/dev/null +for commit in $(git rev-list "$CI_COMMIT_RANGE"); do + git show -s --format=%B "$commit" | grep "^Signed-off-by: " >/dev/null 2>&1 ret=$? if [ $ret -eq 1 ]; then echo -e "${red} >>> Missing Signed-off-by trailer in commit $commit" - failed=`expr $failed + 1` + failed=$(("$failed" + 1)) fi done diff --git a/tests/benchmarks/bench1.sh b/tests/benchmarks/bench1.sh index 21b7e6ec..6b3b20f3 100755 --- a/tests/benchmarks/bench1.sh +++ b/tests/benchmarks/bench1.sh @@ -1,13 +1,14 @@ +#!/bin/bash export CIPHER=aes128-cbc export DEST=localhost echo "Upload raw SSH statistics" -echo "local machine: `uname -a`" -echo "Cipher : $CIPHER ; Destination : $DEST (`ssh $DEST uname -a`)" -echo "Local ssh version: `ssh -V 2>&1`" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(ssh -V 2>&1)" echo "Ping latency to $DEST": ping -q -c 1 -n $DEST -echo "Destination $DEST SSHD version : `echo | nc $DEST 22 | head -n1`" -echo "ssh login latency :`(time -f user:%U ssh $DEST 'id > /dev/null') 2>&1`" +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command time -f user:%U ssh $DEST 'id > /dev/null') 2>&1)" ./generate.py | dd bs=4096 count=100000 | time ssh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 diff --git a/tests/benchmarks/bench2.sh b/tests/benchmarks/bench2.sh index aa42689d..cf240fda 100755 --- a/tests/benchmarks/bench2.sh +++ b/tests/benchmarks/bench2.sh @@ -1,13 +1,14 @@ +#!/bin/bash export CIPHER=aes128-cbc export DEST=localhost echo "Upload raw SSH statistics" -echo "local machine: `uname -a`" -echo "Cipher : $CIPHER ; Destination : $DEST (`ssh $DEST uname -a`)" -echo "Local ssh version: `samplessh -V 2>&1`" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(samplessh -V 2>&1)" echo "Ping latency to $DEST": ping -q -c 1 -n $DEST -echo "Destination $DEST SSHD version : `echo | nc $DEST 22 | head -n1`" -echo "ssh login latency :`(time -f user:%U samplessh $DEST 'id > /dev/null') 2>&1`" +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command time -f user:%U samplessh $DEST 'id > /dev/null') 2>&1)" ./generate.py | dd bs=4096 count=100000 | strace samplessh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 diff --git a/tests/pkcs11/setup-softhsm-tokens.sh b/tests/pkcs11/setup-softhsm-tokens.sh index bd8e0944..f61c5a67 100755 --- a/tests/pkcs11/setup-softhsm-tokens.sh +++ b/tests/pkcs11/setup-softhsm-tokens.sh @@ -94,8 +94,8 @@ fi # when creating more keys, we need to restart the p11-kit # so it can pick up the new keys if [ -h "$TESTDIR/p11-kit-server.socket" ]; then - kill -9 $(cat $TESTDIR/p11-kit-server.pid) - rm $TESTDIR/p11-kit-server.socket + kill -9 "$(cat "$TESTDIR/p11-kit-server.pid")" + rm "$TESTDIR/p11-kit-server.socket" fi # p11-kit complains if there is no runtime directory @@ -113,7 +113,7 @@ if [ $ret -ne 0 ]; then echo "$out" exit 1 fi -eval $out +eval "$out" # Symlink the p11-kit-server socket to "known place" P11_KIT_SERVER_ADDRESS_PATH=${P11_KIT_SERVER_ADDRESS:10} diff --git a/tests/unittests/hello world.sh b/tests/unittests/hello world.sh index 9a021b4e..8f687028 100755 --- a/tests/unittests/hello world.sh +++ b/tests/unittests/hello world.sh @@ -1 +1,2 @@ -/bin/echo -n $1 2>&1 \ No newline at end of file +#!/bin/sh +printf '%s' "$1" 2>&1 From 49c61bb263b280c9474d8216c5e6aa1a232804f9 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 21 Mar 2024 17:48:10 +0100 Subject: [PATCH 146/795] ci: Add shellcheck Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 2 ++ .gitlab-ci/shellcheck.sh | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100755 .gitlab-ci/shellcheck.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76796323..c47e6d45 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -110,6 +110,8 @@ review: codespell --ignore-words-list=keypair,sorce,ned,nd,ue || ERROR=1; ./.gitlab-ci/clang-format-check.sh || ERROR=1; ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; + ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; + ./.gitlab-ci/shellcheck.sh || ERROR=1; exit $ERROR # the format is not always matching our intentions allow_failure: true diff --git a/.gitlab-ci/shellcheck.sh b/.gitlab-ci/shellcheck.sh new file mode 100755 index 00000000..e7db0b63 --- /dev/null +++ b/.gitlab-ci/shellcheck.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# Simplified and de-github-ed version of +# https://github.com/ludeeus/action-shellcheck/blob/master/action.yaml + +statuscode=0 + +declare -a filepaths +shebangregex="^#! */[^ ]*/(env *)?[abk]*sh" +set -f # temporarily disable globbing so that globs in inputs aren't expanded + +while IFS= read -r -d '' file; do + filepaths+=("$file") +done < <(find . \ + -type f \ + '(' \ + -name '*.bash' \ + -o -name '.bashrc' \ + -o -name 'bashrc' \ + -o -name '.bash_aliases' \ + -o -name '.bash_completion' \ + -o -name '.bash_login' \ + -o -name '.bash_logout' \ + -o -name '.bash_profile' \ + -o -name 'bash_profile' \ + -o -name '*.ksh' \ + -o -name 'suid_profile' \ + -o -name '*.zsh' \ + -o -name '.zlogin' \ + -o -name 'zlogin' \ + -o -name '.zlogout' \ + -o -name 'zlogout' \ + -o -name '.zprofile' \ + -o -name 'zprofile' \ + -o -name '.zsenv' \ + -o -name 'zsenv' \ + -o -name '.zshrc' \ + -o -name 'zshrc' \ + -o -name '*.sh' \ + -o -path '*/.profile' \ + -o -path '*/profile' \ + -o -name '*.shlib' \ + ')' \ + -print0) + +while IFS= read -r -d '' file; do + head -n1 "$file" | grep -Eqs "$shebangregex" || continue + filepaths+=("$file") +done < <(find . \ + -type f ! -name '*.*' -perm /111 \ + -print0) + +shellcheck "${filepaths[@]}" || statuscode=$? + +set +f # re-enable globbing + +exit "$statuscode" From 9d5c31205c8b87970a1efa2d5d8c3ec9b872bc87 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 13 Mar 2024 11:15:42 +0100 Subject: [PATCH 147/795] Reformat ssh_silent_disconnect Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/session.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/session.c b/src/session.c index 58b879ac..5b12c16e 100644 --- a/src/session.c +++ b/src/session.c @@ -502,14 +502,16 @@ const char* ssh_get_hmac_out(ssh_session session) { * * @param[in] session The SSH session to disconnect. */ -void ssh_silent_disconnect(ssh_session session) { - if (session == NULL) { - return; - } +void +ssh_silent_disconnect(ssh_session session) +{ + if (session == NULL) { + return; + } - ssh_socket_close(session->socket); - session->alive = 0; - ssh_disconnect(session); + ssh_socket_close(session->socket); + session->alive = 0; + ssh_disconnect(session); } /** From 07cb0be12f4a3d5691f6a580343cb94d658decf5 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 13 Mar 2024 11:20:24 +0100 Subject: [PATCH 148/795] Do not close socket passed through options on error conditions Fixes: #244 Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/priv.h | 1 + src/client.c | 9 ++------- src/packet_cb.c | 4 +--- src/session.c | 18 ++++++++++++++++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 596cc2d6..bfef771d 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -308,6 +308,7 @@ int ssh_auth_reply_success(ssh_session session, int partial); /* client.c */ int ssh_send_banner(ssh_session session, int is_server); +void ssh_session_socket_close(ssh_session session); /* connect.c */ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, diff --git a/src/client.c b/src/client.c index 9e1606a3..b3d0fe62 100644 --- a/src/client.c +++ b/src/client.c @@ -486,9 +486,7 @@ static void ssh_client_connection_callback(ssh_session session) return; error: - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state = SSH_SESSION_STATE_ERROR; + ssh_session_socket_close(session); SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); } @@ -798,10 +796,7 @@ ssh_disconnect(ssh_session session) } ssh_packet_send(session); - /* Do not close the socket, if the fd was set via options. */ - if (session->opts.fd == SSH_INVALID_SOCKET) { - ssh_socket_close(session->socket); - } + ssh_session_socket_close(session); } error: diff --git a/src/packet_cb.c b/src/packet_cb.c index 363c605f..7edb6791 100644 --- a/src/packet_cb.c +++ b/src/packet_cb.c @@ -81,9 +81,7 @@ SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) error != NULL ? error : "no error"); SAFE_FREE(error); - ssh_socket_close(session->socket); - session->alive = 0; - session->session_state = SSH_SESSION_STATE_ERROR; + ssh_session_socket_close(session); /* correctly handle disconnect during authorization */ session->auth.state = SSH_AUTH_STATE_FAILED; diff --git a/src/session.c b/src/session.c index 5b12c16e..279352b6 100644 --- a/src/session.c +++ b/src/session.c @@ -495,6 +495,21 @@ const char* ssh_get_hmac_out(ssh_session session) { return NULL; } +/** + * @internal + * @brief Close the connection socket if it is a socket created by us. + * Does not close the sockets provided by the user through options API. + */ +void +ssh_session_socket_close(ssh_session session) +{ + if (session->opts.fd == SSH_INVALID_SOCKET) { + ssh_socket_close(session->socket); + } + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + /** * @brief Disconnect impolitely from a remote host by closing the socket. * @@ -509,8 +524,7 @@ ssh_silent_disconnect(ssh_session session) return; } - ssh_socket_close(session->socket); - session->alive = 0; + ssh_session_socket_close(session); ssh_disconnect(session); } From 60085debb13fbe5ea92346dbcf2c130c470bbd7e Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 22 Mar 2024 20:16:36 +0100 Subject: [PATCH 149/795] ci: Remove duplicate check for sign-off trailers Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c47e6d45..e9a9f396 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -110,7 +110,6 @@ review: codespell --ignore-words-list=keypair,sorce,ned,nd,ue || ERROR=1; ./.gitlab-ci/clang-format-check.sh || ERROR=1; ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; - ./.gitlab-ci/git-check-signoff-trailer.sh ${CI_MERGE_REQUEST_DIFF_BASE_SHA} || ERROR=1; ./.gitlab-ci/shellcheck.sh || ERROR=1; exit $ERROR # the format is not always matching our intentions From 78378291b13dd4d39b0d88e6ce622fd6ae83b060 Mon Sep 17 00:00:00 2001 From: Norbert Pocs Date: Sat, 30 Dec 2023 18:34:00 +0100 Subject: [PATCH 150/795] ecdh_crypto.c: free secret when error happens Signed-off-by: Norbert Pocs Reviewed-by: Jakub Jelen --- src/ecdh_crypto.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ecdh_crypto.c b/src/ecdh_crypto.c index 817f066a..603c293b 100644 --- a/src/ecdh_crypto.c +++ b/src/ecdh_crypto.c @@ -397,6 +397,7 @@ int ecdh_build_k(ssh_session session) "Could not derive shared key: %s", ERR_error_string(ERR_get_error(), NULL)); EVP_PKEY_CTX_free(dh_ctx); + free(secret); return -1; } From 996037e77ba12565da7572a11c269b20cdb67871 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Mon, 25 Mar 2024 22:49:30 +1300 Subject: [PATCH 151/795] cmake: fix missing includes in ConfigureChecks Signed-off-by: Noah Miller Reviewed-by: Jakub Jelen --- ConfigureChecks.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 64aaea07..a83868ca 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -76,6 +76,7 @@ if (WIN32) endif (WIN32) if (OPENSSL_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) check_include_file(openssl/des.h HAVE_OPENSSL_DES_H) @@ -102,6 +103,7 @@ if (OPENSSL_FOUND) check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) + unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) endif() From 4a83c50ce91d1d9ce85dcdb9177c9c06c895f01d Mon Sep 17 00:00:00 2001 From: Ajit Singh Date: Tue, 2 Apr 2024 05:24:30 +0530 Subject: [PATCH 152/795] sftp.c: call ssh_set_error Since sftp_init() returns 0 on success, < 0 on error with ssh error set. This change sets the appropriate ssh error when the SSH_FXP_VERSION packet cannot be unpacked and sftp_init() return with -1. Signed-off-by: Ajit Singh Reviewed-by: Jakub Jelen --- src/sftp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sftp.c b/src/sftp.c index 2bfe04e4..76a9e13e 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -464,7 +464,10 @@ int sftp_init(sftp_session sftp) /* TODO: are we sure there are 4 bytes ready? */ rc = ssh_buffer_unpack(packet->payload, "d", &version); - if (rc != SSH_OK){ + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Unable to unpack SSH_FXP_VERSION packet"); sftp_set_error(sftp, SSH_FX_FAILURE); return -1; } From d2a8a464a73ad49dba7a2552c01633d8f9439160 Mon Sep 17 00:00:00 2001 From: Noah Miller Date: Fri, 5 Apr 2024 21:41:04 +1300 Subject: [PATCH 153/795] Fix mbedTLS issues Signed-off-by: Noah Miller Reviewed-by: Jakub Jelen --- src/libmbedcrypto.c | 2 +- src/pki_mbedcrypto.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index 55951764..8fb36e53 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -119,7 +119,7 @@ int hmac_update(HMACCTX c, const void *data, size_t len) int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { int rc; - *len = (unsigned int)mbedtls_md_get_size(c->md_info); + *len = (unsigned int)mbedtls_md_get_size(c->MBEDTLS_PRIVATE(md_info)); rc = !mbedtls_md_hmac_finish(c, hashmacbuf); mbedtls_md_free(c); SAFE_FREE(c); diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index d3fda0ae..962ae1fe 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -1012,7 +1012,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) if (d == NULL) { goto fail; } - rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP) + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); if (rc != 0) { goto fail; } From 74a8d271ad11a39ac117246737605cf29d0bd905 Mon Sep 17 00:00:00 2001 From: Adam Kerrison Date: Tue, 4 Jul 2023 12:27:28 +0100 Subject: [PATCH 154/795] Add support for more options in ssh_options_get() Signed-off-by: Adam Kerrison Squashed-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/options.h | 2 + src/options.c | 139 ++++++++++-- tests/unittests/torture_options.c | 361 ++++++++++++++++++++++++++++-- 3 files changed, 472 insertions(+), 30 deletions(-) diff --git a/include/libssh/options.h b/include/libssh/options.h index 9050d3be..d32e1589 100644 --- a/include/libssh/options.h +++ b/include/libssh/options.h @@ -33,6 +33,8 @@ int ssh_options_set_algo(ssh_session session, char **place); int ssh_options_apply(ssh_session session); +char *ssh_options_get_algo(ssh_session session, enum ssh_kex_types_e algo); + #ifdef __cplusplus } #endif diff --git a/src/options.c b/src/options.c index 961aba4e..4408ff8d 100644 --- a/src/options.c +++ b/src/options.c @@ -1290,6 +1290,46 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return 0; } +/** + * @brief This function returns the current algorithms used for algorithm + * negotiation. It is either libssh default, option manually set or option + * read from configuration file. + * + * This function will return NULL on error + * + * @param session An allocated SSH session structure. + * @param algo One of the ssh_kex_types_e values. + */ +char *ssh_options_get_algo(ssh_session session, + enum ssh_kex_types_e algo) +{ + char *value = NULL; + + /* Check session and algo values are valid */ + + if (session == NULL) { + return NULL; + } + + if (algo >= SSH_LANG_C_S) { + ssh_set_error_invalid(session); + return NULL; + } + + /* Get the option the user has set, if there is one */ + value = session->opts.wanted_methods[algo]; + if (value == NULL) { + /* The user has not set a value, return the appropriate default */ + if (ssh_fips_mode()) + value = (char *)ssh_kex_get_fips_methods(algo); + else + value = (char *)ssh_kex_get_default_methods(algo); + } + + return value; +} + + /** * @brief This function can get ssh the ssh port. It must only be used on * a valid ssh session. This function is useful when the session @@ -1356,7 +1396,44 @@ int ssh_options_get_port(ssh_session session, unsigned int* port_target) { * Get the path to the known_hosts file being used. * * - SSH_OPTIONS_CONTROL_PATH: - * Get the path to the control socket being used for connection multiplexing. + * Get the path to the control socket being used for connection + * multiplexing. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Get the key exchange methods to be used. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HOSTKEYS: + * Get the preferred server host key types. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Get the preferred public key algorithms to be used for + * authentication. + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Get the symmetric cipher client to server. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Get the symmetric cipher server to client. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_C_S: + * Get the Message Authentication Code algorithm client to server + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_S_C: + * Get the Message Authentication Code algorithm server to client + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Get the compression to use for client to server communication + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Get the compression to use for server to client communication + * If the option has not been set, returns the defaults. * * @param value The value to get into. As a char**, space will be * allocated by the function for the value, it is @@ -1380,14 +1457,14 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) switch(type) { - case SSH_OPTIONS_HOST: { + case SSH_OPTIONS_HOST: src = session->opts.host; break; - } - case SSH_OPTIONS_USER: { + + case SSH_OPTIONS_USER: src = session->opts.username; break; - } + case SSH_OPTIONS_IDENTITY: { struct ssh_iterator *it; it = ssh_list_get_iterator(session->opts.identity); @@ -1400,22 +1477,58 @@ int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) src = ssh_iterator_value(char *, it); break; } - case SSH_OPTIONS_PROXYCOMMAND: { + + case SSH_OPTIONS_PROXYCOMMAND: src = session->opts.ProxyCommand; break; - } - case SSH_OPTIONS_KNOWNHOSTS: { + + case SSH_OPTIONS_KNOWNHOSTS: src = session->opts.knownhosts; break; - } - case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: { + + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: src = session->opts.global_knownhosts; break; - } - case SSH_OPTIONS_CONTROL_PATH: { + case SSH_OPTIONS_CONTROL_PATH: src = session->opts.control_path; break; - } + + case SSH_OPTIONS_CIPHERS_C_S: + src = ssh_options_get_algo(session, SSH_CRYPT_C_S); + break; + + case SSH_OPTIONS_CIPHERS_S_C: + src = ssh_options_get_algo(session, SSH_CRYPT_S_C); + break; + + case SSH_OPTIONS_KEY_EXCHANGE: + src = ssh_options_get_algo(session, SSH_KEX); + break; + + case SSH_OPTIONS_HOSTKEYS: + src = ssh_options_get_algo(session, SSH_HOSTKEYS); + break; + + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + src = session->opts.pubkey_accepted_types; + break; + + case SSH_OPTIONS_HMAC_C_S: + src = ssh_options_get_algo(session, SSH_MAC_C_S); + break; + + case SSH_OPTIONS_HMAC_S_C: + src = ssh_options_get_algo(session, SSH_MAC_S_C); + break; + + case SSH_OPTIONS_COMPRESSION_C_S: + src = ssh_options_get_algo(session, SSH_COMP_C_S); + break; + + case SSH_OPTIONS_COMPRESSION_S_C: + src = ssh_options_get_algo(session, SSH_COMP_S_C); + break; + default: ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); return SSH_ERROR; diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index e41c15da..78b71146 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -98,7 +98,7 @@ static void torture_options_set_ciphers(void **state) { /* Test known ciphers */ rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "aes128-ctr,aes192-ctr,aes256-ctr"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], @@ -111,7 +111,7 @@ static void torture_options_set_ciphers(void **state) { /* Test one unknown cipher */ rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "aes128-ctr,unknown-crap@example.com,aes256-ctr"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], "aes128-ctr,aes256-ctr"); @@ -122,6 +122,53 @@ static void torture_options_set_ciphers(void **state) { assert_false(rc == 0); } +static void torture_options_get_ciphers(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "aes256-gcm@openssh.com," + "aes256-ctr," + "aes256-cbc," + "aes128-gcm@openssh.com," + "aes128-ctr," + "aes128-cbc"); + } else { + assert_string_equal(value, + "chacha20-poly1305@openssh.com," + "aes256-gcm@openssh.com," + "aes128-gcm@openssh.com," + "aes256-ctr," + "aes192-ctr," + "aes128-ctr"); + } + ssh_string_free_char(value); + + /* Test explicit ciphers */ + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(value, "aes128-ctr,aes192-ctr,aes256-ctr"); + } + ssh_string_free_char(value); +} + static void torture_options_set_key_exchange(void **state) { ssh_session session = *state; @@ -135,7 +182,7 @@ static void torture_options_set_key_exchange(void **state) "diffie-hellman-group18-sha512," "diffie-hellman-group14-sha256," "diffie-hellman-group14-sha1"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_KEX]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_KEX], @@ -157,7 +204,7 @@ static void torture_options_set_key_exchange(void **state) "diffie-hellman-group16-sha512," "unknown-crap@example.com," "diffie-hellman-group18-sha512"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_KEX]); assert_string_equal(session->opts.wanted_methods[SSH_KEX], "diffie-hellman-group16-sha512," @@ -170,6 +217,66 @@ static void torture_options_set_key_exchange(void **state) assert_false(rc == 0); } +static void torture_options_get_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdh-sha2-nistp256," + "ecdh-sha2-nistp384," + "ecdh-sha2-nistp521," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256," + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + } else { + assert_string_equal(value, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group18-sha512," + "diffie-hellman-group16-sha512," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256"); + } + ssh_string_free_char(value); + + /* Test explicit kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(value, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + ssh_string_free_char(value); +} + static void torture_options_set_hostkey(void **state) { ssh_session session = *state; int rc; @@ -178,7 +285,7 @@ static void torture_options_set_hostkey(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], @@ -194,7 +301,7 @@ static void torture_options_set_hostkey(void **state) { "ecdsa-sha2-nistp521," "unknown-crap@example.com," "rsa-sha2-256"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], "ecdsa-sha2-nistp521," @@ -207,6 +314,61 @@ static void torture_options_set_hostkey(void **state) { assert_false(rc == 0); } +static void torture_options_get_hostkey(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "rsa-sha2-512," + "rsa-sha2-256"); + } else { + assert_string_equal(value, + "ssh-ed25519-cert-v01@openssh.com," + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com," + "rsa-sha2-512,rsa-sha2-256"); + } + ssh_string_free_char(value); + + /* Test explicit host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + static void torture_options_set_pubkey_accepted_types(void **state) { ssh_session session = *state; int rc; @@ -216,7 +378,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.pubkey_accepted_types); if (ssh_fips_mode()) { assert_string_equal(session->opts.pubkey_accepted_types, @@ -231,7 +393,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "ssh-ed25519,unknown-crap@example.com,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.pubkey_accepted_types); assert_string_equal(session->opts.pubkey_accepted_types, "ssh-ed25519,ssh-rsa"); @@ -257,7 +419,7 @@ static void torture_options_set_pubkey_accepted_types(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, "rsa-sha2-256,ssh-rsa"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.pubkey_accepted_types); if (ssh_fips_mode()) { assert_string_equal(session->opts.pubkey_accepted_types, @@ -275,13 +437,37 @@ static void torture_options_set_pubkey_accepted_types(void **state) { assert_int_equal(type, SSH_DIGEST_SHA256); } +static void torture_options_get_pubkey_accepted_types(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_get(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + + static void torture_options_set_macs(void **state) { ssh_session session = *state; int rc; /* Test known MACs */ rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1"); @@ -289,14 +475,14 @@ static void torture_options_set_macs(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); /* Test unknown MACs */ rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com,hmac-sha1-etm@openssh.com,unknown@example.com"); - assert_true(rc == 0); + assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1-etm@openssh.com"); @@ -305,7 +491,141 @@ static void torture_options_set_macs(void **state) { assert_false(rc == 0); } -static void torture_options_get_host(void **state) { +static void torture_options_get_macs(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha1," + "hmac-sha2-512"); + } else { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha2-512"); + } + ssh_string_free_char(value); + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, "hmac-sha1"); + ssh_string_free_char(value); +} + +static void torture_options_set_compression(void **state) +{ + ssh_session session = *state; + int rc; + const char *known_value; + const char *multiple; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + known_value = "none"; + multiple = "none,squeeze"; + } else { + known_value = "zlib"; + multiple = "zlib,squeeze"; + } +#else + known_value = "none"; + multiple = "none,squeeze"; +#endif + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, known_value); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test multiple known compression */ + if (!ssh_fips_mode()) { + rc = ssh_options_set(session, + SSH_OPTIONS_COMPRESSION_S_C, + "none,zlib@openssh.com"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif + } + + /* Test unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, multiple); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test all unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "squeeze"); + assert_false(rc == 0); +} + +static void torture_options_get_compression(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + const char *test_value = NULL; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + test_value = "none"; + } else { + test_value = "zlib@openssh.com"; + } +#else + test_value = "none"; +#endif + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); +#ifdef WITH_ZLIB + assert_string_equal(value, "none,zlib@openssh.com"); +#else + assert_string_equal(value, "none"); +#endif + ssh_string_free_char(value); + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, test_value); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, test_value); + ssh_string_free_char(value); +} + +static void torture_options_get_host(void **state) +{ ssh_session session = *state; int rc; char* host = NULL; @@ -317,7 +637,7 @@ static void torture_options_get_host(void **state) { assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host)); assert_string_equal(host, "localhost"); - free(host); + ssh_string_free_char(host); } static void torture_options_set_port(void **state) { @@ -363,7 +683,7 @@ static void torture_options_get_user(void **state) { assert_int_equal(rc, SSH_OK); assert_non_null(user); assert_string_equal(user, "magicaltrevor"); - free(user); + ssh_string_free_char(user); } static void torture_options_set_fd(void **state) { @@ -454,7 +774,7 @@ static void torture_options_get_identity(void **state) { assert_int_equal(rc, SSH_OK); assert_non_null(identity); assert_string_equal(identity, "identity2"); - free(identity); + ssh_string_free_char(identity); } static void torture_options_set_global_knownhosts(void **state) @@ -773,7 +1093,7 @@ static void torture_options_config_match(void **state) localuser = ssh_get_local_username(); assert_non_null(localuser); fputs(localuser, config); - free(localuser); + ssh_string_free_char(localuser); fputs("\n" "\tPort 33\n" "Match all\n" @@ -2354,10 +2674,17 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_options_control_master, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_control_path, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_set_ciphers, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_ciphers, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_key_exchange, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_set_hostkey, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_hostkey, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_set_pubkey_accepted_types, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_pubkey_accepted_types, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_set_macs, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_macs, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_compression, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_get_compression, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), cmocka_unit_test_setup_teardown(torture_options_config_match, From 1bdc78d69f474acc23174d471858a9bcab6b8d4f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 9 Jan 2024 12:05:21 +0100 Subject: [PATCH 155/795] Reformat rest of torture_options Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/unittests/torture_options.c | 218 ++++++++++++++++++------------ 1 file changed, 130 insertions(+), 88 deletions(-) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 78b71146..bcf86dcf 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -91,12 +91,14 @@ static void torture_options_set_host(void **state) { assert_ssh_return_code_equal(session, rc, SSH_ERROR); } -static void torture_options_set_ciphers(void **state) { +static void torture_options_set_ciphers(void **state) +{ ssh_session session = *state; int rc; /* Test known ciphers */ - rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, "aes128-ctr,aes192-ctr,aes256-ctr"); assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); @@ -277,7 +279,8 @@ static void torture_options_get_key_exchange(void **state) ssh_string_free_char(value); } -static void torture_options_set_hostkey(void **state) { +static void torture_options_set_hostkey(void **state) +{ ssh_session session = *state; int rc; @@ -289,10 +292,10 @@ static void torture_options_set_hostkey(void **state) { assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); } else { assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], - "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); } /* Test one unknown host key */ @@ -369,7 +372,8 @@ static void torture_options_get_hostkey(void **state) ssh_string_free_char(value); } -static void torture_options_set_pubkey_accepted_types(void **state) { +static void torture_options_set_pubkey_accepted_types(void **state) +{ ssh_session session = *state; int rc; enum ssh_digest_e type; @@ -461,7 +465,8 @@ static void torture_options_get_pubkey_accepted_types(void **state) } -static void torture_options_set_macs(void **state) { +static void torture_options_set_macs(void **state) +{ ssh_session session = *state; int rc; @@ -474,20 +479,30 @@ static void torture_options_set_macs(void **state) { /* Test multiple known MACs */ rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, - "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], - "hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha1,hmac-sha2-256"); + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); /* Test unknown MACs */ - rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com,hmac-sha1-etm@openssh.com,unknown@example.com"); + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com,hmac-sha1-etm@openssh.com," + "unknown@example.com"); assert_ssh_return_code(session, rc); assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); - assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1-etm@openssh.com"); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com"); /* Test all unknown MACs */ - rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "unknown-crap@example.com"); + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); assert_false(rc == 0); } @@ -640,7 +655,8 @@ static void torture_options_get_host(void **state) ssh_string_free_char(host); } -static void torture_options_set_port(void **state) { +static void torture_options_set_port(void **state) +{ ssh_session session = *state; int rc; unsigned int port = 42; @@ -661,32 +677,37 @@ static void torture_options_set_port(void **state) { assert_true(rc == -1); } -static void torture_options_get_port(void **state) { - ssh_session session = *state; - unsigned int given_port = 1234; - unsigned int port_container; - int rc; - rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); - assert_true(rc == 0); - rc = ssh_options_get_port(session, &port_container); - assert_true(rc == 0); - assert_int_equal(port_container, 1234); +static void torture_options_get_port(void **state) +{ + ssh_session session = *state; + unsigned int given_port = 1234; + unsigned int port_container; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); + assert_true(rc == 0); + rc = ssh_options_get_port(session, &port_container); + assert_true(rc == 0); + assert_int_equal(port_container, 1234); } -static void torture_options_get_user(void **state) { - ssh_session session = *state; - char* user = NULL; - int rc; - rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); - assert_int_equal(rc, SSH_OK); - rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); - assert_int_equal(rc, SSH_OK); - assert_non_null(user); - assert_string_equal(user, "magicaltrevor"); - ssh_string_free_char(user); +static void torture_options_get_user(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_int_equal(rc, SSH_OK); + assert_non_null(user); + assert_string_equal(user, "magicaltrevor"); + ssh_string_free_char(user); } -static void torture_options_set_fd(void **state) { +static void torture_options_set_fd(void **state) +{ ssh_session session = *state; socket_t fd = 42; int rc; @@ -700,7 +721,8 @@ static void torture_options_set_fd(void **state) { assert_true(session->opts.fd == SSH_INVALID_SOCKET); } -static void torture_options_set_user(void **state) { +static void torture_options_set_user(void **state) +{ ssh_session session = *state; int rc; #ifndef _WIN32 @@ -732,29 +754,26 @@ static void torture_options_set_user(void **state) { #endif } -/* TODO */ -#if 0 -static voidtorture_options_set_sshdir) +static void torture_options_set_identity(void **state) { -} -END_TEST -#endif - -static void torture_options_set_identity(void **state) { ssh_session session = *state; int rc; rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); assert_true(rc == 0); - assert_string_equal(session->opts.identity_non_exp->root->data, "identity1"); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity1"); rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); assert_true(rc == 0); - assert_string_equal(session->opts.identity_non_exp->root->data, "identity2"); - assert_string_equal(session->opts.identity_non_exp->root->next->data, "identity1"); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity2"); + assert_string_equal(session->opts.identity_non_exp->root->next->data, + "identity1"); } -static void torture_options_get_identity(void **state) { +static void torture_options_get_identity(void **state) +{ ssh_session session = *state; char *identity = NULL; int rc; @@ -769,7 +788,8 @@ static void torture_options_get_identity(void **state) { rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); assert_int_equal(rc, SSH_OK); - assert_string_equal(session->opts.identity_non_exp->root->data, "identity2"); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity2"); rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); assert_int_equal(rc, SSH_OK); assert_non_null(identity); @@ -874,7 +894,8 @@ static void torture_options_proxycommand(void **state) { assert_null(session->opts.ProxyCommand); } -static void torture_options_control_master (void **state) { +static void torture_options_control_master (void **state) +{ ssh_session session = *state; int rc, val = SSH_CONTROL_MASTER_NO; @@ -919,13 +940,16 @@ static void torture_options_control_master (void **state) { assert_int_equal(rc, SSH_ERROR); } -static void torture_options_control_path(void **state) { +static void torture_options_control_path(void **state) +{ ssh_session session = *state; char *str = NULL; int rc; /* Set Control Path */ - rc = ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, "/tmp/ssh-%r@%h:%p"); + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_PATH, + "/tmp/ssh-%r@%h:%p"); assert_int_equal(rc, 0); assert_string_equal(session->opts.control_path, "/tmp/ssh-%r@%h:%p"); @@ -942,7 +966,8 @@ static void torture_options_control_path(void **state) { SSH_STRING_FREE_CHAR(str); } -static void torture_options_config_host(void **state) { +static void torture_options_config_host(void **state) +{ ssh_session session = *state; FILE *config = NULL; @@ -1747,7 +1772,8 @@ static void torture_options_caret_sign(void **state) free(awaited); } -static void torture_options_apply (void **state) { +static void torture_options_apply (void **state) +{ ssh_session session = *state; struct ssh_list *awaited_list = NULL; struct ssh_iterator *it1 = NULL, *it2 = NULL; @@ -2435,13 +2461,16 @@ static void torture_bind_options_set_macs(void **state) /* Test unknown MACs */ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, - "unknown-crap@example.com,hmac-sha1,unknown@example.com"); + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); /* Test all unknown MACs */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, "unknown-crap@example.com"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); assert_int_not_equal(rc, 0); /* Test known MACs */ @@ -2462,13 +2491,16 @@ static void torture_bind_options_set_macs(void **state) /* Test unknown MACs */ rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, - "unknown-crap@example.com,hmac-sha1,unknown@example.com"); + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); /* Test all unknown MACs */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, "unknown-crap@example.com"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com"); assert_int_not_equal(rc, 0); } @@ -2495,7 +2527,8 @@ static void torture_bind_options_parse_config(void **state) assert_non_null(bind->config_dir); assert_string_equal(bind->config_dir, cwd); - rc = ssh_bind_options_parse_config(bind, "%d/"LIBSSH_CUSTOM_BIND_CONFIG_FILE); + rc = ssh_bind_options_parse_config(bind, + "%d/" LIBSSH_CUSTOM_BIND_CONFIG_FILE); assert_int_equal(rc, 0); assert_int_equal(bind->bindport, 42); @@ -2544,8 +2577,9 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) bind = test_state->bind; /* Test known Pubkey Types */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); if (ssh_fips_mode()) { @@ -2559,8 +2593,9 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) SAFE_FREE(bind->pubkey_accepted_key_types); /* Test with some unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); assert_string_equal(bind->pubkey_accepted_key_types, @@ -2569,26 +2604,27 @@ static void torture_bind_options_set_pubkey_accepted_key_types(void **state) SAFE_FREE(bind->pubkey_accepted_key_types); /* Test with only unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "unknown-type"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); assert_int_equal(rc, -1); assert_null(bind->pubkey_accepted_key_types); /* Test with something set and then try unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384"); assert_int_equal(rc, 0); assert_non_null(bind->pubkey_accepted_key_types); - assert_string_equal(bind->pubkey_accepted_key_types, - "ecdsa-sha2-nistp384"); - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - "unknown-type"); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); assert_int_equal(rc, -1); /* Check that nothing changed */ assert_non_null(bind->pubkey_accepted_key_types); - assert_string_equal(bind->pubkey_accepted_key_types, - "ecdsa-sha2-nistp384"); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); } static void torture_bind_options_set_hostkey_algorithms(void **state) @@ -2604,57 +2640,63 @@ static void torture_bind_options_set_hostkey_algorithms(void **state) bind = test_state->bind; /* Test known Pubkey Types */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); if (ssh_fips_mode()) { assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); } else { assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); } SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); /* Test with some unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "ecdsa-sha2-nistp384,unknown-type"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384,unknown-type"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); /* Test with only unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "unknown-type"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); assert_int_equal(rc, -1); assert_null(bind->wanted_methods[SSH_HOSTKEYS]); /* Test with something set and then try unknown type */ - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384"); assert_int_equal(rc, 0); assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); - rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - "unknown-type"); + "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); assert_int_equal(rc, -1); /* Check that nothing changed */ assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], - "ecdsa-sha2-nistp384"); + "ecdsa-sha2-nistp384"); } #endif /* WITH_SERVER */ -int torture_run_tests(void) { +int torture_run_tests(void) +{ int rc; struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(torture_options_set_host, setup, teardown), From a8b7e17aa0cb51a62f308af0bb456a26d461234e Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 10 Jan 2024 10:37:10 +0100 Subject: [PATCH 156/795] kex: Avoid trailing comma in cipher list Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/kex.c | 23 ++++++++++------------- tests/unittests/torture_config.c | 6 +----- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/kex.c b/src/kex.c index 0df4d3eb..b071d5ea 100644 --- a/src/kex.c +++ b/src/kex.c @@ -48,7 +48,7 @@ #ifdef WITH_BLOWFISH_CIPHER # if defined(HAVE_OPENSSL_BLOWFISH_H) || defined(HAVE_LIBGCRYPT) || defined(HAVE_LIBMBEDCRYPTO) -# define BLOWFISH "blowfish-cbc," +# define BLOWFISH ",blowfish-cbc" # else # define BLOWFISH "" # endif @@ -58,10 +58,9 @@ #ifdef HAVE_LIBGCRYPT # define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ - "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" + "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" #elif defined(HAVE_LIBMBEDCRYPTO) # ifdef MBEDTLS_GCM_C @@ -69,23 +68,21 @@ # else # define GCM "" # endif /* MBEDTLS_GCM_C */ -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" #elif defined(HAVE_LIBCRYPTO) # ifdef HAVE_OPENSSL_AES_H # define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," -# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr," -# define AES_CBC "aes256-cbc,aes192-cbc,aes128-cbc," +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" # else /* HAVE_OPENSSL_AES_H */ # define AES "" # define AES_CBC "" # endif /* HAVE_OPENSSL_AES_H */ -# define DES "3des-cbc" -# define DES_SUPPORTED "3des-cbc" +# define DES_SUPPORTED ",3des-cbc" #endif /* HAVE_LIBCRYPTO */ #ifdef WITH_ZLIB diff --git a/tests/unittests/torture_config.c b/tests/unittests/torture_config.c index ebc2cdbd..83a5f773 100644 --- a/tests/unittests/torture_config.c +++ b/tests/unittests/torture_config.c @@ -1431,7 +1431,7 @@ static void torture_config_plus(void **state, const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); const char *hostkeys_added = ",ssh-rsa"; - const char *ciphers_added = "aes128-cbc,aes256-cbc"; + const char *ciphers_added = ",aes128-cbc,aes256-cbc"; const char *kex_added = ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; const char *mac_added = ",hmac-sha1,hmac-sha1-etm@openssh.com"; char *awaited = NULL; @@ -1558,8 +1558,6 @@ static void torture_config_minus(void **state, awaited = calloc(strlen(def_ciphers) + 1, 1); rc = snprintf(awaited, strlen(def_ciphers) + 1, "%s", def_ciphers); assert_int_equal(rc, strlen(def_ciphers)); - /* remove the comma at the end of the list */ - awaited[strlen(awaited) - 1] = '\0'; } /* remove the substring from the defaults */ helper_remove_substring(awaited, ciphers_removed, 0); @@ -1676,8 +1674,6 @@ static void torture_config_caret(void **state, rc = snprintf(awaited, strlen(ciphers_prio) + strlen(def_ciphers) + 1, "%s%s", ciphers_prio, def_ciphers); assert_int_equal(rc, strlen(ciphers_prio) + strlen(def_ciphers)); - /* remove the comma at the end of the list */ - awaited[strlen(awaited) - 1] = '\0'; } assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); From 5dd42dfa2205fc3108689ef80542181e9e5da336 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 26 Jan 2024 15:06:17 +0100 Subject: [PATCH 157/795] examples: Avoid buffer overrun and provide helpful warning message CID 1533680: Memory - illegal accesses (OVERRUN) Thanks coverity Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- examples/ssh_server.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/examples/ssh_server.c b/examples/ssh_server.c index 3e9f344b..faf5e003 100644 --- a/examples/ssh_server.c +++ b/examples/ssh_server.c @@ -531,6 +531,14 @@ static int auth_publickey(ssh_session session, } } + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + if (p[i] == '#' || p[i] == '\0' || p[i] == '\n') { continue; } @@ -545,7 +553,16 @@ static int auth_publickey(ssh_session session, type = ssh_key_type_from_name(q); - q = &p[i + 1]; + i++; + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + q = &p[i]; for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { if (isspace((int)p[i])) { p[i] = '\0'; From d34bfdab69b9cfdf3696a418a583881a614e0c2b Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Tue, 9 Apr 2024 21:45:10 +0530 Subject: [PATCH 158/795] reformat Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- include/libssh/server.h | 44 ++-- src/options.c | 381 +++++++++++++++--------------- tests/unittests/torture_options.c | 204 +++++++++++----- 3 files changed, 364 insertions(+), 265 deletions(-) diff --git a/include/libssh/server.h b/include/libssh/server.h index 885ef576..9ce79277 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -36,28 +36,28 @@ extern "C" { #endif enum ssh_bind_options_e { - SSH_BIND_OPTIONS_BINDADDR, - SSH_BIND_OPTIONS_BINDPORT, - SSH_BIND_OPTIONS_BINDPORT_STR, - SSH_BIND_OPTIONS_HOSTKEY, - SSH_BIND_OPTIONS_DSAKEY, /* deprecated */ - SSH_BIND_OPTIONS_RSAKEY, /* deprecated */ - SSH_BIND_OPTIONS_BANNER, - SSH_BIND_OPTIONS_LOG_VERBOSITY, - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, - SSH_BIND_OPTIONS_ECDSAKEY, /* deprecated */ - SSH_BIND_OPTIONS_IMPORT_KEY, - SSH_BIND_OPTIONS_KEY_EXCHANGE, - SSH_BIND_OPTIONS_CIPHERS_C_S, - SSH_BIND_OPTIONS_CIPHERS_S_C, - SSH_BIND_OPTIONS_HMAC_C_S, - SSH_BIND_OPTIONS_HMAC_S_C, - SSH_BIND_OPTIONS_CONFIG_DIR, - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, - SSH_BIND_OPTIONS_PROCESS_CONFIG, - SSH_BIND_OPTIONS_MODULI, - SSH_BIND_OPTIONS_RSA_MIN_SIZE, + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_RSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + SSH_BIND_OPTIONS_ECDSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_IMPORT_KEY, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + SSH_BIND_OPTIONS_CIPHERS_C_S, + SSH_BIND_OPTIONS_CIPHERS_S_C, + SSH_BIND_OPTIONS_HMAC_C_S, + SSH_BIND_OPTIONS_HMAC_S_C, + SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + SSH_BIND_OPTIONS_MODULI, + SSH_BIND_OPTIONS_RSA_MIN_SIZE, }; typedef struct ssh_bind_struct* ssh_bind; diff --git a/src/options.c b/src/options.c index 4408ff8d..81be625d 100644 --- a/src/options.c +++ b/src/options.c @@ -2017,7 +2017,8 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * - SSH_LOG_NOLOG: No logging * - SSH_LOG_WARNING: Only warnings * - SSH_LOG_PROTOCOL: High level protocol information - * - SSH_LOG_PACKET: Lower level protocol information, packet level + * - SSH_LOG_PACKET: Lower level protocol information, + * packet level * - SSH_LOG_FUNCTIONS: Every function path * The default is SSH_LOG_NOLOG. * @@ -2026,8 +2027,8 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * string that will be converted to a numerical * value (e.g. "3") and interpreted according * to the values of - * SSH_BIND_OPTIONS_LOG_VERBOSITY above (const - * char *). + * SSH_BIND_OPTIONS_LOG_VERBOSITY above + * (const char *). * * - SSH_BIND_OPTIONS_RSAKEY: * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY @@ -2048,16 +2049,16 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * (ssh_key). It will be free'd by ssh_bind_free(). * * - SSH_BIND_OPTIONS_CIPHERS_C_S: - * Set the symmetric cipher client to server (const char *, - * comma-separated list). + * Set the symmetric cipher client to server + * (const char *, comma-separated list). * * - SSH_BIND_OPTIONS_CIPHERS_S_C: - * Set the symmetric cipher server to client (const char *, - * comma-separated list). + * Set the symmetric cipher server to client + * (const char *, comma-separated list). * * - SSH_BIND_OPTIONS_KEY_EXCHANGE: - * Set the key exchange method to be used (const char *, - * comma-separated list). ex: + * Set the key exchange method to be used + * (const char *, comma-separated list). ex: * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1" * * - SSH_BIND_OPTIONS_HMAC_C_S: @@ -2113,94 +2114,98 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * datatype which should be used is described at the * corresponding value of type above. * - * @return 0 on success, < 0 on error, invalid option, or parameter. + * @return 0 on success, < 0 on error, invalid option, or + * parameter. */ -int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, - const void *value) +int +ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, + const void *value) { - bool allowed; - char *p, *q; - const char *v; - int i, rc; - char **wanted_methods = sshbind->wanted_methods; + bool allowed; + char *p, *q; + const char *v; + int i, rc; + char **wanted_methods = sshbind->wanted_methods; - if (sshbind == NULL) { - return -1; - } + if (sshbind == NULL) { + return -1; + } - switch (type) { + switch (type) { case SSH_BIND_OPTIONS_RSAKEY: case SSH_BIND_OPTIONS_ECDSAKEY: /* deprecated */ case SSH_BIND_OPTIONS_HOSTKEY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int key_type; - ssh_key key; - ssh_key *bind_key_loc = NULL; - char **bind_key_path_loc; - - rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); - if (rc != SSH_OK) { - return -1; - } - allowed = ssh_bind_key_size_allowed(sshbind, key); - if (!allowed) { - ssh_set_error(sshbind, - SSH_FATAL, - "The host key size %d is too small.", - ssh_key_size(key)); - ssh_key_free(key); - return -1; - } - - key_type = ssh_key_type(key); - switch (key_type) { - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int key_type; + ssh_key key; + ssh_key *bind_key_loc = NULL; + char **bind_key_path_loc; + + rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + return -1; + } + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + ssh_key_free(key); + return -1; + } + + key_type = ssh_key_type(key); + switch (key_type) { + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; - bind_key_path_loc = &sshbind->ecdsakey; + bind_key_loc = &sshbind->ecdsa; + bind_key_path_loc = &sshbind->ecdsakey; #else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); + ssh_set_error(sshbind, + SSH_FATAL, + "ECDSA key used and libssh compiled " + "without ECDSA support"); #endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - bind_key_path_loc = &sshbind->rsakey; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - bind_key_path_loc = &sshbind->ed25519key; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", key_type); - } - - if (bind_key_loc == NULL) { - ssh_key_free(key); - return -1; - } - - /* Set the location of the key on disk even though we don't - need it in case some other function wants it */ - rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); - if (rc < 0) { - ssh_key_free(key); - return -1; - } - ssh_key_free(*bind_key_loc); - *bind_key_loc = key; - } - break; + break; + case SSH_KEYTYPE_RSA: + bind_key_loc = &sshbind->rsa; + bind_key_path_loc = &sshbind->rsakey; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", + key_type); + } + + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + + /* Set the location of the key on disk even though we don't + need it in case some other function wants it */ + rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); + if (rc < 0) { + ssh_key_free(key); + return -1; + } + ssh_key_free(*bind_key_loc); + *bind_key_loc = key; + } + break; case SSH_BIND_OPTIONS_IMPORT_KEY: if (value == NULL) { ssh_set_error_invalid(sshbind); @@ -2221,28 +2226,29 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, key_type = ssh_key_type(key); switch (key_type) { - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: #ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; + bind_key_loc = &sshbind->ecdsa; #else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); + ssh_set_error(sshbind, + SSH_FATAL, + "ECDSA key used and libssh compiled " + "without ECDSA support"); #endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", key_type); + break; + case SSH_KEYTYPE_RSA: + bind_key_loc = &sshbind->rsa; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", + key_type); } if (bind_key_loc == NULL) return -1; @@ -2251,89 +2257,89 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } break; case SSH_BIND_OPTIONS_BINDADDR: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - SAFE_FREE(sshbind->bindaddr); - sshbind->bindaddr = strdup(value); - if (sshbind->bindaddr == NULL) { - ssh_set_error_oom(sshbind); - return -1; + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->bindaddr); + sshbind->bindaddr = strdup(value); + if (sshbind->bindaddr == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } } - } - break; + break; case SSH_BIND_OPTIONS_BINDPORT: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int *x = (int *) value; - sshbind->bindport = *x & 0xffffU; - } - break; - case SSH_BIND_OPTIONS_BINDPORT_STR: - if (value == NULL) { - sshbind->bindport = 22 & 0xffffU; - } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(sshbind); - return -1; - } - i = strtol(q, &p, 10); - if (q == p) { - SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); - SAFE_FREE(q); + if (value == NULL) { + ssh_set_error_invalid(sshbind); return -1; + } else { + int *x = (int *)value; + sshbind->bindport = *x & 0xffffU; } - SAFE_FREE(q); + break; + case SSH_BIND_OPTIONS_BINDPORT_STR: + if (value == NULL) { + sshbind->bindport = 22 & 0xffffU; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); - sshbind->bindport = i & 0xffffU; - } - break; - case SSH_BIND_OPTIONS_LOG_VERBOSITY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int *x = (int *) value; - ssh_set_log_level(*x & 0xffffU); - } - break; - case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: - if (value == NULL) { - ssh_set_log_level(0); - } else { - q = strdup(value); - if (q == NULL) { - ssh_set_error_oom(sshbind); - return -1; + sshbind->bindport = i & 0xffffU; } - i = strtol(q, &p, 10); - if (q == p) { - SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); - SAFE_FREE(q); + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); return -1; + } else { + int *x = (int *)value; + ssh_set_log_level(*x & 0xffffU); } - SAFE_FREE(q); + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + if (value == NULL) { + ssh_set_log_level(0); + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); - ssh_set_log_level(i & 0xffffU); - } - break; + ssh_set_log_level(i & 0xffffU); + } + break; case SSH_BIND_OPTIONS_BANNER: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - SAFE_FREE(sshbind->banner); - sshbind->banner = strdup(value); - if (sshbind->banner == NULL) { - ssh_set_error_oom(sshbind); - return -1; + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->banner); + sshbind->banner = strdup(value); + if (sshbind->banner == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } } - } - break; + break; case SSH_BIND_OPTIONS_CIPHERS_C_S: v = value; if (v == NULL || v[0] == '\0') { @@ -2394,7 +2400,7 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } } break; - case SSH_BIND_OPTIONS_HMAC_S_C: + case SSH_BIND_OPTIONS_HMAC_S_C: v = value; if (v == NULL || v[0] == '\0') { ssh_set_error_invalid(sshbind); @@ -2484,21 +2490,26 @@ int ssh_bind_options_set(ssh_bind sshbind, enum ssh_bind_options_e type, } else { int *x = (int *)value; if (*x > 0 && *x < 768) { - ssh_set_error(sshbind, SSH_REQUEST_DENIED, + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, "The provided value (%u) for minimal RSA key " - "size is too small. Use at least 768 bits.", *x); + "size is too small. Use at least 768 bits.", + *x); return -1; } sshbind->rsa_min_size = *x; } break; default: - ssh_set_error(sshbind, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); - return -1; - break; - } + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "Unknown ssh option %d", + type); + return -1; + break; + } - return 0; + return 0; } static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index bcf86dcf..2ccfa7af 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -2049,7 +2049,8 @@ static int sshbind_teardown(void **state) return 0; } -static void torture_bind_options_import_key(void **state) +static void +torture_bind_options_import_key(void **state) { struct bind_st *test_state; ssh_bind bind; @@ -2694,95 +2695,182 @@ static void torture_bind_options_set_hostkey_algorithms(void **state) #endif /* WITH_SERVER */ - -int torture_run_tests(void) +int +torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_options_set_host, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_host, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_port, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_port, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_fd, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_user, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_user, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_identity, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_identity, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_proxycommand, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_control_master, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_control_path, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_ciphers, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_ciphers, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_key_exchange, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_hostkey, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_hostkey, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_pubkey_accepted_types, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_pubkey_accepted_types, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_macs, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_macs, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_compression, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_get_compression, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_fd, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_control_master, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_control_path, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_set_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_get_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_compression, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_compression, + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_config_host, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_host, + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_config_match, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_config_match_multi, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_getopt, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_plus_sign, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_minus_sign, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_caret_sign, - setup, teardown), + setup, + teardown), cmocka_unit_test_setup_teardown(torture_options_apply, setup, teardown), - cmocka_unit_test_setup_teardown(torture_options_set_verbosity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_verbosity, + setup, + teardown), }; #ifdef WITH_SERVER struct CMUnitTest sshbind_tests[] = { cmocka_unit_test_setup_teardown(torture_bind_options_import_key, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_hostkey, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_bindaddr, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_bindport, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_bindport_str, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity_str, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_rsakey, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), #ifdef HAVE_ECC cmocka_unit_test_setup_teardown(torture_bind_options_ecdsakey, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), #endif cmocka_unit_test_setup_teardown(torture_bind_options_banner, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_ciphers, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_key_exchange, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_set_macs, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_parse_config, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_config_dir, - sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_options_set_pubkey_accepted_key_types, - sshbind_setup, sshbind_teardown), - cmocka_unit_test_setup_teardown(torture_bind_options_set_hostkey_algorithms, - sshbind_setup, sshbind_teardown), + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_pubkey_accepted_key_types, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_hostkey_algorithms, + sshbind_setup, + sshbind_teardown), }; #endif /* WITH_SERVER */ From 2daf3dc4a8d83dd74d98ce8cea2b2e457fedbc5a Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Tue, 9 Apr 2024 22:04:35 +0530 Subject: [PATCH 159/795] feat: add option to read user-supplied key string in ssh_bind_options_set() Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- include/libssh/server.h | 1 + src/options.c | 117 ++++++++++++++++------------------------ 2 files changed, 48 insertions(+), 70 deletions(-) diff --git a/include/libssh/server.h b/include/libssh/server.h index 9ce79277..e437f292 100644 --- a/include/libssh/server.h +++ b/include/libssh/server.h @@ -58,6 +58,7 @@ enum ssh_bind_options_e { SSH_BIND_OPTIONS_PROCESS_CONFIG, SSH_BIND_OPTIONS_MODULI, SSH_BIND_OPTIONS_RSA_MIN_SIZE, + SSH_BIND_OPTIONS_IMPORT_KEY_STR, }; typedef struct ssh_bind_struct* ssh_bind; diff --git a/src/options.c b/src/options.c index 81be625d..61a7e4a7 100644 --- a/src/options.c +++ b/src/options.c @@ -2048,6 +2048,10 @@ static int ssh_bind_set_algo(ssh_bind sshbind, * Set the Private Key for the server directly * (ssh_key). It will be free'd by ssh_bind_free(). * + * - SSH_BIND_OPTIONS_IMPORT_KEY_STR: + * Set the Private key for the server from a + * base64 encoded buffer (const char *). + * * - SSH_BIND_OPTIONS_CIPHERS_C_S: * Set the symmetric cipher client to server * (const char *, comma-separated list). @@ -2137,18 +2141,37 @@ ssh_bind_options_set(ssh_bind sshbind, case SSH_BIND_OPTIONS_ECDSAKEY: /* deprecated */ case SSH_BIND_OPTIONS_HOSTKEY: + case SSH_BIND_OPTIONS_IMPORT_KEY: + case SSH_BIND_OPTIONS_IMPORT_KEY_STR: if (value == NULL) { ssh_set_error_invalid(sshbind); return -1; } else { int key_type; - ssh_key key; ssh_key *bind_key_loc = NULL; - char **bind_key_path_loc; - - rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); - if (rc != SSH_OK) { - return -1; + ssh_key key = NULL; + char **bind_key_path_loc = NULL; + + if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + const char *key_str = (const char *)value; + rc = ssh_pki_import_privkey_base64(key_str, + NULL, + NULL, + NULL, + &key); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, + SSH_FATAL, + "Failed to import key from buffer"); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY) { + key = (ssh_key)value; + } else { + rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + return -1; + } } allowed = ssh_bind_key_size_allowed(sshbind, key); if (!allowed) { @@ -2156,10 +2179,8 @@ ssh_bind_options_set(ssh_bind sshbind, SSH_FATAL, "The host key size %d is too small.", ssh_key_size(key)); - ssh_key_free(key); return -1; } - key_type = ssh_key_type(key); switch (key_type) { case SSH_KEYTYPE_ECDSA_P256: @@ -2189,69 +2210,25 @@ ssh_bind_options_set(ssh_bind sshbind, "Unsupported key type %d", key_type); } - - if (bind_key_loc == NULL) { - ssh_key_free(key); - return -1; - } - - /* Set the location of the key on disk even though we don't - need it in case some other function wants it */ - rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); - if (rc < 0) { - ssh_key_free(key); - return -1; - } - ssh_key_free(*bind_key_loc); - *bind_key_loc = key; - } - break; - case SSH_BIND_OPTIONS_IMPORT_KEY: - if (value == NULL) { - ssh_set_error_invalid(sshbind); - return -1; - } else { - int key_type; - ssh_key *bind_key_loc = NULL; - ssh_key key = (ssh_key)value; - - allowed = ssh_bind_key_size_allowed(sshbind, key); - if (!allowed) { - ssh_set_error(sshbind, - SSH_FATAL, - "The host key size %d is too small.", - ssh_key_size(key)); - return -1; - } - - key_type = ssh_key_type(key); - switch (key_type) { - case SSH_KEYTYPE_ECDSA_P256: - case SSH_KEYTYPE_ECDSA_P384: - case SSH_KEYTYPE_ECDSA_P521: -#ifdef HAVE_ECC - bind_key_loc = &sshbind->ecdsa; -#else - ssh_set_error(sshbind, - SSH_FATAL, - "ECDSA key used and libssh compiled " - "without ECDSA support"); -#endif - break; - case SSH_KEYTYPE_RSA: - bind_key_loc = &sshbind->rsa; - break; - case SSH_KEYTYPE_ED25519: - bind_key_loc = &sshbind->ed25519; - break; - default: - ssh_set_error(sshbind, - SSH_FATAL, - "Unsupported key type %d", - key_type); + if (type == SSH_BIND_OPTIONS_RSAKEY || + type == SSH_BIND_OPTIONS_ECDSAKEY || + type == SSH_BIND_OPTIONS_HOSTKEY) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + /* Set the location of the key on disk even though we don't + need it in case some other function wants it */ + rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); + if (rc < 0) { + ssh_key_free(key); + return -1; + } + } else { + if (bind_key_loc == NULL) { + return -1; + } } - if (bind_key_loc == NULL) - return -1; ssh_key_free(*bind_key_loc); *bind_key_loc = key; } From 4edd0669fd4df44fc343f73a8ea54b5fef4d8a53 Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Tue, 9 Apr 2024 22:07:22 +0530 Subject: [PATCH 160/795] test: test coverage for SSH_BIND_OPTIONS_IMPORT_KEY_STR and ed25519 keys Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- tests/unittests/torture_options.c | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 2ccfa7af..a9c41cc9 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -2072,6 +2072,15 @@ torture_bind_options_import_key(void **state) assert_int_equal(rc, -1); SSH_KEY_FREE(key); + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); + /* set rsa key */ base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); @@ -2092,6 +2101,51 @@ torture_bind_options_import_key(void **state) #endif } +static void +torture_bind_options_import_key_str(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc; + const char *base64_key = ""; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, -1); + + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#endif +} + static void torture_bind_options_hostkey(void **state) { struct bind_st *test_state; @@ -2819,6 +2873,9 @@ torture_run_tests(void) cmocka_unit_test_setup_teardown(torture_bind_options_import_key, sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_import_key_str, + sshbind_setup, + sshbind_teardown), cmocka_unit_test_setup_teardown(torture_bind_options_hostkey, sshbind_setup, sshbind_teardown), From b500c2f0cf4472a7f9b060d54be0dff1f3c9ecda Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Mon, 25 Mar 2024 23:49:32 +0530 Subject: [PATCH 161/795] feat: add support for sftp extension "home-directory" Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 21 ++++++++++ src/sftp.c | 89 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index 754a54c3..4bda8c8f 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -1144,6 +1144,27 @@ LIBSSH_API int sftp_server_version(sftp_session sftp); */ LIBSSH_API char *sftp_expand_path(sftp_session sftp, const char *path); +/** + * @brief Get the specified user's home directory + * + * This calls the "home-directory" extension. You should check if the extension + * is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "home-directory", "1"); + * @endcode + * + * @param sftp The sftp session handle. + * + * @param username username of the user whose home directory is requested. + * + * @return On success, a newly allocated string containing the + * absolute real-path of the home directory of the user. + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_home_directory(sftp_session sftp, const char *username); + #ifdef WITH_SERVER /** * @brief Create a new sftp server session. diff --git a/src/sftp.c b/src/sftp.c index 76a9e13e..ba198f99 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -3100,4 +3100,93 @@ char *sftp_expand_path(sftp_session sftp, const char *path) return NULL; } +char * +sftp_home_directory(sftp_session sftp, const char *username) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "home-directory", + username ? username : ""); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + while (msg == NULL) { + rc = sftp_read_and_dispatch(sftp); + if (rc < 0) { + return NULL; + } + msg = sftp_dequeue(sftp, id); + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *homepath = NULL; + + rc = ssh_buffer_unpack(msg->payload, "ds", &ignored, &homepath); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to query user home directory"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return homepath; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { + ssh_set_error( + sftp->session, + SSH_FATAL, + "Received message %d when attempting to query user home directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + #endif /* WITH_SFTP */ From a9c998c080b0d6078c66b7a4b00960841b939bd1 Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Mon, 25 Mar 2024 23:51:38 +0530 Subject: [PATCH 162/795] test: add tests for sftp extension "home-directory" Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- tests/client/CMakeLists.txt | 1 + tests/client/torture_sftp_home_directory.c | 133 +++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 tests/client/torture_sftp_home_directory.c diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index c99c94ef..4021bcc7 100755 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -57,6 +57,7 @@ if (WITH_SFTP) torture_sftp_rename torture_sftp_expand_path torture_sftp_aio + torture_sftp_home_directory ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff --git a/tests/client/torture_sftp_home_directory.c b/tests/client/torture_sftp_home_directory.c new file mode 100644 index 00000000..e44c0091 --- /dev/null +++ b/tests/client/torture_sftp_home_directory.c @@ -0,0 +1,133 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_sftp_home_directory(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *home_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "home-directory", "1"); + if (!rc) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for NULL sftp session */ + home_path = sftp_home_directory(NULL, NULL); + assert_null(home_path); + + /* testing for ~ */ + /* + home_path = sftp_home_directory(t->sftp, NULL); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + + home_path = sftp_home_directory(t->sftp, ""); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + */ + + /* + OpenSSH code handling this extension does not handle empty string for + username. getpwnam() also does not handle empty string. + PR in OpenSSH for fix: + https://github.com/openssh/openssh-portable/pull/477/ + */ + + /* testing for ~user */ + home_path = sftp_home_directory(t->sftp, pwd->pw_name); + fprintf(stderr, + "sftp error: %d, ssh error: %s\n", + sftp_get_error(t->sftp), + ssh_get_error(t->sftp->session)); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_home_directory, + session_setup, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} From 095ab5ad61280eec906dbdbaab49c529c5b2e263 Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Tue, 16 Apr 2024 03:10:52 +0530 Subject: [PATCH 163/795] use internal-sftp for testing Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- tests/client/torture_sftp_home_directory.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/client/torture_sftp_home_directory.c b/tests/client/torture_sftp_home_directory.c index e44c0091..a2399f02 100644 --- a/tests/client/torture_sftp_home_directory.c +++ b/tests/client/torture_sftp_home_directory.c @@ -7,11 +7,19 @@ #include #include +#include #include static int sshd_setup(void **state) { + /* + The SFTP server used for testing is executed as a separate binary, which + is making the uid_wrapper lose information about what user is used, and + therefore, pwd is initialized to some bad value. + If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); torture_setup_sshd_server(state, false); return 0; } @@ -19,6 +27,7 @@ sshd_setup(void **state) static int sshd_teardown(void **state) { + unsetenv("TORTURE_SFTP_SERVER"); torture_teardown_sshd_server(state); return 0; } From 455d26a4793b00061ca6d549c860fa214d36eeb4 Mon Sep 17 00:00:00 2001 From: Debanga Sarma Date: Thu, 18 Apr 2024 02:30:21 +0530 Subject: [PATCH 164/795] parse count, longname and attrs fields of SSH_FXP_NAME message Signed-off-by: Debanga Sarma Reviewed-by: Jakub Jelen --- src/sftp.c | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/sftp.c b/src/sftp.c index ba198f99..a7ed5a75 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -3149,11 +3149,12 @@ sftp_home_directory(sftp_session sftp, const char *username) } if (msg->packet_type == SSH_FXP_NAME) { - uint32_t ignored = 0; + uint32_t count = 0; char *homepath = NULL; + char *longpath = NULL; + sftp_attributes attr = NULL; - rc = ssh_buffer_unpack(msg->payload, "ds", &ignored, &homepath); - sftp_message_free(msg); + rc = ssh_buffer_unpack(msg->payload, "ds", &count, &homepath); if (rc != SSH_OK) { ssh_set_error(sftp->session, SSH_ERROR, @@ -3161,7 +3162,44 @@ sftp_home_directory(sftp_session sftp, const char *username) sftp_set_error(sftp, SSH_FX_FAILURE); return NULL; } + /* + for SFTP version > 3, longname field in SSH_FXP_NAME is omitted. + */ + if (sftp->version <= 3) { + rc = ssh_buffer_unpack(msg->payload, "s", &longpath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to extract longname from payload"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + attr = sftp_parse_attr(sftp, msg->payload, 0); + if (attr == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + sftp_message_free(msg); + if (count != 1) { + if (count > 1) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Multiple results returned"); + } else { + ssh_set_error(sftp->session, SSH_ERROR, "No result returned"); + } + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (longpath) { + free(longpath); + } + sftp_attributes_free(attr); return homepath; } else if (msg->packet_type == SSH_FXP_STATUS) { status = parse_status_msg(msg); From 164ca9ae93b10288a47e3f6d705e9eb16793f4be Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 25 Apr 2024 15:35:17 +0200 Subject: [PATCH 165/795] libcrypto: Check return values in KDF handling Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- src/libcrypto.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libcrypto.c b/src/libcrypto.c index f45ffa96..33834dbd 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -169,13 +169,25 @@ int ssh_kdf(struct ssh_crypto_struct *crypto, #if OPENSSL_VERSION_NUMBER < 0x30000000L EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); #else - EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); - EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); - OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + EVP_KDF_CTX *ctx = NULL; + OSSL_PARAM_BLD *param_bld = NULL; OSSL_PARAM *params = NULL; - const char *md = sshkdf_digest_to_md(crypto->digest_type); + const char *md = NULL; + EVP_KDF *kdf = NULL; + md = sshkdf_digest_to_md(crypto->digest_type); + if (md == NULL) { + return -1; + } + + kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + if (kdf == NULL) { + return -1; + } + ctx = EVP_KDF_CTX_new(kdf); EVP_KDF_free(kdf); + + param_bld = OSSL_PARAM_BLD_new(); if (param_bld == NULL) { EVP_KDF_CTX_free(ctx); return -1; From 19e62a78a677c2617d663cf5df0b2db924842d58 Mon Sep 17 00:00:00 2001 From: Abdelrahman Youssef Date: Wed, 13 Mar 2024 16:02:18 +0200 Subject: [PATCH 166/795] sftp: Added lsetstat extension Signed-off-by: Abdelrahman Youssef Reviewed-by: Jakub Jelen --- include/libssh/sftp.h | 23 +++++++++++ src/sftp.c | 93 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h index 4bda8c8f..cf4458c3 100644 --- a/include/libssh/sftp.h +++ b/include/libssh/sftp.h @@ -948,6 +948,29 @@ LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char */ LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); +/** + * @brief This request is like setstat (excluding mode and size) but sets file + * attributes on symlinks themselves. + * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * + * @param sftp The sftp session handle. + * + * @param file The symbolic link which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr); + /** * @brief Change the file owner and group * diff --git a/src/sftp.c b/src/sftp.c index a7ed5a75..154de480 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -1893,6 +1893,10 @@ int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) sftp_status_message status = NULL; int rc; + if (sftp == NULL || file == NULL || attr == NULL) { + return -1; + } + buffer = ssh_buffer_new(); if (buffer == NULL) { ssh_set_error_oom(sftp->session); @@ -1967,6 +1971,95 @@ int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) return -1; } +int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + const char *extension_name = "lsetstat@openssh.com"; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + id = sftp_get_new_id(sftp); + + rc = ssh_buffer_pack(buffer, "dss", id, extension_name, file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + while (msg == NULL) { + if (sftp_read_and_dispatch(sftp) < 0) { + return -1; + } + msg = sftp_dequeue(sftp, id); + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to lsetstat", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + /* Change the file owner and group */ int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { struct sftp_attributes_struct attr; From fc451a8f3d111341256f9fb3d1443614f4a09f3a Mon Sep 17 00:00:00 2001 From: Abdelrahman yossef Date: Tue, 2 Apr 2024 16:40:06 +0200 Subject: [PATCH 167/795] fs_wrapper: added stat and lstat Signed-off-by: Abdelrahman Youssef Reviewed-by: Jakub Jelen --- tests/CMakeLists.txt | 14 ++-- tests/chown_wrapper.c | 21 ----- tests/fs_wrapper.c | 173 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 28 deletions(-) delete mode 100644 tests/chown_wrapper.c create mode 100644 tests/fs_wrapper.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46c19ff7..8165c308 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -244,15 +244,15 @@ if (CLIENT_TESTING OR SERVER_TESTING) set(CHROOT_WRAPPER "${CHROOT_WRAPPER_LIBRARY}") endif() - # chown wrapper - add_library(chown_wrapper SHARED chown_wrapper.c) - set(CHOWN_WRAPPER_LIBRARY - ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chown_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + # fs wrapper + add_library(fs_wrapper SHARED fs_wrapper.c) + set(FS_WRAPPER_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}fs_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) set(TEST_TARGET_LIBRARIES ${TEST_TARGET_LIBRARIES} - chown_wrapper + fs_wrapper ) - set(CHOWN_WRAPPER "${CHOWN_WRAPPER_LIBRARY}") + set(FS_WRAPPER "${FS_WRAPPER_LIBRARY}") # ssh_ping add_executable(ssh_ping ssh_ping.c) @@ -274,7 +274,7 @@ if (CLIENT_TESTING OR SERVER_TESTING) set(TORTURE_ENVIRONMENT - "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}:${CHOWN_WRAPPER}") + "LD_PRELOAD=${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}:${FS_WRAPPER}") if (priv_wrapper_FOUND) list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER=1 PRIV_WRAPPER_CHROOT_DISABLE=1) list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER_PRCTL_DISABLE="ALL" PRIV_WRAPPER_SETRLIMIT_DISABLE="ALL") diff --git a/tests/chown_wrapper.c b/tests/chown_wrapper.c deleted file mode 100644 index ee6910ed..00000000 --- a/tests/chown_wrapper.c +++ /dev/null @@ -1,21 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include - -typedef int (*__libc_chown)(const char *pathname, uid_t owner, gid_t group); - -/* silent gcc */ -int chown(const char *pathname, uid_t owner, gid_t group); - -int chown(const char *pathname, uid_t owner, gid_t group) -{ - __libc_chown original_chown; - if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { - /* fake it! */ - return 0; - } - - original_chown = (__libc_chown)dlsym(RTLD_NEXT, "chown"); - return (*original_chown)(pathname, owner, group); -} diff --git a/tests/fs_wrapper.c b/tests/fs_wrapper.c new file mode 100644 index 00000000..fd7e742a --- /dev/null +++ b/tests/fs_wrapper.c @@ -0,0 +1,173 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +void destructor(void) __attribute__((destructor)); + +struct file { + char *name; + uid_t uid; + gid_t gid; +} file = {0}; + +void +destructor(void) +{ + free(file.name); +} + +typedef int (*__libc_chown)(const char *pathname, uid_t owner, gid_t group); + +typedef int (*__libc_fchownat)(int dirfd, + const char *pathname, + uid_t owner, + gid_t group, + int flags); + +typedef int (*__libc_stat)(const char *pathname, struct stat *statbuf); + +typedef int (*__libc_xstat)(int ver, + const char *pathname, + struct stat *statbuf); + +typedef int (*__libc_lxstat)(int ver, + const char *pathname, + struct stat *statbuf); + +typedef int (*__libc_lstat)(const char *pathname, struct stat *statbuf); + +static int +chown_helper(const char *pathname, uid_t owner, gid_t group) +{ + if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { + /* + * The OpenSSH server modified the PTY which requires root permissions + * see torture_request_pty_modes + * */ + return 0; + } + if (strlen(pathname) > 4 && strncmp(pathname, "/tmp", 4) == 0) { + /* + * faking chown because It requires root permissions to modify the owner + * under /tmp + * It's also a helper for torture_sftp_setstat + * */ + if (file.name != NULL) { + free((char *)file.name); + } + file.name = strdup(pathname); + file.uid = owner; + file.gid = group; + return 0; + } + return -1; +} + +static void +stat_helper(const char *pathname, struct stat *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +/* silent gcc */ +int chown(const char *pathname, uid_t owner, gid_t group); + +int +chown(const char *pathname, uid_t owner, gid_t group) +{ + __libc_chown original_chown = NULL; + int rc; + + rc = chown_helper(pathname, owner, group); + if (rc == 0) { + return 0; + } + original_chown = (__libc_chown)dlsym(RTLD_NEXT, "chown"); + return (*original_chown)(pathname, owner, group); +} + +/* SFTP Server calls fchownat for symlinks */ +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags) +{ + __libc_fchownat original_fchownat = NULL; + int rc; + + rc = chown_helper(pathname, owner, group); + if (rc == 0) { + return 0; + } + + original_fchownat = (__libc_fchownat)dlsym(RTLD_NEXT, "fchownat"); + return (*original_fchownat)(dirfd, pathname, owner, group, flags); +} +int stat(const char *pathname, struct stat *statbuf); + +int +stat(const char *pathname, struct stat *statbuf) +{ + int rc; + __libc_stat original_stat = NULL; + + original_stat = (__libc_stat)dlsym(RTLD_NEXT, "stat"); + rc = (*original_stat)(pathname, statbuf); + stat_helper(pathname, statbuf); + + return rc; +} + +/* CentOS8 calls xstat */ +int __xstat(int ver, const char *pathname, struct stat *statbuf); + +int +__xstat(int ver, const char *pathname, struct stat *statbuf) +{ + int rc; + __libc_xstat original_xstat = NULL; + + original_xstat = (__libc_xstat)dlsym(RTLD_NEXT, "__xstat"); + rc = (*original_xstat)(ver, pathname, statbuf); + stat_helper(pathname, statbuf); + + return rc; +} + +int __lxstat(int ver, const char *pathname, struct stat *statbuf); + +int +__lxstat(int ver, const char *pathname, struct stat *statbuf) +{ + int rc; + __libc_lxstat original_lxstat = NULL; + + original_lxstat = (__libc_lxstat)dlsym(RTLD_NEXT, "__lxstat"); + rc = (*original_lxstat)(ver, pathname, statbuf); + stat_helper(pathname, statbuf); + + return rc; +} +int lstat(const char *pathname, struct stat *statbuf); + +int +lstat(const char *pathname, struct stat *statbuf) +{ + int rc; + __libc_lstat original_lstat = NULL; + + original_lstat = (__libc_lstat)dlsym(RTLD_NEXT, "lstat"); + rc = (*original_lstat)(pathname, statbuf); + stat_helper(pathname, statbuf); + + return rc; +} From efc11762321824fe0aa7127230207f4b21944bd5 Mon Sep 17 00:00:00 2001 From: Abdelrahman yossef Date: Tue, 2 Apr 2024 16:59:15 +0200 Subject: [PATCH 168/795] tests: setstat and lsetstat Signed-off-by: Abdelrahman Youssef Reviewed-by: Jakub Jelen --- tests/client/CMakeLists.txt | 1 + tests/client/torture_sftp_setstat.c | 373 ++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 tests/client/torture_sftp_setstat.c diff --git a/tests/client/CMakeLists.txt b/tests/client/CMakeLists.txt index 4021bcc7..70c92f13 100755 --- a/tests/client/CMakeLists.txt +++ b/tests/client/CMakeLists.txt @@ -58,6 +58,7 @@ if (WITH_SFTP) torture_sftp_expand_path torture_sftp_aio torture_sftp_home_directory + torture_sftp_setstat ${SFTP_BENCHMARK_TESTS}) endif (WITH_SFTP) diff --git a/tests/client/torture_sftp_setstat.c b/tests/client/torture_sftp_setstat.c new file mode 100644 index 00000000..f1d7b76b --- /dev/null +++ b/tests/client/torture_sftp_setstat.c @@ -0,0 +1,373 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "libssh/sftp.h" +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup_setstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char name[128] = {0}; + const char *test_1 = "l&setstat_test\n"; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + t = s->ssh.tsftp; + + snprintf(name, sizeof(name), "%s/libssh_sftp_setstat_test", t->testdir); + torture_write_file(name, test_1); + s->private_data = name; + + return 0; +} + +static int +session_setup_lsetstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char path[128] = {0}; + const char *test_1 = "lsetstat_test_1\n"; + int rc; + + char tmp_file[128] = {0}; + + 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); + + t = s->ssh.tsftp; + + rc = sftp_extension_supported(t->sftp, "lsetstat@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", t->testdir); + torture_write_file(tmp_file, test_1); + + snprintf(path, sizeof(path), "%s/linkname", t->testdir); + rc = symlink(tmp_file, path); + assert_int_equal(rc, SSH_OK); + s->private_data = path; + + return 0; +} +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/*setstat tests*/ +static void +torture_sftp_setstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct sftp_attributes_struct attr; + struct passwd *pwd = NULL; + sftp_attributes tmp_attr = NULL; + const char *name = (char *)s->private_data; + int rc; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + tmp_attr = sftp_stat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_setstat_size(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + size_t len = 30; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE; + attr.size = len; + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(len, sb.st_size); +} + +static void +torture_sftp_setstat_chmod(void **state) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + attr.permissions = mode; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sb.st_mode & ACCESSPERMS, mode); +} + +static void +torture_sftp_setstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_setstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE; + + /* testing null sftp */ + rc = sftp_setstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing non-existing file */ + rc = sftp_setstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_setstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +/*lsetstat tests*/ +static void +torture_sftp_lsetstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_lstat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_lsetstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = lstat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_lsetstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID; + + /* testing non-existing file */ + rc = sftp_lsetstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_lsetstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* testing null sftp */ + rc = sftp_lsetstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chown, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chmod, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_utimes, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_size, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_negative, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_utimes, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_chown, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_negative, + session_setup_lsetstat, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + return rc; +} From 3227a4cae09a4b03d8c2c00afcc00148e8ddb942 Mon Sep 17 00:00:00 2001 From: Abdelrahman Youssef Date: Tue, 16 Apr 2024 14:50:40 +0200 Subject: [PATCH 169/795] use internal-sftp Signed-off-by: Abdelrahman Youssef Reviewed-by: Jakub Jelen --- tests/client/torture_sftp_setstat.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/client/torture_sftp_setstat.c b/tests/client/torture_sftp_setstat.c index f1d7b76b..fb0ce95d 100644 --- a/tests/client/torture_sftp_setstat.c +++ b/tests/client/torture_sftp_setstat.c @@ -15,6 +15,13 @@ static int sshd_setup(void **state) { + /* + * Without root permissions, the exec-ed SFTP server does not inherit some + * wrappers so we use internal-sftp for this test, which does not have this + * issue. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); return 0; } @@ -22,6 +29,7 @@ sshd_setup(void **state) static int sshd_teardown(void **state) { + unsetenv("TORTURE_SFTP_SERVER"); torture_teardown_sshd_server(state); return 0; } From 46a28cfc490d8c6bbd30a1cba6496502726f8ec4 Mon Sep 17 00:00:00 2001 From: Diego Roux <9531300-diegoroux@users.noreply.gitlab.com> Date: Wed, 27 Mar 2024 14:31:11 +0000 Subject: [PATCH 170/795] log: fixes legacy fallback for multiple sessions. Legacy code in 'ssh_set_callbacks' will fallback to 'ssh_legacy_log_callback' (if the current log cb is NULL) setting the user data to the current session. However, if any other session is created afterwards, it won't update the user data with the new session, potentially leading to a use-after-free. Fixes #238. Signed-off-by: Diego Roux Reviewed-by: Jakub Jelen --- include/libssh/priv.h | 4 ++ src/callbacks.c | 9 ++++ src/log.c | 6 +++ src/session.c | 2 + tests/client/torture_connect.c | 84 ++++++++++++++++++++++++++--- tests/unittests/torture_callbacks.c | 13 +++-- 6 files changed, 108 insertions(+), 10 deletions(-) diff --git a/include/libssh/priv.h b/include/libssh/priv.h index bfef771d..4434d143 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -274,6 +274,10 @@ void ssh_log_common(struct ssh_common_struct *common, const char *function, const char *format, ...) PRINTF_ATTRIBUTE(4, 5); +void _ssh_remove_legacy_log_cb(void); + +/* log.c */ +void _ssh_reset_log_cb(void); /* ERROR HANDLING */ diff --git a/src/callbacks.c b/src/callbacks.c index 3ed2f11c..cea4301a 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -45,6 +45,15 @@ static void ssh_legacy_log_callback(int priority, log_fn(session, priority, buffer, log_data); } +void +_ssh_remove_legacy_log_cb(void) +{ + if (ssh_get_log_callback() == ssh_legacy_log_callback) { + _ssh_reset_log_cb(); + ssh_set_log_userdata(NULL); + } +} + int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) { if (session == NULL || cb == NULL) { return SSH_ERROR; diff --git a/src/log.c b/src/log.c index 5bae18b8..bef65a84 100644 --- a/src/log.c +++ b/src/log.c @@ -221,6 +221,12 @@ int ssh_set_log_callback(ssh_logging_callback cb) { return SSH_OK; } +void +_ssh_reset_log_cb(void) +{ + ssh_log_cb = NULL; +} + ssh_logging_callback ssh_get_log_callback(void) { return ssh_log_cb; } diff --git a/src/session.c b/src/session.c index 279352b6..b9efc6fa 100644 --- a/src/session.c +++ b/src/session.c @@ -358,6 +358,8 @@ void ssh_free(ssh_session session) } } + _ssh_remove_legacy_log_cb(); + /* burn connection, it could contain sensitive data */ explicit_bzero(session, sizeof(struct ssh_session_struct)); SAFE_FREE(session); diff --git a/tests/client/torture_connect.c b/tests/client/torture_connect.c index f086488d..fd3e3d32 100644 --- a/tests/client/torture_connect.c +++ b/tests/client/torture_connect.c @@ -258,18 +258,90 @@ static void torture_connect_uninitialized(UNUSED_PARAM(void **state)) ssh_free(session); } +static void +internal_log(ssh_session session, + int priority, + const char *message, + void *userdata) +{ + (void)session; + (void)priority; + (void)message; + (void)userdata; + + return; +} + +static void +torture_legacy_callback(void **state) +{ + struct ssh_callbacks_struct cb[2] = {0}; + int rc, verbosity = SSH_LOG_WARNING; + ssh_session session = NULL; + + /* unused. */ + (void)state; + + /* + * Legacy code in 'ssh_set_callbacks' used to + * create the conditions for a use-after-free + * issue, in multi-session programs, by failing + * to update a pointer with the new session. + * + * To verify it won't happen again, this test + * creates two consecutive sessions and frees + * them; if any fault occurs then the pointer + * remained at the previous session, failing + * to be updated. + */ + for (int i = 0; i < 2; i++) { + session = ssh_new(); + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(session, rc); + + cb[i].log_function = internal_log; + + ssh_callbacks_init(&cb[i]); + ssh_set_callbacks(session, &cb[i]); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + ssh_free(session); + } +} + int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_connect_peer_discon_msg, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_connect_nonblocking, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_connect_ipv6, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_connect_double, session_setup, session_teardown), - cmocka_unit_test_setup_teardown(torture_connect_failure, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_peer_discon_msg, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_ipv6, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_double, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_failure, + session_setup, + session_teardown), #if 0 cmocka_unit_test_setup_teardown(torture_connect_timeout, session_setup, session_teardown), #endif - cmocka_unit_test_setup_teardown(torture_connect_socket, session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_socket, + session_setup, + session_teardown), + cmocka_unit_test(torture_legacy_callback), cmocka_unit_test(torture_connect_uninitialized), }; diff --git a/tests/unittests/torture_callbacks.c b/tests/unittests/torture_callbacks.c index 25111b2f..ac5b4b1b 100644 --- a/tests/unittests/torture_callbacks.c +++ b/tests/unittests/torture_callbacks.c @@ -3,9 +3,10 @@ #define LIBSSH_STATIC #include "torture.h" -#include +#include #include #include +#include static int myauthcallback (const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) { @@ -249,11 +250,15 @@ static void torture_callbacks_iterate(void **state){ int torture_run_tests(void) { int rc; struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(torture_callbacks_size, setup, teardown), - cmocka_unit_test_setup_teardown(torture_callbacks_exists, setup, teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_size, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_exists, + setup, + teardown), cmocka_unit_test(torture_log_callback), cmocka_unit_test(torture_callbacks_execute_list), - cmocka_unit_test(torture_callbacks_iterate) + cmocka_unit_test(torture_callbacks_iterate), }; ssh_init(); From 3577eea324f3397b812ba2309c5e3ebc4e6d3700 Mon Sep 17 00:00:00 2001 From: Feynman-young Date: Thu, 11 Apr 2024 13:03:17 +0800 Subject: [PATCH 171/795] Add ssh_set_error_invalid in ssh_options_set(). Add ssh_set_error_invalid in ssh_options_set with case SSH_OPTIONS_HOST after ssh_config_parse_uri returns error. Signed-off-by: Wenjie Yang Reviewed-by: Jakub Jelen --- src/options.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/options.c b/src/options.c index 61a7e4a7..b81ac97f 100644 --- a/src/options.c +++ b/src/options.c @@ -637,6 +637,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, char *username = NULL, *hostname = NULL; rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); if (rc != SSH_OK) { + ssh_set_error_invalid(session); return -1; } if (username != NULL) { From cbabc72555c41610e34b0f0fe1d40c32947c1f43 Mon Sep 17 00:00:00 2001 From: Feynman-young Date: Thu, 11 Apr 2024 13:04:55 +0800 Subject: [PATCH 172/795] Add an error handler unittest for ssh_options_set(). Add an error handler unittest for ssh_options_set with case SSH_OPTIONS_HOST when ssh_config_parse_uri returns error. Signed-off-by: Wenjie Yang Reviewed-by: Jakub Jelen --- tests/unittests/torture_options.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index a9c41cc9..5b007fa0 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -88,6 +88,8 @@ static void torture_options_set_host(void **state) { /* disallow metacharacters in the username */ rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); + assert_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); assert_ssh_return_code_equal(session, rc, SSH_ERROR); } From 2e4a9e3f7b979fcd0fbbe814cd7acbcec9d10fe3 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 23 Apr 2024 21:07:17 +0200 Subject: [PATCH 173/795] libgcrypt: Initialize pointers Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki_gcrypt.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 65bb77e6..10e7c59f 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -153,7 +153,7 @@ static ssh_string asn1_get_bit_string(ssh_buffer buffer) ssh_string str; unsigned char type; uint32_t size; - unsigned char unused, last, *p; + unsigned char unused, last, *p = NULL; uint32_t len; len = ssh_buffer_get_data(buffer, &type, 1); @@ -412,10 +412,10 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, ssh_auth_callback cb, void *userdata, const char *desc) { ssh_buffer buffer = NULL; ssh_buffer out = NULL; - const char *p; + const char *p = NULL; unsigned char *iv = NULL; - const char *header_begin; - const char *header_end; + const char *header_begin = NULL; + const char *header_end = NULL; unsigned int header_begin_size; unsigned int header_end_size; unsigned int key_len = 0; @@ -637,8 +637,8 @@ static int b64decode_rsa_privatekey(const char *pkey, gcry_sexp_t *r, #ifdef HAVE_GCRYPT_ECC static int pki_key_ecdsa_to_nid(gcry_sexp_t k) { - gcry_sexp_t sexp; - const char *tmp; + gcry_sexp_t sexp = NULL; + const char *tmp = NULL; size_t size; sexp = gcry_sexp_find_token(k, "curve", 0); @@ -786,7 +786,7 @@ static int b64decode_ecdsa_privatekey(const char *pkey, gcry_sexp_t *r, void *userdata, const char *desc) { - const unsigned char *data; + const unsigned char *data = NULL; ssh_buffer buffer = NULL; gcry_error_t err = 0; ssh_string v = NULL; @@ -1070,7 +1070,7 @@ int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) ssh_key pki_key_dup(const ssh_key key, int demote) { - ssh_key new; + ssh_key new = NULL; gcry_error_t err = 0; int rc; @@ -1259,9 +1259,9 @@ static int _bignum_cmp(const gcry_sexp_t s1, const gcry_sexp_t s2, const char *what) { - gcry_sexp_t sexp; - bignum b1; - bignum b2; + gcry_sexp_t sexp = NULL; + bignum b1 = NULL; + bignum b2 = NULL; int result; sexp = gcry_sexp_find_token(s1, what, 0); @@ -1368,8 +1368,8 @@ int pki_key_compare(const ssh_key k1, ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) { - ssh_buffer buffer; - ssh_string type_s; + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; ssh_string str = NULL; ssh_string e = NULL; ssh_string n = NULL; @@ -1627,7 +1627,7 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) { const char *s = NULL; /* used in RSA */ - gcry_sexp_t sexp; + gcry_sexp_t sexp = NULL; size_t size = 0; ssh_string sig_blob = NULL; int rc; @@ -1732,7 +1732,7 @@ ssh_signature pki_signature_from_blob(const ssh_key pubkey, enum ssh_keytypes_e type, enum ssh_digest_e hash_type) { - ssh_signature sig; + ssh_signature sig = NULL; gcry_error_t err; size_t len; size_t rsalen; @@ -1894,8 +1894,8 @@ ssh_signature pki_do_sign_hash(const ssh_key privkey, enum ssh_digest_e hash_type) { const char *hash_c = NULL; - ssh_signature sig; - gcry_sexp_t sexp; + ssh_signature sig = NULL; + gcry_sexp_t sexp = NULL; gcry_error_t err; sig = ssh_signature_new(); @@ -2071,7 +2071,7 @@ int pki_verify_data_signature(ssh_signature signature, size_t input_len) { const char *hash_type = NULL; - gcry_sexp_t sexp; + gcry_sexp_t sexp = NULL; gcry_error_t err; unsigned char ghash[SHA512_DIGEST_LEN + 1] = {0}; From dceb17d2ad379dcf2130ddc2216b0ffd3440bcb4 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 23 Apr 2024 21:15:08 +0200 Subject: [PATCH 174/795] libgcrypt: Reformat Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki_gcrypt.c | 193 ++++++++++++++++++++++++++--------------------- 1 file changed, 109 insertions(+), 84 deletions(-) diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 10e7c59f..6c498c5b 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -548,90 +548,109 @@ static ssh_buffer privatekey_string_to_buffer(const char *pkey, int type, return out; } -static int b64decode_rsa_privatekey(const char *pkey, gcry_sexp_t *r, - ssh_auth_callback cb, void *userdata, const char *desc) { - const unsigned char *data; - ssh_string n = NULL; - ssh_string e = NULL; - ssh_string d = NULL; - ssh_string p = NULL; - ssh_string q = NULL; - ssh_string unused1 = NULL; - ssh_string unused2 = NULL; - ssh_string u = NULL; - ssh_string v = NULL; - ssh_buffer buffer = NULL; - int rc = 1; - - buffer = privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, cb, userdata, desc); - if (buffer == NULL) { - return 0; - } +static int +b64decode_rsa_privatekey(const char *pkey, + gcry_sexp_t *r, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + const unsigned char *data = NULL; + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string unused1 = NULL; + ssh_string unused2 = NULL; + ssh_string u = NULL; + ssh_string v = NULL; + ssh_buffer buffer = NULL; + int rc = 1; + gcry_error_t rv = 0; - if (!asn1_check_sequence(buffer)) { - SSH_BUFFER_FREE(buffer); - return 0; - } + buffer = privatekey_string_to_buffer(pkey, + SSH_KEYTYPE_RSA, + cb, + userdata, + desc); + if (buffer == NULL) { + return 0; + } - v = asn1_get_int(buffer); - if (v == NULL) { - SSH_BUFFER_FREE(buffer); - return 0; - } + if (!asn1_check_sequence(buffer)) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + v = asn1_get_int(buffer); + if (v == NULL) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 0) { + SSH_STRING_FREE(v); + SSH_BUFFER_FREE(buffer); + return 0; + } + + n = asn1_get_int(buffer); + e = asn1_get_int(buffer); + d = asn1_get_int(buffer); + q = asn1_get_int(buffer); + p = asn1_get_int(buffer); + unused1 = asn1_get_int(buffer); + unused2 = asn1_get_int(buffer); + u = asn1_get_int(buffer); - data = ssh_string_data(v); - if (ssh_string_len(v) != 1 || data[0] != 0) { - SSH_STRING_FREE(v); SSH_BUFFER_FREE(buffer); - return 0; - } - n = asn1_get_int(buffer); - e = asn1_get_int(buffer); - d = asn1_get_int(buffer); - q = asn1_get_int(buffer); - p = asn1_get_int(buffer); - unused1 = asn1_get_int(buffer); - unused2 = asn1_get_int(buffer); - u = asn1_get_int(buffer); - - SSH_BUFFER_FREE(buffer); - - if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || - unused1 == NULL || unused2 == NULL|| u == NULL) { - rc = 0; - goto error; - } + if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || + unused1 == NULL || unused2 == NULL || u == NULL) { + rc = 0; + goto error; + } - if (gcry_sexp_build(r, NULL, - "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", - ssh_string_len(n), ssh_string_data(n), - ssh_string_len(e), ssh_string_data(e), - ssh_string_len(d), ssh_string_data(d), - ssh_string_len(p), ssh_string_data(p), - ssh_string_len(q), ssh_string_data(q), - ssh_string_len(u), ssh_string_data(u))) { - rc = 0; - } + rv = gcry_sexp_build( + r, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(u), + ssh_string_data(u)); + if (rv) { + rc = 0; + } error: - ssh_string_burn(n); - SSH_STRING_FREE(n); - ssh_string_burn(e); - SSH_STRING_FREE(e); - ssh_string_burn(d); - SSH_STRING_FREE(d); - ssh_string_burn(p); - SSH_STRING_FREE(p); - ssh_string_burn(q); - SSH_STRING_FREE(q); - SSH_STRING_FREE(unused1); - SSH_STRING_FREE(unused2); - ssh_string_burn(u); - SSH_STRING_FREE(u); - SSH_STRING_FREE(v); - - return rc; + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + SSH_STRING_FREE(unused1); + SSH_STRING_FREE(unused2); + ssh_string_burn(u); + SSH_STRING_FREE(u); + SSH_STRING_FREE(v); + + return rc; } #ifdef HAVE_GCRYPT_ECC @@ -1200,16 +1219,20 @@ ssh_key pki_key_dup(const ssh_key key, int demote) return new; } -static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int type){ - gcry_sexp_t params; +static int +pki_key_generate(ssh_key key, int parameter, const char *type_s, int type) +{ + gcry_sexp_t params = NULL; int rc; rc = gcry_sexp_build(¶ms, - NULL, - "(genkey(%s(nbits %d)(transient-key)))", - type_s, - parameter); - if (rc != 0) + NULL, + "(genkey(%s(nbits %d)(transient-key)))", + type_s, + parameter); + if (rc != 0) { return SSH_ERROR; + } + switch (type) { case SSH_KEYTYPE_RSA: rc = gcry_pk_genkey(&key->rsa, params); @@ -1228,7 +1251,9 @@ static int pki_key_generate(ssh_key key, int parameter, const char *type_s, int return SSH_OK; } -int pki_key_generate_rsa(ssh_key key, int parameter){ +int +pki_key_generate_rsa(ssh_key key, int parameter) +{ return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); } From 89c53e1962bbff7b8866c09895489eaaca736f39 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Wed, 17 Apr 2024 10:49:31 +0200 Subject: [PATCH 175/795] libgcrypt: Prevent signature blob to start with 1 bit This should prevent the long standing random failures of libgcrypt pipeline. I was recently able to reproduce it only with dropbear, which sounds like choking on the signature starting with bit 1, possibly interpretting it as a negative value. Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki_gcrypt.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pki_gcrypt.c b/src/pki_gcrypt.c index 6c498c5b..8aec75e9 100644 --- a/src/pki_gcrypt.c +++ b/src/pki_gcrypt.c @@ -1664,7 +1664,13 @@ ssh_string pki_signature_to_blob(const ssh_signature sig) return NULL; } s = gcry_sexp_nth_data(sexp, 1, &size); - if (*s == 0) { + + /* + * Remove leading zeroes, but only the ones that do not make the MPI + * representation look like a negative value (first bit is one), + * which might confuse some implementations. + */ + while (size > 1 && s[0] == 0 && (s[1] & 0x80) == 0) { size--; s++; } From 7f442afd5748d9791e9821d571b6a9561ae2c036 Mon Sep 17 00:00:00 2001 From: Wenjie Yang Date: Wed, 1 May 2024 14:22:12 +0800 Subject: [PATCH 176/795] Fix missing memory free functions in pki_key_to_blob(). Signed-off-by: Wenjie Yang Reviewed-by: Sahana Prasad Reviewed-by: Jakub Jelen --- src/pki_crypto.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pki_crypto.c b/src/pki_crypto.c index f4ce8bdf..77a0695f 100644 --- a/src/pki_crypto.c +++ b/src/pki_crypto.c @@ -1509,6 +1509,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) RSA_get0_factors(key_rsa, &bp, &bq); RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp); #else + OSSL_PARAM_free(params); rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); if (rc != 1) { goto fail; @@ -1755,6 +1756,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) e = NULL; if (type == SSH_KEY_PRIVATE) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); if (rc < 0) { goto fail; @@ -1797,6 +1799,7 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) } } #if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); OSSL_PARAM_free(params); #endif /* OPENSSL_VERSION_NUMBER */ break; From 917032029862a7111b2c1d2f6d7a6fc56131618f Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Tue, 7 May 2024 17:46:14 +0200 Subject: [PATCH 177/795] ci: Update tags for shared linux and windows runners Use the Windows tags from the following article: https://docs.gitlab.com/ee/ci/runners/hosted_runners/windows.html The Windows runner are now extremely slow so moving them out of the pipeline/dependency chain. The Linux tags were removed with GitLab 17.0. But we need to use the new tags to avoid the generic jobs being picked up by specific runners, such as freebsd. https://about.gitlab.com/blog/2023/08/15/removing-tags-from-small-saas-runner-on-linux/ https://docs.gitlab.com/ee/update/deprecations.html#removal-of-tags-from-small-saas-runners-on-linux Signed-off-by: Jakub Jelen --- .gitlab-ci.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e9a9f396..3ad5a985 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,7 +47,7 @@ workflow: make -j$(nproc) install # Do not use after_script as it does not make the targets fail tags: - - shared + - saas-linux-small-amd64 only: - merge_requests - branches @@ -115,7 +115,7 @@ review: # the format is not always matching our intentions allow_failure: true tags: - - shared + - saas-linux-small-amd64 only: - merge_requests @@ -353,7 +353,7 @@ fedora/mingw32: export CI_COMMIT_RANGE="$CI_COMMIT_BEFORE_SHA..$CI_COMMIT_SHA" tags: - - shared + - saas-linux-small-amd64 except: - tags only: @@ -521,8 +521,12 @@ freebsd/openssl_1.1.1/x86_64: ############################################################################### # Visual Studio builds # ############################################################################### +# 2024-05-13: These jobs run out of the stages as they take extremely long and +# usually timeout with the update to Gitlab 17.0 .vs: - stage: test + stage: analysis + needs: [] + allow_failure: true cache: key: vcpkg.${CI_JOB_NAME} paths: @@ -533,8 +537,7 @@ freebsd/openssl_1.1.1/x86_64: - cmake --build . - ctest --output-on-failure tags: - - windows - - shared-windows + - saas-windows-medium-amd64 only: - merge_requests - branches @@ -601,7 +604,7 @@ coverity: --form description="CI build" https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME tags: - - shared + - saas-linux-small-amd64 only: refs: - master From 8577f588c3cabb84cdd75a03472da8cba2679213 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 4 Apr 2024 17:43:16 +0200 Subject: [PATCH 178/795] tests: Support logging into separate file for exec-ed libssh test server Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider --- tests/server/test_server/main.c | 24 ++++++++++++++++++++++++ tests/server/torture_server.c | 9 +++++++++ tests/torture.c | 7 +++++-- tests/torture.h | 1 + 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/server/test_server/main.c b/tests/server/test_server/main.c index 39c01223..97ad8b80 100644 --- a/tests/server/test_server/main.c +++ b/tests/server/test_server/main.c @@ -59,6 +59,7 @@ struct arguments_st { char *password; char *config_file; + char *log_file; bool with_global_config; char *pid_file; }; @@ -84,6 +85,7 @@ static void free_arguments(struct arguments_st *arguments) SAFE_FREE(arguments->username); SAFE_FREE(arguments->password); SAFE_FREE(arguments->config_file); + SAFE_FREE(arguments->log_file); SAFE_FREE(arguments->pid_file); end: @@ -174,6 +176,7 @@ static void print_server_state(struct server_state_st *state) state->parse_global_config? "TRUE": "FALSE"); printf("config_file = %s\n", state->config_file? state->config_file: "NULL"); + printf("log_file = %s\n", state->log_file ? state->log_file : "NULL"); printf("=================================================\n"); } } @@ -297,6 +300,11 @@ static int init_server_state(struct server_state_st *state, arguments->config_file = NULL; } + if (arguments->log_file) { + state->log_file = arguments->log_file; + arguments->log_file = NULL; + } + /* TODO make configurable */ state->max_tries = 3; state->error = 0; @@ -440,6 +448,14 @@ static struct argp_option options[] = { .doc = "Use this server configuration file.", .group = 0 }, + { + .name = "log_file", + .key = 'l', + .arg = "LOG_FILE", + .flags = 0, + .doc = "Output log to this file.", + .group = 0 + }, { .name = NULL } }; @@ -553,6 +569,14 @@ static error_t parse_opt (int key, char *arg, struct argp_state *state) goto end; } break; + case 'l': + arguments->log_file = strdup(arg); + if (arguments->log_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; case ARGP_KEY_ARG: if (state->arg_num >= 1) { /* Too many arguments. */ diff --git a/tests/server/torture_server.c b/tests/server/torture_server.c index f6b9dea4..c89171e6 100644 --- a/tests/server/torture_server.c +++ b/tests/server/torture_server.c @@ -54,6 +54,8 @@ static int libssh_server_setup(void **state) struct test_server_st *tss = NULL; struct torture_state *s = NULL; + char log_file[1024]; + assert_non_null(state); tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); @@ -62,6 +64,13 @@ static int libssh_server_setup(void **state) torture_setup_socket_dir((void **)&s); torture_setup_create_libssh_config((void **)&s); + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + s->log_file = strdup(log_file); + /* The second argument is the relative path to the "server" directory binary */ torture_setup_libssh_server((void **)&s, "./test_server/test_server"); diff --git a/tests/torture.c b/tests/torture.c index 78edaae1..22854e8a 100644 --- a/tests/torture.c +++ b/tests/torture.c @@ -1020,10 +1020,12 @@ void torture_setup_libssh_server(void **state, const char *server_path) /* Write the start command */ printed = snprintf(start_cmd, sizeof(start_cmd), "%s" - "%s -f%s -v4 -p22 -i%s -C%s%s%s", + "%s -f%s -v4 -p22 -i%s -C%s%s%s%s%s", timeout_cmd, server_path, s->pcap_file, s->srv_pidfile, - s->srv_config, extra_options, TORTURE_SSH_SERVER); + s->srv_config, + s->log_file ? " -l " : "", s->log_file ? s->log_file : "", + extra_options, TORTURE_SSH_SERVER); if (printed < 0) { fail_msg("Failed to print start command!"); /* Unreachable */ @@ -1116,6 +1118,7 @@ void torture_free_state(struct torture_state *s) free(s->srv_config); free(s->socket_dir); free(s->pcap_file); + free(s->log_file); free(s->srv_pidfile); free(s->srv_additional_config); free(s); diff --git a/tests/torture.h b/tests/torture.h index eb2765da..cf947108 100644 --- a/tests/torture.h +++ b/tests/torture.h @@ -68,6 +68,7 @@ struct torture_sftp { struct torture_state { char *socket_dir; char *pcap_file; + char *log_file; char *srv_pidfile; char *srv_config; bool srv_pam; From 081a59371b2292c051bfb3c986ff78efa95d5e5a Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 13 May 2024 09:56:37 +0200 Subject: [PATCH 179/795] server: Introduce ssh_send_disconnect() This will only send the disconnect message and close the socket. We should not free any memory here. This should be done by the server implementation. Pair-Programmed-With: Jakub Jelen Signed-off-by: Andreas Schneider Signed-off-by: Jakub Jelen --- src/messages.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/messages.c b/src/messages.c index be9462ac..72c0aacc 100644 --- a/src/messages.c +++ b/src/messages.c @@ -40,8 +40,8 @@ #include "libssh/session.h" #include "libssh/misc.h" #include "libssh/pki.h" -#include "libssh/dh.h" #include "libssh/messages.h" +#include "libssh/socket.h" #ifdef WITH_SERVER #include "libssh/server.h" #include "libssh/gssapi.h" @@ -97,6 +97,41 @@ static int ssh_message_reply_default(ssh_message msg) { #endif +static int ssh_send_disconnect(ssh_session session) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye"); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + ssh_session_socket_close(session); + } + + return rc; +} + #ifdef WITH_SERVER static int ssh_execute_server_request(ssh_session session, ssh_message msg) @@ -303,7 +338,7 @@ static int ssh_execute_server_request(ssh_session session, ssh_message msg) if (rc == 0) { ssh_message_reply_default(msg); } else { - ssh_disconnect(session); + ssh_send_disconnect(session); } return SSH_OK; @@ -1182,7 +1217,7 @@ SSH_PACKET_CALLBACK(ssh_packet_channel_open){ ssh_session_set_disconnect_message(session, "No more sessions allowed!"); ssh_set_error(session, SSH_FATAL, "No more sessions allowed!"); session->session_state = SSH_SESSION_STATE_ERROR; - ssh_disconnect(session); + ssh_send_disconnect(session); goto error; } From 649f3810299b297562b168504562242e8cdc1bf6 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 16:53:26 +0100 Subject: [PATCH 180/795] cmake: Rename torture_server test This makes it easier to select it as a single test with: `ctest -R torture_server_default*` Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/server/CMakeLists.txt | 2 +- tests/server/{torture_server.c => torture_server_default.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/server/{torture_server.c => torture_server_default.c} (100%) diff --git a/tests/server/CMakeLists.txt b/tests/server/CMakeLists.txt index f741e95a..4f2dd6e2 100644 --- a/tests/server/CMakeLists.txt +++ b/tests/server/CMakeLists.txt @@ -7,7 +7,7 @@ find_package(socket_wrapper) add_subdirectory(test_server) set(LIBSSH_SERVER_TESTS - torture_server + torture_server_default torture_server_auth_kbdint torture_server_config torture_server_algorithms diff --git a/tests/server/torture_server.c b/tests/server/torture_server_default.c similarity index 100% rename from tests/server/torture_server.c rename to tests/server/torture_server_default.c From 8aa808a6001460e656a9e78b31ddb9428ee4a390 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 4 Apr 2024 18:01:24 +0200 Subject: [PATCH 181/795] include: Introduce a SSH_CHANNEL_FREE() macro Signed-off-by: Andreas Schneider --- include/libssh/libssh.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 13f1b812..0f6b6813 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -454,6 +454,13 @@ LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); LIBSSH_API int ssh_channel_close(ssh_channel channel); +#define SSH_CHANNEL_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_channel_free(x); \ + (x) = NULL; \ + } \ + } while (0) LIBSSH_API void ssh_channel_free(ssh_channel channel); LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); From 765597e31f7b79267dbe53a9a8e01ae8c12bf4e8 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 26 Feb 2024 14:01:41 +0100 Subject: [PATCH 182/795] tests:client: We need to set channel to NULL after we freed it This fixes an invalid memory read in ssh_channel_get_exit_status() below. Signed-off-by: Andreas Schneider --- tests/client/torture_session.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index 8a43e586..57f53148 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -434,7 +434,7 @@ static void torture_freed_channel_get_exit_status(void **state) (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { channel_freed = true; } - ssh_channel_free(channel); + SSH_CHANNEL_FREE(channel); if (!channel_freed) { rc = ssh_channel_get_exit_status(channel); From d7bfbebad61cdaaff0fc664bf6d1a8766438e724 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 14:55:58 +0100 Subject: [PATCH 183/795] tests:client: Add test for exit_status Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/client/torture_session.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index 57f53148..e6c10680 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -397,6 +397,33 @@ static void torture_freed_channel_read_nonblocking(void **state) 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]; + int exit_status = -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_status(channel); + assert_int_equal(exit_status, 0); +} + + /* Ensure that calling 'ssh_channel_get_exit_status' on a freed channel does not * lead to segmentation faults. */ static void torture_freed_channel_get_exit_status(void **state) @@ -540,6 +567,9 @@ int torture_run_tests(void) { 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_freed_channel_get_exit_status, session_setup, session_teardown), From 3ce68badcad63a8a27f40b232771c309b53fec61 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 15:32:58 +0100 Subject: [PATCH 184/795] channels: Reformat ssh_channel_exit_status_termination() Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/channels.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/channels.c b/src/channels.c index 9e613715..27ec7c5f 100644 --- a/src/channels.c +++ b/src/channels.c @@ -3359,14 +3359,15 @@ ssh_session ssh_channel_get_session(ssh_channel channel) static int ssh_channel_exit_status_termination(void *c) { - ssh_channel channel = c; - if(channel->exit_status != -1 || - /* When a channel is closed, no exit status message can - * come anymore */ - (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || - channel->session->session_state == SSH_SESSION_STATE_ERROR) - return 1; - else + ssh_channel channel = c; + if (channel->exit_status != -1 || + /* When a channel is closed, no exit status message can + * come anymore */ + (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + { + return 1; + } return 0; } From bc1acb53120c8ba886f62530ee814e0aec094b5c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 14:44:58 +0100 Subject: [PATCH 185/795] channels: Make exit_status and uint32_t This is what we get in the packet and is defined in RFC4254. Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- include/libssh/channels.h | 2 +- src/channels.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/libssh/channels.h b/include/libssh/channels.h index cb2bea43..d80e0dd6 100644 --- a/include/libssh/channels.h +++ b/include/libssh/channels.h @@ -80,7 +80,7 @@ struct ssh_channel_struct { ssh_buffer stdout_buffer; ssh_buffer stderr_buffer; void *userarg; - int exit_status; + uint32_t exit_status; enum ssh_channel_request_state_e request_state; struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ diff --git a/src/channels.c b/src/channels.c index 27ec7c5f..06dad5df 100644 --- a/src/channels.c +++ b/src/channels.c @@ -122,7 +122,7 @@ ssh_channel ssh_channel_new(ssh_session session) } channel->session = session; - channel->exit_status = -1; + channel->exit_status = (uint32_t)-1; channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; if (session->channels == NULL) { @@ -3360,7 +3360,7 @@ ssh_session ssh_channel_get_session(ssh_channel channel) static int ssh_channel_exit_status_termination(void *c) { ssh_channel channel = c; - if (channel->exit_status != -1 || + if (channel->exit_status != (uint32_t)-1 || /* When a channel is closed, no exit status message can * come anymore */ (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || From b2d3a4670aa382ccc34f7f65e66f79f4881e907f Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 14:50:12 +0100 Subject: [PATCH 186/795] channels: Use a structure to store exit information Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- include/libssh/channels.h | 7 ++++++- src/channels.c | 16 ++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/libssh/channels.h b/include/libssh/channels.h index d80e0dd6..7a3535ec 100644 --- a/include/libssh/channels.h +++ b/include/libssh/channels.h @@ -80,7 +80,12 @@ struct ssh_channel_struct { ssh_buffer stdout_buffer; ssh_buffer stderr_buffer; void *userarg; - uint32_t exit_status; + struct { + bool status; + uint32_t code; + char *signal; + bool core_dumped; + } exit; enum ssh_channel_request_state_e request_state; struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ diff --git a/src/channels.c b/src/channels.c index 06dad5df..7b174076 100644 --- a/src/channels.c +++ b/src/channels.c @@ -122,7 +122,7 @@ ssh_channel ssh_channel_new(ssh_session session) } channel->session = session; - channel->exit_status = (uint32_t)-1; + channel->exit.code = (uint32_t)-1; channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; if (session->channels == NULL) { @@ -808,19 +808,23 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { if (strcmp(request,"exit-status") == 0) { SAFE_FREE(request); - rc = ssh_buffer_unpack(packet, "d", &channel->exit_status); + rc = ssh_buffer_unpack(packet, "d", &channel->exit.code); if (rc != SSH_OK) { SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet"); return SSH_PACKET_USED; } - SSH_LOG(SSH_LOG_PACKET, "received exit-status %d", channel->exit_status); + channel->exit.status = true; + + SSH_LOG(SSH_LOG_PACKET, + "received exit-status %u", + channel->exit.code); ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, channel_exit_status_function, channel->session, channel, - channel->exit_status); + channel->exit.code); return SSH_PACKET_USED; } @@ -3360,7 +3364,7 @@ ssh_session ssh_channel_get_session(ssh_channel channel) static int ssh_channel_exit_status_termination(void *c) { ssh_channel channel = c; - if (channel->exit_status != (uint32_t)-1 || + if (channel->exit.status || /* When a channel is closed, no exit status message can * come anymore */ (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || @@ -3402,7 +3406,7 @@ int ssh_channel_get_exit_status(ssh_channel channel) if (rc == SSH_ERROR || channel->session->session_state == SSH_SESSION_STATE_ERROR) return SSH_ERROR; - return channel->exit_status; + return channel->exit.code; } /* From fdf8dc275019ed263d9f7c4c52393af2dbe68122 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 16:59:45 +0100 Subject: [PATCH 187/795] channels: Reformat SSH_PACKET_CALLBACK(channel_rcv_request) Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/channels.c | 228 +++++++++++++++++++++++++------------------------ 1 file changed, 117 insertions(+), 111 deletions(-) diff --git a/src/channels.c b/src/channels.c index 7b174076..16e7b467 100644 --- a/src/channels.c +++ b/src/channels.c @@ -784,29 +784,28 @@ SSH_PACKET_CALLBACK(channel_rcv_close) { return SSH_PACKET_USED; } -SSH_PACKET_CALLBACK(channel_rcv_request) { - ssh_channel channel; - char *request=NULL; +SSH_PACKET_CALLBACK(channel_rcv_request) +{ + ssh_channel channel = NULL; + char *request = NULL; uint8_t want_reply; int rc; - (void)user; - (void)type; - - channel = channel_from_msg(session,packet); - if (channel == NULL) { - SSH_LOG(SSH_LOG_FUNCTIONS,"%s", ssh_get_error(session)); - return SSH_PACKET_USED; - } - - rc = ssh_buffer_unpack(packet, "sb", - &request, - &want_reply); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - return SSH_PACKET_USED; - } - - if (strcmp(request,"exit-status") == 0) { + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "sb", &request, &want_reply); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-status") == 0) { SAFE_FREE(request); rc = ssh_buffer_unpack(packet, "d", &channel->exit.code); if (rc != SSH_OK) { @@ -826,60 +825,62 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { channel, channel->exit.code); - return SSH_PACKET_USED; - } + return SSH_PACKET_USED; + } - if (strcmp(request,"signal") == 0) { + if (strcmp(request, "signal") == 0) { char *sig = NULL; - SAFE_FREE(request); - SSH_LOG(SSH_LOG_PACKET, "received signal"); + SAFE_FREE(request); + SSH_LOG(SSH_LOG_PACKET, "received signal"); - rc = ssh_buffer_unpack(packet, "s", &sig); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - return SSH_PACKET_USED; - } + rc = ssh_buffer_unpack(packet, "s", &sig); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } - SSH_LOG(SSH_LOG_PACKET, - "Remote connection sent a signal SIG %s", sig); + SSH_LOG(SSH_LOG_PACKET, "Remote connection sent a signal SIG %s", sig); ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, channel_signal_function, channel->session, channel, sig); - SAFE_FREE(sig); - - return SSH_PACKET_USED; - } - - if (strcmp(request, "exit-signal") == 0) { - const char *core = "(core dumped)"; - char *sig = NULL; - char *errmsg = NULL; - char *lang = NULL; - uint8_t core_dumped; - - SAFE_FREE(request); - - rc = ssh_buffer_unpack(packet, "sbss", - &sig, /* signal name */ - &core_dumped, /* core dumped */ - &errmsg, /* error message */ - &lang); - if (rc != SSH_OK) { - SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); - return SSH_PACKET_USED; - } - - if (core_dumped == 0) { - core = ""; - } - - SSH_LOG(SSH_LOG_PACKET, - "Remote connection closed by signal SIG %s %s", sig, core); - ssh_callbacks_execute_list(channel->callbacks, + SAFE_FREE(sig); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + char *sig = NULL; + char *errmsg = NULL; + char *lang = NULL; + uint8_t core_dumped; + + SAFE_FREE(request); + + rc = ssh_buffer_unpack(packet, + "sbss", + &sig, /* signal name */ + &core_dumped, /* core dumped */ + &errmsg, /* error message */ + &lang); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (core_dumped == 0) { + core = ""; + } + + SSH_LOG(SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", + sig, + core); + ssh_callbacks_execute_list(channel->callbacks, ssh_channel_callbacks, channel_exit_signal_function, channel->session, @@ -891,71 +892,76 @@ SSH_PACKET_CALLBACK(channel_rcv_request) { SAFE_FREE(lang); SAFE_FREE(errmsg); - SAFE_FREE(sig); - - return SSH_PACKET_USED; - } - if(strcmp(request,"keepalive@openssh.com")==0){ - SAFE_FREE(request); - SSH_LOG(SSH_LOG_DEBUG,"Responding to Openssh's keepalive"); - - rc = ssh_buffer_pack(session->out_buffer, - "bd", - SSH2_MSG_CHANNEL_FAILURE, - channel->remote_channel); - if (rc != SSH_OK) { - return SSH_PACKET_USED; - } - ssh_packet_send(session); - - return SSH_PACKET_USED; - } + SAFE_FREE(sig); - if (strcmp(request, "auth-agent-req@openssh.com") == 0) { - int status; - - SAFE_FREE(request); - SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); - - status = SSH2_MSG_CHANNEL_FAILURE; - ssh_callbacks_iterate(channel->callbacks, - ssh_channel_callbacks, - channel_auth_agent_req_function) { - ssh_callbacks_iterate_exec(channel_auth_agent_req_function, - channel->session, - channel); - /* in lieu of a return value, if the callback exists it's supported */ - status = SSH2_MSG_CHANNEL_SUCCESS; - break; + return SSH_PACKET_USED; } - ssh_callbacks_iterate_end(); + if (strcmp(request, "keepalive@openssh.com") == 0) { + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Responding to Openssh's keepalive"); - if (want_reply) { rc = ssh_buffer_pack(session->out_buffer, "bd", - status, + SSH2_MSG_CHANNEL_FAILURE, channel->remote_channel); if (rc != SSH_OK) { return SSH_PACKET_USED; } ssh_packet_send(session); + + return SSH_PACKET_USED; } - return SSH_PACKET_USED; - } + if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; + + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); + + status = SSH2_MSG_CHANNEL_FAILURE; + ssh_callbacks_iterate (channel->callbacks, + ssh_channel_callbacks, + channel_auth_agent_req_function) { + ssh_callbacks_iterate_exec(channel_auth_agent_req_function, + channel->session, + channel); + /* in lieu of a return value, if the callback exists it's supported + */ + status = SSH2_MSG_CHANNEL_SUCCESS; + break; + } + ssh_callbacks_iterate_end(); + + if (want_reply) { + rc = ssh_buffer_pack(session->out_buffer, + "bd", + status, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } + + return SSH_PACKET_USED; + } #ifdef WITH_SERVER - /* If we are here, that means we have a request that is not in the understood - * client requests. That means we need to create a ssh message to be passed - * to the user code handling ssh messages - */ - ssh_message_handle_channel_request(session,channel,packet,request,want_reply); + /* If we are here, that means we have a request that is not in the + * understood client requests. That means we need to create a ssh message to + * be passed to the user code handling ssh messages + */ + ssh_message_handle_channel_request(session, + channel, + packet, + request, + want_reply); #else SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); #endif - SAFE_FREE(request); + SAFE_FREE(request); - return SSH_PACKET_USED; + return SSH_PACKET_USED; } /* From d40a6448a45fafdf2ee8e78ec06e727b8b8f7626 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 15:11:57 +0100 Subject: [PATCH 188/795] channels: Store exit-signal in channel structure Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- src/channels.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/channels.c b/src/channels.c index 16e7b467..e180c20f 100644 --- a/src/channels.c +++ b/src/channels.c @@ -890,9 +890,15 @@ SSH_PACKET_CALLBACK(channel_rcv_request) errmsg, lang); + channel->exit.core_dumped = core_dumped; + if (sig != NULL) { + SAFE_FREE(channel->exit.signal); + channel->exit.signal = sig; + } + channel->exit.status = true; + SAFE_FREE(lang); SAFE_FREE(errmsg); - SAFE_FREE(sig); return SSH_PACKET_USED; } @@ -1317,6 +1323,7 @@ void ssh_channel_do_free(ssh_channel channel) ssh_list_free(channel->callbacks); channel->callbacks = NULL; } + SAFE_FREE(channel->exit.signal); channel->session = NULL; SAFE_FREE(channel); From 04d86aeeae73c78af8b3dcdabb2e588cd31a8923 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Thu, 8 Sep 2022 14:38:18 +0200 Subject: [PATCH 189/795] channels: Implement better ssh_channel_get_exit_state() variant This way we will get errors as return code else we don't know if the function failed (SSH_ERROR) or the exit_status is -1 which would correspond to SSH_ERROR. Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- include/libssh/libssh.h | 6 ++- include/libssh/libsshpp.hpp | 18 ++++++- src/channels.c | 99 +++++++++++++++++++++++++++++----- tests/client/torture_session.c | 5 +- 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h index 0f6b6813..afbbbb32 100644 --- a/include/libssh/libssh.h +++ b/include/libssh/libssh.h @@ -462,7 +462,11 @@ LIBSSH_API int ssh_channel_close(ssh_channel channel); } \ } while (0) LIBSSH_API void ssh_channel_free(ssh_channel channel); -LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped); +SSH_DEPRECATED LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); diff --git a/include/libssh/libsshpp.hpp b/include/libssh/libsshpp.hpp index e0f21e85..553a7777 100644 --- a/include/libssh/libsshpp.hpp +++ b/include/libssh/libsshpp.hpp @@ -498,8 +498,22 @@ class Channel { return_throwable; } - int getExitStatus(){ - return ssh_channel_get_exit_status(channel); + /* + * @deprecated Please use getExitState() + */ + int getExitStatus() { + uint32_t exit_status = (uint32_t)-1; + ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + return exit_status; + } + void_throwable getExitState(uint32_t & pexit_code, + char **pexit_signal, + int & pcore_dumped) { + ssh_throw(ssh_channel_get_exit_state(channel, + &pexit_code, + pexit_signal, + &pcore_dumped)); + return_throwable; } Session &getSession(){ return *session; diff --git a/src/channels.c b/src/channels.c index e180c20f..7370d498 100644 --- a/src/channels.c +++ b/src/channels.c @@ -3388,6 +3388,83 @@ static int ssh_channel_exit_status_termination(void *c) return 0; } +/** + * @brief Get the exit state of the channel (error code from the executed + * instruction or signal). + * + * @param[in] channel The channel to get the status from. + * + * @param[out] pexit_code A pointer to an uint32_t to store the exit status. + * + * @param[out] pexit_signal A pointer to store the exit signal as a string. + * The signal is without the SIG prefix, e.g. "TERM" or + * "KILL"). The caller has to free the memory. + * + * @param[out] pcore_dumped A pointer to store a boolean value if it dumped a + * core. + * + * @return SSH_OK on success, SSH_AGAIN if we don't have a status + * or an SSH error. + * @warning This function may block until a timeout (or never) + * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * SSH_ERROR immediately. + * + * If you're looking for an async handling of this register a callback for the + * exit status! + * + * @see ssh_channel_exit_status_callback + * @see ssh_channel_exit_signal_callback + */ +int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped) +{ + ssh_session session = NULL; + int rc; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + session = channel->session; + + rc = ssh_handle_packets_termination(channel->session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_exit_status_termination, + channel); + if (rc == SSH_ERROR || channel->session->session_state == + SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + /* If we don't have any kind of exit state, return SSH_AGAIN */ + if (!channel->exit.status) { + return SSH_AGAIN; + } + + if (pexit_code != NULL) { + *pexit_code = channel->exit.code; + } + + if (pexit_signal != NULL) { + *pexit_signal = NULL; + if (channel->exit.signal != NULL) { + *pexit_signal = strdup(channel->exit.signal); + if (pexit_signal == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + } + + if (pcore_dumped != NULL) { + *pcore_dumped = channel->exit.core_dumped; + } + + return SSH_OK; +} + /** * @brief Get the exit status of the channel (error code from the executed * instruction). @@ -3405,21 +3482,19 @@ static int ssh_channel_exit_status_termination(void *c) * exit status. * * @see ssh_channel_exit_status_callback + * @deprecated Please use ssh_channel_exit_state() */ int ssh_channel_get_exit_status(ssh_channel channel) { - int rc; - if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { - return SSH_ERROR; - } - rc = ssh_handle_packets_termination(channel->session, - SSH_TIMEOUT_DEFAULT, - ssh_channel_exit_status_termination, - channel); - if (rc == SSH_ERROR || channel->session->session_state == - SSH_SESSION_STATE_ERROR) - return SSH_ERROR; - return channel->exit.code; + uint32_t exit_status = (uint32_t)-1; + int rc; + + rc = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return exit_status; } /* diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index e6c10680..0e889afa 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -403,7 +403,7 @@ static void torture_channel_exit_status(void **state) ssh_session session = s->ssh.session; ssh_channel channel = NULL; char request[256]; - int exit_status = -1; + uint32_t exit_status = (uint32_t)-1; int rc; rc = snprintf(request, sizeof(request), "true"); @@ -419,7 +419,8 @@ static void torture_channel_exit_status(void **state) rc = ssh_channel_request_exec(channel, request); assert_ssh_return_code(session, rc); - exit_status = ssh_channel_get_exit_status(channel); + exit_status = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + assert_ssh_return_code(session, rc); assert_int_equal(exit_status, 0); } From a5f082db831f57e9390578331eee20f8928c4edf Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 2 Feb 2024 15:57:37 +0100 Subject: [PATCH 190/795] tests:client: Add test which checks if we got an exit signal Fixes #235 Signed-off-by: Andreas Schneider Reviewed-by: Jakub Jelen --- tests/client/torture_session.c | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/client/torture_session.c b/tests/client/torture_session.c index 0e889afa..d188ff09 100644 --- a/tests/client/torture_session.c +++ b/tests/client/torture_session.c @@ -424,6 +424,42 @@ static void torture_channel_exit_status(void **state) 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); + + exit_status = ssh_channel_get_exit_state(channel, + &exit_status, + &exit_signal, + &core_dumped); + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, 0); + assert_string_equal(exit_signal, "TERM"); + SAFE_FREE(exit_signal); +} + /* Ensure that calling 'ssh_channel_get_exit_status' on a freed channel does not * lead to segmentation faults. */ @@ -571,6 +607,9 @@ int torture_run_tests(void) { 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_freed_channel_get_exit_status, session_setup, session_teardown), From 1db37cd9f466c2f49856b111abb4863b787b72f8 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 12 Apr 2024 13:46:11 +0200 Subject: [PATCH 191/795] cmake: Fix typo in error message Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- cmake/Modules/FindMbedTLS.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/FindMbedTLS.cmake b/cmake/Modules/FindMbedTLS.cmake index baec8adc..ee52ad84 100644 --- a/cmake/Modules/FindMbedTLS.cmake +++ b/cmake/Modules/FindMbedTLS.cmake @@ -94,7 +94,7 @@ if (MBEDTLS_VERSION) ) else (MBEDTLS_VERSION) find_package_handle_standard_args(MBedTLS - "Could NOT find mbedTLS, try to set the path to mbedLS root folder in + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder in the system variable MBEDTLS_ROOT_DIR" MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) From a8883199d4c62be832fce0132fc4dfd2e2322fc9 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 12 Apr 2024 13:56:32 +0200 Subject: [PATCH 192/795] cmake: Compatibility with MbedTLS 3.6.0 Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- cmake/Modules/FindMbedTLS.cmake | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmake/Modules/FindMbedTLS.cmake b/cmake/Modules/FindMbedTLS.cmake index ee52ad84..9f647028 100644 --- a/cmake/Modules/FindMbedTLS.cmake +++ b/cmake/Modules/FindMbedTLS.cmake @@ -34,7 +34,7 @@ set(_MBEDTLS_ROOT_HINTS_AND_PATHS find_path(MBEDTLS_INCLUDE_DIR NAMES - mbedtls/config.h + mbedtls/ssl.h HINTS ${_MBEDTLS_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES @@ -73,6 +73,14 @@ set(MBEDTLS_LIBRARIES ${MBEDTLS_SSL_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} ${MBEDTLS_X509_LIBRARY}) if (MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") + # mbedtls 2.8 + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+.[0-9]+.[0-9]+).*" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +elseif (MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h") + # mbedtls 3.6 file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str REGEX "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") @@ -93,7 +101,7 @@ if (MBEDTLS_VERSION) in the system variable MBEDTLS_ROOT_DIR" ) else (MBEDTLS_VERSION) - find_package_handle_standard_args(MBedTLS + find_package_handle_standard_args(MbedTLS "Could NOT find mbedTLS, try to set the path to mbedTLS root folder in the system variable MBEDTLS_ROOT_DIR" MBEDTLS_INCLUDE_DIR From 0882338142c88cb7e3c1a014378f9888c59347e6 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 12 Apr 2024 14:17:56 +0200 Subject: [PATCH 193/795] Detect blowfish in mbedtls and skip it if not found Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- CMakeLists.txt | 2 +- ConfigureChecks.cmake | 6 +++++- config.h.cmake | 6 +++--- include/libssh/crypto.h | 4 ++-- src/kex.c | 8 ++------ src/libcrypto.c | 8 ++++---- src/libgcrypt.c | 8 ++++---- src/libmbedcrypto.c | 4 ++-- tests/client/torture_algorithms.c | 8 ++++---- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index acc1e606..fa4a2e0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,7 +229,7 @@ message(STATUS "Pcap debugging support : ${WITH_PCAP}") message(STATUS "Build shared library: ${BUILD_SHARED_LIBS}") message(STATUS "Unit testing: ${UNIT_TESTING}") message(STATUS "Client code testing: ${CLIENT_TESTING}") -message(STATUS "Blowfish cipher support: ${WITH_BLOWFISH_CIPHER}") +message(STATUS "Blowfish cipher support: ${HAVE_BLOWFISH}") message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}") message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}") set(_SERVER_TESTING OFF) diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index a83868ca..a24be64d 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -90,7 +90,7 @@ if (OPENSSL_FOUND) endif() if (WITH_BLOWFISH_CIPHER) - check_include_file(openssl/blowfish.h HAVE_OPENSSL_BLOWFISH_H) + check_include_file(openssl/blowfish.h HAVE_BLOWFISH) endif() check_include_file(openssl/ecdh.h HAVE_OPENSSL_ECDH_H) @@ -235,6 +235,10 @@ if (MBEDTLS_FOUND) set(CMAKE_REQUIRED_INCLUDES "${MBEDTLS_INCLUDE_DIR}/mbedtls") check_include_file(chacha20.h HAVE_MBEDTLS_CHACHA20_H) check_include_file(poly1305.h HAVE_MBEDTLS_POLY1305_H) + if (WITH_BLOWFISH_CIPHER) + check_include_file(blowfish.h HAVE_BLOWFISH) + endif() + unset(CMAKE_REQUIRED_INCLUDES) endif (MBEDTLS_FOUND) diff --git a/config.h.cmake b/config.h.cmake index 391ee162..b4a44bc3 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -64,9 +64,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_WSPIAPI_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_BLOWFISH_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_OPENSSL_DES_H 1 @@ -180,6 +177,9 @@ /* Define to 1 if you have the `cmocka_set_test_filter' function. */ #cmakedefine HAVE_CMOCKA_SET_TEST_FILTER 1 +/* Define to 1 if we have support for blowfish */ +#cmakedefine HAVE_BLOWFISH 1 + /*************************** LIBRARIES ***************************/ /* Define to 1 if you have the `crypto' library (-lcrypto). */ diff --git a/include/libssh/crypto.h b/include/libssh/crypto.h index 32016827..8dcf5408 100644 --- a/include/libssh/crypto.h +++ b/include/libssh/crypto.h @@ -86,9 +86,9 @@ enum ssh_key_exchange_e { enum ssh_cipher_e { SSH_NO_CIPHER=0, -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH SSH_BLOWFISH_CBC, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ SSH_3DES_CBC, SSH_AES128_CBC, SSH_AES192_CBC, diff --git a/src/kex.c b/src/kex.c index b071d5ea..9dccb898 100644 --- a/src/kex.c +++ b/src/kex.c @@ -46,12 +46,8 @@ #include "libssh/bignum.h" #include "libssh/token.h" -#ifdef WITH_BLOWFISH_CIPHER -# if defined(HAVE_OPENSSL_BLOWFISH_H) || defined(HAVE_LIBGCRYPT) || defined(HAVE_LIBMBEDCRYPTO) -# define BLOWFISH ",blowfish-cbc" -# else -# define BLOWFISH "" -# endif +#ifdef HAVE_BLOWFISH +# define BLOWFISH ",blowfish-cbc" #else # define BLOWFISH "" #endif diff --git a/src/libcrypto.c b/src/libcrypto.c index 33834dbd..e69f3194 100644 --- a/src/libcrypto.c +++ b/src/libcrypto.c @@ -397,12 +397,12 @@ static void evp_cipher_init(struct ssh_cipher_struct *cipher) case SSH_3DES_CBC: cipher->cipher = EVP_des_ede3_cbc(); break; -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH case SSH_BLOWFISH_CBC: cipher->cipher = EVP_bf_cbc(); break; /* ciphers not using EVP */ -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ case SSH_AEAD_CHACHA20_POLY1305: SSH_LOG(SSH_LOG_TRACE, "The ChaCha cipher cannot be handled here"); break; @@ -1163,7 +1163,7 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), * The table of supported ciphers */ static struct ssh_cipher_struct ssh_ciphertab[] = { -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH { .name = "blowfish-cbc", .blocksize = 8, @@ -1175,7 +1175,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = evp_cipher_decrypt, .cleanup = evp_cipher_cleanup }, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ #ifdef HAS_AES { .name = "aes128-ctr", diff --git a/src/libgcrypt.c b/src/libgcrypt.c index 4feda00c..195bb755 100644 --- a/src/libgcrypt.c +++ b/src/libgcrypt.c @@ -116,7 +116,7 @@ int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { return 1; } -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH /* the wrapper functions for blowfish */ static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ if (cipher->key == NULL) { @@ -153,7 +153,7 @@ static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, void *out, size_t len) { gcry_cipher_decrypt(cipher->key[0], out, len, in, len); } -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { int mode=GCRY_CIPHER_MODE_CBC; @@ -732,7 +732,7 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), /* the table of supported ciphers */ static struct ssh_cipher_struct ssh_ciphertab[] = { -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH { .name = "blowfish-cbc", .blocksize = 8, @@ -744,7 +744,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .encrypt = blowfish_encrypt, .decrypt = blowfish_decrypt }, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ { .name = "aes128-ctr", .blocksize = 16, diff --git a/src/libmbedcrypto.c b/src/libmbedcrypto.c index 8fb36e53..c05f5b28 100644 --- a/src/libmbedcrypto.c +++ b/src/libmbedcrypto.c @@ -898,7 +898,7 @@ none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), #endif /* WITH_INSECURE_NONE */ static struct ssh_cipher_struct ssh_ciphertab[] = { -#ifdef WITH_BLOWFISH_CIPHER +#ifdef HAVE_BLOWFISH { .name = "blowfish-cbc", .blocksize = 8, @@ -910,7 +910,7 @@ static struct ssh_cipher_struct ssh_ciphertab[] = { .decrypt = cipher_decrypt_cbc, .cleanup = cipher_cleanup }, -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH */ { .name = "aes128-ctr", .blocksize = 16, diff --git a/tests/client/torture_algorithms.c b/tests/client/torture_algorithms.c index 60354f9b..d1190605 100644 --- a/tests/client/torture_algorithms.c +++ b/tests/client/torture_algorithms.c @@ -496,7 +496,7 @@ static void torture_algorithms_3des_cbc_hmac_sha2_512_etm(void **state) { test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512-etm@openssh.com"); } -#if defined(WITH_BLOWFISH_CIPHER) && defined(OPENSSH_BLOWFISH_CBC) +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) static void torture_algorithms_blowfish_cbc_hmac_sha1(void **state) { struct torture_state *s = *state; @@ -556,7 +556,7 @@ static void torture_algorithms_blowfish_cbc_hmac_sha2_512_etm(void **state) { test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512-etm@openssh.com"); } -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH && defined(OPENSSH_BLOWFISH_CBC) */ #ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM static void torture_algorithms_chacha20_poly1305(void **state) @@ -921,7 +921,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512_etm, session_setup, session_teardown), -#if defined(WITH_BLOWFISH_CIPHER) && defined(OPENSSH_BLOWFISH_CBC) +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1, session_setup, session_teardown), @@ -940,7 +940,7 @@ int torture_run_tests(void) { cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512_etm, session_setup, session_teardown), -#endif /* WITH_BLOWFISH_CIPHER */ +#endif /* HAVE_BLOWFISH_CIPHER && defined(OPENSSH_BLOWFISH_CBC) */ #ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305, session_setup, From b815ca08b378eb923e0522350f99179dbedc2437 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 12 Apr 2024 14:39:32 +0200 Subject: [PATCH 194/795] mbedcrypto: Initialize mpi structs to avoid crashes Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki_mbedcrypto.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index 962ae1fe..fbcc444a 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -886,12 +886,12 @@ ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) ssh_string n = NULL; ssh_string str = NULL; #if MBEDTLS_VERSION_MAJOR > 2 - mbedtls_mpi E; - mbedtls_mpi N; - mbedtls_mpi D; - mbedtls_mpi IQMP; - mbedtls_mpi P; - mbedtls_mpi Q; + mbedtls_mpi E = {0}; + mbedtls_mpi N = {0}; + mbedtls_mpi D = {0}; + mbedtls_mpi IQMP = {0}; + mbedtls_mpi P = {0}; + mbedtls_mpi Q = {0}; #endif int rc; From fc5dd6f57ce41726329cabae0f252ff769c51481 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 15 Apr 2024 12:07:13 +0200 Subject: [PATCH 195/795] mbedcrypto: Simplify copy&paste code between v2 and v3 Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/pki_mbedcrypto.c | 47 +++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/pki_mbedcrypto.c b/src/pki_mbedcrypto.c index fbcc444a..af70a50b 100644 --- a/src/pki_mbedcrypto.c +++ b/src/pki_mbedcrypto.c @@ -128,56 +128,43 @@ ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, if (valid < 0) { goto fail; } -#if MBEDTLS_VERSION_MAJOR > 2 valid = mbedtls_pk_parse_key( pk, (const unsigned char *)b64_key, b64len, tmp, - strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE), + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , mbedtls_ctr_drbg_random, - ctr_drbg); -#else - valid = mbedtls_pk_parse_key( - pk, - (const unsigned char *)b64_key, - b64len, - tmp, - strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE)); + ctr_drbg #endif + ); } else { -#if MBEDTLS_VERSION_MAJOR > 2 valid = mbedtls_pk_parse_key(pk, (const unsigned char *)b64_key, b64len, NULL, - 0, + 0 +#if MBEDTLS_VERSION_MAJOR > 2 + , mbedtls_ctr_drbg_random, - ctr_drbg); -#else - valid = mbedtls_pk_parse_key(pk, - (const unsigned char *)b64_key, - b64len, - NULL, - 0); + ctr_drbg #endif + ); } } else { -#if MBEDTLS_VERSION_MAJOR > 2 valid = mbedtls_pk_parse_key(pk, (const unsigned char *)b64_key, b64len, (const unsigned char *)passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE), + strnlen(passphrase, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , mbedtls_ctr_drbg_random, - ctr_drbg); -#else - valid = mbedtls_pk_parse_key(pk, - (const unsigned char *)b64_key, - b64len, - (const unsigned char *)passphrase, - strnlen(passphrase, MAX_PASSPHRASE_SIZE)); + ctr_drbg #endif + ); } if (valid != 0) { char error_buf[100]; @@ -329,13 +316,11 @@ int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) goto fail; } + rsa = mbedtls_pk_rsa(*key->rsa); #if MBEDTLS_VERSION_MAJOR > 2 mbedtls_mpi_init(&N); mbedtls_mpi_init(&E); -#endif - rsa = mbedtls_pk_rsa(*key->rsa); -#if MBEDTLS_VERSION_MAJOR > 2 rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n), ssh_string_len(n)); #else From 32d99ec5e5da45388bcbd6e76a21e554dd7f2afe Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 15 Apr 2024 14:04:30 +0200 Subject: [PATCH 196/795] mbedcrypto: Fix bignum_dup() Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- include/libssh/libmbedcrypto.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libssh/libmbedcrypto.h b/include/libssh/libmbedcrypto.h index 918fe293..71ebcccd 100644 --- a/include/libssh/libmbedcrypto.h +++ b/include/libssh/libmbedcrypto.h @@ -129,7 +129,7 @@ int ssh_mbedcry_hex2bn(bignum *dest, char *data); *(dest) = bignum_new(); \ } \ if (*(dest) != NULL) { \ - mbedtls_mpi_copy(orig, *(dest)); \ + mbedtls_mpi_copy(*(dest), orig); \ } \ } while(0) From c15ef71999a7c416e5de8bf38e45f20378b002d5 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 15 Apr 2024 14:04:36 +0200 Subject: [PATCH 197/795] tests: Test coverage for bignum_dup() Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/unittests/torture_bignum.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/unittests/torture_bignum.c b/tests/unittests/torture_bignum.c index c36b81f8..1884328d 100644 --- a/tests/unittests/torture_bignum.c +++ b/tests/unittests/torture_bignum.c @@ -27,10 +27,12 @@ static void check_str (int n, ssh_string str) } } -static void check_bignum(int n, const char *nstr) { - bignum num, num2; - ssh_string str; - char *dec; +static void check_bignum(int n, const char *nstr) +{ + bignum num = NULL, num2 = NULL; + bignum num3 = NULL; + ssh_string str = NULL; + char *dec = NULL; num = bignum_new(); assert_non_null(num); @@ -66,8 +68,13 @@ static void check_bignum(int n, const char *nstr) { assert_string_equal (nstr, dec); ssh_crypto_free(dec); + bignum_dup(num, &num3); + assert_non_null(num3); + assert_int_equal(0, bignum_cmp(num, num3)); + bignum_safe_free(num); bignum_safe_free(num2); + bignum_safe_free(num3); } From 48d8733f6eff2ae42ea57581f507df444477108a Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 12 Apr 2024 13:30:56 +0200 Subject: [PATCH 198/795] ci: Add CI target with mbedtls 3.6.0 branch Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3ad5a985..2007482f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -463,6 +463,11 @@ tumbleweed/openssl_3.0.x/x86_64/clang: variables: CMAKE_ADDITIONAL_OPTIONS: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config" +tumbleweed/mbedtls-3.6.x/x86_64/gcc: + extends: .tumbleweed + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DKRB5_CONFIG=/usr/lib/mit/bin/krb5-config -DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_BLOWFISH_CIPHER=OFF " + tumbleweed/static-analysis: extends: .tests stage: analysis From 0796331c673d66ca69690c585cecf0f07a66da69 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 16 May 2024 10:00:11 +0200 Subject: [PATCH 199/795] ci: Run mbedtls CI also on Centos9 as it will likely not get rebase to 3.6 soon Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- .gitlab-ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2007482f..2c86f859 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -133,6 +133,12 @@ centos9s/openssl_3.0.x/x86_64: make -j$(nproc) && ctest --output-on-failure +centos9s/mbedtls_2.x/x86_64: + image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD + extends: .tests + variables: + CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON -DWITH_BLOWFISH_CIPHER=OFF" + centos9s/openssl_3.0.x/x86_64/fips: extends: .fips image: $CI_REGISTRY/$BUILD_IMAGES_PROJECT:$CENTOS9_BUILD @@ -288,7 +294,7 @@ fedora/libgcrypt/x86_64: variables: CMAKE_ADDITIONAL_OPTIONS: "-DWITH_GCRYPT=ON -DWITH_DEBUG_CRYPTO=ON" -fedora/mbedtls/x86_64: +fedora/mbedtls_2.x/x86_64: extends: .fedora variables: CMAKE_ADDITIONAL_OPTIONS: "-DWITH_MBEDTLS=ON -DWITH_DEBUG_CRYPTO=ON " From e17161dc4fce9e9db53078f54d0ac26958ad7561 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 16 May 2024 17:46:39 +0200 Subject: [PATCH 200/795] tests: Fix setting home dir argument Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- tests/client/torture_auth_cert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/client/torture_auth_cert.c b/tests/client/torture_auth_cert.c index 18766b93..29723bcf 100644 --- a/tests/client/torture_auth_cert.c +++ b/tests/client/torture_auth_cert.c @@ -100,7 +100,7 @@ static int session_setup_ssh_dir(void **state) session_setup(state); - rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, &no_home); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_SSH_DIR, no_home); assert_ssh_return_code(s->ssh.session, rc); return 0; From 51a728dcdf8b7c5183247f192991d4ac8233c293 Mon Sep 17 00:00:00 2001 From: Wenjie Yang Date: Tue, 7 May 2024 21:39:22 +0800 Subject: [PATCH 201/795] Remove the offending supression record. Signed-off-by: Wenjie Yang Reviewed-by: Jakub Jelen --- tests/valgrind.supp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/valgrind.supp b/tests/valgrind.supp index 7289d90c..2baa2ef9 100644 --- a/tests/valgrind.supp +++ b/tests/valgrind.supp @@ -123,13 +123,6 @@ Memcheck:Cond fun:SHA1_* } - -{ - openssl_CRYPTO_leak - Memcheck:Leak - fun:*alloc - fun:CRYPTO_* -} { openssl_CRYPTO_leak Memcheck:Cond From f3fe85f45ef1158c3f97a6abe804df2bcb0df352 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Thu, 16 May 2024 00:10:32 +0200 Subject: [PATCH 202/795] external: Update OpenSSH blowfish implementation Import blowfish that was last changed in OpenSSH v8.9: https://github.com/openssh/openssh-portable/commit/158bf854e2a22cf0906430 "The main change is that Niels Provos kindly agreed to rescind the BSD license advertising clause, shifting them to the 3-term BSD license." Fixes: #153 Signed-off-by: Bastian Germann Reviewed-by: Jakub Jelen --- include/libssh/blf.h | 7 ++----- src/external/blowfish.c | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/include/libssh/blf.h b/include/libssh/blf.h index 201821a2..71928a7d 100644 --- a/include/libssh/blf.h +++ b/include/libssh/blf.h @@ -1,4 +1,4 @@ -/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */ +/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */ /* * Blowfish - a fast block cipher designed by Bruce Schneier * @@ -13,10 +13,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products + * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR diff --git a/src/external/blowfish.c b/src/external/blowfish.c index 4008a9c0..42d5df1e 100644 --- a/src/external/blowfish.c +++ b/src/external/blowfish.c @@ -1,4 +1,4 @@ -/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */ +/* $OpenBSD: blowfish.c,v 1.20 2021/11/29 01:04:45 djm Exp $ */ /* * Blowfish block cipher for OpenBSD * Copyright 1997 Niels Provos @@ -14,10 +14,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Niels Provos. - * 4. The name of the author may not be used to endorse or promote products + * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR From 0cbd35f1fd372d4f0c3871fad26251061436a24d Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 30 May 2024 16:03:20 +0200 Subject: [PATCH 203/795] INSTALL: Update minimal CMake version to match reality Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- INSTALL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 7ed4ff20..a6a9cfec 100644 --- a/INSTALL +++ b/INSTALL @@ -7,7 +7,7 @@ In order to build libssh, you need to install several components: - A C compiler -- [CMake](https://www.cmake.org) >= 3.5.0 +- [CMake](https://www.cmake.org) >= 3.12.0 - [libz](https://www.zlib.net) >= 1.2 - [openssl](https://www.openssl.org) >= 1.1.1 or From 70d09933129f4eaf081ec5c78466cac40472a1e2 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 30 May 2024 16:03:47 +0200 Subject: [PATCH 204/795] gssapi: Fix typo Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- src/gssapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gssapi.c b/src/gssapi.c index 5254d38d..7fbaa804 100644 --- a/src/gssapi.c +++ b/src/gssapi.c @@ -223,7 +223,7 @@ ssh_gssapi_handle_userauth(ssh_session session, const char *user, maj_stat = gss_indicate_mechs(&min_stat, &supported); if (maj_stat != GSS_S_COMPLETE) { - SSH_LOG(SSH_LOG_DEBUG, "indicate mecks %d, %d", maj_stat, min_stat); + SSH_LOG(SSH_LOG_DEBUG, "indicate mechs %d, %d", maj_stat, min_stat); ssh_gssapi_log_error(SSH_LOG_DEBUG, "indicate mechs", maj_stat, From c93a730bc1f3cfb78dbc74a78dc6f2ef6d5e51dd Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 31 May 2024 09:15:05 +0200 Subject: [PATCH 205/795] examples: Make sure the callback structure is initialized When the callback structure is allocated with malloc, some fields might be uninitialized and therefore could cause undefined behavior or crashes. Signed-off-by: Jakub Jelen Reviewed-by: Sahana Prasad --- examples/sshd_direct-tcpip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sshd_direct-tcpip.c b/examples/sshd_direct-tcpip.c index 84389b28..744c5aa6 100644 --- a/examples/sshd_direct-tcpip.c +++ b/examples/sshd_direct-tcpip.c @@ -526,7 +526,7 @@ message_callback(UNUSED_PARAM(ssh_session session), } pFd = malloc(sizeof *pFd); - cb_chan = malloc(sizeof *cb_chan); + cb_chan = calloc(1, sizeof *cb_chan); event_fd_data = malloc(sizeof *event_fd_data); if (pFd == NULL || cb_chan == NULL || event_fd_data == NULL) { SAFE_FREE(pFd); From e90df7195543ab8fcd81e97a9eee655a8c92be42 Mon Sep 17 00:00:00 2001 From: Francesco Rollo Date: Wed, 29 May 2024 18:01:01 +0200 Subject: [PATCH 206/795] feature: Add match_localnetwork predicate and its feature Signed-off-by: Francesco Rollo Reviewed-by: Jakub Jelen Reviewed-by: Eshan Kelkar --- include/libssh/misc.h | 2 +- include/libssh/priv.h | 5 + src/config.c | 161 ++++++++++++++++-- src/match.c | 375 ++++++++++++++++++++++++++++++++++++++++++ src/misc.c | 9 +- 5 files changed, 535 insertions(+), 17 deletions(-) diff --git a/include/libssh/misc.h b/include/libssh/misc.h index fc8596f7..28982990 100644 --- a/include/libssh/misc.h +++ b/include/libssh/misc.h @@ -33,7 +33,7 @@ # endif /* _MSC_VER */ #else -# include +#include #endif /* _WIN32 */ #ifdef __cplusplus diff --git a/include/libssh/priv.h b/include/libssh/priv.h index 4434d143..42e55753 100644 --- a/include/libssh/priv.h +++ b/include/libssh/priv.h @@ -330,6 +330,11 @@ int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); int match_pattern_list(const char *string, const char *pattern, size_t len, int dolower); int match_hostname(const char *host, const char *pattern, unsigned int len); +#ifndef _WIN32 +int match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family); +#endif /* connector.c */ int ssh_connector_set_event(ssh_connector connector, ssh_event event); diff --git a/src/config.c b/src/config.c index 7135c3b1..79839007 100644 --- a/src/config.c +++ b/src/config.c @@ -39,6 +39,8 @@ # include # include # include +# include +# include #endif #include "libssh/config_parser.h" @@ -160,7 +162,8 @@ enum ssh_config_match_e { MATCH_HOST, MATCH_ORIGINALHOST, MATCH_USER, - MATCH_LOCALUSER + MATCH_LOCALUSER, + MATCH_LOCALNETWORK }; struct ssh_config_match_keyword_table_s { @@ -168,16 +171,18 @@ struct ssh_config_match_keyword_table_s { enum ssh_config_match_e opcode; }; -static struct ssh_config_match_keyword_table_s ssh_config_match_keyword_table[] = { - { "all", MATCH_ALL }, - { "canonical", MATCH_CANONICAL }, - { "final", MATCH_FINAL }, - { "exec", MATCH_EXEC }, - { "host", MATCH_HOST }, - { "originalhost", MATCH_ORIGINALHOST }, - { "user", MATCH_USER }, - { "localuser", MATCH_LOCALUSER }, - { NULL, MATCH_UNKNOWN }, +static struct ssh_config_match_keyword_table_s + ssh_config_match_keyword_table[] = { + {"all", MATCH_ALL}, + {"canonical", MATCH_CANONICAL}, + {"final", MATCH_FINAL}, + {"exec", MATCH_EXEC}, + {"host", MATCH_HOST}, + {"originalhost", MATCH_ORIGINALHOST}, + {"user", MATCH_USER}, + {"localuser", MATCH_LOCALUSER}, + {"localnetwork", MATCH_LOCALNETWORK}, + {NULL, MATCH_UNKNOWN}, }; static int ssh_config_parse_line(ssh_session session, const char *line, @@ -572,6 +577,99 @@ ssh_config_make_absolute(ssh_session session, return out; } +#ifndef _WIN32 +/** + * @brief Checks if host address matches the local network specified. + * + * Verify whether a local network interface address matches any of the CIDR + * patterns. + * + * @param addrlist The CIDR pattern-list to be checked, can contain both + * IPv4 and IPv6 addresses and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param negate The negate condition. The return value is negated + * (returns 1 instead of 0 and vice versa). + * + * @return 1 if match found. + * @return 0 if no match found. + * @return -1 on errors. + */ +static int +ssh_match_localnetwork(const char *addrlist, bool negate) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + int r, found = 0; + char address[NI_MAXHOST], err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socklen_t sa_len; + + r = getifaddrs(&ifaddrs); + if (r != 0) { + SSH_LOG(SSH_LOG_WARN, + "Match localnetwork: getifaddrs() failed: %s", + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + sa_len = sizeof(struct sockaddr_in6); + break; + default: + SSH_LOG(SSH_LOG_TRACE, + "Interface %s: unsupported address family %d", + ifa->ifa_name, + ifa->ifa_addr->sa_family); + continue; + } + + r = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (r != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Interface %s getnameinfo failed: %s", + ifa->ifa_name, + gai_strerror(r)); + continue; + } + SSH_LOG(SSH_LOG_TRACE, + "Interface %s address %s", + ifa->ifa_name, + address); + + r = match_cidr_address_list(address, + addrlist, + ifa->ifa_addr->sa_family); + if (r == 1) { + SSH_LOG(SSH_LOG_TRACE, + "Matched interface %s: address %s in %s", + ifa->ifa_name, + address, + addrlist); + found = 1; + break; + } + } + + freeifaddrs(ifaddrs); + + return (found == (negate ? 0 : 1)); +} +#endif + static int ssh_config_parse_line(ssh_session session, const char *line, @@ -795,6 +893,47 @@ ssh_config_parse_line(ssh_session session, args++; break; +#ifndef _WIN32 + case MATCH_LOCALNETWORK: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match local network keyword" + "requires argument", + count); + SAFE_FREE(x); + return -1; + } + rv = match_cidr_address_list(NULL, p, -1); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - List invalid entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + rv = ssh_match_localnetwork(p, negate); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Error while retrieving " + "network interface information -" + " List entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + + result &= rv; + args++; + break; +#endif + case MATCH_UNKNOWN: default: ssh_set_error(session, SSH_FATAL, diff --git a/src/match.c b/src/match.c index 3e58f733..d04d66e0 100644 --- a/src/match.c +++ b/src/match.c @@ -198,3 +198,378 @@ int match_pattern_list(const char *string, const char *pattern, int match_hostname(const char *host, const char *pattern, unsigned int len) { return match_pattern_list(host, pattern, len, 1); } + +#ifndef _WIN32 +/** + * @brief Tries to match the host IPv6 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_6(struct in6_addr *host_addr, + struct in6_addr *net_addr, + unsigned int bits) +{ + const uint32_t *a = host_addr->s6_addr32; + const uint32_t *b = net_addr->s6_addr32; + + unsigned int qwords_whole, bits_left; + + /* The number of complete 32-bit words covered by the prefix */ + qwords_whole = bits / 32; + + /* + * The number of bits remaining in the incomplete (last) 32-bit word + * covered by the prefix + */ + bits_left = bits % 32; + + if (qwords_whole) { + if (memcmp(a, b, qwords_whole * 4) != 0) { + return 0; + } + } + + if (bits_left) { + if ((a[qwords_whole] ^ b[qwords_whole]) & + htonl((0xFFFFFFFFu << (32 - bits_left)) & 0xFFFFFFFFu)) { + return 0; + } + } + + return 1; +} + +/** + * @brief Tries to match the host IPv4 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_4(struct in_addr *host_addr, + struct in_addr *net_addr, + unsigned int bits) +{ + if (bits == 0) { + /* C99 6.5.7 (3): u32 << 32 is undefined behaviour */ + return 1; + } + + return !((host_addr->s_addr ^ net_addr->s_addr) & + htonl((0xFFFFFFFFu << (32 - bits)) & 0xFFFFFFFFu)); +} + +/** + * @brief Checks if the mask length is valid according to the address family + * (IPv4 or IPv6). + * + * @param[in] family The address family (e.g. AF_INET or AF_INET6) + * + * @param[in] mask The subnet mask (prefix) + * + * @return true if the mask length does not exceed the maximum valid length + * according to the address family (IPv4 or IPv6). + * @return false if the mask length exceeds the maximum valid length + * or there is no match with IPv4 or IPv6 address family. + */ +static bool +masklen_valid(int family, unsigned int mask) +{ + switch (family) { + case AF_INET: + return mask <= 32; + case AF_INET6: + return mask <= 128; + default: + return false; + } +} + +/** + * @brief Extracts address family given a network address. + * + * @param[in] address The network address. + * + * @return The value of the address family if no errors. + * @return -1 in case of errors. + */ +static int +get_address_family(const char *address) +{ + struct addrinfo hints, *ai = NULL; + int rc = -1, rv; + + ZERO_STRUCT(hints); + if (address == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad arguments"); + goto out; + } + + hints.ai_flags = AI_NUMERICHOST; + rv = getaddrinfo(address, NULL, &hints, &ai); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address information - getaddrinfo() failed: %s", + gai_strerror(rv)); + goto out; + } + + rc = ai->ai_family; + freeaddrinfo(ai); + +out: + return rc; +} + +/** + * @brief Tries to match the host address against a CIDR list provided + * by the user. If the host address family is unknown, it can be derived by + * passing -1 as sa_family argument. + * + * It can be also used to validate a CIDR list when the passed address is NULL + * and sa_family is -1. + * + * @param[in] address The host address to verify (NULL to validate CIDR list). + * + * @param[in] addrlist The CIDR list against which the match is being verified. + * The CIDR list can contain both IPv4 and IPv6 addresses + * and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param[in] sa_family The socket address family (e.g. AF_INET or AF_INET6, + * -1 to validate CIDR list or unknown address family). + * + * @usage To validate CIDR list: match_cidr_address_list(NULL, addrlist, -1). + * @usage To verify a match with unknown address family: + * match_cidr_address_list(address, addrlist, -1). + * @return 1 only on positive match. + * @return 0 on negative match or valid CIDR list. + * @return -1 on errors or invalid CIDR list. + */ +int +match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family) +{ + char *list = NULL, *cp = NULL, *a = NULL, *b = NULL, *sp = NULL; + char addr_buffer[64], addr[NI_MAXHOST]; + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + unsigned long mask_len; + size_t addr_len, tmp_len; + int rc = 0, r, ai_family; + + ZERO_STRUCT(try_addr); + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr); + ZERO_STRUCT(match_addr6); + + if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != -1) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid argument: sa_family %d is not valid", + sa_family); + return -1; + } + + if (address != NULL) { + strncpy(addr, address, NI_MAXHOST - 1); + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(addr, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* + * If sa_family is set to -1 and address is not NULL then + * the socket address family should be derived + */ + if (sa_family == -1) { + r = get_address_family(addr); + if (r == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive address family for address " + "\"%.100s\"", + addr); + return -1; + } + sa_family = r; + } + + /* + * Translate host address from dot notation to binary network format + * according to family type, + * i.e. IPv4 (store in in_addr) or IPv6 (store in in6_addr) + */ + if (sa_family == AF_INET) { + if (inet_pton(AF_INET, addr, &try_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr); + return -1; + } + } else if (sa_family == AF_INET6) { + if (inet_pton(AF_INET6, addr, &try_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + sa_family, + addr); + return -1; + } + } + + b = list = strdup(addrlist); + if (b == NULL) { + return -1; + } + + while ((cp = strsep(&list, ",")) != NULL) { + if (*cp == '\0') { + SSH_LOG(SSH_LOG_TRACE, "Empty entry in list \"%.100s\"", b); + rc = -1; + break; + } + + /* + * Stop junk from reaching address translation. +3 for the "/prefix". + * INET6_ADDRSTRLEN is 46 and includes space for '\0' terminator. The + * maximum IPv6 address printable is the one that carries IPv4 too. + * E.g. ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255 is 46 chars + * long ('\0' included) and the maximum prefix length possible is 96. + * This explains why +3. All the other IPv6 addresses with maximum /127 + * prefix length (39 + 4) are covered just by INET6_ADDRSTRLEN itself + */ + addr_len = strlen(cp); + if (addr_len > INET6_ADDRSTRLEN + 3) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" too long: %zu > %d (MAX ALLOWED)", + cp, + addr_len, + INET6_ADDRSTRLEN + 3); + rc = -1; + break; + } + +#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/" + tmp_len = strspn(cp, VALID_CIDR_CHARS); + if (tmp_len != addr_len) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" contains invalid characters " + "-> \"%c\" is an invalid character", + cp, + cp[tmp_len]); + rc = -1; + break; + } +#undef VALID_CIDR_CHARS + + strncpy(addr_buffer, cp, sizeof(addr_buffer) - 1); + sp = strchr(addr_buffer, '/'); + if (sp != NULL) { + *sp = '\0'; + sp++; + mask_len = strtoul(sp, &cp, 10); + if (*sp < '0' || *sp > '9' || *cp != '\0') { + SSH_LOG(SSH_LOG_TRACE, "Error while parsing prefix: %s", sp); + rc = -1; + break; + } + if (mask_len > 128) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid prefix: %lu exceeds the maximum allowed " + "(>128)", + mask_len); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Missing prefix length for list entry \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + ai_family = get_address_family(addr_buffer); + if (ai_family == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address family for \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + if (ai_family == AF_INET) { + if (inet_pton(AF_INET, addr_buffer, &match_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else if (ai_family == AF_INET6) { + if (inet_pton(AF_INET6, addr_buffer, &match_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + ai_family, + addr_buffer); + rc = -1; + break; + } + + if (masklen_valid(ai_family, mask_len) != true) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid mask length %lu for list entry \"%.100s\"", + mask_len, + addr_buffer); + rc = -1; + break; + } + + /* Verify match between host address and network address*/ + if (((ai_family == AF_INET && sa_family == AF_INET) && + cidr_match_4(&try_addr, &match_addr, mask_len)) || + ((ai_family == AF_INET6 && sa_family == AF_INET6) && + cidr_match_6(&try_addr6, &match_addr6, mask_len))) { + rc = 1; + break; + } + } + SAFE_FREE(b); + + return rc; +} +#endif diff --git a/src/misc.c b/src/misc.c index c7a9706c..463e85f2 100644 --- a/src/misc.c +++ b/src/misc.c @@ -27,12 +27,12 @@ #ifndef _WIN32 /* This is needed for a standard getpwuid_r on opensolaris */ #define _POSIX_PTHREAD_SEMANTICS -#include -#include -#include -#include #include #include +#include +#include +#include +#include #endif /* _WIN32 */ @@ -2226,5 +2226,4 @@ int ssh_check_username_syntax(const char *username) return SSH_OK; } - /** @} */ From e33ef71dee48173c885723e17e611ccc84cd506e Mon Sep 17 00:00:00 2001 From: Francesco Rollo Date: Wed, 29 May 2024 18:01:51 +0200 Subject: [PATCH 207/795] tests: Add tests for CIDR matching and predicate matching Signed-off-by: Francesco Rollo Reviewed-by: Jakub Jelen Reviewed-by: Eshan Kelkar --- tests/unittests/CMakeLists.txt | 3 + .../torture_config_match_localnetwork.c | 710 ++++++++++++++++++ 2 files changed, 713 insertions(+) create mode 100644 tests/unittests/torture_config_match_localnetwork.c diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index c053e5b8..a22532a9 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -48,6 +48,9 @@ if (UNIX AND NOT WIN32) torture_pki_ed25519 # requires /dev/null torture_channel + # requires some non-standard API from netdb.h, in.h + # and arpa/inet.h for handling IP addresses + torture_config_match_localnetwork ) if (WITH_SERVER) diff --git a/tests/unittests/torture_config_match_localnetwork.c b/tests/unittests/torture_config_match_localnetwork.c new file mode 100644 index 00000000..e4461ed3 --- /dev/null +++ b/tests/unittests/torture_config_match_localnetwork.c @@ -0,0 +1,710 @@ +#include "config.h" +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "match.c" +#include +#include +#include + +/* This list contains common local subnet addresses and more generic ones */ +#define IPV4_LIST \ + "158.46.192.0/18,213.86.215.224/27,61.67.54.0/23,164.155.128.0/21," \ + "171.10.0.0/16,205.59.221.0/24,122.105.209.48/28,10.0.1.0/24," \ + "130.192.28.0/22,172.16.16.0/16,192.168.0.0/24,169.254.0.0/16" + +#define IPV6_LIST "fe80::/64" + +static int +setup(void **state) +{ + ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int +teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_session session, + const char *file, + const char *string, + int expected) +{ + /* + * Initialisation of ret is not needed, but the compiler is not able to + * understand fail() so it will complain about uninitialised use of ret + * below in assert_ssh_return_code_equal() + */ + int ret = -1; + + /* + * make sure either config file or config string is given, + * not both + */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_config_parse_file(session, file); + } else if (string != NULL) { + ret = ssh_config_parse_string(session, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_ssh_return_code_equal(session, ret, expected); +} + +/** + * @brief converts subnet mask to prefix length (IPv4) + */ +static int +subnet_mask_to_prefix_length_4(struct in_addr subnet_mask) +{ + uint32_t mask; + int prefix_length = 0; + + mask = ntohl(subnet_mask.s_addr); + + /* Count the number of consecutive 1 bits */ + while (mask & 0x80000000) { + prefix_length++; + mask <<= 1; + } + return prefix_length; +} + +/** + * @brief converts subnet mask to prefix length (IPv6) + */ +static int +subnet_mask_to_prefix_length_6(struct in6_addr subnet_mask) +{ + uint32_t *mask = NULL, chunk; + int i, prefix_length = 0; + + mask = (uint32_t *)&subnet_mask.s6_addr[0]; + + /* Count the number of consecutive 1 bits in each 32-bit chunk */ + for (i = 0; i < 4; i++) { + chunk = ntohl(mask[i]); + while (chunk) { + if (chunk & 0x80000000) { + prefix_length++; + chunk <<= 1; + } else { + break; + } + } + } + return prefix_length; +} + +/** + * @brief helper function returning the IPv4 and IPv6 network ID + * (in CIDR format) corresponding to any of the running local interfaces. + * The network interface corresponding to IPv4 and IPv6 network ID may be + * different ("loopback" local interface is ignored). + */ +static int +get_network_id(char *net_id_4, char *net_id_6) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + struct in_addr addr, network_id_4, subnet_mask_4; + struct in6_addr addr6, network_id_6, subnet_mask_6; + struct sockaddr_in netmask; + struct sockaddr_in6 netmask6; + char address[NI_MAXHOST], *a = NULL; + char *network_id_str = NULL, network_id_str6[INET6_ADDRSTRLEN]; + int i, prefix_length, rc, found_4 = 0, found_6 = 0; + socklen_t sa_len; + + ZERO_STRUCT(addr); + ZERO_STRUCT(network_id_4); + ZERO_STRUCT(subnet_mask_4); + + ZERO_STRUCT(addr6); + ZERO_STRUCT(network_id_6); + ZERO_STRUCT(subnet_mask_6); + + if (getifaddrs(&ifaddrs) != 0) { + goto out; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (found_4 && found_6) { + break; + } + + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + /* Skip loopback interface */ + if (strcmp(ifa->ifa_name, "lo") == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + if (found_4) { + continue; + } + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + if (found_6) { + continue; + } + sa_len = sizeof(struct sockaddr_in6); + break; + default: + continue; + } + + rc = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (rc != 0) { + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + + /* Extract subnet mask */ + memcpy(&netmask, ifa->ifa_netmask, sizeof(struct sockaddr_in)); + subnet_mask_4 = netmask.sin_addr; + + rc = inet_pton(AF_INET, address, &addr); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + network_id_4.s_addr = addr.s_addr & subnet_mask_4.s_addr; + + /* Convert network ID to string and compute prefix length */ + network_id_str = inet_ntoa(network_id_4); + if (network_id_str == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_4(subnet_mask_4); + if (prefix_length > 32) { + continue; + } + + snprintf(net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_4 = 1; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(address, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* Extract subnet mask */ + memcpy(&netmask6, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); + subnet_mask_6 = netmask6.sin6_addr; + + rc = inet_pton(AF_INET6, address, &addr6); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + for (i = 0; i < 4; i++) { + network_id_6.s6_addr32[i] = + addr6.s6_addr32[i] & subnet_mask_6.s6_addr32[i]; + } + + /* Convert network ID to string and compute prefix length */ + if (inet_ntop(AF_INET6, + &network_id_6, + network_id_str6, + INET6_ADDRSTRLEN) == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_6(subnet_mask_6); + if (prefix_length > 128) { + continue; + } + + snprintf(net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_6 = 1; + } + } + + freeifaddrs(ifaddrs); + +out: + /* if both net_id_4 and net_id_6 are not set then we should fail */ + return (found_4 && found_6) ? 0 : -1; +} + +/** + * @brief Verify the match between a IPv4/IPv6 address and a IPv4/IPv6 subnet + */ +static void +assert_true_match_cidr(const char *try, + const char *match, + unsigned int mask_len, + int af, + int rv) +{ + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + int r1, r2; + + switch (af) { + case AF_INET: + ZERO_STRUCT(try_addr); + ZERO_STRUCT(match_addr); + + r1 = inet_pton(AF_INET, try, &try_addr); + r2 = inet_pton(AF_INET, match, &match_addr); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_4(&try_addr, &match_addr, mask_len), rv); + break; + case AF_INET6: + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr6); + + r1 = inet_pton(AF_INET6, try, &try_addr6); + r2 = inet_pton(AF_INET6, match, &match_addr6); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_6(&try_addr6, &match_addr6, mask_len), rv); + break; + default: + fail(); + } +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + */ +static void +torture_config_match_localnetwork(void **state, bool use_file) +{ + ssh_session session = *state; + const char *config = NULL; + char config_string[2048]; + char network_id_4[NI_MAXHOST], network_id_6[NI_MAXHOST]; + const char *file = NULL, *string = NULL; + + if (use_file == true) { + file = "libssh_testconfig_localnetwork.tmp"; + } + + if (get_network_id(network_id_4, network_id_6) == -1) { + fail(); + } + + /* IPv4 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* IPv6 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_6); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* Test negate condition */ + snprintf(config_string, + sizeof(config_string), + "Match Host station !localnetwork %s\n" + "\tHostName expected.com\n" + "Host station\n" + "\tHostName negate.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "station"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "negate.com"); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration file. + */ +static void +torture_config_match_localnetwork_file(void **state) +{ + torture_config_match_localnetwork(state, true); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration string. + */ +static void +torture_config_match_localnetwork_string(void **state) +{ + torture_config_match_localnetwork(state, false); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv4 addresses + */ +static void +torture_match_cidr_address_list_ipv4(void **state) +{ + int rc; + (void)state; + + /* Test some valid IPv4 addresses */ + rc = match_cidr_address_list("192.158.50.5", "192.158.50.0/28", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.2.200.200", "10.2.128.0/17", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("192.168.175.40", "192.168.175.0/26", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.31.140.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.3.9.50", "10.3.8.0/23", AF_INET); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("158.15.96.13", "158.12.30.0/12", -1); + assert_int_equal(rc, 1); + + /* Test some valid IPv4 addresses against IPV4_LIST */ + rc = match_cidr_address_list("164.155.128.15", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("158.46.223.71", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("205.59.221.160", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.0.1.254", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.16.58.1", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("169.254.20.28", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + + rc = match_cidr_address_list("255.255.255.255", "0.0.0.0/0", AF_INET); + assert_int_equal(rc, 1); + + /* Test some not matching IPv4 addresses */ + rc = match_cidr_address_list("172.21.0.200", "172.20.240.0/20", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.10.14.100", "10.10.10.0/22", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.150.8", "192.168.150.0/29", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.238.16.50", "10.255.0.0/12", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("172.31.160.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.4.98", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("0.0.0.0", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("122.105.210.57", IPV4_LIST, -1); + assert_int_equal(rc, 0); + + /* Test some invalid input */ + rc = match_cidr_address_list("192.168.1.x", "192.168.1.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("0.168.f2.b8", "172.0.0.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/22", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.16.5/abc1", "172.16.16.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.18.251", "172.16.16.0", AF_INET); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("172.67.3.x", IPV4_LIST, -1); + assert_int_equal(rc, -1); + + /* Test invalid CIDR list */ + rc = match_cidr_address_list(NULL, "192.168.1.0/33", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "10.0.0.0/24 , 192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list( + NULL, + "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128junkdata", + -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv6 addresses + */ +static void +torture_match_cidr_address_list_ipv6(void **state) +{ + /* Test link-local addresses against fe80::/64 */ + int i, rc, valid_addr_len, invalid_addr_len; + const char *valid_addr[] = {"fe80::aadf:b119:507a:986a%abcdef", + "fe80::0000:b418:efd4:5160:0a25%abcdef", + "fe80::c7f5:7f94:4bd9:c35c%abcdef", + "fe80::321f:46c2:0cea:ec54%abcdef", + "fe80::906d:b670:86a2:fd68%abc", + "fe80::b1c2:0000:0039:b598%", + "fe80::07e8:39e6:cb49:9cd4", + "fe80::1%abcdef", + "fe80:0:0:0:202:b3ff:fe1e:8329%abcdef", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329"}; + + const char *invalid_addr[] = {"fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + "2001:0db8:85a3::8a2e:0370:7334%abcdef", + "fd00::adf8:7c21:147c:6c97", + "::1%lo", + "fe80::1:4d88:68a8:1200:f3e7%abcdef"}; + + (void)state; + + /* Test valid link-local addresses */ + valid_addr_len = sizeof(valid_addr) / sizeof(valid_addr[0]); + for (i = 0; i < valid_addr_len; i++) { + rc = match_cidr_address_list(valid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 1); + } + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8328/127", + AF_INET6); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("fe80::aadf:b119:507a:986a%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 1); + + /* Test some invalid input */ + invalid_addr_len = sizeof(invalid_addr) / sizeof(invalid_addr[0]); + for (i = 0; i < invalid_addr_len; i++) { + rc = match_cidr_address_list(invalid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 0); + } + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 0); + + /* Test errors */ + rc = match_cidr_address_list("fe80::be50:09ca::2be3", IPV6_LIST, AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:x:202:b3ff:fe1e:8329", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80::202:ghfc:zzzz:1a49", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329/131", + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329//127", + AF_INET6); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("fe80::ba67:1002:gffx:zz32", IPV6_LIST, -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr_match_4 function works correctly + */ +static void +torture_match_cidr_v4(void **state) +{ + int af = AF_INET; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("192.168.1.20", "192.168.1.0", 24, af, 1); + assert_true_match_cidr("172.31.5.128", "172.31.0.0", 16, af, 1); + assert_true_match_cidr("10.0.0.158", "10.0.0.128", 25, af, 1); + assert_true_match_cidr("192.168.255.250", "192.168.255.248", 29, af, 1); + assert_true_match_cidr("122.105.209.57", "122.105.209.48", 28, af, 1); + assert_true_match_cidr("192.168.100.150", "192.168.64.0", 18, af, 1); + + /* Test some not matching input */ + assert_true_match_cidr("172.16.56.30", "172.16.48.0", 21, af, 0); + assert_true_match_cidr("10.18.5.5", "10.10.4.0", 23, af, 0); + assert_true_match_cidr("172.16.32.50", "172.16.0.0", 19, af, 0); + assert_true_match_cidr("203.0.120.10", "203.0.112.0", 21, af, 0); + assert_true_match_cidr("172.31.112.150", "172.31.96.0", 20, af, 0); + assert_true_match_cidr("198.52.20.200", "198.48.0.0", 14, af, 0); +} + +/** + * @brief Verify the cidr_match_6 function works correctly + */ +static void +torture_match_cidr_v6(void **state) +{ + int af = AF_INET6; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "2001:0db8:85a3:0000::", + 64, + af, + 1); + assert_true_match_cidr("2001:0db8:0000:0042:0000:8a2e:0370:7334", + "2001:0db8:0000::", + 48, + af, + 1); + assert_true_match_cidr("fe80::8a2e:0370:7334", "fe80::", 64, af, 1); + assert_true_match_cidr("fd00::8a2e:0370:7334", "fd00::", 56, af, 1); + assert_true_match_cidr("fe80:0000:0000:0000:0000:0000:fe1e:32ff", + "fe80::", + 96, + af, + 1); + assert_true_match_cidr("2001:0db8:1a2b:3c4d:5e6f:7a8b::18", + "2001:0db8:1a2b:3c4d:5e6f:7a8b::", + 120, + af, + 1); + + /* Test some not matching input */ + assert_true_match_cidr("2001:0db8:1234:5678:9abc:def0:1234:5678", + "2001:0db8:1234:5678::", + 96, + af, + 0); + assert_true_match_cidr("2001:3858:accd::", + "2001:3858:abcd:eaa1::", + 48, + af, + 0); + assert_true_match_cidr("2001:0db8:1234:5678::ff4c", + "2001:0db8:1234:5600::", + 110, + af, + 0); + assert_true_match_cidr("fe80::0001:af12:a1b2:c3d4:e5f7", + "fe80::", + 64, + af, + 0); + assert_true_match_cidr("2001:0db8:84ff:ffff:ffff:ffff:ffff:fffa", + "2001:0db8:8500::", + 80, + af, + 0); + assert_true_match_cidr("::3", "::", 127, af, 0); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_config_match_localnetwork_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_localnetwork_file, + setup, + teardown), + cmocka_unit_test(torture_match_cidr_address_list_ipv4), + cmocka_unit_test(torture_match_cidr_address_list_ipv6), + cmocka_unit_test(torture_match_cidr_v4), + cmocka_unit_test(torture_match_cidr_v6), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup, teardown); + ssh_finalize(); + return rc; +} From cf1e02010cf358b50d7744d61fa2bc8184fbffcb Mon Sep 17 00:00:00 2001 From: Francesco Rollo Date: Fri, 7 Jun 2024 12:58:21 +0200 Subject: [PATCH 208/795] fix: change ipv6 addresses processing for CIDR matching Signed-off-by: Francesco Rollo Reviewed-by: Jakub Jelen --- src/config.c | 1 + src/match.c | 26 +++++++++------- .../torture_config_match_localnetwork.c | 30 ++++++++++--------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/config.c b/src/config.c index 79839007..1c3fd482 100644 --- a/src/config.c +++ b/src/config.c @@ -41,6 +41,7 @@ # include # include # include +# include #endif #include "libssh/config_parser.h" diff --git a/src/match.c b/src/match.c index d04d66e0..65fb156f 100644 --- a/src/match.c +++ b/src/match.c @@ -40,6 +40,11 @@ #include #include #include +#ifndef _WIN32 +#include +#include +#include +#endif #include "libssh/priv.h" @@ -219,29 +224,28 @@ cidr_match_6(struct in6_addr *host_addr, struct in6_addr *net_addr, unsigned int bits) { - const uint32_t *a = host_addr->s6_addr32; - const uint32_t *b = net_addr->s6_addr32; + const uint8_t *a = host_addr->s6_addr; + const uint8_t *b = net_addr->s6_addr; - unsigned int qwords_whole, bits_left; + unsigned int byte_whole, bits_left; - /* The number of complete 32-bit words covered by the prefix */ - qwords_whole = bits / 32; + /* The number of a complete byte covered by the prefix */ + byte_whole = bits / 8; /* - * The number of bits remaining in the incomplete (last) 32-bit word + * The number of bits remaining in the incomplete (last) byte * covered by the prefix */ - bits_left = bits % 32; + bits_left = bits % 8; - if (qwords_whole) { - if (memcmp(a, b, qwords_whole * 4) != 0) { + if (byte_whole) { + if (memcmp(a, b, byte_whole) != 0) { return 0; } } if (bits_left) { - if ((a[qwords_whole] ^ b[qwords_whole]) & - htonl((0xFFFFFFFFu << (32 - bits_left)) & 0xFFFFFFFFu)) { + if ((a[byte_whole] ^ b[byte_whole]) & (0xFFu << (8 - bits_left))) { return 0; } } diff --git a/tests/unittests/torture_config_match_localnetwork.c b/tests/unittests/torture_config_match_localnetwork.c index e4461ed3..33ed05d1 100644 --- a/tests/unittests/torture_config_match_localnetwork.c +++ b/tests/unittests/torture_config_match_localnetwork.c @@ -103,20 +103,22 @@ subnet_mask_to_prefix_length_4(struct in_addr subnet_mask) static int subnet_mask_to_prefix_length_6(struct in6_addr subnet_mask) { - uint32_t *mask = NULL, chunk; - int i, prefix_length = 0; + uint8_t *mask = NULL, chunk; + int i, j, prefix_length = 0; - mask = (uint32_t *)&subnet_mask.s6_addr[0]; + mask = subnet_mask.s6_addr; - /* Count the number of consecutive 1 bits in each 32-bit chunk */ - for (i = 0; i < 4; i++) { - chunk = ntohl(mask[i]); + /* Count the number of consecutive 1 bits in each byte chunk */ + for (i = 0; i < 16; i++) { + chunk = mask[i]; while (chunk) { - if (chunk & 0x80000000) { - prefix_length++; - chunk <<= 1; - } else { - break; + for (j = 0; j < 8; j++) { + if (chunk & 0x80) { + prefix_length++; + chunk <<= 1; + } else { + break; + } } } } @@ -244,9 +246,9 @@ get_network_id(char *net_id_4, char *net_id_6) } /* Calculate the network ID */ - for (i = 0; i < 4; i++) { - network_id_6.s6_addr32[i] = - addr6.s6_addr32[i] & subnet_mask_6.s6_addr32[i]; + for (i = 0; i < 16; i++) { + network_id_6.s6_addr[i] = + addr6.s6_addr[i] & subnet_mask_6.s6_addr[i]; } /* Convert network ID to string and compute prefix length */ From 60aa354c190e3fe126139273a8d9f5fa08fda381 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 13 May 2024 09:42:00 +0530 Subject: [PATCH 209/795] options.c: Fix formatting Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- src/options.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/options.c b/src/options.c index b81ac97f..705a8dae 100644 --- a/src/options.c +++ b/src/options.c @@ -1220,7 +1220,8 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, if (*x > 0 && *x < 768) { ssh_set_error(session, SSH_REQUEST_DENIED, "The provided value (%u) for minimal RSA key " - "size is too small. Use at least 768 bits.", *x); + "size is too small. Use at least 768 bits.", + *x); return -1; } session->opts.rsa_min_size = *x; From 414a276d2b8a369d7a1619a292fd92810c1b12ce Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 13 May 2024 09:21:33 +0530 Subject: [PATCH 210/795] options.c: Use format specifier %d for int %u was being used for printing int type argument which is signed. This commit changes the format specifier to %d. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- src/options.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/options.c b/src/options.c index 705a8dae..01c1650f 100644 --- a/src/options.c +++ b/src/options.c @@ -1219,7 +1219,7 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, int *x = (int *)value; if (*x > 0 && *x < 768) { ssh_set_error(session, SSH_REQUEST_DENIED, - "The provided value (%u) for minimal RSA key " + "The provided value (%d) for minimal RSA key " "size is too small. Use at least 768 bits.", *x); return -1; @@ -2471,7 +2471,7 @@ ssh_bind_options_set(ssh_bind sshbind, if (*x > 0 && *x < 768) { ssh_set_error(sshbind, SSH_REQUEST_DENIED, - "The provided value (%u) for minimal RSA key " + "The provided value (%d) for minimal RSA key " "size is too small. Use at least 768 bits.", *x); return -1; From e1a64c924d81d580fb5b1b7fc2a2d549235e5a23 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Tue, 14 May 2024 15:20:17 +0530 Subject: [PATCH 211/795] options.c: Add validation against negative rsa min size The argument for RSA_MIN_SIZE ssh and sshbind option is of (int *) type, and hence the caller can supply a pointer to a location storing a negative value. The commit adds a check to not allow minimum rsa key size to be set to a negative value. Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- src/options.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/options.c b/src/options.c index 01c1650f..b402f50a 100644 --- a/src/options.c +++ b/src/options.c @@ -1217,6 +1217,14 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type, return -1; } else { int *x = (int *)value; + + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + if (*x > 0 && *x < 768) { ssh_set_error(session, SSH_REQUEST_DENIED, "The provided value (%d) for minimal RSA key " @@ -2468,6 +2476,14 @@ ssh_bind_options_set(ssh_bind sshbind, return -1; } else { int *x = (int *)value; + + if (*x < 0) { + ssh_set_error_invalid(sshbind); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + if (*x > 0 && *x < 768) { ssh_set_error(sshbind, SSH_REQUEST_DENIED, From b73608e7b725f36e6e1f0b8a59994cd2932e20a9 Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Wed, 15 May 2024 00:29:48 +0530 Subject: [PATCH 212/795] torture_options.c: Add test for SSH_OPTIONS_RSA_MIN_SIZE Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- tests/unittests/torture_options.c | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 5b007fa0..7f65a327 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -1954,6 +1954,44 @@ static void torture_options_set_verbosity (void **state) assert_int_not_equal(new_level, 0); } +static void torture_options_set_rsa_min_size(void **state) +{ + ssh_session session = *state; + int min_allowed = 768, key_size, rc; + + /* Check that passing NULL leads to failure */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); +} + #ifdef WITH_SERVER const char template[] = "temp_dir_XXXXXX"; @@ -2868,6 +2906,9 @@ torture_run_tests(void) cmocka_unit_test_setup_teardown(torture_options_set_verbosity, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_rsa_min_size, + setup, + teardown), }; #ifdef WITH_SERVER From b3e40e2bf79c16eee4e529957953f69887b7853a Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Fri, 17 May 2024 20:01:27 +0530 Subject: [PATCH 213/795] torture_options.c: Add test for SSH_BIND_OPTIONS_RSA_MIN_SIZE Signed-off-by: Eshan Kelkar Reviewed-by: Jakub Jelen --- tests/unittests/torture_options.c | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/unittests/torture_options.c b/tests/unittests/torture_options.c index 7f65a327..40d523eb 100644 --- a/tests/unittests/torture_options.c +++ b/tests/unittests/torture_options.c @@ -2360,6 +2360,51 @@ static void torture_bind_options_rsakey(void **state) assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); } +static void torture_bind_options_set_rsa_min_size(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc, min_allowed = 768, key_size; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Check that passing NULL leads to failure */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); +} + #ifdef HAVE_ECC static void torture_bind_options_ecdsakey(void **state) { @@ -2940,6 +2985,9 @@ torture_run_tests(void) cmocka_unit_test_setup_teardown(torture_bind_options_rsakey, sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_rsa_min_size, + sshbind_setup, + sshbind_teardown), #ifdef HAVE_ECC cmocka_unit_test_setup_teardown(torture_bind_options_ecdsakey, sshbind_setup, From 5802017b7fade1c6f1dc5b03aabb36488574cede Mon Sep 17 00:00:00 2001 From: Eshan Kelkar Date: Mon, 27 May 2024 12:06:14 +0530 Subject: [PATCH 214/795] options.c: Use a consistent scheme for datatype in documentation For the data type of the third argument corresponding to the second argument