intrusion detection system

This commit is contained in:
netblue30 2021-07-28 08:30:24 -04:00
parent 8b50039a1f
commit a627071b33
18 changed files with 1118 additions and 10 deletions

3
.gitignore vendored
View file

@ -22,12 +22,13 @@ firejail-users.5
firejail.1
firemon.1
firecfg.1
jailcheck.5
jailcheck.1
mkdeb.sh
src/firejail/firejail
src/firemon/firemon
src/firecfg/firecfg
src/ftee/ftee
src/fids/fids
src/tags
src/faudit/faudit
src/fnet/fnet

View file

@ -26,7 +26,7 @@ COMPLETIONDIRS = src/zsh_completion src/bash_completion
.PHONY: all
all: all_items mydirs $(MAN_TARGET) filters
APPS = src/firecfg/firecfg src/firejail/firejail src/firemon/firemon src/profstats/profstats src/jailcheck/jailcheck
SBOX_APPS = src/fbuilder/fbuilder src/ftee/ftee
SBOX_APPS = src/fbuilder/fbuilder src/ftee/ftee src/fids/fids
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
@ -135,7 +135,7 @@ endif
install -m 0644 -t $(DESTDIR)$(DOCDIR) COPYING README RELNOTES etc/templates/*
# profiles and settings
install -m 0755 -d $(DESTDIR)$(sysconfdir)/firejail
install -m 0644 -t $(DESTDIR)$(sysconfdir)/firejail etc/profile-a-l/*.profile etc/profile-m-z/*.profile etc/inc/*.inc etc/net/*.net etc/firejail.config
install -m 0644 -t $(DESTDIR)$(sysconfdir)/firejail etc/profile-a-l/*.profile etc/profile-m-z/*.profile etc/inc/*.inc etc/net/*.net etc/firejail.config etc/ids.config
sh -c "if [ ! -f $(DESTDIR)/$(sysconfdir)/firejail/login.users ]; then install -c -m 0644 etc/login.users $(DESTDIR)/$(sysconfdir)/firejail/.; fi;"
ifeq ($(BUSYBOX_WORKAROUND),yes)
./mketc.sh $(DESTDIR)$(sysconfdir)/firejail/disable-common.inc

View file

@ -202,6 +202,36 @@ The old whitelist/blacklist will remain as aliasses for the next one or two rele
in order to give users a chance to switch their local profiles.
The latest discussion on this issue is here: https://github.com/netblue30/firejail/issues/4379
### Intrusion Detection System ###
We are adding IDS capabilities in the next release. We have the list of files in [/etc/firejail/ids.config](https://github.com/netblue30/firejail/blob/master/etc/ids.config),
and we generate a [BLAKE2](https://en.wikipedia.org/wiki/BLAKE_%28hash_function%29) checksum in /var/lib/firejail/username.ids.
The program runs as regular user, each user has his own file in /var/lib/firejail.
Initialize the database:
`````
$ firejail --ids-init
Loading /etc/firejail/ids.config config file
500 1000 1500 2000
2457 files scanned
IDS database initialized
`````
Later, we check it:
`````
$ firejail --ids-check
Loading /etc/firejail/ids.config config file
500 1000 1500
Warning: modified /home/netblue/.bashrc
2000
2457 files scanned: modified 1, permissions 0, new 0, removed 0
`````
The program will print the files that have been modified since the database was created, or the files with different access permissions.
New files and deleted files are also flagged.
Currently while scanning the file system symbolic links are not followed, and files the user doesn't have read access are silently dropped.
The program can also be run as root (sudo firejail --ids-init/--ids-check).
### Profile Statistics
A small tool to print profile statistics. Compile as usual and run in /etc/profiles:

3
configure vendored
View file

@ -4350,7 +4350,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/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/jailcheck/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/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/jailcheck/Makefile src/fids/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@ -5084,6 +5084,7 @@ do
"src/bash_completion/Makefile") CONFIG_FILES="$CONFIG_FILES src/bash_completion/Makefile" ;;
"test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;;
"src/jailcheck/Makefile") CONFIG_FILES="$CONFIG_FILES src/jailcheck/Makefile" ;;
"src/fids/Makefile") CONFIG_FILES="$CONFIG_FILES src/fids/Makefile" ;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
esac

View file

@ -300,7 +300,7 @@ AC_CONFIG_FILES([Makefile src/common.mk src/lib/Makefile src/fcopy/Makefile src/
src/firemon/Makefile src/libtrace/Makefile src/libtracelog/Makefile src/firecfg/Makefile src/fbuilder/Makefile src/fsec-print/Makefile \
src/ftee/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/jailcheck/Makefile])
src/jailcheck/Makefile src/fids/Makefile])
AC_OUTPUT
cat <<EOF

134
etc/ids.config Normal file
View file

@ -0,0 +1,134 @@
# /etc/firejail/ids.config - configuration file for Firejail's Intrusion Detection System
#
# Each line is a file or directory name such as
# /usr/bin
# or
# ${HOME}/Desktop/*.desktop
#
# ${HOME} is expanded to user home directory, and * is the regular
# globbing match for zero or more characters.
#
# File or directory names starting with ! are not scanned. For example
# !${HOME}/.ssh/known_hosts
# ${HOME}/.ssh
# will scan all files in ~/.ssh directory with the exception of knonw_hosts
#
# This config file is overwritten when a new version of Firejail is installed.
# For global customization use /etc/firejal/ids.config.local.
include ids.config.local
### system executables ###
/bin
/sbin
/usr/bin
/usr/sbin
/usr/games
/usr/libexec
### user executables ###
#/usr/local
#/opt
### system libraries ###
#/lib
#/usr/lib
#/usr/lib32
#/usr/lib64
#/usr/libx32
### shells local ###
${HOME}/.bashrc # bash
${HOME}/.bash_profile
${HOME}/.bash_login
${HOME}/.bash_logout
${HOME}/.zshenv #zsh
${HOME}/.zshprofile
${HOME}/.zshrc
${HOME}/.zlogin
${HOME}/.zlogout
${HOME}/.config/fish/config.fish # fish
${HOME}/.profile # others
${HOME}/.login
${HOME}/.logout
${HOME}/.cshrc
${HOME}/.tcshrc
${HOME}/.kshrc
### shells global ###
/etc/shells # all
/etc/profile
/etc/profile.d
/etc/environment
/etc/skel
/etc/dircolors
/etc/bash.bashrc # bash
/etc/bash_completion*
/etc/bashrc
/etc/zshenv # zsh
/etc/zprofile
/etc/zshrc
/etc/zlogin
/etc/zlogout
/etc/fish # fish
/etc/complete.tcsh # tcsh
/etc/csh.cshrc
/etc/csh.login
/etc/csh.logout
/etc/ksh.kshrc # ksh
### X11 ###
${HOME}/.xsessionrc
${HOME}/.xsession
${HOME}/.Xsession
${HOME}/.xinitrc
${HOME}/.xprofile
${HOME}/.xmodmaprc
${HOME}/.xserverrc
${HOME}/.Xresurces
/etc/X11
### window/desktop manager ###
${HOME}/.config/autostart
${HOME}/Desktop/*.desktop
${HOME}/.config/lxsession/LXDE/autostart
${HOME}/.gnomerc
${HOME}/.gtkrc
${HOME}/.kderc
### security ###
${HOME}/.gnupg
${HOME}/.config/firejail
/etc/apparmor*
/etc/selinux
/etc/security
/etc/group*
/etc/gshadow*
/etc/passwd*
/etc/shadow*
/etc/pam.*
/etc/sudoers*
/etc/securetty
/etc/cracklib
/etc/libaudit.conf
/etc/tripwire
/etc/aide
/etc/chkrootkit.conf
/etc/rkhunter.conf
*** network security ***
/etc/services
/etc/hosts.*
/etc/ssl
/etc/ca-certificates*
/usr/share/ca-certificates
!${HOME}/.ssh/known_hosts # excluding
${HOME}/.ssh
/etc/ssh
/etc/snort
/etc/wireshark
### system config ###
/etc/default
/etc/crontab
/etc/cron.*

View file

@ -40,7 +40,7 @@ BINOBJS = $(foreach file, $(OBJS), $file)
CFLAGS = @CFLAGS@
CFLAGS += -ggdb $(HAVE_FATAL_WARNINGS) -O2 -DVERSION='"$(VERSION)"' $(HAVE_GCOV)
CFLAGS += -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/firejail"' -DLIBDIR='"$(libdir)"' -DBINDIR='"$(bindir)"'
CFLAGS += -DPREFIX='"$(prefix)"' -DSYSCONFDIR='"$(sysconfdir)/firejail"' -DLIBDIR='"$(libdir)"' -DBINDIR='"$(bindir)"' -DVARDIR='"/var/lib/firejail"'
MANFLAGS = $(HAVE_LTS) $(HAVE_OUTPUT) $(HAVE_X11) $(HAVE_PRIVATE_HOME) $(HAVE_APPARMOR) $(HAVE_OVERLAYFS) $(HAVE_USERTMPFS) $(HAVE_DBUSPROXY) $(HAVE_FIRETUNNEL) $(HAVE_GLOBALCFG) $(HAVE_CHROOT) $(HAVE_NETWORK) $(HAVE_USERNS) $(HAVE_FILE_TRANSFER) $(HAVE_SELINUX) $(HAVE_SUID) $(HAVE_FORCE_NONEWPRIVS)
CFLAGS += $(MANFLAGS)
CFLAGS += -fstack-protector-all -D_FORTIFY_SOURCE=2 -fPIE -Wformat -Wformat-security

18
src/fids/Makefile.in Normal file
View file

@ -0,0 +1,18 @@
.PHONY: all
all: fids
include ../common.mk
%.o : %.c $(H_FILE_LIST) ../include/common.h
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(INCLUDE) -c $< -o $@
#fseccomp: $(OBJS) ../lib/common.o ../lib/errno.o ../lib/syscall.o
fids: $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) $(EXTRA_LDFLAGS)
.PHONY: clean
clean:; rm -fr *.o fids *.gcov *.gcda *.gcno *.plist
.PHONY: distclean
distclean: clean
rm -fr Makefile

176
src/fids/blake2b.c Normal file
View file

@ -0,0 +1,176 @@
/*
* Copyright (C) 2014-2021 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.
*/
/* A simple unkeyed BLAKE2b Implementation based on the official reference
* from https://github.com/BLAKE2/BLAKE2.
*
* The original code was released under CC0 1.0 Universal license (Creative Commons),
* a public domain license.
*/
#include "fids.h"
// little-endian vs big-endian is irrelevant since the checksum is calculated and checked on the same computer.
static inline uint64_t load64( const void *src ) {
uint64_t w;
memcpy( &w, src, sizeof( w ) );
return w;
}
// mixing function
#define ROTR64(x, y) (((x) >> (y)) ^ ((x) << (64 - (y))))
#define G(a, b, c, d, x, y) { \
v[a] = v[a] + v[b] + x; \
v[d] = ROTR64(v[d] ^ v[a], 32); \
v[c] = v[c] + v[d]; \
v[b] = ROTR64(v[b] ^ v[c], 24); \
v[a] = v[a] + v[b] + y; \
v[d] = ROTR64(v[d] ^ v[a], 16); \
v[c] = v[c] + v[d]; \
v[b] = ROTR64(v[b] ^ v[c], 63); }
// init vector
static const uint64_t iv[8] = {
0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,
0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,
0x510E527FADE682D1, 0x9B05688C2B3E6C1F,
0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179
};
const uint8_t sigma[12][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }
};
// blake2b context
typedef struct {
uint8_t b[128]; // input buffer
uint64_t h[8]; // chained state
uint64_t t[2]; // total number of bytes
size_t c; // pointer for b[]
size_t outlen; // digest size
} CTX;
// compress function
static void compress(CTX *ctx, int last) {
uint64_t m[16];
uint64_t v[16];
size_t i;
for (i = 0; i < 16; i++)
m[i] = load64(&ctx->b[8 * i]);
for (i = 0; i < 8; i++) {
v[i] = ctx->h[i];
v[i + 8] = iv[i];
}
v[12] ^= ctx->t[0];
v[13] ^= ctx->t[1];
if (last)
v[14] = ~v[14];
for (i = 0; i < 12; i++) {
G( 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]);
G( 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]);
G( 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]);
G( 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]);
G( 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]);
G( 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]);
G( 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]);
G( 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]);
}
for( i = 0; i < 8; ++i )
ctx->h[i] ^= v[i] ^ v[i + 8];
}
static int init(CTX *ctx, size_t outlen) { // (keylen=0: no key)
size_t i;
if (outlen == 0 || outlen > 64)
return -1;
for (i = 0; i < 8; i++)
ctx->h[i] = iv[i];
ctx->h[0] ^= 0x01010000 ^ outlen;
ctx->t[0] = 0;
ctx->t[1] = 0;
ctx->c = 0;
ctx->outlen = outlen;
return 0;
}
static void update(CTX *ctx, const void *in, size_t inlen) {
size_t i;
for (i = 0; i < inlen; i++) {
if (ctx->c == 128) {
ctx->t[0] += ctx->c;
if (ctx->t[0] < ctx->c)
ctx->t[1]++;
compress(ctx, 0);
ctx->c = 0;
}
ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
}
}
static void final(CTX *ctx, void *out) {
size_t i;
ctx->t[0] += ctx->c;
if (ctx->t[0] < ctx->c)
ctx->t[1]++;
while (ctx->c < 128)
ctx->b[ctx->c++] = 0;
compress(ctx, 1);
for (i = 0; i < ctx->outlen; i++) {
((uint8_t *) out)[i] =
(ctx->h[i >> 3] >> (8 * (i & 7))) & 0xFF;
}
}
// public function
int blake2b(void *out, size_t outlen, const void *in, size_t inlen) {
CTX ctx;
if (init(&ctx, outlen))
return -1;
update(&ctx, in, inlen);
final(&ctx, out);
return 0;
}

