diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 8256152..8320039 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -247,12 +247,18 @@ a slash. Thus "-R" means recovery and "-/R" readonly. 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 index dd4462d..3cc305a 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6446,7 +6446,7 @@ f_histadd(typval_T *argvars UNUSED, typval_T *rettv) #endif rettv->vval.v_number = FALSE; - if (check_restricted() || check_secure()) + if (check_secure()) return; #ifdef FEAT_CMDHIST str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ @@ -7456,6 +7456,9 @@ f_luaeval(typval_T *argvars, typval_T *rettv) char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; + str = get_tv_string_buf(&argvars[0], buf); do_luaeval(str, argvars + 1, rettv); } @@ -8188,6 +8191,8 @@ f_mzeval(typval_T *argvars, typval_T *rettv) char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; str = get_tv_string_buf(&argvars[0], buf); do_mzeval(str, rettv); } @@ -8398,6 +8403,9 @@ f_py3eval(typval_T *argvars, typval_T *rettv) char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; + if (p_pyx == 0) p_pyx = 3; @@ -8416,6 +8424,9 @@ f_pyeval(typval_T *argvars, typval_T *rettv) char_u *str; char_u buf[NUMBUFLEN]; + if (check_restricted() || check_secure()) + return; + if (p_pyx == 0) p_pyx = 2; @@ -8431,6 +8442,9 @@ f_pyeval(typval_T *argvars, typval_T *rettv) 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) @@ -10272,7 +10286,7 @@ f_setbufvar(typval_T *argvars, typval_T *rettv UNUSED) typval_T *varp; char_u nbuf[NUMBUFLEN]; - if (check_restricted() || check_secure()) + if (check_secure()) return; (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ varname = get_tv_string_chk(&argvars[1]); @@ -10792,7 +10806,7 @@ f_settabvar(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = 0; - if (check_restricted() || check_secure()) + if (check_secure()) return; tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); @@ -13674,7 +13688,7 @@ f_writefile(typval_T *argvars, typval_T *rettv) list_T *list; 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 index 111fe01..1827fec 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -4693,7 +4693,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_cmds.h b/src/ex_cmds.h index 48b0253..82d6e29 100644 --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -56,6 +56,7 @@ * curbuf_lock is set */ #define MODIFY 0x200000L /* forbidden in non-'modifiable' buffer */ #define EXFLAGS 0x400000L /* allow flags after count in argument */ +#define RESTRICT 0x800000L /* forbidden in restricted mode */ #define FILES (XFILE | EXTRA) /* multiple extra files allowed */ #define WORD1 (EXTRA | NOSPC) /* one extra word allowed */ #define FILE1 (FILES | NOSPC) /* 1 file allowed, defaults to current file */ @@ -860,13 +861,13 @@ EX(CMD_lunmap, "lunmap", ex_unmap, EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN, ADDR_LINES), EX(CMD_lua, "lua", ex_lua, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_luado, "luado", ex_luado, - RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_luafile, "luafile", ex_luafile, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_lvimgrep, "lvimgrep", ex_vimgrep, RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE, @@ -929,10 +930,10 @@ EX(CMD_mode, "mode", ex_mode, WORD1|TRLBAR|CMDWIN, ADDR_LINES), EX(CMD_mzscheme, "mzscheme", ex_mzscheme, - RANGE|EXTRA|DFLALL|NEEDARG|CMDWIN|SBOXOK, + RANGE|EXTRA|DFLALL|NEEDARG|CMDWIN|SBOXOK|RESTRICT, ADDR_LINES), EX(CMD_mzfile, "mzfile", ex_mzfile, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_next, "next", ex_next, RANGE|NOTADR|BANG|FILES|EDITCMD|ARGOPT|TRLBAR, @@ -1115,37 +1116,37 @@ EX(CMD_pwd, "pwd", ex_pwd, TRLBAR|CMDWIN, ADDR_LINES), EX(CMD_python, "python", ex_python, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_pydo, "pydo", ex_pydo, - RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_pyfile, "pyfile", ex_pyfile, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_py3, "py3", ex_py3, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_py3do, "py3do", ex_py3do, - RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_python3, "python3", ex_py3, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_py3file, "py3file", ex_py3file, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_pyx, "pyx", ex_pyx, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_pyxdo, "pyxdo", ex_pyxdo, - RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_pythonx, "pythonx", ex_pyx, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_pyxfile, "pyxfile", ex_pyxfile, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_quit, "quit", ex_quit, BANG|RANGE|COUNT|NOTADR|TRLBAR|CMDWIN, @@ -1199,13 +1200,13 @@ EX(CMD_runtime, "runtime", ex_runtime, BANG|NEEDARG|FILES|TRLBAR|SBOXOK|CMDWIN, ADDR_LINES), EX(CMD_ruby, "ruby", ex_ruby, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_rubydo, "rubydo", ex_rubydo, - RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_rubyfile, "rubyfile", ex_rubyfile, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_rundo, "rundo", ex_rundo, NEEDARG|FILE1, @@ -1472,13 +1473,13 @@ EX(CMD_tabs, "tabs", ex_tabs, TRLBAR|CMDWIN, ADDR_TABS), EX(CMD_tcl, "tcl", ex_tcl, - RANGE|EXTRA|NEEDARG|CMDWIN, + RANGE|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_tcldo, "tcldo", ex_tcldo, - RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_tclfile, "tclfile", ex_tclfile, - RANGE|FILE1|NEEDARG|CMDWIN, + RANGE|FILE1|NEEDARG|CMDWIN|RESTRICT, ADDR_LINES), EX(CMD_tearoff, "tearoff", ex_tearoff, NEEDARG|EXTRA|TRLBAR|NOTRLCOM|CMDWIN, diff --git a/src/ex_docmd.c b/src/ex_docmd.c index ef86fc8..aaf2f9d 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2372,11 +2372,16 @@ do_one_cmd( #ifdef HAVE_SANDBOX if (sandbox != 0 && !(ea.argt & SBOXOK)) { - /* Command not allowed in sandbox. */ + // Command not allowed in sandbox. errormsg = (char_u *)_(e_sandbox); goto doend; } #endif + if (restricted != 0 && (ea.argt & RESTRICT)) + { + errormsg = (char_u *)_("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 index 7b45033..fc8d613 100644 --- a/src/if_perl.xs +++ b/src/if_perl.xs @@ -930,6 +930,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" @@ -978,13 +979,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 { @@ -996,7 +996,6 @@ ex_perl(exarg_T *eap) } } else -#endif perl_eval_sv(sv, G_DISCARD | G_NOARGS); SvREFCNT_dec(sv); @@ -1259,13 +1258,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 { @@ -1281,7 +1279,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 index e36089a..5f1c38c 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -156,6 +156,7 @@ NEW_TESTS = test_arabic.res \ test_quotestar.res \ test_regex_char_classes.res \ test_registers.res \ + test_restricted.res \ test_retab.res \ test_ruby.res \ test_scrollbind.res \ diff --git a/src/testdir/test_restricted.vim b/src/testdir/test_restricted.vim new file mode 100644 index 0000000..9dd937c --- /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