"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
[got-portable] landlock support, second try
To:
gameoftrees@openbsd.org
Date:
Fri, 28 Jan 2022 23:19:38 +0100

Download raw body.

Thread
[ I've sent this mail some days ago, but since I haven't got the usual
copy mailed back -but no smtp errors either- I'm trying to post it
again.  Apologies if it was actually delivered ]

Hello,

TL;DR the changes from the previous diff are minimal:

 * dropped the unveil(2) emulation
 * fixed some comments
 * moved the #ifdef HAVE_LINUX_LANDLOCK_H inside PROFILE, just like
   pledge(2)

regress passes \o/

                                  ***

recap of the previous episodes: landlock is a new-ish set of linux APIs
which are conceptually similar to unveil(2), albeit with various
differences in practice.  In the previous attempt, other then removing
access to the filesystem for the libexec helpers, I tried to emulate
(with decent dose success if I can say so) unveil(2) on top of landlock:
the problem was that the landlock ruleset persist across execve(2) so,
in order to run ssh(1) for got send/fetch/clone we would have needed to
""unveil"" ~/.ssh too.  no no no.

I've played with the idea of a got-dial libexec helper, but I really
disliked the result (and also accidentally deleted my worktree) so...

In the end, locking down the libexec helpers is already a noticeable
step forward since they're the biggest attack surface.

