aboutsummaryrefslogtreecommitdiff
path: root/libcap/psx.c
blob: 1de5ac1711870632a5892f62fa652a5e426c3880 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
 * Copyright (c) 2019 Andrew G Morgan <morgan@kernel.org>
 *
 * This file contains a collection of routines that perform thread
 * synchronization to ensure that a whole process is running as a
 * single privilege object - independent of the number of pthreads.
 *
 * The whole file would be unnecessary if glibc exported an explicit
 * psx_syscall()-like function that leveraged the nptl:setxid
 * mechanism to synchronize thread state over the whole process.
 */
#define _POSIX_C_SOURCE 199309L
#define _GNU_SOURCE

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/psx_syscall.h>
#include <sys/syscall.h>

/*
 * share_psx_syscall() is invoked to advertize the two functions
 * psx_syscall3() and psx_syscall6(). The linkage is weak here so some
 * code external to this library can override it transparently during
 * the linkage process.
 */
__attribute__((weak))
void share_psx_syscall(long int (*syscall_fn)(long int,
					      long int, long int, long int),
		       long int (*syscall6_fn)(long int,
					       long int, long int, long int,
					       long int, long int, long int))
{
}

/*
 * type to keep track of registered threads.
 */
typedef struct registered_thread_s {
    struct registered_thread_s *next, *prev;
    pthread_t thread;
} registered_thread_t;

static pthread_once_t psx_tracker_initialized = PTHREAD_ONCE_INIT;

/*
 * This global structure holds the global coordination state for
 * libcap's psx_posix_syscall() support.
 */
static struct psx_tracker_s {
    pthread_mutex_t mu;

    int initialized;
    int psx_sig;

    struct {
	pthread_mutex_t mu;
	pthread_cond_t cond;
	long syscall_nr;
	long arg1, arg2, arg3, arg4, arg5, arg6;
	int six;
	int active;
	int todo;
    } cmd;

    struct sigaction sig_action;
    registered_thread_t *root;
} psx_tracker;

/*
 * psx_posix_syscall_actor performs the system call on the targeted
 * thread and decreases the outstanding syscall counter.
 */
static void psx_posix_syscall_actor(int signum, siginfo_t *info, void *ignore) {
    /* bail early if this isn't something we recognize */
    if (signum != psx_tracker.psx_sig || !psx_tracker.cmd.active ||
	info == NULL || info->si_code != SI_TKILL || info->si_pid != getpid()) {
	return;
    }

    if (!psx_tracker.cmd.six) {
	(void) syscall(psx_tracker.cmd.syscall_nr,
		       psx_tracker.cmd.arg1,
		       psx_tracker.cmd.arg2,
		       psx_tracker.cmd.arg3);
    } else {
	(void) syscall(psx_tracker.cmd.syscall_nr,
		       psx_tracker.cmd.arg1,
		       psx_tracker.cmd.arg2,
		       psx_tracker.cmd.arg3,
		       psx_tracker.cmd.arg4,
		       psx_tracker.cmd.arg5,
		       psx_tracker.cmd.arg6);
    }

    pthread_mutex_lock(&psx_tracker.cmd.mu);
    if (! --psx_tracker.cmd.todo) {
	pthread_cond_signal(&psx_tracker.cmd.cond);
    }
    pthread_mutex_unlock(&psx_tracker.cmd.mu);
}

long int psx_syscall3(long int syscall_nr,
		      long int arg1, long int arg2, long int arg3) {
    return psx_syscall(syscall_nr, arg1, arg2, arg3);
}

long int psx_syscall6(long int syscall_nr,
		      long int arg1, long int arg2, long int arg3,
		      long int arg4, long int arg5, long int arg6) {
    return psx_syscall(syscall_nr, arg1, arg2, arg3, arg4, arg5, arg6);
}

/*
 * psx_signal_start initializes the signal handler as a constructor
 * using a linker trick. This runs before Go has a chance to mess with
 * all the signal handlers. The constructor index is the priority we
 * can use without generating a linker complaint.
 */
static void psx_signal_start(void) {
    /*
     * glibc nptl picks from the SIGRTMIN end, so we pick from the
     * SIGRTMAX end
     */
    psx_tracker.psx_sig = SIGRTMAX;
    psx_tracker.sig_action.sa_sigaction = psx_posix_syscall_actor;
    sigemptyset(&psx_tracker.sig_action.sa_mask);
    psx_tracker.sig_action.sa_flags = SA_SIGINFO | SA_RESTART;;
    sigaction(psx_tracker.psx_sig, &psx_tracker.sig_action, NULL);
}

/*
 * psx_syscall_start initializes the subsystem.
 */
static void psx_syscall_start(void) {
    psx_tracker.initialized = 1;
    psx_signal_start();
    share_psx_syscall(psx_syscall3, psx_syscall6);
}

static void psx_do_registration(pthread_t thread) {
    int first_time = !psx_tracker.initialized;
    (void) pthread_once(&psx_tracker_initialized, psx_syscall_start);

    if (first_time) {
	/* First invocation, use recursion to register main() thread. */
	psx_do_registration(pthread_self());
    }

    registered_thread_t *node = calloc(1, sizeof(registered_thread_t));
    node->next = psx_tracker.root;
    if (node->next) {
	node->next->prev = node;
    }
    node->thread = thread;
    psx_tracker.root = node;
}

