-
Notifications
You must be signed in to change notification settings - Fork 567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Landlock support to Firejail #5315
Conversation
c7db5d2
to
ba828be
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My two cents on the cli/profile API:
- What about dot namespaces command like we have for
caps
/seccomp
? So instead oflandlock-read
it would belandlock.read
? write
andrestricted-write
should have the opposite logic. This way users will tend to usewrite
if they want to allow writing most of the time.restricted-write
->write
write
->write-any
(or something else, if anybody has a godd idea)
Co-authored-by: rusty-snake <41237666+rusty-snake@users.noreply.github.com>
I wanted to do it initially, but I ran into a problem with Bash completion, that it would complete it as "--landlock ", but there is no such option as "--landlock" (unlike --caps and --seccomp). If it isn't a problem, I'd change it back. |
We could add a Anyway I know the problem. If you use command-line options other than I think it's ok then to leave it as is. |
I've worked on this pull request. What's changed:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I also have comments about some high-level things; this review is mostly
to leave a "request for changes" status.
#include <sys/types.h> | ||
#include <sys/prctl.h> | ||
#include <linux/prctl.h> | ||
#include <linux/landlock.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is breaking the build on systems that do not support Landlock (such as on
Ubuntu 20.04, which uses an older kernel), even when Landlock is not enabled.
Partial error log of the codeql analyze
job from
https://github.com/netblue30/firejail/runs/7853807421:
gcc -g -O2 -ggdb -O2 -DVERSION='"0.9.71"' -DPREFIX='"/usr"' -DSYSCONFDIR='"/etc/firejail"' -DLIBDIR='"/usr/lib"' -DBINDIR='"/usr/bin"' -DVARDIR='"/var/lib/firejail"' -DHAVE_OUTPUT -DHAVE_X11 -DHAVE_PRIVATE_HOME -DHAVE_USERTMPFS -DHAVE_DBUSPROXY -DHAVE_GLOBALCFG -DHAVE_CHROOT -DHAVE_NETWORK -DHAVE_USERNS -DHAVE_FILE_TRANSFER -DHAVE_SUID -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security -fstack-clash-protection -fstack-protector-strong -c landlock.c -o landlock.o
landlock.c:11:10: fatal error: linux/landlock.h: No such file or directory
11 | #include <linux/landlock.h>
| ^~~~~~~~~~~~~~~~~~
compilation terminated.
make[1]: *** [Makefile:8: landlock.o] Error 1
make[1]: Leaving directory '/home/runner/work/firejail/firejail/src/firejail'
make: *** [Makefile:31: src/firejail/firejail] Error 2
See src/include/gcov_wrapper.h
and try to create a landlock_wrapper.h
in a
similar fashion. That is:
- In
landlock_wrapper.h
, declare dummy functions ifHAVE_LANDLOCK
is not
defined. - Outside of
landlock_wrapper.h
, only include"landlock_wrapper.h"
instead
of<linux/landlock.h>
. - Outside of
landlock_wrapper.h
, avoid using#ifdef HAVE_LANDLOCK
where it
can be helped (just call thelandlock
functions as if LandLock is always
enabled).
@@ -52,7 +52,7 @@ syn match fjVar /\v\$\{(CFG|DESKTOP|DOCUMENTS|DOWNLOADS|HOME|MUSIC|PATH|PICTURES | |||
|
|||
" Commands grabbed from: src/firejail/profile.c | |||
" Generate list with: { rg -o 'strn?cmp\(ptr, "([^"]+) "' -r '$1' src/firejail/profile.c; echo private-lib; } | grep -vEx '(include|ignore|caps\.drop|caps\.keep|protocol|restrict-namespaces|seccomp|seccomp\.drop|seccomp\.keep|env|rmenv|net|ip)' | sort -u | tr $'\n' '|' # private-lib is special-cased in the code and doesn't match the regex; grep-ed patterns are handled later with 'syn match nextgroup=' directives (except for include which is special-cased as a fjCommandNoCond keyword) | |||
syn match fjCommand /\v(apparmor|bind|blacklist|blacklist-nolog|cpu|defaultgw|dns|hostname|hosts-file|ip6|iprange|join-or-start|mac|mkdir|mkfile|mtu|name|netfilter|netfilter6|netmask|nice|noblacklist|noexec|nowhitelist|overlay-named|private|private-bin|private-cwd|private-etc|private-home|private-lib|private-opt|private-srv|read-only|read-write|rlimit-as|rlimit-cpu|rlimit-fsize|rlimit-nofile|rlimit-nproc|rlimit-sigpending|timeout|tmpfs|veth-name|whitelist|xephyr-screen) / skipwhite contained | |||
syn match fjCommand /\v(apparmor|bind|blacklist|blacklist-nolog|cpu|defaultgw|dns|hostname|hosts-file|ip6|iprange|join-or-start|landlock|landlock.proc|landlock.read|landlock.write|landlock.special|landlock.execute|mac|mkdir|mkfile|mtu|name|netfilter|netfilter6|netmask|nice|noblacklist|noexec|nowhitelist|overlay-named|private|private-bin|private-cwd|private-etc|private-home|private-lib|private-opt|private-srv|read-only|read-write|rlimit-as|rlimit-cpu|rlimit-fsize|rlimit-nofile|rlimit-nproc|rlimit-sigpending|timeout|tmpfs|veth-name|whitelist|xephyr-screen) / skipwhite contained |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be sorted by name.
#ifdef HAVE_LANDLOCK | ||
|
||
extern int landlock_create_ruleset(struct landlock_ruleset_attr *rsattr,size_t size,__u32 flags); | ||
|
||
extern int landlock_add_rule(int fd,enum landlock_rule_type t,void *attr,__u32 flags); | ||
|
||
extern int landlock_restrict_self(int fd,__u32 flags); | ||
|
||
extern int create_full_ruleset(); | ||
|
||
extern int add_read_access_rule_by_path(int rset_fd,char *allowed_path); | ||
|
||
extern int add_write_access_rule_by_path(int rset_fd,char *allowed_path); | ||
|
||
extern int add_create_special_rule_by_path(int rset_fd,char *allowed_path); | ||
|
||
extern int add_execute_rule_by_path(int rset_fd,char *allowed_path); | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the #ifdef
(see the new gcov comment) and extraneous blank lines (see
the rest of the file).
if (result!=0) return result; | ||
else { | ||
close(fd); | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (result!=0) return result; | |
else { | |
close(fd); | |
return 0; | |
} | |
if (result != 0) | |
return result; | |
else { | |
close(fd); | |
return 0; | |
} |
if () foo
is done nowhere in the code AFAIK.
The same applies to other if () foo
changes in this PR.
@@ -1093,9 +1113,12 @@ int sandbox(void* sandbox_arg) { | |||
//**************************** | |||
// rebuild etc directory, set dns | |||
//**************************** | |||
if (!arg_writable_etc) | |||
if (!arg_writable_etc){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!arg_writable_etc){ | |
if (!arg_writable_etc) { |
|
||
int create_full_ruleset() { | ||
struct landlock_ruleset_attr attr; | ||
attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | 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_EXECUTE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
attr.handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | 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_EXECUTE; | |
attr.handled_access_fs = \ | |
LANDLOCK_ACCESS_FS_READ_FILE | | |
LANDLOCK_ACCESS_FS_READ_DIR | | |
LANDLOCK_ACCESS_FS_WRITE_FILE | | |
LANDLOCK_ACCESS_FS_REMOVE_FILE | | |
LANDLOCK_ACCESS_FS_REMOVE_DIR | | |
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_EXECUTE; |
To make it readable.
The same applies to the other target.allowed_access
changes as well.
#ifdef HAVE_LANDLOCK | ||
extern int arg_landlock; // Landlock ruleset file descriptor | ||
extern int arg_landlock_proc; // Landlock rule for accessing /proc (0 for no access, 1 for read-only and 2 for read-write) | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the #ifdef
and move them below this line (see the new gcov comment):
extern char *apparmor_profile; // apparmor profile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was very confusing to see an arg_
variable refer to a data structure,
rather than a flag or something that is actually specified on the command line.
I thought that it meant the path argument that is eventually passed to a
landlock call (such as the PATH in landlock.foo PATH
) and only noticed that
that is not the case because argv[i] + N
is also passed to the landlock calls
in main.c:
+ else if (strncmp(argv[i], "--landlock.read=", 16) == 0) { + if (arg_landlock == -1) + arg_landlock = create_full_ruleset(); + if (add_read_access_rule_by_path(arg_landlock, argv[i] + 16)) { + fprintf(stderr, "An error has occured while adding a rule to the Landlock ruleset.\n"); + } + }
Suggestion: Avoid being too clever and just use separate variables. Use
arg_landlock
to check if --landlock
is used (for example, in sandbox.c) and
add a new landlock_ruleset_fd
variable to store the file descriptor (note
that this variable name matches the kernel API):
extern int arg_apparmor; // apparmor
extern char *apparmor_profile; // apparmor profile
+extern int arg_landlock; // Landlock
+extern int arg_landlock_proc; // Landlock rule for accessing /proc (0 for no access, 1 for read-only and 2 for read-write)
+extern int landlock_ruleset_fd; // Landlock ruleset file descriptor
Example usage on main.c:
else if (strcmp(argv[i], "--landlock") == 0) {
+ arg_landlock = 1;
+
- if (arg_landlock == -1)
- arg_landlock = create_full_ruleset();
+ if (landlock_ruleset_fd < 0) {
+ landlock_ruleset_fd = create_full_ruleset();
+ if (landlock_ruleset_fd < 0)
+ errExit("create_full_ruleset");
+ }
The same applies to the other --landlock
arguments (other than maybe setting
arg_landlock = 1
).
struct landlock_path_beneath_attr target; | ||
target.parent_fd = allowed_fd; | ||
target.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; | ||
result = landlock_add_rule(rset_fd,LANDLOCK_RULE_PATH_BENEATH,&target,0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many whitespace issues. See this again:
#ifdef HAVE_LANDLOCK | ||
.SH LANDLOCK | ||
.TP | ||
Landlock is a Linux security module first introduced in the 5.13 version of 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 basic set of rules and with custom set of rules. Basic set of rules allows read-only access to /bin, /dev, /etc, /lib, /opt, /proc, /usr and /var, read-write access to the home directory, and allows execution of binaries located in /bin, /opt and /usr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Landlock is a Linux security module first introduced in the 5.13 version of 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 basic set of rules and with custom set of rules. Basic set of rules allows read-only access to /bin, /dev, /etc, /lib, /opt, /proc, /usr and /var, read-write access to the home directory, and allows execution of binaries located in /bin, /opt and /usr. | |
Landlock is a Linux security module first introduced in the 5.13 version of | |
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 basic set of rules and with custom set of rules. Basic set of | |
rules allows read-only access to /bin, /dev, /etc, /lib, /opt, /proc, /usr and | |
/var, read-write access to the home directory, and allows execution of binaries | |
located in /bin, /opt and /usr. |
.br | ||
|
||
.br | ||
- 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this technical background? IMHO a "landlock implies NNP" is enough.
if (arg_landlock == -1) arg_landlock = create_full_ruleset(); | ||
const char *home_dir = env_get("HOME"); | ||
int home_fd = open(home_dir,O_PATH | O_CLOEXEC); | ||
struct landlock_path_beneath_attr target; | ||
target.parent_fd = home_fd; | ||
target.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SYM; | ||
if (landlock_add_rule(arg_landlock,LANDLOCK_RULE_PATH_BENEATH,&target,0)) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
close(home_fd); | ||
if (add_read_access_rule_by_path(arg_landlock, "/bin/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_execute_rule_by_path(arg_landlock, "/bin/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_read_access_rule_by_path(arg_landlock, "/dev/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_read_access_rule_by_path(arg_landlock, "/etc/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_read_access_rule_by_path(arg_landlock, "/lib/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_execute_rule_by_path(arg_landlock, "/lib/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_read_access_rule_by_path(arg_landlock, "/opt/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_execute_rule_by_path(arg_landlock, "/opt/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_read_access_rule_by_path(arg_landlock, "/usr/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_execute_rule_by_path(arg_landlock, "/usr/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_read_access_rule_by_path(arg_landlock, "/var/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to move this into a function to 1. avoid duplication and 2. prevent future change from chaning only commandline/profile implementation.
int allowed_fd = open(allowed_path,O_PATH | O_CLOEXEC); | ||
struct landlock_path_beneath_attr target; | ||
target.parent_fd = allowed_fd; | ||
target.allowed_access = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | LANDLOCK_ACCESS_FS_MAKE_CHAR | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_SYM; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you add LANDLOCK_ACCESS_FS_MAKE_CHAR
here instead of special
? (I want to understand)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a mistake. I plan to move it to special
if (add_read_access_rule_by_path(arg_landlock, "/lib/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
if (add_execute_rule_by_path(arg_landlock, "/lib/")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there distros which use /lib64
as real dir?
else if (strncmp(ptr+14, "ro", 2) == 0) arg_landlock_proc = 1; | ||
else if (strncmp(ptr+14, "rw", 2) == 0) arg_landlock_proc = 2; | ||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my experience with crablock I can tell that --landlock.path_beneath="/proc:READ_FILE"
is often enough. Maybe it would be interesting to expose this as a read-only-files-but-no-dirs option.
@@ -52,7 +52,7 @@ syn match fjVar /\v\$\{(CFG|DESKTOP|DOCUMENTS|DOWNLOADS|HOME|MUSIC|PATH|PICTURES | |||
|
|||
" Commands grabbed from: src/firejail/profile.c | |||
" Generate list with: { rg -o 'strn?cmp\(ptr, "([^"]+) "' -r '$1' src/firejail/profile.c; echo private-lib; } | grep -vEx '(include|ignore|caps\.drop|caps\.keep|protocol|restrict-namespaces|seccomp|seccomp\.drop|seccomp\.keep|env|rmenv|net|ip)' | sort -u | tr $'\n' '|' # private-lib is special-cased in the code and doesn't match the regex; grep-ed patterns are handled later with 'syn match nextgroup=' directives (except for include which is special-cased as a fjCommandNoCond keyword) | |||
syn match fjCommand /\v(apparmor|bind|blacklist|blacklist-nolog|cpu|defaultgw|dns|hostname|hosts-file|ip6|iprange|join-or-start|mac|mkdir|mkfile|mtu|name|netfilter|netfilter6|netmask|nice|noblacklist|noexec|nowhitelist|overlay-named|private|private-bin|private-cwd|private-etc|private-home|private-lib|private-opt|private-srv|read-only|read-write|rlimit-as|rlimit-cpu|rlimit-fsize|rlimit-nofile|rlimit-nproc|rlimit-sigpending|timeout|tmpfs|veth-name|whitelist|xephyr-screen) / skipwhite contained | |||
syn match fjCommand /\v(apparmor|bind|blacklist|blacklist-nolog|cpu|defaultgw|dns|hostname|hosts-file|ip6|iprange|join-or-start|landlock|landlock.proc|landlock.read|landlock.write|landlock.special|landlock.execute|mac|mkdir|mkfile|mtu|name|netfilter|netfilter6|netmask|nice|noblacklist|noexec|nowhitelist|overlay-named|private|private-bin|private-cwd|private-etc|private-home|private-lib|private-opt|private-srv|read-only|read-write|rlimit-as|rlimit-cpu|rlimit-fsize|rlimit-nofile|rlimit-nproc|rlimit-sigpending|timeout|tmpfs|veth-name|whitelist|xephyr-screen) / skipwhite contained |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
landlock
takes no value, right?
// Landlock ruleset paths | ||
if (strcmp(ptr, "landlock") == 0) { | ||
if (arg_landlock == -1) arg_landlock = create_full_ruleset(); | ||
const char *home_dir = env_get("HOME"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work with --private
?
- Access to the /proc directory is managed through the \fB\-\-landlock.proc\fR command line option. | ||
|
||
.br | ||
- 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note for me: Does this make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When implementing Landlock support, I ran into a problem, that access rules for /proc
(and /etc
if --writable-etc
wasn't supplied) had no effect when added before sandbox setup (access was denied even when I explicitly allowed it with command-line options), so I had to make these rules a part of sandbox setup. This is why --landlock.proc
schedules the rule addition instead of adding it immediately.
.br | ||
|
||
.TP | ||
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe make clear that --landlock.*
can only allow additional access kinds/paths but not remove access kinds granted by --landlock
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review 2/?.
} | ||
} | ||
|
||
int create_full_ruleset() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All functions in this file should probably have a landlock_
prefix to make it
clear on the call site that they are about Landlock (rather than being about
something else, like AppArmor).
#ifdef HAVE_LANDLOCK | ||
else if (strcmp(argv[i], "--landlock") == 0) { | ||
if (arg_landlock == -1) arg_landlock = create_full_ruleset(); | ||
const char *home_dir = env_get("HOME"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use cfg.homedir
instead.
The same applies to the other env_get("HOME")
calls.
if (landlock_add_rule(arg_landlock,LANDLOCK_RULE_PATH_BENEATH,&target,0)) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} | ||
close(home_fd); | ||
if (add_read_access_rule_by_path(arg_landlock, "/bin/")) { | ||
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (landlock_add_rule(arg_landlock,LANDLOCK_RULE_PATH_BENEATH,&target,0)) { | |
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | |
} | |
close(home_fd); | |
if (add_read_access_rule_by_path(arg_landlock, "/bin/")) { | |
fprintf(stderr,"An error has occured while adding a rule to the Landlock ruleset.\n"); | |
} | |
if (landlock_add_rule(arg_landlock, LANDLOCK_RULE_PATH_BENEATH, &target, 0)) { | |
fprintf(stderr, "Error: cannot add a landlock rule to %s\n: %s", | |
cfg.homedir, strerror(errno)); | |
} | |
if (add_read_access_rule_by_path(arg_landlock, "/bin/")) { | |
fprintf(stderr, "Error: cannot add a landlock rule to /bin: \n", | |
strerror(errno)); | |
} |
Make the error messages match the rest of the code and make them more
informative.
The same applies to the other fprintf
calls.
#ifdef HAVE_LANDLOCK | ||
// set Landlock | ||
if (arg_landlock >= 0) { | ||
if (landlock_restrict_self(arg_landlock,0)) { | ||
fprintf(stderr,"An error has occured while enabling Landlock self-restriction. Exiting...\n"); | ||
exit(1); // it isn't safe to continue if Landlock self-restriction was enabled and the "landlock_restrict_self" syscall has failed | ||
} | ||
} | ||
#endif | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#ifdef HAVE_LANDLOCK | |
// set Landlock | |
if (arg_landlock >= 0) { | |
if (landlock_restrict_self(arg_landlock,0)) { | |
fprintf(stderr,"An error has occured while enabling Landlock self-restriction. Exiting...\n"); | |
exit(1); // it isn't safe to continue if Landlock self-restriction was enabled and the "landlock_restrict_self" syscall has failed | |
} | |
} | |
#endif | |
// set Landlock | |
if (arg_landlock >= 0) { | |
// It isn't safe to continue if Landlock self-restriction was | |
// enabled and the "landlock_restrict_self" syscall has failed. | |
if (landlock_restrict_self(arg_landlock, 0) != 0) { | |
errExit("landlock_restrict_self"); | |
} | |
} |
Simplify and use errExit
(see src/include/common.h
).
Also, note that the code indentation usually isn't changed by the presence of
#ifdef
.
#ifdef HAVE_LANDLOCK | ||
// Landlock ruleset paths | ||
if (strcmp(ptr, "landlock") == 0) { | ||
if (arg_landlock == -1) arg_landlock = create_full_ruleset(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (arg_landlock == -1) arg_landlock = create_full_ruleset(); | |
if (arg_landlock == -1) { | |
arg_landlock = create_full_ruleset(); | |
if (!arg_landlock) { | |
fprintf(stderr, "Error: failed to create a landlock full ruleset: %s\n", | |
strerror(errno)); | |
} | |
} |
Check for errors.
The same applies to any other calls of create_full_ruleset
.
Also, shouldn't firejail just exit if something like this fails?
If so, use errExit
instead of fprintf
:
errExit("create_full_ruleset");
#ifdef HAVE_LANDLOCK | ||
extern int arg_landlock; // Landlock ruleset file descriptor | ||
extern int arg_landlock_proc; // Landlock rule for accessing /proc (0 for no access, 1 for read-only and 2 for read-write) | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was very confusing to see an arg_
variable refer to a data structure,
rather than a flag or something that is actually specified on the command line.
I thought that it meant the path argument that is eventually passed to a
landlock call (such as the PATH in landlock.foo PATH
) and only noticed that
that is not the case because argv[i] + N
is also passed to the landlock calls
in main.c:
+ else if (strncmp(argv[i], "--landlock.read=", 16) == 0) { + if (arg_landlock == -1) + arg_landlock = create_full_ruleset(); + if (add_read_access_rule_by_path(arg_landlock, argv[i] + 16)) { + fprintf(stderr, "An error has occured while adding a rule to the Landlock ruleset.\n"); + } + }
Suggestion: Avoid being too clever and just use separate variables. Use
arg_landlock
to check if --landlock
is used (for example, in sandbox.c) and
add a new landlock_ruleset_fd
variable to store the file descriptor (note
that this variable name matches the kernel API):
extern int arg_apparmor; // apparmor
extern char *apparmor_profile; // apparmor profile
+extern int arg_landlock; // Landlock
+extern int arg_landlock_proc; // Landlock rule for accessing /proc (0 for no access, 1 for read-only and 2 for read-write)
+extern int landlock_ruleset_fd; // Landlock ruleset file descriptor
Example usage on main.c:
else if (strcmp(argv[i], "--landlock") == 0) {
+ arg_landlock = 1;
+
- if (arg_landlock == -1)
- arg_landlock = create_full_ruleset();
+ if (landlock_ruleset_fd < 0) {
+ landlock_ruleset_fd = create_full_ruleset();
+ if (landlock_ruleset_fd < 0)
+ errExit("create_full_ruleset");
+ }
The same applies to the other --landlock
arguments (other than maybe setting
arg_landlock = 1
).
Landlock is going in, thanks @ChrysoliteAzalea! |
The build will break for a while on systems without landlock support in the kernel, I'm bringing in a fix - is fixed for now! |
…ock" This reverts commit 54cb3e7, reversing changes made to 97b1e02. There were many issues and requests for changes raised in the pull request (both code-wise and design-wise) and most of them are still unresolved[1]. [1] netblue30#5315
This reverts commit 836ffe3. Relates to netblue30#5315.
…ock" This reverts commit 54cb3e7, reversing changes made to 97b1e02. There were many issues and requests for changes raised in the pull request (both code-wise and design-wise) and most of them are still unresolved[1]. [1] netblue30#5315
This PR was not ready at the time of its merging. I would like to ask for this pull-request to be re-opened. |
Can you reopen a merged PR? I think you can only reopen a closed PR. |
… file" This reverts commit 6e687c3. Relates to netblue30#5315.
This reverts commit 836ffe3. Relates to netblue30#5315.
…ock" This reverts commit 54cb3e7, reversing changes made to 97b1e02. There were many issues and requests for changes raised in the pull request (both code-wise and design-wise) and most of them are still unresolved[1]. [1] netblue30#5315
…ock" This reverts commit 54cb3e7, reversing changes made to 97b1e02. There were many issues and requests for changes raised in the pull request (both code-wise and design-wise) and most of them are still unresolved[1]. [1] netblue30#5315
…ock" This reverts commit 54cb3e7, reversing changes made to 97b1e02. There were many issues and requests for changes raised in the pull request (both code-wise and design-wise) and most of them are still unresolved[1]. [1] netblue30#5315
Commands used to find and print the relevant commits: $ TZ=UTC0 git log --date='format-local:%Y-%m-%d' \ --pretty='%H # %cd | %s' 60db9f7~1..60db9f7 | grep 'Revert "'; git log --reverse --pretty=%b 60db9f7~1..60db9f7 | sed -E -n 's/^This reverts commit ([0-9a-z]+).*/\1/p' | TZ=UTC0 xargs git show --date='format-local:%Y-%m-%d' \ --pretty='%H # %cd | %s' -s | grep -v 'Merge pull request'; TZ=UTC0 git log --no-merges --date='format-local:%Y-%m-%d' \ --pretty='%H # %cd | %s' 54cb3e7~1..54cb3e7 Explanation: The first `git log` basically takes the revision range from one commit before a given merge commit until the merge commit itself and prints the commits in that range (which should usually mean all commits that were in the branch that was merged). In this case, it's the commits that do the revert. The second `git log` finds the hash of all commits that were reverted and prints them. The `grep -v` and third `git log` are only needed because the merge commit of the original branch was reverted directly (on commit 97874c3), rather than reverting each individual commit on that branch. So these commands are used to print all of the commits in the original branch. Relates to netblue30#5315 netblue30#5347.
Hello everyone!
In Linux kernel 5.13 version, a new LSM was added called "Landlock" that allows unprivileged processes to impose filesystem access self-restrictions (similar to the "unveil" in OpenBSD). It's similar to another Linux kernel feature -- seccomp, that allows unprivileged processes to impose system call access self-restrictions. I propose using this LSM in Firejail, and I've added support for four options (for both command-line and profiles) that create and populate the ruleset. I think it's a feature worth having, if we plan to make Firejail usable without SUID bit.
Landlock support was proposed in issues #3992 and #5269.
Enabling Landlock self-restriction requires a process to have either CAP_SYS_ADMIN in an effective capability set, or the "No New Privileges" enabled.
P.S. I'd like to note, that this PR uses my tinyLL library that has to be built before the version of Firejail in this PR, and has to be located in a directory like /usr/lib.Necessary functions have been added to Firejail's code.