1 | /* longjmp cleanup function for unwinding past signal handlers. |
2 | Copyright (C) 1995-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <hurd.h> |
20 | #include <thread_state.h> |
21 | #include <jmpbuf-unwind.h> |
22 | #include <assert.h> |
23 | #include <stdint.h> |
24 | #include <pointer_guard.h> |
25 | |
26 | /* _hurd_setup_sighandler puts a link on the `active resources' chain so that |
27 | _longjmp_unwind will call this function with the `struct sigcontext *' |
28 | describing the context interrupted by the signal, when `longjmp' is jumping |
29 | to an environment that unwinds past the interrupted frame. */ |
30 | |
31 | void |
32 | _hurdsig_longjmp_from_handler (void *data, jmp_buf env, int val) |
33 | { |
34 | struct sigcontext *scp = data; |
35 | struct hurd_sigstate *ss = _hurd_self_sigstate (); |
36 | int onstack; |
37 | inline void cleanup (void) |
38 | { |
39 | /* Destroy the MiG reply port used by the signal handler, and restore |
40 | the reply port in use by the thread when interrupted. */ |
41 | mach_port_t reply_port = THREAD_GETMEM (THREAD_SELF, reply_port); |
42 | /* Assigning MACH_PORT_DEAD here tells libc's mig_get_reply_port not to |
43 | get another reply port, but avoids mig_dealloc_reply_port trying to |
44 | deallocate it after the receive fails (which it will, because the |
45 | reply port will be bogus, regardless). */ |
46 | THREAD_SETMEM (THREAD_SELF, reply_port, MACH_PORT_DEAD); |
47 | if (MACH_PORT_VALID (reply_port)) |
48 | __mach_port_mod_refs (__mach_task_self (), reply_port, |
49 | MACH_PORT_RIGHT_RECEIVE, -1); |
50 | if (scp->sc_reply_port) |
51 | __mach_port_mod_refs (__mach_task_self (), scp->sc_reply_port, |
52 | MACH_PORT_RIGHT_RECEIVE, -1); |
53 | } |
54 | |
55 | __spin_lock (&ss->lock); |
56 | /* We should only ever be called from _longjmp_unwind (in jmp-unwind.c), |
57 | which calls us inside a critical section. */ |
58 | assert (__spin_lock_locked (&ss->critical_section_lock)); |
59 | /* Are we on the alternate signal stack now? */ |
60 | onstack = (ss->sigaltstack.ss_flags & SS_ONSTACK); |
61 | __spin_unlock (&ss->lock); |
62 | |
63 | if (onstack && ! scp->sc_onstack) |
64 | { |
65 | /* We are unwinding off the signal stack. We must use sigreturn to |
66 | do it robustly. Mutate the sigcontext so that when sigreturn |
67 | resumes from that context, it will be as if `__longjmp (ENV, VAL)' |
68 | were done. */ |
69 | |
70 | struct hurd_userlink *link; |
71 | |
72 | inline uintptr_t demangle_ptr (uintptr_t x) |
73 | { |
74 | PTR_DEMANGLE (x); |
75 | return x; |
76 | } |
77 | |
78 | /* Continue _longjmp_unwind's job of running the unwind |
79 | forms for frames being unwound, since we will not |
80 | return to its loop like this one, which called us. */ |
81 | for (link = ss->active_resources; |
82 | link && _JMPBUF_UNWINDS (env[0].__jmpbuf, link, demangle_ptr); |
83 | link = link->thread.next) |
84 | if (_hurd_userlink_unlink (link)) |
85 | { |
86 | if (link->cleanup == &_hurdsig_longjmp_from_handler) |
87 | { |
88 | /* We are unwinding past another signal handler invocation. |
89 | Just finish the cleanup for this (inner) one, and then |
90 | swap SCP to restore to the outer context. */ |
91 | cleanup (); |
92 | scp = link->cleanup_data; |
93 | } |
94 | else |
95 | (*link->cleanup) (link->cleanup_data, env, val); |
96 | } |
97 | |
98 | #define sc_machine_thread_state paste(sc_,machine_thread_state) |
99 | #define paste(a,b) paste1(a,b) |
100 | #define paste1(a,b) a##b |
101 | |
102 | /* There are no more unwind forms to be run! |
103 | Now we can just have the sigreturn do the longjmp for us. */ |
104 | _hurd_longjmp_thread_state |
105 | ((struct machine_thread_state *) &scp->sc_machine_thread_state, |
106 | env, val); |
107 | |
108 | /* Restore to the same current signal mask. If sigsetjmp saved the |
109 | mask, longjmp has already restored it as desired; if not, we |
110 | should leave it as it is. */ |
111 | scp->sc_mask = ss->blocked; |
112 | |
113 | /* sigreturn expects the link added by _hurd_setup_sighandler |
114 | to still be there, but _longjmp_unwind removed it just before |
115 | calling us. Put it back now so sigreturn can find it. */ |
116 | link = (void *) &scp[1]; |
117 | assert (! link->resource.next && ! link->resource.prevp); |
118 | assert (link->thread.next == ss->active_resources); |
119 | assert (link->thread.prevp == &ss->active_resources); |
120 | if (link->thread.next) |
121 | link->thread.next->thread.prevp = &link->thread.next; |
122 | ss->active_resources = link; |
123 | |
124 | /* We must momentarily exit the critical section so that sigreturn |
125 | does not get upset with us. But we don't want signal handlers |
126 | running right now, because we are presently in the bogus state of |
127 | having run all the unwind forms back to ENV's frame, but our SP is |
128 | still inside those unwound frames. */ |
129 | __spin_lock (&ss->lock); |
130 | __spin_unlock (&ss->critical_section_lock); |
131 | ss->blocked = ~(sigset_t) 0 & ~_SIG_CANT_MASK; |
132 | __spin_unlock (&ss->lock); |
133 | |
134 | /* Restore to the modified signal context that now |
135 | performs `longjmp (ENV, VAL)'. */ |
136 | __sigreturn (scp: scp); |
137 | assert (! "sigreturn returned!" ); |
138 | } |
139 | |
140 | /* We are not unwinding off the alternate signal stack. So nothing |
141 | really funny is going on here. We can just clean up this handler |
142 | frame and let _longjmp_unwind continue unwinding. */ |
143 | cleanup (); |
144 | ss->intr_port = scp->sc_intr_port; |
145 | } |
146 | |