From 7d08a51ec529edc5ccf7978d403ec4ef151e82a7 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 22 Dec 2025 20:59:11 +0100 Subject: [PATCH 1/2] CVE-2026-0968: sftp: Sanitize input handling in sftp_parse_longname() Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider (cherry picked from commit 20856f44c146468c830da61dcbbbaa8ce71e390b) --- src/sftp.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/sftp.c b/src/sftp.c index 60e591f0..70f9ed15 100644 --- a/src/sftp.c +++ b/src/sftp.c @@ -1289,13 +1289,18 @@ static char *sftp_parse_longname(const char *longname, const char *p, *q; size_t len, field = 0; + if (longname == NULL || longname_field < SFTP_LONGNAME_PERM || + longname_field > SFTP_LONGNAME_NAME) { + return NULL; + } + p = longname; /* Find the beginning of the field which is specified by sftp_longname_field_e. */ - while(field != longname_field) { + while(*p != '\0' && field != longname_field) { if(isspace(*p)) { field++; p++; - while(*p && isspace(*p)) { + while(*p != '\0' && isspace(*p)) { p++; } } else { @@ -1303,8 +1308,13 @@ static char *sftp_parse_longname(const char *longname, } } + /* If we reached NULL before we got our field fail */ + if (field != longname_field) { + return NULL; + } + q = p; - while (! isspace(*q)) { + while (*q != '\0' && !isspace(*q)) { q++; } -- 2.53.0 From ccedc7907ebe2c1a977b739b85034953de582fb3 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Mon, 22 Dec 2025 21:00:03 +0100 Subject: [PATCH 2/2] CVE-2026-0968 tests: Reproducer for invalid longname data Signed-off-by: Jakub Jelen Reviewed-by: Andreas Schneider (cherry picked from commit 90a5d8f47399e8db61b56793cd21476ff6a528e0) --- tests/unittests/CMakeLists.txt | 7 +++ tests/unittests/torture_unit_sftp.c | 86 +++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 tests/unittests/torture_unit_sftp.c diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 28458d49..658f14bc 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -95,6 +95,13 @@ if (UNIX AND NOT WIN32) endif (WITH_SERVER) endif (UNIX AND NOT WIN32) +if (WITH_SFTP) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_unit_sftp + ) +endif (WITH_SFTP) + foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) add_cmocka_test(${_UNIT_TEST} SOURCES ${_UNIT_TEST}.c diff --git a/tests/unittests/torture_unit_sftp.c b/tests/unittests/torture_unit_sftp.c new file mode 100644 index 00000000..8cdaba8e --- /dev/null +++ b/tests/unittests/torture_unit_sftp.c @@ -0,0 +1,86 @@ +#include "config.h" + +#include "sftp.c" +#include "torture.h" + +#define LIBSSH_STATIC + +static void test_sftp_parse_longname(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Valid example from SFTP draft, page 18: + * https://datatracker.ietf.org/doc/draft-spaghetti-sshm-filexfer/ + */ + lname = "-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_OWNER); + assert_string_equal(value, "mjos"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_GROUP); + assert_string_equal(value, "staff"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_SIZE); + assert_string_equal(value, "348911"); + free(value); + /* This function is broken further as the date contains space which breaks + * the parsing altogether */ + value = sftp_parse_longname(lname, SFTP_LONGNAME_DATE); + assert_string_equal(value, "Mar"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_TIME); + assert_string_equal(value, "25"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_string_equal(value, "14:29"); + free(value); +} + +static void test_sftp_parse_longname_invalid(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Invalid inputs should not crash + */ + lname = NULL; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_null(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = ""; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, ""); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = "-rwxr-xr-x 1"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sftp_parse_longname), + cmocka_unit_test(test_sftp_parse_longname_invalid), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} -- 2.53.0