1 /*- 2 * Copyright (c) 2025 Klara, Inc. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/param.h> 8 #include <sys/conf.h> 9 #include <sys/stat.h> 10 #include <sys/wait.h> 11 12 #include <dlfcn.h> 13 #include <fcntl.h> 14 #include <limits.h> 15 #include <poll.h> 16 #include <stdarg.h> 17 #include <stdbool.h> 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <time.h> 21 #include <unistd.h> 22 23 #include <atf-c.h> 24 25 static const struct tzcase { 26 const char *tzfn; 27 const char *expect; 28 } tzcases[] = { 29 /* 30 * A handful of time zones and the expected result of 31 * strftime("%z (%Z)", tm) when that time zone is active 32 * and tm represents a date in the summer of 2025. 33 */ 34 { "America/Vancouver", "-0700 (PDT)" }, 35 { "America/New_York", "-0400 (EDT)" }, 36 { "Europe/London", "+0100 (BST)" }, 37 { "Europe/Paris", "+0200 (CEST)" }, 38 { "Asia/Kolkata", "+0530 (IST)" }, 39 { "Asia/Tokyo", "+0900 (JST)" }, 40 { "Australia/Canberra", "+1000 (AEST)" }, 41 { "UTC", "+0000 (UTC)" }, 42 { 0 }, 43 }; 44 45 static const time_t then = 1751328000; /* 2025-07-01 00:00:00 UTC */ 46 47 #ifdef DETECT_TZ_CHANGES 48 static const char *tz_change_interval_sym = "__tz_change_interval"; 49 static int *tz_change_interval_p; 50 static const int tz_change_interval = 3; 51 static int tz_change_timeout = 90; 52 53 static bool debugging; 54 55 static void 56 debug(const char *fmt, ...) 57 { 58 va_list ap; 59 60 if (debugging) { 61 va_start(ap, fmt); 62 vfprintf(stderr, fmt, ap); 63 va_end(ap); 64 fputc('\n', stderr); 65 } 66 } 67 68 static void 69 change_tz(const char *tzn) 70 { 71 static const char *zfn = "/usr/share/zoneinfo"; 72 static const char *tfn = "root/etc/.localtime"; 73 static const char *dfn = "root/etc/localtime"; 74 ssize_t clen; 75 int zfd, sfd, dfd; 76 77 ATF_REQUIRE((zfd = open(zfn, O_DIRECTORY | O_SEARCH)) >= 0); 78 ATF_REQUIRE((sfd = openat(zfd, tzn, O_RDONLY)) >= 0); 79 ATF_REQUIRE((dfd = open(tfn, O_CREAT | O_TRUNC | O_WRONLY)) >= 0); 80 do { 81 clen = copy_file_range(sfd, NULL, dfd, NULL, SSIZE_MAX, 0); 82 ATF_REQUIRE_MSG(clen != -1, "failed to copy %s/%s: %m", 83 zfn, tzn); 84 } while (clen > 0); 85 ATF_CHECK_EQ(0, close(dfd)); 86 ATF_CHECK_EQ(0, close(sfd)); 87 ATF_CHECK_EQ(0, close(zfd)); 88 ATF_REQUIRE_EQ(0, rename(tfn, dfn)); 89 debug("time zone %s installed", tzn); 90 } 91 92 /* 93 * Test time zone change detection. 94 * 95 * The parent creates a chroot containing only /etc/localtime, initially 96 * set to UTC. It then forks a child which enters the chroot, repeatedly 97 * checks the current time zone, and prints it to stdout if it changes 98 * (including once on startup). Meanwhile, the parent waits for output 99 * from the child. Every time it receives a line of text from the child, 100 * it checks that it is as expected, then changes /etc/localtime within 101 * the chroot to the next case in the list. Once it reaches the end of 102 * the list, it closes a pipe to notify the child, which terminates. 103 * 104 * Note that ATF and / or Kyua may have set the timezone before the test 105 * case starts (even unintentionally). Therefore, we start the test only 106 * after we've received and discarded the first report from the child, 107 * which should come almost immediately on startup. 108 */ 109 ATF_TC(detect_tz_changes); 110 ATF_TC_HEAD(detect_tz_changes, tc) 111 { 112 atf_tc_set_md_var(tc, "descr", "Test timezone change detection"); 113 atf_tc_set_md_var(tc, "require.user", "root"); 114 atf_tc_set_md_var(tc, "timeout", "600"); 115 } 116 ATF_TC_BODY(detect_tz_changes, tc) 117 { 118 char obuf[1024] = ""; 119 char ebuf[1024] = ""; 120 struct pollfd fds[3]; 121 int opd[2], epd[2], spd[2]; 122 time_t changed, now; 123 const struct tzcase *tzcase = NULL; 124 struct tm *tm; 125 size_t olen = 0, elen = 0; 126 ssize_t rlen; 127 long curoff = LONG_MIN; 128 pid_t pid; 129 int nfds, status; 130 131 /* speed up the test if possible */ 132 tz_change_interval_p = dlsym(RTLD_SELF, tz_change_interval_sym); 133 if (tz_change_interval_p != NULL && 134 *tz_change_interval_p > tz_change_interval) { 135 debug("reducing detection interval from %d to %d", 136 *tz_change_interval_p, tz_change_interval); 137 *tz_change_interval_p = tz_change_interval; 138 tz_change_timeout = tz_change_interval * 3; 139 } 140 /* prepare chroot */ 141 ATF_REQUIRE_EQ(0, mkdir("root", 0755)); 142 ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755)); 143 change_tz("UTC"); 144 time(&changed); 145 /* output, error, sync pipes */ 146 if (pipe(opd) != 0 || pipe(epd) != 0 || pipe(spd) != 0) 147 atf_tc_fail("failed to pipe"); 148 /* fork child */ 149 if ((pid = fork()) < 0) 150 atf_tc_fail("failed to fork"); 151 if (pid == 0) { 152 /* child */ 153 dup2(opd[1], STDOUT_FILENO); 154 close(opd[0]); 155 close(opd[1]); 156 dup2(epd[1], STDERR_FILENO); 157 close(epd[0]); 158 close(epd[1]); 159 close(spd[0]); 160 unsetenv("TZ"); 161 ATF_REQUIRE_EQ(0, chroot("root")); 162 ATF_REQUIRE_EQ(0, chdir("/")); 163 fds[0].fd = spd[1]; 164 fds[0].events = POLLIN; 165 for (;;) { 166 ATF_REQUIRE(poll(fds, 1, 100) >= 0); 167 if (fds[0].revents & POLLHUP) { 168 /* parent closed sync pipe */ 169 _exit(0); 170 } 171 ATF_REQUIRE((tm = localtime(&then)) != NULL); 172 if (tm->tm_gmtoff == curoff) 173 continue; 174 olen = strftime(obuf, sizeof(obuf), "%z (%Z)", tm); 175 ATF_REQUIRE(olen > 0); 176 fprintf(stdout, "%s\n", obuf); 177 fflush(stdout); 178 curoff = tm->tm_gmtoff; 179 } 180 _exit(2); 181 } 182 /* parent */ 183 close(opd[1]); 184 close(epd[1]); 185 close(spd[1]); 186 /* receive output until child terminates */ 187 fds[0].fd = opd[0]; 188 fds[0].events = POLLIN; 189 fds[1].fd = epd[0]; 190 fds[1].events = POLLIN; 191 fds[2].fd = spd[0]; 192 fds[2].events = POLLIN; 193 nfds = 3; 194 for (;;) { 195 ATF_REQUIRE(poll(fds, 3, 1000) >= 0); 196 time(&now); 197 if (fds[0].revents & POLLIN && olen < sizeof(obuf)) { 198 rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen); 199 ATF_REQUIRE(rlen >= 0); 200 olen += rlen; 201 } 202 if (olen > 0) { 203 ATF_REQUIRE_EQ('\n', obuf[olen - 1]); 204 obuf[--olen] = '\0'; 205 /* tzcase will be NULL at first */ 206 if (tzcase != NULL) { 207 debug("%s", obuf); 208 ATF_REQUIRE_STREQ(tzcase->expect, obuf); 209 debug("change to %s detected after %d s", 210 tzcase->tzfn, (int)(now - changed)); 211 if (tz_change_interval_p != NULL) { 212 ATF_CHECK((int)(now - changed) >= 213 *tz_change_interval_p - 1); 214 ATF_CHECK((int)(now - changed) <= 215 *tz_change_interval_p + 1); 216 } 217 } 218 olen = 0; 219 /* first / next test case */ 220 if (tzcase == NULL) 221 tzcase = tzcases; 222 else 223 tzcase++; 224 if (tzcase->tzfn == NULL) { 225 /* test is over */ 226 break; 227 } 228 change_tz(tzcase->tzfn); 229 changed = now; 230 } 231 if (fds[1].revents & POLLIN && elen < sizeof(ebuf)) { 232 rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen); 233 ATF_REQUIRE(rlen >= 0); 234 elen += rlen; 235 } 236 if (elen > 0) { 237 ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); 238 elen = 0; 239 } 240 if (nfds > 2 && fds[2].revents & POLLHUP) { 241 /* child closed sync pipe */ 242 break; 243 } 244 /* 245 * The timeout for this test case is set to 10 minutes, 246 * because it can take that long to run with the default 247 * 61-second interval. However, each individual tzcase 248 * entry should not take much longer than the detection 249 * interval to test, so we can detect a problem long 250 * before Kyua terminates us. 251 */ 252 if ((now - changed) > tz_change_timeout) { 253 close(spd[0]); 254 if (tz_change_interval_p == NULL && 255 tzcase == tzcases) { 256 /* 257 * The most likely explanation in this 258 * case is that libc was built without 259 * time zone change detection. 260 */ 261 atf_tc_skip("time zone change detection " 262 "does not appear to be enabled"); 263 } 264 atf_tc_fail("timed out waiting for change to %s " 265 "to be detected", tzcase->tzfn); 266 } 267 } 268 close(opd[0]); 269 close(epd[0]); 270 close(spd[0]); /* this will wake up and terminate the child */ 271 if (olen > 0) 272 ATF_REQUIRE_EQ(olen, fwrite(obuf, 1, olen, stdout)); 273 if (elen > 0) 274 ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); 275 ATF_REQUIRE_EQ(pid, waitpid(pid, &status, 0)); 276 ATF_REQUIRE(WIFEXITED(status)); 277 ATF_REQUIRE_EQ(0, WEXITSTATUS(status)); 278 } 279 #endif /* DETECT_TZ_CHANGES */ 280 281 static void 282 test_tz_env(const char *tzval, const char *expect) 283 { 284 char buf[128]; 285 struct tm *tm; 286 size_t len; 287 288 setenv("TZ", tzval, 1); 289 ATF_REQUIRE((tm = localtime(&then)) != NULL); 290 len = strftime(buf, sizeof(buf), "%z (%Z)", tm); 291 ATF_REQUIRE(len > 0); 292 ATF_CHECK_STREQ(expect, buf); 293 } 294 295 ATF_TC(tz_env); 296 ATF_TC_HEAD(tz_env, tc) 297 { 298 atf_tc_set_md_var(tc, "descr", "Test TZ environment variable"); 299 } 300 ATF_TC_BODY(tz_env, tc) 301 { 302 const struct tzcase *tzcase; 303 304 for (tzcase = tzcases; tzcase->tzfn != NULL; tzcase++) 305 test_tz_env(tzcase->tzfn, tzcase->expect); 306 } 307 308 ATF_TC(tz_env_setugid); 309 ATF_TC_HEAD(tz_env_setugid, tc) 310 { 311 atf_tc_set_md_var(tc, "descr", "Test TZ environment variable " 312 "in setugid process"); 313 atf_tc_set_md_var(tc, "require.user", "root"); 314 } 315 ATF_TC_BODY(tz_env_setugid, tc) 316 { 317 const struct tzcase *tzcase; 318 319 ATF_REQUIRE_EQ(0, seteuid(UID_NOBODY)); 320 ATF_REQUIRE(issetugid()); 321 for (tzcase = tzcases; tzcase->tzfn != NULL; tzcase++) 322 test_tz_env(tzcase->tzfn, tzcase->expect); 323 } 324 325 ATF_TP_ADD_TCS(tp) 326 { 327 #ifdef DETECT_TZ_CHANGES 328 debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") && 329 isatty(STDERR_FILENO); 330 ATF_TP_ADD_TC(tp, detect_tz_changes); 331 #endif /* DETECT_TZ_CHANGES */ 332 ATF_TP_ADD_TC(tp, tz_env); 333 ATF_TP_ADD_TC(tp, tz_env_setugid); 334 return (atf_no_error()); 335 } 336