The branch, master has been updated via 0422a95 test_echo_tcp_sendmsg_recvmsg_fd: add test_tcp_sendmsg_recvmsg_fd_mixed() tests via b35e3ce test_echo_tcp_sendmsg_recvmsg_fd: add test_tcp_sendmsg_recvmsg_fd_different() tests via 2d93f75 test_echo_tcp_sendmsg_recvmsg_fd: also test passing the same socket up to 6 times via 5aa939f test_echo_tcp_sendmsg_recvmsg_fd: split out test_tcp_sendmsg_recvmsg_fd_same() via cbd5910 test_echo_tcp_sendmsg_recvmsg_fd: split out test_tcp_sendmsg_recvmsg_fd_array() via b2e366d tests/echo_srv: allow more than once tcp connection at a time via e896cec swrap: fix fd-passing without 4 padding bytes via 3c7ef47 swrap: fix invalid read in swrap_sendmsg_unix_scm_rights() from 13a6aca swrap: fix copy on write leak of ~38M for every fork.
https://git.samba.org/?p=socket_wrapper.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 0422a957c1b8959c5b48d7188a524296dc9b778b Author: Stefan Metzmacher <me...@samba.org> Date: Fri Feb 5 16:25:43 2021 +0100 test_echo_tcp_sendmsg_recvmsg_fd: add test_tcp_sendmsg_recvmsg_fd_mixed() tests Here we mix sockets and other valid file descriptors. Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit b35e3ce2e2b78fb7a1ccf6d5e011239c14fe27a9 Author: Stefan Metzmacher <me...@samba.org> Date: Fri Feb 5 14:40:45 2021 +0100 test_echo_tcp_sendmsg_recvmsg_fd: add test_tcp_sendmsg_recvmsg_fd_different() tests Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit 2d93f75669a432723c90e20c3f9b2e2abfa55545 Author: Stefan Metzmacher <me...@samba.org> Date: Fri Feb 5 14:20:16 2021 +0100 test_echo_tcp_sendmsg_recvmsg_fd: also test passing the same socket up to 6 times Note SWRAP_MAX_PASSED_FDS is currently 6. This test demonstrates that even 64-bit systems required commit: "swrap: fix fd-passing without 4 padding bytes" Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit 5aa939f2eb3f3ab04b65fde031a0c1f151928121 Author: Stefan Metzmacher <me...@samba.org> Date: Fri Feb 5 14:10:03 2021 +0100 test_echo_tcp_sendmsg_recvmsg_fd: split out test_tcp_sendmsg_recvmsg_fd_same() Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit cbd5910b1814cad1f1c4e9c9e8515f217edcd4d2 Author: Stefan Metzmacher <me...@samba.org> Date: Thu Feb 4 17:04:30 2021 +0100 test_echo_tcp_sendmsg_recvmsg_fd: split out test_tcp_sendmsg_recvmsg_fd_array() This will allow us to test more combinations in order to get better coverage. For now we just test a single fd. Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit b2e366d87bf8b340fe5e0b0186369cda7a94d186 Author: Stefan Metzmacher <me...@samba.org> Date: Fri Feb 5 15:41:27 2021 +0100 tests/echo_srv: allow more than once tcp connection at a time We should not wait for the last connection to disconnect, there would not be any reason to use fork at all. Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit e896cecf1fc08d091d09fa29f1cfbfc22dab89ff Author: Stefan Metzmacher <me...@samba.org> Date: Thu Feb 4 16:20:13 2021 +0100 swrap: fix fd-passing without 4 padding bytes We noticed the problem on 32 bit platforms and sending a single application fd, the hidden pipe-fd doesn't fit into the padding bytes. This can also happen on 64 bit platforms and an even number of application fds. Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> commit 3c7ef4751527bd8c93d5431d9f1e36c4fe648f3d Author: Stefan Metzmacher <me...@samba.org> Date: Fri Feb 5 19:36:26 2021 +0100 swrap: fix invalid read in swrap_sendmsg_unix_scm_rights() Here the fds_out array is larger than the fds_in array, so we can only copy the fds_in array using size_fds_in, leaving the last slot of fds_out untouched, which is filled by fds_out[num_fds_in] = pipefd[0] later. Signed-off-by: Stefan Metzmacher <me...@samba.org> Reviewed-by: Andreas Schneider <a...@samba.org> ----------------------------------------------------------------------- Summary of changes: src/socket_wrapper.c | 71 ++++- tests/echo_srv.c | 2 +- tests/test_echo_tcp_sendmsg_recvmsg_fd.c | 510 +++++++++++++++++++++++++++---- 3 files changed, 518 insertions(+), 65 deletions(-) Changeset truncated at 500 lines: diff --git a/src/socket_wrapper.c b/src/socket_wrapper.c index 43a5892..ece3493 100644 --- a/src/socket_wrapper.c +++ b/src/socket_wrapper.c @@ -5450,7 +5450,7 @@ static int swrap_sendmsg_unix_scm_rights(const struct cmsghdr *cmsg, *new_cmsg = *cmsg; __fds_out.p = CMSG_DATA(new_cmsg); fds_out = __fds_out.fds; - memcpy(fds_out, fds_in, size_fds_out); + memcpy(fds_out, fds_in, size_fds_in); new_cmsg->cmsg_len = cmsg->cmsg_len; for (i = 0; i < num_fds_in; i++) { @@ -5962,8 +5962,48 @@ static ssize_t swrap_sendmsg_after_unix(struct msghdr *msg_tmp, static int swrap_recvmsg_before_unix(struct msghdr *msg_in, struct msghdr *msg_tmp) { +#ifdef HAVE_STRUCT_MSGHDR_MSG_CONTROL + const size_t cm_extra_space = CMSG_SPACE(sizeof(int)); + uint8_t *cm_data = NULL; + size_t cm_data_space = 0; + + *msg_tmp = *msg_in; + + SWRAP_LOG(SWRAP_LOG_TRACE, + "msg_in->msg_controllen=%zu", + (size_t)msg_in->msg_controllen); + + /* Nothing to do */ + if (msg_in->msg_controllen == 0 || msg_in->msg_control == NULL) { + return 0; + } + + /* + * We need to give the kernel a bit more space in order + * recv the pipe fd, added by swrap_sendmsg_before_unix()). + * swrap_recvmsg_after_unix() will hide it again. + */ + cm_data_space = msg_in->msg_controllen; + if (cm_data_space < (INT32_MAX - cm_extra_space)) { + cm_data_space += cm_extra_space; + } + cm_data = calloc(1, cm_data_space); + if (cm_data == NULL) { + return -1; + } + memcpy(cm_data, msg_in->msg_control, msg_in->msg_controllen); + + msg_tmp->msg_controllen = cm_data_space; + msg_tmp->msg_control = cm_data; + + SWRAP_LOG(SWRAP_LOG_TRACE, + "msg_tmp->msg_controllen=%zu", + (size_t)msg_tmp->msg_controllen); + return 0; +#else /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ *msg_tmp = *msg_in; return 0; +#endif /* ! HAVE_STRUCT_MSGHDR_MSG_CONTROL */ } static ssize_t swrap_recvmsg_after_unix(struct msghdr *msg_tmp, @@ -5976,9 +6016,14 @@ static ssize_t swrap_recvmsg_after_unix(struct msghdr *msg_tmp, size_t cm_data_space = 0; int rc = -1; + SWRAP_LOG(SWRAP_LOG_TRACE, + "msg_tmp->msg_controllen=%zu", + (size_t)msg_tmp->msg_controllen); + /* Nothing to do */ if (msg_tmp->msg_controllen == 0 || msg_tmp->msg_control == NULL) { - goto done; + *msg_out = *msg_tmp; + return ret; } for (cmsg = CMSG_FIRSTHDR(msg_tmp); @@ -6006,15 +6051,27 @@ static ssize_t swrap_recvmsg_after_unix(struct msghdr *msg_tmp, } /* - * msg_tmp->msg_control is still the buffer of the caller. + * msg_tmp->msg_control was created by swrap_recvmsg_before_unix() + * and msg_out->msg_control is still the buffer of the caller. */ - memcpy(msg_tmp->msg_control, cm_data, cm_data_space); - msg_tmp->msg_controllen = cm_data_space; + SAFE_FREE(msg_tmp->msg_control); + msg_tmp->msg_control = msg_out->msg_control; + msg_tmp->msg_controllen = msg_out->msg_controllen; + *msg_out = *msg_tmp; + + cm_data_space = MIN(cm_data_space, msg_out->msg_controllen); + memcpy(msg_out->msg_control, cm_data, cm_data_space); + msg_out->msg_controllen = cm_data_space; SAFE_FREE(cm_data); -done: -#endif /* ! HAVE_STRUCT_MSGHDR_MSG_CONTROL */ + + SWRAP_LOG(SWRAP_LOG_TRACE, + "msg_out->msg_controllen=%zu", + (size_t)msg_out->msg_controllen); + return ret; +#else /* HAVE_STRUCT_MSGHDR_MSG_CONTROL */ *msg_out = *msg_tmp; return ret; +#endif /* ! HAVE_STRUCT_MSGHDR_MSG_CONTROL */ } static ssize_t swrap_sendmsg_before(int fd, diff --git a/tests/echo_srv.c b/tests/echo_srv.c index 0aefa9a..fbb6637 100644 --- a/tests/echo_srv.c +++ b/tests/echo_srv.c @@ -537,6 +537,7 @@ static void echo_tcp(int sock) pid_t pid; while (1) { + waitpid(-1, NULL, WNOHANG); s = accept(sock, &addr.sa.s, &addr.sa_socklen); if (s == -1 && errno == ECONNABORTED) { continue; @@ -575,7 +576,6 @@ static void echo_tcp(int sock) close(s); exit(0); } - waitpid(-1, NULL, 0); close(s); } diff --git a/tests/test_echo_tcp_sendmsg_recvmsg_fd.c b/tests/test_echo_tcp_sendmsg_recvmsg_fd.c index 8ae1a60..215d5ff 100644 --- a/tests/test_echo_tcp_sendmsg_recvmsg_fd.c +++ b/tests/test_echo_tcp_sendmsg_recvmsg_fd.c @@ -33,33 +33,86 @@ static int teardown(void **state) return 0; } -static void test_tcp_sendmsg_recvmsg_fd(void **state) +struct test_fd { + int fd; + struct torture_address sock_addr; + struct torture_address peer_addr; +}; + +static int test_fill_test_fd(struct test_fd *tfd, int fd) { - struct torture_address addr = { - .sa_socklen = sizeof(struct sockaddr_in), + struct torture_address saddr = { + .sa_socklen = sizeof(struct sockaddr_storage), }; - int pass_sock_fd; - int sv[2]; - int child_fd, parent_fd; - pid_t pid; - int rc; + struct torture_address paddr = { + .sa_socklen = sizeof(struct sockaddr_storage), + }; + int ret; - (void) state; /* unused */ + *tfd = (struct test_fd) { .fd = fd, }; - /* create socket file descriptor to be passed */ - pass_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - assert_int_not_equal(pass_sock_fd, -1); + ret = getsockname(fd, &saddr.sa.s, &saddr.sa_socklen); + if (ret == -1 && errno == ENOTSOCK) { + return 0; + } + if (ret == -1) { + return ret; + } - addr.sa.in.sin_family = AF_INET; - addr.sa.in.sin_port = htons(torture_server_port()); + ret = getpeername(fd, &paddr.sa.s, &paddr.sa_socklen); + if (ret == -1) { + return ret; + } - rc = inet_pton(addr.sa.in.sin_family, - torture_server_address(AF_INET), - &addr.sa.in.sin_addr); - assert_int_equal(rc, 1); + tfd->sock_addr = saddr; + tfd->peer_addr = paddr; + return 0; +} - rc = connect(pass_sock_fd, &addr.sa.s, addr.sa_socklen); - assert_int_equal(rc, 0); +static void _assert_torture_address_equal(const struct torture_address *ga, + const struct torture_address *ea, + const char * const file, + const int line) +{ + _assert_int_equal(ga->sa_socklen, ea->sa_socklen, file, line); + if (ga->sa_socklen == 0) { + return; + } + _assert_memory_equal(&ga->sa, &ea->sa, ga->sa_socklen, file, line); +} + +#define assert_test_fd_equal(gfd, efd) \ + _assert_test_fd_equal(gfd, efd, __FILE__, __LINE__) + +static void _assert_test_fd_equal(const struct test_fd *gfd, + const struct test_fd *efd, + const char * const file, + const int line) +{ + if (efd->fd == -1) { + _assert_int_equal(gfd->fd, -1, file, line); + return; + } + + _assert_int_not_equal(gfd->fd, -1, file, line); + + _assert_torture_address_equal(&gfd->sock_addr, &efd->sock_addr, file, line); + _assert_torture_address_equal(&gfd->peer_addr, &efd->peer_addr, file, line); +} + +static void test_tcp_sendmsg_recvmsg_fd_array(const int *fds, size_t num_fds) +{ + struct test_fd tfds[num_fds]; + size_t idx; + int sv[2]; + int child_fd, parent_fd; + pid_t pid; + int rc; + + for (idx = 0; idx < num_fds; idx++) { + rc = test_fill_test_fd(&tfds[idx], fds[idx]); + assert_int_equal(rc, 0); + } /* create unix domain socket stream */ rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, sv); @@ -73,17 +126,11 @@ static void test_tcp_sendmsg_recvmsg_fd(void **state) if (pid == 0) { /* Child */ - struct torture_address peer_addr = { - .sa_socklen = sizeof(struct sockaddr_in), - }; struct msghdr child_msg; - char cmsgbuf[CMSG_SPACE(sizeof(int))]; + int recv_fd_array[num_fds]; + char cmsgbuf[CMSG_SPACE(sizeof(int)*num_fds)]; struct cmsghdr *cmsg; - int rcv_sock_fd, port; ssize_t ret; - char send_buf[64] = {0}; - char recv_buf[64] = {0}; - char ipstr[INET_ADDRSTRLEN]; char byte = { 0, }; struct iovec iov; @@ -104,45 +151,70 @@ static void test_tcp_sendmsg_recvmsg_fd(void **state) assert_non_null(cmsg); assert_int_equal(cmsg->cmsg_type, SCM_RIGHTS); - memcpy(&rcv_sock_fd, CMSG_DATA(cmsg), sizeof(rcv_sock_fd)); - assert_int_not_equal(rcv_sock_fd, -1); - - /* extract peer info from received socket fd */ - ret = getpeername(rcv_sock_fd, &peer_addr.sa.s, &peer_addr.sa_socklen); - assert_int_not_equal(ret, -1); - - port = ntohs(peer_addr.sa.in.sin_port); - inet_ntop(AF_INET, &peer_addr.sa.in.sin_addr, ipstr, sizeof(ipstr)); - - /* check whether it is the same socket previously connected */ - assert_string_equal(ipstr, torture_server_address(AF_INET)); - assert_int_equal(port, torture_server_port()); + memcpy(recv_fd_array, CMSG_DATA(cmsg), sizeof(int)*num_fds); + for (idx = 0; idx < num_fds; idx++) { + assert_int_not_equal(recv_fd_array[idx], -1); + } - snprintf(send_buf, sizeof(send_buf), "packet"); + for (idx = 0; idx < num_fds; idx++) { + struct test_fd recv_tfd = { .fd = -1, }; - ret = write(rcv_sock_fd, - send_buf, - sizeof(send_buf)); - assert_int_not_equal(ret, -1); + ret = test_fill_test_fd(&recv_tfd, recv_fd_array[idx]); + assert_int_equal(ret, 0); - ret = read(rcv_sock_fd, - recv_buf, - sizeof(recv_buf)); - assert_int_not_equal(ret, -1); + assert_test_fd_equal(&recv_tfd, &tfds[idx]); + } - assert_memory_equal(send_buf, recv_buf, sizeof(send_buf)); + for (idx = 0; idx < num_fds; idx++) { + int recv_fd = recv_fd_array[idx]; + char send_buf[64] = {0,}; + char recv_buf[64] = {0,}; + + if (tfds[idx].sock_addr.sa_socklen == 0) { + /* + * skip fds not belonging to + * a socket. + */ + continue; + } + + if (tfds[idx].sock_addr.sa.s.sa_family == AF_UNIX) { + /* + * skip fds not belonging to + * a socket. + */ + continue; + } + + snprintf(send_buf, sizeof(send_buf), "packet"); + + ret = write(recv_fd, + send_buf, + sizeof(send_buf)); + assert_int_not_equal(ret, -1); + + ret = read(recv_fd, + recv_buf, + sizeof(recv_buf)); + assert_int_not_equal(ret, -1); + + assert_memory_equal(send_buf, recv_buf, sizeof(send_buf)); + } exit(0); } else { /* Parent */ struct msghdr parent_msg; struct cmsghdr *cmsg; - char cmsgbuf[CMSG_SPACE(sizeof(pass_sock_fd))]; + char cmsgbuf[CMSG_SPACE(sizeof(int)*num_fds)]; + int pass_fd_array[num_fds]; char byte = '!'; struct iovec iov; int cs; - (void) state; /* unused */ + for (idx = 0; idx < num_fds; idx++) { + pass_fd_array[idx] = tfds[idx].fd; + } iov.iov_base = &byte; iov.iov_len = 1; @@ -156,10 +228,10 @@ static void test_tcp_sendmsg_recvmsg_fd(void **state) cmsg = CMSG_FIRSTHDR(&parent_msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(pass_sock_fd)); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)*num_fds); /* place previously connected socket fd as ancillary data */ - memcpy(CMSG_DATA(cmsg), &pass_sock_fd, sizeof(pass_sock_fd)); + memcpy(CMSG_DATA(cmsg), pass_fd_array, sizeof(int)*num_fds); parent_msg.msg_controllen = cmsg->cmsg_len; rc = sendmsg(parent_fd, &parent_msg, 0); @@ -173,13 +245,337 @@ static void test_tcp_sendmsg_recvmsg_fd(void **state) } } +static void test_tcp_sendmsg_recvmsg_fd_same(size_t num_fds) +{ + struct torture_address addr = { + .sa_socklen = sizeof(struct sockaddr_in), + }; + int pass_sock_fd; + int fd_array[num_fds]; + size_t idx; + int rc; + + /* create socket file descriptor to be passed */ + pass_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert_int_not_equal(pass_sock_fd, -1); + + addr.sa.in.sin_family = AF_INET; + addr.sa.in.sin_port = htons(torture_server_port()); + + rc = inet_pton(addr.sa.in.sin_family, + torture_server_address(AF_INET), + &addr.sa.in.sin_addr); + assert_int_equal(rc, 1); + + rc = connect(pass_sock_fd, &addr.sa.s, addr.sa_socklen); + assert_int_equal(rc, 0); + + for (idx = 0; idx < num_fds; idx++) { + fd_array[idx] = pass_sock_fd; + } + + test_tcp_sendmsg_recvmsg_fd_array(fd_array, num_fds); + + close(pass_sock_fd); +} + +static void test_tcp_sendmsg_recvmsg_fd_1(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_same(1); +} + +static void test_tcp_sendmsg_recvmsg_fd_2s(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_same(2); +} + +static void test_tcp_sendmsg_recvmsg_fd_3s(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_same(3); +} + +static void test_tcp_sendmsg_recvmsg_fd_4s(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_same(4); +} + +static void test_tcp_sendmsg_recvmsg_fd_5s(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_same(5); +} + +static void test_tcp_sendmsg_recvmsg_fd_6s(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_same(6); +} + +static void test_tcp_sendmsg_recvmsg_fd_different(size_t num_fds) +{ + int fd_array[num_fds]; + size_t idx; + + for (idx = 0; idx < num_fds; idx++) { + struct torture_address addr = { + .sa_socklen = sizeof(struct sockaddr_in), + }; + int pass_sock_fd; + int rc; + + /* create socket file descriptor to be passed */ + pass_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert_int_not_equal(pass_sock_fd, -1); + + addr.sa.in.sin_family = AF_INET; + addr.sa.in.sin_port = htons(torture_server_port()); + + rc = inet_pton(addr.sa.in.sin_family, + torture_server_address(AF_INET), + &addr.sa.in.sin_addr); + assert_int_equal(rc, 1); + + rc = connect(pass_sock_fd, &addr.sa.s, addr.sa_socklen); + assert_int_equal(rc, 0); + + fd_array[idx] = pass_sock_fd; + } + + test_tcp_sendmsg_recvmsg_fd_array(fd_array, num_fds); + + for (idx = 0; idx < num_fds; idx++) { + close(fd_array[idx]); + } +} + +static void test_tcp_sendmsg_recvmsg_fd_2d(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_different(2); +} + +static void test_tcp_sendmsg_recvmsg_fd_3d(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_different(3); +} + +static void test_tcp_sendmsg_recvmsg_fd_4d(void **state) +{ + (void) state; /* unused */ + test_tcp_sendmsg_recvmsg_fd_different(4); +} + -- Socket Wrapper Repository