unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: "Mattias Engdegård" <mattiase@acm.org>
To: Philipp <p.stephani2@gmail.com>
Cc: alan@idiocy.org, 45198@debbugs.gnu.org, stefan@marxist.se,
	"João Távora" <joaotavora@gmail.com>,
	"Stefan Monnier" <monnier@iro.umontreal.ca>
Subject: bug#45198: 28.0.50; Sandbox mode
Date: Mon, 19 Apr 2021 17:41:58 +0200	[thread overview]
Message-ID: <F61CF0B6-6AB0-415C-BAD4-54273D0DD476@acm.org> (raw)
In-Reply-To: <E967667D-698C-47FE-9E95-429C30C83907@gmail.com>

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


17 apr. 2021 kl. 21.42 skrev Philipp <p.stephani2@gmail.com>:

>> --- a/lisp/subr.el
> 
> Should this maybe be in a platform-specific file such as ns-fns.el (like dos-fns.el, w32-fns.el)?

Yes, and thank you for noticing. Now moved.

> I don't think we use the "darwin-" prefix anywhere else in Emacs.  Maybe use a "ns-" prefix.

As Alan correctly noted this has nothing with NS to do per se, so I'm staying with darwin- for now.

> Also I think such internal functions commonly have an "internal" piece somewhere in their name, e.g. "ns-enter-sandbox-internal".

Maybe, but there is already a platform-specific prefix and a clear note in the doc string that it's internal. Doesn't that suffice?

>> +    (darwin-sandbox-init
> 
> Can you also refer to the documents listed below, so that readers aren't wondering what this syntax means?

Thank you but that sounds unlikely since the PROFILE argument is described as SBPL in the function doc string.

>> +     (concat "(version 1)\n"
> 
> Since this uses Lisp syntax, maybe use (prin1-to-string `(...))?

I'd rather not, since it's not exactly Emacs Lisp syntax. I'd like to avoiding conflating the two as far as possible. However...

>> +                          (format "(allow file-read* (subpath %S))\n" dir))
> 
> Are you sure that the string quoting syntaxes are compatible?

It had me concerned when I wrote it too but it didn't seem possible to cause a false positive that way -- false negatives (access denial) at worst. In particular, names containing backslashes or double quotes work correctly.

>> +       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.
> 
> I'd also refer to the Chromium document here, otherwise C-h f for this function won't be very useful.

I wouldn't worry -- an Emacs developer who doesn't already know it is more likely to duckduckgo "macos sandbox profile language" and get an up-to-date reference.

>> +  if (sandbox_init_with_parameters (SSDATA (profile), 0, NULL, &err) != 0)
> 
> If you're using SSDATA, better check that the string doesn't contain embedded null bytes.

There is really no way that that could ever be a problem but I added a check just in case.

> Also does this need to be encoded somehow?  What happens if the string contains non-Unicode characters like raw bytes?

Then there will be false negatives (permissions not granted) or syntax errors. This is just a system call wrapper; the caller is responsible for using reasonable arguments.

>> +    error ("sandbox error: %s", err);
> 
> This looks like an error that clients could handle, so I'd signal a specific error symbol here.

That error just indicates a programming mistake. Nobody is going to handle it.

>> diff --git a/test/src/emacs-tests.el b/test/src/emacs-tests.el
> 
> This should probably be in subr-tests.el since it tests a function in subr.el.

Right again, moved to a new file.

> I like tests that are somewhat repetitive but more decoupled and easier to read better than tests with factored-out assertions.

There is merit to such a style but the more important concern in this case is to avoid false positives and negatives, and make the tests robust in that regard.

It is very easy to make a mistake that renders a test meaningless, especially when testing a mechanism that does not alter the value of a computation but merely allows it to proceed or causes it to fail. The test has now been rewritten in a kind of oracular-combinatorial style which is effective in these situations. Doing so also improved coverage.

Thank you very much for your review and comments! I'd like to push the resulting patch but perhaps it and the rest of the sandbox development should go into a separate branch?


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

From c5006dc5260f547ff132fd42c580ea0e0fb227a7 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] 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 list of directories under which files can be
read.  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            | 40 ++++++++++++++++
 lisp/loadup.el                |  2 +
 src/sysdep.c                  | 34 +++++++++++++
 test/lisp/darwin-fns-tests.el | 90 +++++++++++++++++++++++++++++++++++
 4 files changed, 166 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..e45aaa0c4e
--- /dev/null
+++ b/lisp/darwin-fns.el
@@ -0,0 +1,40 @@
+;;; 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 (dirs)
+  "Enter a sandbox only permitting reading files under DIRS.
+DIRS is a list of directory names.  Most other operations such as
+writing files and network access are disallowed.
+Existing open descriptors can still be used freely.
+
+This is not a supported interface and is for internal use only."
+  (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))
+                      dirs ""))))
+
+(provide 'darwin-fns)
+
+;;; darwin-fns.el ends here
diff --git a/lisp/loadup.el b/lisp/loadup.el
index d6cfcd6fc8..a6a7251d4b 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -324,6 +324,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 d940acc4e0..4ea1f0050a 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -4286,8 +4286,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..1e426407a1
--- /dev/null
+++ b/test/lisp/darwin-fns-tests.el
@@ -0,0 +1,90 @@
+;;; 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 '(,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)


  parent reply	other threads:[~2021-04-19 15:41 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 [this message]
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
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=F61CF0B6-6AB0-415C-BAD4-54273D0DD476@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).