From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier Newsgroups: gmane.emacs.bugs Subject: bug#45198: 28.0.50; Sandbox mode Date: Sat, 12 Dec 2020 13:01:04 -0500 Message-ID: Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="6171"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Bastien , =?UTF-8?Q?Jo=C3=A3o_?= =?UTF-8?Q?T=C3=A1vora?= To: 45198@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sat Dec 12 20:41:01 2020 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1koAlG-0001Sh-6D for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 12 Dec 2020 20:40:58 +0100 Original-Received: from localhost ([::1]:51392 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1koAlF-0003Ev-80 for geb-bug-gnu-emacs@m.gmane-mx.org; Sat, 12 Dec 2020 14:40:57 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:38074) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ko9ko-0005sN-Fd for bug-gnu-emacs@gnu.org; Sat, 12 Dec 2020 13:36:26 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:34911) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1ko9kn-0008Iy-Qg; Sat, 12 Dec 2020 13:36:25 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1ko9Uw-00006j-FR; Sat, 12 Dec 2020 13:20:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Stefan Monnier Original-Sender: "Debbugs-submit" Resent-CC: joaotavora@gmail.com, bzg@gnu.org, bug-gnu-emacs@gnu.org Resent-Date: Sat, 12 Dec 2020 18:20:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 45198 X-GNU-PR-Package: emacs X-Debbugs-Original-To: bug-gnu-emacs@gnu.org X-Debbugs-Original-Xcc: =?UTF-8?Q?Jo=C3=A3o_?= =?UTF-8?Q?T=C3=A1vora?= , Bastien Original-Received: via spool by submit@debbugs.gnu.org id=B.1607797188368 (code B ref -1); Sat, 12 Dec 2020 18:20:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 12 Dec 2020 18:19:48 +0000 Original-Received: from localhost ([127.0.0.1]:46384 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ko9Uh-00005r-G9 for submit@debbugs.gnu.org; Sat, 12 Dec 2020 13:19:48 -0500 Original-Received: from lists.gnu.org ([209.51.188.17]:59840) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ko9Uf-00005i-O7 for submit@debbugs.gnu.org; Sat, 12 Dec 2020 13:19:46 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:57788) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ko9Uf-0001Ug-HC for bug-gnu-emacs@gnu.org; Sat, 12 Dec 2020 13:19:45 -0500 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:40496) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ko9UZ-0002Je-4U for bug-gnu-emacs@gnu.org; Sat, 12 Dec 2020 13:19:44 -0500 Original-Received: from pmg3.iro.umontreal.ca (localhost [127.0.0.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 70572440F18 for ; Sat, 12 Dec 2020 13:01:10 -0500 (EST) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg3.iro.umontreal.ca (Proxmox) with ESMTP id 8766E440C81 for ; Sat, 12 Dec 2020 13:01:05 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1607796065; bh=6moWS3PcQQwNhJrOlK2HH1Zp99YpES4JTXd/Kwohx2M=; h=From:To:Subject:Date:From; b=QEFcd6Y/qxeokKcIkoKiL1cbk59eotbql6FsDizKrV3zOvbzlPq9ab+pfjBpEVDQB KbD41x+AeVXxmAoJKL/rR3nmD3KGvqLnimeyhZY5axBWrEesyIGf9RM/5GWub347hp 08TONzyu1+M3vSdDBOnV/OAsDtnw1kosNYare4J9Y6dQFyccFi6UAcmV/gecAiZ/vx DlwT3jP50X5788NkCnmCt3GDGiiVYwSabVBp4eOWzM55gpXIEj6vA2sZB+kKuAcMkN 0tKYvXj3ZLZ6kLev64Z4/NVJCQ9mSg999DwGP17U4ePlMzld5hdXGJrk5tcImRr/K1 B5RF0wYu86EmQ== Original-Received: from alfajor (69-165-136-52.dsl.teksavvy.com [69.165.136.52]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 59FD712045F for ; Sat, 12 Dec 2020 13:01:05 -0500 (EST) Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, T_SPF_TEMPERROR=0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:195864 Archived-At: Package: Emacs Version: 28.0.50 Currently we cannot enable `flymake-mode` by default in `elisp-mode` for simple reasons of security: having this mode enabled means that whenever we happen to open a file containing ELisp code, our Emacs will happily try to compile this file, including evaluating arbitrary code contained in its macros. Similarly, elpa.gnu.org can't automatically take a documentation file in Org format and convert it to Texinfo (and then Info) when generating a package's tarball, because the Org -> Texinfo conversion can run arbitrary code. To try and address these problems I suggest the function `sandbox-enter` in the patch below, which should let us run untrusted ELisp code safely (in an Emacs subprocess). The patch for `elisp-flymake-byte-compile` is just there to give an idea of how I expect it to work: currently the compiler will still try to write the result of its compilation, which will fail because we're now sandboxed, so it needs more work before it behaves like it should. One thing I'm particularly eager to hear your opinion about is whether there might be more holes to plug (i.e. more places where we need to call `ensure_no_sandbox`). Clearly, from a security perspective, this is the main drawback of this approach: it's based on a black list rather than on a whitelist. Still, I have the impression that it should be manageable. Stefan diff --git a/etc/NEWS b/etc/NEWS index 514209516d..9a569b4bd2 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -85,6 +85,16 @@ useful on systems such as FreeBSD which ships only with "etc/termcap". * Changes in Emacs 28.1 +** Sandbox mode to run untrusted code. +The new function 'sandbox-enter' puts Emacs mode into a state in which +it can (hopefully) run untrusted code safely. This mode is restricted such +that Emacs cannot exit the mode, nor can it write to files or launch +new processes. It can still read arbitrary files and communicate over +pre-existing communication links. This can only be used in batch mode. +The expected use is to launch a new batch Emacs session, put it +into sandbox mode, then run the untrusted code, send back the +result via 'message', and then exit. + ** Minibuffer scrolling is now conservative by default. This is controlled by the new variable 'scroll-minibuffer-conservatively'. diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index fa360a8f3f..189ee896b6 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el @@ -1839,6 +1839,7 @@ elisp-flymake--batch-compile-for-flymake (push (list string position fill level) collected) t))) + (sandbox-enter) ;FIXME: this will break the subsequent delete-file! (unwind-protect (byte-compile-file file) (ignore-errors diff --git a/src/emacs-module.c b/src/emacs-module.c index 0f3ef59fd8..1bebdfeb4a 100644 --- a/src/emacs-module.c +++ b/src/emacs-module.c @@ -1091,6 +1091,7 @@ DEFUN ("module-load", Fmodule_load, Smodule_load, 1, 1, 0, doc: /* Load module FILE. */) (Lisp_Object file) { + ensure_no_sandbox (); dynlib_handle_ptr handle; emacs_init_function module_init; void *gpl_sym; @@ -1151,6 +1152,7 @@ DEFUN ("module-load", Fmodule_load, Smodule_load, 1, 1, 0, Lisp_Object funcall_module (Lisp_Object function, ptrdiff_t nargs, Lisp_Object *arglist) { + ensure_no_sandbox (); const struct Lisp_Module_Function *func = XMODULE_FUNCTION (function); eassume (0 <= func->min_arity); if (! (func->min_arity <= nargs diff --git a/src/emacs.c b/src/emacs.c index 2a32083ba1..0df70ae43e 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -139,6 +139,8 @@ #define MAIN_PROGRAM struct gflags gflags; bool initialized; +bool emacs_is_sandboxed; + /* If true, Emacs should not attempt to use a window-specific code, but instead should use the virtual terminal under which it was started. */ bool inhibit_window_system; @@ -2886,6 +2888,30 @@ DEFUN ("daemon-initialized", Fdaemon_initialized, Sdaemon_initialized, 0, 0, 0, return Qt; } +DEFUN ("sandbox-enter", Fsandbox_enter, Ssandbox_enter, 0, 0, 0, + doc: /* Put Emacs in sandboxed mode. +After calling this function, Emacs cannot have externally visible +side effects any more. For example, it will not be able to write to files, +create new processes, open new displays, or call functionality provided by +modules, ... +It can still send data to pre-existing processes, on the other hand. +There is no mechanism to exit sandboxed mode. */) + (void) +{ + if (!noninteractive) + /* We could allow a sandbox in interactive sessions, but it seems + useless, so we'll assume that it was a pilot-error. */ + error ("Can't enter sandbox in interactive session"); + emacs_is_sandboxed = true; + return Qnil; +} + +void ensure_no_sandbox (void) +{ + if (emacs_is_sandboxed) + error ("Sandboxed"); +} + void syms_of_emacs (void) { @@ -2906,6 +2932,7 @@ syms_of_emacs (void) defsubr (&Sinvocation_directory); defsubr (&Sdaemonp); defsubr (&Sdaemon_initialized); + defsubr (&Ssandbox_enter); DEFVAR_LISP ("command-line-args", Vcommand_line_args, doc: /* Args passed by shell to Emacs, as a list of strings. diff --git a/src/fileio.c b/src/fileio.c index 702c143828..e221048493 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -689,6 +689,7 @@ DEFUN ("make-temp-file-internal", Fmake_temp_file_internal, (Lisp_Object prefix, Lisp_Object dir_flag, Lisp_Object suffix, Lisp_Object text) { + ensure_no_sandbox (); CHECK_STRING (prefix); CHECK_STRING (suffix); Lisp_Object encoded_prefix = ENCODE_FILE (prefix); @@ -2043,6 +2044,7 @@ DEFUN ("copy-file", Fcopy_file, Scopy_file, 2, 6, Lisp_Object keep_time, Lisp_Object preserve_uid_gid, Lisp_Object preserve_permissions) { + ensure_no_sandbox (); Lisp_Object handler; ptrdiff_t count = SPECPDL_INDEX (); Lisp_Object encoded_file, encoded_newname; @@ -2301,6 +2303,7 @@ DEFUN ("make-directory-internal", Fmake_directory_internal, doc: /* Create a new directory named DIRECTORY. */) (Lisp_Object directory) { + ensure_no_sandbox (); const char *dir; Lisp_Object handler; Lisp_Object encoded_dir; @@ -2327,6 +2330,7 @@ DEFUN ("delete-directory-internal", Fdelete_directory_internal, doc: /* Delete the directory named DIRECTORY. Does not follow symlinks. */) (Lisp_Object directory) { + ensure_no_sandbox (); const char *dir; Lisp_Object encoded_dir; @@ -2356,6 +2360,7 @@ DEFUN ("delete-file", Fdelete_file, Sdelete_file, 1, 2, With a prefix argument, TRASH is nil. */) (Lisp_Object filename, Lisp_Object trash) { + ensure_no_sandbox (); Lisp_Object handler; Lisp_Object encoded_file; @@ -2494,6 +2499,7 @@ DEFUN ("rename-file", Frename_file, Srename_file, 2, 3, This is what happens in interactive use with M-x. */) (Lisp_Object file, Lisp_Object newname, Lisp_Object ok_if_already_exists) { + ensure_no_sandbox (); Lisp_Object handler; Lisp_Object encoded_file, encoded_newname; @@ -2614,6 +2620,7 @@ DEFUN ("add-name-to-file", Fadd_name_to_file, Sadd_name_to_file, 2, 3, This is what happens in interactive use with M-x. */) (Lisp_Object file, Lisp_Object newname, Lisp_Object ok_if_already_exists) { + ensure_no_sandbox (); Lisp_Object handler; Lisp_Object encoded_file, encoded_newname; @@ -2667,6 +2674,7 @@ DEFUN ("make-symbolic-link", Fmake_symbolic_link, Smake_symbolic_link, 2, 3, This happens for interactive use with M-x. */) (Lisp_Object target, Lisp_Object linkname, Lisp_Object ok_if_already_exists) { + ensure_no_sandbox (); Lisp_Object handler; Lisp_Object encoded_target, encoded_linkname; @@ -3176,6 +3184,7 @@ DEFUN ("set-file-selinux-context", Fset_file_selinux_context, or if Emacs was not compiled with SELinux support. */) (Lisp_Object filename, Lisp_Object context) { + ensure_no_sandbox (); Lisp_Object absname; Lisp_Object handler; #if HAVE_LIBSELINUX @@ -3307,6 +3316,7 @@ DEFUN ("set-file-acl", Fset_file_acl, Sset_file_acl, support. */) (Lisp_Object filename, Lisp_Object acl_string) { + ensure_no_sandbox (); #if USE_ACL Lisp_Object absname; Lisp_Object handler; @@ -3392,6 +3402,7 @@ DEFUN ("set-file-modes", Fset_file_modes, Sset_file_modes, 2, 3, symbolic notation, like the `chmod' command from GNU Coreutils. */) (Lisp_Object filename, Lisp_Object mode, Lisp_Object flag) { + ensure_no_sandbox (); CHECK_FIXNUM (mode); int nofollow = symlink_nofollow_flag (flag); Lisp_Object absname = Fexpand_file_name (filename, @@ -3458,6 +3469,7 @@ DEFUN ("set-file-times", Fset_file_times, Sset_file_times, 1, 3, 0, TIMESTAMP is in the format of `current-time'. */) (Lisp_Object filename, Lisp_Object timestamp, Lisp_Object flag) { + ensure_no_sandbox (); int nofollow = symlink_nofollow_flag (flag); struct timespec ts[2]; @@ -5039,6 +5051,7 @@ DEFUN ("write-region", Fwrite_region, Swrite_region, 3, 7, (Lisp_Object start, Lisp_Object end, Lisp_Object filename, Lisp_Object append, Lisp_Object visit, Lisp_Object lockname, Lisp_Object mustbenew) { + ensure_no_sandbox (); return write_region (start, end, filename, append, visit, lockname, mustbenew, -1); } @@ -5837,6 +5850,7 @@ DEFUN ("do-auto-save", Fdo_auto_save, Sdo_auto_save, 0, 2, "", A non-nil CURRENT-ONLY argument means save only current buffer. */) (Lisp_Object no_message, Lisp_Object current_only) { + ensure_no_sandbox (); struct buffer *old = current_buffer, *b; Lisp_Object tail, buf, hook; bool auto_saved = 0; diff --git a/src/lisp.h b/src/lisp.h index e83304462f..107a2d9f03 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -603,6 +603,10 @@ #define ENUM_BF(TYPE) enum TYPE subsequent starts. */ extern bool initialized; +/* Whether we've been neutralized. */ +extern bool emacs_is_sandboxed; +extern void ensure_no_sandbox (void); + extern struct gflags { /* True means this Emacs instance was born to dump. */ diff --git a/src/process.c b/src/process.c index 4fe8ac7fc0..68460868c4 100644 --- a/src/process.c +++ b/src/process.c @@ -862,6 +862,8 @@ allocate_pty (char pty_name[PTY_NAME_SIZE]) static struct Lisp_Process * allocate_process (void) { + ensure_no_sandbox (); + return ALLOCATE_ZEROED_PSEUDOVECTOR (struct Lisp_Process, thread, PVEC_PROCESS); } diff --git a/src/xdisp.c b/src/xdisp.c index 96dd4fade2..72c37e8194 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -15442,6 +15442,10 @@ #define RESUME_POLLING \ static void redisplay_internal (void) { + /* Not sure if it's important to avoid redisplay inside a sandbox, + but it seems safer to avoid risking introducing security holes via + image libraries and such. */ + ensure_no_sandbox (); struct window *w = XWINDOW (selected_window); struct window *sw; struct frame *fr; diff --git a/src/xterm.c b/src/xterm.c index 3de0d2e73c..e8a4d2f29d 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -12698,6 +12698,7 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) xcb_connection_t *xcb_conn; #endif + ensure_no_sandbox (); block_input (); if (!x_initialized)