Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/src/exec_intercept.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2021-2023 Todd C. Miller <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include <config.h>
20
21
#include <sys/socket.h>
22
#include <netinet/in.h>
23
#include <netinet/tcp.h>
24
25
#if defined(HAVE_STDINT_H)
26
# include <stdint.h>
27
#elif defined(HAVE_INTTYPES_H)
28
# include <inttypes.h>
29
#endif
30
#include <stdlib.h>
31
#include <string.h>
32
#include <unistd.h>
33
#include <errno.h>
34
#include <fcntl.h>
35
#include <limits.h>
36
37
#include <sudo.h>
38
#include <sudo_exec.h>
39
#include <sudo_plugin.h>
40
#include <sudo_plugin_int.h>
41
#include <sudo_rand.h>
42
#include <intercept.pb-c.h>
43
#include <exec_intercept.h>
44
45
#ifdef _PATH_SUDO_INTERCEPT
46
static union sudo_token_un intercept_token;
47
static in_port_t intercept_listen_port;
48
static struct intercept_closure *accept_closure;
49
static void intercept_accept_cb(int fd, int what, void *v);
50
static void intercept_cb(int fd, int what, void *v);
51
52
/*
53
* Enable the closure->ev event with the specified events and callback,
54
* and set the connection state to new_state if it is valid.
55
* Returns true on success, else false.
56
*/
57
static bool
58
intercept_enable_event(int fd, short events, enum intercept_state new_state,
59
sudo_ev_callback_t callback, struct intercept_closure *closure)
60
{
61
int rc;
62
debug_decl(intercept_enable_event, SUDO_DEBUG_EXEC);
63
64
rc = sudo_ev_set(&closure->ev, fd, events, callback, closure);
65
if (rc == -1 || sudo_ev_add(NULL, &closure->ev, NULL, false) == -1) {
66
sudo_warn("%s", U_("unable to add event to queue"));
67
debug_return_bool(false);
68
}
69
if (new_state != INVALID_STATE)
70
closure->state = new_state;
71
debug_return_bool(true);
72
}
73
74
static bool
75
enable_read_event(int fd, enum intercept_state new_state,
76
sudo_ev_callback_t callback, struct intercept_closure *closure)
77
{
78
return intercept_enable_event(fd, SUDO_EV_READ|SUDO_EV_PERSIST,
79
new_state, callback, closure);
80
}
81
82
static bool
83
enable_write_event(int fd, sudo_ev_callback_t callback,
84
struct intercept_closure *closure)
85
{
86
return intercept_enable_event(fd, SUDO_EV_WRITE|SUDO_EV_PERSIST,
87
INVALID_STATE, callback, closure);
88
}
89
90
/*
91
* Create an intercept closure.
92
* Returns an opaque pointer to the closure, which is also
93
* passed to the event callback when not using ptrace(2).
94
*/
95
void *
96
intercept_setup(int fd, struct sudo_event_base *evbase,
97
const struct command_details *details)
98
{
99
struct intercept_closure *closure;
100
debug_decl(intercept_setup, SUDO_DEBUG_EXEC);
101
102
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
103
"intercept fd %d\n", fd);
104
105
closure = calloc(1, sizeof(*closure));
106
if (closure == NULL) {
107
sudo_warnx("%s", U_("unable to allocate memory"));
108
goto bad;
109
}
110
closure->details = details;
111
closure->listen_sock = -1;
112
sudo_ev_set_base(&closure->ev, evbase);
113
114
if (ISSET(details->flags, CD_USE_PTRACE)) {
115
/*
116
* We can perform a policy check immediately using ptrace(2)
117
* but should ignore the execve(2) of the initial command
118
* (and sesh for SELinux RBAC).
119
*/
120
closure->state = RECV_POLICY_CHECK;
121
closure->initial_command = 1;
122
if (ISSET(details->flags, CD_RBAC_ENABLED))
123
closure->initial_command++;
124
} else {
125
/*
126
* Not using ptrace(2), use LD_PRELOAD (or its equivalent). If
127
* we've already seen an InterceptHello, expect a policy check first.
128
*/
129
const int new_state = sudo_token_isset(intercept_token) ?
130
RECV_SECRET : RECV_HELLO_INITIAL;
131
if (!enable_read_event(fd, new_state, intercept_cb, closure))
132
goto bad;
133
}
134
135
debug_return_ptr(closure);
136
137
bad:
138
free(closure);
139
debug_return_ptr(NULL);
140
}
141
142
/*
143
* Reset intercept_closure so it can be reused.
144
*/
145
void
146
intercept_closure_reset(struct intercept_closure *closure)
147
{
148
size_t n;
149
debug_decl(intercept_closure_reset, SUDO_DEBUG_EXEC);
150
151
if (closure->listen_sock != -1) {
152
close(closure->listen_sock);
153
closure->listen_sock = -1;
154
}
155
free(closure->buf);
156
free(closure->command);
157
if (closure->run_argv != NULL) {
158
for (n = 0; closure->run_argv[n] != NULL; n++)
159
free(closure->run_argv[n]);
160
free(closure->run_argv);
161
}
162
if (closure->run_envp != NULL) {
163
for (n = 0; closure->run_envp[n] != NULL; n++)
164
free(closure->run_envp[n]);
165
free(closure->run_envp);
166
}
167
closure->errstr = NULL;
168
closure->command = NULL;
169
closure->run_argv = NULL;
170
closure->run_envp = NULL;
171
closure->buf = NULL;
172
closure->len = 0;
173
closure->off = 0;
174
/* Does not currently reset token. */
175
176
debug_return;
177
}
178
179
/*
180
* Close intercept socket and free closure when we are done with
181
* the connection.
182
*/
183
static void
184
intercept_connection_close(struct intercept_closure *closure)
185
{
186
const int fd = sudo_ev_get_fd(&closure->ev);
187
debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC);
188
189
sudo_ev_del(NULL, &closure->ev);
190
close(fd);
191
intercept_closure_reset(closure);
192
free(closure);
193
194
debug_return;
195
}
196
197
void
198
intercept_cleanup(struct exec_closure *ec)
199
{
200
debug_decl(intercept_cleanup, SUDO_DEBUG_EXEC);
201
202
if (accept_closure != NULL) {
203
/* DSO-based intercept. */
204
intercept_connection_close(accept_closure);
205
accept_closure = NULL;
206
} else if (ec->intercept != NULL) {
207
/* ptrace-based intercept. */
208
intercept_closure_reset(ec->intercept);
209
free(ec->intercept);
210
ec->intercept = NULL;
211
}
212
213
debug_return;
214
}
215
216
/*
217
* Prepare to listen on localhost using an ephemeral port.
218
* Sets intercept_token and intercept_listen_port as side effects.
219
*/
220
static bool
221
prepare_listener(struct intercept_closure *closure)
222
{
223
struct sockaddr_in sin4;
224
socklen_t sin4_len = sizeof(sin4);
225
int sock = -1;
226
debug_decl(prepare_listener, SUDO_DEBUG_EXEC);
227
228
/* Generate a random token. */
229
do {
230
arc4random_buf(&intercept_token, sizeof(intercept_token));
231
} while (!sudo_token_isset(intercept_token));
232
233
/* Create localhost listener socket (currently AF_INET only). */
234
sock = socket(AF_INET, SOCK_STREAM, 0);
235
if (sock == -1) {
236
sudo_warn("socket");
237
goto bad;
238
}
239
memset(&sin4, 0, sizeof(sin4));
240
sin4.sin_family = AF_INET;
241
sin4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
242
sin4.sin_port = 0;
243
if (bind(sock, (struct sockaddr *)&sin4, sizeof(sin4)) == -1) {
244
sudo_warn("bind");
245
goto bad;
246
}
247
if (getsockname(sock, (struct sockaddr *)&sin4, &sin4_len) == -1) {
248
sudo_warn("getsockname");
249
goto bad;
250
}
251
if (listen(sock, SOMAXCONN) == -1) {
252
sudo_warn("listen");
253
goto bad;
254
}
255
256
closure->listen_sock = sock;
257
intercept_listen_port = ntohs(sin4.sin_port);
258
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
259
"%s: listening on port %hu", __func__, intercept_listen_port);
260
261
debug_return_bool(true);
262
263
bad:
264
if (sock != -1)
265
close(sock);
266
debug_return_bool(false);
267
}
268
269
/*
270
* Allocate a new command_info[] and update command and runcwd in it.
271
* Fills in cmnd_out with a copy of the command if not NULL.
272
* Returns the new command_info[] which the caller must free.
273
*/
274
static char **
275
update_command_info(char * const *old_command_info, const char *cmnd,
276
const char *runcwd, char **cmnd_out, struct intercept_closure *closure)
277
{
278
char **command_info;
279
char * const *oci;
280
size_t n;
281
debug_decl(update_command_info, SUDO_DEBUG_EXEC);
282
283
/* Rebuild command_info[] with new command and add a runcwd. */
284
for (n = 0; old_command_info[n] != NULL; n++)
285
continue;
286
command_info = reallocarray(NULL, n + 3, sizeof(char *));
287
if (command_info == NULL) {
288
goto bad;
289
}
290
for (oci = old_command_info, n = 0; *oci != NULL; oci++) {
291
const char *cp = *oci;
292
switch (*cp) {
293
case 'c':
294
if (strncmp(cp, "command=", sizeof("command=") - 1) == 0) {
295
if (cmnd == NULL) {
296
/* No new command specified, use old value. */
297
cmnd = cp + sizeof("command=") - 1;
298
}
299
/* Filled in at the end. */
300
continue;
301
}
302
break;
303
case 'r':
304
if (strncmp(cp, "runcwd=", sizeof("runcwd=") - 1) == 0) {
305
/* Filled in at the end. */
306
continue;
307
}
308
break;
309
}
310
command_info[n] = strdup(cp);
311
if (command_info[n] == NULL) {
312
goto bad;
313
}
314
n++;
315
}
316
317
/* Append new command. */
318
if (cmnd == NULL) {
319
closure->errstr = N_("command not set by the security policy");
320
goto bad;
321
}
322
command_info[n] = sudo_new_key_val("command", cmnd);
323
if (command_info[n] == NULL) {
324
goto oom;
325
}
326
n++;
327
328
/* Append actual runcwd. */
329
command_info[n] = sudo_new_key_val("runcwd", runcwd ? runcwd : "unknown");
330
if (command_info[n] == NULL) {
331
goto oom;
332
}
333
n++;
334
335
command_info[n] = NULL;
336
337
if (cmnd_out != NULL) {
338
*cmnd_out = strdup(cmnd);
339
if (*cmnd_out == NULL) {
340
goto oom;
341
}
342
}
343
debug_return_ptr(command_info);
344
345
oom:
346
closure->errstr = N_("unable to allocate memory");
347
348
bad:
349
if (command_info != NULL) {
350
for (n = 0; command_info[n] != NULL; n++) {
351
free(command_info[n]);
352
}
353
free(command_info);
354
}
355
debug_return_ptr(NULL);
356
}
357
358
/*
359
* Perform a policy check for the given command.
360
* While argv must be NULL-terminated, envp need not be.
361
* Sets closure->state to the result of the policy check before returning.
362
* Return false on error, else true.
363
*/
364
bool
365
intercept_check_policy(const char *command, int argc, char **argv, int envc,
366
char **envp, const char *runcwd, int *oldcwd, void *v)
367
{
368
struct intercept_closure *closure = v;
369
char **command_info = NULL;
370
char **command_info_copy = NULL;
371
char **user_env_out = NULL;
372
char **run_argv = NULL;
373
int rc, saved_dir = -1;
374
size_t i;
375
bool ret = true;
376
struct stat sb;
377
debug_decl(intercept_check_policy, SUDO_DEBUG_EXEC);
378
379
/* Change to runcwd before the policy check if necessary. */
380
if (*command != '/') {
381
if (runcwd == NULL || (saved_dir = open(".", O_RDONLY)) == -1 ||
382
chdir(runcwd) == -1) {
383
if (runcwd == NULL) {
384
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
385
"relative command path but no runcwd specified");
386
} else if (saved_dir == -1) {
387
sudo_debug_printf(
388
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
389
"unable to open current directory for reading");
390
} else {
391
sudo_debug_printf(
392
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
393
"unable to chdir for %s", runcwd);
394
}
395
if (ISSET(closure->details->flags, CD_INTERCEPT)) {
396
/* Inability to change cwd is fatal in intercept mode. */
397
if (closure->errstr == NULL)
398
closure->errstr = N_("command rejected by policy");
399
audit_reject(policy_plugin.name, SUDO_POLICY_PLUGIN,
400
closure->errstr, closure->details->info);
401
closure->state = POLICY_REJECT;
402
goto done;
403
}
404
}
405
}
406
407
/*
408
* Short-circuit the policy check if the command doesn't exist.
409
* Otherwise, both sudo and the shell will report the error.
410
*/
411
if (stat(command, &sb) == -1) {
412
closure->errstr = NULL;
413
closure->state = POLICY_ERROR;
414
goto done;
415
}
416
417
if (ISSET(closure->details->flags, CD_INTERCEPT)) {
418
/* We don't currently have a good way to validate the environment. */
419
sudo_debug_set_active_instance(policy_plugin.debug_instance);
420
rc = policy_plugin.u.policy->check_policy(argc, argv, NULL,
421
&command_info, &run_argv, &user_env_out, &closure->errstr);
422
sudo_debug_set_active_instance(sudo_debug_instance);
423
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
424
"check_policy returns %d", rc);
425
426
switch (rc) {
427
case 1:
428
/* Rebuild command_info[] with runcwd and extract command. */
429
command_info_copy = update_command_info(command_info, NULL,
430
runcwd, &closure->command, closure);
431
if (command_info_copy == NULL)
432
goto oom;
433
command_info = command_info_copy;
434
closure->state = POLICY_ACCEPT;
435
break;
436
case 0:
437
if (closure->errstr == NULL)
438
closure->errstr = N_("command rejected by policy");
439
audit_reject(policy_plugin.name, SUDO_POLICY_PLUGIN,
440
closure->errstr, command_info);
441
closure->state = POLICY_REJECT;
442
goto done;
443
default:
444
/* Plugin error? */
445
goto bad;
446
}
447
} else {
448
/* No actual policy check, just logging child processes. */
449
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
450
"not checking policy, audit only");
451
closure->command = strdup(command);
452
if (closure->command == NULL)
453
goto oom;
454
455
/* Rebuild command_info[] with new command and runcwd. */
456
command_info_copy = update_command_info(closure->details->info,
457
command, runcwd, NULL, closure);
458
if (command_info_copy == NULL)
459
goto oom;
460
command_info = command_info_copy;
461
closure->state = POLICY_ACCEPT;
462
run_argv = argv;
463
}
464
465
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
466
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
467
"run_command: %s", closure->command);
468
for (i = 0; command_info[i] != NULL; i++) {
469
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
470
"command_info[%zu]: %s", i, command_info[i]);
471
}
472
for (i = 0; run_argv[i] != NULL; i++) {
473
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
474
"run_argv[%zu]: %s", i, run_argv[i]);
475
}
476
}
477
478
/* Make a copy of run_argv, it may share contents of argv. */
479
for (i = 0; run_argv[i] != NULL; i++)
480
continue;
481
closure->run_argv = reallocarray(NULL, i + 1, sizeof(char *));
482
if (closure->run_argv == NULL)
483
goto oom;
484
for (i = 0; run_argv[i] != NULL; i++) {
485
closure->run_argv[i] = strdup(run_argv[i]);
486
if (closure->run_argv[i] == NULL)
487
goto oom;
488
}
489
closure->run_argv[i] = NULL;
490
491
/* Make a copy of envp, which may not be NULL-terminated. */
492
closure->run_envp = reallocarray(NULL, (size_t)envc + 1, sizeof(char *));
493
if (closure->run_envp == NULL)
494
goto oom;
495
for (i = 0; i < (size_t)envc; i++) {
496
closure->run_envp[i] = strdup(envp[i]);
497
if (closure->run_envp[i] == NULL)
498
goto oom;
499
}
500
closure->run_envp[i] = NULL;
501
502
if (ISSET(closure->details->flags, CD_INTERCEPT)) {
503
audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN, command_info,
504
closure->run_argv, closure->run_envp);
505
506
/* Call approval plugins and audit the result. */
507
if (!approval_check(command_info, closure->run_argv, closure->run_envp)) {
508
if (closure->errstr == NULL)
509
closure->errstr = N_("approval plugin error");
510
closure->state = POLICY_REJECT;
511
goto done;
512
}
513
}
514
515
/* Audit the event again for the sudo front-end. */
516
audit_accept("sudo", SUDO_FRONT_END, command_info, closure->run_argv,
517
closure->run_envp);
518
519
goto done;
520
521
oom:
522
closure->errstr = N_("unable to allocate memory");
523
524
bad:
525
if (saved_dir != -1) {
526
if (fchdir(saved_dir) == -1) {
527
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
528
"%s: unable to restore saved cwd", __func__);
529
}
530
close(saved_dir);
531
saved_dir = -1;
532
}
533
if (closure->errstr == NULL)
534
closure->errstr = N_("policy plugin error");
535
audit_error(policy_plugin.name, SUDO_POLICY_PLUGIN, closure->errstr,
536
command_info ? command_info : closure->details->info);
537
closure->state = POLICY_ERROR;
538
ret = false;
539
540
done:
541
if (command_info_copy != NULL) {
542
for (i = 0; command_info_copy[i] != NULL; i++) {
543
free(command_info_copy[i]);
544
}
545
free(command_info_copy);
546
}
547
*oldcwd = saved_dir;
548
549
debug_return_bool(ret);
550
}
551
552
static bool
553
intercept_check_policy_req(PolicyCheckRequest *req,
554
struct intercept_closure *closure)
555
{
556
char **argv = NULL;
557
bool ret = false;
558
int oldcwd = -1;
559
size_t n;
560
debug_decl(intercept_check_policy_req, SUDO_DEBUG_EXEC);
561
562
if (req->command == NULL || req->n_argv > INT_MAX || req->n_envp > INT_MAX) {
563
closure->errstr = N_("invalid PolicyCheckRequest");
564
goto done;
565
}
566
567
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
568
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
569
"req_command: %s", req->command);
570
for (n = 0; n < req->n_argv; n++) {
571
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
572
"req_argv[%zu]: %s", n, req->argv[n]);
573
}
574
}
575
576
/* If argv is empty, reserve an extra slot for the command. */
577
if (req->n_argv == 0)
578
req->n_argv = 1;
579
580
/*
581
* Rebuild argv from PolicyCheckReq so it is NULL-terminated.
582
* The plugin API requires us to pass the pathname to exec in argv[0].
583
*/
584
argv = reallocarray(NULL, req->n_argv + 1, sizeof(char *));
585
if (argv == NULL) {
586
closure->errstr = N_("unable to allocate memory");
587
goto done;
588
}
589
argv[0] = req->command;
590
for (n = 1; n < req->n_argv; n++) {
591
argv[n] = req->argv[n];
592
}
593
argv[n] = NULL;
594
595
ret = intercept_check_policy(req->command, (int)req->n_argv, argv,
596
(int)req->n_envp, req->envp, req->cwd, &oldcwd, closure);
597
598
done:
599
if (oldcwd != -1) {
600
if (fchdir(oldcwd) == -1) {
601
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
602
"%s: unable to restore saved cwd", __func__);
603
}
604
close(oldcwd);
605
}
606
607
free(argv);
608
609
debug_return_bool(ret);
610
}
611
612
/*
613
* Read token from sudo_intercept.so and verify w/ intercept_token.
614
* Returns true on success, false on mismatch and -1 on error.
615
*/
616
static int
617
intercept_verify_token(int fd, struct intercept_closure *closure)
618
{
619
ssize_t nread;
620
debug_decl(intercept_verify_token, SUDO_DEBUG_EXEC);
621
622
nread = recv(fd, closure->token.u8 + closure->off,
623
sizeof(closure->token) - closure->off, 0);
624
switch (nread) {
625
case 0:
626
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
627
"EOF reading token");
628
debug_return_int(false);
629
case -1:
630
debug_return_int(-1);
631
default:
632
if (nread + closure->off == sizeof(closure->token))
633
break;
634
/* partial read, update offset and try again */
635
closure->off += (uint32_t)nread;
636
errno = EAGAIN;
637
debug_return_int(-1);
638
}
639
640
closure->off = 0;
641
if (memcmp(&closure->token, &intercept_token, sizeof(closure->token)) != 0) {
642
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
643
"token mismatch: got 0x%8x%8x%8x%8x, expected 0x%8x%8x%8x%8x",
644
closure->token.u32[3], closure->token.u32[2],
645
closure->token.u32[1], closure->token.u32[0],
646
intercept_token.u32[3], intercept_token.u32[2],
647
intercept_token.u32[1], intercept_token.u32[0]);
648
debug_return_int(false);
649
}
650
debug_return_int(true);
651
}
652
653
/*
654
* Read a message from sudo_intercept.so and act on it.
655
*/
656
static bool
657
intercept_read(int fd, struct intercept_closure *closure)
658
{
659
InterceptRequest *req = NULL;
660
bool ret = false;
661
ssize_t nread;
662
debug_decl(intercept_read, SUDO_DEBUG_EXEC);
663
664
if (closure->state == RECV_SECRET) {
665
switch (intercept_verify_token(fd, closure)) {
666
case true:
667
closure->state = RECV_POLICY_CHECK;
668
break;
669
case false:
670
goto done;
671
default:
672
if (errno == EINTR || errno == EAGAIN) {
673
sudo_debug_printf(
674
SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
675
"reading intercept token");
676
debug_return_bool(true);
677
}
678
sudo_warn("recv");
679
goto done;
680
}
681
}
682
683
if (closure->len == 0) {
684
uint32_t req_len;
685
686
/* Read message size (uint32_t in host byte order). */
687
nread = recv(fd, &req_len, sizeof(req_len), 0);
688
if (nread != sizeof(req_len)) {
689
if (nread == -1) {
690
if (errno == EINTR || errno == EAGAIN) {
691
sudo_debug_printf(
692
SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
693
"reading intercept message size");
694
debug_return_bool(true);
695
}
696
sudo_warn("recv");
697
}
698
goto done;
699
}
700
701
if (req_len == 0) {
702
/* zero-length message is possible */
703
goto unpack;
704
}
705
if (req_len > MESSAGE_SIZE_MAX) {
706
sudo_warnx(U_("client request too large: %zu"), (size_t)req_len);
707
goto done;
708
}
709
if ((closure->buf = malloc(req_len)) == NULL) {
710
sudo_warnx("%s", U_("unable to allocate memory"));
711
goto done;
712
}
713
closure->len = req_len;
714
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: expecting %u bytes from client",
715
__func__, closure->len);
716
}
717
718
nread = recv(fd, closure->buf + closure->off, closure->len - closure->off,
719
0);
720
switch (nread) {
721
case 0:
722
/* EOF, other side must have exited. */
723
goto done;
724
case -1:
725
if (errno == EINTR || errno == EAGAIN) {
726
sudo_debug_printf(
727
SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
728
"reading intercept message");
729
debug_return_bool(true);
730
}
731
sudo_warn("recv");
732
goto done;
733
default:
734
closure->off += (uint32_t)nread;
735
break;
736
}
737
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from client",
738
__func__, nread);
739
740
if (closure->off != closure->len) {
741
/* Partial read. */
742
debug_return_bool(true);
743
}
744
745
unpack:
746
req = intercept_request__unpack(NULL, closure->len, closure->buf);
747
if (req == NULL) {
748
sudo_warnx(U_("unable to unpack %s size %zu"), "InterceptRequest",
749
(size_t)closure->len);
750
goto done;
751
}
752
753
sudo_debug_printf(SUDO_DEBUG_INFO,
754
"%s: finished receiving %u bytes from client", __func__, closure->len);
755
sudo_ev_del(NULL, &closure->ev);
756
free(closure->buf);
757
closure->buf = NULL;
758
closure->len = 0;
759
closure->off = 0;
760
761
switch (req->type_case) {
762
case INTERCEPT_REQUEST__TYPE_POLICY_CHECK_REQ:
763
if (closure->state != RECV_POLICY_CHECK) {
764
/* Only a single policy check request is allowed. */
765
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
766
"state mismatch, expected RECV_POLICY_CHECK (%d), got %d",
767
RECV_POLICY_CHECK, closure->state);
768
goto done;
769
}
770
771
ret = intercept_check_policy_req(req->u.policy_check_req, closure);
772
if (!ret)
773
goto done;
774
if (!ISSET(closure->details->flags, CD_INTERCEPT)) {
775
/* Just logging, reuse event to read next InterceptHello. */
776
ret = enable_read_event(fd, RECV_HELLO, intercept_cb, closure);
777
goto done;
778
}
779
break;
780
case INTERCEPT_REQUEST__TYPE_HELLO:
781
switch (closure->state) {
782
case RECV_HELLO_INITIAL:
783
if (!prepare_listener(closure))
784
goto done;
785
break;
786
case RECV_HELLO:
787
break;
788
default:
789
/* Only accept hello on a socket with an accepted command. */
790
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
791
"got InterceptHello without an accepted command");
792
goto done;
793
}
794
break;
795
default:
796
sudo_warnx(U_("unexpected type_case value %d in %s from %s"),
797
req->type_case, "InterceptRequest", "sudo_intercept.so");
798
goto done;
799
}
800
801
/* Switch event to write mode for the reply. */
802
if (!enable_write_event(fd, intercept_cb, closure))
803
goto done;
804
805
ret = true;
806
807
done:
808
intercept_request__free_unpacked(req, NULL);
809
debug_return_bool(ret);
810
}
811
812
static bool
813
fmt_intercept_response(InterceptResponse *resp,
814
struct intercept_closure *closure)
815
{
816
uint32_t resp_len;
817
bool ret = false;
818
debug_decl(fmt_intercept_response, SUDO_DEBUG_EXEC);
819
820
closure->len = (uint32_t)intercept_response__get_packed_size(resp);
821
if (closure->len > MESSAGE_SIZE_MAX) {
822
sudo_warnx(U_("server message too large: %zu"), (size_t)closure->len);
823
goto done;
824
}
825
826
/* Wire message size is used for length encoding, precedes message. */
827
resp_len = closure->len;
828
closure->len += sizeof(resp_len);
829
830
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
831
"size + InterceptResponse %zu bytes", (size_t)closure->len);
832
833
if ((closure->buf = malloc(closure->len)) == NULL) {
834
sudo_warnx("%s", U_("unable to allocate memory"));
835
goto done;
836
}
837
memcpy(closure->buf, &resp_len, sizeof(resp_len));
838
intercept_response__pack(resp, closure->buf + sizeof(resp_len));
839
840
ret = true;
841
842
done:
843
debug_return_bool(ret);
844
}
845
846
static bool
847
fmt_hello_response(struct intercept_closure *closure)
848
{
849
HelloResponse hello_resp = HELLO_RESPONSE__INIT;
850
InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
851
debug_decl(fmt_hello_response, SUDO_DEBUG_EXEC);
852
853
hello_resp.portno = intercept_listen_port;
854
hello_resp.token_lo = intercept_token.u64[0];
855
hello_resp.token_hi = intercept_token.u64[1];
856
hello_resp.log_only = !ISSET(closure->details->flags, CD_INTERCEPT);
857
858
resp.u.hello_resp = &hello_resp;
859
resp.type_case = INTERCEPT_RESPONSE__TYPE_HELLO_RESP;
860
861
debug_return_bool(fmt_intercept_response(&resp, closure));
862
}
863
864
static bool
865
fmt_accept_message(struct intercept_closure *closure)
866
{
867
PolicyAcceptMessage msg = POLICY_ACCEPT_MESSAGE__INIT;
868
InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
869
size_t n;
870
debug_decl(fmt_accept_message, SUDO_DEBUG_EXEC);
871
872
msg.run_command = closure->command;
873
msg.run_argv = closure->run_argv;
874
for (n = 0; closure->run_argv[n] != NULL; n++)
875
continue;
876
msg.n_run_argv = n;
877
msg.run_envp = closure->run_envp;
878
for (n = 0; closure->run_envp[n] != NULL; n++)
879
continue;
880
msg.n_run_envp = n;
881
882
resp.u.accept_msg = &msg;
883
resp.type_case = INTERCEPT_RESPONSE__TYPE_ACCEPT_MSG;
884
885
debug_return_bool(fmt_intercept_response(&resp, closure));
886
}
887
888
static bool
889
fmt_reject_message(struct intercept_closure *closure)
890
{
891
PolicyRejectMessage msg = POLICY_REJECT_MESSAGE__INIT;
892
InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
893
debug_decl(fmt_reject_message, SUDO_DEBUG_EXEC);
894
895
msg.reject_message = (char *)closure->errstr;
896
897
resp.u.reject_msg = &msg;
898
resp.type_case = INTERCEPT_RESPONSE__TYPE_REJECT_MSG;
899
900
debug_return_bool(fmt_intercept_response(&resp, closure));
901
}
902
903
static bool
904
fmt_error_message(struct intercept_closure *closure)
905
{
906
PolicyErrorMessage msg = POLICY_ERROR_MESSAGE__INIT;
907
InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
908
debug_decl(fmt_error_message, SUDO_DEBUG_EXEC);
909
910
msg.error_message = (char *)closure->errstr;
911
912
resp.u.error_msg = &msg;
913
resp.type_case = INTERCEPT_RESPONSE__TYPE_ERROR_MSG;
914
915
debug_return_bool(fmt_intercept_response(&resp, closure));
916
}
917
918
/*
919
* Write a response to sudo_intercept.so.
920
*/
921
static bool
922
intercept_write(int fd, struct intercept_closure *closure)
923
{
924
bool ret = false;
925
ssize_t nwritten;
926
debug_decl(intercept_write, SUDO_DEBUG_EXEC);
927
928
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "state %d",
929
closure->state);
930
931
if (closure->len == 0) {
932
/* Format new message. */
933
switch (closure->state) {
934
case RECV_HELLO_INITIAL:
935
case RECV_HELLO:
936
if (!fmt_hello_response(closure))
937
goto done;
938
break;
939
case POLICY_ACCEPT:
940
if (!fmt_accept_message(closure))
941
goto done;
942
break;
943
case POLICY_REJECT:
944
if (!fmt_reject_message(closure))
945
goto done;
946
break;
947
default:
948
if (!fmt_error_message(closure))
949
goto done;
950
break;
951
}
952
}
953
954
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending %u bytes to client",
955
__func__, closure->len - closure->off);
956
nwritten = send(fd, closure->buf + closure->off,
957
closure->len - closure->off, 0);
958
if (nwritten == -1) {
959
if (errno == EINTR || errno == EAGAIN) {
960
sudo_debug_printf(
961
SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
962
"writing intercept message");
963
debug_return_bool(true);
964
}
965
sudo_warn("send");
966
goto done;
967
}
968
closure->off += (uint32_t)nwritten;
969
970
if (closure->off != closure->len) {
971
/* Partial write. */
972
debug_return_bool(true);
973
}
974
975
sudo_debug_printf(SUDO_DEBUG_INFO,
976
"%s: sent %u bytes to client", __func__, closure->len);
977
sudo_ev_del(NULL, &closure->ev);
978
free(closure->buf);
979
closure->buf = NULL;
980
closure->len = 0;
981
closure->off = 0;
982
983
switch (closure->state) {
984
case RECV_HELLO_INITIAL:
985
/* Reuse the listener event. */
986
close(fd);
987
if (!enable_read_event(closure->listen_sock, RECV_CONNECTION,
988
intercept_accept_cb, closure))
989
goto done;
990
closure->listen_sock = -1;
991
closure->state = RECV_CONNECTION;
992
accept_closure = closure;
993
break;
994
case POLICY_ACCEPT:
995
/* Reuse event to read InterceptHello from sudo_intercept.so ctor. */
996
if (!enable_read_event(fd, RECV_HELLO, intercept_cb, closure))
997
goto done;
998
break;
999
default:
1000
/* Done with this connection. */
1001
intercept_connection_close(closure);
1002
}
1003
1004
ret = true;
1005
1006
done:
1007
debug_return_bool(ret);
1008
}
1009
1010
static void
1011
intercept_cb(int fd, int what, void *v)
1012
{
1013
struct intercept_closure *closure = v;
1014
bool success = false;
1015
debug_decl(intercept_cb, SUDO_DEBUG_EXEC);
1016
1017
switch (what) {
1018
case SUDO_EV_READ:
1019
success = intercept_read(fd, closure);
1020
break;
1021
case SUDO_EV_WRITE:
1022
success = intercept_write(fd, closure);
1023
break;
1024
default:
1025
sudo_warnx("%s: unexpected event type %d", __func__, what);
1026
break;
1027
}
1028
1029
if (!success)
1030
intercept_connection_close(closure);
1031
1032
debug_return;
1033
}
1034
1035
/*
1036
* Accept a new connection from the client register a new event for it.
1037
*/
1038
static void
1039
intercept_accept_cb(int fd, int what, void *v)
1040
{
1041
struct intercept_closure *closure = v;
1042
struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
1043
struct sockaddr_in sin4;
1044
socklen_t sin4_len = sizeof(sin4);
1045
int client_sock, flags, on = 1;
1046
debug_decl(intercept_accept_cb, SUDO_DEBUG_EXEC);
1047
1048
if (closure->state != RECV_CONNECTION) {
1049
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
1050
"state mismatch, expected RECV_CONNECTION (%d), got %d",
1051
RECV_CONNECTION, closure->state);
1052
intercept_connection_close(closure);
1053
accept_closure = NULL;
1054
debug_return;
1055
}
1056
1057
client_sock = accept(fd, (struct sockaddr *)&sin4, &sin4_len);
1058
if (client_sock == -1) {
1059
sudo_warn("accept");
1060
goto bad;
1061
}
1062
flags = fcntl(client_sock, F_GETFL, 0);
1063
if (flags != -1)
1064
(void)fcntl(client_sock, F_SETFL, flags | O_NONBLOCK);
1065
1066
/* Send data immediately, we need low latency IPC. */
1067
(void)setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
1068
1069
/*
1070
* Create a new intercept closure and register an event for client_sock.
1071
*/
1072
if (intercept_setup(client_sock, evbase, closure->details) == NULL) {
1073
goto bad;
1074
}
1075
1076
debug_return;
1077
1078
bad:
1079
if (client_sock != -1)
1080
close(client_sock);
1081
debug_return;
1082
}
1083
#else /* _PATH_SUDO_INTERCEPT */
1084
void *
1085
intercept_setup(int fd, struct sudo_event_base *evbase,
1086
const struct command_details *details)
1087
{
1088
debug_decl(intercept_setup, SUDO_DEBUG_EXEC);
1089
1090
/* Intercept support not compiled in. */
1091
1092
debug_return_ptr(NULL);
1093
}
1094
1095
void
1096
intercept_cleanup(struct exec_closure *ec)
1097
{
1098
debug_decl(intercept_cleanup, SUDO_DEBUG_EXEC);
1099
1100
/* Intercept support not compiled in. */
1101
1102
debug_return;
1103
}
1104
#endif /* _PATH_SUDO_INTERCEPT */
1105
1106