1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2026 Mariusz Zaborski <oshogbo@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/resource.h> 29 #include <sys/select.h> 30 31 #include <errno.h> 32 #include <inttypes.h> 33 #include <stdlib.h> 34 #include <string.h> 35 36 #include <libcasper.h> 37 38 #include <atf-c.h> 39 40 #define NCONNECTIONS (FD_SETSIZE + 64) 41 #define FD_HEADROOM 64 42 43 /* Test that file descriptors past FD_SETSIZE (1024) work. */ 44 ATF_TC_WITHOUT_HEAD(many_connections); 45 ATF_TC_BODY(many_connections, tc) 46 { 47 struct rlimit rl; 48 cap_channel_t *chan; 49 cap_channel_t **clones; 50 size_t i; 51 52 if (getrlimit(RLIMIT_NOFILE, &rl) != 0) 53 atf_tc_skip("getrlimit: %s", strerror(errno)); 54 if (rl.rlim_max < NCONNECTIONS + FD_HEADROOM) 55 atf_tc_skip("RLIMIT_NOFILE hard cap %ju below required %d", 56 (uintmax_t)rl.rlim_max, NCONNECTIONS + FD_HEADROOM); 57 rl.rlim_cur = rl.rlim_max; 58 ATF_REQUIRE_MSG(setrlimit(RLIMIT_NOFILE, &rl) == 0, 59 "setrlimit: %s", strerror(errno)); 60 61 chan = cap_init(); 62 ATF_REQUIRE_MSG(chan != NULL, "cap_init failed: %s", strerror(errno)); 63 64 clones = calloc(NCONNECTIONS, sizeof(*clones)); 65 ATF_REQUIRE(clones != NULL); 66 67 /* 68 * Every cap_clone(3) adds one more connection to the helper. 69 * After this loop the helper is watching more fds than an 70 * fd_set can hold. 71 */ 72 for (i = 0; i < NCONNECTIONS; i++) { 73 clones[i] = cap_clone(chan); 74 ATF_REQUIRE_MSG(clones[i] != NULL, 75 "cap_clone failed at %zu/%d: %s", 76 i, NCONNECTIONS, strerror(errno)); 77 } 78 79 for (i = 0; i < NCONNECTIONS; i++) 80 cap_close(clones[i]); 81 free(clones); 82 cap_close(chan); 83 } 84 85 #define CHURN_CONNECTIONS 50 86 #define CHURN_CLOSE_STEP 5 87 88 /* Test that gaps in the file descriptor list do not break casper. */ 89 ATF_TC_WITHOUT_HEAD(connection_churn); 90 ATF_TC_BODY(connection_churn, tc) 91 { 92 cap_channel_t *chan, *survivor, *extra; 93 cap_channel_t *clones[CHURN_CONNECTIONS]; 94 size_t i, survivor_idx; 95 96 chan = cap_init(); 97 ATF_REQUIRE_MSG(chan != NULL, "cap_init failed: %s", strerror(errno)); 98 99 for (i = 0; i < CHURN_CONNECTIONS; i++) { 100 clones[i] = cap_clone(chan); 101 ATF_REQUIRE_MSG(clones[i] != NULL, 102 "cap_clone failed at %zu: %s", i, strerror(errno)); 103 } 104 105 /* 106 * Close every Nth clone. 107 */ 108 for (i = 0; i < CHURN_CONNECTIONS; i += CHURN_CLOSE_STEP) { 109 cap_close(clones[i]); 110 clones[i] = NULL; 111 } 112 113 /* 114 * Force a poll() cycle: the helper handles POLLIN on chan and 115 * POLLHUP on the closed clones in the same walk. 116 */ 117 extra = cap_clone(chan); 118 ATF_REQUIRE_MSG(extra != NULL, "cap_clone after churn failed: %s", 119 strerror(errno)); 120 121 /* A surviving clone must still round-trip. */ 122 survivor_idx = 1; 123 survivor = cap_clone(clones[survivor_idx]); 124 ATF_REQUIRE_MSG(survivor != NULL, 125 "cap_clone on survivor failed: %s", strerror(errno)); 126 127 cap_close(survivor); 128 cap_close(extra); 129 for (i = 0; i < CHURN_CONNECTIONS; i++) { 130 if (clones[i] != NULL) 131 cap_close(clones[i]); 132 } 133 cap_close(chan); 134 } 135 136 ATF_TP_ADD_TCS(tp) 137 { 138 139 ATF_TP_ADD_TC(tp, many_connections); 140 ATF_TP_ADD_TC(tp, connection_churn); 141 return (atf_no_error()); 142 } 143