Download raw body.
[WIP] landlock for got-portable
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)
[WIP] landlock for got-portable