23 Commits

Author SHA1 Message Date
8700f4f5a2 Changed perm/temp permission logic 2025-05-21 18:54:35 +02:00
0df75ee195 Fixed old sqlite3 version compatibility bug. 2025-05-21 18:31:05 +02:00
467087d76e Added performance tests and creation permission flags 2025-05-21 16:21:33 +02:00
448c862731 Changed -O0 to -Og in the debug build 2025-05-20 11:25:38 +02:00
b550c93884 Fixed arbitrary permission order 2025-05-20 11:07:28 +02:00
a7e5d7d92d Fixed undefined order of permissions 2025-05-20 10:50:58 +02:00
56165c0b76 Fixed a mistake in README 2025-05-20 10:12:18 +02:00
b1ee452890 Updated readme 2025-05-20 10:11:32 +02:00
8e1c325f98 Added mutex to permissions checks to avoid inconsistent permission checking 2025-05-20 09:59:18 +02:00
754a26884c Changed realpath to readlink
/proc/pid/exe already seems to be a link to the absolute path to the
executable. This fixes bugs related to containerised applications.
2025-05-20 09:57:59 +02:00
2f82ab63ac Fixed undefined permission check errors 2025-05-20 09:24:07 +02:00
90d94c7615 Fixed SQL injection
Fixed an emabarassignly obvious SQL injection bug by throwing
`sqlite3_exec` away.
2025-05-19 21:18:19 +02:00
a1ba96bf67 Updated the test 2025-05-19 21:15:42 +02:00
e4dbc5becc Fixed wrong pid bug
The issue was that the thread ID wasn't factored in. A presumption was,
that FUSE already returned the PID, not TID. The issue was fixed by
implementing a function that translates the TID to PID.
2025-05-14 20:37:32 +02:00
33f55384bc Added more leak checks 2025-05-13 18:01:03 +02:00
c8f19fe30d Fixed invalid pi bug 2025-05-13 17:59:32 +02:00
4febeb7a82 Added a logfile 2025-05-13 17:59:00 +02:00
c7ec5819c6 Fixed typos 2025-05-08 10:16:02 +02:00
31f6cc6ab8 Added missing license headers 2025-05-07 16:06:19 +02:00
d4a2cb3749 Fixed garbage collector being inactive 2025-05-07 15:55:47 +02:00
bd4cedf996 Added garbage collection to the temporary permission table. 2025-05-07 15:43:34 +02:00
2a1e94f054 Updated readme 2025-05-06 18:22:15 +02:00
fb18484aa8 Merge pull request 'new-dialogue' (#8) from new-dialogue into main
Reviewed-on: #8
2025-05-06 18:10:43 +02:00
20 changed files with 514 additions and 188 deletions

View File

@@ -36,7 +36,7 @@ CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES)) LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
ifeq ($(DEBUG),1) ifeq ($(DEBUG),1)
CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \ CFLAGS += -Og -pedantic -g -Wall -Wextra -Wcast-align \
-Wcast-qual -Wdisabled-optimization -Wformat=2 \ -Wcast-qual -Wdisabled-optimization -Wformat=2 \
-Winit-self -Wlogical-op -Wmissing-declarations \ -Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wredundant-decls -Wshadow \ -Wmissing-include-dirs -Wredundant-decls -Wshadow \

View File

@@ -15,20 +15,36 @@ Traditional access control mechanisms in operating systems allow the same level
- Install dependencies - Install dependencies
- libfuse3 - libfuse3
- Debian: `sudo apt install fuse3 libfuse3-dev` - Debian: `sudo apt install fuse3 libfuse3-dev`
- zenity
- Debian: `sudo apt install zenity`
- Build tools - Build tools
- Debian: `sudo apt install gcc make pkg-config` - Debian: `sudo apt install gcc make pkg-config`
- Build using `make`: - Build using `make`:
- In the project directory: `make` - In the project directory: `make`
- Use `make DEBUG=1` for testing. - Add `DEBUG=1` to show more compiler warnings.
- Add `TEST=1` to also test the program.
- Add `DIALOGUE=0` to not compile the dialogue program.
- Resulting binaries should appear in the `build` directory. - Resulting binaries should appear in the `build` directory.
## Installation
Currently, there is no installer implemented.
## Usage ## Usage
`icfs <FUSE arguments> [target directory]` ```
icfs <FUSE arguments> [target directory] [path to permanent permission database]
```
The filesystem will be mounted over the target directory, and ask user permission every time a file in that directory is opened. The filesystem will be mounted over the target directory, and ask user permission every time a file in that directory is opened. We highly recommend adding `-o default_permissions` to increase performance and add an additional security layer.
### Development build
Execute this command in the root directory of this project:
```
env PATH="$(realpath ./build):$PATH" build/icfs <FUSE arguments> [target directory] [path to permanent permission database]
```
The `env PATH="$(realpath ./build):$PATH"` adds the access dialogue program to PATH, allowing ICFS to call it seamlessly.
## Docs ## Docs

View File

@@ -1,3 +1,11 @@
/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#ifndef ACCESS_T_H #ifndef ACCESS_T_H
#define ACCESS_T_H #define ACCESS_T_H

View File

