1 /*- 2 * Copyright (c) 2025 Kyle Evans <kevans@FreeBSD.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/wait.h> 8 9 #include <dlfcn.h> 10 #include <stdbool.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 14 #include <atf-c.h> 15 16 #define ARBITRARY_EXIT_CODE 42 17 18 static char * 19 get_shlib(const char *srcdir) 20 { 21 char *shlib; 22 23 shlib = NULL; 24 if (asprintf(&shlib, "%s/libatexit.so", srcdir) < 0) 25 atf_tc_fail("failed to construct path to libatexit.so"); 26 return (shlib); 27 } 28 29 static void 30 run_test(const atf_tc_t *tc, bool with_fatal_atexit, bool with_exit) 31 { 32 pid_t p; 33 void (*set_fatal_atexit)(bool); 34 void (*set_exit_code)(int); 35 void *hdl; 36 char *shlib; 37 38 shlib = get_shlib(atf_tc_get_config_var(tc, "srcdir")); 39 40 hdl = dlopen(shlib, RTLD_LAZY); 41 ATF_REQUIRE_MSG(hdl != NULL, "dlopen: %s", dlerror()); 42 43 free(shlib); 44 45 if (with_fatal_atexit) { 46 set_fatal_atexit = dlsym(hdl, "set_fatal_atexit"); 47 ATF_REQUIRE_MSG(set_fatal_atexit != NULL, 48 "set_fatal_atexit: %s", dlerror()); 49 } 50 if (with_exit) { 51 set_exit_code = dlsym(hdl, "set_exit_code"); 52 ATF_REQUIRE_MSG(set_exit_code != NULL, "set_exit_code: %s", 53 dlerror()); 54 } 55 56 p = atf_utils_fork(); 57 if (p == 0) { 58 /* 59 * Don't let the child clobber the results file; stderr/stdout 60 * have been replaced by atf_utils_fork() to capture it. We're 61 * intentionally using exit() instead of _exit() here to run 62 * __cxa_finalize at exit, otherwise we'd just leave it be. 63 */ 64 closefrom(3); 65 66 if (with_fatal_atexit) 67 set_fatal_atexit(true); 68 if (with_exit) 69 set_exit_code(ARBITRARY_EXIT_CODE); 70 71 dlclose(hdl); 72 73 /* 74 * If the dtor was supposed to exit (most cases), then we should 75 * not have made it to this point. If it's not supposed to 76 * exit, then we just exit with success here because we might 77 * be expecting either a clean exit or a signal on our way out 78 * as the final __cxa_finalize tries to run a callback in the 79 * unloaded DSO. 80 */ 81 if (with_exit) 82 exit(1); 83 exit(0); 84 } 85 86 dlclose(hdl); 87 atf_utils_wait(p, with_exit ? ARBITRARY_EXIT_CODE : 0, "", ""); 88 } 89 90 ATF_TC_WITHOUT_HEAD(simple_cxa_atexit); 91 ATF_TC_BODY(simple_cxa_atexit, tc) 92 { 93 /* 94 * This test exits in a global object's dtor so that we check for our 95 * dtor being run at dlclose() time. If it isn't, then the forked child 96 * will have a chance to exit(1) after dlclose() to raise a failure. 97 */ 98 run_test(tc, false, true); 99 } 100 101 ATF_TC_WITHOUT_HEAD(late_cxa_atexit); 102 ATF_TC_BODY(late_cxa_atexit, tc) 103 { 104 /* 105 * This test creates another global object during a __cxa_atexit handler 106 * invocation. It's been observed in the wild that we weren't executing 107 * it, then the DSO gets torn down and it was executed at application 108 * exit time instead. In the best case scenario we would crash if 109 * something else hadn't been mapped there. 110 */ 111 run_test(tc, true, false); 112 } 113 114 ATF_TC_WITHOUT_HEAD(late_cxa_atexit_ran); 115 ATF_TC_BODY(late_cxa_atexit_ran, tc) 116 { 117 /* 118 * This is a slight variation of the previous test where we trigger an 119 * exit() in our late-registered __cxa_atexit handler so that we can 120 * ensure it was ran *before* dlclose() finished and not through some 121 * weird chain of events afterwards. 122 */ 123 run_test(tc, true, true); 124 } 125 126 ATF_TP_ADD_TCS(tp) 127 { 128 ATF_TP_ADD_TC(tp, simple_cxa_atexit); 129 ATF_TP_ADD_TC(tp, late_cxa_atexit); 130 ATF_TP_ADD_TC(tp, late_cxa_atexit_ran); 131 return (atf_no_error()); 132 } 133