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