diff --git a/docs/sudoers.man.in b/docs/sudoers.man.in index d1b5cb576..636cd9303 100644 --- a/docs/sudoers.man.in +++ b/docs/sudoers.man.in @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDOERS" "@mansectform@" "May 4, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual" +.TH "SUDOERS" "@mansectform@" "May 24, 2022" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .nh .if n .ad l .SH "NAME" @@ -3307,18 +3307,20 @@ To prevent this from happening, will not permit a set-user-ID or set-group-ID program to be run in intercept mode unless \fIintercept_allow_setid\fR -is set. +is enable. This flag has no effect unless the \fIintercept\fR flag is enabled or the \fIINTERCEPT\fR tag has been set for the command. This flag is -\fIoff\fR -by default except on Linux systems that support -seccomp(2) -filtering, where it defaults to -\fIon\fR. +\fIon\fR +by default when the +\fIintercept_type\fR +option is set to +\fItrace\fR, +otherwise it default to +\fIoff\fR. .sp This setting is only supported by version 1.9.8 or higher. .TP 18n @@ -4215,6 +4217,63 @@ option is disabled. The default is \fI@editor@\fR. .TP 18n +intercept_type +The underlying mechanism used by the +\fIintercept\fR +and +\fIlog_subcmds\fR +options. +It has the following possible values: +.PP +.RS 18n +.PD 0 +.TP 8n +dso +Preload a dynamic shared object (shared library) that intercepts the +\fBexecl\fR(), +\fBexecle\fR(), +\fBexeclp\fR(), +\fBexecv\fR(), +\fBexecve\fR(), +\fBexecvp\fR(), +and +\fBexecvpe\fR() +library functions. +A value of +\fIdso\fR +is incompatible with +\fBsudo\fR's +SELinux RBAC support. +.PD +.TP 8n +trace +Use +ptrace(2) +to intercept the +execve(2) +system call. +This is only supported on Linux systems where +seccomp(2) +filtering is enabled. +If the +\fI/proc/sys/kernel/seccomp/actions_avail\fR +file is missing or does not contain a +\(lqtrap\(rq +element, setting +\fIintercept_type\fR +to +\fItrace\fR +will have no effect and +\fIdso\fR +will be used instead. +.PP +The default is to use +\fItrace\fR +if it is supported by the system and +\fIdso\fR +if it is not. +.RE +.TP 18n iolog_dir The top-level directory to use when constructing the path name for the input/output log directory. @@ -6777,7 +6836,7 @@ by default and interferes with file descriptor inheritance. .sp Linux systems that support seccomp(2) -filtering will use a different method involving +filtering can use a different method involving ptrace(2) instead of pre-loading a shared library. This method supports both static and dynamic executables as well as diff --git a/docs/sudoers.mdoc.in b/docs/sudoers.mdoc.in index 04661891c..5e2fe5717 100644 --- a/docs/sudoers.mdoc.in +++ b/docs/sudoers.mdoc.in @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd May 4, 2022 +.Dd May 24, 2022 .Dt SUDOERS @mansectform@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -3123,18 +3123,20 @@ To prevent this from happening, will not permit a set-user-ID or set-group-ID program to be run in intercept mode unless .Em intercept_allow_setid -is set. +is enable. This flag has no effect unless the .Em intercept flag is enabled or the .Em INTERCEPT tag has been set for the command. This flag is -.Em off -by default except on Linux systems that support -.Xr seccomp 2 -filtering, where it defaults to -.Em on . +.Em on +by default when the +.Em intercept_type +option is set to +.Em trace , +otherwise it default to +.Em off . .Pp This setting is only supported by version 1.9.8 or higher. .It intercept_authenticate @@ -3982,6 +3984,57 @@ list or the option is disabled. The default is .Pa @editor@ . +.It intercept_type +The underlying mechanism used by the +.Em intercept +and +.Em log_subcmds +options. +It has the following possible values: +.Bl -tag -width 6n +.It dso +Preload a dynamic shared object (shared library) that intercepts the +.Fn execl , +.Fn execle , +.Fn execlp , +.Fn execv , +.Fn execve , +.Fn execvp , +and +.Fn execvpe +library functions. +A value of +.Em dso +is incompatible with +.Nm sudo Ns 's +SELinux RBAC support. +.It trace +Use +.Xr ptrace 2 +to intercept the +.Xr execve 2 +system call. +This is only supported on Linux systems where +.Xr seccomp 2 +filtering is enabled. +If the +.Pa /proc/sys/kernel/seccomp/actions_avail +file is missing or does not contain a +.Dq trap +element, setting +.Em intercept_type +to +.Em trace +will have no effect and +.Em dso +will be used instead. +.El +.Pp +The default is to use +.Em trace +if it is supported by the system and +.Em dso +if it is not. .It iolog_dir The top-level directory to use when constructing the path name for the input/output log directory. @@ -6266,7 +6319,7 @@ by default and interferes with file descriptor inheritance. .Pp Linux systems that support .Xr seccomp 2 -filtering will use a different method involving +filtering can use a different method involving .Xr ptrace 2 instead of pre-loading a shared library. This method supports both static and dynamic executables as well as diff --git a/plugins/sudoers/def_data.c b/plugins/sudoers/def_data.c index e5a80e2b8..3925d8c93 100644 --- a/plugins/sudoers/def_data.c +++ b/plugins/sudoers/def_data.c @@ -44,6 +44,12 @@ static struct def_values def_data_log_format[] = { { NULL, 0 }, }; +static struct def_values def_data_intercept_type[] = { + { "dso", dso }, + { "trace", trace }, + { NULL, 0 }, +}; + struct sudo_defs_types sudo_defs_table[] = { { "syslog", T_LOGFAC|T_BOOL, @@ -657,6 +663,10 @@ struct sudo_defs_types sudo_defs_table[] = { "passprompt_regex", T_LIST|T_SPACE|T_BOOL, N_("List of regular expressions to use when matching a password prompt"), NULL, + }, { + "intercept_type", T_TUPLE, + N_("The mechanism used by the intercept and log_subcmds options: %s"), + def_data_intercept_type, }, { NULL, 0, NULL } diff --git a/plugins/sudoers/def_data.h b/plugins/sudoers/def_data.h index 4795177bb..7903e2418 100644 --- a/plugins/sudoers/def_data.h +++ b/plugins/sudoers/def_data.h @@ -306,6 +306,8 @@ #define def_log_passwords (sudo_defs_table[I_LOG_PASSWORDS].sd_un.flag) #define I_PASSPROMPT_REGEX 152 #define def_passprompt_regex (sudo_defs_table[I_PASSPROMPT_REGEX].sd_un.list) +#define I_INTERCEPT_TYPE 153 +#define def_intercept_type (sudo_defs_table[I_INTERCEPT_TYPE].sd_un.tuple) enum def_tuple { never, @@ -319,5 +321,7 @@ enum def_tuple { tty, kernel, sudo, - json + json, + dso, + trace }; diff --git a/plugins/sudoers/def_data.in b/plugins/sudoers/def_data.in index d0cc1780d..f15b71544 100644 --- a/plugins/sudoers/def_data.in +++ b/plugins/sudoers/def_data.in @@ -475,3 +475,7 @@ log_passwords passprompt_regex T_LIST|T_SPACE|T_BOOL "List of regular expressions to use when matching a password prompt" +intercept_type + T_TUPLE + "The mechanism used by the intercept and log_subcmds options: %s" + dso trace diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c index 9521b2ec0..d5bd8080d 100644 --- a/plugins/sudoers/defaults.c +++ b/plugins/sudoers/defaults.c @@ -548,8 +548,7 @@ init_defaults(void) #endif if ((def_rlimit_core = strdup("0,0")) == NULL) goto oom; - if (ISSET(sudo_user.flags, CAN_INTERCEPT_SETID)) - def_intercept_allow_setid = true; + def_intercept_type = dso; def_netgroup_tuple = false; def_sudoedit_checkdir = true; def_iolog_mode = S_IRUSR|S_IWUSR; diff --git a/plugins/sudoers/policy.c b/plugins/sudoers/policy.c index fab06017a..2401e3de7 100644 --- a/plugins/sudoers/policy.c +++ b/plugins/sudoers/policy.c @@ -297,10 +297,24 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults) goto oom; continue; } + if (MATCHES(*cur, "intercept_ptrace=")) { + int val = sudo_strtobool(*cur + sizeof("intercept_ptrace=") - 1); + if (val == -1) { + INVALID("intercept_ptrace="); /* Not a fatal error. */ + } else if (!append_default("intercept_type", + val ? "trace" : "dso", true, NULL, defaults)) { + goto oom; + } + continue; + } if (MATCHES(*cur, "intercept_setid=")) { - if (parse_bool(*cur, sizeof("intercept_setid") - 1, - &sudo_user.flags, CAN_INTERCEPT_SETID) == -1) - goto bad; + int val = sudo_strtobool(*cur + sizeof("intercept_setid=") - 1); + if (val == -1) { + INVALID("intercept_setid="); /* Not a fatal error. */ + } else if (!append_default("intercept_allow_setid", NULL, val, + NULL, defaults)) { + goto oom; + } continue; } #ifdef HAVE_SELINUX @@ -605,7 +619,7 @@ sudoers_policy_store_result(bool accepted, char *argv[], char *envp[], } /* Increase the length of command_info as needed, it is *not* checked. */ - command_info = calloc(70, sizeof(char *)); + command_info = calloc(71, sizeof(char *)); if (command_info == NULL) goto oom; @@ -777,6 +791,10 @@ sudoers_policy_store_result(bool accepted, char *argv[], char *envp[], if ((command_info[info_len++] = strdup("intercept=true")) == NULL) goto oom; } + if (def_intercept_type == trace) { + if ((command_info[info_len++] = strdup("use_ptrace=true")) == NULL) + goto oom; + } if (def_noexec) { if ((command_info[info_len++] = strdup("noexec=true")) == NULL) goto oom; diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index 6c09ccc82..22c509f87 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -145,7 +145,6 @@ struct sudo_user { */ #define RUNAS_USER_SPECIFIED 0x01 #define RUNAS_GROUP_SPECIFIED 0x02 -#define CAN_INTERCEPT_SETID 0x04 /* * Return values for sudoers_lookup(), also used as arguments for log_auth() diff --git a/src/exec_ptrace.c b/src/exec_ptrace.c index 6661e943f..d3d524dfb 100644 --- a/src/exec_ptrace.c +++ b/src/exec_ptrace.c @@ -51,6 +51,8 @@ # include "exec_intercept.h" # include "exec_ptrace.h" +static int seccomp_trap_supported = -1; + /* Register getters and setters. */ # ifdef SECCOMP_AUDIT_ARCH_COMPAT static inline unsigned long @@ -736,7 +738,7 @@ done: * Check whether seccomp(2) filtering supports ptrace(2) traps. * Only supported by Linux 4.14 and higher. */ -bool +static bool have_seccomp_action(const char *action) { char line[LINE_MAX]; @@ -1274,6 +1276,24 @@ exec_ptrace_stopped(pid_t pid, int status, void *intercept) debug_return_bool(group_stop); } + +bool +exec_ptrace_intercept_supported(void) +{ + if (seccomp_trap_supported == -1) + seccomp_trap_supporetd = have_seccomp_action("trap"); + + return seccomp_trap_supported == true; +} + +bool +exec_ptrace_subcmds_supported(void) +{ + if (seccomp_trap_supported == -1) + seccomp_trap_supported = have_seccomp_action("trap"); + + return seccomp_trap_supported == true; +} #else /* STUB */ bool @@ -1295,4 +1315,41 @@ exec_ptrace_seize(pid_t child) { return true; } + +/* STUB */ +bool +exec_ptrace_intercept_supported(void) +{ + return false; +} + +/* STUB */ +bool +exec_ptrace_subcmds_supported(void) +{ + return false; +} #endif /* HAVE_PTRACE_INTERCEPT */ + +/* + * Adjust flags based on the availability of ptrace support. + */ +void +exec_ptrace_fix_flags(struct command_details *details) +{ + debug_decl(exec_ptrace_fix_flags, SUDO_DEBUG_EXEC); + + if (ISSET(details->flags, CD_USE_PTRACE)) { + /* If both CD_INTERCEPT and CD_LOG_SUBCMDS set, CD_INTERCEPT wins. */ + if (ISSET(details->flags, CD_INTERCEPT)) { + if (!exec_ptrace_intercept_supported()) + CLR(details->flags, CD_USE_PTRACE); + } else if (ISSET(details->flags, CD_LOG_SUBCMDS)) { + if (!exec_ptrace_subcmds_supported()) + CLR(details->flags, CD_USE_PTRACE); + } else { + CLR(details->flags, CD_USE_PTRACE); + } + } + debug_return; +} diff --git a/src/parse_args.c b/src/parse_args.c index 9cc320bd7..b1dfba3b2 100644 --- a/src/parse_args.c +++ b/src/parse_args.c @@ -82,6 +82,7 @@ static struct sudo_settings sudo_settings[] = { { "cmnd_cwd" }, { "askpass" }, { "intercept_setid" }, + { "intercept_ptrace" }, { NULL } }; @@ -585,8 +586,10 @@ parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv, #ifdef ENABLE_SUDO_PLUGIN_API sudo_settings[ARG_PLUGIN_DIR].value = sudo_conf_plugin_dir_path(); #endif - if (have_seccomp_action("trap")) + if (exec_ptrace_intercept_supported()) sudo_settings[ARG_INTERCEPT_SETID].value = "true"; + if (exec_ptrace_subcmds_supported()) + sudo_settings[ARG_INTERCEPT_PTRACE].value = "true"; if (mode == MODE_HELP) help(); diff --git a/src/sudo.c b/src/sudo.c index c039cab49..96de4db64 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -650,10 +650,10 @@ bad: static void command_info_to_details(char * const info[], struct command_details *details) { - int i; - id_t id; - char *cp; const char *errstr; + char *cp; + id_t id; + int i; debug_decl(command_info_to_details, SUDO_DEBUG_PCOMM); memset(details, 0, sizeof(*details)); @@ -857,17 +857,15 @@ command_info_to_details(char * const info[], struct command_details *details) break; } SET_FLAG("umask_override=", CD_OVERRIDE_UMASK) + SET_FLAG("use_ptrace=", CD_USE_PTRACE) SET_FLAG("use_pty=", CD_USE_PTY) SET_STRING("utmp_user=", utmp_user) break; } } - if (ISSET(details->flags, CD_INTERCEPT|CD_LOG_SUBCMDS)) { - /* Use ptrace(2) for intercept/log_subcmds if possible. */ - if (sudo_settings[ARG_INTERCEPT_SETID].value != NULL) - SET(details->flags, CD_USE_PTRACE); - } + /* Only use ptrace(2) for intercept/log_subcmds if supported. */ + exec_ptrace_fix_flags(details); if (!ISSET(details->flags, CD_SET_EUID)) details->cred.euid = details->cred.uid; diff --git a/src/sudo.h b/src/sudo.h index 2e088de72..2cfce0aa4 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -103,6 +103,7 @@ #define ARG_CWD 24 #define ARG_ASKPASS 25 #define ARG_INTERCEPT_SETID 26 +#define ARG_INTERCEPT_PTRACE 27 /* * Flags for tgetpass() @@ -338,6 +339,8 @@ int serialize_rlimits(char **info, size_t info_max); bool parse_policy_rlimit(const char *str); /* exec_ptrace.c */ -bool have_seccomp_action(const char *action); +void exec_ptrace_fix_flags(struct command_details *details); +bool exec_ptrace_intercept_supported(void); +bool exec_ptrace_subcmds_supported(void); #endif /* SUDO_SUDO_H */ diff --git a/src/sudo_exec.h b/src/sudo_exec.h index c34172e59..77c2c9aac 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -92,7 +92,6 @@ union sudo_token_un { /* * 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 @@ -100,7 +99,7 @@ union sudo_token_un { # ifndef HAVE_PTRACE_INTERCEPT # define HAVE_PTRACE_INTERCEPT 1 # endif /* HAVE_PTRACE_INTERCEPT */ -# endif /* __amd64__ || __i386__ || __aarch64__ || __riscv */ +# endif /* __amd64__ || __i386__ || __aarch64__ || __riscv || __s390__ */ # endif /* HAVE_DECL_SECCOMP_SET_MODE_FILTER */ #endif /* _PATH_SUDO_INTERCEPT && __linux__ */