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

From:
Omar Polo <op@omarpolo.com>
Subject:
Re: [WIP] landlock for got-portable
To:
Thomas Adam <thomas@xteddy.org>
Cc:
gameoftrees@openbsd.org
Date:
Fri, 24 Sep 2021 23:16:36 +0200

Download raw body.

Thread
Thomas Adam <thomas@xteddy.org> writes:

> On Thu, Sep 23, 2021 at 08:42:21AM +0200, Omar Polo wrote:
>> Hello,
>
> Hi,
>
>>   1.  landlock persist across execve(2)
>> 
>>       This is a major pain because, even if stsp@ told me that unveil
>>       usage in got already takes this assumption into account, ld.so (or
>>       whom it may concern) in the executed binary can't open the shared
>>       libraries.  I'm addressing this with a allowing "rx" on "/lib64",
>>       but this works only on the linux machine I'm using, I don't know
>>       if it's possible *at runtime* too obtain the path of the linked
>>       libraries and add that, or do some other kinds of "magic" in this
>>       regard.
>> 
>>       This is a (the only?) major problem that I still have to sort out.
>
> Could rpath help out here?

I haven't find out how to obtain the rpath programmatically.  It would
surely fix the issue thought.  (I'm manually adding /lib64 *just for
testing* and it works.)

> [...]
>
> I think I'd prefer to see something like this, albeit I suspect we will need
> to:
>
> 1.  Remove the #define unveil(s, p) 0 and allow calls to unveil() be a trigger
> for calling landlock_*() functions.

I've improved this in the attached patch.  There's a (disabled)
landlock_unveil and landlock_no_fs.

