unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: "Mattias Engdegård" <mattiase@acm.org>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: "Alan Third" <alan@idiocy.org>,
	45198@debbugs.gnu.org, "Stefan Kangas" <stefan@marxist.se>,
	Philipp <p.stephani2@gmail.com>,
	"João Távora" <joaotavora@gmail.com>
Subject: bug#45198: 28.0.50; Sandbox mode
Date: Fri, 17 Sep 2021 21:49:39 +0200	[thread overview]
Message-ID: <8355EDD1-FF78-43B1-8F96-4EB3316E8FEB@acm.org> (raw)
In-Reply-To: <jwvfsu3732l.fsf-monnier+emacs@gnu.org>

[-- Attachment #1: Type: text/plain, Size: 497 bytes --]

17 sep. 2021 kl. 15.20 skrev Stefan Monnier <monnier@iro.umontreal.ca>:

> For `elpa-admin.el` we need a writable directory as well.
> We also need the ability to run sub-processes.  Your `bwrap`
> implementation for GNU/Linux should allow that, AFAICT, but I can't tell
> if `darwin-sandbox-enter` also allows it.

Looks like it can be made to work.

Of course this whole exercise doesn't really touch the questions that really matter, such as whether it is practical for actual use.


[-- Attachment #2: 0001-Add-macOS-sandboxing-bug-45198.patch --]
[-- Type: application/octet-stream, Size: 10223 bytes --]

From 03233ad9abb0c18bdbd00eb2cad42db8a252cafe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Sat, 17 Apr 2021 20:53:39 +0200
Subject: [PATCH 1/2] Add macOS sandboxing (bug#45198)

This is the corresponding low-level sandboxing facility corresponding
to the recently added Seccomp for Linux.  `darwin-sandbox-init` gives
direct access to the system sandboxing call; `darwin--sandbox-enter`
is a wrapper that takes a plist specifying directories under which
files can be read, written or executed.  These should be considered
internal mechanisms for now.

* lisp/darwin-fns.el: New file.
* lisp/loadup.el: Load it.
* src/sysdep.c (Fdarwin_sandbox_init): New function.
* test/lisp/darwin-fns-tests.el: New file.
---
 lisp/darwin-fns.el            | 56 +++++++++++++++++++++
 lisp/loadup.el                |  2 +
 src/sysdep.c                  | 34 +++++++++++++
 test/lisp/darwin-fns-tests.el | 91 +++++++++++++++++++++++++++++++++++
 4 files changed, 183 insertions(+)
 create mode 100644 lisp/darwin-fns.el
 create mode 100644 test/lisp/darwin-fns-tests.el

diff --git a/lisp/darwin-fns.el b/lisp/darwin-fns.el
new file mode 100644
index 0000000000..feba9739b5
--- /dev/null
+++ b/lisp/darwin-fns.el
@@ -0,0 +1,56 @@
+;;; darwin-fns.el --- Darwin-specific functions  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(defun darwin--sandbox-enter (spec)
+  "Enter a sandbox only permitting actions described by SPEC.
+SPEC is a plist allowing the keys:
+`:read-dirs'  -- value is a list of directories in which reading is allowed.
+`:write-dirs' -- value is a list of directories in which writing is allowed.
+`:exec-dirs'  -- value is a list of directories from which executables
+                 can be run as subprocesses.
+Most other operations such as network access are disallowed.
+Existing open descriptors can still be used freely.
+
+This is not a supported interface and is for internal use only."
+  (let ((read-dirs (plist-get spec :read-dirs))
+        (write-dirs (plist-get spec :write-dirs))
+        (exec-dirs (plist-get spec :exec-dirs)))
+    (darwin-sandbox-init
+     (concat
+      "(version 1)\n"
+      "(deny default)\n"
+      ;; Emacs seems to need /dev/null; allowing it does no harm.
+      "(allow file-read* (path \"/dev/null\"))\n"
+      (mapconcat (lambda (dir)
+                   (format "(allow file-read* (subpath %S))\n" dir))
+                 read-dirs "")
+      (mapconcat (lambda (dir)
+                   (format "(allow file-write* (subpath %S))\n" dir))
+                 write-dirs "")
+      (mapconcat (lambda (dir)
+                   (format "(allow process-exec (subpath %S))\n" dir))
+                 exec-dirs "")
+      (and exec-dirs
+           "(allow process-fork)\n")))))
+
+(provide 'darwin-fns)
+
+;;; darwin-fns.el ends here
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 158c02ecea..163b639640 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -325,6 +325,8 @@
       (load "term/pc-win")
       (load "ls-lisp")
       (load "disp-table"))) ; needed to setup ibm-pc char set, see internal.el
+(if (eq system-type 'darwin)
+    (load "darwin-fns"))
 (if (featurep 'ns)
     (progn
       (load "term/common-win")
diff --git a/src/sysdep.c b/src/sysdep.c
index 8eaee22498..79a1fad4da 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -4458,8 +4458,42 @@ str_collate (Lisp_Object s1, Lisp_Object s2,
 }
 #endif	/* WINDOWSNT */
 
+#ifdef DARWIN_OS
+
+/* This function prototype is not in the platform header files.
+   See https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf
+   and https://chromium.googlesource.com/chromium/src/+/master/sandbox/mac/seatbelt_sandbox_design.md */
+int sandbox_init_with_parameters(const char *profile,
+                                 uint64_t flags,
+                                 const char *const parameters[],
+                                 char **errorbuf);
+
+DEFUN ("darwin-sandbox-init", Fdarwin_sandbox_init, Sdarwin_sandbox_init,
+       1, 1, 0,
+       doc: /* Enter a sandbox whose permitted access is curtailed by PROFILE.
+Already open descriptors can be used freely.
+PROFILE is a string in the macOS sandbox profile language,
+a set of rules in a Lisp-like syntax.
+
+This is not a supported interface and is for internal use only. */)
+  (Lisp_Object profile)
+{
+  CHECK_STRING (profile);
+  if (memchr (SSDATA (profile), '\0', SBYTES (profile)))
+    error ("NUL in sandbox profile");
+  char *err = NULL;
+  if (sandbox_init_with_parameters (SSDATA (profile), 0, NULL, &err) != 0)
+    error ("sandbox error: %s", err);
+  return Qnil;
+}
+
+#endif	/* DARWIN_OS */
+
 void
 syms_of_sysdep (void)
 {
   defsubr (&Sget_internal_run_time);
+#ifdef DARWIN_OS
+  defsubr (&Sdarwin_sandbox_init);
+#endif
 }
diff --git a/test/lisp/darwin-fns-tests.el b/test/lisp/darwin-fns-tests.el
new file mode 100644
index 0000000000..fa0d58ac3d
--- /dev/null
+++ b/test/lisp/darwin-fns-tests.el
@@ -0,0 +1,91 @@
+;;; darwin-fns-tests.el --- tests for darwin-fns.el  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2021  Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published
+;; by the Free Software Foundation, either version 3 of the License,
+;; or (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+(require 'ert)
+
+(defun darwin-fns-tests--run-emacs (expr1 expr2)
+  "Run Emacs in batch mode and evaluate EXPR1 and EXPR2.
+Return (EXIT-STATUS . OUTPUT), where OUTPUT is stderr and stdout."
+  (let ((emacs (expand-file-name invocation-name invocation-directory))
+        (process-environment nil))
+    (with-temp-buffer
+      (let ((res (call-process emacs nil t nil
+                               "--quick" "--batch"
+                               (format "--eval=%S" expr1)
+                               (format "--eval=%S" expr2))))
+        (cons res (buffer-string))))))
+
+(ert-deftest darwin-fns-sandbox ()
+  (skip-unless (eq system-type 'darwin))
+  ;; Test file reading and writing under various sandboxing conditions.
+  (let* ((some-text "abcdef")
+         (new-text "ghijkl")
+         (test-file (file-truename (make-temp-file "test")))
+         (file-dir (file-name-directory test-file)))
+    (unwind-protect
+        (dolist (mode '(read write))
+          (ert-info ((symbol-name mode) :prefix "mode: ")
+            (dolist (sandbox '(allow-all deny-all allow-read))
+              (ert-info ((symbol-name sandbox) :prefix "sandbox: ")
+                ;; Prepare initial file contents.
+                (with-temp-buffer
+                  (insert some-text)
+                  (write-file test-file))
+
+                (let* ((sandbox-form
+                        (pcase-exhaustive sandbox
+                          ('allow-all nil)
+                          ('deny-all '(darwin--sandbox-enter nil))
+                          ('allow-read `(darwin--sandbox-enter
+                                         '(:read-dirs (,file-dir))))))
+                       (action-form
+                        (pcase-exhaustive mode
+                          ('read `(progn (find-file-literally ,test-file)
+                                         (message "OK: %s" (buffer-string))))
+                          ('write `(with-temp-buffer
+                                     (insert ,new-text)
+                                     (write-file ,test-file)))))
+                       (allowed (or (eq sandbox 'allow-all)
+                                    (and (eq sandbox 'allow-read)
+                                         (eq mode 'read))))
+                       (res-out (darwin-fns-tests--run-emacs
+                                 sandbox-form action-form))
+                       (exit-status (car res-out))
+                       (output (cdr res-out))
+                       (file-contents
+                        (with-temp-buffer
+                          (insert-file-contents-literally test-file)
+                          (buffer-string))))
+                  (if allowed
+                      (should (equal exit-status 0))
+                    (should-not (equal exit-status 0)))
+                  (when (eq mode 'read)
+                    (if allowed
+                        (should (equal output (format "OK: %s\n" some-text)))
+                      (should-not (string-search some-text output))))
+                  (should (equal file-contents
+                                 (if (and (eq mode 'write) allowed)
+                                     new-text
+                                   some-text))))))))
+
+      ;; Clean up.
+      (ignore-errors (delete-file test-file)))))
+
+
+(provide 'darwin-fns-tests)
-- 
2.21.1 (Apple Git-122.3)


[-- Attachment #3: 0002-platform-independent-sandbox-interface.patch --]
[-- Type: application/octet-stream, Size: 4934 bytes --]

From aa7780d2a40cf1da60ae236e9468cea9c36a8350 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Fri, 17 Sep 2021 09:30:53 +0200
Subject: [PATCH 2/2] platform-independent sandbox interface

---
 lisp/sandbox.el | 91 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)
 create mode 100644 lisp/sandbox.el

diff --git a/lisp/sandbox.el b/lisp/sandbox.el
new file mode 100644
index 0000000000..589d25615a
--- /dev/null
+++ b/lisp/sandbox.el
@@ -0,0 +1,91 @@
+;;; -*- lexical-binding: t -*-
+
+(require 'cl-lib)
+
+(defconst sandbox-mechanism
+  ;; FIXME: make it a defcustom? What about other systems?
+  (cond ((eq system-type 'darwin) 'darwin)
+        ((eq system-type 'gnu/linux) 'bwrap)))
+
+(defun sandbox-available-p ()
+  "Non-nil if a sandboxing mechanism is available."
+  ;; FIXME: We should check for availability of bwrap etc.
+  (not (null sandbox-mechanism)))
+
+(defun sandbox--program-args (sandbox-spec prog)
+  "Return (PROGRAM . ARGS) for running PROG according to SANDBOX-SPEC."
+  (pcase-exhaustive sandbox-mechanism
+    ('darwin
+     (list prog "--eval"
+           (prin1-to-string `(darwin--sandbox-enter ',sandbox-spec))))
+    ('bwrap
+     ;; FIXME: with seccomp?
+     (let* ((read-dirs (plist-get sandbox-spec :read-dirs))
+            (write-dirs (plist-get sandbox-spec :write-dirs))
+            (exec-dirs (plist-get sandbox-spec :exec-dirs))
+            (ro-dirs (cl-set-difference
+                      (cl-union read-dirs exec-dirs :test #'equal)
+                      write-dirs :test #'equal)))
+       `("bwrap"
+         "--unshare-all"
+         "--dev" "/dev"
+         "--proc" "/proc"
+         "--tmpfs" "/tmp"
+         ,@(mapcan (lambda (dir) (let ((d (expand-file-name dir)))
+                                   (list "--ro-bind" d d)))
+                   ro-dirs)
+         ,@(mapcan (lambda (dir) (let ((d (expand-file-name dir)))
+                                   (list "--bind" d d)))
+                   write-dirs)
+         ,prog)))))
+
+(defun sandbox--emacs-command (sandbox-spec args)
+  "Command and arguments for running Emacs with SANDBOX-SPEC and ARGS."
+  (let* ((emacs (expand-file-name invocation-name invocation-directory))
+         (program-args (sandbox--program-args sandbox-spec emacs)))
+    `(,@program-args "--batch" ,@args)))
+
+(defun sandbox-run-emacs (sandbox-spec destination args)
+  "Run sandboxed Emacs in batch mode, synchronously.
+SANDBOX-SPEC is a sandbox specification plist.  Currently defined key:
+ `:read-dirs'  -- the value is a list of directories that can be read from.
+ `:write-dirs' -- the value is a list of directories that can be written to.
+ `:exec-dirs'  -- the value is a list of directories from which
+                  executables can be run as subprocesses.
+DESTINATION is as in `call-process'.
+ARGS is a list of command-line arguments passed to the sandboxed Emacs.
+Return value is as in `call-process'.
+
+Depending on the platform, the sandbox restrictions do not necessarily
+take effect until Emacs has been initialised and loaded the site and user
+init files.  If that is not desirable, suppress their use by adding the
+corresponding flags (eg \"-Q\") to ARGS."
+  (let ((command (sandbox--emacs-command sandbox-spec args)))
+    (apply #'call-process (car command) nil destination nil (cdr command))))
+
+(defun sandbox-start-emacs (sandbox-spec params args)
+  "Run sandboxed Emacs in batch mode, asynchronously.
+SANDBOX-SPEC is a sandbox specification plist.  Currently defined key:
+ `:read-dirs'  -- the value is a list of directories that can be read from.
+ `:write-dirs' -- the value is a list of directories that can be written to.
+ `:exec-dirs'  -- the value is a list of directories from which
+                  executables can be run as subprocesses.
+ARGS is a list of command-line arguments passed to the sandboxed Emacs.
+PARAMS is a plist of parameters passed to `make-process'.  Do not
+  supply `:command'; it will be overridden by ARGS.
+Return value is as in `make-process'.
+
+Depending on the platform, the sandbox restrictions do not necessarily
+take effect until Emacs has been initialised and loaded the site and user
+init files.  If that is not desirable, suppress their use by adding the
+corresponding flags (eg \"-Q\") to ARGS."
+  (let* ((command (sandbox--emacs-command sandbox-spec args))
+         (params (copy-sequence params))
+         (params (plist-put params :command command)))
+    (unless (plist-member params :name)
+      (setq params (plist-put params :name "emacs")))
+    (unless (plist-member params :connection-type)
+      (setq params (plist-put params :connection-type 'pipe)))
+    (apply #'make-process params)))
+
+(provide 'sandbox)
-- 
2.21.1 (Apple Git-122.3)


  reply	other threads:[~2021-09-17 19:49 UTC|newest]

Thread overview: 102+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-12 18:01 bug#45198: 28.0.50; Sandbox mode Stefan Monnier
2020-12-12 19:48 ` Eli Zaretskii
2020-12-12 21:06   ` Stefan Monnier
2020-12-13  3:29     ` Eli Zaretskii
2020-12-13  4:25       ` Stefan Monnier
2020-12-13 11:14         ` João Távora
2020-12-13 17:07         ` Philipp Stephani
2020-12-13 15:31 ` Mattias Engdegård
2020-12-13 17:09   ` Philipp Stephani
2020-12-13 17:04 ` Philipp Stephani
2020-12-13 17:57   ` Stefan Monnier
2020-12-13 18:13     ` Philipp Stephani
2020-12-13 18:43       ` Stefan Monnier
2020-12-14 11:05         ` Philipp Stephani
2020-12-14 14:44           ` Stefan Monnier
2020-12-14 15:37             ` Philipp Stephani
2020-12-19 22:41             ` Philipp Stephani
2020-12-19 23:16               ` Stefan Monnier
2020-12-20 12:28                 ` Philipp Stephani
2020-12-22 10:57                   ` Philipp Stephani
2020-12-22 14:43                     ` Stefan Monnier
2020-12-19 18:18           ` Philipp Stephani
2021-04-10 17:44             ` Philipp Stephani
2020-12-19 22:22           ` Philipp Stephani
2020-12-20 15:09             ` Eli Zaretskii
2020-12-20 18:14               ` Philipp Stephani
2020-12-20 18:29                 ` Eli Zaretskii
2020-12-20 18:39                   ` Philipp Stephani
2020-12-29 13:50             ` Philipp Stephani
2020-12-29 15:43               ` Eli Zaretskii
2020-12-29 16:05                 ` Philipp Stephani
2020-12-29 17:09                   ` Eli Zaretskii
2020-12-31 15:05                     ` Philipp Stephani
2020-12-31 16:50                       ` Eli Zaretskii
2021-04-10 19:11             ` Philipp Stephani
2020-12-13 18:52       ` Stefan Monnier
2020-12-13 20:13     ` João Távora
2020-12-14 11:12 ` Mattias Engdegård
2020-12-14 13:44   ` Philipp Stephani
2020-12-14 14:48     ` Stefan Monnier
2020-12-14 15:59     ` Mattias Engdegård
2020-12-17 13:08       ` Philipp Stephani
2020-12-17 17:55         ` Mattias Engdegård
2020-12-18 15:21           ` Philipp Stephani
2020-12-18 18:50             ` Mattias Engdegård
2020-12-19 15:08               ` Philipp Stephani
2020-12-19 17:19                 ` Mattias Engdegård
2020-12-19 18:11                   ` Stefan Monnier
2020-12-19 18:46                     ` Mattias Engdegård
2020-12-19 19:48                       ` João Távora
2020-12-19 21:01                       ` Stefan Monnier
2020-12-20 13:15                         ` Mattias Engdegård
2020-12-20 14:02                           ` Stefan Monnier
2020-12-20 14:12                             ` Mattias Engdegård
2020-12-20 15:08                               ` Stefan Monnier
2020-12-22 11:12                   ` Philipp Stephani
2020-12-28  8:23                     ` Stefan Kangas
2020-12-29 13:58                       ` Philipp Stephani
2020-12-30 14:59 ` Mattias Engdegård
2020-12-30 15:36   ` Alan Third
2021-04-17 15:26 ` Mattias Engdegård
2021-04-17 15:44   ` Philipp
2021-04-17 15:57     ` Eli Zaretskii
2021-04-17 16:10       ` Philipp
2021-04-17 16:15         ` Eli Zaretskii
2021-04-17 16:19           ` Eli Zaretskii
2021-04-17 16:20           ` Philipp Stephani
2021-04-17 16:33             ` Eli Zaretskii
2021-04-17 19:14               ` Philipp Stephani
2021-04-17 19:23                 ` Eli Zaretskii
2021-04-17 19:52                   ` Philipp
2021-04-18  6:20                     ` Eli Zaretskii
2021-04-18  9:11                       ` Philipp Stephani
2021-04-18  9:23                         ` Eli Zaretskii
2021-04-17 17:48         ` Mattias Engdegård
2021-04-17 18:21           ` Stefan Monnier
2021-04-17 18:59             ` Mattias Engdegård
2021-04-17 19:42               ` Philipp
2021-04-17 19:57                 ` Alan Third
2021-04-19 15:41                 ` Mattias Engdegård
2021-04-17 19:19           ` Philipp Stephani
2021-04-17 17:22     ` Mattias Engdegård
2021-04-17 17:57       ` Stefan Monnier
2021-04-17 19:21         ` Philipp Stephani
2021-04-17 19:16       ` Philipp Stephani
2021-04-17 16:58   ` Stefan Monnier
2021-04-17 17:14     ` Eli Zaretskii
2021-04-17 17:53       ` Stefan Monnier
2021-04-17 18:15         ` Eli Zaretskii
2021-04-17 18:47           ` Stefan Monnier
2021-04-17 19:14             ` Eli Zaretskii
2021-04-17 20:26               ` Stefan Monnier
2021-04-18  6:24                 ` Eli Zaretskii
2021-04-18 14:25                   ` Stefan Monnier
2021-07-05 19:12                     ` Philipp
2021-09-17 12:13 ` Mattias Engdegård
2021-09-17 13:20   ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-09-17 19:49     ` Mattias Engdegård [this message]
2022-09-11 11:28       ` Lars Ingebrigtsen
2022-09-13 12:37         ` mattiase
2022-09-13 12:53           ` João Távora
2022-09-13 13:02             ` João Távora

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=8355EDD1-FF78-43B1-8F96-4EB3316E8FEB@acm.org \
    --to=mattiase@acm.org \
    --cc=45198@debbugs.gnu.org \
    --cc=alan@idiocy.org \
    --cc=joaotavora@gmail.com \
    --cc=monnier@iro.umontreal.ca \
    --cc=p.stephani2@gmail.com \
    --cc=stefan@marxist.se \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).