mirror of
https://github.com/netblue30/firejail.git
synced 2026-05-15 22:01:33 -06:00
prevent leaking user information by modifying /home directory, /etc/passwd and /etc/group
This commit is contained in:
parent
bd16cb30ba
commit
4f003daec3
4 changed files with 344 additions and 54 deletions
4
RELNOTES
4
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 <netblue30@yahoo.com> ongoing development
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
328
src/firejail/restrict_users.c
Normal file
328
src/firejail/restrict_users.c
Normal 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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue