diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index 8f2f97e6..35ba2609 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -33,6 +33,7 @@ fuzzer(ssh_client_config_fuzzer) fuzzer(ssh_known_hosts_fuzzer) fuzzer(ssh_privkey_fuzzer) fuzzer(ssh_pubkey_fuzzer) +fuzzer(ssh_sftp_attr_fuzzer) fuzzer(ssh_sshsig_fuzzer) if (WITH_SERVER) fuzzer(ssh_server_fuzzer) diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer.c b/tests/fuzz/ssh_sftp_attr_fuzzer.c new file mode 100644 index 00000000..e843cc37 --- /dev/null +++ b/tests/fuzz/ssh_sftp_attr_fuzzer.c @@ -0,0 +1,131 @@ +/* + * Copyright 2026 libssh authors + * + * 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 +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" + +#include "nallocinc.c" + +/* SFTP protocol version constants */ +#define SFTP_PROTOCOL_VERSION_3 3 +#define SFTP_PROTOCOL_VERSION_4 4 + +/* Flags for sftp_parse_attr expectname parameter */ +#define SFTP_EXPECT_NAME 1 +#define SFTP_NO_NAME 0 + +/* + * Helper to create a minimal sftp_session for fuzzing. + * We don't use sftp_new() as it requires a real SSH connection. + */ +static sftp_session create_minimal_sftp_session(ssh_session session) +{ + sftp_session sftp; + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + return NULL; + } + sftp->session = session; + + return sftp; +} + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_attributes attr = NULL; + int versions[] = { + SFTP_PROTOCOL_VERSION_3, SFTP_PROTOCOL_VERSION_3, + SFTP_PROTOCOL_VERSION_4, SFTP_PROTOCOL_VERSION_4 + }; + int expectnames[] = {SFTP_NO_NAME, SFTP_EXPECT_NAME, SFTP_NO_NAME, SFTP_EXPECT_NAME}; + size_t i; + + /* Minimum bytes for a valid SFTP message */ + if (size == 0) { + return 0; + } + + assert(nalloc_start(data, size) > 0); + + /* Allocate shared resources once for all test iterations */ + session = ssh_new(); + if (session == NULL) { + goto cleanup; + } + + sftp = create_minimal_sftp_session(session); + if (sftp == NULL) { + goto cleanup; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + goto cleanup; + } + + /* Main fuzzing target: sftp_parse_attr */ + /* Parses untrusted SFTP messages from client */ + /* Test all combinations (v3/v4, with/without name) */ + for (i = 0; i < (sizeof(versions) / sizeof(versions[0])); i++) { + sftp->version = versions[i]; + + /* Reset and repopulate buffer for each iteration */ + ssh_buffer_reinit(buffer); + if (ssh_buffer_add_data(buffer, data, size) == SSH_OK) { + attr = sftp_parse_attr(sftp, buffer, expectnames[i]); + sftp_attributes_free(attr); + attr = NULL; + } + } + +cleanup: + ssh_buffer_free(buffer); + free(sftp); + ssh_free(session); + nalloc_end(); + + return 0; +} diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/04069f01e1b3cead6a975b05c89ed937ecdccd8b b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/04069f01e1b3cead6a975b05c89ed937ecdccd8b new file mode 100644 index 00000000..0bf30cc5 Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/04069f01e1b3cead6a975b05c89ed937ecdccd8b differ diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/3c7d22add37e715968d08d7ecdc485468224e22e b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/3c7d22add37e715968d08d7ecdc485468224e22e new file mode 100644 index 00000000..6ea12548 Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/3c7d22add37e715968d08d7ecdc485468224e22e differ diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/42e5dec24d93b7e96782a637f3bba4d4c81694c0 b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/42e5dec24d93b7e96782a637f3bba4d4c81694c0 new file mode 100644 index 00000000..208edefe Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/42e5dec24d93b7e96782a637f3bba4d4c81694c0 differ diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/8e2f0674d9cde8f8d4420afcc510b46efa401c34 b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/8e2f0674d9cde8f8d4420afcc510b46efa401c34 new file mode 100644 index 00000000..a0fd5efc Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/8e2f0674d9cde8f8d4420afcc510b46efa401c34 differ diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/9069ca78e7450a285173431b3e52c5c25299e473 b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/9069ca78e7450a285173431b3e52c5c25299e473 new file mode 100644 index 00000000..593f4708 Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/9069ca78e7450a285173431b3e52c5c25299e473 differ diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/b2527d28f012dfaf7fa8beae32c05c4a0c8a41f3 b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/b2527d28f012dfaf7fa8beae32c05c4a0c8a41f3 new file mode 100644 index 00000000..b14dc385 Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/b2527d28f012dfaf7fa8beae32c05c4a0c8a41f3 differ diff --git a/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/be142bc50527cebc6e024382c1609e7ab38cd236 b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/be142bc50527cebc6e024382c1609e7ab38cd236 new file mode 100644 index 00000000..2b092826 Binary files /dev/null and b/tests/fuzz/ssh_sftp_attr_fuzzer_corpus/be142bc50527cebc6e024382c1609e7ab38cd236 differ