]>
Commit | Line | Data |
---|---|---|
486010a3 FW |
1 | /* Copyright (C) 2002-2021 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. | |
486010a3 FW |
3 | |
4 | The GNU C Library is free software; you can redistribute it and/or | |
5 | modify it under the terms of the GNU Lesser General Public | |
6 | License as published by the Free Software Foundation; either | |
7 | version 2.1 of the License, or (at your option) any later version. | |
8 | ||
9 | The GNU C Library is distributed in the hope that it will be useful, | |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | Lesser General Public License for more details. | |
13 | ||
14 | You should have received a copy of the GNU Lesser General Public | |
15 | License along with the GNU C Library; if not, see | |
16 | <https://www.gnu.org/licenses/>. */ | |
17 | ||
18 | #include <futex-internal.h> | |
19 | #include <ldsodefs.h> | |
20 | #include <list.h> | |
21 | #include <lowlevellock.h> | |
22 | #include <pthreadP.h> | |
23 | #include <unistd.h> | |
24 | ||
25 | /* Check for consistency across set*id system call results. The abort | |
26 | should not happen as long as all privileges changes happen through | |
27 | the glibc wrappers. ERROR must be 0 (no error) or an errno | |
28 | code. */ | |
29 | static void | |
30 | setxid_error (struct xid_command *cmdp, int error) | |
31 | { | |
32 | do | |
33 | { | |
34 | int olderror = cmdp->error; | |
35 | if (olderror == error) | |
36 | break; | |
37 | if (olderror != -1) | |
38 | { | |
39 | /* Mismatch between current and previous results. Save the | |
40 | error value to memory so that is not clobbered by the | |
41 | abort function and preserved in coredumps. */ | |
42 | volatile int xid_err __attribute__ ((unused)) = error; | |
43 | abort (); | |
44 | } | |
45 | } | |
46 | while (atomic_compare_and_exchange_bool_acq (&cmdp->error, error, -1)); | |
47 | } | |
48 | ||
49 | /* Set by __nptl_setxid and used by __nptl_setxid_sighandler. */ | |
50 | static struct xid_command *xidcmd; | |
51 | ||
52 | /* We use the SIGSETXID signal in the setuid, setgid, etc. implementations to | |
53 | tell each thread to call the respective setxid syscall on itself. This is | |
54 | the handler. */ | |
55 | void | |
56 | __nptl_setxid_sighandler (int sig, siginfo_t *si, void *ctx) | |
57 | { | |
58 | int result; | |
59 | ||
60 | /* Safety check. It would be possible to call this function for | |
61 | other signals and send a signal from another process. This is not | |
62 | correct and might even be a security problem. Try to catch as | |
63 | many incorrect invocations as possible. */ | |
64 | if (sig != SIGSETXID | |
65 | || si->si_pid != __getpid () | |
66 | || si->si_code != SI_TKILL) | |
67 | return; | |
68 | ||
69 | result = INTERNAL_SYSCALL_NCS (xidcmd->syscall_no, 3, xidcmd->id[0], | |
70 | xidcmd->id[1], xidcmd->id[2]); | |
71 | int error = 0; | |
72 | if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (result))) | |
73 | error = INTERNAL_SYSCALL_ERRNO (result); | |
74 | setxid_error (xidcmd, error); | |
75 | ||
76 | /* Reset the SETXID flag. */ | |
77 | struct pthread *self = THREAD_SELF; | |
78 | int flags, newval; | |
79 | do | |
80 | { | |
81 | flags = THREAD_GETMEM (self, cancelhandling); | |
82 | newval = THREAD_ATOMIC_CMPXCHG_VAL (self, cancelhandling, | |
83 | flags & ~SETXID_BITMASK, flags); | |
84 | } | |
85 | while (flags != newval); | |
86 | ||
87 | /* And release the futex. */ | |
88 | self->setxid_futex = 1; | |
89 | futex_wake (&self->setxid_futex, 1, FUTEX_PRIVATE); | |
90 | ||
91 | if (atomic_decrement_val (&xidcmd->cntr) == 0) | |
92 | futex_wake ((unsigned int *) &xidcmd->cntr, 1, FUTEX_PRIVATE); | |
93 | } | |
94 | libc_hidden_def (__nptl_setxid_sighandler) | |
95 | ||
96 | static void | |
97 | setxid_mark_thread (struct xid_command *cmdp, struct pthread *t) | |
98 | { | |
99 | int ch; | |
100 | ||
101 | /* Wait until this thread is cloned. */ | |
102 | if (t->setxid_futex == -1 | |
103 | && ! atomic_compare_and_exchange_bool_acq (&t->setxid_futex, -2, -1)) | |
104 | do | |
105 | futex_wait_simple (&t->setxid_futex, -2, FUTEX_PRIVATE); | |
106 | while (t->setxid_futex == -2); | |
107 | ||
108 | /* Don't let the thread exit before the setxid handler runs. */ | |
109 | t->setxid_futex = 0; | |
110 | ||
111 | do | |
112 | { | |
113 | ch = t->cancelhandling; | |
114 | ||
115 | /* If the thread is exiting right now, ignore it. */ | |
116 | if ((ch & EXITING_BITMASK) != 0) | |
117 | { | |
118 | /* Release the futex if there is no other setxid in | |
119 | progress. */ | |
120 | if ((ch & SETXID_BITMASK) == 0) | |
121 | { | |
122 | t->setxid_futex = 1; | |
123 | futex_wake (&t->setxid_futex, 1, FUTEX_PRIVATE); | |
124 | } | |
125 | return; | |
126 | } | |
127 | } | |
128 | while (atomic_compare_and_exchange_bool_acq (&t->cancelhandling, | |
129 | ch | SETXID_BITMASK, ch)); | |
130 | } | |
131 | ||
132 | ||
133 | static void | |
134 | setxid_unmark_thread (struct xid_command *cmdp, struct pthread *t) | |
135 | { | |
136 | int ch; | |
137 | ||
138 | do | |
139 | { | |
140 | ch = t->cancelhandling; | |
141 | if ((ch & SETXID_BITMASK) == 0) | |
142 | return; | |
143 | } | |
144 | while (atomic_compare_and_exchange_bool_acq (&t->cancelhandling, | |
145 | ch & ~SETXID_BITMASK, ch)); | |
146 | ||
147 | /* Release the futex just in case. */ | |
148 | t->setxid_futex = 1; | |
149 | futex_wake (&t->setxid_futex, 1, FUTEX_PRIVATE); | |
150 | } | |
151 | ||
152 | ||
153 | static int | |
154 | setxid_signal_thread (struct xid_command *cmdp, struct pthread *t) | |
155 | { | |
156 | if ((t->cancelhandling & SETXID_BITMASK) == 0) | |
157 | return 0; | |
158 | ||
159 | int val; | |
160 | pid_t pid = __getpid (); | |
161 | val = INTERNAL_SYSCALL_CALL (tgkill, pid, t->tid, SIGSETXID); | |
162 | ||
163 | /* If this failed, it must have had not started yet or else exited. */ | |
164 | if (!INTERNAL_SYSCALL_ERROR_P (val)) | |
165 | { | |
166 | atomic_increment (&cmdp->cntr); | |
167 | return 1; | |
168 | } | |
169 | else | |
170 | return 0; | |
171 | } | |
172 | ||
173 | int | |
174 | attribute_hidden | |
175 | __nptl_setxid (struct xid_command *cmdp) | |
176 | { | |
177 | int signalled; | |
178 | int result; | |
179 | lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE); | |
180 | ||
181 | xidcmd = cmdp; | |
182 | cmdp->cntr = 0; | |
183 | cmdp->error = -1; | |
184 | ||
185 | struct pthread *self = THREAD_SELF; | |
186 | ||
187 | /* Iterate over the list with system-allocated threads first. */ | |
188 | list_t *runp; | |
189 | list_for_each (runp, &GL (dl_stack_used)) | |
190 | { | |
191 | struct pthread *t = list_entry (runp, struct pthread, list); | |
192 | if (t == self) | |
193 | continue; | |
194 | ||
195 | setxid_mark_thread (cmdp, t); | |
196 | } | |
197 | ||
198 | /* Now the list with threads using user-allocated stacks. */ | |
199 | list_for_each (runp, &GL (dl_stack_user)) | |
200 | { | |
201 | struct pthread *t = list_entry (runp, struct pthread, list); | |
202 | if (t == self) | |
203 | continue; | |
204 | ||
205 | setxid_mark_thread (cmdp, t); | |
206 | } | |
207 | ||
208 | /* Iterate until we don't succeed in signalling anyone. That means | |
209 | we have gotten all running threads, and their children will be | |
210 | automatically correct once started. */ | |
211 | do | |
212 | { | |
213 | signalled = 0; | |
214 | ||
215 | list_for_each (runp, &GL (dl_stack_used)) | |
216 | { | |
217 | struct pthread *t = list_entry (runp, struct pthread, list); | |
218 | if (t == self) | |
219 | continue; | |
220 | ||
221 | signalled += setxid_signal_thread (cmdp, t); | |
222 | } | |
223 | ||
224 | list_for_each (runp, &GL (dl_stack_user)) | |
225 | { | |
226 | struct pthread *t = list_entry (runp, struct pthread, list); | |
227 | if (t == self) | |
228 | continue; | |
229 | ||
230 | signalled += setxid_signal_thread (cmdp, t); | |
231 | } | |
232 | ||
233 | int cur = cmdp->cntr; | |
234 | while (cur != 0) | |
235 | { | |
236 | futex_wait_simple ((unsigned int *) &cmdp->cntr, cur, | |
237 | FUTEX_PRIVATE); | |
238 | cur = cmdp->cntr; | |
239 | } | |
240 | } | |
241 | while (signalled != 0); | |
242 | ||
243 | /* Clean up flags, so that no thread blocks during exit waiting | |
244 | for a signal which will never come. */ | |
245 | list_for_each (runp, &GL (dl_stack_used)) | |
246 | { | |
247 | struct pthread *t = list_entry (runp, struct pthread, list); | |
248 | if (t == self) | |
249 | continue; | |
250 | ||
251 | setxid_unmark_thread (cmdp, t); | |
252 | } | |
253 | ||
254 | list_for_each (runp, &GL (dl_stack_user)) | |
255 | { | |
256 | struct pthread *t = list_entry (runp, struct pthread, list); | |
257 | if (t == self) | |
258 | continue; | |
259 | ||
260 | setxid_unmark_thread (cmdp, t); | |
261 | } | |
262 | ||
263 | /* This must be last, otherwise the current thread might not have | |
264 | permissions to send SIGSETXID syscall to the other threads. */ | |
265 | result = INTERNAL_SYSCALL_NCS (cmdp->syscall_no, 3, | |
266 | cmdp->id[0], cmdp->id[1], cmdp->id[2]); | |
267 | int error = 0; | |
268 | if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (result))) | |
269 | { | |
270 | error = INTERNAL_SYSCALL_ERRNO (result); | |
271 | __set_errno (error); | |
272 | result = -1; | |
273 | } | |
274 | setxid_error (cmdp, error); | |
275 | ||
276 | lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE); | |
277 | return result; | |
278 | } |