snac2

Fork of https://codeberg.org/grunfink/snac2
git clone https://git.inz.fi/snac2
Log | Files | Refs | README | LICENSE

commit 75f615905629f64f40363161281d640010153d64
parent f2213021c492e43479b5602e5dee87c4ee04a7c8
Author: shtrophic <christoph@liebender.dev>
Date:   Tue, 12 Nov 2024 21:01:09 +0100

sandboxing port to linux via landlock

Diffstat:
MMakefile | 4+++-
Mdata.c | 39+--------------------------------------
Asandbox.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msnac.h | 2++
4 files changed, 190 insertions(+), 39 deletions(-)

diff --git a/Makefile b/Makefile @@ -4,7 +4,7 @@ CFLAGS?=-g -Wall -Wextra -pedantic all: snac -snac: snac.o main.o data.o http.o httpd.o webfinger.o \ +snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \ activitypub.o html.o utils.o format.o upgrade.o mastoapi.o $(CC) $(CFLAGS) -L/usr/local/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@ @@ -36,6 +36,8 @@ uninstall: activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h \ http_codes.h +sandbox.o: sandbox.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h \ + xs_glob.h xs_set.h xs_time.h xs_regex.h xs_match.h xs_unicode.h snac.h data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ xs_set.h xs_time.h xs_regex.h xs_match.h xs_unicode.h xs_random.h snac.h \ http_codes.h diff --git a/data.c b/data.c @@ -115,44 +115,7 @@ int srv_open(const char *basedir, int auto_upgrade) #define st_mtim st_mtimespec #endif -#ifdef __OpenBSD__ - if (xs_is_true(xs_dict_get(srv_config, "disable_openbsd_security"))) { - srv_debug(1, xs_dup("OpenBSD security disabled by admin")); - } - else { - int smail = !xs_is_true(xs_dict_get(srv_config, "disable_email_notifications")); - const char *address = xs_dict_get(srv_config, "address"); - - srv_debug(1, xs_fmt("Calling unveil()")); - unveil(basedir, "rwc"); - unveil("/tmp", "rwc"); - unveil("/etc/resolv.conf", "r"); - unveil("/etc/hosts", "r"); - unveil("/etc/ssl/openssl.cnf", "r"); - unveil("/etc/ssl/cert.pem", "r"); - unveil("/usr/share/zoneinfo", "r"); - - if (smail) - unveil("/usr/sbin/sendmail", "x"); - - if (*address == '/') - unveil(address, "rwc"); - - unveil(NULL, NULL); - - srv_debug(1, xs_fmt("Calling pledge()")); - - xs *p = xs_str_new("stdio rpath wpath cpath flock inet proc dns fattr"); - - if (smail) - p = xs_str_cat(p, " exec"); - - if (*address == '/') - p = xs_str_cat(p, " unix"); - - pledge(p, NULL); - } -#endif /* __OpenBSD__ */ + sbox_enter(srv_basedir); /* read (and drop) emojis.json, possibly creating it */ xs_free(emojis()); diff --git a/sandbox.c b/sandbox.c @@ -0,0 +1,184 @@ +#include "xs.h" + +#include "snac.h" + +#include <unistd.h> + +#if defined (__linux__) +# define __USE_GNU +# include <linux/landlock.h> +# include <linux/prctl.h> +# include <sys/syscall.h> +# include <sys/prctl.h> +# include <fcntl.h> +# include <arpa/inet.h> +#endif + +void sbox_enter(const char *basedir) +{ + if (xs_is_true(xs_dict_get(srv_config, "disable_openbsd_security"))) { + srv_log(xs_dup("disable_openbsd_security is deprecated. Use disable_sandbox instead.")); + return; + } + if (xs_is_true(xs_dict_get(srv_config, "disable_sandbox"))) { + srv_debug(0, xs_dup("Sandbox disabled by admin")); + return; + } + + const char *address = xs_dict_get(srv_config, "address"); + +#if defined (__OpenBSD__) + int smail = !xs_is_true(xs_dict_get(srv_config, "disable_email_notifications")); + + srv_debug(1, xs_fmt("Calling unveil()")); + unveil(basedir, "rwc"); + unveil("/tmp", "rwc"); + unveil("/etc/resolv.conf", "r"); + unveil("/etc/hosts", "r"); + unveil("/etc/ssl/openssl.cnf", "r"); + unveil("/etc/ssl/cert.pem", "r"); + unveil("/usr/share/zoneinfo", "r"); + + if (smail) + unveil("/usr/sbin/sendmail", "x"); + + if (*address == '/') + unveil(address, "rwc"); + + unveil(NULL, NULL); + + srv_debug(1, xs_fmt("Calling pledge()")); + + xs *p = xs_str_new("stdio rpath wpath cpath flock inet proc dns fattr"); + + if (smail) + p = xs_str_cat(p, " exec"); + + if (*address == '/') + p = xs_str_cat(p, " unix"); + + pledge(p, NULL); + + xs_free(p); +#elif defined (__linux__) + int error, ruleset_fd, abi; + struct landlock_ruleset_attr rules = {0}; + struct landlock_path_beneath_attr path = {0}; + struct landlock_net_port_attr net = {0}; + + rules.handled_access_fs = + LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_READ_FILE | + 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 | + LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV; + rules.handled_access_net = + LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP; + + abi = syscall(SYS_landlock_create_ruleset, NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + switch (abi) { + case -1: + srv_debug(0, xs_dup("Kernel without landlock support")); + return; + case 1: + rules.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; + __attribute__((fallthrough)); + case 2: + rules.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; + __attribute__((fallthrough)); + case 3: + rules.handled_access_net &= ~(LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP); + __attribute__((fallthrough)); + case 4: + rules.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; + } + srv_debug(1, xs_fmt("lanlock abi: %d", abi)); + + ruleset_fd = syscall(SYS_landlock_create_ruleset, &rules, sizeof(struct landlock_ruleset_attr), 0); + if (ruleset_fd == -1) { + srv_debug(0, xs_fmt("landlock_create_ruleset failed: %s", strerror(errno))); + return; + } + +#define LL_R LANDLOCK_ACCESS_FS_READ_FILE +#define LL_X LANDLOCK_ACCESS_FS_EXECUTE +#define LL_RWC (LL_R | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_TRUNCATE) +#define LL_UNX (LL_R | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_SOCK) +#define LL_CON LANDLOCK_ACCESS_NET_CONNECT_TCP +#define LL_BND LANDLOCK_ACCESS_NET_BIND_TCP + +#define LANDLOCK_PATH(p, r) do {\ + path.allowed_access = r;\ + if (abi < 3)\ + path.allowed_access &= ~LANDLOCK_ACCESS_FS_TRUNCATE;\ + path.parent_fd = open(p, O_PATH | O_CLOEXEC);\ + if (path.parent_fd == -1) {\ + srv_debug(2, xs_fmt("open %s: %s", p, strerror(errno)));\ + goto close;\ + }\ + error = syscall(SYS_landlock_add_rule, ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path, 0); \ + if (error) {\ + srv_debug(0, xs_fmt("LANDLOCK_PATH(%s): %s", p, strerror(errno)));\ + goto close;\ + }\ +} while (0) + +#define LANDLOCK_PORT(p, r) do {\ + uint16_t _p = p;\ + net.port = _p;\ + net.allowed_access = r;\ + error = syscall(SYS_landlock_add_rule, ruleset_fd, LANDLOCK_RULE_NET_PORT, &net, 0);\ + if (error) {\ + srv_debug(0, xs_fmt("LANDLOCK_PORT(%d): %s", _p, strerror(errno)));\ + goto close;\ + }\ +} while (0) + + LANDLOCK_PATH(basedir, LL_RWC); + LANDLOCK_PATH("/tmp", LL_RWC); + LANDLOCK_PATH("/dev/shm", LL_RWC); + LANDLOCK_PATH("/etc/resolv.conf", LL_R ); + LANDLOCK_PATH("/etc/hosts", LL_R ); + LANDLOCK_PATH("/etc/ssl/openssl.cnf", LL_R ); + LANDLOCK_PATH("/etc/ssl/cert.pem", LL_R ); + LANDLOCK_PATH("/usr/share/zoneinfo", LL_R ); + + if (*address == '/') + LANDLOCK_PATH(address, LL_UNX); + + if (abi > 3) { + if (*address != '/') { + LANDLOCK_PORT( + (uint16_t)xs_number_get(xs_dict_get(srv_config, "port")), LL_BND); + } + + LANDLOCK_PORT(80, LL_CON); + LANDLOCK_PORT(443, LL_CON); + } + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + srv_debug(0, xs_fmt("prctl SET_NO_NEW_PRIVS: %s", strerror(errno))); + goto close; + } + + if (syscall(SYS_landlock_restrict_self, ruleset_fd, 0)) + srv_debug(0, xs_fmt("landlock_restrict_self: %s", strerror(errno))); + + srv_log(xs_dup("landlocked")); + +close: + close(ruleset_fd); + +#endif +} diff --git a/snac.h b/snac.h @@ -75,6 +75,8 @@ void snac_log(snac *user, xs_str *str); int srv_open(const char *basedir, int auto_upgrade); void srv_free(void); +void sbox_enter(const char *basedir); + int user_open(snac *snac, const char *uid); void user_free(snac *snac); xs_list *user_list(void);