Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Add --exec / -e to su for shell-bypass #254

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
161 changes: 113 additions & 48 deletions src/su.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@

#ident "$Id$"

#include <getopt.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
Expand Down Expand Up @@ -91,10 +90,12 @@ static bool caller_on_console = false;
static /*@only@*/char *caller_pass;
#endif
#endif /* !USE_PAM */
static bool doshell = false;
static bool do_interactive_shell = false;
static bool fakelogin = false;
static /*@observer@*/const char *shellstr;
static /*@null@*/char *command = NULL;
static /*@null@*/char *exec_command = NULL;
static int optidx;


/* not needed by sulog.c anymore */
Expand Down Expand Up @@ -327,11 +328,11 @@ static void prepare_pam_close_session (void)
if ( (sigaddset (&ourset, SIGTERM) != 0)
|| (sigaddset (&ourset, SIGALRM) != 0)
|| (sigaction (SIGTERM, &action, NULL) != 0)
|| ( !doshell /* handle SIGINT (Ctrl-C), SIGQUIT
* (Ctrl-\), and SIGTSTP (Ctrl-Z)
* since the child will not control
* the tty.
*/
|| (!do_interactive_shell /* handle SIGINT (Ctrl-C), SIGQUIT
* (Ctrl-\), and SIGTSTP (Ctrl-Z)
* since the child will not control
* the tty.
*/
&& ( (sigaddset (&ourset, SIGINT) != 0)
|| (sigaddset (&ourset, SIGQUIT) != 0)
|| (sigaddset (&ourset, SIGTSTP) != 0)
Expand Down Expand Up @@ -440,12 +441,14 @@ static void usage (int status)
"\n"
"Options:\n"
" -c, --command COMMAND pass COMMAND to the invoked shell\n"
" -e, --exec PATH run PATH without shell, follow -- with args\n"
" -h, --help display this help message and exit\n"
" -, -l, --login make the shell a login shell\n"
" -m, -p,\n"
" --preserve-environment do not reset environment variables, and\n"
" keep the same shell\n"
" -s, --shell SHELL use SHELL instead of the default in passwd\n"
" -- pass all subsequent arguments on as-is\n"
"\n"
"If no username is given, assume root.\n"), (E_SUCCESS != status) ? stderr : stdout);
exit (status);
Expand Down Expand Up @@ -760,6 +763,48 @@ static void save_caller_context (char **argv)
pw_free (pw);
}

/*
* flags_match - test arg against flag candidates
*/
static bool flags_match(const char *arg, const char *a, const char *b, const char *c)
{
return (a != NULL && strcmp (arg, a) == 0) ||
(b != NULL && strcmp (arg, b) == 0) ||
(c != NULL && strcmp (arg, c) == 0);
}

/* is_flag_like - test if arg resembles a flag
*
* lone "--" and bare leading-hyphen-less words are not flag-like,
* everything else is considered a probable flag.
*/
static bool is_flag_like(const char *arg)
{
if (arg[0] != '-')
return false;

if (strcmp (arg, "--") == 0)
return false;

return true;
}

static void flag_arg_required(const char *arg)
{
fprintf (stderr,
_("%s: option \'%s\' requires an argument\n"),
Prog, arg);
usage (E_USAGE);
}

static void flag_unknown(const char *arg)
{
fprintf (stderr,
_("%s: unrecognized option \'%s\'\n"),
Prog, arg);
usage (E_BAD_ARG);
}

/*
* process_flags - Process the command line arguments
*
Expand All @@ -769,51 +814,58 @@ static void save_caller_context (char **argv)
*/
static void process_flags (int argc, char **argv)
{
int c;
static struct option long_options[] = {
{"command", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{"login", no_argument, NULL, 'l'},
{"preserve-environment", no_argument, NULL, 'p'},
{"shell", required_argument, NULL, 's'},
{NULL, 0, NULL, '\0'}
};

while ((c = getopt_long (argc, argv, "c:hlmps:",
long_options, NULL)) != -1) {
switch (c) {
case 'c':
command = optarg;
break;
case 'h':
for (optidx = 1; optidx < argc; optidx++) {
const char *arg = argv[optidx];

if (flags_match (arg, "--command", "-c", NULL)) {
if (optidx == argc - 1) {
flag_arg_required (arg);
}

command = argv[++optidx];
} else if (flags_match (arg, "--exec", "-e", NULL)) {
if (optidx == argc - 1) {
flag_arg_required (arg);
}

exec_command = argv[++optidx];
} else if (flags_match (arg, "--help", "-h", NULL)) {
usage (E_SUCCESS);
break;
case 'l':
} else if (flags_match (arg, "--login", "-l", "-")) {
fakelogin = true;
break;
case 'm':
case 'p':
} else if (flags_match (arg, "--preserve-environment", "-p", "-m")) {
/* This will only have an effect if the target
* user do not have a restricted shell, or if
* su is called by root.
*/
change_environment = false;
} else if (flags_match (arg, "--shell", "-s", NULL)) {
if (optidx == argc - 1) {
flag_arg_required (arg);
}

shellstr = argv[++optidx];
} else if (is_flag_like (arg)) {
flag_unknown (arg);
} else {
break;
case 's':
shellstr = optarg;
break;
default:
usage (E_USAGE); /* NOT REACHED */
}
}

if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) {
fakelogin = true;
optind++;
if (NULL != exec_command && NULL != command) {
fprintf (stderr,
_("%s: COMMAND and PATH are mutually exclusive\n"),
argv[0]);
usage (E_USAGE);
}

if (NULL != exec_command) {
command = exec_command;
}

if (optind < argc) {
STRFCPY (name, argv[optind++]); /* use this login id */
/* if next arg is not "--", treat as USER */
if (optidx < argc && strcmp (argv[optidx], "--")) {
STRFCPY (name, argv[optidx++]); /* use this login id */
}
if ('\0' == name[0]) { /* use default user */
struct passwd *root_pw = getpwnam ("root");
Expand All @@ -829,9 +881,14 @@ static void process_flags (int argc, char **argv)
}
}

doshell = (argc == optind); /* any arguments remaining? */
/* if more and next arg is "--", skip it and leave rest as-is */
if (optidx < argc && strcmp (argv[optidx], "--") == 0) {
optidx++;
}

do_interactive_shell = (argc == optidx); /* any arguments remaining? */
if (NULL != command) {
doshell = false;
do_interactive_shell = false;
}
}

Expand Down Expand Up @@ -1107,7 +1164,7 @@ int main (int argc, char **argv)

set_environment (pw);

if (!doshell) {
if (!do_interactive_shell) {
/* There is no need for a controlling terminal.
* This avoids the callee to inject commands on
* the caller's tty. */
Expand Down Expand Up @@ -1175,10 +1232,10 @@ int main (int argc, char **argv)
cp = Basename (shellstr);
}

if (!doshell) {
if (!do_interactive_shell) {
int err;
/* Position argv to the remaining arguments */
argv += optind;
argv += optidx;
if (NULL != command) {
argv -= 2;
argv[0] = "-c";
Expand All @@ -1189,10 +1246,18 @@ int main (int argc, char **argv)
* with the rest of the command line included.
*/
argv[-1] = cp;
execve_shell (shellstr, &argv[-1], environ);
err = errno;
(void) fprintf (stderr,
_("Cannot execute %s\n"), shellstr);

if (NULL != exec_command) {
(void) execve (command, &argv[1], environ);
err = errno;
(void) fprintf (stderr,
_("Cannot execute \'%s\'\n"), command);
} else {
execve_shell (shellstr, &argv[-1], environ);
err = errno;
(void) fprintf (stderr,
_("Cannot execute \'%s\'\n"), shellstr);
}
errno = err;
} else {
(void) shell (shellstr, cp, environ);
Expand Down