@@ -11,7 +11,9 @@
See the file LICENSE. See the file LICENSE.
*/ */
#include "process_info.h"
#include "real_filename.h" #include "real_filename.h"
#include "set_mode_t.h"
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#define FUSE_USE_VERSION 31 #define FUSE_USE_VERSION 31
@@ -41,8 +43,13 @@
#include "fuse_operations.h" #include "fuse_operations.h"
#include "proc_operations.h" #include "proc_operations.h"
#include "sourcefs.h" #include "sourcefs.h"
#include "temp_permissions_table.h"
#include "ui-socket.h" #include "ui-socket.h"
int auto_create_perm = GRANT_TEMP;
void set_auto_create_perm(int val) { auto_create_perm = val; }
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
(void)conn; (void)conn;
cfg->use_ino = 1; cfg->use_ino = 1;
@@ -53,7 +60,9 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
in current function (recommended in high level API) or set fi->direct_io in current function (recommended in high level API) or set fi->direct_io
in xmp_create() or xmp_open(). */ in xmp_create() or xmp_open(). */
cfg->direct_io = 1; cfg->direct_io = 1;
#if FUSE_VERSION > FUSE_MAKE_VERSION(3, 14)
cfg->parallel_direct_writes = 1; cfg->parallel_direct_writes = 1;
#endif
/* Pick up changes from lower filesystem right away. This is /* Pick up changes from lower filesystem right away. This is
also necessary for better hardlink support. When the kernel also necessary for better hardlink support. When the kernel
@@ -67,6 +76,7 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
cfg->negative_timeout = 0; cfg->negative_timeout = 0;
fprintf(stderr, "%d\n", getpid()); fprintf(stderr, "%d\n", getpid());
assert(get_mountpoint() != NULL); assert(get_mountpoint() != NULL);
init_garbage_collector();
return NULL; return NULL;
} }
@@ -82,7 +92,7 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
else else
res = source_stat(path, stbuf); res = source_stat(path, stbuf);
if (res == -1) { if (res == -1) {
perror("Stat failed"); perror("[ICFS] Stat failed");
return -errno; return -errno;
} }
@@ -146,7 +156,7 @@ static int xmp_opendir(const char *path, struct fuse_file_info *fi) {
d->dp = source_opendir(path); d->dp = source_opendir(path);
if (d->dp == NULL) { if (d->dp == NULL) {
perror("Opendir failed"); perror("[ICFS] Opendir failed");
res = -errno; res = -errno;
free(d); free(d);
return res; return res;
@@ -265,8 +275,7 @@ static int xmp_unlink(const char *path) {
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
// ask the user for the permission for deleting the file // ask the user for the permission for deleting the file
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
@@ -313,8 +322,7 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
@@ -344,8 +352,7 @@ static int xmp_link(const char *from, const char *to) {
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(from, pi, 0)) { if (!interactive_access(from, pi, 0)) {
@@ -369,8 +376,7 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, 0)) { if (!interactive_access(path, pi, 0)) {
@@ -400,8 +406,7 @@ static int xmp_chown(const char *path, uid_t uid, gid_t gid,
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, 0)) { if (!interactive_access(path, pi, 0)) {
@@ -459,12 +464,11 @@ static int xmp_create(const char *path, mode_t mode,
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, GRANT_PERM)) { if (!interactive_access(path, pi, auto_create_perm)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@@ -484,8 +488,7 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
struct process_info pi; struct process_info pi;
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi = get_process_info(fc->pid);
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi)); // fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, 0)) { if (!interactive_access(path, pi, 0)) {

View File

@@ -14,5 +14,6 @@
#include <fuse3/fuse.h> #include <fuse3/fuse.h>
const struct fuse_operations *get_fuse_operations(); const struct fuse_operations *get_fuse_operations();
void set_auto_create_perm(int val);
#endif #endif

View File

@@ -10,6 +10,7 @@
See the file LICENSE. See the file LICENSE.
*/ */
#include <string.h>
#define FUSE_USE_VERSION 31 #define FUSE_USE_VERSION 31
#define _GNU_SOURCE #define _GNU_SOURCE
@@ -29,7 +30,26 @@ const char *mountpoint = NULL;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc < 3) { if (argc < 3) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to " fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database\n"); "the permanent permissions database] <ICFS "
"arguments>\n\t--no-perm-on-create - reqire access "
"permissions to create new files "
"(incompatible with --perm-on-create)\n\t--perm-on-create "
"- give permanent permissions to files a process creates "
"automatically (incompatible with --no-perm-on-create)\n");
return EXIT_FAILURE;
}
if ((0 == strcmp(argv[argc - 1], "--no-perm-on-create") &&
0 == strcmp(argv[argc - 2], "--temp-on-create")) ||
(0 == strcmp(argv[argc - 2], "--no-perm-on-create") &&
0 == strcmp(argv[argc - 1], "--temp-on-create"))) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database] <ICFS "
"arguments>\n\t--no-perm-on-create - reqire access "
"permissions to create new files"
"(incompatible with --temp-on-create)\n\t--perm-on-create "
"- give permanent permissions to files a process creates "
"automatically (incompatible with --no-perm-on-create)\n");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@@ -37,11 +57,20 @@ int main(int argc, char *argv[]) {
// permissions than it's caller reqested // permissions than it's caller reqested
umask(0); umask(0);
if (0 == strcmp(argv[argc - 1], "--no-perm-on-create")) {
set_auto_create_perm(0);
argc--;
}
if (0 == strcmp(argv[argc - 1], "--perm-on-create")) {
set_auto_create_perm(GRANT_PERM);
argc--;
}
// ui socket should always be initialized before anything else, since it // ui socket should always be initialized before anything else, since it
// handles the setuid bits! // handles the setuid bits!
int ret = init_ui_socket(argv[argc - 1]); int ret = init_ui_socket(argv[argc - 1]);
if (ret != 0) { if (ret != 0) {
fprintf(stderr, "Could not initalize ui-socket.\n"); fprintf(stderr, "[ICFS] Could not initalize ui-socket.\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -49,7 +78,7 @@ int main(int argc, char *argv[]) {
ret = source_init(mountpoint); ret = source_init(mountpoint);
if (ret != 0) { if (ret != 0) {
perror("source_init"); perror("[ICFS] source_init");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }

View File

@@ -29,19 +29,19 @@ const char *const table_name = "permissions";
const int column_count = 3; const int column_count = 3;
const char *const schema[] = {"executable", "filename", "mode"}; const char *const schema[] = {"executable", "filename", "mode"};
const char *const types[] = {"TEXT", "TEXT", "INTEGER"}; const char *const types[] = {"TEXT", "TEXT", "INTEGER"};
uid_t ruid, euid, current_pid; uid_t ruid, euid, current_uid;
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
void set_db_fsuid() { void set_db_fsuid() {
pthread_mutex_lock(&uid_switch); pthread_mutex_lock(&uid_switch);
if (current_pid == ruid) if (current_uid == ruid)
return; return;
int status = -1; int status = -1;
status = setfsuid(ruid); status = setfsuid(ruid);
if (status < 0) { if (status < 0) {
fprintf(stderr, "Couldn't set uid to %d.\n", ruid); fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", ruid);
exit(status); exit(status);
} }
pthread_mutex_unlock(&uid_switch); pthread_mutex_unlock(&uid_switch);
@@ -49,14 +49,14 @@ void set_db_fsuid() {
void set_real_fsuid() { void set_real_fsuid() {
pthread_mutex_lock(&uid_switch); pthread_mutex_lock(&uid_switch);
if (current_pid == ruid) if (current_uid == ruid)
return; return;
int status = -1; int status = -1;
status = setfsuid(ruid); status = setfsuid(ruid);
if (status < 0) { if (status < 0) {
fprintf(stderr, "Couldn't set uid to %d.\n", euid); fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", euid);
exit(status); exit(status);
} }
pthread_mutex_unlock(&uid_switch); pthread_mutex_unlock(&uid_switch);
@@ -67,12 +67,13 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
(void)notused; (void)notused;
(void)colname; (void)colname;
if (argc < 3) { if (argc < 3) {
fprintf(stderr, "Unexpected amount of arguments given to the callback.\n"); fprintf(stderr,
"[ICFS] Unexpected amount of arguments given to the callback.\n");
return 1; return 1;
} }
int column_num = atoi(argv[0]); int column_num = atoi(argv[0]);
if (column_num >= column_count) { if (column_num >= column_count) {
fprintf(stderr, "Table contains unexpected amount of columns.\n"); fprintf(stderr, "[ICFS] Table contains unexpected amount of columns.\n");
return 1; return 1;
} }
@@ -80,7 +81,8 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
strcmp(types[column_num], argv[2]) == 0) { strcmp(types[column_num], argv[2]) == 0) {
return 0; return 0;
} }
fprintf(stderr, "Column %d does not conform to the schema.\n", column_num); fprintf(stderr, "[ICFS] Column %d does not conform to the schema.\n",
column_num);
return 1; return 1;
} }
@@ -88,14 +90,15 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)colname; (void)colname;
if (argc < 3) { if (argc < 3) {
fprintf(stderr, fprintf(
"Unexpected amount of arguments given to the callback: %d.\n", stderr,
argc); "[ICFS] Unexpected amount of arguments given to the callback: %d.\n",
argc);
return 1; return 1;
} }
if (atoi(argv[2])) { if (atoi(argv[2])) {
fprintf(stderr, "Third column was: %s\n", argv[2]); fprintf(stderr, "[ICFS] Third column was: %s\n", argv[2]);
*(int *)flag = 1; *(int *)flag = 1;
} else { } else {
*(int *)flag = -1; *(int *)flag = -1;
@@ -104,7 +107,7 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) {
} }
int create_database_schema() { int create_database_schema() {
fprintf(stderr, "Creating table 'permissions'.\n"); fprintf(stderr, "[ICFS] Creating table 'permissions'.\n");
const char *create_query = const char *create_query =
"CREATE TABLE permissions(executable TEXT NOT " "CREATE TABLE permissions(executable TEXT NOT "
"NULL, filename TEXT NOT NULL, mode INTEGER NOT NULL);"; "NULL, filename TEXT NOT NULL, mode INTEGER NOT NULL);";
@@ -112,12 +115,12 @@ int create_database_schema() {
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err); int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err); fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
sqlite3_free(err); sqlite3_free(err);
return 1; return 1;
} }
fprintf(stderr, "Database created successfully\n"); fprintf(stderr, "[ICFS] Database created successfully\n");
return 0; return 0;
} }
@@ -131,14 +134,14 @@ int ensure_database_schema() {
int result = sqlite3_table_column_metadata( int result = sqlite3_table_column_metadata(
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL); perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
if (result == SQLITE_ERROR) { if (result == SQLITE_ERROR) {
fprintf(stderr, "Table '%s' does not exist.\n", table_name); fprintf(stderr, "[ICFS] Table '%s' does not exist.\n", table_name);
if (create_database_schema()) { if (create_database_schema()) {
fprintf(stderr, "Table could not be created.\n"); fprintf(stderr, "[ICFS] Table could not be created.\n");
return 1; return 1;
} }
return 0; return 0;
} else if (result != SQLITE_OK) { } else if (result != SQLITE_OK) {
fprintf(stderr, "Database metadata could not be retrieved.\n"); fprintf(stderr, "[ICFS] Database metadata could not be retrieved.\n");
return 1; return 1;
} }
@@ -148,12 +151,12 @@ int ensure_database_schema() {
sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err); sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err); fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
sqlite3_free(err); sqlite3_free(err);
return 1; return 1;
} }
fprintf(stderr, "Schema is correct.\n"); fprintf(stderr, "[ICFS] Schema is correct.\n");
return 0; return 0;
} }
@@ -168,24 +171,25 @@ int init_perm_permissions_table(const char *db_filename) {
umask(0077); umask(0077);
ruid = getuid(); ruid = getuid();
euid = geteuid(); euid = geteuid();
fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid()); fprintf(stderr, "[ICFS] Running with uid: %d, gid: %d\n", euid, getegid());
if (sqlite3_open_v2(db_filename, &perm_database, if (sqlite3_open_v2(db_filename, &perm_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_FULLMUTEX, SQLITE_OPEN_FULLMUTEX,
NULL)) { NULL)) {
perror("Can't open permanent permissions database"); perror("[ICFS] Can't open permanent permissions database");
return -1; return -1;
} }
umask(0); umask(0);
if (ensure_database_schema()) { if (ensure_database_schema()) {
fprintf(stderr, "Database schema is not correct.\n"); fprintf(stderr, "[ICFS] Database schema is not correct.\n");
return -1; return -1;
} }
int status = seteuid(ruid); int status = seteuid(ruid);
if (status < 0) { if (status < 0) {
fprintf(stderr, "Couldn't set euid to ruid during database setup.\n"); fprintf(stderr,
"[ICFS] Couldn't set euid to ruid during database setup.\n");
exit(status); exit(status);
} }
@@ -201,48 +205,42 @@ void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); }
* Checks if the process has a permanent access to the file. * Checks if the process has a permanent access to the file.
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found * found
*/ */
access_t check_perm_access_noparent(const char *filename, access_t check_perm_access_noparent(const char *filename,
struct process_info pi) { struct process_info pi) {
if (pi.name == NULL)
char *query = NULL;
int ret = asprintf(&query,
"SELECT * FROM %s WHERE executable = \'%s\' "
"AND ((\'%s\' LIKE CONCAT(filename, \'%%\') AND filename "
"GLOB \'*/\') OR filename = \'%s\');",
table_name, pi.name, filename, filename);
fprintf(stderr, "query: %s\n", query);
if (ret < 0) {
// If asprintf fails, the contents of query are undefined (see man
// asprintf). That does not explicitly rule out that query will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this.
fprintf(stderr, "Could not create query on access check");
perror("");
return NDEF; return NDEF;
}
char *sqlite_error = NULL; access_t ret = NDEF;
int flag = 0; sqlite3_stmt *stmt = NULL;
ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error); const char *sql =
free((void *)query); "SELECT mode FROM permissions WHERE executable = ?1 "
if (ret != SQLITE_OK) { "AND (( ?2 LIKE (filename || \'%\') AND filename "
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error); "GLOB \'*/\') OR filename = ?2 ) ORDER BY LENGTH( filename ) DESC;";
sqlite3_free(sqlite_error); sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
return NDEF; sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
} sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
if (flag == 1) { int step_ret = sqlite3_step(stmt);
return ALLOW; if (step_ret != SQLITE_ROW && step_ret != SQLITE_DONE) {
fprintf(stderr, "[ICFS] SQLite error: %s\n", sqlite3_errstr(step_ret));
sqlite3_finalize(stmt);
return ret;
} }
if (flag == -1) { if (step_ret == SQLITE_ROW) {
return DENY; int mode_col = sqlite3_column_int(stmt, 0);
if (mode_col) {
ret = ALLOW;
} else {
ret = DENY;
}
} }
return NDEF; sqlite3_finalize(stmt);
return ret;
} }
/** /**
@@ -250,7 +248,7 @@ access_t check_perm_access_noparent(const char *filename,
* file. * file.
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP or DENY_TEMP. * found. Does not return ALLOW_TEMP or DENY_TEMP.
* @note: In case one of the parent processes is killed while this function * @note: In case one of the parent processes is killed while this function
@@ -258,7 +256,7 @@ access_t check_perm_access_noparent(const char *filename,
* false negatives, though. * false negatives, though.
*/ */
access_t check_perm_access(const char *filename, struct process_info pi) { access_t check_perm_access(const char *filename, struct process_info pi) {
if (pi.PID == 0) { if (pi.PID == 0 || pi.name == NULL) {
return NDEF; return NDEF;
} }
@@ -272,7 +270,7 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
} }
current_pi.name = NULL; current_pi.name = NULL;
while (current_pi.name == NULL) { while (current_pi.name == NULL) {
current_pi.PID = get_parent_pid(current_pi.PID); current_pi.PID = get_main_thread_pid(get_parent_pid(current_pi.PID));
if (current_pi.PID != 0) { if (current_pi.PID != 0) {
current_pi.name = get_process_name_by_pid(current_pi.PID); current_pi.name = get_process_name_by_pid(current_pi.PID);
} else { } else {
@@ -310,7 +308,7 @@ int set_perm_access(const char *filename, struct process_info pi,
// asprintf). That does not explicitly rule out that query will be a valid // asprintf). That does not explicitly rule out that query will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to // pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this. // justify preparing for this.
fprintf(stderr, "Could not create query on rule insertion"); fprintf(stderr, "[ICFS] Could not create query on rule insertion\n");
perror(""); perror("");
return 1; return 1;
} }
@@ -319,9 +317,8 @@ int set_perm_access(const char *filename, struct process_info pi,
ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error); ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
free(query); free(query);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error); fprintf(stderr, "[ICFS] SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error); sqlite3_free(sqlite_error);
free(query);
return 1; return 1;
} }

View File

@@ -30,7 +30,7 @@ void destroy_perm_permissions_table();
* Checks if the process has a permanent access to the file. * Checks if the process has a permanent access to the file.
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found * found
*/ */
@@ -43,7 +43,7 @@ access_t check_perm_access(const char *filename, struct process_info pi);
* @param pi: The process information * @param pi: The process information
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and * @param mode: Kind of access rule to be set - SET_DENY to deny access, and
* SET_ALLOW to allow access. * SET_ALLOW to allow access.
* @return: 0 on success, -1 on failure * @return: 0 on success, 1 on failure
*/ */
int set_perm_access(const char *filename, struct process_info pi, int set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode); set_mode_t mode);

