diff --git a/RELNOTES b/RELNOTES index 731f03eb76d..a11674bab5a 100644 --- a/RELNOTES +++ b/RELNOTES @@ -1,7 +1,9 @@ -firejail (0.9.34) baseline; urgency=low +firejail (0.9.35) baseline; urgency=low * added unbound and dnscrypt-proxy profiles * added --noblacklist option * whitelist command enhancements + * prevent leaking user information by modifying /home directory, + /etc/passwd and /etc/group * bugfixes -- netblue30 ongoing development diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index b29e11923e3..25a39d0c42c 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -52,6 +52,8 @@ #define RESOLVCONF_FILE "/run/firejail/mnt/resolv.conf" #define LDPRELOAD_FILE "/run/firejail/mnt/ld.so.preload" #define UTMP_FILE "/run/firejail/mnt/utmp" +#define PASSWD_FILE "/run/firejail/mnt/passwd" +#define GROUP_FILE "/run/firejail/mnt/group" // profiles #define DEFAULT_USER_PROFILE "generic" @@ -468,5 +470,10 @@ void protocol_store(const char *prlist); void protocol_filter(void); void protocol_filter_save(void); void protocol_filter_load(const char *fname); + +// restrict_users.c +void restrict_users(void); + + #endif diff --git a/src/firejail/fs.c b/src/firejail/fs.c index aec1698b053..ad84b22223b 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c @@ -539,49 +539,6 @@ void fs_proc_sys_dev_boot(void) { } } -static void sanitize_home(void) { - assert(getuid() != 0); // this code works only for regular users - - if (arg_debug) - printf("Cleaning /home directory\n"); - - struct stat s; - if (stat(cfg.homedir, &s) == -1) { - // cannot find home directory, just return - fprintf(stderr, "Warning: cannot find home directory\n"); - return; - } - - fs_build_mnt_dir(); - if (mkdir(WHITELIST_HOME_DIR, 0755) == -1) - errExit("mkdir"); - - // keep a copy of the user home directory - if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mount bind"); - - // mount tmpfs in the new home - if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) - errExit("mount tmpfs"); - - // create user home directory - if (mkdir(cfg.homedir, 0755) == -1) - errExit("mkdir"); - - // set mode and ownership - if (chown(cfg.homedir, s.st_uid, s.st_gid) == -1) - errExit("chown"); - if (chmod(cfg.homedir, s.st_mode) == -1) - errExit("chmod"); - - // mount user home directory - if (mount(WHITELIST_HOME_DIR, cfg.homedir, NULL, MS_BIND|MS_REC, NULL) < 0) - errExit("mount bind"); - - // mask home dir under /run - if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) - errExit("mount tmpfs"); -} // build a basic read-only filesystem void fs_basic_fs(void) { @@ -605,9 +562,8 @@ void fs_basic_fs(void) { fs_var_cache(); fs_var_utmp(); - // only in user mode - if (getuid()) - sanitize_home(); + // don't leak user information + restrict_users(); } @@ -751,9 +707,8 @@ void fs_overlayfs(void) { fs_var_cache(); fs_var_utmp(); - // only in user mode - if (getuid()) - sanitize_home(); + // don't leak user information + restrict_users(); // cleanup and exit free(option); @@ -874,10 +829,8 @@ void fs_chroot(const char *rootdir) { fs_var_cache(); fs_var_utmp(); - // only in user mode - if (getuid()) - sanitize_home(); - + // don't leak user information + restrict_users(); } #endif diff --git a/src/firejail/restrict_users.c b/src/firejail/restrict_users.c new file mode 100644 index 00000000000..c0a14ff6fa0 --- /dev/null +++ b/src/firejail/restrict_users.c @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2014, 2015 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. +*/ +#include "firejail.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXBUF 1024 + +// linked list of users +typedef struct user_list { + struct user_list *next; + const char *user; +} USER_LIST; +USER_LIST *ulist = NULL; + +static void ulist_add(const char *user) { + assert(user); + + USER_LIST *nlist = malloc(sizeof(USER_LIST)); + memset(nlist, 0, sizeof(USER_LIST)); + nlist->user = user; + nlist->next = ulist; + ulist = nlist; +} + +static USER_LIST *ulist_find(const char *user) { + assert(user); + + USER_LIST *ptr = ulist; + while (ptr) { + if (strcmp(ptr->user, user) == 0) + return ptr; + ptr = ptr->next; + } + + return NULL; +} + +static void sanitize_home(void) { + assert(getuid() != 0); // this code works only for regular users + + if (arg_debug) + printf("Cleaning /home directory\n"); + + struct stat s; + if (stat(cfg.homedir, &s) == -1) { + // cannot find home directory, just return + fprintf(stderr, "Warning: cannot find home directory\n"); + return; + } + + fs_build_mnt_dir(); + if (mkdir(WHITELIST_HOME_DIR, 0755) == -1) + errExit("mkdir"); + + // keep a copy of the user home directory + if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + + // mount tmpfs in the new home + if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) + errExit("mount tmpfs"); + + // create user home directory + if (mkdir(cfg.homedir, 0755) == -1) + errExit("mkdir"); + + // set mode and ownership + if (chown(cfg.homedir, s.st_uid, s.st_gid) == -1) + errExit("chown"); + if (chmod(cfg.homedir, s.st_mode) == -1) + errExit("chmod"); + + // mount user home directory + if (mount(WHITELIST_HOME_DIR, cfg.homedir, NULL, MS_BIND|MS_REC, NULL) < 0) + errExit("mount bind"); + + // mask home dir under /run + if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) + errExit("mount tmpfs"); +} + +static void sanitize_passwd(void) { + struct stat s; + if (stat("/etc/passwd", &s) == -1) + return; + if (arg_debug) + printf("Sanitizing /etc/passwd\n"); + + FILE *fpin = NULL; + FILE *fpout = NULL; + fs_build_mnt_dir(); + + // open files + fpin = fopen("/etc/passwd", "r"); + if (!fpin) + goto errout; + fpout = fopen(PASSWD_FILE, "w"); + if (!fpout) + goto errout; + + // read the file line by line + char buf[MAXBUF]; + uid_t myuid = getuid(); + while (fgets(buf, MAXBUF, fpin)) { + // comments and empty lines + if (*buf == '\0' || *buf == '#') + continue; + + // sample line: + // www-data:x:33:33:www-data:/var/www:/bin/sh + // drop lines with uid > 1000 and not the current user + char *ptr = buf; + + // advance to uid + while (*ptr != ':' && *ptr != '\0') + ptr++; + if (*ptr == '\0') + goto errout; + char *ptr1 = ptr; + ptr++; + while (*ptr != ':' && *ptr != '\0') + ptr++; + if (*ptr == '\0') + goto errout; + ptr++; + if (*ptr == '\0') + goto errout; + + // process uid + int uid; + int rv = sscanf(ptr, "%d:", &uid); + if (rv == 0 || uid < 0) + goto errout; + if (uid < 1000) { // todo extract UID_MIN from /etc/login.def + fprintf(fpout, "%s", buf); + continue; + } + if ((uid_t) uid != myuid) { + // store user name - necessary to process /etc/group + *ptr1 = '\0'; + char *user = strdup(buf); + if (!user) + errExit("malloc"); + ulist_add(user); + continue; // skip line + } + fprintf(fpout, "%s", buf); + } + fclose(fpin); + fclose(fpout); + if (chown(PASSWD_FILE, 0, 0) == -1) + errExit("chown"); + if (chmod(PASSWD_FILE, 0644) == -1) + errExit("chmod"); + + // mount-bind tne new password file + if (mount(PASSWD_FILE, "/etc/passwd", "none", MS_BIND, "mode=400,gid=0") < 0) + errExit("mount"); + + return; + +errout: + fprintf(stderr, "Warning: failed to clean up /etc/passwd\n"); + if (fpin) + fclose(fpin); + if (fpout) + fclose(fpout); +} + +// returns 1 if fails, 0 if OK +static int copy_line(FILE *fpout, char *buf, char *ptr) { + // fpout: GROUP_FILE + // buf: pulse:x:115:netblue,bingo + // ptr: 115:neblue,bingo + + while (*ptr != ':' && *ptr != '\0') + ptr++; + if (*ptr == '\0') + return 1; + + ptr++; + if (*ptr == '\n' || *ptr == '\0') { + fprintf(fpout, "%s", buf); + return 0; + } + + // print what we have so far + char tmp = *ptr; + *ptr = '\0'; + fprintf(fpout, "%s", buf); + *ptr = tmp; + + // tokenize + char *token = strtok(ptr, ",\n"); + int first = 1; + while (token) { + char *newtoken = strtok(NULL, ",\n"); + if (ulist_find(token)) { + //skip + token = newtoken; + continue; + } + if (!first) + fprintf(fpout, ","); + first = 0; + fprintf(fpout, "%s", token); + token = newtoken; + } + fprintf(fpout, "\n"); + return 0; +} + +static void sanitize_group(void) { + struct stat s; + if (stat("/etc/group", &s) == -1) + return; + if (arg_debug) + printf("Sanitizing /etc/group\n"); + + FILE *fpin = NULL; + FILE *fpout = NULL; + fs_build_mnt_dir(); + + // open files + fpin = fopen("/etc/group", "r"); + if (!fpin) + goto errout; + fpout = fopen(GROUP_FILE, "w"); + if (!fpout) + goto errout; + + // read the file line by line + char buf[MAXBUF]; + gid_t mygid = getgid(); + while (fgets(buf, MAXBUF, fpin)) { + // comments and empty lines + if (*buf == '\0' || *buf == '#') + continue; + + // sample line: + // pulse:x:115:netblue,bingo + // drop lines with uid > 1000 and not the current user group + char *ptr = buf; + + // advance to uid + while (*ptr != ':' && *ptr != '\0') + ptr++; + if (*ptr == '\0') + goto errout; + ptr++; + while (*ptr != ':' && *ptr != '\0') + ptr++; + if (*ptr == '\0') + goto errout; + ptr++; + if (*ptr == '\0') + goto errout; + + // process uid + int gid; + int rv = sscanf(ptr, "%d:", &gid); + if (rv == 0 || gid < 0) + goto errout; + if (gid < 1000) { // todo extract GID_MIN from /etc/login.def + if (copy_line(fpout, buf, ptr)) + goto errout; + continue; + } + if ((gid_t) gid != mygid) { + continue; // skip line + } + fprintf(fpout, "%s", buf); + if (copy_line(fpout, buf, ptr)) + goto errout; + } + fclose(fpin); + fclose(fpout); + if (chown(GROUP_FILE, 0, 0) == -1) + errExit("chown"); + if (chmod(GROUP_FILE, 0644) == -1) + errExit("chmod"); + + // mount-bind tne new group file + if (mount(GROUP_FILE, "/etc/group", "none", MS_BIND, "mode=400,gid=0") < 0) + errExit("mount"); + + return; + +errout: + fprintf(stderr, "Warning: failed to clean up /etc/group\n"); + if (fpin) + fclose(fpin); + if (fpout) + fclose(fpout); +} + +void restrict_users(void) { + // only in user mode + if (getuid()) { + sanitize_home(); + sanitize_passwd(); + sanitize_group(); + } +}