-
Notifications
You must be signed in to change notification settings - Fork 247
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
I propose adding Landlock support to Bubblewrap #519
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -34,10 +34,13 @@ | |||||
#include <linux/sched.h> | ||||||
#include <linux/seccomp.h> | ||||||
#include <linux/filter.h> | ||||||
#include <linux/landlock.h> | ||||||
|
||||||
#include "utils.h" | ||||||
#include "network.h" | ||||||
#include "bind-mount.h" | ||||||
#include "ll_wrapper.h" | ||||||
#include "add_rule.h" | ||||||
|
||||||
#ifndef CLONE_NEWCGROUP | ||||||
#define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ | ||||||
|
@@ -85,6 +88,7 @@ int opt_userns_block_fd = -1; | |||||
int opt_info_fd = -1; | ||||||
int opt_json_status_fd = -1; | ||||||
int opt_seccomp_fd = -1; | ||||||
int opt_landlock_ruleset_fd = -1; | ||||||
const char *opt_sandbox_hostname = NULL; | ||||||
char *opt_args_data = NULL; /* owned */ | ||||||
int opt_userns_fd = -1; | ||||||
|
@@ -331,6 +335,7 @@ usage (int ecode, FILE *out) | |||||
" --symlink SRC DEST Create symlink at DEST with target SRC\n" | ||||||
" --seccomp FD Load and use seccomp rules from FD (not repeatable)\n" | ||||||
" --add-seccomp-fd FD Load and use seccomp rules from FD (repeatable)\n" | ||||||
" --landlock Enable Landlock self-restriction\n" | ||||||
" --block-fd FD Block on FD until some data to read is available\n" | ||||||
" --userns-block-fd FD Block on FD until the user namespace is ready\n" | ||||||
" --info-fd FD Write information about the running container to FD\n" | ||||||
|
@@ -1070,6 +1075,13 @@ privileged_op (int privileged_op_socket, | |||||
break; | ||||||
|
||||||
case PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE: | ||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
int exposed_fd = open(arg2,O_PATH | O_CLOEXEC); | ||||||
add_read_access_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
add_execute_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
close(exposed_fd); | ||||||
} | ||||||
bind_result = bind_mount (proc_fd, NULL, arg2, BIND_READONLY); | ||||||
|
||||||
if (bind_result != BIND_MOUNT_SUCCESS) | ||||||
|
@@ -1081,6 +1093,15 @@ privileged_op (int privileged_op_socket, | |||||
case PRIV_SEP_OP_BIND_MOUNT: | ||||||
/* We always bind directories recursively, otherwise this would let us | ||||||
access files that are otherwise covered on the host */ | ||||||
|
||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
int exposed_fd = open(arg1,O_PATH | O_CLOEXEC); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bubblewrap is in a GNUish coding style, the same as GLib and Flatpak (I'm not going to repeat this everywhere, but the same principles apply everywhere):
Suggested change
|
||||||
add_read_access_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
add_write_access_rule(opt_landlock_ruleset_fd,exposed_fd,1); | ||||||
add_execute_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
close(exposed_fd); | ||||||
Comment on lines
+1099
to
+1103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like something that would benefit from being factored out into a helper function. |
||||||
} | ||||||
bind_result = bind_mount (proc_fd, arg1, arg2, BIND_RECURSIVE | flags); | ||||||
|
||||||
if (bind_result != BIND_MOUNT_SUCCESS) | ||||||
|
@@ -1090,28 +1111,62 @@ privileged_op (int privileged_op_socket, | |||||
break; | ||||||
|
||||||
case PRIV_SEP_OP_PROC_MOUNT: | ||||||
if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) | ||||||
die_with_error ("Can't mount proc on %s", arg1); | ||||||
break; | ||||||
{ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't need re-indenting into a block, and it would be more obviously correct without the re-indent. |
||||||
if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) | ||||||
die_with_error ("Can't mount proc on %s", arg1); | ||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
int exposed_fd = open(arg1,O_PATH | O_CLOEXEC); | ||||||
add_read_access_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
add_write_access_rule(opt_landlock_ruleset_fd,exposed_fd,1); | ||||||
add_execute_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
close(exposed_fd); | ||||||
} | ||||||
break; | ||||||
} | ||||||
|
||||||
case PRIV_SEP_OP_TMPFS_MOUNT: | ||||||
{ | ||||||
cleanup_free char *mode = xasprintf ("mode=%#o", perms); | ||||||
cleanup_free char *opt = label_mount (mode, opt_file_label); | ||||||
if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0) | ||||||
die_with_error ("Can't mount tmpfs on %s", arg1); | ||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
int exposed_fd = open(arg1,O_PATH | O_CLOEXEC); | ||||||
add_read_access_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
add_write_access_rule(opt_landlock_ruleset_fd,exposed_fd,1); | ||||||
add_execute_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
close(exposed_fd); | ||||||
} | ||||||
break; | ||||||
} | ||||||
|
||||||
case PRIV_SEP_OP_DEVPTS_MOUNT: | ||||||
if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC, | ||||||
"newinstance,ptmxmode=0666,mode=620") != 0) | ||||||
die_with_error ("Can't mount devpts on %s", arg1); | ||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
int exposed_fd = open(arg1,O_PATH | O_CLOEXEC); | ||||||
add_read_access_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
add_write_access_rule(opt_landlock_ruleset_fd,exposed_fd,0); | ||||||
add_execute_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
close(exposed_fd); | ||||||
} | ||||||
break; | ||||||
|
||||||
case PRIV_SEP_OP_MQUEUE_MOUNT: | ||||||
if (mount ("mqueue", arg1, "mqueue", 0, NULL) != 0) | ||||||
die_with_error ("Can't mount mqueue on %s", arg1); | ||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
int exposed_fd = open(arg1,O_PATH | O_CLOEXEC); | ||||||
add_read_access_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
add_write_access_rule(opt_landlock_ruleset_fd,exposed_fd,1); | ||||||
add_execute_rule(opt_landlock_ruleset_fd,exposed_fd); | ||||||
close(exposed_fd); | ||||||
} | ||||||
break; | ||||||
|
||||||
case PRIV_SEP_OP_SET_HOSTNAME: | ||||||
|
@@ -1705,6 +1760,16 @@ parse_args_recurse (int *argcp, | |||||
argv += 1; | ||||||
argc -= 1; | ||||||
} | ||||||
else if (strcmp (arg, "--landlock") == 0) | ||||||
{ | ||||||
struct landlock_ruleset_attr llattrs; | ||||||
llattrs.handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE | 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; | ||||||
opt_landlock_ruleset_fd = landlock_create_ruleset(&llattrs,sizeof(llattrs),0); | ||||||
if (opt_landlock_ruleset_fd<0) { | ||||||
warn("An error has occured while creating Landlock ruleset. The sandbox will start without Landlock protection.\n"); | ||||||
opt_landlock_ruleset_fd = -1; | ||||||
} | ||||||
} | ||||||
else if (strcmp (arg, "--unshare-all") == 0) | ||||||
{ | ||||||
/* Keep this in order with the older (legacy) --unshare arguments, | ||||||
|
@@ -3214,6 +3279,11 @@ main (int argc, | |||||
|
||||||
/* Should be the last thing before execve() so that filters don't | ||||||
* need to handle anything above */ | ||||||
Comment on lines
3280
to
3281
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment should stay attached to |
||||||
if (opt_landlock_ruleset_fd>-1) | ||||||
{ | ||||||
if (landlock_restrict_self(opt_landlock_ruleset_fd,0)!=0) | ||||||
warn("An error has occured while enabling Landlock restrictions. The sandbox will start without Landlock protection.\n"); | ||||||
Comment on lines
+3284
to
+3285
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want Landlock to be a security restriction, then it should fail closed. We can have a separate |
||||||
} | ||||||
seccomp_programs_apply (); | ||||||
|
||||||
if (setup_finished_pipe[1] != -1) | ||||||
|
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.
Because bubblewrap is sometimes setuid root, we have to be very careful about external dependencies: every external dependency has to be written in a way that makes it safe to call into with elevated privileges, and maintained in a way that treats misplaced trust in the execution environment as a security vulnerability. I'm reasonably confident that glibc, libcap and libselinux are like that, but you'll notice we don't depend on anything larger, like GLib.
Is your Landlock library intended to be setuid-safe?
Is this a personal project, or does it have a bus factor of more than 1?
bubblewrap is already short of maintainer bandwidth (as you can see from the time it took to get this reviewed), so it is not really viable for us to end up in a situation where the bubblewrap maintainers also become responsible for maintaining and securing a Landlock helper library.
If this is going to be an external dependency, then it would have to be integrated into the build system properly, and build-time optional, like the libselinux code already is.
--landlock
would fail with an error message if the external dependency was not enabled. If we had a new option like--landlock-try
, it could carry on regardless.If you can guarantee that a Landlock library will be safe for setuid processes to link against (no constructors/initializers that e.g. trust environment variables), then another possibility would be to link to the library, but refuse to do anything with it (make
--landlock
fail with an error) if we find that we're setuid, similar to what's done for--userns
.Alternatively, if what you're doing with Landlock is sufficiently simple, then bwrap could open-code the syscalls in its own code. We'd have to look at a proposed implementation to make a judgement on whether it's small enough that we can take responsibility for its security, and we'd also have to think about cost vs. benefit in terms of what new hardening or new features Landlock gives us.