View File

@@ -1,16 +1,94 @@
/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#include "proc_operations.h" #include "proc_operations.h"
#include <linux/limits.h>
#include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
/**
* @brief Returns the PID of the main thread (i.e., the process ID) of the
* process that the given thread ID (tid) belongs to.
*
* @param tid The thread ID (TID) of any thread in the process.
* @return pid_t The process ID (main thread's PID), or -1 on error.
*/
pid_t get_main_thread_pid(pid_t tid) {
// Validate input
if (tid <= 0) {
// errno = EINVAL;
return 0;
}
char path[PATH_MAX];
snprintf(path, sizeof(path), "/proc/%d/status", tid);
FILE *fp = fopen(path, "r");
if (!fp) {
return 0; // Could not open the file
}
pid_t tgid = 0;
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (sscanf(line, "Tgid: %d", &tgid) == 1) {
break;
}
}
fclose(fp);
if (tgid != tid) {
fprintf(stderr,
"[ICFS] The tid and and pid wasn't equal. tid:%d, pid:%d.\n", tid,
tgid);
}
return tgid;
}
char *get_process_name_by_pid(const int pid) { char *get_process_name_by_pid(const int pid) {
char path[1024]; char path[1024];
sprintf(path, "/proc/%d/exe", pid); sprintf(path, "/proc/%d/exe", pid);
char *name = realpath(path, NULL); size_t size = 128;
char *name = malloc(size);
if (name == NULL) { if (name == NULL) {
fprintf(stderr, "Could not get process name by pid %d", pid); fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
perror(""); perror("");
return NULL;
} }
while (1) {
ssize_t len = readlink(path, name, size);
if (len == -1) {
fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
perror("");
free(name);
return NULL;
}
if ((size_t)len >= size) {
size *= 2;
char *new_name = realloc(name, size);
if (!new_name) {
free(name);
return NULL;
}
name = new_name;
} else {
// readlink does not set the null character
name[len] = 0;
break;
}
}
return name; return name;
} }
@@ -28,7 +106,7 @@ pid_t get_parent_pid(pid_t pid) {
FILE *file = fopen(path, "r"); FILE *file = fopen(path, "r");
if (file == NULL) { if (file == NULL) {
perror("Failed to open /proc/<pid>/status"); perror("[ICFS] Failed to open /proc/<pid>/status");
return 0; return 0;
} }

View File

@@ -1,3 +1,11 @@
/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#ifndef PROC_OPERATIONS #ifndef PROC_OPERATIONS
#define PROC_OPERATIONS #define PROC_OPERATIONS
@@ -14,4 +22,13 @@ char *get_process_name_by_pid(const int pid);
*/ */
pid_t get_parent_pid(pid_t pid); pid_t get_parent_pid(pid_t pid);
/**
* @brief Returns the PID of the main thread (i.e., the process ID) of the
* process that the given thread ID (tid) belongs to.
*
* @param tid The thread ID (TID) of any thread in the process.
* @return pid_t The process ID (main thread's PID), or -1 on error.
*/
pid_t get_main_thread_pid(pid_t tid);
#endif // !PROC_OPERATIONS #endif // !PROC_OPERATIONS

View File

@@ -9,10 +9,25 @@
#ifndef PROCESS_INFO_H #ifndef PROCESS_INFO_H
#define PROCESS_INFO_H #define PROCESS_INFO_H
#include "proc_operations.h"
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
struct process_info { struct process_info {
pid_t PID; pid_t PID;
char *name; char *name;
}; };
static inline struct process_info get_process_info(pid_t pid) {
struct process_info pi;
pi.PID = get_main_thread_pid(pid);
pi.name = get_process_name_by_pid(pi.PID);
if (pi.name == NULL) {
pi.PID = 0;
}
return pi;
}
#endif // PROCESS_INFO_H #endif // PROCESS_INFO_H

View File

@@ -1,3 +1,11 @@
/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#ifndef REAL_FILENAME_H #ifndef REAL_FILENAME_H
#define REAL_FILENAME_H #define REAL_FILENAME_H

View File

@@ -1,3 +1,10 @@
/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#ifndef SET_MODE_T_H #ifndef SET_MODE_T_H
#define SET_MODE_T_H #define SET_MODE_T_H

View File

@@ -32,7 +32,7 @@ const char *source_filename_translate(const char *filename) {
int source_init(const char *root_path) { int source_init(const char *root_path) {
handle.mountpoint = malloc(strlen(root_path) + 1); handle.mountpoint = malloc(strlen(root_path) + 1);
if (handle.mountpoint == NULL) { if (handle.mountpoint == NULL) {
perror("Malloc failed"); perror("[ICFS] Malloc failed");
return -1; return -1;
} }
@@ -41,7 +41,8 @@ int source_init(const char *root_path) {
int root_fd = open(root_path, O_PATH); int root_fd = open(root_path, O_PATH);
if (root_fd == -1) { if (root_fd == -1) {
fprintf(stderr, "Could not initialize source file system at %s", root_path); fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
root_path);
perror(""); perror("");
return -1; return -1;
} }
@@ -65,7 +66,7 @@ const char *real_filename(const char *filename) {
// Allocate memory (+1 for null terminator) // Allocate memory (+1 for null terminator)
char *result = malloc(total_len + 1); char *result = malloc(total_len + 1);
if (result == NULL) { if (result == NULL) {
fprintf(stderr, "Memory allocation failed"); fprintf(stderr, "[ICFS] Memory allocation failed");
perror(""); perror("");
return NULL; return NULL;
} }
@@ -111,7 +112,7 @@ DIR *source_opendir(const char *filename) {
const char *relative_filename = source_filename_translate(filename); const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0); int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) { if (fd < 0) {
perror("Openat failed"); perror("[ICFS] Openat failed");
return NULL; return NULL;
} }
DIR *dir_pointer = fdopendir(fd); DIR *dir_pointer = fdopendir(fd);
@@ -148,7 +149,7 @@ int source_truncate(const char *filename, off_t length) {
const char *relative_filename = source_filename_translate(filename); const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0); int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) { if (fd < 0) {
perror("Openat failed"); perror("[ICFS] Openat failed");
return -1; return -1;
} }
return ftruncate(fd, length); return ftruncate(fd, length);

View File

@@ -16,6 +16,8 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
#include <time.h>
#include <unistd.h>
struct temp_process_permissions { struct temp_process_permissions {
// yes, this is a correct type for start time in jiffies (see // yes, this is a correct type for start time in jiffies (see
@@ -26,7 +28,9 @@ struct temp_process_permissions {
}; };
map(pid_t, struct temp_process_permissions) temp_permissions_table; map(pid_t, struct temp_process_permissions) temp_permissions_table;
pthread_mutex_t temp_permissions_table_lock; pthread_rwlock_t temp_permissions_table_lock = PTHREAD_RWLOCK_INITIALIZER;
pthread_t gc_thread;
int is_gc_active = 0;
/** /**
* Function to get the process creation time (in jiffies) from the proc * Function to get the process creation time (in jiffies) from the proc
@@ -48,20 +52,21 @@ unsigned long long get_process_creation_time(pid_t pid) {
// Open the status file // Open the status file
fp = fopen(path, "r"); fp = fopen(path, "r");
if (fp == NULL) { if (fp == NULL) {
perror("fopen"); perror("[ICFS] fopen");
return 0; return 0;
} }
// Read the creation time (the 22nd field in the stat file) // Read the creation time (the 22nd field in the stat file)
for (int i = 1; i < 22; i++) { for (int i = 1; i < 22; i++) {
if (fscanf(fp, "%*s") == EOF) { if (fscanf(fp, "%*s") == EOF) {
fprintf(stderr, "Error reading process stat file on the number %d\n", i); fprintf(stderr,
"[ICFS] Error reading process stat file on the number %d\n", i);
fclose(fp); fclose(fp);
return 0; return 0;
} }
} }
if (fscanf(fp, "%llu", &creation_time) != 1) { if (fscanf(fp, "%llu", &creation_time) != 1) {
fprintf(stderr, "Error reading creation time\n"); fprintf(stderr, "[ICFS] Error reading creation time\n");
fclose(fp); fclose(fp);
return 0; return 0;
} }
@@ -72,16 +77,70 @@ unsigned long long get_process_creation_time(pid_t pid) {
return creation_time; return creation_time;
} }
int is_valid(pid_t pid, struct temp_process_permissions *entry) {
unsigned long long creation_time = get_process_creation_time(pid);
if (creation_time == 0) {
return 0;
}
if (creation_time != entry->creation_time) {
return 0;
}
return 1;
}
void *garbage_collector(void *arg) {
(void)arg;
while (is_gc_active) {
sleep(1);
pthread_rwlock_wrlock(&temp_permissions_table_lock);
vec(pid_t) blacklist;
init(&blacklist);
for_each(&temp_permissions_table, pid, entry) {
if (!is_valid(*pid, entry)) {
push(&blacklist, *pid);
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
cleanup(&entry->allowed_files);
for_each(&entry->denied_files, denied_file) { free(*denied_file); }
cleanup(&entry->denied_files);
}
}
for_each(&blacklist, pid) { erase(&temp_permissions_table, *pid); }
cleanup(&blacklist);
pthread_rwlock_unlock(&temp_permissions_table_lock);
}
return NULL;
}
/** /**
* Initializes the temporary permissions table. * Initializes the temporary permissions table.
* *
* @return: 0 on success, -1 on failure (e.g. ENOMEM) * @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/ */
int init_temp_permissions_table(void) { int init_temp_permissions_table(void) {
pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT);
init(&temp_permissions_table); init(&temp_permissions_table);
return 0; return 0;
} }
/**
* Starts the temporary permissions table garbage_collector.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
int init_garbage_collector(void) {
is_gc_active = 1;
if (pthread_create(&gc_thread, NULL, garbage_collector, NULL) != 0) {
return -1;
}
return 0;
}
/** /**
* Destroys the temporary permissions table. * Destroys the temporary permissions table.
@@ -91,6 +150,11 @@ int init_temp_permissions_table(void) {
* screwed. * screwed.
*/ */
void destroy_temp_permissions_table(void) { void destroy_temp_permissions_table(void) {
if (is_gc_active) {
is_gc_active = 0;
pthread_join(gc_thread, NULL);
}
// free the memory allocated for the table // free the memory allocated for the table
for_each(&temp_permissions_table, entry) { for_each(&temp_permissions_table, entry) {
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); } for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
@@ -102,27 +166,26 @@ void destroy_temp_permissions_table(void) {
} }
cleanup(&temp_permissions_table); cleanup(&temp_permissions_table);
pthread_mutex_destroy(&temp_permissions_table_lock);
} }
/** /**
* Checks if the process has a temporary access to the file. * Checks if the process has a temporary access to the file.
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pid: PID of the process * @param pid: PID of the process
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found is avaliable * found is avaliable
*/ */
access_t check_temp_access_noparent(const char *filename, pid_t pid) { access_t check_temp_access_noparent(const char *filename, pid_t pid) {
// TODO: more efficient locking // TODO: more efficient locking
pthread_mutex_lock(&temp_permissions_table_lock); pthread_rwlock_rdlock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry = struct temp_process_permissions *permission_entry =
get(&temp_permissions_table, pid); get(&temp_permissions_table, pid);
if (permission_entry != NULL) { if (permission_entry != NULL) {
unsigned long long process_creation_time = get_process_creation_time(pid); unsigned long long process_creation_time = get_process_creation_time(pid);
if (process_creation_time == 0) { if (process_creation_time == 0) {
perror("Could not retrieve process creation time"); perror("[ICFS] Could not retrieve process creation time");
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_rwlock_unlock(&temp_permissions_table_lock);
return NDEF; return NDEF;
} }
@@ -130,29 +193,35 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
// the process is the same as the one that was granted temporary access // the process is the same as the one that was granted temporary access
// to the file // to the file
size_t filename_len = strlen(filename); size_t filename_len = strlen(filename);
access_t ret = NDEF;
size_t maxlen = 0;
for_each(&permission_entry->denied_files, denied_file) { for_each(&permission_entry->denied_files, denied_file) {
size_t denied_file_len = strlen(*denied_file); size_t denied_file_len = strlen(*denied_file);
if (strncmp(*denied_file, filename, denied_file_len) == 0 && if ((strncmp(*denied_file, filename, denied_file_len) == 0 &&
((denied_file_len < filename_len && ((denied_file_len < filename_len &&
(*denied_file)[denied_file_len - 1] == '/') || (*denied_file)[denied_file_len - 1] == '/') ||
(denied_file_len == filename_len))) { (denied_file_len == filename_len))) &&
pthread_mutex_unlock(&temp_permissions_table_lock); denied_file_len > maxlen) {
return DENY; maxlen = denied_file_len;
ret = DENY;
} }
} }
for_each(&permission_entry->allowed_files, allowed_file) { for_each(&permission_entry->allowed_files, allowed_file) {
size_t allowed_file_len = strlen(*allowed_file); size_t allowed_file_len = strlen(*allowed_file);
if (strncmp(*allowed_file, filename, allowed_file_len) == 0 && if ((strncmp(*allowed_file, filename, allowed_file_len) == 0 &&
((allowed_file_len < filename_len && ((allowed_file_len < filename_len &&
(*allowed_file)[allowed_file_len - 1] == '/') || (*allowed_file)[allowed_file_len - 1] == '/') ||
(allowed_file_len == filename_len))) { (allowed_file_len == filename_len))) &&
pthread_mutex_unlock(&temp_permissions_table_lock); allowed_file > maxlen) {
return ALLOW; maxlen = allowed_file_len;
ret = ALLOW;
} }
} }
pthread_rwlock_unlock(&temp_permissions_table_lock);
return ret;
} }
} }
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_rwlock_unlock(&temp_permissions_table_lock);
return NDEF; return NDEF;
} }
@@ -161,7 +230,7 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
* file. * file.
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP. * found. Does not return ALLOW_TEMP.
* @note: In case one of the parent processes is killed while this function * @note: In case one of the parent processes is killed while this function
@@ -175,7 +244,7 @@ access_t check_temp_access(const char *filename, struct process_info pi) {
if (access != NDEF) { if (access != NDEF) {
return access; return access;
} }
current_pid = get_parent_pid(current_pid); current_pid = get_main_thread_pid(get_parent_pid(current_pid));
} }
return NDEF; return NDEF;
@@ -192,7 +261,9 @@ access_t check_temp_access(const char *filename, struct process_info pi) {
*/ */
int set_temp_access(const char *filename, struct process_info pi, int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode) { set_mode_t mode) {
pthread_mutex_lock(&temp_permissions_table_lock); if (pi.PID == 0)
return NDEF;
pthread_rwlock_wrlock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry = struct temp_process_permissions *permission_entry =
get(&temp_permissions_table, pi.PID); get(&temp_permissions_table, pi.PID);
@@ -201,8 +272,8 @@ int set_temp_access(const char *filename, struct process_info pi,
unsigned long long process_creation_time = unsigned long long process_creation_time =
get_process_creation_time(pi.PID); get_process_creation_time(pi.PID);
if (process_creation_time == 0) { if (process_creation_time == 0) {
perror("Could not retrieve process creation time"); perror("[ICFS] Could not retrieve process creation time");
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_rwlock_unlock(&temp_permissions_table_lock);
return -1; return -1;
} }
@@ -216,7 +287,7 @@ int set_temp_access(const char *filename, struct process_info pi,
push(&permission_entry->denied_files, strdup(filename)); push(&permission_entry->denied_files, strdup(filename));
} }
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_rwlock_unlock(&temp_permissions_table_lock);
return 0; return 0;
} }
// we have an entry for the process, but the process is different // we have an entry for the process, but the process is different
@@ -241,6 +312,6 @@ int set_temp_access(const char *filename, struct process_info pi,
insert(&temp_permissions_table, pi.PID, new_permission_entry); insert(&temp_permissions_table, pi.PID, new_permission_entry);
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_rwlock_unlock(&temp_permissions_table_lock);
return 0; return 0;
} }