16
src/fids/config Normal file
View file

@ -0,0 +1,16 @@
/bin
/sbin
/usr/bin
/usr/sbin
/usr/games
/opt
/usr/share/ca-certificates
/home/netblue/.bashrc
/home/netblue/.config/firejail
/home/netblue/.config/autostart
/home/netblue/Desktop/*.desktop
/home/netblue/.ssh
/home/netblue/.gnupg

159
src/fids/db.c Normal file
View file

@ -0,0 +1,159 @@
/*
* Copyright (C) 2014-2021 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"fids.h"
typedef struct db_t {
struct db_t *next;
char *fname;
char *checksum;
char *mode;
int checked;
} DB;
#define MAXBUF 4096
static DB *database[HASH_MAX] = {NULL};
// djb2 hash function by Dan Bernstein
static unsigned hash(const char *str) {
const unsigned char *s = (unsigned char *) str;
unsigned long hash = 5381;
int c;
while (c = *s++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash & (HASH_MAX - 1);
}
#if 0
// for testing the hash table
static void db_print(void) {
int i;
for (i = 0; i < HASH_MAX; i++) {
int cnt = 0;
DB *ptr = database[i];
while (ptr) {
cnt++;
ptr = ptr->next;
}
printf("%d ", cnt);
fflush(0);
}
printf("\n");
}
#endif
static void db_add(const char *fname, const char *checksum, const char *mode) {
DB *ptr = malloc(sizeof(DB));
if (!ptr)
errExit("malloc");
ptr->fname = strdup(fname);
ptr->checksum = strdup(checksum);
ptr->mode = strdup(mode);
ptr->checked = 0;
if (!ptr->fname || !ptr->checksum || !ptr->mode)
errExit("strdup");
unsigned h = hash(fname);
ptr->next = database[h];
database[h] = ptr;
}
void db_check(const char *fname, const char *checksum, const char *mode) {
assert(fname);
assert(checksum);
assert(mode);
unsigned h =hash(fname);
DB *ptr = database[h];
while (ptr) {
if (strcmp(fname, ptr->fname) == 0) {
ptr->checked = 1;
break;
}
ptr = ptr->next;
}
if (ptr ) {
if (strcmp(checksum, ptr->checksum)) {
f_modified++;
fprintf(stderr, "\nWarning: modified %s\n", fname);
}
if (strcmp(mode, ptr->mode)) {
f_permissions++;
fprintf(stderr, "\nWarning: permissions %s: old %s, new %s\n",
fname, ptr->mode, mode);
}
}
else {
f_new++;
fprintf(stderr, "\nWarning: new file %s\n", fname);
}
}
void db_missing(void) {
int i;
for (i = 0; i < HASH_MAX; i++) {
DB *ptr = database[i];
while (ptr) {
if (!ptr->checked) {
f_removed++;
fprintf(stderr, "Warning: removed %s\n", ptr->fname);
}
ptr = ptr->next;
}
}
}
// return 0 if ok, 1 if error
int db_init(void) {
char buf[MAXBUF];
while(fgets(buf, MAXBUF, stdin)) {
// split - tab separated
char *mode = buf;
char *ptr = strchr(buf, '\t');
if (!ptr)
goto errexit;
*ptr = '\0';
char *checksum = ptr + 1;
ptr = strchr(checksum, '\t');
if (!ptr)
goto errexit;
*ptr = '\0';
char *fname = ptr + 1;
ptr = strchr(fname, '\n');
if (!ptr)
goto errexit;
*ptr = '\0';
db_add(fname, checksum, mode);
}
// db_print();
return 0;
errexit:
fprintf(stderr, "Error fids: database corrupted\n");
exit(1);
}

56
src/fids/db_exclude.c Normal file
View file

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014-2021 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"fids.h"
typedef struct db_exclude_t {
struct db_exclude_t *next;
char *fname;
int len;
} DB_EXCLUDE;
static DB_EXCLUDE *database = NULL;
void db_exclude_add(const char *fname) {
assert(fname);
DB_EXCLUDE *ptr = malloc(sizeof(DB_EXCLUDE));
if (!ptr)
errExit("malloc");
ptr->fname = strdup(fname);
if (!ptr->fname)
errExit("strdup");
ptr->len = strlen(fname);
ptr->next = database;
database = ptr;
}
int db_exclude_check(const char *fname) {
assert(fname);
DB_EXCLUDE *ptr = database;
while (ptr != NULL) {
if (strncmp(fname, ptr->fname, ptr->len) == 0)
return 1;
ptr = ptr->next;
}
return 0;
}

51
src/fids/fids.h Normal file
View file

@ -0,0 +1,51 @@
/*
* Copyright (C) 2014-2021 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.
*/
#ifndef FIDS_H
#define FIDS_H
#include "../include/common.h"
// main.c
#define MAX_DIR_LEVEL 20 // max directory tree depth
#define MAX_INCLUDE_LEVEL 10 // max include level for config files
extern int f_scanned;
extern int f_modified;
extern int f_new;
extern int f_removed;
extern int f_permissions;
// db.c
#define HASH_MAX 2048 // power of 2
int db_init(void);
void db_check(const char *fname, const char *checksum, const char *mode);
void db_missing(void);
// db_exclude.c
void db_exclude_add(const char *fname);
int db_exclude_check(const char *fname);
// blake2b.c
//#define KEY_SIZE 128 // key size in bytes
#define KEY_SIZE 256
//#define KEY_SIZE 512
int blake2b(void *out, size_t outlen, const void *in, size_t inlen);
#endif

370
src/fids/main.c Normal file
View file

@ -0,0 +1,370 @@
/*
* Copyright (C) 2014-2021 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 "fids.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <dirent.h>
#include <glob.h>
#define MAXBUF 4096
static int dir_level = 1;
static int include_level = 0;
int arg_init = 0;
int arg_check = 0;
char *arg_homedir = NULL;
char *arg_dbfile = NULL;
int f_scanned = 0;
int f_modified = 0;
int f_new = 0;
int f_removed = 0;
int f_permissions = 0;
static inline int is_dir(const char *fname) {
assert(fname);
struct stat s;
int rv = stat(fname, &s);
if (S_ISDIR(s.st_mode))
return 1;
return 0;
}
static inline int is_link(const char *fname) {
assert(fname);
char c;
ssize_t rv = readlink(fname, &c, 1);
return (rv != -1);
}
// mode is an array of 10 chars or more
static inline void file_mode(const char *fname, char *mode) {
assert(fname);
assert(mode);
struct stat s;
if (stat(fname, &s)) {
*mode = '\0';
return;
}
sprintf(mode, (s.st_mode & S_IRUSR) ? "r" : "-");
sprintf(mode + 1, (s.st_mode & S_IWUSR) ? "w" : "-");
sprintf(mode + 2, (s.st_mode & S_IXUSR) ? "x" : "-");
sprintf(mode + 3, (s.st_mode & S_IRGRP) ? "r" : "-");
sprintf(mode + 4, (s.st_mode & S_IWGRP) ? "w" : "-");
sprintf(mode + 5, (s.st_mode & S_IXGRP) ? "x" : "-");
sprintf(mode + 6, (s.st_mode & S_IROTH) ? "r" : "-");
sprintf(mode + 7, (s.st_mode & S_IWOTH) ? "w" : "-");
sprintf(mode + 8, (s.st_mode & S_IXOTH) ? "x" : "-");
}
static void file_checksum(const char *fname) {
assert(fname);
int fd = open(fname, O_RDONLY);
if (fd == -1)
return;
off_t size = lseek(fd, 0, SEEK_END);
if (size < 0) {
close(fd);
return;
}
char *content = "empty";
int mmapped = 0;
if (size == 0) {
// empty files don't mmap - use "empty" string as the file content
size = 6; // strlen("empty") + 1
}
else {
content = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
mmapped = 1;
}
unsigned char checksum[KEY_SIZE / 8];
blake2b(checksum, sizeof(checksum), content, size);
if (mmapped)
munmap(content, size);
// calculate blake2 checksum
char str_checksum[(KEY_SIZE / 8) * 2 + 1];
int i;
char *ptr = str_checksum;
for (i = 0; i < sizeof(checksum); i++, ptr += 2)
sprintf(ptr, "%02x", (unsigned char ) checksum[i]);
// build permissions string
char mode[10];
file_mode(fname, mode);
if (arg_init)
printf("%s\t%s\t%s\n", mode, str_checksum, fname);
else if (arg_check)
db_check(fname, str_checksum, mode);
else
assert(0);
f_scanned++;
if (f_scanned % 500 == 0)
fprintf(stderr, "%d ", f_scanned);
fflush(0);
}
void list_directory(const char *fname) {
assert(fname);
if (dir_level > MAX_DIR_LEVEL) {
fprintf(stderr, "Warning fids: maximum depth level exceeded for %s\n", fname);
return;
}
if (db_exclude_check(fname))
return;
if (is_link(fname))
return;
if (!is_dir(fname)) {
file_checksum(fname);
return;
}
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(fname)))
return;
dir_level++;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char *path;
if (asprintf(&path, "%s/%s", fname, entry->d_name) == -1)
errExit("asprintf");
list_directory(path);
free(path);
}
closedir(dir);
dir_level--;
}
void globbing(const char *fname) {
assert(fname);
// filter top directory
if (strcmp(fname, "/") == 0)
return;
glob_t globbuf;
int globerr = glob(fname, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf);
if (globerr) {
fprintf(stderr, "Error fids: failed to glob pattern %s\n", fname);
exit(1);
}
int i;
for (i = 0; i < globbuf.gl_pathc; i++) {
char *path = globbuf.gl_pathv[i];
assert(path);
list_directory(path);
}
globfree(&globbuf);
}
static void process_config(const char *fname) {
assert(fname);
if (++include_level >= MAX_INCLUDE_LEVEL) {
fprintf(stderr, "Error ids: maximum include level for config files exceeded\n");
exit(1);
}
// make sure the file is owned by root
struct stat s;
if (stat(fname, &s)) {
if (include_level == 1) {
fprintf(stderr, "Error ids: config file not found\n");
exit(1);
}
return;
}
if (s.st_uid || s.st_gid) {
fprintf(stderr, "Error ids: config file not owned by root\n");
exit(1);
}
fprintf(stderr, "Loading %s config file\n", fname);
FILE *fp = fopen(fname, "r");
if (!fp) {
fprintf(stderr, "Error fids: cannot open config file %s\n", fname);
exit(1);
}
char buf[MAXBUF];
int line = 0;
while (fgets(buf, MAXBUF, fp)) {
line++;
// trim \n
char *ptr = strchr(buf, '\n');
if (ptr)
*ptr = '\0';
// comments
ptr = strchr(buf, '#');
if (ptr)
*ptr = '\0';
// empty space
ptr = buf;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
char *start = ptr;
// empty line
if (*start == '\0')
continue;
// trailing spaces
ptr = start + strlen(start);
ptr--;
while (*ptr == ' ' || *ptr == '\t')
*ptr-- = '\0';
// replace ${HOME}
if (strncmp(start, "include", 7) == 0) {
ptr = start + 7;
if ((*ptr != ' ' && *ptr != '\t') || *ptr == '\0') {
fprintf(stderr, "Error fids: invalid line %d in %s\n", line, fname);
exit(1);
}
while (*ptr == ' ' || *ptr == '\t')
ptr++;
if (*ptr == '/')
process_config(ptr);
else {
// assume the file is in /etc/firejail
char *tmp;
if (asprintf(&tmp, "/etc/firejail/%s", ptr) == -1)
errExit("asprintf");
process_config(tmp);
free(tmp);
}
}
else if (*start == '!') {
// exclude file or dir
start++;
if (strncmp(start, "${HOME}", 7))
db_exclude_add(start);
else {
char *fname;
if (asprintf(&fname, "%s%s", arg_homedir, start + 7) == -1)
errExit("asprintf");
db_exclude_add(fname);
free(fname);
}
}
else if (strncmp(start, "${HOME}", 7))
globbing(start);
else {
char *fname;
if (asprintf(&fname, "%s%s", arg_homedir, start + 7) == -1)
errExit("asprintf");
globbing(fname);
free(fname);
}
}
fclose(fp);
include_level--;
}
void usage(void) {
printf("Usage: fids [--help|-h|-?] --init|--check homedir\n");
}
int main(int argc, char **argv) {
int i;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 ||
strcmp(argv[i], "-?") == 0 ||
strcmp(argv[i], "--help") == 0) {
usage();
return 0;
}
else if (strcmp(argv[i], "--init") == 0)
arg_init = 1;
else if (strcmp(argv[i], "--check") == 0)
arg_check = 1;
else if (strncmp(argv[i], "--", 2) == 0) {
fprintf(stderr, "Error fids: invalid argument %s\n", argv[i]);
exit(1);
}
}
if (argc != 3) {
fprintf(stderr, "Error fids: invalid number of arguments\n");
exit(1);
}
arg_homedir = argv[2];
int op = arg_check + arg_init;
if (op == 0 || op == 2) {
fprintf(stderr, "Error fids: use either --init or --check\n");
exit(1);
}
if (arg_init) {
process_config(SYSCONFDIR"/ids.config");
fprintf(stderr, "\n%d files scanned\n", f_scanned);
fprintf(stderr, "IDS database initialized\n");
}
else if (arg_check) {
if (db_init()) {
fprintf(stderr, "Error: IDS database not initialized, please run \"firejail --ids-init\"\n");
exit(1);
}
process_config(SYSCONFDIR"/ids.config");
fprintf(stderr, "\n%d files scanned: modified %d, permissions %d, new %d, removed %d\n",
f_scanned, f_modified, f_permissions, f_new, f_removed);
db_missing();
}
else
assert(0);
return 0;
}

View file

@ -835,7 +835,6 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc,
#define PATH_FNET_MAIN (LIBDIR "/firejail/fnet") // when called from main thread
#define PATH_FNET (RUN_FIREJAIL_LIB_DIR "/fnet") // when called from sandbox thread
//#define PATH_FNETFILTER (LIBDIR "/firejail/fnetfilter")
#define PATH_FNETFILTER (RUN_FIREJAIL_LIB_DIR "/fnetfilter")
#define PATH_FIREMON (PREFIX "/bin/firemon")
@ -848,17 +847,16 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc,
// it is also run from inside the sandbox by --debug; in this case we do an access(filename, X_OK) test first
#define PATH_FSEC_PRINT (LIBDIR "/firejail/fsec-print")
//#define PATH_FSEC_OPTIMIZE (LIBDIR "/firejail/fsec-optimize")
#define PATH_FSEC_OPTIMIZE (RUN_FIREJAIL_LIB_DIR "/fsec-optimize")
//#define PATH_FCOPY (LIBDIR "/firejail/fcopy")
#define PATH_FCOPY (RUN_FIREJAIL_LIB_DIR "/fcopy")
#define SBOX_STDIN_FILE "/run/firejail/mnt/sbox_stdin"
//#define PATH_FLDD (LIBDIR "/firejail/fldd")
#define PATH_FLDD (RUN_FIREJAIL_LIB_DIR "/fldd")
#define PATH_FIDS (LIBDIR "/firejail/fids")
// bitmapped filters for sbox_run
#define SBOX_ROOT (1 << 0) // run the sandbox as root
#define SBOX_USER (1 << 1) // run the sandbox as a regular user
@ -903,4 +901,7 @@ void dhcp_start(void);
// selinux.c
void selinux_relabel_path(const char *path, const char *inside_path);
// ids.c
void run_ids(int argc, char **argv);
#endif

89
src/firejail/ids.c Normal file
View file

@ -0,0 +1,89 @@
/*
* Copyright (C) 2014-2021 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/types.h>
#include <sys/stat.h>
#include <fcntl.h>
static void ids_init(void) {
// store checksums as root in /var/lib/firejail/${USERNAME}.ids
char *fname;
if (asprintf(&fname, VARDIR"/%s.ids", cfg.username) == -1)
errExit("asprintf");
int rv = unlink(fname);
(void) rv;
int fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd < 0) {
fprintf(stderr, "Error: cannot create %s\n", fname);
exit(1);
}
// redirect output
close(STDOUT_FILENO);
if (dup(fd) != STDOUT_FILENO)
errExit("dup");
close(fd);
sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FIDS, "--init", cfg.homedir);
}
static void ids_check(void) {
// store checksums as root in /var/lib/firejail/${USERNAME}.ids
char *fname;
if (asprintf(&fname, VARDIR"/%s.ids", cfg.username) == -1)
errExit("asprintf");
int fd = open(fname, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Error: cannot open %s\n", fname);
exit(1);
}
// redirect input
close(STDIN_FILENO);
if (dup(fd) != STDIN_FILENO)
errExit("dup");
close(fd);
sbox_run(SBOX_USER | SBOX_CAPS_NONE | SBOX_SECCOMP| SBOX_ALLOW_STDIN, 3, PATH_FIDS, "--check", cfg.homedir);
}
void run_ids(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Error: only one IDS command expected\n");
exit(1);
}
EUID_ROOT();
struct stat s;
if (stat(VARDIR, &s)) // /var/lib/firejail
create_empty_dir_as_root(VARDIR, 0700);
if (strcmp(argv[1], "--ids-init") == 0)
ids_init();
else if (strcmp(argv[1], "--ids-check") == 0)
ids_check();
else
fprintf(stderr, "Error: unrecognized IDS command\n");
exit(0);
}

View file

@ -1068,6 +1068,10 @@ int main(int argc, char **argv, char **envp) {
if (check_arg(argc, argv, "--build", 0)) // supports both --build and --build=filename
run_builder(argc, argv); // this function will not return
// intrusion detection system
if (check_arg(argc, argv, "--ids-", 0)) // supports both --ids-init and --ids-check
run_ids(argc, argv); // this function will not return
EUID_ROOT();
#ifndef HAVE_SUID
if (geteuid() != 0) {

View file

@ -98,6 +98,8 @@ static char *usage_str =
" --help, -? - this help screen.\n"
" --hostname=name - set sandbox hostname.\n"
" --hosts-file=file - use file as /etc/hosts.\n"
" --ids-check - verify file system.\n"
" --ids-init - initialize IDS database.\n"
" --ignore=command - ignore command in profile files.\n"
#ifdef HAVE_NETWORK
" --interface=name - move interface in sandbox.\n"