Skip to content

Commit

Permalink
feature: create the link only if its endpoint is available
Browse files Browse the repository at this point in the history
  • Loading branch information
i-pankrat committed Oct 10, 2024
1 parent 116f7bf commit 21e74bb
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 17 deletions.
15 changes: 15 additions & 0 deletions src/firejail/firejail.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#define DEFAULT_ROOT_PROFILE "server"
#define MAX_INCLUDE_LEVEL 16 // include levels in profile files

#define MAX_LINKS_LEVEL 5 // maximum link depth

#define ASSERT_PERMS(file, uid, gid, mode) \
do { \
Expand Down Expand Up @@ -162,6 +163,13 @@ typedef struct landlock_entry_t {
char *data;
} LandlockEntry;

typedef struct delayed_link_entry_t {
struct delayed_link_entry_t *next;
char link_filenames[MAX_LINKS_LEVEL][PATH_MAX]; // contains all links from initial file to final one
char dst[PATH_MAX];
int size; // size of link_filenames
} DelayedLinkEntry;

typedef struct config_t {
// user data
char *username;
Expand All @@ -172,6 +180,7 @@ typedef struct config_t {
ProfileEntry *profile;
ProfileEntry *profile_rebuild_etc; // blacklist files in /etc directory used by fs_rebuild_etc()
LandlockEntry *lprofile;
DelayedLinkEntry *delayed_links; // delay links until we can copy them correctly

#define MAX_PROFILE_IGNORE 32
char *profile_ignore[MAX_PROFILE_IGNORE];
Expand Down Expand Up @@ -495,6 +504,10 @@ char *profile_list_normalize(char *list);
char *profile_list_compress(char *list);
void profile_list_augment(char **list, const char *items);

void fs_create_delayed_links();
void remove_blacklisted_delayed_links(const char *blacklist_path, bool can_whitelist_save);
void delayed_links_add(char *src, char *dst);

// list.c
void list(void);
void tree(void);
Expand Down Expand Up @@ -614,6 +627,8 @@ int ascii_isupper(unsigned char c);
int ascii_isxdigit(unsigned char c);
int invalid_name(const char *name);
void check_homedir(const char *dir);
void normalize_link_path(const char *link_src, const char *link_dst, char *res);
bool dir_contains(const char *directory, const char *item);

// Get info regarding the last kernel mount operation from /proc/self/mountinfo
// The return value points to a static area, and will be overwritten by subsequent calls.
Expand Down
2 changes: 2 additions & 0 deletions src/firejail/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ static void disable_file(OPERATION op, const char *filename) {

// modify the file
if (op == BLACKLIST_FILE || op == BLACKLIST_NOLOG) {
remove_blacklisted_delayed_links(fname, false);

// some distros put all executables under /usr/bin and make /bin a symbolic link
if ((strcmp(fname, "/bin") == 0 || strcmp(fname, "/usr/bin") == 0) &&
is_link(filename) &&
Expand Down
29 changes: 22 additions & 7 deletions src/firejail/fs_etc.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,19 +285,34 @@ static void duplicate(const char *fname, const char *private_dir, const char *pr

build_dirs(src, dst, strlen(private_dir), strlen(private_run_dir));

// follow links by default, thus making a copy of the file or directory pointed by the symlink
// 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);
else
// Delay link creation until we are sure that the final link file
// will (or will not) be available in the sandbox. If the final link file is not available,
// it will not be created. A warning will be written.
if (is_link(src)) {
if (arg_debug)
printf("Delay creating link %s in the sandbox\n", src);
delayed_links_add(src, dst);
} else
sbox_run(SBOX_ROOT | SBOX_SECCOMP, 4, PATH_FCOPY, "--follow-link", src, dst);

free(dst);
fs_logger2("clone", src);
}

void fs_create_delayed_links() {
DelayedLinkEntry *ptr = cfg.delayed_links;
DelayedLinkEntry *tmp = NULL;

while (ptr) {
if (arg_debug)
printf("Create delayed link %s\n", ptr->link_filenames[0]);
sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FCOPY, ptr->link_filenames[0], ptr->dst);
tmp = ptr;
ptr = ptr->next;
free(tmp);
}
}

static void duplicate_globbing(const char *fname, const char *private_dir, const char *private_run_dir) {
assert(fname);

Expand Down
5 changes: 5 additions & 0 deletions src/firejail/fs_whitelist.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,11 @@ static void tmpfs_topdirs(const TopDir * const topdirs) {
continue;
}

// Check whether bind mount of tmpfs on topdirs[i].path will affect delayed links
// Bind mount of tmpfs on topdirs[i].path has the same meaning that blacklisting topdirs[i].path)
// exсept that whitelisted entries will be restored inside topdirs[i].path later.
remove_blacklisted_delayed_links(topdirs[i].path, true);

// special case /run
// open /run/firejail, so it can be restored right after mounting the tmpfs
int fd = -1;
Expand Down
116 changes: 116 additions & 0 deletions src/firejail/profile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1759,6 +1759,122 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
return 1;
}

bool does_whitelist_entry_save_file(const char *file, const char *whitelisted_file) {
bool is_whitelisted = false;
if (!is_dir(whitelisted_file))
is_whitelisted = strcmp(whitelisted_file, file) == 0;
else
is_whitelisted = dir_contains(whitelisted_file, file);

return is_whitelisted;
}

bool does_whitelist_save_file(const char *file) {
ProfileEntry *entry = cfg.profile;
bool is_whitelisted = false;

while (entry) {
if (entry->wparam) {
char *wfile = entry->wparam->file;
char *wlink = entry->wparam->link;

is_whitelisted = does_whitelist_entry_save_file(file, wfile);
if (!is_whitelisted && wlink) {
is_whitelisted = does_whitelist_entry_save_file(file, wlink);
}

if (is_whitelisted)
return true;
}

entry = entry->next;
}

return false;
}

bool is_link_blacklisted(DelayedLinkEntry *link, const char *rblacklist_path, bool can_whitelist_save) {
for (int i = 0; i < link->size; i++) {
bool is_blacklisted = false;

if (!is_dir(rblacklist_path))
is_blacklisted = strcmp(link->link_filenames[i], rblacklist_path) == 0;
else
is_blacklisted = dir_contains(rblacklist_path, link->link_filenames[i]);

if (is_blacklisted) {
// used to check whether whitelist will save link
if (!can_whitelist_save || can_whitelist_save && !does_whitelist_save_file(link->link_filenames[i]))
return true;
}
}

return false;
}

void remove_blacklisted_delayed_links(const char *blacklist_path, bool can_whitelist_save) {
char *rblacklist_path = realpath(blacklist_path, NULL);
if (rblacklist_path == NULL)
errExit("realpath");

DelayedLinkEntry *prev = NULL;
DelayedLinkEntry *ptr = cfg.delayed_links;

while (ptr) {
if (is_link_blacklisted(ptr, rblacklist_path, can_whitelist_save)) {

fprintf(stderr, "***\n");
fprintf(stderr, "*** Warning: cannot create delayed link %s\n", ptr->link_filenames[0]);
fprintf(stderr, "*** One of the intermediate links or the final file blacklisted by path %s\n", rblacklist_path);
fprintf(stderr, "***\n");

// Remove link from the delayed links
if (prev)
prev->next = ptr->next;
else
cfg.delayed_links = ptr->next;

DelayedLinkEntry *tmp = ptr;
ptr = ptr->next;
free(tmp);
} else {
prev = ptr;
ptr = ptr->next;
}
}
}

// Accept normalized absolute path
void delayed_links_add(char *src, char *dst) {
char link_point[PATH_MAX];
DelayedLinkEntry *link = malloc(sizeof(DelayedLinkEntry));

if (link == NULL)
errExit("malloc");

link->size = 0;
char *link_src = link->link_filenames[link->size++];
strncpy(link_src, src, PATH_MAX);
link_src[PATH_MAX - 1] = '\0';
strncpy(link->dst, dst, PATH_MAX);
link->dst[PATH_MAX - 1] = '\0';

char *cur_absolute = src;
size_t link_point_len = 0;

while ((link_point_len = readlink(cur_absolute, link_point, PATH_MAX)) != (size_t)-1) {
if (link->size == MAX_LINKS_LEVEL)
errExit("too many intermediate links");

link_point[link_point_len] = '\0';
normalize_link_path(cur_absolute, link_point, link->link_filenames[link->size]);
cur_absolute = link->link_filenames[link->size++];
}

link->next = cfg.delayed_links;
cfg.delayed_links = link;
}

// add a profile entry in cfg.profile list; use str to populate the list
void profile_add(char *str) {
EUID_ASSERT();
Expand Down
29 changes: 19 additions & 10 deletions src/firejail/sandbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,7 @@ int sandbox(void* sandbox_arg) {
fs_mnt(0);

// Install new /etc last, so we can use it as long as possible
float time_private_etc = 0;
if (arg_private_etc) {
if (cfg.chrootdir)
fwarning("private-etc feature is disabled in chroot\n");
Expand All @@ -1051,17 +1052,8 @@ int sandbox(void* sandbox_arg) {
if (umount2("/etc/passwd", MNT_DETACH) == -1)
fprintf(stderr, "/etc/passwd: unmount: %s\n", strerror(errno));

fs_private_dir_mount("/etc", RUN_ETC_DIR);
fmessage("Private /etc installed in %0.2f ms\n", timetrace_end());
time_private_etc += timetrace_end();

// create /etc/ld.so.preload file again
if (need_preload)
fs_trace_touch_preload();

// openSUSE configuration is split between /etc and /usr/etc
// process private-etc a second time
if (access("/usr/etc", F_OK) == 0)
fs_private_dir_list("/usr/etc", RUN_USR_ETC_DIR, cfg.etc_private_keep);
}
}

Expand All @@ -1076,6 +1068,23 @@ int sandbox(void* sandbox_arg) {
fs_blacklist(); // mkdir and mkfile are processed all over again
EUID_ROOT();

if (arg_private_etc && !cfg.chrootdir && !arg_overlay) {
timetrace_start();
// create delayed links from etc
fs_create_delayed_links();
fs_private_dir_mount("/etc", RUN_ETC_DIR);
fmessage("Private /etc installed in %0.2f ms\n", timetrace_end());

// create /etc/ld.so.preload file again
if (need_preload)
fs_trace_touch_preload();

// openSUSE configuration is split between /etc and /usr/etc
// process private-etc a second time
if (access("/usr/etc", F_OK) == 0)
fs_private_dir_list("/usr/etc", RUN_USR_ETC_DIR, cfg.etc_private_keep);
}

//****************************
// nosound/no3d/notv/novideo and fix for pulseaudio 7.0
//****************************
Expand Down
79 changes: 79 additions & 0 deletions src/firejail/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -1541,3 +1541,82 @@ void check_homedir(const char *dir) {
fmessage("No full support for symbolic links in path of user directory.\n"
"Please provide resolved path in password database (/etc/passwd).\n\n");
}

/*
* Normalize link destination path according to link source path
*
* Examples:
* Input: /usr/share/timezone/localtime, Europe/Moscow, ...
* Output: /usr/share/timezone/Europe/Moscow
*
* Input: /usr/share/timezone/localtime, ../../../../////usr/share//././timezone/Europe/Moscow, ...
* Output: /usr/share/timezone/Europe/Moscow
*
* Input: /usr/share/timezone/localtime, /some/other/location/./Europe/Moscow, ...
* Output: /some/other/location/Europe/Moscow
*/
void normalize_link_path(const char *link_src, const char *link_dst, char *res)
{
size_t res_len = 0;
size_t link_dst_len = strlen(link_dst);
const char *ptr = link_dst;
const char *end = &link_dst[link_dst_len];
const char *next;

if (link_dst[0] != '/') {
// relative path
size_t link_src_len = strlen(link_src);
if (!is_dir(link_src)) {
char *slash = strrchr(link_src, '/');
if (slash != NULL)
link_src_len = slash - link_src;
}

memcpy(res, link_src, link_src_len);
res_len = link_src_len;
}

for (ptr = link_dst; ptr < end; ptr = next + 1) {
size_t len;
next = memchr(ptr, '/', end - ptr);
if (next == NULL)
next = end;
len = next - ptr;

switch (len) {
case 2:
if (ptr[0] == '.' && ptr[1] == '.') {
const char *slash = memrchr(res, '/', res_len);
if (slash != NULL)
res_len = slash - res;
continue;
}
break;
case 1:
if (ptr[0] == '.')
continue;
break;
case 0:
continue;
}
res[res_len++] = '/';
memcpy(&res[res_len], ptr, len);
res_len += len;
}

if (res_len == 0)
res[res_len++] = '/';

res[res_len] = '\0';
}

// Checks if the path is a subpath
// example: /some/path /some/path/some/subpath
bool dir_contains(const char *directory, const char *item) {
char tmp[PATH_MAX];
strcpy(tmp, directory);
size_t tmp_len = strlen(tmp);
tmp[tmp_len++] = '/';
tmp[tmp_len] = '\0';
return strncmp(tmp, item, tmp_len) == 0;
}

0 comments on commit 21e74bb

Please sign in to comment.