From b0b7b8a45e85dfc598aeaf79bb00f39bdbd4b3ae Mon Sep 17 00:00:00 2001 From: Francois Date: Fri, 6 Nov 2026 00:41:47 +0100 Subject: [PATCH] Fix VSOCK address reuse on reconnect connect_vsock() overwrites the colon in sshfs.vsock, so subsequent connection attempts cannot parse the saved address, even when the first socket() or connect() call fails. Let strtoul() stop at the colon and keep the address const, preserving the existing CID and port parsing behavior. Add Linux unit tests that reuse the same address after a successful connection, a socket() failure and a connect() failure. Mock the socket calls so no VSOCK device or FUSE mount is needed. --- sshfs.c | 5 +-- test/meson.build | 9 ++++ test/test_vsock.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 test/test_vsock.c diff --git a/sshfs.c b/sshfs.c index 4cdf1223..0682be9a 100644 --- a/sshfs.c +++ b/sshfs.c @@ -1322,7 +1322,7 @@ static int connect_to(struct conn *conn, char *host, char *port) return 0; } -static int connect_vsock(struct conn *conn, char *vsock) +static int connect_vsock(struct conn *conn, const char *vsock) { #ifndef __linux__ fprintf(stderr, "vsock is not available\n"); @@ -1333,14 +1333,13 @@ static int connect_vsock(struct conn *conn, char *vsock) struct sockaddr_vm addr; unsigned int cid; unsigned int port; - char *delim; + const char *delim; delim = strchr(vsock, ':'); if (delim == NULL) { fprintf(stderr, "invalid vsock, expecting CID:PORT\n"); return -1; } - *delim = '\0'; errno = 0; cid = strtoul(vsock, NULL, 10); if (errno) { diff --git a/test/meson.build b/test/meson.build index 18ac05ee..9e8934f5 100644 --- a/test/meson.build +++ b/test/meson.build @@ -19,6 +19,15 @@ test_cache = executable('test_cache', 'test_cache.c', install: false) test('cache', test_cache, suite: 'unit') +if host_machine.system() == 'linux' + test_vsock = executable('test_vsock', ['test_vsock.c', '../cache.c'], + include_directories: include_dirs, + dependencies: sshfs_deps, + c_args: ['-DFUSE_USE_VERSION=31'], + install: false) + test('vsock', test_vsock, suite: 'unit') +endif + # Provide something helpful when running 'ninja test' wrong_cmd = executable('wrong_command', 'wrong_command.c', install: false) diff --git a/test/test_vsock.c b/test/test_vsock.c new file mode 100644 index 00000000..7c72ba75 --- /dev/null +++ b/test/test_vsock.c @@ -0,0 +1,106 @@ +/* Exercise the real connection helper without a VSOCK device or FUSE mount. */ +#define _GNU_SOURCE +#include +#include + +static int test_socket(int domain, int type, int protocol); +static int test_connect(int fd, const struct sockaddr *addr, socklen_t len); +static int test_close(int fd); +int sshfs_main(int argc, char *argv[]); + +#define socket test_socket +#define connect test_connect +#define close test_close +#define main sshfs_main +#include "../sshfs.c" +#undef main +#undef close +#undef connect +#undef socket + +#define TEST_FD 42 + +enum failure { + NO_FAILURE, + SOCKET_FAILURE, + CONNECT_FAILURE, +}; + +static enum failure failure; +static int socket_calls; +static int connect_calls; +static int close_calls; + +static int test_socket(int domain, int type, int protocol) +{ + g_assert_cmpint(domain, ==, AF_VSOCK); + g_assert_cmpint(type, ==, SOCK_STREAM); + g_assert_cmpint(protocol, ==, 0); + socket_calls++; + if (failure == SOCKET_FAILURE && socket_calls == 1) { + errno = EAFNOSUPPORT; + return -1; + } + return TEST_FD; +} + +static int test_connect(int fd, const struct sockaddr *addr, socklen_t len) +{ + const struct sockaddr_vm *vm = (const struct sockaddr_vm *) addr; + + g_assert_cmpint(fd, ==, TEST_FD); + g_assert_cmpuint(len, ==, sizeof(*vm)); + g_assert_cmpint(vm->svm_family, ==, AF_VSOCK); + g_assert_cmpuint(vm->svm_cid, ==, 3); + g_assert_cmpuint(vm->svm_port, ==, 12345); + connect_calls++; + if (failure == CONNECT_FAILURE && connect_calls == 1) { + errno = ECONNREFUSED; + return -1; + } + return 0; +} + +static int test_close(int fd) +{ + g_assert_cmpint(fd, ==, TEST_FD); + close_calls++; + return 0; +} + +static void test_reuse_address(gconstpointer data) +{ + char address[] = "3:12345"; + struct conn first = { .rfd = -1, .wfd = -1 }; + struct conn second = { .rfd = -1, .wfd = -1 }; + int first_result; + int second_result; + + failure = GPOINTER_TO_INT(data); + socket_calls = connect_calls = close_calls = 0; + first_result = connect_vsock(&first, address); + second_result = connect_vsock(&second, address); + + g_assert_cmpint(first_result, ==, failure == NO_FAILURE ? 0 : -1); + g_assert_cmpint(second_result, ==, 0); + g_assert_cmpstr(address, ==, "3:12345"); + g_assert_cmpint(first.rfd, ==, failure == NO_FAILURE ? TEST_FD : -1); + g_assert_cmpint(first.wfd, ==, first.rfd); + g_assert_cmpint(second.rfd, ==, TEST_FD); + g_assert_cmpint(second.wfd, ==, TEST_FD); + g_assert_cmpint(socket_calls, ==, 2); + g_assert_cmpint(connect_calls, ==, failure == SOCKET_FAILURE ? 1 : 2); + g_assert_cmpint(close_calls, ==, failure == CONNECT_FAILURE ? 1 : 0); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_data_func("/vsock/reuse-after-success", + GINT_TO_POINTER(NO_FAILURE), test_reuse_address); + g_test_add_data_func("/vsock/retry-after-socket-failure", + GINT_TO_POINTER(SOCKET_FAILURE), test_reuse_address); + g_test_add_data_func("/vsock/retry-after-connect-failure", + GINT_TO_POINTER(CONNECT_FAILURE), test_reuse_address); + return g_test_run(); +}