1 /* $NetBSD: t_fdpass.c,v 1.4 2026/10/02 01:31:31 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2026 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: t_fdpass.c,v 1.4 2026/10/02 01:31:31 riastradh Exp $"); 31 32 #include <sys/param.h> /* needed by sys/mbuf.h */ 33 34 #include <sys/socket.h> 35 36 #include <atf-c.h> 37 #include <errno.h> 38 #include <fcntl.h> 39 40 #include <rump/rump.h> 41 #include <rump/rump_syscalls.h> 42 43 #include "h_macros.h" 44 45 /* 46 * XXX We include <sys/mbuf.h> last to avoid conflict with atf-c.h over 47 * m_type. 48 */ 49 #include <sys/mbuf.h> /* MHLEN */ 50 51 static unsigned 52 countfds(void) 53 { 54 int fd, nfds, maxfd; 55 56 RL(maxfd = rump_sys_fcntl(-1, F_MAXFD)); 57 for (fd = nfds = 0; fd <= maxfd; fd++) { 58 if (rump_sys_fcntl(fd, F_GETFL) != -1) 59 nfds++; 60 } 61 62 return nfds; 63 } 64 65 enum { 66 MAXNFDS = 32, 67 }; 68 69 static void 70 test_pr60832(const int fds[static 0], unsigned nfds) 71 { 72 static char buf[4096]; 73 int sock[2]; 74 socklen_t optlen; 75 int sndbuf; 76 unsigned resid, clen, nfds_before, nfds_after; 77 ssize_t nsent, nrcvd; 78 size_t total = 0; 79 80 /* 81 * Create a socket pair, nonblocking so that we fail promptly 82 * when buffers are full instead of hanging until timeout. 83 */ 84 RL(rump_sys_socketpair(AF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0, sock)); 85 86 /* 87 * Count the number of file descriptors in this process so we 88 * make sure we receive the right total number of them at the 89 * end. 90 */ 91 nfds_before = countfds(); 92 93 /* 94 * Find how much space is required for the control message. 95 * 96 * Note: We use CMSG_SPACE for struct msghdr::msg_controllen; 97 * for struct cmsghdr::cmsg_len below, we will use CMSG_LEN. 98 * Confused? Thank the cmsg(3) API designers... 99 */ 100 ATF_REQUIRE(nfds <= MAXNFDS); 101 clen = CMSG_SPACE(nfds * sizeof(int)); 102 103 /* 104 * Find out how much we can write to the socket (SO_SNDBUF) 105 * without blocking. 106 * 107 * XXX Why can we still write after sndbuf - sndlowat bytes? 108 */ 109 optlen = sizeof(sndbuf); 110 RL(rump_sys_getsockopt(sock[0], SOL_SOCKET, SO_SNDBUF, &sndbuf, 111 &optlen)); 112 ATF_REQUIRE_MSG(optlen == sizeof(sndbuf), 113 "optlen=%zu sizeof(sndbuf)=%zu", 114 (size_t)optlen, sizeof(sndbuf)); 115 116 printf("sndbuf=%d\n", sndbuf); 117 ATF_REQUIRE(sndbuf > 0); 118 ATF_REQUIRE((unsigned)sndbuf <= __type_max(__typeof(resid))); 119 120 /* 121 * Fill the buffer until we have only MHLEN bytes left. Count 122 * how many bytes we have sent total as we go. 123 * 124 * XXX Seems like this should really go until we have sndlowat 125 * bytes left. 126 */ 127 total = 0; 128 for (resid = sndbuf; 129 resid > (size_t)MHLEN + clen; 130 resid -= (size_t)nsent) { 131 const ssize_t nsend = MIN(resid - ((size_t)MHLEN + clen), 132 sizeof(buf)); 133 struct iovec iov[] = { 134 { .iov_base = buf, .iov_len = nsend }, 135 }; 136 const struct msghdr msg = { 137 .msg_name = NULL, 138 .msg_namelen = 0, 139 .msg_iov = iov, 140 .msg_iovlen = __arraycount(iov), 141 .msg_control = NULL, 142 .msg_controllen = 0, 143 .msg_flags = 0, 144 }; 145 146 printf("sendmsg, resid=%u nsend=%zd\n", 147 resid, nsend); 148 ATF_REQUIRE(nsend > 0); 149 RL(nsent = rump_sys_sendmsg(sock[0], &msg, 0)); 150 printf("sent %zd of %zd\n", nsent, nsend); 151 ATF_REQUIRE_MSG(nsent == nsend, "nsent=%zd nsend=%zd", 152 nsent, nsend); 153 total += (size_t)nsent; 154 } 155 ATF_REQUIRE_MSG(resid == (size_t)MHLEN + clen, "resid=%u", resid); 156 157 /* 158 * Try to fill the remainder of the buffer, but with file 159 * descriptors as well. The control message length should 160 * count toward sndbuf as well as the data length. 161 */ 162 { 163 union { 164 struct cmsghdr hdr; 165 unsigned char buf[CMSG_SPACE(MAXNFDS * sizeof(int))]; 166 } cmsgbuf; 167 const ssize_t nsend = resid - clen; 168 struct iovec iov[] = { 169 { .iov_base = buf, .iov_len = nsend } 170 }; 171 const struct msghdr msg = { 172 .msg_name = NULL, 173 .msg_namelen = 0, 174 .msg_iov = iov, 175 .msg_iovlen = __arraycount(iov), 176 .msg_control = &cmsgbuf, 177 .msg_controllen = clen, 178 .msg_flags = 0, 179 }; 180 struct cmsghdr *const cmsg = CMSG_FIRSTHDR(&msg); 181 182 cmsg->cmsg_len = CMSG_LEN(nfds * sizeof(int)); 183 cmsg->cmsg_level = SOL_SOCKET; 184 cmsg->cmsg_type = SCM_RIGHTS; 185 memcpy(CMSG_DATA(cmsg), fds, nfds * sizeof(int)); 186 187 printf("send %zd data bytes with %u control bytes\n", nsend, clen); 188 RL(nsent = rump_sys_sendmsg(sock[0], &msg, 0)); 189 total += (size_t)nsent; 190 } 191 192 /* 193 * Receive the data and file descriptors. In some chunk 194 * (though it may not be the last chunk, because the last chunk 195 * may be split into smaller chunks), we should receive the 196 * fds. 197 */ 198 for (resid = total; resid > 0; resid -= (size_t)nrcvd) { 199 union { 200 struct cmsghdr hdr; 201 unsigned char buf[CMSG_SPACE(MAXNFDS * sizeof(int))]; 202 } cmsgbuf; 203 const ssize_t nrecv = sizeof(buf); 204 struct iovec iov[] = { 205 { .iov_base = buf, .iov_len = nrecv } 206 }; 207 struct msghdr msg = { 208 .msg_name = NULL, 209 .msg_namelen = 0, 210 .msg_iov = iov, 211 .msg_iovlen = __arraycount(iov), 212 .msg_control = &cmsgbuf, 213 .msg_controllen = sizeof(cmsgbuf), 214 .msg_flags = 0, 215 }; 216 struct cmsghdr *cmsg; 217 218 printf("recvmsg, resid=%u nrecv=%zu\n", resid, nrecv); 219 nrcvd = rump_sys_recvmsg(sock[1], &msg, 0); 220 if (nrcvd == -1) { 221 int error = errno; 222 223 atf_tc_fail_nonfatal("recvmsg: %d (%s)", error, 224 strerror(error)); 225 break; 226 } 227 printf("nrcvd=%zu\n", nrcvd); 228 ATF_REQUIRE_MSG((size_t)nrcvd <= resid, "nrcvd=%zd resid=%u", 229 nrcvd, resid); 230 for (cmsg = CMSG_FIRSTHDR(&msg); 231 cmsg != NULL; 232 cmsg = CMSG_NXTHDR(&msg, cmsg)) { 233 unsigned i, n; 234 const int *fdptr; 235 236 if (cmsg->cmsg_level != SOL_SOCKET) { 237 atf_tc_fail_nonfatal("cmsg_level=%d," 238 " expected %d\n", 239 cmsg->cmsg_level, SOL_SOCKET); 240 continue; 241 } 242 if (cmsg->cmsg_type != SCM_RIGHTS) { 243 atf_tc_fail_nonfatal("cmsg_type=%d," 244 " expected %d\n", 245 cmsg->cmsg_type, SCM_RIGHTS); 246 continue; 247 } 248 if (cmsg->cmsg_len < CMSG_LEN(sizeof(int))) { 249 atf_tc_fail_nonfatal("cmsg_len=%zu," 250 " expected >=%zu\n", 251 (size_t)cmsg->cmsg_len, 252 (size_t)CMSG_LEN(sizeof(int))); 253 continue; 254 } 255 if ((cmsg->cmsg_len - CMSG_LEN(0)) % sizeof(int)) { 256 atf_tc_fail_nonfatal("cmsg_len=%zu," 257 " expected 0 mod %zu after %zu\n", 258 (size_t)cmsg->cmsg_len, 259 (size_t)sizeof(int), 260 (size_t)CMSG_LEN(0)); 261 continue; 262 } 263 n = (cmsg->cmsg_len - CMSG_LEN(0))/sizeof(int); 264 ATF_CHECK(n > 0); 265 fdptr = (const int *)CMSG_DATA(cmsg); 266 for (i = 0; i < n; i++) 267 RL(rump_sys_close(fdptr[i])); 268 } 269 } 270 271 /* 272 * For one final recvmsg, we should block. 273 */ 274 { 275 union { 276 struct cmsghdr hdr; 277 unsigned char buf[CMSG_SPACE(MAXNFDS * sizeof(int))]; 278 } cmsgbuf; 279 struct iovec iov[] = { 280 { .iov_base = buf, .iov_len = sizeof(buf) } 281 }; 282 struct msghdr msg = { 283 .msg_name = NULL, 284 .msg_namelen = 0, 285 .msg_iov = iov, 286 .msg_iovlen = __arraycount(iov), 287 .msg_control = &cmsgbuf, 288 .msg_controllen = sizeof(cmsgbuf), 289 .msg_flags = 0, 290 }; 291 292 ATF_CHECK_ERRNO(EAGAIN, rump_sys_recvmsg(sock[1], &msg, 0) == -1); 293 } 294 295 /* 296 * Verify the total number of file descriptors has not changed. 297 * (We closed all the copies we sent ourself in the recvmsg 298 * loop above.) 299 */ 300 nfds_after = countfds(); 301 ATF_CHECK_MSG(nfds_before == nfds_after, 302 "nfds_before=%u nfds_after=%u", nfds_before, nfds_after); 303 304 RL(rump_sys_close(sock[0])); 305 RL(rump_sys_close(sock[1])); 306 } 307 308 ATF_TC(pr60832); 309 ATF_TC_HEAD(pr60832, tc) 310 { 311 atf_tc_set_md_var(tc, "descr", "Test sendmsg fails on partial data"); 312 } 313 ATF_TC_BODY(pr60832, tc) 314 { 315 int fds[MAXNFDS]; 316 unsigned i; 317 318 REQUIRE_LIBC(setlinebuf(stdout), EOF); 319 printf("MHLEN=%d\n", MHLEN); 320 321 /* 322 * Initialize rump before we try using it. 323 */ 324 RL(rump_init()); 325 326 /* 327 * Create some spare file descriptors. 328 */ 329 for (i = 0; i < __arraycount(fds); i++) 330 RL(fds[i] = rump_sys_socket(AF_LOCAL, SOCK_STREAM, 0)); 331 332 /* 333 * Run the test with different numbers of file descriptors. 334 */ 335 for (i = 0; i < __arraycount(fds); i++) { 336 const unsigned nfds = i + 1; 337 338 printf("test %u fd%s\n", nfds, nfds == 1 ? "" : "s"); 339 test_pr60832(fds, nfds); 340 } 341 } 342 343 ATF_TP_ADD_TCS(tp) 344 { 345 346 ATF_TP_ADD_TC(tp, pr60832); 347 348 return atf_no_error(); 349 } 350