From a627071b33b42dd24a90070236d2f85eeebc423c Mon Sep 17 00:00:00 2001 From: netblue30 Date: Wed, 28 Jul 2021 08:30:24 -0400 Subject: intrusion detection system --- src/common.mk.in | 2 +- src/fids/Makefile.in | 18 +++ src/fids/blake2b.c | 176 +++++++++++++++++++++++ src/fids/config | 16 +++ src/fids/db.c | 159 +++++++++++++++++++++ src/fids/db_exclude.c | 56 ++++++++ src/fids/fids.h | 51 +++++++ src/fids/main.c | 370 ++++++++++++++++++++++++++++++++++++++++++++++++ src/firejail/firejail.h | 9 +- src/firejail/ids.c | 89 ++++++++++++ src/firejail/main.c | 4 + src/firejail/usage.c | 2 + 12 files changed, 947 insertions(+), 5 deletions(-) create mode 100644 src/fids/Makefile.in create mode 100644 src/fids/blake2b.c create mode 100644 src/fids/config create mode 100644 src/fids/db.c create mode 100644 src/fids/db_exclude.c create mode 100644 src/fids/fids.h create mode 100644 src/fids/main.c create mode 100644 src/firejail/ids.c (limited to 'src') diff --git a/src/common.mk.in b/src/common.mk.in index 5ae8bf204..d117433dc 100644 --- a/src/common.mk.in +++ b/src/common.mk.in @@ -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 diff --git a/src/fids/Makefile.in b/src/fids/Makefile.in new file mode 100644 index 000000000..5530bcee2 --- /dev/null +++ b/src/fids/Makefile.in @@ -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 diff --git a/src/fids/blake2b.c b/src/fids/blake2b.c new file mode 100644 index 000000000..f2aa5ae66 --- /dev/null +++ b/src/fids/blake2b.c @@ -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; +} diff --git a/src/fids/config b/src/fids/config new file mode 100644 index 000000000..c18c97260 --- /dev/null +++ b/src/fids/config @@ -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 + diff --git a/src/fids/db.c b/src/fids/db.c new file mode 100644 index 000000000..e0021bc48 --- /dev/null +++ b/src/fids/db.c @@ -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); +} + diff --git a/src/fids/db_exclude.c b/src/fids/db_exclude.c new file mode 100644 index 000000000..994e6f9df --- /dev/null +++ b/src/fids/db_exclude.c @@ -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; +} + diff --git a/src/fids/fids.h b/src/fids/fids.h new file mode 100644 index 000000000..a2e2886fe --- /dev/null +++ b/src/fids/fids.h @@ -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 \ No newline at end of file diff --git a/src/fids/main.c b/src/fids/main.c new file mode 100644 index 000000000..b53a9828e --- /dev/null +++ b/src/fids/main.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 545573c08..2a7d88575 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h @@ -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 diff --git a/src/firejail/ids.c b/src/firejail/ids.c new file mode 100644 index 000000000..59acdb1fe --- /dev/null +++ b/src/firejail/ids.c @@ -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 +#include +#include + + +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); +} \ No newline at end of file diff --git a/src/firejail/main.c b/src/firejail/main.c index 655e6e9d0..ef3bf8bf5 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c @@ -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) { diff --git a/src/firejail/usage.c b/src/firejail/usage.c index b4f3021c7..d843c74ae 100644 --- a/src/firejail/usage.c +++ b/src/firejail/usage.c @@ -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" -- cgit v1.2.3-54-g00ecf