aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar netblue30 <netblue30@protonmail.com>2021-02-20 10:06:58 -0500
committerLibravatar netblue30 <netblue30@protonmail.com>2021-02-20 10:06:58 -0500
commit42e2db1275e37bf669a074c023ea9f9a8b40db43 (patch)
tree59169acd88cbce9160b1657a7016c789559e0e20 /src
parentrun sort.py (diff)
downloadfirejail-42e2db127.tar.gz
firejail-42e2db127.tar.zst
firejail-42e2db127.zip
jaitest - simple sandbox testing utility program
Diffstat (limited to 'src')
-rw-r--r--src/jailtest/Makefile.in14
-rw-r--r--src/jailtest/access.c124
-rw-r--r--src/jailtest/jailtest.h32
-rw-r--r--src/jailtest/main.c134
-rw-r--r--src/jailtest/noexec.c94
-rw-r--r--src/jailtest/utils.c124
-rw-r--r--src/jailtest/virtual.c99
-rw-r--r--src/man/Makefile.in2
-rw-r--r--src/man/jailtest.txt82
9 files changed, 704 insertions, 1 deletions
diff --git a/src/jailtest/Makefile.in b/src/jailtest/Makefile.in
new file mode 100644
index 000000000..9c9c0c508
--- /dev/null
+++ b/src/jailtest/Makefile.in
@@ -0,0 +1,14 @@
1all: jailtest
2
3include ../common.mk
4
5%.o : %.c $(H_FILE_LIST) ../include/common.h ../include/pid.h
6 $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@
7
8jailtest: $(OBJS)
9 $(CC) $(LDFLAGS) -o $@ $(OBJS) ../lib/common.o ../lib/pid.o $(LIBS) $(EXTRA_LDFLAGS)
10
11clean:; rm -fr *.o jailtest *.gcov *.gcda *.gcno *.plist
12
13distclean: clean
14 rm -fr Makefile
diff --git a/src/jailtest/access.c b/src/jailtest/access.c
new file mode 100644
index 000000000..e68227bd2
--- /dev/null
+++ b/src/jailtest/access.c
@@ -0,0 +1,124 @@
1#include "jailtest.h"
2#include <dirent.h>
3#include <sys/wait.h>
4
5typedef struct {
6 char *tfile;
7 char *tdir;
8} TestDir;
9
10#define MAX_TEST_FILES 16
11TestDir td[MAX_TEST_FILES];
12static int files_cnt = 0;
13
14void access_setup(const char *directory) {
15 // I am root!
16 assert(directory);
17 assert(user_home_dir);
18
19 if (files_cnt >= MAX_TEST_FILES) {
20 fprintf(stderr, "Error: maximum number of test directories exceded\n");
21 exit(1);
22 }
23
24 char *fname = strdup(directory);
25 if (!fname)
26 errExit("strdup");
27 if (strncmp(fname, "~/", 2) == 0) {
28 free(fname);
29 if (asprintf(&fname, "%s/%s", user_home_dir, directory + 2) == -1)
30 errExit("asprintf");
31 }
32
33 char *path = realpath(fname, NULL);
34 free(fname);
35 if (path == NULL) {
36 fprintf(stderr, "Warning: invalid directory %s, skipping...\n", directory);
37 return;
38 }
39
40 // file in home directory
41 if (strncmp(path, user_home_dir, strlen(user_home_dir)) != 0) {
42 fprintf(stderr, "Warning: file %s is not in user home directory, skipping...\n", directory);
43 free(path);
44 return;
45 }
46
47 // try to open the dir as root
48 DIR *dir = opendir(path);
49 if (!dir) {
50 fprintf(stderr, "Warning: directory %s not found, skipping\n", directory);
51 free(path);
52 return;
53 }
54 closedir(dir);
55
56 // create a test file
57 char *test_file;
58 if (asprintf(&test_file, "%s/jailtest-access-%d", path, getpid()) == -1)
59 errExit("asprintf");
60
61 FILE *fp = fopen(test_file, "w");
62 if (!fp) {
63 printf("Warning: I cannot create test file in directory %s, skipping...\n", directory);
64 return;
65 }
66 fprintf(fp, "this file was created by firetest utility, you can safely delete it\n");
67 fclose(fp);
68 int rv = chown(test_file, user_uid, user_gid);
69 if (rv)
70 errExit("chown");
71
72 char *dname = strdup(directory);
73 if (!dname)
74 errExit("strdup");
75 td[files_cnt].tdir = dname;
76 td[files_cnt].tfile = test_file;
77 files_cnt++;
78}
79
80void access_destroy(void) {
81 // remove test files
82 int i;
83
84 for (i = 0; i < files_cnt; i++) {
85 int rv = unlink(td[i].tfile);
86 (void) rv;
87 }
88 files_cnt = 0;
89}
90
91void access_test(void) {
92 // I am root in sandbox mount namespace
93 assert(user_uid);
94 int i;
95
96 pid_t child = fork();
97 if (child == -1)
98 errExit("fork");
99
100 if (child == 0) { // child
101 // drop privileges
102 if (setgid(user_gid) != 0)
103 errExit("setgid");
104 if (setuid(user_uid) != 0)
105 errExit("setuid");
106
107 for (i = 0; i < files_cnt; i++) {
108 assert(td[i].tfile);
109
110 // try to open the file for reading
111 FILE *fp = fopen(td[i].tfile, "r");
112 if (fp) {
113
114 printf(" Warning: I can read %s\n", td[i].tdir);
115 fclose(fp);
116 }
117 }
118 exit(0);
119 }
120
121 // wait for the child to finish
122 int status;
123 wait(&status);
124}
diff --git a/src/jailtest/jailtest.h b/src/jailtest/jailtest.h
new file mode 100644
index 000000000..678f94bef
--- /dev/null
+++ b/src/jailtest/jailtest.h
@@ -0,0 +1,32 @@
1#ifndef JAILTEST_H
2#define JAILTEST_H
3
4#include "../include/common.h"
5
6// main.c
7extern uid_t user_uid;
8extern gid_t user_gid;
9extern char *user_name;
10extern char *user_home_dir;
11
12// access.c
13void access_setup(const char *directory);
14void access_test(void);
15void access_destroy(void);
16
17// noexec.c
18void noexec_setup(void);
19void noexec_test(const char *msg);
20
21// virtual.c
22void virtual_setup(const char *directory);
23void virtual_destroy(void);
24void virtual_test(void);
25
26// utils.c
27char *get_sudo_user(void);
28char *get_homedir(const char *user, uid_t *uid, gid_t *gid);
29int find_child(pid_t parent, pid_t *child);
30pid_t switch_to_child(pid_t pid);
31
32#endif \ No newline at end of file
diff --git a/src/jailtest/main.c b/src/jailtest/main.c
new file mode 100644
index 000000000..78f162706
--- /dev/null
+++ b/src/jailtest/main.c
@@ -0,0 +1,134 @@
1#include "jailtest.h"
2#include "../include/firejail_user.h"
3#include "../include/pid.h"
4#include <sys/wait.h>
5
6uid_t user_uid = 0;
7gid_t user_gid = 0;
8char *user_name = NULL;
9char *user_home_dir = NULL;
10int arg_debug = 0;
11
12static char *usage_str =
13 "Usage: jailtest [options] directory [directory]\n\n"
14 "Options:\n"
15 " --debug - print debug messages.\n"
16 " --help, -? - this help screen.\n"
17 " --version - print program version and exit.\n";
18
19
20static void usage(void) {
21 printf("firetest - version %s\n\n", VERSION);
22 puts(usage_str);
23}
24
25static void cleanup(void) {
26 // running only as root
27 if (getuid() == 0) {
28 if (arg_debug)
29 printf("cleaning up!\n");
30 access_destroy();
31 virtual_destroy();
32 }
33}
34
35int main(int argc, char **argv) {
36 int i;
37 int findex = 0;
38
39 for (i = 1; i < argc; i++) {
40 if (strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "--help") == 0) {
41 usage();
42 return 0;
43 }
44 else if (strcmp(argv[i], "--version") == 0) {
45 printf("firetest version %s\n\n", VERSION);
46 return 0;
47 }
48 else if (strncmp(argv[i], "--hello=", 8) == 0) { // used by noexec test
49 printf(" Warning: I can run programs in %s\n", argv[i] + 8);
50 return 0;
51 }
52 else if (strcmp(argv[i], "--debug") == 0)
53 arg_debug = 1;
54 else if (strncmp(argv[i], "--", 2) == 0) {
55 fprintf(stderr, "Error: invalid option\n");
56 return 1;
57 }
58 else {
59 findex = i;
60 break;
61 }
62 }
63
64 // user setup
65 if (getuid() != 0) {
66 fprintf(stderr, "Error: you need to be root (via sudo) to run this program\n");
67 exit(1);
68 }
69 user_name = get_sudo_user();
70 assert(user_name);
71 user_home_dir = get_homedir(user_name, &user_uid, &user_gid);
72 if (user_uid == 0) {
73 fprintf(stderr, "Error: root user not supported\n");
74 exit(1);
75 }
76
77 // test setup
78 atexit(cleanup);
79 if (findex > 0) {
80 for (i = findex; i < argc; i++)
81 access_setup(argv[i]);
82 }
83
84 noexec_setup();
85 virtual_setup(user_home_dir);
86 virtual_setup("/tmp");
87 virtual_setup("/var/tmp");
88 virtual_setup("/dev");
89 virtual_setup("/etc");
90 virtual_setup("/bin");
91
92 // print processes
93 pid_read(0);
94 for (i = 0; i < max_pids; i++) {
95 if (pids[i].level == 1) {
96 uid_t uid = pid_get_uid(i);
97 if (uid != user_uid) // not interested in other user sandboxes
98 continue;
99
100 // in case the pid is that of a firejail process, use the pid of the first child process
101 uid_t pid = switch_to_child(i);
102 pid_print_list(i, 0); // no wrapping
103
104 pid_t child = fork();
105 if (child == -1)
106 errExit("fork");
107 if (child == 0) {
108 int rv = join_namespace(pid, "mnt");
109 if (rv == 0) {
110 virtual_test();
111 noexec_test(user_home_dir);
112 noexec_test("/tmp");
113 noexec_test("/var/tmp");
114 access_test();
115 }
116 else {
117 printf(" Error: I cannot join the process mount space\n");
118 exit(1);
119 }
120
121 // drop privileges in order not to trigger cleanup()
122 if (setgid(user_gid) != 0)
123 errExit("setgid");
124 if (setuid(user_uid) != 0)
125 errExit("setuid");
126 return 0;
127 }
128 int status;
129 wait(&status);
130 }
131 }
132
133 return 0;
134}
diff --git a/src/jailtest/noexec.c b/src/jailtest/noexec.c
new file mode 100644
index 000000000..d2f85514a
--- /dev/null
+++ b/src/jailtest/noexec.c
@@ -0,0 +1,94 @@
1#include "jailtest.h"
2#include <sys/wait.h>
3#include <sys/stat.h>
4#include <fcntl.h>
5
6static unsigned char *execfile = NULL;
7static int execfile_len = 0;
8
9void noexec_setup(void) {
10 // grab a copy of myself
11 char *self = realpath("/proc/self/exe", NULL);
12 if (self) {
13 struct stat s;
14 if (access(self, X_OK) == 0 && stat(self, &s) == 0) {
15 assert(s.st_size);
16 execfile = malloc(s.st_size);
17
18 int fd = open(self, O_RDONLY);
19 if (fd == -1)
20 errExit("open");
21 int len = 0;
22 do {
23 int rv = read(fd, execfile + len, s.st_size - len);
24 if (rv == -1)
25 errExit("read");
26 if (rv == 0) {
27 // something went wrong!
28 free(execfile);
29 execfile = NULL;
30 printf("Warning: I cannot grab a copy of myself, skipping noexec test...\n");
31 break;
32 }
33 len += rv;
34 }
35 while (len < s.st_size);
36 execfile_len = s.st_size;
37 close(fd);
38 }
39 }
40}
41
42
43void noexec_test(const char *path) {
44 assert(user_uid);
45
46 // I am root in sandbox mount namespace
47 if (!execfile)
48 return;
49
50 char *fname;
51 if (asprintf(&fname, "%s/jailtest-noexec-%d", path, getpid()) == -1)
52 errExit("asprintf");
53
54 pid_t child = fork();
55 if (child == -1)
56 errExit("fork");
57
58 if (child == 0) { // child
59 // drop privileges
60 if (setgid(user_gid) != 0)
61 errExit("setgid");
62 if (setuid(user_uid) != 0)
63 errExit("setuid");
64 int fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0700);
65 if (fd == -1) {
66 printf(" I cannot create files in %s, skipping noexec...\n", path);
67 exit(1);
68 }
69
70 int len = 0;
71 while (len < execfile_len) {
72 int rv = write(fd, execfile + len, execfile_len - len);
73 if (rv == -1 || rv == 0) {
74 printf(" I cannot create files in %s, skipping noexec....\n", path);
75 exit(1);
76 }
77 len += rv;
78 }
79 fchmod(fd, 0700);
80 close(fd);
81
82 char *arg;
83 if (asprintf(&arg, "--hello=%s", path) == -1)
84 errExit("asprintf");
85 int rv = execl(fname, fname, arg, NULL);
86 (void) rv; // if we get here execl failed
87 exit(0);
88 }
89
90 int status;
91 wait(&status);
92 int rv = unlink(fname);
93 (void) rv;
94} \ No newline at end of file
diff --git a/src/jailtest/utils.c b/src/jailtest/utils.c
new file mode 100644
index 000000000..b24783355
--- /dev/null
+++ b/src/jailtest/utils.c
@@ -0,0 +1,124 @@
1#include "jailtest.h"
2#include <errno.h>
3#include <pwd.h>
4#include <dirent.h>
5
6#define BUFLEN 4096
7
8char *get_sudo_user(void) {
9 char *user = getenv("SUDO_USER");
10 if (!user) {
11 user = getpwuid(getuid())->pw_name;
12 if (!user) {
13 fprintf(stderr, "Error: cannot detect login user\n");
14 exit(1);
15 }
16 }
17
18 return user;
19}
20
21char *get_homedir(const char *user, uid_t *uid, gid_t *gid) {
22 // find home directory
23 struct passwd *pw = getpwnam(user);
24 if (!pw)
25 goto errexit;
26
27 char *home = pw->pw_dir;
28 if (!home)
29 goto errexit;
30
31 *uid = pw->pw_uid;
32 *gid = pw->pw_gid;
33
34 return home;
35
36errexit:
37 fprintf(stderr, "Error: cannot find home directory for user %s\n", user);
38 exit(1);
39}
40
41int find_child(pid_t parent, pid_t *child) {
42 *child = 0; // use it to flag a found child
43
44 DIR *dir;
45 if (!(dir = opendir("/proc"))) {
46 // sleep 2 seconds and try again
47 sleep(2);
48 if (!(dir = opendir("/proc"))) {
49 fprintf(stderr, "Error: cannot open /proc directory\n");
50 exit(1);
51 }
52 }
53
54 struct dirent *entry;
55 char *end;
56 while (*child == 0 && (entry = readdir(dir))) {
57 pid_t pid = strtol(entry->d_name, &end, 10);
58 if (end == entry->d_name || *end)
59 continue;
60 if (pid == parent)
61 continue;
62
63 // open stat file
64 char *file;
65 if (asprintf(&file, "/proc/%u/status", pid) == -1) {
66 perror("asprintf");
67 exit(1);
68 }
69 FILE *fp = fopen(file, "r");
70 if (!fp) {
71 free(file);
72 continue;
73 }
74
75 // look for firejail executable name
76 char buf[BUFLEN];
77 while (fgets(buf, BUFLEN - 1, fp)) {
78 if (strncmp(buf, "PPid:", 5) == 0) {
79 char *ptr = buf + 5;
80 while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) {
81 ptr++;
82 }
83 if (*ptr == '\0') {
84 fprintf(stderr, "Error: cannot read /proc file\n");
85 exit(1);
86 }
87 if (parent == atoi(ptr)) {
88 // we don't want /usr/bin/xdg-dbus-proxy!
89 char *cmdline = pid_proc_cmdline(pid);
90 if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0)
91 *child = pid;
92 free(cmdline);
93 }
94 break; // stop reading the file
95 }
96 }
97 fclose(fp);
98 free(file);
99 }
100 closedir(dir);
101 return (*child)? 0:1; // 0 = found, 1 = not found
102}
103
104pid_t switch_to_child(pid_t pid) {
105 pid_t rv = pid;
106 errno = 0;
107 char *comm = pid_proc_comm(pid);
108 if (!comm) {
109 if (errno == ENOENT)
110 fprintf(stderr, "Error: cannot find process with pid %d\n", pid);
111 else
112 fprintf(stderr, "Error: cannot read /proc file\n");
113 exit(1);
114 }
115
116 if (strcmp(comm, "firejail") == 0) {
117 if (find_child(pid, &rv) == 1) {
118 fprintf(stderr, "Error: no valid sandbox\n");
119 exit(1);
120 }
121 }
122 free(comm);
123 return rv;
124}
diff --git a/src/jailtest/virtual.c b/src/jailtest/virtual.c
new file mode 100644
index 000000000..48296fdb1
--- /dev/null
+++ b/src/jailtest/virtual.c
@@ -0,0 +1,99 @@
1#include "jailtest.h"
2#include <dirent.h>
3#include <sys/wait.h>
4
5
6#define MAX_TEST_FILES 16
7static char *dirs[MAX_TEST_FILES];
8static char *files[MAX_TEST_FILES];
9static int files_cnt = 0;
10
11void virtual_setup(const char *directory) {
12 // I am root!
13 assert(directory);
14 assert(*directory == '/');
15 assert(files_cnt < MAX_TEST_FILES);
16
17 // try to open the dir as root
18 DIR *dir = opendir(directory);
19 if (!dir) {
20 fprintf(stderr, "Warning: directory %s not found, skipping\n", directory);
21 return;
22 }
23 closedir(dir);
24
25 // create a test file
26 char *test_file;
27 if (asprintf(&test_file, "%s/jailtest-private-%d", directory, getpid()) == -1)
28 errExit("asprintf");
29
30 FILE *fp = fopen(test_file, "w");
31 if (!fp) {
32 printf("Warning: I cannot create test file in directory %s, skipping...\n", directory);
33 return;
34 }
35 fprintf(fp, "this file was created by firetest utility, you can safely delete it\n");
36 fclose(fp);
37 if (strcmp(directory, user_home_dir) == 0) {
38 int rv = chown(test_file, user_uid, user_gid);
39 if (rv)
40 errExit("chown");
41 }
42
43 char *dname = strdup(directory);
44 if (!dname)
45 errExit("strdup");
46 dirs[files_cnt] = dname;
47 files[files_cnt] = test_file;
48 files_cnt++;
49}
50
51void virtual_destroy(void) {
52 // remove test files
53 int i;
54
55 for (i = 0; i < files_cnt; i++) {
56 int rv = unlink(files[i]);
57 (void) rv;
58 }
59 files_cnt = 0;
60}
61
62void virtual_test(void) {
63 // I am root in sandbox mount namespace
64 assert(user_uid);
65 int i;
66
67 printf(" Virtual dirs: "); fflush(0);
68
69 for (i = 0; i < files_cnt; i++) {
70 assert(files[i]);
71
72 // I am root!
73 pid_t child = fork();
74 if (child == -1)
75 errExit("fork");
76
77 if (child == 0) { // child
78 // drop privileges
79 if (setgid(user_gid) != 0)
80 errExit("setgid");
81 if (setuid(user_uid) != 0)
82 errExit("setuid");
83
84 // try to open the file for reading
85 FILE *fp = fopen(files[i], "r");
86 if (fp)
87 fclose(fp);
88 else
89 printf("%s, ", dirs[i]);
90 fflush(0);
91 exit(0);
92 }
93
94 // wait for the child to finish
95 int status;
96 wait(&status);
97 }
98 printf("\n");
99}
diff --git a/src/man/Makefile.in b/src/man/Makefile.in
index 1c4444307..1a1f8ba08 100644
--- a/src/man/Makefile.in
+++ b/src/man/Makefile.in
@@ -1,4 +1,4 @@
1all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man 1all: firecfg.man firejail.man firejail-login.man firejail-users.man firejail-profile.man firemon.man jailtest.man
2include ../common.mk 2include ../common.mk
3 3
4%.man: %.txt 4%.man: %.txt
diff --git a/src/man/jailtest.txt b/src/man/jailtest.txt
new file mode 100644
index 000000000..bc1999163
--- /dev/null
+++ b/src/man/jailtest.txt
@@ -0,0 +1,82 @@
1.TH JAILTEST 1 "MONTH YEAR" "VERSION" "JAILTEST man page"
2.SH NAME
3jailtest \- Simple utility program to test running sandboxes
4.SH SYNOPSIS
5sudo jailtest [OPTIONS] [directory]
6.SH DESCRIPTION
7WORK IN PROGRESS!
8jailtest attaches itself to all sandboxes started by the user and performs some basic tests
9on the sandbox filesystem:
10.TP
11\fB1. Virtual directories
12jailtest extracts a list with the main virtual directories installed by the sandbox.
13These directories are build by firejail at startup using --private* and --whitelist commands.
14.TP
15\fB2. Noexec test
16jailtest inserts executable programs in /home/username, /tmp, and /var/tmp directories
17and tries to run them form inside the sandbox, thus testing if the directory is executable or not.
18.TP
19\fB3. Read access test
20jailtest creates test files in the directories specified by the user and tries to read
21them from inside the sandbox.
22
23.TP
24The program is running as root exclusively under sudo.
25
26.SH OPTIONS
27.TP
28\fB\-\-debug
29Print debug messages
30.TP
31\fB\-?\fR, \fB\-\-help\fR
32Print options end exit.
33.TP
34\fB\-\-version
35Print program version and exit.
36.TP
37\fB[directory]
38One or more directories in user home to test for read access.
39
40.SH OUTPUT
41For each sandbox detected we print the following line:
42
43 PID:USER:Sandbox Name:Command
44
45It is followed by relevant sandbox information, such as the virtual directories and various warnings.
46
47.SH EXAMPLE
48
49.br
50$ sudo jailtest ~/.ssh ~/.gnupg
51.br
521429:netblue::/usr/bin/firejail /opt/firefox/firefox
53.br
54 Virtual dirs: /home/netblue, /tmp, /var/tmp, /dev, /etc,
55.br
565602:netblue::/usr/bin/firejail /usr/bin/ssh netblue@x.y.z.net
57.br
58 Virtual dirs: /var/tmp, /dev,
59.br
60 Warning: I can read ~/.ssh
61.br
625926:netblue::/usr/bin/firejail /usr/bin/gimp-2.10
63.br
64 Virtual dirs: /tmp, /var/tmp, /dev,
65.br
66 Warning: I can run programs in /home/netblue
67.br
686394:netblue:libreoffice:/usr/bin/firejail libreoffice
69.br
70 Virtual dirs: /tmp, /var/tmp, /dev,
71.br
72
73.SH LICENSE
74This 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.
75.PP
76Homepage: https://firejail.wordpress.com
77.SH SEE ALSO
78\&\flfirejail\fR\|(1),
79\&\flfirecfg\fR\|(1),
80\&\flfirejail-profile\fR\|(5),
81\&\flfirejail-login\fR\|(5)
82\&\flfirejail-users\fR\|(5)