As I mentioned before, the regress suite passes (where "passes" mean
that it fails in the same way it does without the patch.)  Here's the
output of `make regress-cmdline' _without_ the landlock patch

	https://tmp.omarpolo.com/test-orig.log

and _with_ the landlock patch

	https://tmp.omarpolo.com/test.log

and a handy diff of the two:

	https://tmp.omarpolo.com/test.diff

The tests were run on:

[op@fedora got-portable]$ uname -a
Linux fedora 5.15.14-200.fc35.x86_64 #1 SMP Tue Jan 11 16:49:27 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

with a locally built libmd (the redhat side of linux doesn't seems to
package it.)

                                  ***

commit 544fe460c79c49a9cc59b78b51c3114336313cb9 (linux)
from: Omar Polo <op@openbsd.org>
date: Tue Jan 25 17:16:37 2022 UTC
 
 add landlock support on linux
 
 landlock is a new set of linux APIs that is conceptually similar to
 unveil(2): the idea is to restrict what a process can do on a
 specified part of the filesystem.  There are some differences in the
 behaviour: the major one being that the landlock ruleset is inherited
 across execve(2).
 
 This "just" restricts the libexec helpers by completely revoking ANY
 filesystem access; after all they are the biggest attack surface.  got
 send/fetch/clone *may* end up spawning ssh(1), so at the moment is not
 possible to landlock the main process.
 
diff 4f88c9e434c3e21dfc79ed9851383863592e05b2 0d6c8d0f0a5bdabf35195adb49d8c33ac8937489
blob - 958a3d67dfe92ddf1caf9fd8e3586f887a36001b
blob + 0b90a45a21dee8de83282118e67277aef5bd3370
--- compat/Makefile.am
+++ compat/Makefile.am
@@ -31,6 +31,10 @@ else
 libopenbsd_compat_a_SOURCES += uuid.c
 endif
 
+if HAVE_LINUX_LANDLOCK
+libopenbsd_compat_a_SOURCES += landlock.c
+endif
+
 EXTRA_DIST = \
 	$(top_srcdir)/include/got_compat.h \
 	imsg.h \
blob - /dev/null
blob + f323e831481a7d6ee233521aa21f522a5eacaabe (mode 644)
--- /dev/null
+++ compat/landlock.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/landlock.h>
+#include <linux/prctl.h>
+
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "got_compat.h"
+
+/*
+ * What's the deal with landlock?  While distro with linux >= 5.13
+ * have the struct declarations, libc wrappers are missing.  The
+ * sample landlock code provided by the authors includes these "shims"
+ * in their example for the landlock API until libc provides them.
+ */
+
+#ifndef landlock_create_ruleset
+static inline int
+landlock_create_ruleset(const struct landlock_ruleset_attr *attr, size_t size,
+    __u32 flags)
+{
+	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
+}
+#endif
+
+#ifndef landlock_add_rule
+static inline int
+landlock_add_rule(int ruleset_fd, enum landlock_rule_type type,
+    const void *attr, __u32 flags)
+{
+	return syscall(__NR_landlock_add_rule, ruleset_fd, type, attr, flags);
+}
+#endif
+
+#ifndef landlock_restrict_self
+static inline int
+landlock_restrict_self(int ruleset_fd, __u32 flags)
+{
+	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
+}
+#endif
+
+static int landlock_fd = -1;
+
+static int
+open_landlock(void)
+{
+	struct landlock_ruleset_attr rattr = {
+		.handled_access_fs =	LANDLOCK_ACCESS_FS_EXECUTE	|
+					LANDLOCK_ACCESS_FS_WRITE_FILE	|
+					LANDLOCK_ACCESS_FS_READ_FILE	|
+					LANDLOCK_ACCESS_FS_READ_DIR	|
+					LANDLOCK_ACCESS_FS_REMOVE_DIR	|
+					LANDLOCK_ACCESS_FS_REMOVE_FILE	|
+					LANDLOCK_ACCESS_FS_MAKE_CHAR	|
+					LANDLOCK_ACCESS_FS_MAKE_DIR	|
+					LANDLOCK_ACCESS_FS_MAKE_REG	|
+					LANDLOCK_ACCESS_FS_MAKE_SOCK	|
+					LANDLOCK_ACCESS_FS_MAKE_FIFO	|
+					LANDLOCK_ACCESS_FS_MAKE_BLOCK	|
+					LANDLOCK_ACCESS_FS_MAKE_SYM,
+	};
+
+	return landlock_create_ruleset(&rattr, sizeof(rattr), 0);
+}
+
+static int
+landlock_apply(void)
+{
+	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
+		return -1;
+
+	if (landlock_restrict_self(landlock_fd, 0))
+		return -1;
+
+	close(landlock_fd);
+	landlock_fd = -1;
+	return 0;
+}
+
+/*
+ * Completely revoke fs access for the current process.
+ */
+int
+landlock_no_fs(void)
+{
+	if ((landlock_fd = open_landlock()) == -1)
+		return -1;
+
+	return landlock_apply();
+}
blob - 43a5990bf98d65ccc952753e2357ac3c76966c71
blob + f285b19bd9d1127e57c16e5cb7b19ecd0f36b028
--- configure.ac
+++ configure.ac
@@ -42,6 +42,7 @@ AC_CHECK_HEADERS([ \
 	fcntl.h \
 	langinfo.h \
 	limits.h \
+	linux/landlock.h \
 	locale.h \
 	netdb.h \
 	netinet/in.h \
@@ -163,6 +164,16 @@ AC_SUBST(PLATFORM)
 AM_CONDITIONAL([HOST_FREEBSD], [test "$PLATFORM" = "freebsd"])
 AM_CONDITIONAL([HOST_LINUX], [test "$PLATFORM" = "linux"])
 
+# Landlock detection.
+AC_MSG_CHECKING([for landlock])
+AM_CONDITIONAL([HAVE_LINUX_LANDLOCK],
+    [test "x$ac_cv_header_linux_landlock_h" = "xyes"])
+if test "x$ac_cv_header_linux_landlock_h" = "xyes"; then
+	AC_MSG_RESULT(yes)
+else
+	AC_MSG_RESULT(no)
+fi
+
 # Clang sanitizers wrap reallocarray even if it isn't available on the target
 # system. When compiled it always returns NULL and crashes the program. To
 # detect this we need a more complicated test.
blob - c07416a808a6799841c0c9978d004659680beda2
blob + a944748ff0e57eca39bbaf958e4279cdfd6d50cc
--- include/got_compat.h
+++ include/got_compat.h
@@ -43,6 +43,10 @@
 #define unveil(s, p) (0)
 #endif
 
+#ifdef HAVE_LINUX_LANDLOCK_H
+int	landlock_no_fs(void);
+#endif
+
 #ifndef INFTIM
 #define INFTIM -1
 #endif
blob - 65c075409a0f6850f37b585385b6429b80a143ed
blob + 5d49a76cce24797690c81eb2df2097c70f190a48
--- libexec/got-fetch-pack/got-fetch-pack.c
+++ libexec/got-fetch-pack/got-fetch-pack.c
@@ -809,7 +809,15 @@ main(int argc, char **argv)
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 	err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
 	if (err) {
 		if (err->code == GOT_ERR_PRIVSEP_PIPE)
blob - b8572cfe5b35acf5d04456dc811ef5e1cf3b918d
blob + ed5379d6b64f8dd9f5fdb614e41bfeb866f4ed25
--- libexec/got-index-pack/got-index-pack.c
+++ libexec/got-index-pack/got-index-pack.c
@@ -1008,7 +1008,15 @@ main(int argc, char **argv)
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 	err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
 	if (err)
 		goto done;
blob - 24a9911b4e2319200c86e0be0d525a3980763d80
blob + f9bebfa57082a3fc3cb4d6139049a7ac13175453
--- libexec/got-read-blob/got-read-blob.c
+++ libexec/got-read-blob/got-read-blob.c
@@ -65,7 +65,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	for (;;) {
 		struct imsg imsg, imsg_outfd;
blob - f324bf809bdb1dbc68030ee2676b88f4ec11dc4e
blob + c7fd8d83bdb9a2ebb61e97f6cea0522cb877ce06
--- libexec/got-read-commit/got-read-commit.c
+++ libexec/got-read-commit/got-read-commit.c
@@ -119,7 +119,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	for (;;) {
 		struct imsg imsg;
blob - 9820c8bd9e58d215598df363fdb6e25fbd81323d
blob + dd8c66482918064be78f91b05ed28afbb23b2471
--- libexec/got-read-gitconfig/got-read-gitconfig.c
+++ libexec/got-read-gitconfig/got-read-gitconfig.c
@@ -341,7 +341,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	for (;;) {
 		struct imsg imsg;
blob - 69f6999b8df42a9f230c521287de6e5aadebd128
blob + b859ce76aec0ea4584ad8d3042fcca3cc959ac48
--- libexec/got-read-gotconfig/got-read-gotconfig.c
+++ libexec/got-read-gotconfig/got-read-gotconfig.c
@@ -498,7 +498,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	if (argc > 1)
 		filename = argv[1];
blob - 3d9bc64ad66ca532a2c58b8d50e3d4ab51abb4db
blob + a0cb6ac36a0989faf5364b55bd6c435fc155300a
--- libexec/got-read-object/got-read-object.c
+++ libexec/got-read-object/got-read-object.c
@@ -140,7 +140,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	for (;;) {
 		if (sigint_received) {
blob - 760a54783d11db6cc8fed461bc956854e6b5f2ed
blob + 00bb9ba4c8bdc10dcff315fc457391c7e0a1d090
--- libexec/got-read-pack/got-read-pack.c
+++ libexec/got-read-pack/got-read-pack.c
@@ -1031,7 +1031,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	err = receive_packidx(&packidx, &ibuf);
 	if (err) {
blob - 870e8ca6c8d4439248bc960e5d6797861de4de07
blob + 9de8f7818ae3436fdc90ca108a1ae0f0bc82544f
--- libexec/got-read-tag/got-read-tag.c
+++ libexec/got-read-tag/got-read-tag.c
@@ -114,7 +114,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	for (;;) {
 		struct imsg imsg;
blob - 35323190d763a2a21c96e0b0f6c3d19f6687f007
blob + d69822de57ceb1ae9f3ccbbeb2fa55115d3d6eea
--- libexec/got-read-tree/got-read-tree.c
+++ libexec/got-read-tree/got-read-tree.c
@@ -113,7 +113,15 @@ main(int argc, char *argv[])
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 
 	for (;;) {
 		struct imsg imsg;
blob - 5a7564d43fbe801786356a9a6036bb9372e66c68
blob + ad4a3b76d7ea4c0d32c25016615561b8f26a992e
--- libexec/got-send-pack/got-send-pack.c
+++ libexec/got-send-pack/got-send-pack.c
@@ -599,7 +599,15 @@ main(int argc, char **argv)
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#ifdef HAVE_LINUX_LANDLOCK_H
+	/* revoke fs access */
+	if (landlock_no_fs() == -1) {
+		err = got_error_from_errno("landlock_no_fs");
+		got_privsep_send_error(&ibuf, err);
+		return 1;
+	}
 #endif
+#endif
 	if ((err = got_privsep_recv_imsg(&imsg, &ibuf, 0)) != 0) {
 		if (err->code == GOT_ERR_PRIVSEP_PIPE)
 			err = NULL;