mirror of
https://github.com/netblue30/firejail.git
synced 2026-05-15 14:16:14 -06:00
jaitest - simple sandbox testing utility program
This commit is contained in:
parent
836989bf18
commit
42e2db1275
14 changed files with 794 additions and 5 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -22,6 +22,7 @@ firejail-users.5
|
|||
firejail.1
|
||||
firemon.1
|
||||
firecfg.1
|
||||
jailtest.5
|
||||
mkdeb.sh
|
||||
src/firejail/firejail
|
||||
src/firemon/firemon
|
||||
|
|
@ -40,6 +41,7 @@ src/fbuilder/fbuilder
|
|||
src/profstats/profstats
|
||||
src/bash_completion/firejail.bash_completion
|
||||
src/zsh_completion/_firejail
|
||||
src/jailtest/jailtest
|
||||
uids.h
|
||||
seccomp
|
||||
seccomp.debug
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ endif
|
|||
|
||||
COMPLETIONDIRS = src/zsh_completion src/bash_completion
|
||||
all: all_items mydirs $(MAN_TARGET) filters
|
||||
APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats
|
||||
APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats src/jailtest/jailtest
|
||||
SBOX_APPS = src/faudit/faudit src/fbuilder/fbuilder src/ftee/ftee
|
||||
SBOX_APPS_NON_DUMPABLE = src/fcopy/fcopy src/fldd/fldd src/fnet/fnet src/fnetfilter/fnetfilter
|
||||
MYDIRS = src/lib $(MAN_SRC) $(COMPLETIONDIRS)
|
||||
MYLIBS = src/libpostexecseccomp/libpostexecseccomp.so src/libtrace/libtrace.so src/libtracelog/libtracelog.so
|
||||
COMPLETIONS = src/zsh_completion/_firejail src/bash_completion/firejail.bash_completion
|
||||
MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5
|
||||
MANPAGES = firejail.1 firemon.1 firecfg.1 firejail-profile.5 firejail-login.5 firejail-users.5 jailtest.5
|
||||
SBOX_APPS_NON_DUMPABLE += src/fsec-optimize/fsec-optimize src/fsec-print/fsec-print src/fseccomp/fseccomp
|
||||
SECCOMP_FILTERS = seccomp seccomp.debug seccomp.32 seccomp.block_secondary seccomp.mdwx seccomp.mdwx.32
|
||||
ALL_ITEMS = $(APPS) $(SBOX_APPS) $(SBOX_APPS_NON_DUMPABLE) $(MYLIBS)
|
||||
|
|
@ -109,6 +109,8 @@ endif
|
|||
install -m 0755 src/firemon/firemon $(DESTDIR)$(bindir)
|
||||
# firecfg executable
|
||||
install -m 0755 src/firecfg/firecfg $(DESTDIR)$(bindir)
|
||||
# jailtest executable
|
||||
install -m 0755 src/jailtest/jailtest $(DESTDIR)$(bindir)
|
||||
# libraries and plugins
|
||||
install -m 0755 -d $(DESTDIR)$(libdir)/firejail
|
||||
install -m 0644 -t $(DESTDIR)$(libdir)/firejail $(MYLIBS) $(SECCOMP_FILTERS) src/firecfg/firecfg.config
|
||||
|
|
@ -177,6 +179,7 @@ uninstall:
|
|||
rm -f $(DESTDIR)$(bindir)/firemon
|
||||
rm -f $(DESTDIR)$(bindir)/firecfg
|
||||
rm -fr $(DESTDIR)$(libdir)/firejail
|
||||
rm -fr $(DESTDIR)$(libdir)/jailtest
|
||||
rm -fr $(DESTDIR)$(datarootdir)/doc/firejail
|
||||
for man in $(MANPAGES); do \
|
||||
rm -f $(DESTDIR)$(mandir)/man5/$$man*; \
|
||||
|
|
|
|||
77
README.md
77
README.md
|
|
@ -198,7 +198,84 @@ We also keep a list of profile fixes for previous released versions in [etc-fixe
|
|||
Milestone page: https://github.com/netblue30/firejail/milestone/1
|
||||
Release discussion: https://github.com/netblue30/firejail/issues/3696
|
||||
|
||||
### jailtest
|
||||
`````
|
||||
JAILTEST(1) JAILTEST man page JAILTEST(1)
|
||||
|
||||
NAME
|
||||
jailtest - Simple utility program to test running sandboxes
|
||||
|
||||
SYNOPSIS
|
||||
sudo jailtest [OPTIONS] [directory]
|
||||
|
||||
DESCRIPTION
|
||||
WORK IN PROGRESS! jailtest attaches itself to all sandboxes started by
|
||||
the user and performs some basic tests on the sandbox filesystem:
|
||||
|
||||
1. Virtual directories
|
||||
jailtest extracts a list with the main virtual directories in‐
|
||||
stalled by the sandbox. These directories are build by firejail
|
||||
at startup using --private* and --whitelist commands.
|
||||
|
||||
2. Noexec test
|
||||
jailtest inserts executable programs in /home/username, /tmp,
|
||||
and /var/tmp directories and tries to run them form inside the
|
||||
sandbox, thus testing if the directory is executable or not.
|
||||
|
||||
3. Read access test
|
||||
jailtest creates test files in the directories specified by the
|
||||
user and tries to read them from inside the sandbox.
|
||||
|
||||
The program is running as root exclusively under sudo.
|
||||
|
||||
OPTIONS
|
||||
--debug
|
||||
Print debug messages
|
||||
|
||||
-?, --help
|
||||
Print options end exit.
|
||||
|
||||
--version
|
||||
Print program version and exit.
|
||||
|
||||
[directory]
|
||||
One or more directories in user home to test for read access.
|
||||
|
||||
OUTPUT
|
||||
For each sandbox detected we print the following line:
|
||||
|
||||
PID:USER:Sandbox Name:Command
|
||||
|
||||
It is followed by relevant sandbox information, such as the virtual di‐
|
||||
rectories and various warnings.
|
||||
|
||||
EXAMPLE
|
||||
$ sudo jailtest ~/.ssh ~/.gnupg
|
||||
1429:netblue::/usr/bin/firejail /opt/firefox/firefox
|
||||
Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc,
|
||||
5602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net
|
||||
Virtual dirs: /var/tmp, /dev,
|
||||
Warning: I can read ~/.ssh
|
||||
5926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10
|
||||
Virtual dirs: /tmp, /var/tmp, /dev,
|
||||
Warning: I can run programs in /home/netblue
|
||||
6394:netblue:libreoffice:/usr/bin/firejail libreoffice
|
||||
Virtual dirs: /tmp, /var/tmp, /dev,
|
||||
|
||||
LICENSE
|
||||
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.
|
||||
|
||||
Homepage: https://firejail.wordpress.com
|
||||
|
||||
SEE ALSO
|
||||
firejail(1), firecfg(1), firejail-profile(5), firejail-login(5) fire‐
|
||||
jail-users(5)
|
||||
|
||||
0.9.65 Feb 2021 JAILTEST(1)
|
||||
`````
|
||||
|
||||
### Profile Statistics
|
||||
|
||||
|
|
|
|||
5
configure
vendored
5
configure
vendored
|
|
@ -4269,7 +4269,7 @@ fi
|
|||
|
||||
ac_config_files="$ac_config_files mkdeb.sh"
|
||||
|
||||
ac_config_files="$ac_config_files Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile"
|
||||
ac_config_files="$ac_config_files Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile src/jailtest/Makefile"
|
||||
|
||||
cat >confcache <<\_ACEOF
|
||||
# This file is a shell script that caches the results of configure
|
||||
|
|
@ -5000,7 +5000,10 @@ do
|
|||
"src/fsec-optimize/Makefile") CONFIG_FILES="$CONFIG_FILES src/fsec-optimize/Makefile" ;;
|
||||
"src/profstats/Makefile") CONFIG_FILES="$CONFIG_FILES src/profstats/Makefile" ;;
|
||||
"src/man/Makefile") CONFIG_FILES="$CONFIG_FILES src/man/Makefile" ;;
|
||||
"src/zsh_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/zsh_completion/Makefile" ;;
|
||||
"src/bash_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/bash_completion/Makefile" ;;
|
||||
"test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;;
|
||||
"src/jailtest/Makefile") CONFIG_FILES="$CONFIG_FILES src/jailtest/Makefile" ;;
|
||||
|
||||
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -234,7 +234,8 @@ AC_CONFIG_FILES([mkdeb.sh], [chmod +x mkdeb.sh])
|
|||
AC_OUTPUT(Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/fnet/Makefile src/firejail/Makefile src/fnetfilter/Makefile \
|
||||
src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \
|
||||
src/ftee/Makefile src/faudit/Makefile src/fseccomp/Makefile src/fldd/Makefile src/libpostexecseccomp/Makefile src/fsec-optimize/Makefile \
|
||||
src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile)
|
||||
src/profstats/Makefile src/man/Makefile src/zsh_completion/Makefile src/bash_completion/Makefile test/Makefile \
|
||||
src/jailtest/Makefile)
|
||||
|
||||
echo
|
||||
echo "Configuration options:"
|
||||
|
|
|
|||
14
src/jailtest/Makefile.in
Normal file
14
src/jailtest/Makefile.in
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
all: jailtest
|
||||
|
||||
include ../common.mk
|
||||
|
||||
%.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h
|
||||
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@
|
||||
|
||||
jailtest: $(OBJS)
|
||||
$(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS)
|
||||
|
||||
clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist
|
||||
|
||||
distclean: clean
|
||||
rm -fr Makefile
|
||||
124
src/jailtest/access.c
Normal file
124
src/jailtest/access.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include "jailtest.h"
|
||||
#include <dirent.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
typedef struct {
|
||||
char *tfile;
|
||||
char *tdir;
|
||||
} TestDir;
|
||||
|
||||
#define MAX_TEST_FILES 16
|
||||
TestDir td[MAX_TEST_FILES];
|
||||
static int files_cnt = 0;
|
||||
|
||||
void access_setup(const char *directory) {
|
||||
// I am root!
|
||||
assert(directory);
|
||||
assert(user_home_dir);
|
||||
|
||||
if (files_cnt >= MAX_TEST_FILES) {
|
||||
fprintf(stderr, "Error: maximum number of test directories exceded\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char *fname = strdup(directory);
|
||||
if (!fname)
|
||||
errExit("strdup");
|
||||
if (strncmp(fname, "~/", 2) == 0) {
|
||||
free(fname);
|
||||
if (asprintf(&fname, "%s/%s", user_home_dir, directory + 2) == -1)
|
||||
errExit("asprintf");
|
||||
}
|
||||
|
||||
char *path = realpath(fname, NULL);
|
||||
free(fname);
|
||||
if (path == NULL) {
|
||||
fprintf(stderr, "Warning: invalid directory %s, skipping...\n", directory);
|
||||
return;
|
||||
}
|
||||
|
||||
// file in home directory
|
||||
if (strncmp(path, user_home_dir, strlen(user_home_dir)) != 0) {
|
||||
fprintf(stderr, "Warning: file %s is not in user home directory, skipping...\n", directory);
|
||||
free(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// try to open the dir as root
|
||||
DIR *dir = opendir(path);
|
||||
if (!dir) {
|
||||
fprintf(stderr, "Warning: directory %s not found, skipping\n", directory);
|
||||
free(path);
|
||||
return;
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
// create a test file
|
||||
char *test_file;
|
||||
if (asprintf(&test_file, "%s/jailtest-access-%d", path, getpid()) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
FILE *fp = fopen(test_file, "w");
|
||||
if (!fp) {
|
||||
printf("Warning: I cannot create test file in directory %s, skipping...\n", directory);
|
||||
return;
|
||||
}
|
||||
fprintf(fp, "this file was created by firetest utility, you can safely delete it\n");
|
||||
fclose(fp);
|
||||
int rv = chown(test_file, user_uid, user_gid);
|
||||
if (rv)
|
||||
errExit("chown");
|
||||
|
||||
char *dname = strdup(directory);
|
||||
if (!dname)
|
||||
errExit("strdup");
|
||||
td[files_cnt].tdir = dname;
|
||||
td[files_cnt].tfile = test_file;
|
||||
files_cnt++;
|
||||
}
|
||||
|
||||
void access_destroy(void) {
|
||||
// remove test files
|
||||
int i;
|
||||
|
||||
for (i = 0; i < files_cnt; i++) {
|
||||
int rv = unlink(td[i].tfile);
|
||||
(void) rv;
|
||||
}
|
||||
files_cnt = 0;
|
||||
}
|
||||
|
||||
void access_test(void) {
|
||||
// I am root in sandbox mount namespace
|
||||
assert(user_uid);
|
||||
int i;
|
||||
|
||||
pid_t child = fork();
|
||||
if (child == -1)
|
||||
errExit("fork");
|
||||
|
||||
if (child == 0) { // child
|
||||
// drop privileges
|
||||
if (setgid(user_gid) != 0)
|
||||
errExit("setgid");
|
||||
if (setuid(user_uid) != 0)
|
||||
errExit("setuid");
|
||||
|
||||
for (i = 0; i < files_cnt; i++) {
|
||||
assert(td[i].tfile);
|
||||
|
||||
// try to open the file for reading
|
||||
FILE *fp = fopen(td[i].tfile, "r");
|
||||
if (fp) {
|
||||
|
||||
printf(" Warning: I can read %s\n", td[i].tdir);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// wait for the child to finish
|
||||
int status;
|
||||
wait(&status);
|
||||
}
|
||||
32
src/jailtest/jailtest.h
Normal file
32
src/jailtest/jailtest.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef JAILTEST_H
|
||||
#define JAILTEST_H
|
||||
|
||||
#include "../include/common.h"
|
||||
|
||||
// main.c
|
||||
extern uid_t user_uid;
|
||||
extern gid_t user_gid;
|
||||
extern char *user_name;
|
||||
extern char *user_home_dir;
|
||||
|
||||
// access.c
|
||||
void access_setup(const char *directory);
|
||||
void access_test(void);
|
||||
void access_destroy(void);
|
||||
|
||||
// noexec.c
|
||||
void noexec_setup(void);
|
||||
void noexec_test(const char *msg);
|
||||
|
||||
// virtual.c
|
||||
void virtual_setup(const char *directory);
|
||||
void virtual_destroy(void);
|
||||
void virtual_test(void);
|
||||
|
||||
// utils.c
|
||||
char *get_sudo_user(void);
|
||||
char *get_homedir(const char *user, uid_t *uid, gid_t *gid);
|
||||
int find_child(pid_t parent, pid_t *child);
|
||||
pid_t switch_to_child(pid_t pid);
|
||||
|
||||
#endif
|
||||
134
src/jailtest/main.c
Normal file
134
src/jailtest/main.c
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
#include "jailtest.h"
|
||||
#include "../include/firejail_user.h"
|
||||
#include "../include/pid.h"
|
||||
#include <sys/wait.h>
|
||||
|
||||
uid_t user_uid = 0;
|
||||
gid_t user_gid = 0;
|
||||
char *user_name = NULL;
|
||||
char *user_home_dir = NULL;
|
||||
int arg_debug = 0;
|
||||
|
||||
static char *usage_str =
|
||||
"Usage: jailtest [options] directory [directory]\n\n"
|
||||
"Options:\n"
|
||||
" --debug - print debug messages.\n"
|
||||
" --help, -? - this help screen.\n"
|
||||
" --version - print program version and exit.\n";
|
||||
|
||||
|
||||
static void usage(void) {
|
||||
printf("firetest - version %s\n\n", VERSION);
|
||||
puts(usage_str);
|
||||
}
|
||||
|
||||
static void cleanup(void) {
|
||||
// running only as root
|
||||
if (getuid() == 0) {
|
||||
if (arg_debug)
|
||||
printf("cleaning up!\n");
|
||||
access_destroy();
|
||||
virtual_destroy();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int i;
|
||||
int findex = 0;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) {
|
||||
usage();
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(argv[i], "--version") == 0) {
|
||||
printf("firetest version %s\n\n", VERSION);
|
||||
return 0;
|
||||
}
|
||||
else if (strncmp(argv[i], "--hello=", 8) == 0) { // used by noexec test
|
||||
printf(" Warning: I can run programs in %s\n", argv[i] + 8);
|
||||
return 0;
|
||||
}
|
||||
else if (strcmp(argv[i], "--debug") == 0)
|
||||
arg_debug = 1;
|
||||
else if (strncmp(argv[i], "--", 2) == 0) {
|
||||
fprintf(stderr, "Error: invalid option\n");
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
findex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// user setup
|
||||
if (getuid() != 0) {
|
||||
fprintf(stderr, "Error: you need to be root (via sudo) to run this program\n");
|
||||
exit(1);
|
||||
}
|
||||
user_name = get_sudo_user();
|
||||
assert(user_name);
|
||||
user_home_dir = get_homedir(user_name, &user_uid, &user_gid);
|
||||
if (user_uid == 0) {
|
||||
fprintf(stderr, "Error: root user not supported\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// test setup
|
||||
atexit(cleanup);
|
||||
if (findex > 0) {
|
||||
for (i = findex; i < argc; i++)
|
||||
access_setup(argv[i]);
|
||||
}
|
||||
|
||||
noexec_setup();
|
||||
virtual_setup(user_home_dir);
|
||||
virtual_setup("/tmp");
|
||||
virtual_setup("/var/tmp");
|
||||
virtual_setup("/dev");
|
||||
virtual_setup("/etc");
|
||||
virtual_setup("/bin");
|
||||
|
||||
// print processes
|
||||
pid_read(0);
|
||||
for (i = 0; i < max_pids; i++) {
|
||||
if (pids[i].level == 1) {
|
||||
uid_t uid = pid_get_uid(i);
|
||||
if (uid != user_uid) // not interested in other user sandboxes
|
||||
continue;
|
||||
|
||||
// in case the pid is that of a firejail process, use the pid of the first child process
|
||||
uid_t pid = switch_to_child(i);
|
||||
pid_print_list(i, 0); // no wrapping
|
||||
|
||||
pid_t child = fork();
|
||||
if (child == -1)
|
||||
errExit("fork");
|
||||
if (child == 0) {
|
||||
int rv = join_namespace(pid, "mnt");
|
||||
if (rv == 0) {
|
||||
virtual_test();
|
||||
noexec_test(user_home_dir);
|
||||
noexec_test("/tmp");
|
||||
noexec_test("/var/tmp");
|
||||
access_test();
|
||||
}
|
||||
else {
|
||||
printf(" Error: I cannot join the process mount space\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// drop privileges in order not to trigger cleanup()
|
||||
if (setgid(user_gid) != 0)
|
||||
errExit("setgid");
|
||||
if (setuid(user_uid) != 0)
|
||||
errExit("setuid");
|
||||
return 0;
|
||||
}
|
||||
int status;
|
||||
wait(&status);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
94
src/jailtest/noexec.c
Normal file
94
src/jailtest/noexec.c
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include "jailtest.h"
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static unsigned char *execfile = NULL;
|
||||
static int execfile_len = 0;
|
||||
|
||||
void noexec_setup(void) {
|
||||
// grab a copy of myself
|
||||
char *self = realpath("/proc/self/exe", NULL);
|
||||
if (self) {
|
||||
struct stat s;
|
||||
if (access(self, X_OK) == 0 && stat(self, &s) == 0) {
|
||||
assert(s.st_size);
|
||||
execfile = malloc(s.st_size);
|
||||
|
||||
int fd = open(self, O_RDONLY);
|
||||
if (fd == -1)
|
||||
errExit("open");
|
||||
int len = 0;
|
||||
do {
|
||||
int rv = read(fd, execfile + len, s.st_size - len);
|
||||
if (rv == -1)
|
||||
errExit("read");
|
||||
if (rv == 0) {
|
||||
// something went wrong!
|
||||
free(execfile);
|
||||
execfile = NULL;
|
||||
printf("Warning: I cannot grab a copy of myself, skipping noexec test...\n");
|
||||
break;
|
||||
}
|
||||
len += rv;
|
||||
}
|
||||
while (len < s.st_size);
|
||||
execfile_len = s.st_size;
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void noexec_test(const char *path) {
|
||||
assert(user_uid);
|
||||
|
||||
// I am root in sandbox mount namespace
|
||||
if (!execfile)
|
||||
return;
|
||||
|
||||
char *fname;
|
||||
if (asprintf(&fname, "%s/jailtest-noexec-%d", path, getpid()) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
pid_t child = fork();
|
||||
if (child == -1)
|
||||
errExit("fork");
|
||||
|
||||
if (child == 0) { // child
|
||||
// drop privileges
|
||||
if (setgid(user_gid) != 0)
|
||||
errExit("setgid");
|
||||
if (setuid(user_uid) != 0)
|
||||
errExit("setuid");
|
||||
int fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0700);
|
||||
if (fd == -1) {
|
||||
printf(" I cannot create files in %s, skipping noexec...\n", path);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
while (len < execfile_len) {
|
||||
int rv = write(fd, execfile + len, execfile_len - len);
|
||||
if (rv == -1 || rv == 0) {
|
||||
printf(" I cannot create files in %s, skipping noexec....\n", path);
|
||||
exit(1);
|
||||
}
|
||||
len += rv;
|
||||
}
|
||||
fchmod(fd, 0700);
|
||||
close(fd);
|
||||
|
||||
char *arg;
|
||||
if (asprintf(&arg, "--hello=%s", path) == -1)
|
||||
errExit("asprintf");
|
||||
int rv = execl(fname, fname, arg, NULL);
|
||||
(void) rv; // if we get here execl failed
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int status;
|
||||
wait(&status);
|
||||
int rv = unlink(fname);
|
||||
(void) rv;
|
||||
}
|
||||
124
src/jailtest/utils.c
Normal file
124
src/jailtest/utils.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include "jailtest.h"
|
||||
#include <errno.h>
|
||||
#include <pwd.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#define BUFLEN 4096
|
||||
|
||||
char *get_sudo_user(void) {
|
||||
char *user = getenv("SUDO_USER");
|
||||
if (!user) {
|
||||
user = getpwuid(getuid())->pw_name;
|
||||
if (!user) {
|
||||
fprintf(stderr, "Error: cannot detect login user\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
char *get_homedir(const char *user, uid_t *uid, gid_t *gid) {
|
||||
// find home directory
|
||||
struct passwd *pw = getpwnam(user);
|
||||
if (!pw)
|
||||
goto errexit;
|
||||
|
||||
char *home = pw->pw_dir;
|
||||
if (!home)
|
||||
goto errexit;
|
||||
|
||||
*uid = pw->pw_uid;
|
||||
*gid = pw->pw_gid;
|
||||
|
||||
return home;
|
||||
|
||||
errexit:
|
||||
fprintf(stderr, "Error: cannot find home directory for user %s\n", user);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int find_child(pid_t parent, pid_t *child) {
|
||||
*child = 0; // use it to flag a found child
|
||||
|
||||
DIR *dir;
|
||||
if (!(dir = opendir("/proc"))) {
|
||||
// sleep 2 seconds and try again
|
||||
sleep(2);
|
||||
if (!(dir = opendir("/proc"))) {
|
||||
fprintf(stderr, "Error: cannot open /proc directory\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
struct dirent *entry;
|
||||
char *end;
|
||||
while (*child == 0 && (entry = readdir(dir))) {
|
||||
pid_t pid = strtol(entry->d_name, &end, 10);
|
||||
if (end == entry->d_name || *end)
|
||||
continue;
|
||||
if (pid == parent)
|
||||
continue;
|
||||
|
||||
// open stat file
|
||||
char *file;
|
||||
if (asprintf(&file, "/proc/%u/status", pid) == -1) {
|
||||
perror("asprintf");
|
||||
exit(1);
|
||||
}
|
||||
FILE *fp = fopen(file, "r");
|
||||
if (!fp) {
|
||||
free(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
// look for firejail executable name
|
||||
char buf[BUFLEN];
|
||||
while (fgets(buf, BUFLEN - 1, fp)) {
|
||||
if (strncmp(buf, "PPid:", 5) == 0) {
|
||||
char *ptr = buf + 5;
|
||||
while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) {
|
||||
ptr++;
|
||||
}
|
||||
if (*ptr == '\0') {
|
||||
fprintf(stderr, "Error: cannot read /proc file\n");
|
||||
exit(1);
|
||||
}
|
||||
if (parent == atoi(ptr)) {
|
||||
// we don't want /usr/bin/xdg-dbus-proxy!
|
||||
char *cmdline = pid_proc_cmdline(pid);
|
||||
if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0)
|
||||
*child = pid;
|
||||
free(cmdline);
|
||||
}
|
||||
break; // stop reading the file
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
free(file);
|
||||
}
|
||||
closedir(dir);
|
||||
return (*child)? 0:1; // 0 = found, 1 = not found
|
||||
}
|
||||
|
||||
pid_t switch_to_child(pid_t pid) {
|
||||
pid_t rv = pid;
|
||||
errno = 0;
|
||||
char *comm = pid_proc_comm(pid);
|
||||
if (!comm) {
|
||||
if (errno == ENOENT)
|
||||
fprintf(stderr, "Error: cannot find process with pid %d\n", pid);
|
||||
else
|
||||
fprintf(stderr, "Error: cannot read /proc file\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (strcmp(comm, "firejail") == 0) {
|
||||
if (find_child(pid, &rv) == 1) {
|
||||
fprintf(stderr, "Error: no valid sandbox\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
free(comm);
|
||||
return rv;
|
||||
}
|
||||
99
src/jailtest/virtual.c
Normal file
99
src/jailtest/virtual.c
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#include "jailtest.h"
|
||||
#include <dirent.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
|
||||
#define MAX_TEST_FILES 16
|
||||
static char *dirs[MAX_TEST_FILES];
|
||||
static char *files[MAX_TEST_FILES];
|
||||
static int files_cnt = 0;
|
||||
|
||||
void virtual_setup(const char *directory) {
|
||||
// I am root!
|
||||
assert(directory);
|
||||
assert(*directory == '/');
|
||||
assert(files_cnt < MAX_TEST_FILES);
|
||||
|
||||
// try to open the dir as root
|
||||
DIR *dir = opendir(directory);
|
||||
if (!dir) {
|
||||
fprintf(stderr, "Warning: directory %s not found, skipping\n", directory);
|
||||
return;
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
// create a test file
|
||||
char *test_file;
|
||||
if (asprintf(&test_file, "%s/jailtest-private-%d", directory, getpid()) == -1)
|
||||
errExit("asprintf");
|
||||
|
||||
FILE *fp = fopen(test_file, "w");
|
||||
if (!fp) {
|
||||
printf("Warning: I cannot create test file in directory %s, skipping...\n", directory);
|
||||
return;
|
||||
}
|
||||
fprintf(fp, "this file was created by firetest utility, you can safely delete it\n");
|
||||
fclose(fp);
|
||||
if (strcmp(directory, user_home_dir) == 0) {
|
||||
int rv = chown(test_file, user_uid, user_gid);
|
||||
if (rv)
|
||||
errExit("chown");
|
||||
}
|
||||
|
||||
char *dname = strdup(directory);
|
||||
if (!dname)
|
||||
errExit("strdup");
|
||||
dirs[files_cnt] = dname;
|
||||
files[files_cnt] = test_file;
|
||||
files_cnt++;
|
||||
}
|
||||
|
||||
void virtual_destroy(void) {
|
||||
// remove test files
|
||||
int i;
|
||||
|
||||
for (i = 0; i < files_cnt; i++) {
|
||||
int rv = unlink(files[i]);
|
||||
(void) rv;
|
||||
}
|
||||
files_cnt = 0;
|
||||
}
|
||||
|
||||
void virtual_test(void) {
|
||||
// I am root in sandbox mount namespace
|
||||
assert(user_uid);
|
||||
int i;
|
||||
|
||||
printf(" Virtual dirs: "); fflush(0);
|
||||
|
||||
for (i = 0; i < files_cnt; i++) {
|
||||
assert(files[i]);
|
||||
|
||||
// I am root!
|
||||
pid_t child = fork();
|
||||
if (child == -1)
|
||||
errExit("fork");
|
||||
|
||||
if (child == 0) { // child
|
||||
// drop privileges
|
||||
if (setgid(user_gid) != 0)
|
||||
errExit("setgid");
|
||||
if (setuid(user_uid) != 0)
|
||||
errExit("setuid");
|
||||
|
||||
// try to open the file for reading
|
||||
FILE *fp = fopen(files[i], "r");
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
else
|
||||
printf("%s, ", dirs[i]);
|
||||
fflush(0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// wait for the child to finish
|
||||
int status;
|
||||
wait(&status);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man
|
||||
all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man
|
||||
include ../common.mk
|
||||
|
||||
%.man: %.txt
|
||||
|
|
|
|||
82
src/man/jailtest.txt
Normal file
82
src/man/jailtest.txt
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
.TH JAILTEST 1 "MONTH YEAR" "VERSION" "JAILTEST man page"
|
||||
.SH NAME
|
||||
jailtest \- Simple utility program to test running sandboxes
|
||||
.SH SYNOPSIS
|
||||
sudo jailtest [OPTIONS] [directory]
|
||||
.SH DESCRIPTION
|
||||
WORK IN PROGRESS!
|
||||
jailtest attaches itself to all sandboxes started by the user and performs some basic tests
|
||||
on the sandbox filesystem:
|
||||
.TP
|
||||
\fB1. Virtual directories
|
||||
jailtest extracts a list with the main virtual directories installed by the sandbox.
|
||||
These directories are build by firejail at startup using --private* and --whitelist commands.
|
||||
.TP
|
||||
\fB2. Noexec test
|
||||
jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories
|
||||
and tries to run them form inside the sandbox, thus testing if the directory is executable or not.
|
||||
.TP
|
||||
\fB3. Read access test
|
||||
jailtest creates test files in the directories specified by the user and tries to read
|
||||
them from inside the sandbox.
|
||||
|
||||
.TP
|
||||
The program is running as root exclusively under sudo.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-\-debug
|
||||
Print debug messages
|
||||
.TP
|
||||
\fB\-?\fR, \fB\-\-help\fR
|
||||
Print options end exit.
|
||||
.TP
|
||||
\fB\-\-version
|
||||
Print program version and exit.
|
||||
.TP
|
||||
\fB[directory]
|
||||
One or more directories in user home to test for read access.
|
||||
|
||||
.SH OUTPUT
|
||||
For each sandbox detected we print the following line:
|
||||
|
||||
PID:USER:Sandbox Name:Command
|
||||
|
||||
It is followed by relevant sandbox information, such as the virtual directories and various warnings.
|
||||
|
||||
.SH EXAMPLE
|
||||
|
||||
.br
|
||||
$ sudo jailtest ~/.ssh ~/.gnupg
|
||||
.br
|
||||
1429:netblue::/usr/bin/firejail /opt/firefox/firefox
|
||||
.br
|
||||
Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc,
|
||||
.br
|
||||
5602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net
|
||||
.br
|
||||
Virtual dirs: /var/tmp, /dev,
|
||||
.br
|
||||
Warning: I can read ~/.ssh
|
||||
.br
|
||||
5926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10
|
||||
.br
|
||||
Virtual dirs: /tmp, /var/tmp, /dev,
|
||||
.br
|
||||
Warning: I can run programs in /home/netblue
|
||||
.br
|
||||
6394:netblue:libreoffice:/usr/bin/firejail libreoffice
|
||||
.br
|
||||
Virtual dirs: /tmp, /var/tmp, /dev,
|
||||
.br
|
||||
|
||||
.SH LICENSE
|
||||
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.
|
||||
.PP
|
||||
Homepage: https://firejail.wordpress.com
|
||||
.SH SEE ALSO
|
||||
\&\flfirejail\fR\|(1),
|
||||
\&\flfirecfg\fR\|(1),
|
||||
\&\flfirejail-profile\fR\|(5),
|
||||
\&\flfirejail-login\fR\|(5)
|
||||
\&\flfirejail-users\fR\|(5)
|
||||
Loading…
Add table
Add a link
Reference in a new issue