From 1676f0ceebbc251d8fd4fca0fb6f86e8fc3ea52e Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Tue, 1 Sep 2020 14:10:02 -0600 Subject: [PATCH] Support "*" for CWD/CHROOT to allow user to specify cwd or chroot. Adds two new command line options, -D (--chdir) and -R (--chroot) that can only be used when sudoers sets runcwd or runchroot to "*". --- doc/sudo.man.in | 24 ++++- doc/sudo.mdoc.in | 24 ++++- doc/sudo_plugin.man.in | 36 ++++++-- doc/sudo_plugin.mdoc.in | 33 ++++++- doc/sudoers.man.in | 34 ++++++- doc/sudoers.mdoc.in | 34 ++++++- plugins/sudoers/def_data.c | 4 +- plugins/sudoers/def_data.in | 4 +- plugins/sudoers/defaults.c | 61 +++++++++--- plugins/sudoers/defaults.h | 4 +- plugins/sudoers/gram.c | 178 +++++++++++++++++++----------------- plugins/sudoers/gram.y | 14 +++ plugins/sudoers/policy.c | 14 ++- plugins/sudoers/sudoers.c | 22 +++++ plugins/sudoers/sudoers.h | 4 + src/parse_args.c | 29 +++++- src/sudo_usage.h.in | 6 +- 17 files changed, 397 insertions(+), 128 deletions(-) diff --git a/doc/sudo.man.in b/doc/sudo.man.in index 4ab330065..46dcc679c 100644 --- a/doc/sudo.man.in +++ b/doc/sudo.man.in @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDO" "@mansectsu@" "August 11, 2020" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" +.TH "SUDO" "@mansectsu@" "September 1, 2020" "Sudo @PACKAGE_VERSION@" "System Manager's Manual" .nh .if n .ad l .SH "NAME" @@ -66,9 +66,11 @@ .if \n(BA [\fB\-a\fR\ \fItype\fR] [\fB\-C\fR\ \fInum\fR] .if \n(LC [\fB\-c\fR\ \fIclass\fR] +[\fB\-D\fR\ \fIdirectory\fR] [\fB\-g\fR\ \fIgroup\fR] [\fB\-h\fR\ \fIhost\fR] [\fB\-p\fR\ \fIprompt\fR] +[\fB\-R\fR\ \fIdirectory\fR] .if \n(SL [\fB\-r\fR\ \fIrole\fR] .if \n(SL [\fB\-t\fR\ \fItype\fR] [\fB\-T\fR\ \fItimeout\fR] @@ -83,9 +85,13 @@ .if \n(BA [\fB\-a\fR\ \fItype\fR] [\fB\-C\fR\ \fInum\fR] .if \n(LC [\fB\-c\fR\ \fIclass\fR] +[\fB\-D\fR\ \fIdirectory\fR] [\fB\-g\fR\ \fIgroup\fR] [\fB\-h\fR\ \fIhost\fR] [\fB\-p\fR\ \fIprompt\fR] +[\fB\-R\fR\ \fIdirectory\fR] +.if \n(SL [\fB\-r\fR\ \fIrole\fR] +.if \n(SL [\fB\-t\fR\ \fItype\fR] [\fB\-T\fR\ \fItimeout\fR] [\fB\-u\fR\ \fIuser\fR] \fIfile\ ...\fR @@ -288,6 +294,13 @@ BSD login classes. .\} .TP 12n +\fB\-D\fR \fIdirectory\fR, \fB\--chdir\fR=\fIdirectory\fR +Run the command in the specified +\fIdirectory\fR +instead of the current working directory. +The security policy may return an error if the user does not have +permission to specify the working directory. +.TP 12n \fB\-E\fR, \fB\--preserve-env\fR Indicates to the security policy that the user wishes to preserve their existing environment variables. @@ -584,6 +597,15 @@ specified by a PAM module unless the flag is disabled in \fIsudoers\fR. .RE +.TP 12n +\fB\-R\fR \fIdirectory\fR, \fB\--chroot\fR=\fIdirectory\fR +Change to the specified root +\fIdirectory\fR +(see +chroot(@mansectsu@)) +before running the command. +The security policy may return an error if the user does not have +permission to specify the root directory. .if \n(SL \{\ .TP 12n \fB\-r\fR \fIrole\fR, \fB\--role\fR=\fIrole\fR diff --git a/doc/sudo.mdoc.in b/doc/sudo.mdoc.in index d597dd519..fd2d20518 100644 --- a/doc/sudo.mdoc.in +++ b/doc/sudo.mdoc.in @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd August 11, 2020 +.Dd September 1, 2020 .Dt SUDO @mansectsu@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -65,9 +65,11 @@ .if \n(LC \{\ .Op Fl c Ar class .\} +.Op Fl D Ar directory .Op Fl g Ar group .Op Fl h Ar host .Op Fl p Ar prompt +.Op Fl R Ar directory .if \n(SL \{\ .Op Fl r Ar role .Op Fl t Ar type @@ -86,9 +88,15 @@ .if \n(LC \{\ .Op Fl c Ar class .\} +.Op Fl D Ar directory .Op Fl g Ar group .Op Fl h Ar host .Op Fl p Ar prompt +.Op Fl R Ar directory +.if \n(SL \{\ +.Op Fl r Ar role +.Op Fl t Ar type +.\} .Op Fl T Ar timeout .Op Fl u Ar user .Ar @@ -279,6 +287,12 @@ This option is only available on systems with .Bx login classes. .\} +.It Fl D Ar directory , Fl -chdir Ns = Ns Ar directory +Run the command in the specified +.Ar directory +instead of the current working directory. +The security policy may return an error if the user does not have +permission to specify the working directory. .It Fl E , -preserve-env Indicates to the security policy that the user wishes to preserve their existing environment variables. @@ -545,6 +559,14 @@ specified by a PAM module unless the .Em passprompt_override flag is disabled in .Em sudoers . +.It Fl R Ar directory , Fl -chroot Ns = Ns Ar directory +Change to the specified root +.Ar directory +(see +.Xr chroot @mansectsu@ ) +before running the command. +The security policy may return an error if the user does not have +permission to specify the root directory. .if \n(SL \{\ .It Fl r Ar role , Fl -role Ns = Ns Ar role Run the command with an SELinux security context that includes diff --git a/doc/sudo_plugin.man.in b/doc/sudo_plugin.man.in index 041391467..2d6be70ce 100644 --- a/doc/sudo_plugin.man.in +++ b/doc/sudo_plugin.man.in @@ -215,6 +215,24 @@ The plugin may optionally pass this, or another value, back in the \fIcommand_info\fR list. .TP 6n +cmnd_chroot=string +The root directory (see +chroot(2)) +to run the command in, as specified by the user via the +\fB\-R\fR +option. +The plugin may ignore or restrict the user's ability to specify a new +root directory. +Only available starting with API version 1.16. +.TP 6n +cmnd_cwd=string +The working directory to run the command in, as specified by the user via the +\fB\-D\fR +option. +The plugin may ignore or restrict the user's ability to specify a new +working directory. +Only available starting with API version 1.16. +.TP 6n debug_flags=string A debug file path name followed by a space and a comma-separated list of debug flags that correspond to the plugin's @@ -250,10 +268,6 @@ contains a plugin-specific \fRDebug\fR entry. .TP 6n -debug_level=number -This setting has been deprecated in favor of -\fIdebug_flags\fR. -.TP 6n ignore_ticket=bool Set to true if the user specified the \fB\-k\fR @@ -425,8 +439,10 @@ For more information, see the section. .TP 6n timeout=string -User-specified command timeout. -Not all plugins support command timeouts and the ability for the +Command timeout specified by the user via the +\fB\-T\fR +option. +Not all plugins support command timeouts and the ability of the user to set a timeout may be restricted by policy. The format of the timeout string is plugin-specific. .PP @@ -5038,6 +5054,14 @@ Version 1.16 (sudo 1.9.3) Initial resource limit values were added to the \fRuser_info\fR list. +.sp +The +\fIcmnd_chroot\fR +and +\fIcmnd_cwd\fR +enties were added to the +\fRsettings\fR +list. .SH "SEE ALSO" sudo.conf(@mansectform@), sudoers(@mansectform@), diff --git a/doc/sudo_plugin.mdoc.in b/doc/sudo_plugin.mdoc.in index f29e549d8..7288d18ed 100644 --- a/doc/sudo_plugin.mdoc.in +++ b/doc/sudo_plugin.mdoc.in @@ -197,6 +197,22 @@ or higher. The plugin may optionally pass this, or another value, back in the .Em command_info list. +.It cmnd_chroot=string +The root directory (see +.Xr chroot 2 ) +to run the command in, as specified by the user via the +.Fl R +option. +The plugin may ignore or restrict the user's ability to specify a new +root directory. +Only available starting with API version 1.16. +.It cmnd_cwd=string +The working directory to run the command in, as specified by the user via the +.Fl D +option. +The plugin may ignore or restrict the user's ability to specify a new +working directory. +Only available starting with API version 1.16. .It debug_flags=string A debug file path name followed by a space and a comma-separated list of debug flags that correspond to the plugin's @@ -231,9 +247,6 @@ if contains a plugin-specific .Li Debug entry. -.It debug_level=number -This setting has been deprecated in favor of -.Em debug_flags . .It ignore_ticket=bool Set to true if the user specified the .Fl k @@ -384,8 +397,10 @@ For more information, see the .Em check_policy section. .It timeout=string -User-specified command timeout. -Not all plugins support command timeouts and the ability for the +Command timeout specified by the user via the +.Fl T +option. +Not all plugins support command timeouts and the ability of the user to set a timeout may be restricted by policy. The format of the timeout string is plugin-specific. .El @@ -4451,6 +4466,14 @@ Support for audit and approval plugins was added. Initial resource limit values were added to the .Li user_info list. +.Pp +The +.Em cmnd_chroot +and +.Em cmnd_cwd +enties were added to the +.Li settings +list. .El .Sh SEE ALSO .Xr sudo.conf @mansectform@ , diff --git a/doc/sudoers.man.in b/doc/sudoers.man.in index c82ae74fc..0b57e4ca5 100644 --- a/doc/sudoers.man.in +++ b/doc/sudoers.man.in @@ -25,7 +25,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.TH "SUDOERS" "@mansectform@" "August 28, 2020" "Sudo @PACKAGE_VERSION@" "File Formats Manual" +.TH "SUDOERS" "@mansectform@" "September 1, 2020" "Sudo @PACKAGE_VERSION@" "File Formats Manual" .nh .if n .ad l .SH "NAME" @@ -1517,7 +1517,15 @@ must be a fully-qualified path name beginning with a \(oq/\(cq or \(oq~\(cq -character. +character, or the special value +\(lq*\(rq. +A value of +\(lq*\(rq +indicates that the user may specify the working directory by running +\fBsudo\fR +with the +\fB\-D\fR +option. By default, commands are run from the invoking user's current working directory, unless the \fB\-i\fR @@ -1540,7 +1548,15 @@ must be a fully-qualified path name beginning with a \(oq/\(cq or \(oq~\(cq -character. +character, or the special value +\(lq*\(rq. +A value of +\(lq*\(rq +indicates that the user may specify the root directory by running +\fBsudo\fR +with the +\fB\-R\fR +option . This setting can be used to run the command in a chroot(2) \(lqsandbox\(rq @@ -4455,6 +4471,12 @@ runchroot If set, \fBsudo\fR will use this value for the root directory when running a command. +The special value +\(lq*\(rq +will allow the user to specify the root directory via +\fBsudo\fR's +\fB\-R\fR +option. See the \fIChroot_Spec\fR section for more details. @@ -4465,6 +4487,12 @@ runcwd If set, \fBsudo\fR will use this value for the working directory when running a command. +The special value +\(lq*\(rq +will allow the user to specify the working directory via +\fBsudo\fR's +\fB\-D\fR +option. See the \fIChdir_Spec\fR section for more details. diff --git a/doc/sudoers.mdoc.in b/doc/sudoers.mdoc.in index 95a4d04b2..d0c6ef463 100644 --- a/doc/sudoers.mdoc.in +++ b/doc/sudoers.mdoc.in @@ -24,7 +24,7 @@ .nr BA @BAMAN@ .nr LC @LCMAN@ .nr PS @PSMAN@ -.Dd August 28, 2020 +.Dd September 1, 2020 .Dt SUDOERS @mansectform@ .Os Sudo @PACKAGE_VERSION@ .Sh NAME @@ -1437,7 +1437,15 @@ must be a fully-qualified path name beginning with a .Sq / or .Sq ~ -character. +character, or the special value +.Dq * . +A value of +.Dq * +indicates that the user may specify the working directory by running +.Nm sudo +with the +.Fl D +option. By default, commands are run from the invoking user's current working directory, unless the .Fl i @@ -1460,7 +1468,15 @@ must be a fully-qualified path name beginning with a .Sq / or .Sq ~ -character. +character, or the special value +.Dq * . +A value of +.Dq * +indicates that the user may specify the root directory by running +.Nm sudo +with the +.Fl R +option . This setting can be used to run the command in a .Xr chroot 2 .Dq sandbox @@ -4166,6 +4182,12 @@ are processed before the contents of If set, .Nm sudo will use this value for the root directory when running a command. +The special value +.Dq * +will allow the user to specify the root directory via +.Nm sudo Ns 's +.Fl R +option. See the .Sx Chroot_Spec section for more details. @@ -4175,6 +4197,12 @@ This setting is only supported by version 1.9.3 or higher. If set, .Nm sudo will use this value for the working directory when running a command. +The special value +.Dq * +will allow the user to specify the working directory via +.Nm sudo Ns 's +.Fl D +option. See the .Sx Chdir_Spec section for more details. diff --git a/plugins/sudoers/def_data.c b/plugins/sudoers/def_data.c index d8f30b8fb..3eab739ec 100644 --- a/plugins/sudoers/def_data.c +++ b/plugins/sudoers/def_data.c @@ -552,11 +552,11 @@ struct sudo_defs_types sudo_defs_table[] = { N_("Set the pam remote host to the local host name"), NULL, }, { - "runcwd", T_STR|T_BOOL|T_PATH|T_TILDE, + "runcwd", T_STR|T_BOOL|T_CHPATH, N_("Working directory to change to before executing the command: %s"), NULL, }, { - "runchroot", T_STR|T_BOOL|T_PATH|T_TILDE, + "runchroot", T_STR|T_BOOL|T_CHPATH, N_("Root directory to change to before executing the command: %s"), NULL, }, { diff --git a/plugins/sudoers/def_data.in b/plugins/sudoers/def_data.in index 918c2bd93..e730aee4d 100644 --- a/plugins/sudoers/def_data.in +++ b/plugins/sudoers/def_data.in @@ -400,8 +400,8 @@ pam_rhost T_FLAG "Set the pam remote host to the local host name" runcwd - T_STR|T_BOOL|T_PATH|T_TILDE + T_STR|T_BOOL|T_CHPATH "Working directory to change to before executing the command: %s" runchroot - T_STR|T_BOOL|T_PATH|T_TILDE + T_STR|T_BOOL|T_CHPATH "Root directory to change to before executing the command: %s" diff --git a/plugins/sudoers/defaults.c b/plugins/sudoers/defaults.c index 0dbdff5ac..5a3d18485 100644 --- a/plugins/sudoers/defaults.c +++ b/plugins/sudoers/defaults.c @@ -67,6 +67,7 @@ static bool store_tuple(const char *str, union sudo_defs_val *sd_un, struct def_ static bool store_uint(const char *str, union sudo_defs_val *sd_un); static bool store_timespec(const char *str, union sudo_defs_val *sd_un); static bool list_op(const char *str, size_t, union sudo_defs_val *sd_un, enum list_ops op); +static bool valid_path(struct sudo_defs_types *def, const char *val, const char *file, int lineno, bool quiet); /* * Table describing compile-time and run-time options. @@ -249,21 +250,13 @@ parse_default_entry(struct sudo_defs_types *def, const char *val, int op, rc = store_syslogpri(val, &def->sd_un); break; case T_STR: - if (ISSET(def->type, T_PATH) && val != NULL && *val != '/' && - (!ISSET(def->type, T_TILDE) || *val != '~')) { - if (!quiet) { - if (lineno > 0) { - sudo_warnx(U_("%s:%d: values for \"%s\" must start with a '/'"), - file, lineno, def->name); - } else { - sudo_warnx(U_("%s: values for \"%s\" must start with a '/'"), - file, def->name); - } + if (val != NULL && ISSET(def->type, T_PATH|T_CHPATH)) { + if (!valid_path(def, val, file, lineno, quiet)) { + rc = -1; + break; } - rc = -1; - break; } - rc = store_str(val, &def->sd_un); + rc = store_str(val, &def->sd_un); break; case T_INT: rc = store_int(val, &def->sd_un); @@ -1017,6 +1010,48 @@ store_timeout(const char *str, union sudo_defs_val *sd_un) debug_return_bool(true); } +static bool +valid_path(struct sudo_defs_types *def, const char *val, + const char *file, int lineno, bool quiet) +{ + bool ret = true; + debug_decl(valid_path, SUDOERS_DEBUG_DEFAULTS); + + if (ISSET(def->type, T_CHPATH)) { + if (val[0] != '/' && val[0] != '~' && (val[0] != '*' || val[1] != '\0')) { + if (!quiet) { + if (lineno > 0) { + sudo_warnx( + U_("%s:%d: values for \"%s\" must start with a '/', '*', or '*'"), + file, lineno, def->name); + } else { + sudo_warnx( + U_("%s: values for \"%s\" must start with a '/', '*', or '*'"), + file, def->name); + } + } + ret = false; + } + } else { + if (val[0] != '/') { + if (!quiet) { + if (lineno > 0) { + sudo_warnx( + U_("%s:%d: values for \"%s\" must start with a '/'"), + file, lineno, def->name); + } else { + sudo_warnx( + U_("%s: values for \"%s\" must start with a '/'"), + file, def->name); + } + } + ret = false; + } + + } + debug_return_bool(ret); +} + static bool list_op(const char *str, size_t len, union sudo_defs_val *sd_un, enum list_ops op) diff --git a/plugins/sudoers/defaults.h b/plugins/sudoers/defaults.h index c31516113..5ff411817 100644 --- a/plugins/sudoers/defaults.h +++ b/plugins/sudoers/defaults.h @@ -111,8 +111,8 @@ struct early_default { #define T_BOOL 0x100 #undef T_PATH #define T_PATH 0x200 -#undef T_TILDE -#define T_TILDE 0x400 +#undef T_CHPATH +#define T_CHPATH 0x400 /* * Argument to update_defaults() diff --git a/plugins/sudoers/gram.c b/plugins/sudoers/gram.c index a722832c2..d52c661d3 100644 --- a/plugins/sudoers/gram.c +++ b/plugins/sudoers/gram.c @@ -583,7 +583,7 @@ short *yysslim; YYSTYPE *yyvs; unsigned int yystacksize; int yyparse(void); -#line 1004 "gram.y" +#line 1018 "gram.y" void sudoerserror(const char *s) { @@ -1746,71 +1746,85 @@ break; case 54: #line 576 "gram.y" { + if (yyvsp[0].string[0] != '/' && yyvsp[0].string[0] != '~') { + if (strcmp(yyvsp[0].string, "*") != 0) { + sudoerserror(N_("values for \"CWD\" must" + " start with a '/', '~', or '*'")); + YYERROR; + } + } yyval.string = yyvsp[0].string; } break; case 55: -#line 581 "gram.y" +#line 588 "gram.y" { + if (yyvsp[0].string[0] != '/' && yyvsp[0].string[0] != '~') { + if (strcmp(yyvsp[0].string, "*") != 0) { + sudoerserror(N_("values for \"CHROOT\" must" + " start with a '/', '~', or '*'")); + YYERROR; + } + } yyval.string = yyvsp[0].string; } break; case 56: -#line 586 "gram.y" -{ - yyval.string = yyvsp[0].string; - } -break; -case 57: -#line 591 "gram.y" -{ - yyval.string = yyvsp[0].string; - } -break; -case 58: -#line 595 "gram.y" -{ - yyval.string = yyvsp[0].string; - } -break; -case 59: #line 600 "gram.y" { yyval.string = yyvsp[0].string; } break; -case 60: +case 57: #line 605 "gram.y" { yyval.string = yyvsp[0].string; } break; -case 61: -#line 610 "gram.y" +case 58: +#line 609 "gram.y" { yyval.string = yyvsp[0].string; } break; -case 62: +case 59: #line 614 "gram.y" { yyval.string = yyvsp[0].string; } break; -case 63: +case 60: #line 619 "gram.y" +{ + yyval.string = yyvsp[0].string; + } +break; +case 61: +#line 624 "gram.y" +{ + yyval.string = yyvsp[0].string; + } +break; +case 62: +#line 628 "gram.y" +{ + yyval.string = yyvsp[0].string; + } +break; +case 63: +#line 633 "gram.y" { yyval.runas = NULL; } break; case 64: -#line 622 "gram.y" +#line 636 "gram.y" { yyval.runas = yyvsp[-1].runas; } break; case 65: -#line 627 "gram.y" +#line 641 "gram.y" { yyval.runas = calloc(1, sizeof(struct runascontainer)); if (yyval.runas != NULL) { @@ -1828,7 +1842,7 @@ case 65: } break; case 66: -#line 642 "gram.y" +#line 656 "gram.y" { yyval.runas = calloc(1, sizeof(struct runascontainer)); if (yyval.runas == NULL) { @@ -1840,7 +1854,7 @@ case 66: } break; case 67: -#line 651 "gram.y" +#line 665 "gram.y" { yyval.runas = calloc(1, sizeof(struct runascontainer)); if (yyval.runas == NULL) { @@ -1852,7 +1866,7 @@ case 67: } break; case 68: -#line 660 "gram.y" +#line 674 "gram.y" { yyval.runas = calloc(1, sizeof(struct runascontainer)); if (yyval.runas == NULL) { @@ -1864,7 +1878,7 @@ case 68: } break; case 69: -#line 669 "gram.y" +#line 683 "gram.y" { yyval.runas = calloc(1, sizeof(struct runascontainer)); if (yyval.runas != NULL) { @@ -1882,27 +1896,27 @@ case 69: } break; case 70: -#line 686 "gram.y" +#line 700 "gram.y" { init_options(&yyval.options); } break; case 71: -#line 689 "gram.y" +#line 703 "gram.y" { free(yyval.options.runcwd); yyval.options.runcwd = yyvsp[0].string; } break; case 72: -#line 693 "gram.y" +#line 707 "gram.y" { free(yyval.options.runchroot); yyval.options.runchroot = yyvsp[0].string; } break; case 73: -#line 697 "gram.y" +#line 711 "gram.y" { yyval.options.notbefore = parse_gentime(yyvsp[0].string); free(yyvsp[0].string); @@ -1913,7 +1927,7 @@ case 73: } break; case 74: -#line 705 "gram.y" +#line 719 "gram.y" { yyval.options.notafter = parse_gentime(yyvsp[0].string); free(yyvsp[0].string); @@ -1924,7 +1938,7 @@ case 74: } break; case 75: -#line 713 "gram.y" +#line 727 "gram.y" { yyval.options.timeout = parse_timeout(yyvsp[0].string); free(yyvsp[0].string); @@ -1938,7 +1952,7 @@ case 75: } break; case 76: -#line 724 "gram.y" +#line 738 "gram.y" { #ifdef HAVE_SELINUX free(yyval.options.role); @@ -1947,7 +1961,7 @@ case 76: } break; case 77: -#line 730 "gram.y" +#line 744 "gram.y" { #ifdef HAVE_SELINUX free(yyval.options.type); @@ -1956,7 +1970,7 @@ case 77: } break; case 78: -#line 736 "gram.y" +#line 750 "gram.y" { #ifdef HAVE_PRIV_SET free(yyval.options.privs); @@ -1965,7 +1979,7 @@ case 78: } break; case 79: -#line 742 "gram.y" +#line 756 "gram.y" { #ifdef HAVE_PRIV_SET free(yyval.options.limitprivs); @@ -1974,97 +1988,97 @@ case 79: } break; case 80: -#line 750 "gram.y" +#line 764 "gram.y" { TAGS_INIT(yyval.tag); } break; case 81: -#line 753 "gram.y" +#line 767 "gram.y" { yyval.tag.nopasswd = true; } break; case 82: -#line 756 "gram.y" +#line 770 "gram.y" { yyval.tag.nopasswd = false; } break; case 83: -#line 759 "gram.y" +#line 773 "gram.y" { yyval.tag.noexec = true; } break; case 84: -#line 762 "gram.y" +#line 776 "gram.y" { yyval.tag.noexec = false; } break; case 85: -#line 765 "gram.y" +#line 779 "gram.y" { yyval.tag.setenv = true; } break; case 86: -#line 768 "gram.y" +#line 782 "gram.y" { yyval.tag.setenv = false; } break; case 87: -#line 771 "gram.y" +#line 785 "gram.y" { yyval.tag.log_input = true; } break; case 88: -#line 774 "gram.y" +#line 788 "gram.y" { yyval.tag.log_input = false; } break; case 89: -#line 777 "gram.y" +#line 791 "gram.y" { yyval.tag.log_output = true; } break; case 90: -#line 780 "gram.y" +#line 794 "gram.y" { yyval.tag.log_output = false; } break; case 91: -#line 783 "gram.y" +#line 797 "gram.y" { yyval.tag.follow = true; } break; case 92: -#line 786 "gram.y" +#line 800 "gram.y" { yyval.tag.follow = false; } break; case 93: -#line 789 "gram.y" +#line 803 "gram.y" { yyval.tag.send_mail = true; } break; case 94: -#line 792 "gram.y" +#line 806 "gram.y" { yyval.tag.send_mail = false; } break; case 95: -#line 797 "gram.y" +#line 811 "gram.y" { yyval.member = new_member(NULL, ALL); if (yyval.member == NULL) { @@ -2074,7 +2088,7 @@ case 95: } break; case 96: -#line 804 "gram.y" +#line 818 "gram.y" { yyval.member = new_member(yyvsp[0].string, ALIAS); if (yyval.member == NULL) { @@ -2084,7 +2098,7 @@ case 96: } break; case 97: -#line 811 "gram.y" +#line 825 "gram.y" { struct sudo_command *c; @@ -2101,7 +2115,7 @@ case 97: } break; case 100: -#line 831 "gram.y" +#line 845 "gram.y" { const char *s; s = alias_add(&parsed_policy, yyvsp[-2].string, HOSTALIAS, @@ -2113,14 +2127,14 @@ case 100: } break; case 102: -#line 843 "gram.y" +#line 857 "gram.y" { HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries); yyval.member = yyvsp[-2].member; } break; case 105: -#line 853 "gram.y" +#line 867 "gram.y" { const char *s; s = alias_add(&parsed_policy, yyvsp[-2].string, CMNDALIAS, @@ -2132,14 +2146,14 @@ case 105: } break; case 107: -#line 865 "gram.y" +#line 879 "gram.y" { HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries); yyval.member = yyvsp[-2].member; } break; case 110: -#line 875 "gram.y" +#line 889 "gram.y" { const char *s; s = alias_add(&parsed_policy, yyvsp[-2].string, RUNASALIAS, @@ -2151,7 +2165,7 @@ case 110: } break; case 113: -#line 890 "gram.y" +#line 904 "gram.y" { const char *s; s = alias_add(&parsed_policy, yyvsp[-2].string, USERALIAS, @@ -2163,28 +2177,28 @@ case 113: } break; case 115: -#line 902 "gram.y" +#line 916 "gram.y" { HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries); yyval.member = yyvsp[-2].member; } break; case 116: -#line 908 "gram.y" +#line 922 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = false; } break; case 117: -#line 912 "gram.y" +#line 926 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = true; } break; case 118: -#line 918 "gram.y" +#line 932 "gram.y" { yyval.member = new_member(yyvsp[0].string, ALIAS); if (yyval.member == NULL) { @@ -2194,7 +2208,7 @@ case 118: } break; case 119: -#line 925 "gram.y" +#line 939 "gram.y" { yyval.member = new_member(NULL, ALL); if (yyval.member == NULL) { @@ -2204,7 +2218,7 @@ case 119: } break; case 120: -#line 932 "gram.y" +#line 946 "gram.y" { yyval.member = new_member(yyvsp[0].string, NETGROUP); if (yyval.member == NULL) { @@ -2214,7 +2228,7 @@ case 120: } break; case 121: -#line 939 "gram.y" +#line 953 "gram.y" { yyval.member = new_member(yyvsp[0].string, USERGROUP); if (yyval.member == NULL) { @@ -2224,7 +2238,7 @@ case 121: } break; case 122: -#line 946 "gram.y" +#line 960 "gram.y" { yyval.member = new_member(yyvsp[0].string, WORD); if (yyval.member == NULL) { @@ -2234,28 +2248,28 @@ case 122: } break; case 124: -#line 956 "gram.y" +#line 970 "gram.y" { HLTQ_CONCAT(yyvsp[-2].member, yyvsp[0].member, entries); yyval.member = yyvsp[-2].member; } break; case 125: -#line 962 "gram.y" +#line 976 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = false; } break; case 126: -#line 966 "gram.y" +#line 980 "gram.y" { yyval.member = yyvsp[0].member; yyval.member->negated = true; } break; case 127: -#line 972 "gram.y" +#line 986 "gram.y" { yyval.member = new_member(yyvsp[0].string, ALIAS); if (yyval.member == NULL) { @@ -2265,7 +2279,7 @@ case 127: } break; case 128: -#line 979 "gram.y" +#line 993 "gram.y" { yyval.member = new_member(NULL, ALL); if (yyval.member == NULL) { @@ -2275,7 +2289,7 @@ case 128: } break; case 129: -#line 986 "gram.y" +#line 1000 "gram.y" { yyval.member = new_member(yyvsp[0].string, WORD); if (yyval.member == NULL) { @@ -2285,18 +2299,18 @@ case 129: } break; case 130: -#line 995 "gram.y" +#line 1009 "gram.y" { ; } break; case 131: -#line 998 "gram.y" +#line 1012 "gram.y" { ; /* EOF */ } break; -#line 2286 "gram.c" +#line 2300 "gram.c" } yyssp -= yym; yystate = *yyssp; diff --git a/plugins/sudoers/gram.y b/plugins/sudoers/gram.y index 2aafa4e84..4b29a6164 100644 --- a/plugins/sudoers/gram.y +++ b/plugins/sudoers/gram.y @@ -574,11 +574,25 @@ opcmnd : cmnd { ; chdirspec : CWD '=' WORD { + if ($3[0] != '/' && $3[0] != '~') { + if (strcmp($3, "*") != 0) { + sudoerserror(N_("values for \"CWD\" must" + " start with a '/', '~', or '*'")); + YYERROR; + } + } $$ = $3; } ; chrootspec : CHROOT '=' WORD { + if ($3[0] != '/' && $3[0] != '~') { + if (strcmp($3, "*") != 0) { + sudoerserror(N_("values for \"CHROOT\" must" + " start with a '/', '~', or '*'")); + YYERROR; + } + } $$ = $3; } ; diff --git a/plugins/sudoers/policy.c b/plugins/sudoers/policy.c index 57da0f7d2..bf85891c2 100644 --- a/plugins/sudoers/policy.c +++ b/plugins/sudoers/policy.c @@ -180,6 +180,16 @@ sudoers_policy_deserialize_info(void *v, char **runas_user, char **runas_group) } continue; } + if (MATCHES(*cur, "cmnd_chroot=")) { + CHECK(*cur, "cmnd_chroot="); + user_runchroot = *cur + sizeof("cmnd_chroot=") - 1; + continue; + } + if (MATCHES(*cur, "cmnd_cwd=")) { + CHECK(*cur, "cmnd_cwd="); + user_runcwd = *cur + sizeof("cmnd_cwd=") - 1; + continue; + } if (MATCHES(*cur, "runas_user=")) { CHECK(*cur, "runas_user="); *runas_user = *cur + sizeof("runas_user=") - 1; @@ -618,7 +628,7 @@ sudoers_policy_exec_setup(char *argv[], char *envp[], mode_t cmnd_umask, goto oom; } } - if (def_runcwd) { + if (def_runcwd && strcmp(def_runcwd, "*") != 0) { /* Set cwd to explicit value in sudoers. */ if (!expand_tilde(&def_runcwd, runas_pw->pw_name)) { sudo_warnx(U_("invalid working directory: %s"), def_runcwd); @@ -787,7 +797,7 @@ sudoers_policy_exec_setup(char *argv[], char *envp[], mode_t cmnd_umask, if (asprintf(&command_info[info_len++], "timeout=%u", timeout) == -1) goto oom; } - if (def_runchroot != NULL) { + if (def_runchroot != NULL && strcmp(def_runchroot, "*") != 0) { if (!expand_tilde(&def_runchroot, runas_pw->pw_name)) { sudo_warnx(U_("invalid chroot directory: %s"), def_runchroot); goto bad; diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 786c01313..c7f78f8af 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -396,6 +396,28 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], } } + if (user_runchroot != NULL) { + if (def_runchroot == NULL || strcmp(def_runchroot, "*") != 0) { + audit_failure(NewArgv, + N_("user not allowed to change root directory to %s"), + user_runchroot); + sudo_warnx("%s", U_("you are not permitted to use the -R option")); + goto bad; + } + free(def_runchroot); + def_runchroot = user_runchroot; + } + if (user_runcwd != NULL) { + if (def_runcwd == NULL || strcmp(def_runcwd, "*") != 0) { + audit_failure(NewArgv, + N_("user not allowed to change directory to %s"), user_runcwd); + sudo_warnx("%s", U_("you are not permitted to use the -D option")); + goto bad; + } + free(def_runcwd); + def_runcwd = user_runcwd; + } + /* * Look up the timestamp dir owner if one is specified. */ diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index 9ef52afe4..d86e65e5e 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -90,6 +90,8 @@ struct sudo_user { char *shost; char *runhost; char *srunhost; + char *runchroot; + char *runcwd; char *prompt; char *cmnd; char *cmnd_args; @@ -236,6 +238,8 @@ struct sudo_user { #define runas_privs (sudo_user.privs) #define runas_limitprivs (sudo_user.limitprivs) #define user_timeout (sudo_user.timeout) +#define user_runchroot (sudo_user.runchroot) +#define user_runcwd (sudo_user.runcwd) /* Default sudoers uid/gid/mode if not set by the Makefile. */ #ifndef SUDOERS_UID diff --git a/src/parse_args.c b/src/parse_args.c index ebdcc4e7b..e932b5fbc 100644 --- a/src/parse_args.c +++ b/src/parse_args.c @@ -100,7 +100,11 @@ static struct sudo_settings sudo_settings[] = { { "remote_host" }, #define ARG_TIMEOUT 22 { "timeout" }, -#define NUM_SETTINGS 23 +#define ARG_CHROOT 23 + { "cmnd_chroot" }, +#define ARG_CWD 24 + { "cmnd_cwd" }, +#define NUM_SETTINGS 25 { NULL } }; @@ -123,7 +127,7 @@ struct environment { * Note that we must disable arg permutation to support setting environment * variables and to better support the optional arg of the -h flag. */ -static const char short_opts[] = "+Aa:BbC:c:D:Eeg:Hh::iKklnPp:r:SsT:t:U:u:Vv"; +static const char short_opts[] = "+Aa:BbC:c:D:Eeg:Hh::iKklnPp:R:r:SsT:t:U:u:Vv"; static struct option long_opts[] = { { "askpass", no_argument, NULL, 'A' }, { "auth-type", required_argument, NULL, 'a' }, @@ -131,6 +135,7 @@ static struct option long_opts[] = { { "bell", no_argument, NULL, 'B' }, { "close-from", required_argument, NULL, 'C' }, { "login-class", required_argument, NULL, 'c' }, + { "chdir", required_argument, NULL, 'D' }, { "preserve-env", optional_argument, NULL, 'E' }, { "edit", no_argument, NULL, 'e' }, { "group", required_argument, NULL, 'g' }, @@ -144,6 +149,7 @@ static struct option long_opts[] = { { "non-interactive", no_argument, NULL, 'n' }, { "preserve-groups", no_argument, NULL, 'P' }, { "prompt", required_argument, NULL, 'p' }, + { "chroot", required_argument, NULL, 'R' }, { "role", required_argument, NULL, 'r' }, { "stdin", no_argument, NULL, 'S' }, { "shell", no_argument, NULL, 's' }, @@ -334,7 +340,12 @@ parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv, break; #endif case 'D': - /* Ignored for backwards compatibility. */ + assert(optarg != NULL); + if (*optarg == '\0') + usage(); + if (sudo_settings[ARG_CWD].value != NULL) + usage(); + sudo_settings[ARG_CWD].value = optarg; break; case 'E': /* @@ -436,6 +447,14 @@ parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv, usage(); sudo_settings[ARG_PROMPT].value = optarg; break; + case 'R': + assert(optarg != NULL); + if (*optarg == '\0') + usage(); + if (sudo_settings[ARG_CHROOT].value != NULL) + usage(); + sudo_settings[ARG_CHROOT].value = optarg; + break; #ifdef HAVE_SELINUX case 'r': assert(optarg != NULL); @@ -775,6 +794,8 @@ help(void) sudo_lbuf_append(&lbuf, " -c, --login-class=class %s\n", _("run command with the specified BSD login class")); #endif + sudo_lbuf_append(&lbuf, " -D, --chdir=directory %s\n", + _("change the working directory before running command")); sudo_lbuf_append(&lbuf, " -E, --preserve-env %s\n", _("preserve user environment when running command")); sudo_lbuf_append(&lbuf, " --preserve-env=list %s\n", @@ -803,6 +824,8 @@ help(void) _("preserve group vector instead of setting to target's")); sudo_lbuf_append(&lbuf, " -p, --prompt=prompt %s\n", _("use the specified password prompt")); + sudo_lbuf_append(&lbuf, " -R, --chroot=directory %s\n", + _("change the root directory before running command")); #ifdef HAVE_SELINUX sudo_lbuf_append(&lbuf, " -r, --role=role %s\n", _("create SELinux security context with specified role")); diff --git a/src/sudo_usage.h.in b/src/sudo_usage.h.in index d37c49a4f..afccc1582 100644 --- a/src/sudo_usage.h.in +++ b/src/sudo_usage.h.in @@ -1,7 +1,7 @@ /* * SPDX-License-Identifier: ISC * - * Copyright (c) 2007-2010, 2013, 2015, 2017 + * Copyright (c) 2007-2010, 2013, 2015, 2017, 2020 * Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any @@ -27,8 +27,8 @@ #define SUDO_USAGE1 " -h | -K | -k | -V" #define SUDO_USAGE2 " -v [-AknS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-u user]" #define SUDO_USAGE3 " -l [-AknS] @BSDAUTH_USAGE@[-g group] [-h host] [-p prompt] [-U user] [-u user] [command]" -#define SUDO_USAGE4 " [-AbEHknPS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-T timeout] [-u user] [VAR=value] [-i|-s] []" -#define SUDO_USAGE5 " -e [-AknS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-T timeout] [-u user] file ..." +#define SUDO_USAGE4 " [-AbEHknPS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] [-D directory] @LOGINCAP_USAGE@[-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] [VAR=value] [-i|-s] []" +#define SUDO_USAGE5 " -e [-AknS] @BSDAUTH_USAGE@@SELINUX_USAGE@[-C num] @LOGINCAP_USAGE@[-D directory] [-g group] [-h host] [-p prompt] [-R directory] [-T timeout] [-u user] file ..." /* * Configure script arguments used to build sudo.