# HG changeset patch # User Bram Moolenaar # Date 1549633507 -3600 # Node ID 93b78c4a7cd52d6eb7ce8ca1592e2e7f262b30ef # Parent ac3d51cbfc8394c2aa171b334638d29e68c8e41b patch 8.1.0881: can execute shell commands in rvim through interfaces commit https://github.com/vim/vim/commit/8c62a08faf89663e5633dc5036cd8695c80f1075 Author: Bram Moolenaar Date: Fri Feb 8 14:34:10 2019 +0100 patch 8.1.0881: can execute shell commands in rvim through interfaces Problem: Can execute shell commands in rvim through interfaces. Solution: Disable using interfaces in restricted mode. Allow for writing file with writefile(), histadd() and a few others. diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -248,12 +248,18 @@ a slash. Thus "-R" means recovery and " changes and writing. {not in Vi} - *-Z* *restricted-mode* *E145* + *-Z* *restricted-mode* *E145* *E981* -Z Restricted mode. All commands that make use of an external shell are disabled. This includes suspending with CTRL-Z, - ":sh", filtering, the system() function, backtick expansion, - delete(), rename(), mkdir(), writefile(), libcall(), - job_start(), etc. + ":sh", filtering, the system() function, backtick expansion + and libcall(). + Also disallowed are delete(), rename(), mkdir(), job_start(), + etc. + Interfaces, such as Python, Ruby and Lua, are also disabled, + since they could be used to execute shell commands. Perl uses + the Safe module. + Note that the user may still find a loophole to execute a + shell command, it has only been made difficult. {not in Vi} *-g* diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6817,7 +6817,7 @@ f_histadd(typval_T *argvars UNUSED, typv #endif rettv->vval.v_number = FALSE; - if (check_restricted() || check_secure()) + if (check_secure()) return; #ifdef FEAT_CMDHIST str = tv_get_string_chk(&argvars[0]); /* NULL on type error */ @@ -7898,6 +7898,9 @@ f_luaeval(typval_T *argvars, typval_T *r char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; + str = tv_get_string_buf(&argvars[0], buf); do_luaeval(str, argvars + 1, rettv); } @@ -8644,6 +8647,8 @@ f_mzeval(typval_T *argvars, typval_T *re char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; str = tv_get_string_buf(&argvars[0], buf); do_mzeval(str, rettv); } @@ -8932,6 +8937,9 @@ f_py3eval(typval_T *argvars, typval_T *r char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; + if (p_pyx == 0) p_pyx = 3; @@ -8950,6 +8958,9 @@ f_pyeval(typval_T *argvars, typval_T *re char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; + if (p_pyx == 0) p_pyx = 2; @@ -8965,6 +8976,9 @@ f_pyeval(typval_T *argvars, typval_T *re static void f_pyxeval(typval_T *argvars, typval_T *rettv) { + if (check_restricted() || check_secure()) + return; + # if defined(FEAT_PYTHON) && defined(FEAT_PYTHON3) init_pyxversion(); if (p_pyx == 2) @@ -10819,7 +10833,7 @@ f_setbufvar(typval_T *argvars, typval_T typval_T *varp; char_u nbuf[NUMBUFLEN]; - if (check_restricted() || check_secure()) + if (check_secure()) return; (void)tv_get_number(&argvars[0]); /* issue errmsg if type error */ varname = tv_get_string_chk(&argvars[1]); @@ -11341,7 +11355,7 @@ f_settabvar(typval_T *argvars, typval_T rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) + if (check_secure()) return; tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); @@ -14714,7 +14728,7 @@ f_writefile(typval_T *argvars, typval_T blob_T *blob = NULL; rettv->vval.v_number = -1; - if (check_restricted() || check_secure()) + if (check_secure()) return; if (argvars[0].v_type == VAR_LIST) diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -4775,7 +4775,7 @@ check_restricted(void) { if (restricted) { - emsg(_("E145: Shell commands not allowed in rvim")); + emsg(_("E145: Shell commands and some functionality not allowed in rvim")); return TRUE; } return FALSE; diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2007,11 +2007,16 @@ do_one_cmd( #ifdef HAVE_SANDBOX if (sandbox != 0 && !(ea.argt & SBOXOK)) { - /* Command not allowed in sandbox. */ + // Command not allowed in sandbox. errormsg = _(e_sandbox); goto doend; } #endif + if (restricted != 0 && (ea.argt & RESTRICT)) + { + errormsg = _("E981: Command not allowed in rvim"); + goto doend; + } if (!curbuf->b_p_ma && (ea.argt & MODIFY)) { /* Command not allowed in non-'modifiable' buffer */ diff --git a/src/if_perl.xs b/src/if_perl.xs --- a/src/if_perl.xs +++ b/src/if_perl.xs @@ -971,6 +971,7 @@ VIM_init(void) #ifdef DYNAMIC_PERL static char *e_noperl = N_("Sorry, this command is disabled: the Perl library could not be loaded."); #endif +static char *e_perlsandbox = N_("E299: Perl evaluation forbidden in sandbox without the Safe module"); /* * ":perl" @@ -1019,13 +1020,12 @@ ex_perl(exarg_T *eap) vim_free(script); } -#ifdef HAVE_SANDBOX - if (sandbox) + if (sandbox || secure) { safe = perl_get_sv("VIM::safe", FALSE); # ifndef MAKE_TEST /* avoid a warning for unreachable code */ if (safe == NULL || !SvTRUE(safe)) - emsg(_("E299: Perl evaluation forbidden in sandbox without the Safe module")); + emsg(_(e_perlsandbox)); else # endif { @@ -1037,7 +1037,6 @@ ex_perl(exarg_T *eap) } } else -#endif perl_eval_sv(sv, G_DISCARD | G_NOARGS); SvREFCNT_dec(sv); @@ -1298,13 +1297,12 @@ do_perleval(char_u *str, typval_T *rettv ENTER; SAVETMPS; -#ifdef HAVE_SANDBOX - if (sandbox) + if (sandbox || secure) { safe = get_sv("VIM::safe", FALSE); # ifndef MAKE_TEST /* avoid a warning for unreachable code */ if (safe == NULL || !SvTRUE(safe)) - emsg(_("E299: Perl evaluation forbidden in sandbox without the Safe module")); + emsg(_(e_perlsandbox)); else # endif { @@ -1320,7 +1318,6 @@ do_perleval(char_u *str, typval_T *rettv } } else -#endif /* HAVE_SANDBOX */ sv = eval_pv((char *)str, 0); if (sv) { diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -213,6 +213,7 @@ NEW_TESTS = \ test_regexp_utf8 \ test_registers \ test_reltime \ + test_restricted \ test_retab \ test_ruby \ test_scriptnames \ @@ -375,6 +376,7 @@ NEW_TESTS_RES = \ test_quotestar.res \ test_regex_char_classes.res \ test_registers.res \ + test_restricted.res \ test_retab.res \ test_ruby.res \ test_scriptnames.res \ diff --git a/src/testdir/test_restricted.vim b/src/testdir/test_restricted.vim new file mode 100644 --- /dev/null +++ b/src/testdir/test_restricted.vim @@ -0,0 +1,107 @@ +" Test for "rvim" or "vim -Z" + +source shared.vim + +func Test_restricted() + let cmd = GetVimCommand('Xrestricted') + if cmd == '' + return + endif + + call writefile([ + \ "silent !ls", + \ "call writefile([v:errmsg], 'Xrestrout')", + \ "qa!", + \ ], 'Xrestricted') + call system(cmd . ' -Z') + call assert_match('E145:', join(readfile('Xrestrout'))) + + call delete('Xrestricted') + call delete('Xrestrout') +endfunc + +func Run_restricted_test(ex_cmd, error) + let cmd = GetVimCommand('Xrestricted') + if cmd == '' + return + endif + + call writefile([ + \ a:ex_cmd, + \ "call writefile([v:errmsg], 'Xrestrout')", + \ "qa!", + \ ], 'Xrestricted') + call system(cmd . ' -Z') + call assert_match(a:error, join(readfile('Xrestrout'))) + + call delete('Xrestricted') + call delete('Xrestrout') +endfunc + +func Test_restricted_lua() + if !has('lua') + throw 'Skipped: Lua is not supported' + endif + call Run_restricted_test('lua print("Hello, Vim!")', 'E981:') + call Run_restricted_test('luado return "hello"', 'E981:') + call Run_restricted_test('luafile somefile', 'E981:') + call Run_restricted_test('call luaeval("expression")', 'E145:') +endfunc + +func Test_restricted_mzscheme() + if !has('mzscheme') + throw 'Skipped: MzScheme is not supported' + endif + call Run_restricted_test('mzscheme statement', 'E981:') + call Run_restricted_test('mzfile somefile', 'E981:') + call Run_restricted_test('call mzeval("expression")', 'E145:') +endfunc + +func Test_restricted_perl() + if !has('perl') + throw 'Skipped: Perl is not supported' + endif + " TODO: how to make Safe mode fail? + " call Run_restricted_test('perl system("ls")', 'E981:') + " call Run_restricted_test('perldo system("hello")', 'E981:') + " call Run_restricted_test('perlfile somefile', 'E981:') + " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:') +endfunc + +func Test_restricted_python() + if !has('python') + throw 'Skipped: Python is not supported' + endif + call Run_restricted_test('python print "hello"', 'E981:') + call Run_restricted_test('pydo return "hello"', 'E981:') + call Run_restricted_test('pyfile somefile', 'E981:') + call Run_restricted_test('call pyeval("expression")', 'E145:') +endfunc + +func Test_restricted_python3() + if !has('python3') + throw 'Skipped: Python3 is not supported' + endif + call Run_restricted_test('py3 print "hello"', 'E981:') + call Run_restricted_test('py3do return "hello"', 'E981:') + call Run_restricted_test('py3file somefile', 'E981:') + call Run_restricted_test('call py3eval("expression")', 'E145:') +endfunc + +func Test_restricted_ruby() + if !has('ruby') + throw 'Skipped: Ruby is not supported' + endif + call Run_restricted_test('ruby print "Hello"', 'E981:') + call Run_restricted_test('rubydo print "Hello"', 'E981:') + call Run_restricted_test('rubyfile somefile', 'E981:') +endfunc + +func Test_restricted_tcl() + if !has('tcl') + throw 'Skipped: Tcl is not supported' + endif + call Run_restricted_test('tcl puts "Hello"', 'E981:') + call Run_restricted_test('tcldo puts "Hello"', 'E981:') + call Run_restricted_test('tclfile somefile', 'E981:') +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -784,6 +784,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 881, +/**/ 880, /**/ 879,