Skip to content
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

feature: add keep-xattrs option #5136

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contrib/vim/syntax/firejail.vim
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ syn match fjVar /\v\$\{(CFG|DESKTOP|DOCUMENTS|DOWNLOADS|HOME|MUSIC|PATH|PICTURES
" 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|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(bind|blacklist|blacklist-nolog|cgroup|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
" Generate list with: rg -o 'strn?cmp\(ptr, "([^ "]*[^ ])"' -r '$1' src/firejail/profile.c | grep -vEx '(include|rlimit|quiet)' | sed -e 's/\./\\./' | sort -u | tr $'\n' '|' # include/rlimit are false positives, quiet is special-cased below
syn match fjCommand /\v(allow-debuggers|allusers|apparmor|caps|deterministic-exit-code|deterministic-shutdown|disable-mnt|ipc-namespace|keep-config-pulse|keep-dev-shm|keep-fd|keep-var-tmp|machine-id|memory-deny-write-execute|netfilter|no3d|noautopulse|nodbus|nodvd|nogroups|noinput|nonewprivs|noprinters|noroot|nosound|notv|nou2f|novideo|overlay|overlay-tmpfs|private|private-cache|private-cwd|private-dev|private-lib|private-tmp|seccomp|seccomp\.32|seccomp\.block-secondary|tracelog|writable-etc|writable-run-user|writable-var|writable-var-log|x11)$/ contained
syn match fjCommand /\v(allow-debuggers|allusers|apparmor|caps|deterministic-exit-code|deterministic-shutdown|disable-mnt|ipc-namespace|keep-config-pulse|keep-dev-shm|keep-fd|keep-xattrs|keep-var-tmp|machine-id|memory-deny-write-execute|netfilter|no3d|noautopulse|nodbus|nodvd|nogroups|noinput|nonewprivs|noprinters|noroot|nosound|notv|nou2f|novideo|overlay|overlay-tmpfs|private|private-cache|private-cwd|private-dev|private-lib|private-tmp|seccomp|seccomp\.32|seccomp\.block-secondary|tracelog|writable-etc|writable-run-user|writable-var|writable-var-log|x11)$/ contained
syn match fjCommand /ignore / nextgroup=fjCommand,fjCommandNoCond skipwhite contained
syn match fjCommand /caps\.drop / nextgroup=fjCapability,fjAll skipwhite contained
syn match fjCommand /caps\.keep / nextgroup=fjCapability skipwhite contained
Expand Down
105 changes: 90 additions & 15 deletions src/fcopy/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include <ftw.h>
#include <errno.h>
#include <pwd.h>
#include <limits.h>
#include <sys/xattr.h>

#include <fcntl.h>
#ifndef O_PATH
Expand All @@ -48,6 +50,7 @@ static unsigned file_cnt = 0;

static char *outpath = NULL;
static char *inpath = NULL;
static char **keep_xattrs = NULL;

#if HAVE_SELINUX
static struct selabel_handle *label_hnd = NULL;
Expand Down Expand Up @@ -100,6 +103,79 @@ static void selinux_relabel_path(const char *path, const char *inside_path) {
#endif
}

// Convert a string of comma-separated values into a NULL-terminated string array.
// The original string is modified in-place.
static char **csv_to_strv(char *clist)
{
// Don't try to split an empty string
if (!clist || *clist == '\0')
return NULL;

// Count items first (number of commas + 1)
size_t strv_len = 1;
for (char *ptr = clist; *ptr; ++ptr) {
if (*ptr == ',')
++strv_len;
}

// 1 more item is allocated for the NULL terminator
char **strv = calloc(strv_len + 1, sizeof(char *));
if (!strv)
errExit("calloc");

char **strvp = strv;
while (clist && *clist != '\0') {
char *end = strchr(clist, ',');
if (end) {
*end = '\0';
end++;
}

*strvp = strdup(clist);
if (!*strvp)
errExit("strdup");

strvp++;
clist = end;
}

return strv;
}

static void copy_xattr(const char *src, const char *dst, const char *name)
{
static unsigned char *xattr_buf = NULL;

if (!xattr_buf) {
xattr_buf = malloc(XATTR_SIZE_MAX);
if (!xattr_buf)
errExit("malloc");
}

ssize_t len = lgetxattr(src, name, xattr_buf, XATTR_SIZE_MAX);
if (len == -1) {
// It's okay for some files to have no desired xattrs,
// so print this message only for debug purposes
if (arg_debug)
printf("Warning fcopy: cannot get %s xattr on %s, xattr is not copied\n", name, src);
return;
}

if (lsetxattr(dst, name, xattr_buf, len, 0) != 0 && !arg_quiet)
printf("Warning fcopy: cannot set %s xattr on %s, xattr is not copied\n", name, dst);
}

static void copy_xattrs(const char *src, const char *dst, char *const names[])
{
if (names == NULL)
return;

while (*names) {
copy_xattr(src, dst, *names);
names++;
}
}

// modified version of the function from util.c
static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) {
assert(srcname);
Expand Down Expand Up @@ -153,6 +229,7 @@ static void copy_file(const char *srcname, const char *destname, mode_t mode, ui
close(dst);

selinux_relabel_path(destname, srcname);
copy_xattrs(srcname, destname, keep_xattrs);

return;

Expand Down Expand Up @@ -418,7 +495,7 @@ static void duplicate_link(const char *src, const char *dest, struct stat *s) {


static void usage(void) {
fputs("Usage: fcopy [--follow-link] src dest\n"
fputs("Usage: fcopy [--follow-link] [--keep-xattrs <xattrs>] src dest\n"
"\n"
"Copy SRC to DEST/SRC. SRC may be a file, directory, or symbolic link.\n"
"If SRC is a directory it is copied recursively. If it is a symlink,\n"
Expand All @@ -445,25 +522,23 @@ int main(int argc, char **argv) {
if (debug && strcmp(debug, "yes") == 0)
arg_debug = 1;

char *src;
char *dest;

if (argc == 3) {
src = argv[1];
dest = argv[2];
arg_follow_link = 0;
}
else if (argc == 4 && !strcmp(argv[1], "--follow-link")) {
src = argv[2];
dest = argv[3];
arg_follow_link = 1;
}
else {
if (argc < 3) {
fprintf(stderr, "Error: arguments missing\n");
usage();
exit(1);
}

// parse arguments, excluding positional ones
for (int i = 1; i < argc - 2; ++i) {
if (strcmp(argv[i], "--follow-link") == 0)
arg_follow_link = 1;
else if (strcmp(argv[i], "--keep-xattrs") == 0)
keep_xattrs = csv_to_strv(argv[++i]);
}

char *src = argv[argc - 2];
char *dest = argv[argc - 1];

warn_dumpable();

// check the two files; remove ending /
Expand Down
2 changes: 1 addition & 1 deletion src/firejail/dhcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ void dhcp_store_exec(void) {
}
}

sbox_run(SBOX_ROOT| SBOX_SECCOMP, 4, PATH_FCOPY, "--follow-link", dhclient_path, RUN_MNT_DIR);
sbox_run(SBOX_ROOT| SBOX_SECCOMP, 6, PATH_FCOPY, "--follow-link", "--keep-xattrs", cfg.keep_xattrs, dhclient_path, RUN_MNT_DIR);
}

void dhcp_start(void) {
Expand Down
1 change: 1 addition & 0 deletions src/firejail/firejail.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ typedef struct config_t {
#define MAX_PROFILE_IGNORE 32
char *profile_ignore[MAX_PROFILE_IGNORE];
char *keep_fd; // inherit file descriptors to sandbox
char *keep_xattrs; // keep xattrs on copied files
char *chrootdir; // chroot directory
char *home_private; // private home directory
char *home_private_keep; // keep list for private home directory
Expand Down
4 changes: 2 additions & 2 deletions src/firejail/fs_bin.c
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ static void duplicate(char *fname) {
if (valid_full_path_file(actual_path)) {
// solving problems such as /bin/sh -> /bin/dash
// copy the real file pointed by symlink
sbox_run(SBOX_ROOT| SBOX_SECCOMP, 3, PATH_FCOPY, actual_path, RUN_BIN_DIR);
sbox_run(SBOX_ROOT| SBOX_SECCOMP, 5, PATH_FCOPY, "--keep-xattrs", cfg.keep_xattrs, actual_path, RUN_BIN_DIR);
prog_cnt++;
char *f = strrchr(actual_path, '/');
if (f && *(++f) !='\0')
Expand All @@ -198,7 +198,7 @@ static void duplicate(char *fname) {
}

// copy a file or a symlink
sbox_run(SBOX_ROOT| SBOX_SECCOMP, 3, PATH_FCOPY, full_path, RUN_BIN_DIR);
sbox_run(SBOX_ROOT| SBOX_SECCOMP, 5, PATH_FCOPY, "--keep-xattrs", cfg.keep_xattrs, full_path, RUN_BIN_DIR);
prog_cnt++;
free(full_path);
report_duplication(fname);
Expand Down
4 changes: 2 additions & 2 deletions src/firejail/fs_etc.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,9 @@ static void duplicate(const char *fname, const char *private_dir, const char *pr
// this will solve problems such as NixOS #4887
// don't follow links to dynamic directories such as /proc
if (strcmp(src, "/etc/mtab") == 0)
sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FCOPY, src, dst);
sbox_run(SBOX_ROOT | SBOX_SECCOMP, 5, PATH_FCOPY, "--keep-xattrs", cfg.keep_xattrs, src, dst);
else
sbox_run(SBOX_ROOT | SBOX_SECCOMP, 4, PATH_FCOPY, "--follow-link", src, dst);
sbox_run(SBOX_ROOT | SBOX_SECCOMP, 6, PATH_FCOPY, "--follow-link", "--keep-xattrs", cfg.keep_xattrs, src, dst);

free(dst);
fs_logger2("clone", src);
Expand Down
4 changes: 2 additions & 2 deletions src/firejail/fs_home.c
Original file line number Diff line number Diff line change
Expand Up @@ -569,11 +569,11 @@ static void duplicate(char *name) {
if (asprintf(&path, "%s/%s", RUN_HOME_DIR, ptr) == -1)
errExit("asprintf");
create_empty_dir_as_user(path, 0755);
sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FCOPY, fname, path);
sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 5, PATH_FCOPY, "--keep-xattrs", cfg.keep_xattrs, fname, path);
free(path);
}
else
sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FCOPY, fname, RUN_HOME_DIR);
sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 5, PATH_FCOPY, "--keep-xattrs", cfg.keep_xattrs, fname, RUN_HOME_DIR);
fs_logger2("clone", fname);
fs_logger_print(); // save the current log

Expand Down
8 changes: 8 additions & 0 deletions src/firejail/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,10 @@ int main(int argc, char **argv, char **envp) {
profile_list_augment(&cfg.keep_fd, add);
}
}
else if (strncmp(argv[i], "--keep-xattrs=", 14) == 0) {
const char *xattr = argv[i] + 14;
profile_list_augment(&cfg.keep_xattrs, xattr);
}
#ifdef HAVE_CHROOT
else if (strncmp(argv[i], "--chroot=", 9) == 0) {
if (checkcfg(CFG_CHROOT)) {
Expand Down Expand Up @@ -2972,6 +2976,10 @@ int main(int argc, char **argv, char **envp) {
}
EUID_ASSERT();

// make sure that the xattr list is not null
if (!cfg.keep_xattrs)
cfg.keep_xattrs = "";

// block X11 sockets
if (arg_x11_block)
x11_block();
Expand Down
5 changes: 5 additions & 0 deletions src/firejail/profile.c
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,11 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
}
return 0;
}
if (strncmp(ptr, "keep-xattrs ", 12) == 0) {
const char *xattr = ptr + 12;
profile_list_augment(&cfg.keep_xattrs, xattr);
return 0;
}
if (strncmp(ptr, "xephyr-screen ", 14) == 0) {
#ifdef HAVE_X11
if (checkcfg(CFG_X11)) {
Expand Down
1 change: 1 addition & 0 deletions src/firejail/usage.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ static char *usage_str =
" --keep-config-pulse - disable automatic ~/.config/pulse init.\n"
" --keep-dev-shm - /dev/shm directory is untouched (even with --private-dev).\n"
" --keep-fd - inherit open file descriptors to sandbox.\n"
" --keep-xattrs=name,name,name - copy the specified extended attributes to the sandbox.\n"
" --keep-var-tmp - /var/tmp directory is untouched.\n"
" --list - list all sandboxes.\n"
#ifdef HAVE_FILE_TRANSFER
Expand Down
4 changes: 4 additions & 0 deletions src/man/firejail-profile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,10 @@ Enable IPC namespace.
\fBkeep-fd
Inherit open file descriptors to sandbox.

.TP
\fBkeep-xattrs name,name,name
Copy the specified extended attributes to the sandbox.

.TP
\fBname sandboxname
Set sandbox name. Example:
Expand Down
10 changes: 10 additions & 0 deletions src/man/firejail.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,16 @@ Example:
.br
$ firejail --keep-fd=3,4,5

.TP
\fB\-\-keep-xattrs=name,name,name
Copy the specified extended attributes. By default, none of the xattrs are copied for files in private-bin, private-etc and private-home. Attributes are always preserved for bind-mounted files. See xattr(7) for details.
.br

.br
Example:
.br
$ firejail --keep-xattrs=security.ima,security.SMACK64

.TP
\fB\-\-keep-var-tmp
/var/tmp directory is untouched.
Expand Down
1 change: 1 addition & 0 deletions src/zsh_completion/_firejail.in
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ _firejail_args=(
'--keep-config-pulse[disable automatic ~/.config/pulse init]'
'--keep-dev-shm[/dev/shm directory is untouched (even with --private-dev)]'
'--keep-fd[inherit open file descriptors to sandbox]'
'*--keep-xattrs=-[copy the specified extended attributes to the sandbox]'
'--keep-var-tmp[/var/tmp directory is untouched]'
'--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]'
Expand Down