diff --git a/README b/README index 607ce1bee8f..c9d44d9ed66 100644 --- a/README +++ b/README @@ -200,7 +200,8 @@ avoidr (https://github.com/avoidr) - fixed mpv profile - various other fixes Азалия Смарагдова/ChrysoliteAzalea (https://github.com/ChrysoliteAzalea) - - add support for custom AppArmor profiles (--apparmor=) + - add support for custom AppArmor profiles (--apparmor=) + - add Landlock support backspac (https://github.com/backspac) - firecfg fixes - add steam-runtime alias diff --git a/config.mk.in b/config.mk.in index c76ca1a981f..d50c7d2f5a3 100644 --- a/config.mk.in +++ b/config.mk.in @@ -38,6 +38,7 @@ HAVE_FIRETUNNEL=@HAVE_FIRETUNNEL@ HAVE_FORCE_NONEWPRIVS=@HAVE_FORCE_NONEWPRIVS@ HAVE_GLOBALCFG=@HAVE_GLOBALCFG@ HAVE_IDS=@HAVE_IDS@ +HAVE_LANDLOCK=@HAVE_LANDLOCK@ HAVE_LTS=@HAVE_LTS@ HAVE_NETWORK=@HAVE_NETWORK@ HAVE_ONLY_SYSCFG_PROFILES=@HAVE_ONLY_SYSCFG_PROFILES@ @@ -60,6 +61,7 @@ MANFLAGS = \ $(HAVE_FORCE_NONEWPRIVS) \ $(HAVE_GLOBALCFG) \ $(HAVE_IDS) \ + $(HAVE_LANDLOCK) \ $(HAVE_LTS) \ $(HAVE_NETWORK) \ $(HAVE_ONLY_SYSCFG_PROFILES) \ diff --git a/configure b/configure index 54b9d4da347..8c2d3b8949f 100755 --- a/configure +++ b/configure @@ -675,6 +675,7 @@ HAVE_OVERLAYFS HAVE_DBUSPROXY EXTRA_LDFLAGS EXTRA_CFLAGS +HAVE_LANDLOCK HAVE_SELINUX AA_LIBS AA_CFLAGS @@ -737,6 +738,7 @@ enable_sanitizer enable_ids enable_apparmor enable_selinux +enable_landlock enable_dbusproxy enable_output enable_usertmpfs @@ -1396,6 +1398,7 @@ Optional Features: --enable-ids enable ids --enable-apparmor enable apparmor --enable-selinux SELinux labeling support + --enable-landlock Landlock self-restriction support --disable-dbusproxy disable dbus proxy --disable-output disable --output logging --disable-usertmpfs disable tmpfs as regular user @@ -3739,6 +3742,58 @@ then : fi +HAVE_LANDLOCK="" + +# Check whether --enable-landlock was given. +if test ${enable_landlock+y} +then : + enableval=$enable_landlock; +fi + +ac_header= ac_cache= +for ac_item in $ac_header_c_list +do + if test $ac_cache; then + ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" + if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then + printf "%s\n" "#define $ac_item 1" >> confdefs.h + fi + ac_header= ac_cache= + elif test $ac_header; then + ac_cache=$ac_item + else + ac_header=$ac_item + fi +done + + + + + + + + +if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes +then : + +printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h + +fi +if test "x$enable_landlock" != "xno" +then : + + ac_fn_c_check_header_compile "$LINENO" "linux/landlock.h" "ac_cv_header_linux_landlock_h" "$ac_includes_default" +if test "x$ac_cv_header_linux_landlock_h" = xyes +then : + HAVE_LANDLOCK="-DHAVE_LANDLOCK" +else $as_nop + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: header not found: linux/landlock.h, building without Landlock support" >&5 +printf "%s\n" "$as_me: WARNING: header not found: linux/landlock.h, building without Landlock support" >&2;} +fi + + +fi + @@ -4112,6 +4167,7 @@ if test "x$enable_lts" = "xyes" then : HAVE_LTS="-DHAVE_LTS" + HAVE_LANDLOCK="" HAVE_IDS="" HAVE_DBUSPROXY="" HAVE_OVERLAYFS="" @@ -4132,35 +4188,6 @@ then : fi -ac_header= ac_cache= -for ac_item in $ac_header_c_list -do - if test $ac_cache; then - ac_fn_c_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" - if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then - printf "%s\n" "#define $ac_item 1" >> confdefs.h - fi - ac_header= ac_cache= - elif test $ac_header; then - ac_cache=$ac_item - else - ac_header=$ac_item - fi -done - - - - - - - - -if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes -then : - -printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h - -fi ac_fn_c_check_header_compile "$LINENO" "linux/seccomp.h" "ac_cv_header_linux_seccomp_h" "$ac_includes_default" if test "x$ac_cv_header_linux_seccomp_h" = xyes then : @@ -5360,6 +5387,7 @@ Features: firetunnel support: $HAVE_FIRETUNNEL global config: $HAVE_GLOBALCFG IDS support: $HAVE_IDS + Landlock support: $HAVE_LANDLOCK LTS: $HAVE_LTS manpage support: $HAVE_MAN network: $HAVE_NETWORK diff --git a/configure.ac b/configure.ac index 93de61b95e1..bd80150edd0 100644 --- a/configure.ac +++ b/configure.ac @@ -81,6 +81,16 @@ AS_IF([test "x$enable_selinux" = "xyes"], [ LIBS="$LIBS -lselinux" ]) +HAVE_LANDLOCK="" +AC_SUBST([HAVE_LANDLOCK]) +AC_ARG_ENABLE([landlock], + [AS_HELP_STRING([--enable-landlock], [Landlock self-restriction support])]) +AS_IF([test "x$enable_landlock" != "xno"], [ + AC_CHECK_HEADER([linux/landlock.h], + [HAVE_LANDLOCK="-DHAVE_LANDLOCK"], + [AC_MSG_WARN([header not found: linux/landlock.h, building without Landlock support])]) +]) + AC_SUBST([EXTRA_CFLAGS]) AC_SUBST([EXTRA_LDFLAGS]) @@ -264,6 +274,7 @@ AC_ARG_ENABLE([lts], [AS_HELP_STRING([--enable-lts], [enable long-term support software version (LTS)])]) AS_IF([test "x$enable_lts" = "xyes"], [ HAVE_LTS="-DHAVE_LTS" + HAVE_LANDLOCK="" HAVE_IDS="" HAVE_DBUSPROXY="" HAVE_OVERLAYFS="" @@ -324,6 +335,7 @@ Features: firetunnel support: $HAVE_FIRETUNNEL global config: $HAVE_GLOBALCFG IDS support: $HAVE_IDS + Landlock support: $HAVE_LANDLOCK LTS: $HAVE_LTS manpage support: $HAVE_MAN network: $HAVE_NETWORK diff --git a/contrib/syntax/lists/profile_commands_arg0.list b/contrib/syntax/lists/profile_commands_arg0.list index e7fecef4bf0..4d49e96d927 100644 --- a/contrib/syntax/lists/profile_commands_arg0.list +++ b/contrib/syntax/lists/profile_commands_arg0.list @@ -12,6 +12,7 @@ keep-config-pulse keep-dev-shm keep-shell-rc keep-var-tmp +landlock machine-id memory-deny-write-execute netfilter diff --git a/contrib/syntax/lists/profile_commands_arg1.list b/contrib/syntax/lists/profile_commands_arg1.list index 5862f16ac50..cce37efa04c 100644 --- a/contrib/syntax/lists/profile_commands_arg1.list +++ b/contrib/syntax/lists/profile_commands_arg1.list @@ -29,6 +29,11 @@ ip6 iprange join-or-start keep-fd +landlock.execute +landlock.proc +landlock.read +landlock.special +landlock.write mac mkdir mkfile diff --git a/src/bash_completion/firejail.bash_completion.in b/src/bash_completion/firejail.bash_completion.in index 98e3a035ed2..eab0f7df65a 100644 --- a/src/bash_completion/firejail.bash_completion.in +++ b/src/bash_completion/firejail.bash_completion.in @@ -42,6 +42,25 @@ _firejail() _filedir -d return 0 ;; + --landlock) + return 0 + ;; + --landlock.read) + _filedir + return 0 + ;; + --landlock.write) + _filedir + return 0 + ;; + --landlock.special) + _filedir + return 0 + ;; + --landlock.execute) + _filedir + return 0 + ;; --tmpfs) _filedir return 0 diff --git a/src/firejail/checkcfg.c b/src/firejail/checkcfg.c index d2289bb405e..7792c6541f2 100644 --- a/src/firejail/checkcfg.c +++ b/src/firejail/checkcfg.c @@ -363,6 +363,13 @@ static const char *const compiletime_support = "disabled" #endif + "\n\t- Landlock support is " +#ifdef HAVE_LANDLOCK + "enabled" +#else + "disabled" +#endif + "\n\t- networking support is " #ifdef HAVE_NETWORK "enabled" diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index c791913eab0..5a96fcbfd02 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -150,6 +150,17 @@ typedef struct profile_entry_t { } ProfileEntry; +typedef struct landlock_entry_t { + struct landlock_entry_t *next; +#define LL_READ 0 +#define LL_WRITE 1 +#define LL_SPECIAL 2 +#define LL_EXEC 3 +#define LL_MAX 4 + int type; + char *data; +} LandlockEntry; + typedef struct config_t { // user data char *username; @@ -159,6 +170,7 @@ typedef struct config_t { // filesystem ProfileEntry *profile; ProfileEntry *profile_rebuild_etc; // blacklist files in /etc directory used by fs_rebuild_etc() + LandlockEntry *lprofile; #define MAX_PROFILE_IGNORE 32 char *profile_ignore[MAX_PROFILE_IGNORE]; @@ -281,6 +293,9 @@ extern int arg_overlay; // overlay option extern int arg_overlay_keep; // place overlay diff in a known directory extern int arg_overlay_reuse; // allow the reuse of overlays +extern int arg_landlock; // add basic Landlock rules +extern int arg_landlock_proc; // 0 - no access; 1 -read-only; 2 - read-write + extern int arg_seccomp; // enable default seccomp filter extern int arg_seccomp32; // enable default seccomp filter for 32 bit arch extern int arg_seccomp_postexec; // need postexec ld.preload library? @@ -950,4 +965,26 @@ void run_ids(int argc, char **argv); // oom.c void oom_set(const char *oom_string); +// landlock.c +#ifdef HAVE_LANDLOCK +int ll_get_fd(void); +int ll_is_supported(void); +int ll_read(const char *allowed_path); +int ll_write(const char *allowed_path); +int ll_special(const char *allowed_path); +int ll_exec(const char *allowed_path); +int ll_basic_system(void); +int ll_restrict(__u32 flags); +void ll_add_profile(int type, const char *data); +#else +static inline int ll_get_fd(void) { return -1; } +static inline int ll_read(...) { return 0; } +static inline int ll_write(...) { return 0; } +static inline int ll_special(...) { return 0; } +static inline int ll_exec(...) { return 0; } +static inline int ll_basic_system(void) { return 0; } +static inline int ll_restrict(...) { return 0; } +static inline void ll_add_profile(...) { return; } +#endif /* HAVE_LANDLOCK */ + #endif diff --git a/src/firejail/landlock.c b/src/firejail/landlock.c new file mode 100644 index 00000000000..27fc1d74803 --- /dev/null +++ b/src/firejail/landlock.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2014-2023 Firejail Authors + * + * This file is part of firejail project + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifdef HAVE_LANDLOCK +#include "firejail.h" +#include +#include +#include +#include +#include +#include + +static int ll_ruleset_fd = -1; +static int ll_abi = -1; + +int ll_get_fd(void) { + return ll_ruleset_fd; +} + +#ifndef landlock_create_ruleset +static inline int +landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, + const size_t size, const __u32 flags) { + return syscall(__NR_landlock_create_ruleset, attr, size, flags); +} +#endif + +#ifndef landlock_add_rule +static inline int +landlock_add_rule(const int ruleset_fd, + const enum landlock_rule_type rule_type, + const void *const rule_attr, + const __u32 flags) { + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, + rule_attr, flags); +} +#endif + +#ifndef landlock_restrict_self +static inline int +landlock_restrict_self(const int ruleset_fd, const __u32 flags) { + return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); +} +#endif + +int ll_is_supported(void) { + if (ll_abi != -1) + goto out; + + ll_abi = landlock_create_ruleset(NULL, 0, + LANDLOCK_CREATE_RULESET_VERSION); + if (ll_abi < 1) { + ll_abi = 0; + fprintf(stderr, "Warning: Landlock is disabled or not supported: %s, " + "ignoring landlock commands\n", + strerror(errno)); + goto out; + } + if (arg_debug) { + printf("Detected Landlock ABI version %d\n", ll_abi); + } +out: + return ll_abi; +} + +static int ll_create_full_ruleset() { + if (!ll_is_supported()) + return -1; + + struct landlock_ruleset_attr attr; + attr.handled_access_fs = + LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_MAKE_BLOCK | + LANDLOCK_ACCESS_FS_MAKE_CHAR | + LANDLOCK_ACCESS_FS_MAKE_DIR | + LANDLOCK_ACCESS_FS_MAKE_FIFO | + LANDLOCK_ACCESS_FS_MAKE_REG | + LANDLOCK_ACCESS_FS_MAKE_SOCK | + LANDLOCK_ACCESS_FS_MAKE_SYM | + LANDLOCK_ACCESS_FS_READ_DIR | + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_REMOVE_DIR | + LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE; + + ll_ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); + if (ll_ruleset_fd < 0) { + fprintf(stderr, "Error: failed to create a Landlock ruleset: %s\n", + strerror(errno)); + } + return ll_ruleset_fd; +} + +int ll_read(const char *allowed_path) { + if (!ll_is_supported()) + return 0; + + if (ll_ruleset_fd == -1) + ll_ruleset_fd = ll_create_full_ruleset(); + + int error; + int allowed_fd = open(allowed_path, O_PATH | O_CLOEXEC); + if (allowed_fd < 0) { + if (arg_debug) { + fprintf(stderr, "%s: failed to open %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + return 0; + } + struct landlock_path_beneath_attr target; + target.parent_fd = allowed_fd; + target.allowed_access = + LANDLOCK_ACCESS_FS_READ_DIR | + LANDLOCK_ACCESS_FS_READ_FILE; + + error = landlock_add_rule(ll_ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &target, 0); + if (error) { + fprintf(stderr, "Error: %s: failed to add Landlock rule for %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + close(allowed_fd); + return error; +} + +int ll_write(const char *allowed_path) { + if (!ll_is_supported()) + return 0; + + if (ll_ruleset_fd == -1) + ll_ruleset_fd = ll_create_full_ruleset(); + + int error; + int allowed_fd = open(allowed_path, O_PATH | O_CLOEXEC); + if (allowed_fd < 0) { + if (arg_debug) { + fprintf(stderr, "%s: failed to open %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + return 0; + } + struct landlock_path_beneath_attr target; + target.parent_fd = allowed_fd; + target.allowed_access = + LANDLOCK_ACCESS_FS_MAKE_DIR | + LANDLOCK_ACCESS_FS_MAKE_REG | + LANDLOCK_ACCESS_FS_MAKE_SYM | + LANDLOCK_ACCESS_FS_REMOVE_DIR | + LANDLOCK_ACCESS_FS_REMOVE_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE; + + error = landlock_add_rule(ll_ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &target, 0); + if (error) { + fprintf(stderr, "Error: %s: failed to add Landlock rule for %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + close(allowed_fd); + return error; +} + +int ll_special(const char *allowed_path) { + if (!ll_is_supported()) + return 0; + + if (ll_ruleset_fd == -1) + ll_ruleset_fd = ll_create_full_ruleset(); + + int error; + int allowed_fd = open(allowed_path, O_PATH | O_CLOEXEC); + if (allowed_fd < 0) { + if (arg_debug) { + fprintf(stderr, "%s: failed to open %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + return 0; + } + struct landlock_path_beneath_attr target; + target.parent_fd = allowed_fd; + target.allowed_access = + LANDLOCK_ACCESS_FS_MAKE_BLOCK | + LANDLOCK_ACCESS_FS_MAKE_CHAR | + LANDLOCK_ACCESS_FS_MAKE_FIFO | + LANDLOCK_ACCESS_FS_MAKE_SOCK; + + error = landlock_add_rule(ll_ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &target, 0); + if (error) { + fprintf(stderr, "Error: %s: failed to add Landlock rule for %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + close(allowed_fd); + return error; +} + +int ll_exec(const char *allowed_path) { + if (!ll_is_supported()) + return 0; + + if (ll_ruleset_fd == -1) + ll_ruleset_fd = ll_create_full_ruleset(); + + int error; + int allowed_fd = open(allowed_path, O_PATH | O_CLOEXEC); + if (allowed_fd < 0) { + if (arg_debug) { + fprintf(stderr, "%s: failed to open %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + return 0; + } + struct landlock_path_beneath_attr target; + target.parent_fd = allowed_fd; + target.allowed_access = + LANDLOCK_ACCESS_FS_EXECUTE; + + error = landlock_add_rule(ll_ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &target, 0); + if (error) { + fprintf(stderr, "Error: %s: failed to add Landlock rule for %s: %s\n", + __func__, allowed_path, strerror(errno)); + } + close(allowed_fd); + return error; +} + +int ll_basic_system(void) { + assert(cfg.homedir); + + if (!ll_is_supported()) + return 0; + + if (ll_ruleset_fd == -1) + ll_ruleset_fd = ll_create_full_ruleset(); + + int error; + char *rundir; + if (asprintf(&rundir, "/run/user/%d", getuid()) == -1) + errExit("asprintf"); + + error = + ll_read("/") || // whole system read + ll_special("/") || // sockets etc. + + ll_write("/tmp") || // write access + ll_write("/dev") || + ll_write("/run/shm") || + ll_write(cfg.homedir) || + ll_write(rundir) || + + ll_exec("/opt") || // exec access + ll_exec("/bin") || + ll_exec("/sbin") || + ll_exec("/lib") || + ll_exec("/lib32") || + ll_exec("/libx32") || + ll_exec("/lib64") || + ll_exec("/usr/bin") || + ll_exec("/usr/sbin") || + ll_exec("/usr/games") || + ll_exec("/usr/lib") || + ll_exec("/usr/lib32") || + ll_exec("/usr/libx32") || + ll_exec("/usr/lib64") || + ll_exec("/usr/local/bin") || + ll_exec("/usr/local/sbin") || + ll_exec("/usr/local/games") || + ll_exec("/usr/local/lib") || + ll_exec("/run/firejail"); // appimage and various firejail features + + if (error) { + fprintf(stderr, "Error: %s: failed to set --landlock rules\n", + __func__); + } + free(rundir); + return error; +} + +int ll_restrict(__u32 flags) { + if (!ll_is_supported()) + return 0; + + int (*fnc[])(const char *) = { + ll_read, + ll_write, + ll_special, + ll_exec, + NULL + }; + + LandlockEntry *ptr = cfg.lprofile; + while (ptr) { + fnc[ptr->type](ptr->data); + ptr = ptr->next; + } + + if (ll_ruleset_fd == -1) + return 0; + + int error; + error = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + if (error) { + fprintf(stderr, "Error: %s: failed to restrict privileges: %s\n", + __func__, strerror(errno)); + goto out; + } + error = landlock_restrict_self(ll_ruleset_fd, flags); + if (error) { + fprintf(stderr, "Error: %s: failed to enforce Landlock: %s\n", + __func__, strerror(errno)); + goto out; + } + if (arg_debug) + printf("%s: Enforcing Landlock\n", __func__); +out: + close(ll_ruleset_fd); + return error; +} + +void ll_add_profile(int type, const char *data) { + assert(type >= 0); + assert(type < LL_MAX); + assert(data); + + if (!ll_is_supported()) + return; + + const char *str = data; + while (*str == ' ' || *str == '\t') + str++; + + LandlockEntry *ptr = malloc(sizeof(LandlockEntry)); + if (!ptr) + errExit("malloc"); + memset(ptr, 0, sizeof(LandlockEntry)); + ptr->type = type; + ptr->data = strdup(str); + if (!ptr->data) + errExit("strdup"); + ptr->next = cfg.lprofile; + cfg.lprofile = ptr; +} + +#endif /* HAVE_LANDLOCK */ diff --git a/src/firejail/main.c b/src/firejail/main.c index 0327f8bdaea..5bcc3a0e58a 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -75,6 +75,9 @@ int arg_overlay = 0; // overlay option int arg_overlay_keep = 0; // place overlay diff in a known directory int arg_overlay_reuse = 0; // allow the reuse of overlays +int arg_landlock = 0; // add basic Landlock rules +int arg_landlock_proc = 2; // 0 - no access; 1 -read-only; 2 - read-write + int arg_seccomp = 0; // enable default seccomp filter int arg_seccomp32 = 0; // enable default seccomp filter for 32 bit arch int arg_seccomp_postexec = 0; // need postexec ld.preload library? @@ -1500,6 +1503,31 @@ int main(int argc, char **argv, char **envp) { else exit_err_feature("seccomp"); } +#ifdef HAVE_LANDLOCK + else if (strcmp(argv[i], "--landlock") == 0) + arg_landlock = 1; + else if (strncmp(argv[i], "--landlock.proc=", 16) == 0) { + if (strncmp(argv[i] + 16, "no", 2) == 0) + arg_landlock_proc = 0; + else if (strncmp(argv[i] + 16, "ro", 2) == 0) + arg_landlock_proc = 1; + else if (strncmp(argv[i] + 16, "rw", 2) == 0) + arg_landlock_proc = 2; + else { + fprintf(stderr, "Error: invalid landlock.proc value: %s\n", + argv[i] + 16); + exit(1); + } + } + else if (strncmp(argv[i], "--landlock.read=", 16) == 0) + ll_add_profile(LL_READ, argv[i] + 16); + else if (strncmp(argv[i], "--landlock.write=", 17) == 0) + ll_add_profile(LL_WRITE, argv[i] + 17); + else if (strncmp(argv[i], "--landlock.special=", 19) == 0) + ll_add_profile(LL_SPECIAL, argv[i] + 19); + else if (strncmp(argv[i], "--landlock.execute=", 19) == 0) + ll_add_profile(LL_EXEC, argv[i] + 19); +#endif else if (strcmp(argv[i], "--memory-deny-write-execute") == 0) { if (checkcfg(CFG_SECCOMP)) arg_memory_deny_write_execute = 1; diff --git a/src/firejail/profile.c b/src/firejail/profile.c index 0f60e9b7d71..62d3c78e70e 100644 --- a/src/firejail/profile.c +++ b/src/firejail/profile.c @@ -1073,6 +1073,44 @@ int profile_check_line(char *ptr, int lineno, const char *fname) { return 0; } +#ifdef HAVE_LANDLOCK + // Landlock ruleset paths + if (strcmp(ptr, "landlock") == 0) { + arg_landlock = 1; + return 0; + } + if (strncmp(ptr, "landlock.proc ", 14) == 0) { + if (strncmp(ptr + 14, "no", 2) == 0) + arg_landlock_proc = 0; + else if (strncmp(ptr + 14, "ro", 2) == 0) + arg_landlock_proc = 1; + else if (strncmp(ptr + 14, "rw", 2) == 0) + arg_landlock_proc = 2; + else { + fprintf(stderr, "Error: invalid landlock.proc value: %s\n", + ptr + 14); + exit(1); + } + return 0; + } + if (strncmp(ptr, "landlock.read ", 14) == 0) { + ll_add_profile(LL_READ, ptr + 14); + return 0; + } + if (strncmp(ptr, "landlock.write ", 15) == 0) { + ll_add_profile(LL_WRITE, ptr + 15); + return 0; + } + if (strncmp(ptr, "landlock.special ", 17) == 0) { + ll_add_profile(LL_SPECIAL, ptr + 17); + return 0; + } + if (strncmp(ptr, "landlock.execute ", 17) == 0) { + ll_add_profile(LL_EXEC, ptr + 17); + return 0; + } +#endif + // memory deny write&execute if (strcmp(ptr, "memory-deny-write-execute") == 0) { if (checkcfg(CFG_SECCOMP)) @@ -1897,8 +1935,7 @@ void profile_read(const char *fname) { fclose(fp); } -char *profile_list_normalize(char *list) -{ +char *profile_list_normalize(char *list) { /* Remove redundant commas. * * As result is always shorter than original, diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 827be5d85b8..dbc115137b7 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c @@ -516,6 +516,28 @@ void start_application(int no_sandbox, int fd, char *set_sandbox_status) { printf("LD_PRELOAD=%s\n", getenv("LD_PRELOAD")); } +#ifdef HAVE_LANDLOCK + //**************************** + // Configure Landlock + //**************************** + if (arg_landlock) + ll_basic_system(); + + if (ll_get_fd() != -1) { + if (arg_landlock_proc >= 1) + ll_read("/proc/"); + if (arg_landlock_proc == 2) + ll_write("/proc/"); + } + + if (ll_restrict(0)) { + // It isn't safe to continue if Landlock self-restriction was + // enabled and the "landlock_restrict_self" syscall has failed. + fprintf(stderr, "Error: ll_restrict() failed, exiting...\n"); + exit(1); + } +#endif + if (just_run_the_shell) { char *arg[2]; arg[0] = cfg.usershell; diff --git a/src/firejail/usage.c b/src/firejail/usage.c index e8758c80729..5f9185da996 100644 --- a/src/firejail/usage.c +++ b/src/firejail/usage.c @@ -133,6 +133,14 @@ static const char *const usage_str = " --keep-fd - inherit open file descriptors to sandbox.\n" " --keep-shell-rc - do not copy shell rc files from /etc/skel\n" " --keep-var-tmp - /var/tmp directory is untouched.\n" +#ifdef HAVE_LANDLOCK + " --landlock - add basic rules to the Landlock ruleset.\n" + " --landlock.proc=no|ro|rw - add an access rule for /proc to the Landlock ruleset.\n" + " --landlock.read=path - add a read access rule for the path to the Landlock ruleset.\n" + " --landlock.write=path - add a write access rule for the path to the Landlock ruleset.\n" + " --landlock.special=path - add an access rule for the path to the Landlock ruleset for creating block/char devices, named pipes and sockets.\n" + " --landlock.execute=path - add an execute access rule for the path to the Landlock ruleset.\n" +#endif " --list - list all sandboxes.\n" #ifdef HAVE_FILE_TRANSFER " --ls=name|pid dir_or_filename - list files in sandbox container.\n" diff --git a/src/firejail/util.c b/src/firejail/util.c index bd32181b5e7..c0f30931cb5 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c @@ -1338,6 +1338,13 @@ void close_all(int *keep_list, size_t sz) { if (keep) continue; +#ifdef HAVE_LANDLOCK + // Don't close the file descriptor of the Landlock ruleset; it + // will be automatically closed by the "ll_restrict" wrapper + // function. + if (fd == ll_get_fd()) + continue; +#endif close(fd); } closedir(dir); diff --git a/src/man/firejail-profile.5.in b/src/man/firejail-profile.5.in index 3a678b14f6d..76f5e4d200f 100644 --- a/src/man/firejail-profile.5.in +++ b/src/man/firejail-profile.5.in @@ -507,6 +507,37 @@ Blacklist all Linux capabilities. .TP \fBcaps.keep capability,capability,capability Whitelist given Linux capabilities. +#ifdef HAVE_LANDLOCK +.TP +\fBlandlock +Create a Landlock ruleset (if it doesn't already exist) and add basic access +rules to it. +.TP +\fBlandlock.proc no|ro|rw +Add an access rule for /proc directory (read-only if set to \fBro\fR and +read-write if set to \fBrw\fR). +The access rule for /proc is added after this directory is set up in the +sandbox. +Access rules for /proc set up with other Landlock-related profile options have +no effect. +.TP +\fBlandlock.read path +Create a Landlock ruleset (if it doesn't already exist) and add a read access +rule for path. +.TP +\fBlandlock.write path +Create a Landlock ruleset (if it doesn't already exist) and add a write access +rule for path. +.TP +\fBlandlock.special path +Create a Landlock ruleset (if it doesn't already exist) and add a rule that +allows the creation of block devices, character devices, named pipes (FIFOs) +and Unix domain sockets beneath given path. +.TP +\fBlandlock.execute path +Create a Landlock ruleset (if it doesn't already exist) and add an execution +permission rule for path. +#endif .TP \fBmemory-deny-write-execute Install a seccomp filter to block attempts to create memory mappings diff --git a/src/man/firejail.1.in b/src/man/firejail.1.in index 06969e851d6..d5a00c41b46 100644 --- a/src/man/firejail.1.in +++ b/src/man/firejail.1.in @@ -1243,6 +1243,52 @@ Example: .br $ firejail --keep-var-tmp +#ifdef HAVE_LANDLOCK +.TP +\fB\-\-landlock +Create a Landlock ruleset (if it doesn't already exist) and add basic access +rules to it. +The basic set of rules applies the following access permissions: +.PP +.RS +- read: /bin, /dev, /etc, /lib, /opt, /proc, /usr, /var +.br +- write: /dev, /proc +.br +- exec: /bin, /lib, /opt, /usr +.RE +.PP +See the \fBLANDLOCK\fR section for more information. +.TP +\fB\-\-landlock.proc=no|ro|rw +Add an access rule for /proc directory (read-only if set to \fBro\fR and +read-write if set to \fBrw\fR). +The access rule for /proc is added after this directory is set up in the +sandbox. +Access rules for /proc set up with other Landlock-related command-line options +have no effect. +.TP +\fB\-\-landlock.read=path +Create a Landlock ruleset (if it doesn't already exist) and add a read access +rule for path. +.TP +\fB\-\-landlock.write=path +Create a Landlock ruleset (if it doesn't already exist) and add a write access +rule for path. +.TP +\fB\-\-landlock.special=path +Create a Landlock ruleset (if it doesn't already exist) and add a rule that +allows the creation of block devices, character devices, named pipes (FIFOs) +and Unix domain sockets beneath given path. +.TP +\fB\-\-landlock.execute=path +Create a Landlock ruleset (if it doesn't already exist) and add an execution +permission rule for path. +.PP +Example: +.PP +$ firejail \-\-landlock.read=/ \-\-landlock.write=/home \-\-landlock.execute=/usr +#endif .TP \fB\-\-list List all sandboxes, see \fBMONITORING\fR section for more details. @@ -3365,6 +3411,47 @@ To enable AppArmor confinement on top of your current Firejail security features $ firejail --apparmor firefox #endif +#ifdef HAVE_LANDLOCK +.SH LANDLOCK +Landlock is a Linux security module first introduced in version 5.13 of the +Linux kernel. +It allows unprivileged processes to restrict their access to the filesystem. +Once imposed, these restrictions can never be removed, and all child processes +created by a Landlock-restricted processes inherit these restrictions. +Firejail supports Landlock as an additional sandboxing feature. +It can be used to ensure that a sandboxed application can only access files and +directories that it was explicitly allowed to access. +Firejail supports populating the ruleset with both a basic set of rules (see +\fB\-\-landlock\fR) and with a custom set of rules. +.TP +Important notes: +.PP +.RS +- A process can install a Landlock ruleset only if it has either +\fBCAP_SYS_ADMIN\fR in its effective capability set, or the "No New +Privileges" restriction enabled. +Because of this, enabling the Landlock feature will also cause Firejail to +enable the "No New Privileges" restriction, regardless of the profile or the +\fB\-\-no\-new\-privs\fR command line option. +.PP +- Access to the /proc directory is managed through the \fB\-\-landlock.proc\fR +command line option. +.PP +- Access to the /etc directory is automatically allowed. +To override this, use the \fB\-\-writable\-etc\fR command line option. +You can also use the \fB\-\-private\-etc\fR option to restrict access to the +/etc directory. +.RE +.PP +To enable Landlock self-restriction on top of your current Firejail security +features, pass \fB\-\-landlock\fR flag to Firejail command line. +You can also use \fB\-\-landlock.read\fR, \fB\-\-landlock.write\fR, +\fB\-\-landlock.special\fR and \fB\-\-landlock.execute\fR options together with +\fB\-\-landlock\fR or instead of it. +Example: +.PP +$ firejail \-\-landlock \-\-landlock.read=/media \-\-landlock.proc=ro mc +#endif .SH DESKTOP INTEGRATION A symbolic link to /usr/bin/firejail under the name of a program, will start the program in Firejail sandbox. The symbolic link should be placed in the first $PATH position. On most systems, a good place diff --git a/src/zsh_completion/_firejail.in b/src/zsh_completion/_firejail.in index 7e87bb991ca..89cb1b84ca9 100644 --- a/src/zsh_completion/_firejail.in +++ b/src/zsh_completion/_firejail.in @@ -106,6 +106,11 @@ _firejail_args=( '--keep-fd[inherit open file descriptors to sandbox]: :' '--keep-shell-rc[do not copy shell rc files from /etc/skel]' '--keep-var-tmp[/var/tmp directory is untouched]' + '--landlock.proc=-[add an access rule for /proc to the Landlock ruleset]: :(no ro rw)' + '--landlock.read=-[add a read access rule for the path to the Landlock ruleset]: :_files' + '--landlock.write=-[add a write access rule for the path to the Landlock ruleset]: :_files' + '--landlock.special=-[add an access rule for the path to the Landlock ruleset for creating block/char devices, named pipes and sockets]: :_files' + '--landlock.execute=-[add an execute access rule for the path to the Landlock ruleset]: :_files' '--machine-id[spoof /etc/machine-id with a random id]' '--memory-deny-write-execute[seccomp filter to block attempts to create memory mappings that are both writable and executable]' '*--mkdir=-[create a directory]:'