prevent leaking user information by modifying /home directory, /etc/passwd and /etc/group

This commit is contained in:
netblue30 2015-11-19 10:00:57 -05:00
parent bd16cb30ba
commit 4f003daec3
4 changed files with 344 additions and 54 deletions

View file

@ -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 <netblue30@yahoo.com> ongoing development

View file

@ -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

View file

@ -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

View file

@ -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 <sys/mount.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <fnmatch.h>
#include <glob.h>
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#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();
}
}