/*
 * psx_register registers the a thread as pariticipating in the single
 * (POSIX) pool of privilege used by the library.
 *
 * In normal, expected, use you should never need to call this because
 * the linker magic wrapping will arrange for this function to be
 * called implicitly every time a pthread is created with
 * pthread_create() or psx_pthread_create(). If, however, you are
 * unable to use the linker trick to wrap pthread_create(), you should
 * include this line in one and only one place in your code. Just
 * after the end of the main() function would be a good place, for
 * example:
 *
 *   int main(int argc, char **argv) {

 *      ...
 *   }
 *
 *   PSX_NO_LINKER_WRAPPING
 *
 * This is required for libpsx to link. It also requires the coder to
 * explicitly use psx_register() for all threads not started with
 * psx_pthread_create().
 *
 * Note, there is no need to ever register the main() process thread.
 */
void psx_register(pthread_t thread) {
    pthread_mutex_lock(&psx_tracker.mu);
    psx_do_registration(thread);
    pthread_mutex_unlock(&psx_tracker.mu);
}

/* provide a prototype */
int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
			  void *(*start_routine) (void *), void *arg);

/*
 * psx_pthread_create is a wrapper for pthread_create() that registers
 * the newly created thread. If your threads are created already, they
 * can be individually registered with psx_register().
 */
int psx_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
		       void *(*start_routine) (void *), void *arg) {
    if (pthread_create == __wrap_pthread_create) {
	return __wrap_pthread_create(thread, attr, start_routine, arg);
    }

    pthread_mutex_lock(&psx_tracker.mu);
    int ret = pthread_create(thread, attr, start_routine, arg);
    if (ret != -1) {
	psx_do_registration(*thread);
    }
    pthread_mutex_unlock(&psx_tracker.mu);
    return ret;
}

/*
 * __wrap_pthread_create is the wrapped destination of all regular
 * pthread_create calls.
 */
int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr,
			  void *(*start_routine) (void *), void *arg) {
    pthread_mutex_lock(&psx_tracker.mu);
    int ret = __real_pthread_create(thread, attr, start_routine, arg);
    if (ret != -1) {
	psx_do_registration(*thread);
    }
    pthread_mutex_unlock(&psx_tracker.mu);
    return ret;
}

/*
 * __psx_syscall performs the syscall on the current thread and if no
 * error is detected it ensures that the syscall is also performed on
 * all (other) registered threads. The return code is the value for
 * the first invocation. It uses a trick to figure out how many
 * arguments the user has supplied. The other half of the trick is
 * provided by the macro psx_syscall() in the <sys/psx_syscall.h>
 * file. The trick is the 7th optional argument (8th over all) to
 * __psx_syscall is the count of arguments supplied to psx_syscall.
 *
 * User:
 *                       psx_syscall(nr, a, b);
 * Expanded by macro to:
 *                       __psx_syscall(nr, a, b, 6, 5, 4, 3, 2, 1, 0);
 * The eighth arg is now ------------------------------------^
 */
long int __psx_syscall(long int syscall_nr, ...) {
    long int arg[7];
    int i;

    va_list aptr;
    va_start(aptr, syscall_nr);
    for (i = 0; i < 7; i++) {
	arg[i] = va_arg(aptr, long int);
    }
    va_end(aptr);

    int count = arg[6];
    if (count < 0 || count > 6) {
	errno = EINVAL;
	return -1;
    }

    long int ret;

    pthread_mutex_lock(&psx_tracker.mu);

    psx_tracker.cmd.syscall_nr = syscall_nr;
    psx_tracker.cmd.arg1 = count > 0 ? arg[0] : 0;
    psx_tracker.cmd.arg2 = count > 1 ? arg[1] : 0;
    psx_tracker.cmd.arg3 = count > 2 ? arg[2] : 0;
    if (count > 3) {
	psx_tracker.cmd.six = 1;
	psx_tracker.cmd.arg1 = arg[3];
	psx_tracker.cmd.arg2 = count > 4 ? arg[4] : 0;
	psx_tracker.cmd.arg3 = count > 5 ? arg[5] : 0;

	ret = syscall(syscall_nr,
		      psx_tracker.cmd.arg1,
		      psx_tracker.cmd.arg2,
		      psx_tracker.cmd.arg3,
		      psx_tracker.cmd.arg4,
		      psx_tracker.cmd.arg5,
		      psx_tracker.cmd.arg6);
    } else {
	psx_tracker.cmd.six = 0;

	ret = syscall(syscall_nr,
		      psx_tracker.cmd.arg1,
		      psx_tracker.cmd.arg2,
		      psx_tracker.cmd.arg3);
    }

    if (ret == -1 || !psx_tracker.initialized) {
	goto defer;
    }

    int restore_errno = errno;
    psx_tracker.cmd.active = 1;

    pthread_t self = pthread_self();
    registered_thread_t *next = NULL, *ref;
    for (ref = psx_tracker.root; ref; ref = next) {
	next = ref->next;
	if (ref->thread == self) {
	    continue;
	}
	if (pthread_kill(ref->thread, psx_tracker.psx_sig) == 0) {
	    pthread_mutex_lock(&psx_tracker.cmd.mu);
	    psx_tracker.cmd.todo++;
	    pthread_mutex_unlock(&psx_tracker.cmd.mu);
	    continue;
	}

	/* need to remove now invalid thread id from linked list */
	if (ref == psx_tracker.root) {
	    psx_tracker.root = next;
	} else if (ref->prev) {
	    ref->prev->next = next;
	}
	if (next) {
	    next->prev = ref->prev;
	}
	free(ref);
    }

    pthread_mutex_lock(&psx_tracker.cmd.mu);
    while (psx_tracker.cmd.todo) {
	pthread_cond_wait(&psx_tracker.cmd.cond, &psx_tracker.cmd.mu);
    }
    pthread_mutex_unlock(&psx_tracker.cmd.mu);

    errno = restore_errno;
defer:

    psx_tracker.cmd.active = 0;
    pthread_mutex_unlock(&psx_tracker.mu);

    return ret;
}