View File

@@ -1,3 +1,10 @@
/*
ICFS: Interactively Controlled File System
Copyright (C) 2024-2025 Fedir Kovalov
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#ifndef TEMP_PERMISSIONS_TABLE_H #ifndef TEMP_PERMISSIONS_TABLE_H
#define TEMP_PERMISSIONS_TABLE_H #define TEMP_PERMISSIONS_TABLE_H
@@ -13,6 +20,13 @@
*/ */
int init_temp_permissions_table(void); int init_temp_permissions_table(void);
/**
* Starts the temporary permissions table garbage_collector.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
int init_garbage_collector(void);
/** /**
* Destroys the temporary permissions table. * Destroys the temporary permissions table.
* *
@@ -27,7 +41,7 @@ void destroy_temp_permissions_table(void);
* file. * file.
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP. * found. Does not return ALLOW_TEMP.
* @note: In case one of the parent processes is killed while this function * @note: In case one of the parent processes is killed while this function

View File

@@ -30,28 +30,36 @@
#define DIALOGUE_NO 1 #define DIALOGUE_NO 1
#define DIALOGUE_PERM 2 #define DIALOGUE_PERM 2
pthread_mutex_t access_check_mutex = PTHREAD_MUTEX_INITIALIZER;
struct dialogue_response { struct dialogue_response {
access_t decision; access_t decision;
char *filename; char *filename;
}; };
FILE *access_log;
int init_ui_socket(const char *perm_permissions_db_filename) { int init_ui_socket(const char *perm_permissions_db_filename) {
FILE *fp = NULL; FILE *fp = NULL;
access_log = fopen("/etc/icfs-log", "a+");
if (init_temp_permissions_table()) { if (init_temp_permissions_table()) {
fprintf(stderr, "Could not initialize temporary permissions table.\n"); fprintf(stderr,
"[ICFS] Could not initialize temporary permissions table.\n");
return 1; return 1;
} }
if (init_perm_permissions_table(perm_permissions_db_filename)) { if (init_perm_permissions_table(perm_permissions_db_filename)) {
fprintf(stderr, "Could not initialize permanent permissions table.\n"); fprintf(stderr,
"[ICFS] Could not initialize permanent permissions table.\n");
return 1; return 1;
} }
// Test if dialogue is installed (get version) // Test if dialogue is installed (get version)
fp = popen("icfs_dialogue --version", "r"); fp = popen("icfs_dialogue --version", "r");
if (fp == NULL) { if (fp == NULL) {
perror("Pipe returned an error"); perror("[ICFS] Pipe returned an error");
return 1; return 1;
} }
@@ -60,6 +68,7 @@ int init_ui_socket(const char *perm_permissions_db_filename) {
} }
void destroy_ui_socket(void) { void destroy_ui_socket(void) {
fclose(access_log);
destroy_temp_permissions_table(); destroy_temp_permissions_table();
destroy_perm_permissions_table(); destroy_perm_permissions_table();
} }
@@ -89,7 +98,7 @@ struct dialogue_response ask_access(const char *filename,
// asprintf). That does not explicitly rule out that command will be a valid // asprintf). That does not explicitly rule out that command will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to // pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this. // justify preparing for this.
fprintf(stderr, "Could not create query on rule insertion"); fprintf(stderr, "[ICFS] Could not create query on rule insertion");
perror(""); perror("");
response.decision = DENY; response.decision = DENY;
response.filename = malloc(2); response.filename = malloc(2);
@@ -103,7 +112,7 @@ struct dialogue_response ask_access(const char *filename,
free(command); free(command);
if (fp == NULL) { if (fp == NULL) {
perror("Pipe returned a error"); perror("[ICFS] Pipe returned a error");
response.decision = DENY; response.decision = DENY;
response.filename = malloc(2); response.filename = malloc(2);
response.filename[0] = '/'; response.filename[0] = '/';
@@ -122,8 +131,9 @@ struct dialogue_response ask_access(const char *filename,
} }
int dialogue_exit_code = WEXITSTATUS(pclose(fp)); int dialogue_exit_code = WEXITSTATUS(pclose(fp));
fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code); fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
if (size(&dialogue_output) == 0) { if (size(&dialogue_output) == 0) {
push(&dialogue_output, '/'); push(&dialogue_output, '/');
@@ -137,6 +147,8 @@ struct dialogue_response ask_access(const char *filename,
// assert(0 == strcmp(response.filename, first(&dialogue_output))); // assert(0 == strcmp(response.filename, first(&dialogue_output)));
cleanup(&dialogue_output); cleanup(&dialogue_output);
time_t now = time(0);
fprintf(access_log, "[ICFS] wrote to access log: %ld\n", now);
if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) { if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) {
response.decision = ALLOW; response.decision = ALLOW;
@@ -158,47 +170,56 @@ struct dialogue_response ask_access(const char *filename,
* 3. user descision * 3. user descision
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @param opts: options (GRANT_TEMP, GRANT_PERM) * @param opts: options (GRANT_TEMP, GRANT_PERM)
* @return: 0 if access is denied, 1 if access is allowed * @return: 0 if access is denied, 1 if access is allowed
*/ */
int interactive_access(const char *filename, struct process_info proc_info, int interactive_access(const char *filename, struct process_info proc_info,
int opts) { int opts) {
char *real_path = real_filename(filename); char *real_path = real_filename(filename);
pthread_mutex_lock(&access_check_mutex);
access_t access = check_temp_access(real_path, proc_info); access_t access = check_temp_access(real_path, proc_info);
if (access == ALLOW) { if (access == ALLOW) {
fprintf(stderr, fprintf(
"Permission allowed to %s based on a rule present in the temp " stderr,
"permission table.\n", "[ICFS] Permission allowed to %s based on a rule present in the temp "
proc_info.name); "permission table.\n",
proc_info.name);
free(real_path); free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1; return 1;
} }
if (access == DENY) { if (access == DENY) {
fprintf(stderr, fprintf(
"Permission denied to %s based on a rule present in the temp " stderr,
"permission table.\n", "[ICFS] Permission denied to %s based on a rule present in the temp "
proc_info.name); "permission table.\n",
proc_info.name);
free(real_path); free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 0; return 0;
} }
access = check_perm_access(real_path, proc_info); access = check_perm_access(real_path, proc_info);
if (access == ALLOW) { if (access == ALLOW) {
fprintf(stderr, fprintf(
"Permission allowed to %s based on a rule present in the perm " stderr,
"permission table.\n", "[ICFS] Permission allowed to %s based on a rule present in the perm "
proc_info.name); "permission table.\n",
proc_info.name);
free(real_path); free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1; return 1;
} }
if (access == DENY) { if (access == DENY) {
fprintf(stderr, fprintf(
"Permission denied to %s based on a rule present in the perm " stderr,
"permission table.\n", "[ICFS] Permission denied to %s based on a rule present in the perm "
proc_info.name); "permission table.\n",
proc_info.name);
free(real_path); free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 0; return 0;
} }
@@ -206,15 +227,19 @@ int interactive_access(const char *filename, struct process_info proc_info,
// permissions are granted // permissions are granted
if (opts & GRANT_PERM) { if (opts & GRANT_PERM) {
fprintf(stderr, "Permission granted permanently to %s.\n", proc_info.name); fprintf(stderr, "[ICFS] Permission granted permanently to %s.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW); set_perm_access(real_path, proc_info, SET_ALLOW);
free(real_path); free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1; return 1;
} }
if (opts & GRANT_TEMP) { if (opts & GRANT_TEMP) {
fprintf(stderr, "Permission granted temporarily to %s.\n", proc_info.name); fprintf(stderr, "[ICFS] Permission granted temporarily to %s.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW); set_temp_access(real_path, proc_info, SET_ALLOW);
free(real_path); free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1; return 1;
} }
@@ -227,7 +252,7 @@ int interactive_access(const char *filename, struct process_info proc_info,
while (source_access(response.filename, F_OK)) { while (source_access(response.filename, F_OK)) {
// if it is invalid, just ask again. // if it is invalid, just ask again.
fprintf(stderr, "Filename returned by zenty wasn't correct: %s\n", fprintf(stderr, "[ICFS] Filename returned by zenty wasn't correct: %s\n",
response.filename); response.filename);
free(response.filename); free(response.filename);
response = ask_access(filename, proc_info); response = ask_access(filename, proc_info);
@@ -238,43 +263,40 @@ int interactive_access(const char *filename, struct process_info proc_info,
real_path = real_filename(response.filename); real_path = real_filename(response.filename);
free(response.filename); free(response.filename);
int ret = 0;
if (response.decision == ALLOW) { if (response.decision == ALLOW) {
fprintf(stderr, fprintf(stderr,
"Permission granted permanently to %s based on zenty response.\n", "[ICFS] Permission granted permanently to %s based on zenty "
"response.\n",
proc_info.name); proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW); set_perm_access(real_path, proc_info, SET_ALLOW);
free(real_path); ret = 1;
return 1; } else if (response.decision == ALLOW_TEMP) {
}
if (response.decision == ALLOW_TEMP) {
fprintf(stderr, fprintf(stderr,
"Permission granted temporarily to %s based on zenty response.\n", "[ICFS] Permission granted temporarily to %s based on zenty "
"response.\n",
proc_info.name); proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW); set_temp_access(real_path, proc_info, SET_ALLOW);
free(real_path); ret = 1;
return 1; } else if (response.decision == DENY_TEMP) {
} fprintf(
stderr,
if (response.decision == DENY_TEMP) { "[ICFS] Permission denied temporarily to %s based on zenty response.\n",
fprintf(stderr, proc_info.name);
"Permission denied temporarily to %s based on zenty response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_DENY); set_temp_access(real_path, proc_info, SET_DENY);
free(real_path); ret = 0;
return 0; } else if (response.decision == DENY) {
} fprintf(
stderr,
if (response.decision == DENY) { "[ICFS] Permission denied permanently to %s based on zenty response.\n",
fprintf(stderr, proc_info.name);
"Permission denied permanently to %s based on zenty response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_DENY); set_perm_access(real_path, proc_info, SET_DENY);
free(real_path); ret = 0;
return 0;
} }
pthread_mutex_unlock(&access_check_mutex);
free(real_path); free(real_path);
// deny on unknown options. // deny on unknown options.
return 0; return ret;
} }

View File

@@ -35,7 +35,7 @@ void destroy_ui_socket(void);
* 3. user descision * 3. user descision
* *
* @param filename: The file that the process is trying to access * @param filename: The file that the process is trying to access
* @pram pi: The process information * @param pi: The process information
* @param opts: options (GRANT_TEMP, GRANT_PERM) * @param opts: options (GRANT_TEMP, GRANT_PERM)
* @return: 0 if access is denied, 1 if access is allowed * @return: 0 if access is denied, 1 if access is allowed
*/ */

6
test/stress.bash Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
count=$1
for i in $(seq $count); do
$2 ./protected/haystack/
done

View File

@@ -10,7 +10,7 @@ chmod 777 ./protected/perm777 ./protected/perm000
echo "Free code, free world." >./protected/motto echo "Free code, free world." >./protected/motto
mkdir protected/haystack mkdir protected/haystack
for i in {1..10}; do for i in {1..100}; do
touch "./protected/haystack/hay$i" touch "./protected/haystack/hay$i"
done done
touch ./protected/haystack/needle touch ./protected/haystack/needle
@@ -22,7 +22,7 @@ make -C ./opener || (
echo "Could not make the opener program." echo "Could not make the opener program."
exit 1 exit 1
) )
for i in {1..10}; do for i in {1..12}; do
cp ./opener/opener "./openers/opener$i" cp ./opener/opener "./openers/opener$i"
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i" ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
done done
@@ -43,11 +43,17 @@ if [[ $1 == "--setuid" ]]; then
echo "Valgrind will not be used due to setuid compatibility issues." echo "Valgrind will not be used due to setuid compatibility issues."
../build/icfs -o default_permissions ./protected ./.pt.db & ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 1 sleep 1
elif [[ $1 == "--performance" ]]; then
echo "Database protection will not be tested due to the lack of setuid capabilites."
echo "To test it, run this script with '--setuid'."
echo "valgrind won't be used to make performance measurements more accurate."
../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5
else else
echo "Database protection will not be tested due to the lack of setuid capabilites." echo "Database protection will not be tested due to the lack of setuid capabilites."
echo "To test it, run this script with '--setuid'." echo "To test it, run this script with '--setuid'."
#valgrind --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback" & #valgrind --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback\|SQLite" &
valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db & valgrind --leak-check=full --show-leak-kinds=all -s ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5 sleep 5
fi fi
@@ -205,9 +211,36 @@ else
echo "[ICFS-TEST]: permanent permissions database access was not tested due to the lack of seuid bit setting capabilites. To test this, run the script with '--setuid' flag" echo "[ICFS-TEST]: permanent permissions database access was not tested due to the lack of seuid bit setting capabilites. To test this, run the script with '--setuid' flag"
fi fi
RUNS_NUM=500
if [[ $1 == '--performance' ]]; then
#warmup
icfs_dialogue --set-fake-response yes
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener10"
icfs_dialogue --set-fake-response yes
echo "[ICFS-TEST]: temp permissions"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener7"
icfs_dialogue --set-fake-response yes_perm
echo "[ICFS-TEST]: perm permissions"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener8"
fi
# unmount # unmount
sleep 0.5 sleep 0.5
#lsof +f -- $(realpath ./protected) #lsof +f -- $(realpath ./protected)
umount "$(realpath ./protected)" umount "$(realpath ./protected)"
sleep 0.5 sleep 3
if [[ $1 == '--performance' ]]; then
#warmup
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
echo "[ICFS-TEST]: bare filesystem"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
fi