landlock_unveil is temporarly disabled.  The idea is to enable it (by
decommenting the #define in got_compat.h) for got.c once I understand
how to make send/fetch works under landlock.  I'm going to try adding a
new libexec helper `got-dial' as per stsp@ suggestion on IRC and see how
it goes.

landlock_no_fs prevents the process from doing ANYTHING to the
filesystem.  Since all the libexec helpers run under pledge("stdio
recvfd"), it's possible to use landlock_no_fs there without other
modifications.

> 2.  Provide host-checking so that if we have a landlock-enabled system, we can
> do something like this in compat/Makefile.am:
>
>     libopenbsd_compat_a_SOURCES += compat/osdep-@PLATFORM@.c

I think I've addressed this too.  I've added compat/landlock.c that's
conditionally linked in the build if HAVE_LINUX_LANDLOCK.

> I have the detection for this on the 'freebsd' branch in got-portable.git if
> you wanted to take a look there.  It currently compiles fine under both
> FreeBSD and Linux.
>
> I'll look at the test suite later on.
>
> Kindly,
> Thomas

Thanks

Omar Polo


diff --git a/compat/Makefile.am b/compat/Makefile.am
index 29fcf763..e0e93c36 100644
--- a/compat/Makefile.am
+++ b/compat/Makefile.am
@@ -34,6 +34,10 @@ libopenbsd_compat_a_SOURCES =  \
 	queue.h \
 	tree.h
 
+if HAVE_LINUX_LANDLOCK
+libopenbsd_compat_a_SOURCES += landlock.c
+endif
+
 EXTRA_DIST = \
 	$(top_srcdir)/include/got_compat.h \
 	imsg.h \
diff --git a/compat/landlock.c b/compat/landlock.c
new file mode 100644
index 00000000..5d140e52
--- /dev/null
+++ b/compat/landlock.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * 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.
+ */
+
+/*
+ * This an implementation of an OpenBSD' unveil(2) compatible API on
+ * top of Linux' landlock.
+ */
+
+#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;
+}
+
+static int
+parse_permissions(const char *permission)
+{
+	int perm = 0;
+
+	for (; *permission; ++permission) {
+		switch (*permission) {
+		case 'r':
+			perm |= LANDLOCK_ACCESS_FS_READ_FILE;
+			perm |= LANDLOCK_ACCESS_FS_READ_DIR;
+			break;
+		case 'w':
+			perm |= LANDLOCK_ACCESS_FS_WRITE_FILE;
+			break;
+		case 'x':
+			perm |= LANDLOCK_ACCESS_FS_EXECUTE;
+			break;
+		case 'c':
+			perm |= LANDLOCK_ACCESS_FS_REMOVE_DIR;
+			perm |= LANDLOCK_ACCESS_FS_REMOVE_FILE;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_CHAR;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_DIR;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_REG;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_SOCK;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_FIFO;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_BLOCK;
+			perm |= LANDLOCK_ACCESS_FS_MAKE_SYM;
+			break;
+		default:
+			return -1;
+		}
+	}
+
+	return perm;
+}
+
+static int
+landlock_unveil_path(const char *path, int permissions)
+{
+	struct landlock_path_beneath_attr pb;
+	struct stat sb;
+	int fd, err, saved_errno;
+	char fpath[PATH_MAX];
+
+	pb.allowed_access = permissions;
+	if ((pb.parent_fd = open(path, O_PATH)) == -1)
+		return -1;
+
+	if (fstat(pb.parent_fd, &sb) == -1)
+		return -1;
+
+	if (!S_ISDIR(sb.st_mode)) {
+		close(pb.parent_fd);
+
+		if (strlcpy(fpath, path, sizeof(fpath)) >= sizeof(fpath)) {
+			errno = ENAMETOOLONG;
+			return -1;
+		}
+
+		permissions |= LANDLOCK_ACCESS_FS_READ_FILE;
+		permissions |= LANDLOCK_ACCESS_FS_READ_DIR;
+		return landlock_unveil_path(dirname(fpath), permissions);
+	}
+
+	err = landlock_add_rule(landlock_fd, LANDLOCK_RULE_PATH_BENEATH,
+	    &pb, 0);
+	saved_errno = errno;
+	close(pb.parent_fd);
+	errno = saved_errno;
+	return err ? -1 : 0;
+}
+
+int
+landlock_unveil(const char *path, const char *permissions)
+{
+	int perms;
+
+	if (landlock_fd == -1) {
+		if ((landlock_fd = open_landlock()) == -1)
+			return -1;
+
+		/* XXX: use rpath on the current executable */
+		if (landlock_unveil("/lib64", "rx") == -1)
+			return -1;
+	}
+
+	if (path == NULL && permissions == NULL)
+		return landlock_apply();
+
+	if (path == NULL ||
+	    permissions == NULL ||
+	    (perms = parse_permissions(permissions)) == -1) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	return landlock_unveil_path(path, perms);
+}
+
+int
+landlock_no_fs(void)
+{
+	if ((landlock_fd = open_landlock()) == -1)
+		return -1;
+
+	return landlock_apply();
+}
diff --git a/configure.ac b/configure.ac
index cdd6e159..f87086fc 100644
--- a/configure.ac
+++ b/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 \
@@ -60,6 +61,8 @@ AC_CHECK_HEADERS([ \
 	wchar.h \
 ])
 
+AM_CONDITIONAL([HAVE_LINUX_LANDLOCK], [test "$ac_cv_header_linux_landlock_h" = "yes"])
+
 # Checks for typ edefs, structures, and compiler characteristics.
 AC_CHECK_HEADER_STDBOOL
 AC_C_INLINE
diff --git a/include/got_compat.h b/include/got_compat.h
index 6270fd4e..d61836b7 100644
--- a/include/got_compat.h
+++ b/include/got_compat.h
@@ -38,6 +38,13 @@
 #define unveil(s, p) (0)
 #endif
 
+#ifdef HAVE_LINUX_LANDLOCK_H
+int	landlock_no_fs(void);
+int	landlock_unveil(const char *, const char *);
+/* #undef unveil */
+/* #define unveil(s, p) landlock_unveil((s), (p)) */
+#endif
+
 #ifndef INFTIM
 #define INFTIM -1
 #endif
diff --git a/libexec/got-fetch-pack/got-fetch-pack.c b/libexec/got-fetch-pack/got-fetch-pack.c
index 2b84c4fa..8060fda1 100644
--- a/libexec/got-fetch-pack/got-fetch-pack.c
+++ b/libexec/got-fetch-pack/got-fetch-pack.c
@@ -810,6 +810,14 @@ main(int argc, char **argv)
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#endif
+#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
 	err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
 	if (err) {
diff --git a/libexec/got-index-pack/got-index-pack.c b/libexec/got-index-pack/got-index-pack.c
index 886a63dd..9733318d 100644
--- a/libexec/got-index-pack/got-index-pack.c
+++ b/libexec/got-index-pack/got-index-pack.c
@@ -1007,6 +1007,14 @@ main(int argc, char **argv)
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#endif
+#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
 	err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
 	if (err)
diff --git a/libexec/got-read-blob/got-read-blob.c b/libexec/got-read-blob/got-read-blob.c
index 35d6fe9a..27c9e246 100644
--- a/libexec/got-read-blob/got-read-blob.c
+++ b/libexec/got-read-blob/got-read-blob.c
@@ -67,6 +67,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	for (;;) {
 		struct imsg imsg, imsg_outfd;
diff --git a/libexec/got-read-commit/got-read-commit.c b/libexec/got-read-commit/got-read-commit.c
index 63723fc1..91cbde05 100644
--- a/libexec/got-read-commit/got-read-commit.c
+++ b/libexec/got-read-commit/got-read-commit.c
@@ -121,6 +121,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	for (;;) {
 		struct imsg imsg;
diff --git a/libexec/got-read-gitconfig/got-read-gitconfig.c b/libexec/got-read-gitconfig/got-read-gitconfig.c
index e295f14e..803b4430 100644
--- a/libexec/got-read-gitconfig/got-read-gitconfig.c
+++ b/libexec/got-read-gitconfig/got-read-gitconfig.c
@@ -343,6 +343,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	for (;;) {
 		struct imsg imsg;
diff --git a/libexec/got-read-gotconfig/got-read-gotconfig.c b/libexec/got-read-gotconfig/got-read-gotconfig.c
index 39e02e68..0df71048 100644
--- a/libexec/got-read-gotconfig/got-read-gotconfig.c
+++ b/libexec/got-read-gotconfig/got-read-gotconfig.c
@@ -500,6 +500,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	if (argc > 1)
 		filename = argv[1];
diff --git a/libexec/got-read-object/got-read-object.c b/libexec/got-read-object/got-read-object.c
index a5909567..c1da411f 100644
--- a/libexec/got-read-object/got-read-object.c
+++ b/libexec/got-read-object/got-read-object.c
@@ -142,6 +142,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	for (;;) {
 		if (sigint_received) {
diff --git a/libexec/got-read-pack/got-read-pack.c b/libexec/got-read-pack/got-read-pack.c
index ead69bd5..a857787c 100644
--- a/libexec/got-read-pack/got-read-pack.c
+++ b/libexec/got-read-pack/got-read-pack.c
@@ -1021,6 +1021,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	err = receive_packidx(&packidx, &ibuf);
 	if (err) {
diff --git a/libexec/got-read-tag/got-read-tag.c b/libexec/got-read-tag/got-read-tag.c
index f2c0102b..9d858d3a 100644
--- a/libexec/got-read-tag/got-read-tag.c
+++ b/libexec/got-read-tag/got-read-tag.c
@@ -116,6 +116,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	for (;;) {
 		struct imsg imsg;
diff --git a/libexec/got-read-tree/got-read-tree.c b/libexec/got-read-tree/got-read-tree.c
index fad33d19..70560fe8 100644
--- a/libexec/got-read-tree/got-read-tree.c
+++ b/libexec/got-read-tree/got-read-tree.c
@@ -115,6 +115,14 @@ main(int argc, char *argv[])
 		return 1;
 	}
 #endif
+#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
 
 	for (;;) {
 		struct imsg imsg;
diff --git a/libexec/got-send-pack/got-send-pack.c b/libexec/got-send-pack/got-send-pack.c
index 8096c808..956ad9f8 100644
--- a/libexec/got-send-pack/got-send-pack.c
+++ b/libexec/got-send-pack/got-send-pack.c
@@ -599,6 +599,14 @@ main(int argc, char **argv)
 		got_privsep_send_error(&ibuf, err);
 		return 1;
 	}
+#endif
+#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
 	if ((err = got_privsep_recv_imsg(&imsg, &ibuf, 0)) != 0) {
 		if (err->code == GOT_ERR_PRIVSEP_PIPE)