Add scaffolding for ptrace-based intercept mode.
This commit is contained in:
1
MANIFEST
1
MANIFEST
@@ -1183,6 +1183,7 @@ src/env_hooks.c
|
||||
src/exec.c
|
||||
src/exec_common.c
|
||||
src/exec_intercept.c
|
||||
src/exec_ptrace.c
|
||||
src/exec_monitor.c
|
||||
src/exec_nopty.c
|
||||
src/exec_preload.c
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: ISC
|
||||
#
|
||||
# Copyright (c) 2010-2021 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
# Copyright (c) 2010-2022 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
#
|
||||
# Permission to use, copy, modify, and distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -141,10 +141,10 @@ SHELL = @SHELL@
|
||||
PROGS = @PROGS@
|
||||
|
||||
OBJS = conversation.o copy_file.o edit_open.o env_hooks.o exec.o exec_common.o \
|
||||
exec_intercept.o exec_monitor.o exec_nopty.o exec_preload.o exec_pty.o \
|
||||
get_pty.o hooks.o limits.o load_plugins.o net_ifs.o parse_args.o \
|
||||
preserve_fds.o signal.o sudo.o sudo_edit.o tcsetpgrp_nobg.o tgetpass.o \
|
||||
ttyname.o utmp.o @SUDO_OBJS@
|
||||
exec_intercept.o exec_monitor.o exec_nopty.o exec_preload.o \
|
||||
exec_ptrace.o exec_pty.o get_pty.o hooks.o limits.o load_plugins.o \
|
||||
net_ifs.o parse_args.o preserve_fds.o signal.o sudo.o sudo_edit.o \
|
||||
tcsetpgrp_nobg.o tgetpass.o ttyname.o utmp.o @SUDO_OBJS@
|
||||
|
||||
IOBJS = $(OBJS:.o=.i) sesh.i
|
||||
|
||||
@@ -607,6 +607,24 @@ exec_preload.o: $(srcdir)/exec_preload.c $(incdir)/compat/stdbool.h \
|
||||
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
|
||||
$(top_builddir)/config.h $(top_builddir)/pathnames.h
|
||||
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/exec_preload.c
|
||||
exec_ptrace.o: $(srcdir)/exec_ptrace.c $(incdir)/compat/stdbool.h \
|
||||
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
|
||||
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
|
||||
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
|
||||
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
|
||||
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
|
||||
$(top_builddir)/config.h $(top_builddir)/pathnames.h
|
||||
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/exec_ptrace.c
|
||||
exec_ptrace.i: $(srcdir)/exec_ptrace.c $(incdir)/compat/stdbool.h \
|
||||
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
|
||||
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
|
||||
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
|
||||
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
|
||||
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(srcdir)/sudo_exec.h \
|
||||
$(top_builddir)/config.h $(top_builddir)/pathnames.h
|
||||
$(CC) -E -o $@ $(CPPFLAGS) $<
|
||||
exec_ptrace.plog: exec_ptrace.i
|
||||
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/exec_ptrace.c --i-file $< --output-file $@
|
||||
exec_pty.o: $(srcdir)/exec_pty.c $(incdir)/compat/stdbool.h \
|
||||
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
|
||||
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
* Copyright (c) 2009-2022 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -86,6 +86,13 @@ exec_setup(struct command_details *details, int intercept_fd, int errfd)
|
||||
bool ret = false;
|
||||
debug_decl(exec_setup, SUDO_DEBUG_EXEC);
|
||||
|
||||
#ifdef HAVE_PTRACE_INTERCEPT
|
||||
if (ISSET(details->flags, CD_USE_PTRACE)) {
|
||||
if (!set_exec_filter())
|
||||
goto done;
|
||||
}
|
||||
#endif /* HAVE_PTRACE_INTERCEPT */
|
||||
|
||||
if (details->pw != NULL) {
|
||||
#ifdef HAVE_PROJECT_H
|
||||
set_project(details->pw);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
* Copyright (c) 2009-2022 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -89,6 +89,31 @@ enable_intercept(char *envp[], const char *dso, int intercept_fd)
|
||||
debug_return_ptr(envp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Called right before execve(2).
|
||||
* The tracee will be suspended until the tracer resumes it.
|
||||
*/
|
||||
static void
|
||||
enable_ptrace(void)
|
||||
{
|
||||
#ifdef HAVE_PTRACE_INTERCEPT
|
||||
const pid_t pid = getpid();
|
||||
debug_decl(enable_ptrace, SUDO_DEBUG_UTIL);
|
||||
|
||||
/*
|
||||
* Parent will trace child and intercept execve(2).
|
||||
* We stop the child here so the parent can seize control.
|
||||
*/
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: suspending child %d",
|
||||
__func__, (int)pid);
|
||||
kill(pid, SIGSTOP);
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: resuming child %d",
|
||||
__func__, (int)pid);
|
||||
|
||||
debug_return;
|
||||
#endif /* HAVE_PTRACE_INTERCEPT */
|
||||
}
|
||||
|
||||
/*
|
||||
* Like execve(2) but falls back to running through /bin/sh
|
||||
* ala execvp(3) if we get ENOEXEC.
|
||||
@@ -104,6 +129,8 @@ sudo_execve(int fd, const char *path, char *const argv[], char *envp[],
|
||||
/* Modify the environment as needed to trap execve(). */
|
||||
if (ISSET(flags, CD_NOEXEC))
|
||||
envp = disable_execute(envp, sudo_conf_noexec_path());
|
||||
else if (ISSET(flags, CD_USE_PTRACE))
|
||||
enable_ptrace();
|
||||
else if (ISSET(flags, CD_INTERCEPT|CD_LOG_SUBCMDS))
|
||||
envp = enable_intercept(envp, sudo_conf_intercept_path(), intercept_fd);
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
* Copyright (c) 2021-2022 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -89,6 +89,7 @@ intercept_setup(int fd, struct sudo_event_base *evbase,
|
||||
struct command_details *details)
|
||||
{
|
||||
struct intercept_closure *closure;
|
||||
int rc;
|
||||
debug_decl(intercept_setup, SUDO_DEBUG_EXEC);
|
||||
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
||||
@@ -99,19 +100,24 @@ intercept_setup(int fd, struct sudo_event_base *evbase,
|
||||
sudo_warnx("%s", U_("unable to allocate memory"));
|
||||
goto bad;
|
||||
}
|
||||
|
||||
/* If we've already seen an InterceptHello, expect a policy check first. */
|
||||
closure->state = sudo_token_isset(intercept_token) ?
|
||||
RECV_SECRET : RECV_HELLO_INITIAL;
|
||||
closure->details = details;
|
||||
closure->listen_sock = -1;
|
||||
|
||||
if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
|
||||
/* This cannot (currently) fail. */
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto bad;
|
||||
if (ISSET(details->flags, CD_USE_PTRACE)) {
|
||||
/* We can perform a policy check immediately using ptrace(2). */
|
||||
closure->state = RECV_POLICY_CHECK;
|
||||
} else {
|
||||
/*
|
||||
* Not using ptrace(2), use LD_PRELOAD (or its equivalent). If
|
||||
* we've already seen an InterceptHello, expect a policy check first.
|
||||
*/
|
||||
closure->state = sudo_token_isset(intercept_token) ?
|
||||
RECV_SECRET : RECV_HELLO_INITIAL;
|
||||
}
|
||||
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
|
||||
rc = sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST,
|
||||
intercept_cb, closure);
|
||||
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto bad;
|
||||
}
|
||||
@@ -124,21 +130,18 @@ bad:
|
||||
}
|
||||
|
||||
/*
|
||||
* Close intercept socket and free closure when we are done with
|
||||
* the connection.
|
||||
* Reset intercept_closure so it can be re-used.
|
||||
*/
|
||||
static void
|
||||
intercept_connection_close(struct intercept_closure *closure)
|
||||
intercept_closure_reset(struct intercept_closure *closure)
|
||||
{
|
||||
const int fd = sudo_ev_get_fd(&closure->ev);
|
||||
size_t n;
|
||||
debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC);
|
||||
debug_decl(intercept_closure_reset, SUDO_DEBUG_EXEC);
|
||||
|
||||
sudo_ev_del(NULL, &closure->ev);
|
||||
close(fd);
|
||||
if (closure->listen_sock != -1)
|
||||
if (closure->listen_sock != -1) {
|
||||
close(closure->listen_sock);
|
||||
|
||||
closure->listen_sock = -1;
|
||||
}
|
||||
free(closure->buf);
|
||||
free(closure->command);
|
||||
if (closure->run_argv != NULL) {
|
||||
@@ -151,6 +154,31 @@ intercept_connection_close(struct intercept_closure *closure)
|
||||
free(closure->run_envp[n]);
|
||||
free(closure->run_envp);
|
||||
}
|
||||
closure->errstr = NULL;
|
||||
closure->command = NULL;
|
||||
closure->run_argv = NULL;
|
||||
closure->run_envp = NULL;
|
||||
closure->buf = NULL;
|
||||
closure->len = 0;
|
||||
closure->off = 0;
|
||||
/* Does not currently reset token. */
|
||||
|
||||
debug_return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Close intercept socket and free closure when we are done with
|
||||
* the connection.
|
||||
*/
|
||||
static void
|
||||
intercept_connection_close(struct intercept_closure *closure)
|
||||
{
|
||||
const int fd = sudo_ev_get_fd(&closure->ev);
|
||||
debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC);
|
||||
|
||||
sudo_ev_del(NULL, &closure->ev);
|
||||
close(fd);
|
||||
intercept_closure_reset(closure);
|
||||
free(closure);
|
||||
|
||||
debug_return;
|
||||
@@ -305,7 +333,7 @@ intercept_check_policy(PolicyCheckRequest *req,
|
||||
char **user_env_out = NULL;
|
||||
char **argv = NULL, **run_argv = NULL;
|
||||
bool ret = false;
|
||||
int result;
|
||||
int rc;
|
||||
size_t n;
|
||||
debug_decl(intercept_check_policy, SUDO_DEBUG_EXEC);
|
||||
|
||||
@@ -338,13 +366,13 @@ intercept_check_policy(PolicyCheckRequest *req,
|
||||
if (ISSET(closure->details->flags, CD_INTERCEPT)) {
|
||||
/* We don't currently have a good way to validate the environment. */
|
||||
sudo_debug_set_active_instance(policy_plugin.debug_instance);
|
||||
result = policy_plugin.u.policy->check_policy(n, argv, NULL,
|
||||
rc = policy_plugin.u.policy->check_policy(n, argv, NULL,
|
||||
&command_info, &run_argv, &user_env_out, &closure->errstr);
|
||||
sudo_debug_set_active_instance(sudo_debug_instance);
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
||||
"check_policy returns %d", result);
|
||||
"check_policy returns %d", rc);
|
||||
|
||||
switch (result) {
|
||||
switch (rc) {
|
||||
case 1:
|
||||
/* Rebuild command_info[] with runcwd and extract command. */
|
||||
command_info_copy = update_command_info(command_info, NULL,
|
||||
@@ -475,7 +503,7 @@ static int
|
||||
intercept_verify_token(int fd, struct intercept_closure *closure)
|
||||
{
|
||||
ssize_t nread;
|
||||
debug_decl(intercept_read_token, SUDO_DEBUG_EXEC);
|
||||
debug_decl(intercept_verify_token, SUDO_DEBUG_EXEC);
|
||||
|
||||
nread = recv(fd, closure->token.u8 + closure->off,
|
||||
sizeof(closure->token) - closure->off, 0);
|
||||
@@ -516,8 +544,9 @@ intercept_read(int fd, struct intercept_closure *closure)
|
||||
{
|
||||
struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
|
||||
InterceptRequest *req = NULL;
|
||||
ssize_t nread;
|
||||
bool ret = false;
|
||||
ssize_t nread;
|
||||
int rc;
|
||||
debug_decl(intercept_read, SUDO_DEBUG_EXEC);
|
||||
|
||||
if (closure->state == RECV_SECRET) {
|
||||
@@ -641,12 +670,9 @@ unpack:
|
||||
}
|
||||
|
||||
/* Switch event to write mode for the reply. */
|
||||
if (sudo_ev_set(&closure->ev, fd, SUDO_EV_WRITE|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
|
||||
/* This cannot (currently) fail. */
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto done;
|
||||
}
|
||||
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
rc = sudo_ev_set(&closure->ev, fd, SUDO_EV_WRITE|SUDO_EV_PERSIST,
|
||||
intercept_cb, closure);
|
||||
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto done;
|
||||
}
|
||||
@@ -770,8 +796,9 @@ static bool
|
||||
intercept_write(int fd, struct intercept_closure *closure)
|
||||
{
|
||||
struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
|
||||
ssize_t nwritten;
|
||||
bool ret = false;
|
||||
ssize_t nwritten;
|
||||
int rc;
|
||||
debug_decl(intercept_write, SUDO_DEBUG_EXEC);
|
||||
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "state %d",
|
||||
@@ -825,16 +852,17 @@ intercept_write(int fd, struct intercept_closure *closure)
|
||||
closure->len = 0;
|
||||
closure->off = 0;
|
||||
|
||||
if (ISSET(closure->details->flags, CD_USE_PTRACE)) {
|
||||
/* Ready for the next policy check from the tracer. */
|
||||
closure->state = RECV_POLICY_CHECK;
|
||||
} else {
|
||||
switch (closure->state) {
|
||||
case RECV_HELLO_INITIAL:
|
||||
/* Re-use event for the listener. */
|
||||
close(fd);
|
||||
if (sudo_ev_set(&closure->ev, closure->listen_sock, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_accept_cb, closure) == -1) {
|
||||
/* This cannot (currently) fail. */
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto done;
|
||||
}
|
||||
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
rc = sudo_ev_set(&closure->ev, closure->listen_sock,
|
||||
SUDO_EV_READ|SUDO_EV_PERSIST, intercept_accept_cb, closure);
|
||||
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto done;
|
||||
}
|
||||
@@ -844,12 +872,9 @@ intercept_write(int fd, struct intercept_closure *closure)
|
||||
break;
|
||||
case POLICY_ACCEPT:
|
||||
/* Re-use event to read InterceptHello from sudo_intercept.so ctor. */
|
||||
if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
|
||||
/* This cannot (currently) fail. */
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto done;
|
||||
}
|
||||
if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
rc = sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST,
|
||||
intercept_cb, closure);
|
||||
if (rc == -1 || sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
|
||||
sudo_warn("%s", U_("unable to add event to queue"));
|
||||
goto done;
|
||||
}
|
||||
@@ -859,6 +884,7 @@ intercept_write(int fd, struct intercept_closure *closure)
|
||||
/* Done with this connection. */
|
||||
intercept_connection_close(closure);
|
||||
}
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
|
@@ -371,10 +371,13 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
|
||||
/*
|
||||
* Allocate a socketpair for communicating with sudo_intercept.so.
|
||||
* This must be inherited across exec, hence no FD_CLOEXEC.
|
||||
* Check if the kernel supports the seccomp(2) filter "trap" action.
|
||||
*/
|
||||
if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) {
|
||||
if (socketpair(PF_UNIX, SOCK_STREAM, 0, intercept_sv) == -1)
|
||||
sudo_fatal("%s", U_("unable to create sockets"));
|
||||
if (have_seccomp_action("trap"))
|
||||
SET(details->flags, CD_USE_PTRACE);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -448,6 +451,11 @@ exec_nopty(struct command_details *details, struct command_status *cstat)
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (ISSET(details->flags, CD_USE_PTRACE)) {
|
||||
/* Seize control of the command using ptrace(2). */
|
||||
exec_ptrace_seize(ec.cmnd_pid);
|
||||
}
|
||||
|
||||
/* Restore signal mask now that signal handlers are setup. */
|
||||
sigprocmask(SIG_SETMASK, &oset, NULL);
|
||||
|
||||
@@ -487,10 +495,16 @@ static void
|
||||
handle_sigchld_nopty(struct exec_closure_nopty *ec)
|
||||
{
|
||||
pid_t pid;
|
||||
int status, wflags = WUNTRACED|WNOHANG;
|
||||
int status, wflags;
|
||||
char signame[SIG2STR_MAX];
|
||||
debug_decl(handle_sigchld_nopty, SUDO_DEBUG_EXEC);
|
||||
|
||||
#ifdef __WALL
|
||||
wflags = __WALL|WNOHANG;
|
||||
#else
|
||||
wflags = WUNTRACED|WNOHANG;
|
||||
#endif
|
||||
|
||||
/* There may be multiple children in intercept mode. */
|
||||
for (;;) {
|
||||
/* Read command status. */
|
||||
@@ -519,6 +533,12 @@ handle_sigchld_nopty(struct exec_closure_nopty *ec)
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO,
|
||||
"%s: command (%d) stopped, SIG%s", __func__, (int)pid, signame);
|
||||
|
||||
if (ISSET(ec->details->flags, CD_USE_PTRACE)) {
|
||||
/* Did exec_ptrace_handled() suppress the signal? */
|
||||
if (exec_ptrace_handled(pid, status))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Only report status for the main command. */
|
||||
if (ec->cmnd_pid != pid)
|
||||
continue;
|
||||
|
258
src/exec_ptrace.c
Normal file
258
src/exec_ptrace.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is an open source non-commercial project. Dear PVS-Studio, please check it.
|
||||
* PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sudo.h"
|
||||
#include "sudo_exec.h"
|
||||
|
||||
#ifdef HAVE_PTRACE_INTERCEPT
|
||||
# include <sys/prctl.h>
|
||||
# include <sys/ptrace.h>
|
||||
# include <sys/user.h>
|
||||
# include <asm/unistd.h>
|
||||
# include <linux/ptrace.h>
|
||||
# include <linux/seccomp.h>
|
||||
# include <linux/filter.h>
|
||||
|
||||
/*
|
||||
* Check whether seccomp(2) filtering supports ptrace(2) traps.
|
||||
* Only supported by Linux 4.14 and higher.
|
||||
*/
|
||||
bool
|
||||
have_seccomp_action(const char *action)
|
||||
{
|
||||
char line[LINE_MAX];
|
||||
bool ret = false;
|
||||
FILE *fp;
|
||||
debug_decl(have_seccomp_action, SUDO_DEBUG_EXEC);
|
||||
|
||||
fp = fopen("/proc/sys/kernel/seccomp/actions_avail", "r");
|
||||
if (fp != NULL) {
|
||||
if (fgets(line, sizeof(line), fp) != NULL) {
|
||||
char *cp, *last;
|
||||
|
||||
for ((cp = strtok_r(line, " \t\n", &last)); cp != NULL;
|
||||
(cp = strtok_r(NULL, " \t\n", &last))) {
|
||||
if (strcmp(cp, action) == 0) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
debug_return_bool(ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* Intercept execve(2) using seccomp(2) and ptrace(2).
|
||||
* If no tracer is present, execve(2) will fail with ENOSYS.
|
||||
* Must be called with CAP_SYS_ADMIN, before privs are dropped.
|
||||
*/
|
||||
bool
|
||||
set_exec_filter(void)
|
||||
{
|
||||
struct sock_filter exec_filter[] = {
|
||||
/* Load syscall number into the accumulator */
|
||||
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
|
||||
/* Jump to trace for execve(2), else allow. */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 0, 1),
|
||||
/* Trace execve(2) syscall */
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
|
||||
/* Allow non-matching syscalls */
|
||||
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
|
||||
};
|
||||
const struct sock_fprog exec_fprog = {
|
||||
nitems(exec_filter),
|
||||
exec_filter
|
||||
};
|
||||
debug_decl(set_exec_filter, SUDO_DEBUG_UTIL);
|
||||
|
||||
/* We must set SECCOMP_MODE_FILTER before dropping privileges. */
|
||||
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &exec_fprog) == -1) {
|
||||
sudo_warn("%s", U_("unable to set seccomp filter"));
|
||||
debug_return_bool(false);
|
||||
}
|
||||
debug_return_bool(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Seize control of the specified child process which must be in
|
||||
* ptrace wait. Returns true on success and false on failure.
|
||||
*/
|
||||
bool
|
||||
exec_ptrace_seize(pid_t child)
|
||||
{
|
||||
const long ptrace_opts = PTRACE_O_TRACESECCOMP|PTRACE_O_TRACECLONE|
|
||||
PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK;
|
||||
int status;
|
||||
pid_t pid;
|
||||
debug_decl(exec_ptrace_seize, SUDO_DEBUG_UTIL);
|
||||
|
||||
/* Seize control of the child process. */
|
||||
if (ptrace(PTRACE_SEIZE, child, NULL, ptrace_opts) == -1) {
|
||||
sudo_warn("ptrace(PTRACE_SEIZE, %d, NULL, 0x%lx)", (int)child,
|
||||
ptrace_opts);
|
||||
debug_return_bool(false);
|
||||
}
|
||||
|
||||
/* The child will stop itself immediately before execve(2). */
|
||||
do {
|
||||
pid = waitpid(child, &status, WUNTRACED);
|
||||
} while (pid == -1 && errno == EINTR);
|
||||
if (pid == -1) {
|
||||
sudo_warn(U_("%s: %s"), __func__, "waitpid");
|
||||
debug_return_bool(false);
|
||||
}
|
||||
if (!WIFSTOPPED(status)) {
|
||||
sudo_warnx(U_("process %d exited unexpectedly"), (int)child);
|
||||
debug_return_bool(false);
|
||||
}
|
||||
if (ptrace(PTRACE_CONT, child, NULL, NULL) == -1) {
|
||||
sudo_warn("ptrace(PTRACE_CONT, %d, NULL, NULL)", (int)child);
|
||||
debug_return_bool(false);
|
||||
}
|
||||
|
||||
debug_return_bool(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a process stopped due to ptrace.
|
||||
* Returns true if the signal was suppressed and false if it was delivered.
|
||||
*/
|
||||
bool
|
||||
exec_ptrace_handled(pid_t pid, int status)
|
||||
{
|
||||
const int stopsig = WSTOPSIG(status);
|
||||
const int sigtrap = status >> 8;
|
||||
long signo = 0;
|
||||
bool group_stop = false;
|
||||
debug_decl(exec_ptrace_handled, SUDO_DEBUG_EXEC);
|
||||
|
||||
if (sigtrap == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
|
||||
/* Trapped child exec. */
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d called exec",
|
||||
__func__, (int)pid);
|
||||
/*
|
||||
* XXX
|
||||
* Get the exec arguments and perform a policy check either over
|
||||
* the socketpair (pty case) or via a direct function call (no pty).
|
||||
*/
|
||||
} else if (sigtrap == (SIGTRAP | (PTRACE_EVENT_CLONE << 8)) ||
|
||||
sigtrap == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
|
||||
sigtrap == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
|
||||
unsigned long new_pid;
|
||||
|
||||
/* New child process, it will inherit the parent's trace flags. */
|
||||
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
|
||||
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &new_pid) != -1) {
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO,
|
||||
"%s: %d forked new child %lu", __func__, (int)pid, new_pid);
|
||||
} else {
|
||||
sudo_debug_printf(
|
||||
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
|
||||
"ptrace(PTRACE_GETEVENTMSG, %d, NULL, %p)", (int)pid,
|
||||
&new_pid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (stopsig) {
|
||||
case SIGSTOP:
|
||||
case SIGTSTP:
|
||||
case SIGTTIN:
|
||||
case SIGTTOU:
|
||||
/* Is this a group-stop? */
|
||||
if (status >> 16 == PTRACE_EVENT_STOP) {
|
||||
/* Group-stop, do not deliver signal. */
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO,
|
||||
"%s: %d: group-stop signal %d",
|
||||
__func__, (int)pid, stopsig);
|
||||
group_stop = true;
|
||||
} else {
|
||||
/* Signal-delivery-stop, deliver signal. */
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO,
|
||||
"%s: %d: signal-delivery-stop signal %d",
|
||||
__func__, (int)pid, stopsig);
|
||||
signo = stopsig;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Not a stop signal so not a group-stop. */
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO,
|
||||
"%s: %d: signal %d", __func__, (int)pid, stopsig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Continue child. */
|
||||
/* XXX - handle ptrace returning ESRCH if process dies */
|
||||
if (group_stop) {
|
||||
/*
|
||||
* Restart child but prevent it from executing
|
||||
* until SIGCONT is received (simulate SIGSTOP, etc).
|
||||
*/
|
||||
if (ptrace(PTRACE_LISTEN, pid, NULL, 0L) == -1)
|
||||
sudo_warn("ptrace(PTRACE_LISTEN,, %d, NULL, %d", pid, stopsig);
|
||||
} else {
|
||||
/* Restart child. */
|
||||
if (ptrace(PTRACE_CONT, pid, NULL, signo) == -1)
|
||||
sudo_warn("ptrace(PTRACE_CONT, %d, NULL, %d", pid, stopsig);
|
||||
}
|
||||
|
||||
debug_return_bool(signo == 0);
|
||||
}
|
||||
#else
|
||||
/* STUB */
|
||||
void
|
||||
exec_ptrace_enable(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* STUB */
|
||||
bool
|
||||
have_seccomp_action(const char *action)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* STUB */
|
||||
bool
|
||||
exec_ptrace_handled(pid_t pid, int status)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* STUB */
|
||||
bool
|
||||
exec_ptrace_seize(pid_t child)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif /* HAVE_PTRACE_INTERCEPT */
|
@@ -992,6 +992,10 @@ backchannel_cb(int fd, int what, void *v)
|
||||
ec->cmnd_pid = cstat.val;
|
||||
sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d",
|
||||
ec->details->command, (int)ec->cmnd_pid);
|
||||
if (ISSET(ec->details->flags, CD_USE_PTRACE)) {
|
||||
/* Seize control of the command using ptrace(2). */
|
||||
exec_ptrace_seize(ec->cmnd_pid);
|
||||
}
|
||||
break;
|
||||
case CMD_WSTATUS:
|
||||
if (WIFSTOPPED(cstat.val)) {
|
||||
@@ -1041,10 +1045,16 @@ backchannel_cb(int fd, int what, void *v)
|
||||
static void
|
||||
handle_sigchld_pty(struct exec_closure_pty *ec)
|
||||
{
|
||||
int n, status, wflags = WUNTRACED|WNOHANG;
|
||||
int n, status, wflags;
|
||||
pid_t pid;
|
||||
debug_decl(handle_sigchld_pty, SUDO_DEBUG_EXEC);
|
||||
|
||||
#ifdef __WALL
|
||||
wflags = __WALL|WNOHANG;
|
||||
#else
|
||||
wflags = WUNTRACED|WNOHANG;
|
||||
#endif
|
||||
|
||||
/* There may be multiple children in intercept mode. */
|
||||
for (;;) {
|
||||
do {
|
||||
@@ -1077,8 +1087,11 @@ handle_sigchld_pty(struct exec_closure_pty *ec)
|
||||
if (pid == ec->monitor_pid)
|
||||
ec->monitor_pid = -1;
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
if (pid != ec->monitor_pid)
|
||||
if (pid != ec->monitor_pid) {
|
||||
if (ISSET(ec->details->flags, CD_USE_PTRACE))
|
||||
exec_ptrace_handled(pid, status);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the monitor dies we get notified via backchannel_cb().
|
||||
@@ -1405,10 +1418,13 @@ exec_pty(struct command_details *details, struct command_status *cstat)
|
||||
/*
|
||||
* Allocate a socketpair for communicating with sudo_intercept.so.
|
||||
* This must be inherited across exec, hence no FD_CLOEXEC.
|
||||
* Check if the kernel supports the seccomp(2) filter "trap" action.
|
||||
*/
|
||||
if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) {
|
||||
if (socketpair(PF_UNIX, SOCK_STREAM, 0, intercept_sv) == -1)
|
||||
sudo_fatal("%s", U_("unable to create sockets"));
|
||||
if (have_seccomp_action("trap"))
|
||||
SET(details->flags, CD_USE_PTRACE);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 1993-1996, 1998-2005, 2007-2021
|
||||
* Copyright (c) 1993-1996, 1998-2005, 2007-2022
|
||||
* Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
@@ -140,6 +140,7 @@ struct user_details {
|
||||
#define CD_LOGIN_SHELL 0x080000
|
||||
#define CD_OVERRIDE_UMASK 0x100000
|
||||
#define CD_LOG_SUBCMDS 0x200000
|
||||
#define CD_USE_PTRACE 0x400000
|
||||
|
||||
struct preserved_fd {
|
||||
TAILQ_ENTRY(preserved_fd) entries;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* Copyright (c) 2010-2017, 2020-2021 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
* Copyright (c) 2010-2017, 2020-2022 Todd C. Miller <Todd.Miller@sudo.ws>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -90,6 +90,18 @@ union sudo_token_un {
|
||||
|
||||
#define sudo_token_isset(_t) ((_t).u64[0] || (_t).u64[1])
|
||||
|
||||
/*
|
||||
* Use ptrace-based intercept (using seccomp) on Linux if possible.
|
||||
* TODO: test other architectures
|
||||
*/
|
||||
#if defined(_PATH_SUDO_INTERCEPT) && defined(__linux__)
|
||||
# if defined(HAVE_DECL_SECCOMP_SET_MODE_FILTER) && HAVE_DECL_SECCOMP_SET_MODE_FILTER
|
||||
# if defined(__amd64__) || defined(__i386__) || defined(__aarch64__)
|
||||
# define HAVE_PTRACE_INTERCEPT 1
|
||||
# endif /* __amd64__ || __i386__ || __aarch64__ */
|
||||
# endif /* HAVE_DECL_SECCOMP_SET_MODE_FILTER */
|
||||
#endif /* _PATH_SUDO_INTERCEPT && __linux__ */
|
||||
|
||||
/*
|
||||
* Symbols shared between exec.c, exec_nopty.c, exec_pty.c and exec_monitor.c
|
||||
*/
|
||||
@@ -132,4 +144,10 @@ bool utmp_logout(const char *line, int status);
|
||||
/* exec_preload.c */
|
||||
char **sudo_preload_dso(char *envp[], const char *dso_file, int intercept_fd);
|
||||
|
||||
/* exec_ptrace.c */
|
||||
bool exec_ptrace_handled(pid_t pid, int status);
|
||||
bool exec_ptrace_seize(pid_t child);
|
||||
bool have_seccomp_action(const char *action);
|
||||
bool set_exec_filter(void);
|
||||
|
||||
#endif /* SUDO_EXEC_H */
|
||||
|
Reference in New Issue
Block a user