96 Commits

Author SHA1 Message Date
5dff492663 Changed semantics of --no-perm-on-create flag 2025-05-22 09:02:11 +02:00
3566131705 Added memory errors warning 2025-05-22 09:01:50 +02:00
6423e3b2ef Fixed yet another SQL injection bug. 2025-05-21 19:04:52 +02:00
5c92ece0db Removed file logging 2025-05-21 18:56:35 +02:00
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
78e108d0d4 Added more tests 2025-05-06 17:56:55 +02:00
b4149ac425 Updated gitignore 2025-05-06 12:19:39 +02:00
6065a0c20a Added permissions globbing tests 2025-05-06 12:18:45 +02:00
15fa0fe193 Added filename return to the dialogue mockup 2025-05-06 12:18:21 +02:00
801a7cdb39 Added temp permission globbing 2025-05-06 12:17:50 +02:00
22b091f017 Fixed empty filename bug. 2025-05-06 12:17:26 +02:00
fd2144a1f9 Added a filename check 2025-05-05 18:59:57 +02:00
420f34a7f3 Added folder globbing for permanent permissions 2025-05-05 18:53:05 +02:00
4539df9842 Fixed wrong fallback filename bug 2025-05-04 17:39:39 +02:00
e32ce5add5 Renamed all zenity mentions to dialogue 2025-05-04 17:25:47 +02:00
4f98a4834e Updated gitignore 2025-05-04 17:10:58 +02:00
8a530b493c Added new tests for the new dialogue 2025-05-04 17:10:19 +02:00
c4ae40c7bd Finished the new dialogue functionality 2025-05-04 17:09:28 +02:00
ecedbbb4ce Added DENY_TEMP access type 2025-05-04 17:05:44 +02:00
10d2988761 Added a version check for the icfs-dialogue 2025-05-04 17:05:07 +02:00
f4576cf7ea Updated gitignore 2025-05-03 12:09:55 +02:00
a2eeb81fed Updated gitignore 2025-05-03 12:09:10 +02:00
3a89449c32 Added gitignore to the dialogue 2025-05-03 12:05:11 +02:00
ac1d7c1535 Renamed zenity-clone to icfs-dialogue and improved makefile 2025-05-03 12:03:54 +02:00
112d514f59 Adapted mock zenity to the new dialogue 2025-05-03 10:32:10 +02:00
d367d6ffe7 Adapted the ui-socket to the new dialogue 2025-05-03 10:31:42 +02:00
82f66a1df3 Fixed inverted responses 2025-05-03 10:30:13 +02:00
8cb7721e39 Updated ui-socket to use the new dialogue 2025-05-01 20:55:05 +02:00
fe84daecfe Removed const where it was simply wrong. 2025-05-01 20:54:39 +02:00
683da15953 Updated the cc.h version 2025-05-01 20:52:32 +02:00
5452c3d1d7 Added filename translation to the ui-socker 2025-05-01 16:17:50 +02:00
a1445c5423 Updated Makefile 2025-05-01 16:17:27 +02:00
ed441b3c5f Absolved fuse operations of responsibility for filename translation 2025-05-01 16:17:11 +02:00
48342b0d5f Updated sourcefs header 2025-05-01 16:16:24 +02:00
31b70b6069 Added mountpoint functions to sourcefs 2025-05-01 16:16:09 +02:00
07cb76f425 Updated the dialogue 2025-05-01 16:15:00 +02:00
747077f365 Update Makefile for the zenity clone 2025-05-01 16:13:31 +02:00
ccb449ae57 Added a new dialogue 2025-04-28 10:11:50 +02:00
c4ef955ff1 Added a cleaner way of allocating the zenity command. 2025-04-15 19:02:36 +02:00
3157940c0b add a missing header 2025-04-15 18:58:18 +02:00
92378c1cde add exerimental deny flag in the tables
For now, perm table does not set the thrid parameter to anything useful
2025-04-15 18:57:57 +02:00
BritishTeapot
13fd0db8a8 Added perf artifacts to gitignore 2025-04-14 16:47:40 +02:00
BritishTeapot
55fb5c54c6 Improved code readability 2025-04-14 16:46:06 +02:00
BritishTeapot
402a5d109f Fixed incorrect executable path problem.
Previously, process name was grabbed from `/proc/pid/cmdline`. This was
revealed to be faulty, since the path to the executable might be
relative, and thus would change the result depending on how the program
was called. Also, it made executable renaming a viable bypass of the
entire access control.

I still don't fully undestand how I managed to not think of this before
:)
2025-04-12 18:44:20 +02:00
BritishTeapot
beec6f4a4c Changed tests to use the database file argument 2025-04-07 19:38:56 +02:00
BritishTeapot
16b8d77fb9 Improved code readability and added database file argument. 2025-04-07 19:38:33 +02:00
BritishTeapot
aea6e94ad7 Fixed incorrect database creation flags 2025-04-02 18:56:31 +02:00
BritishTeapot
52fcb4d4e3 Fixed an arbitrary return value in temp permissions init 2025-04-02 18:49:14 +02:00
badbf2ff98 Merge pull request 'setuid' (#7) from setuid into main
Reviewed-on: #7
2025-04-01 19:57:01 +02:00
BritishTeapot
07e4ce3eb4 Added missing license headers 2025-04-01 19:56:10 +02:00
BritishTeapot
cf2b7a280a Added a test for the database protection feature. 2025-04-01 19:52:37 +02:00
BritishTeapot
4c8092378b Added database protection with setuid.
Added the initial support for the database protection with the setuid
mechanism. In the beginning the program creates(or opens) the database
as a special user, and then switches to the real uid and functions
normally.
2025-04-01 19:34:15 +02:00
291ad62897 Merge pull request 'creation_permissions' (#6) from creation_permissions into main
Reviewed-on: #6
2025-03-31 14:37:25 +02:00
BritishTeapot
40ca81d744 Updated header function description for interactive_access 2025-03-31 14:36:26 +02:00
BritishTeapot
d4e86c8620 Reorgised the code 2025-03-31 14:33:43 +02:00
BritishTeapot
845c264989 Removed unnecessary include 2025-03-31 13:32:10 +02:00
BritishTeapot
57091bf0ce Made create to grant permissions automatically.
Creating files grants permanent permissions to them now. This makes
sense because if a program creates a new file, then it clearly can't
steal any data. This is particularly useful for programs which open an
obscene amount of auxilary files (e.g. neovim with a huge amount of
plugins).
2025-03-30 19:48:11 +02:00
BritishTeapot
62f3e5bde9 Added the test permanent permissions table to gitignore 2025-03-30 19:09:02 +02:00
BritishTeapot
608943d685 Added new permanent permissions tests. 2025-03-30 19:07:32 +02:00
BritishTeapot
7e111b16b7 Added permanent permissions
Finally implemented the permanent permission tables using sqlite3. For
now, performance wasn't a consideration. There are a lot of
optimizations that could be made, like having prepared queries. The code
remains fairly untested.
2025-03-30 19:06:57 +02:00
BritishTeapot
4ce97555e4 Fixed a testing bug
The script was correctly opening the `truth` file by piping `echo` to
it, but then it tried to deny another operation on it. But since pipes
are opened by the script process, the permission was given to the
script. And since the permissions are preserved for the entire runtime
of a process, and child processes inherit permissions of their parents,
any command executed later would also have the necessary permissions to
open `truth` (which was the case for the second operation). Now the
second operation is performed on a different file.
2025-03-24 17:17:33 +01:00
BritishTeapot
da37376fde Added permission checks for chmod, link, rename and chown
Those clearly need to ask for permissions.
2025-03-24 17:11:01 +01:00
BritishTeapot
6342de0dd3 Added tests to Makefile 2025-03-24 16:28:56 +01:00
BritishTeapot
2e21ae7b18 Deleted a useless file. 2025-03-18 16:50:53 +01:00
2d76dc6596 Merge pull request 'Temp_permission_table' (#5) from Temp_permission_table into main
Reviewed-on: #5
2025-03-18 15:47:08 +01:00
BritishTeapot
b705228685 Made temporary process table work!
Temprorary permissions table now works! It needs a better way for
cleaning though.
2025-03-18 15:45:32 +01:00
BritishTeapot
fe4b8801c0 Moved permission table logic to a separate file 2025-03-18 14:02:42 +01:00
BritishTeapot
e1846440bc Merge branch 'main' into Temp_permission_table 2025-03-18 14:00:57 +01:00
BritishTeapot
47a8845013 Wrote more tests 2025-03-18 13:58:02 +01:00
BritishTeapot
b6ce683364 WIP: Added the initial process table implementation.
Wrote a basic process table implementation with CC's vectors and hash
maps. So far, it looks like it does not really work.
2025-03-18 10:07:45 +01:00
BritishTeapot
70e81d64c1 Added the CC (Convenient Containers) library.
Added the CC library for vectors and hash maps.
2025-03-18 10:05:12 +01:00
BritishTeapot
67a148c7aa Fixed inverted access control permissions bug.
Fixed an (admitedly quite silly) bug that caused the access control
descisions to be inverted.
2025-03-18 10:03:32 +01:00
BritishTeapot
c59123330f Updated .gitignore 2025-03-18 09:55:20 +01:00
730d6bc27d Merge pull request 'Testing' (#4) from Testing into main
Reviewed-on: #4
2025-03-18 09:53:25 +01:00
29 changed files with 11801 additions and 230 deletions

10
.gitignore vendored
View File

@@ -2,3 +2,13 @@ build/*
.clang-tidy
.cache
test/protected/*
test/perf*
test/callgraph*
test/openers
test/opener/opener
test/opener/opener.o
test/.*
*compile_commands.json
src/gui/ui/*
src/gui/*.o
src/gui/icfs_dialogue

View File

@@ -2,9 +2,17 @@ SHELL=/bin/bash
# configurable options
SOURCES_DIR := ./src
TESTS_DIR := ./tests
BUILD_DIR := ./build
ifndef ($(SOURCES_DIR))
SOURCES_DIR := ./src
endif
ifndef ($(TESTS_DIR))
TESTS_DIR := ./tests
endif
ifndef ($(BUILD_DIR))
BUILD_DIR := ./build
endif
CC := gcc
CXX := g++
@@ -12,7 +20,7 @@ CXX := g++
# dependencies
PACKAGE_NAMES := fuse3
PACKAGE_NAMES := fuse3 sqlite3
ifeq ($(TEST), 1)
# PACKAGE_NAMES += check # TODO: use check?
@@ -28,7 +36,7 @@ CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
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 \
-Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
@@ -43,25 +51,30 @@ endif
# set up targets
TARGETS := icfs
TARGETS := $(BUILD_DIR)/icfs
ifeq ($(TEST), 1)
TARGETS += icfs_test
endif
ifneq ($(DIALOGUE), 0)
TARGETS += $(BUILD_DIR)/icfs_dialogue
endif
# build!
default: $(TARGETS)
.PHONY: clean
.PHONY: clean icfs_test clean-icfs clean-icfs_dialogue
icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o
$(BUILD_DIR)/icfs_dialogue:
make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(shell realpath $(DEBUG)) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR))
$(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o $(BUILD_DIR)/proc_operations.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs
icfs_test: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs_test
# $(BUILD_DIR)/icfs_test # TODO: implement testing
icfs_test: $(BUILD_DIR)/icfs
cd ./test && ./test.bash
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
@@ -72,11 +85,31 @@ $(BUILD_DIR)/main.o: $(SOURCES_DIR)/main.c
$(BUILD_DIR)/fuse_operations.o: $(SOURCES_DIR)/fuse_operations.c $(SOURCES_DIR)/fuse_operations.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h
$(BUILD_DIR)/sourcefs.o: $(SOURCES_DIR)/sourcefs.c $(SOURCES_DIR)/sourcefs.h $(SOURCES_DIR)/real_filename.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
clean:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs*
$(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/proc_operations.o: $(SOURCES_DIR)/proc_operations.c $(SOURCES_DIR)/proc_operations.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
CLEAN_TARGETS=clean-icfs
ifneq ($(DIALOGUE), 0)
CLEAN_TARGETS += clean-icfs_dialogue
endif
clean: $(CLEAN_TARGETS)
clean-icfs:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs
clean-icfs_dialogue:
make -C $(SOURCES_DIR)/gui clean SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR))

View File

@@ -15,20 +15,36 @@ Traditional access control mechanisms in operating systems allow the same level
- Install dependencies
- libfuse3
- Debian: `sudo apt install fuse3 libfuse3-dev`
- zenity
- Debian: `sudo apt install zenity`
- Build tools
- Debian: `sudo apt install gcc make pkg-config`
- Build using `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.
## Installation
Currently, there is no installer implemented.
## 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

14
src/access_t.h Normal file
View File

@@ -0,0 +1,14 @@
/*
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
#define ACCESS_T_H
typedef enum { DENY, ALLOW, ALLOW_TEMP, DENY_TEMP, NDEF } access_t;
#endif // !ACCESS_T_H

9591
src/cc.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,11 @@
See the file LICENSE.
*/
#include "process_info.h"
#include "real_filename.h"
#include "set_mode_t.h"
#include <assert.h>
#include <stddef.h>
#define FUSE_USE_VERSION 31
#define _GNU_SOURCE
@@ -36,30 +41,14 @@
#include <sys/file.h> /* flock(2) */
#include "fuse_operations.h"
#include "proc_operations.h"
#include "sourcefs.h"
#include "temp_permissions_table.h"
#include "ui-socket.h"
// TODO: move this to other file
const char *get_process_name_by_pid(const int pid) {
char *name = (char *)calloc(1024, sizeof(char));
if (name) {
sprintf(name, "/proc/%d/cmdline", pid);
FILE *f = fopen(name, "r");
if (f) {
size_t size;
size = fread(name, sizeof(char), 1024, f);
if (size > 0) {
if ('\n' == name[size - 1])
name[size - 1] = '\0';
}
fclose(f);
}
}
return name;
}
int auto_create_perm = GRANT_TEMP;
// TODO: move this somewhere else
const char *real_filename(const char *filename) { return filename; }
void set_auto_create_perm(int val) { auto_create_perm = val; }
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
(void)conn;
@@ -70,8 +59,10 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
To make parallel_direct_writes valid, need either set cfg->direct_io
in current function (recommended in high level API) or set fi->direct_io
in xmp_create() or xmp_open(). */
// cfg->direct_io = 1;
// cfg->parallel_direct_writes = 1;
cfg->direct_io = 1;
#if FUSE_VERSION > FUSE_MAKE_VERSION(3, 14)
cfg->parallel_direct_writes = 1;
#endif
/* Pick up changes from lower filesystem right away. This is
also necessary for better hardlink support. When the kernel
@@ -83,22 +74,25 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
cfg->entry_timeout = 0;
cfg->attr_timeout = 0;
cfg->negative_timeout = 0;
fprintf(stderr, "%d\n", getpid());
assert(get_mountpoint() != NULL);
init_garbage_collector();
return NULL;
}
static int xmp_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi) {
struct fuse_file_info *file_info) {
int res;
(void)path;
if (fi)
res = fstat(fi->fh, stbuf);
if (file_info)
res = fstat(file_info->fh, stbuf);
else
res = source_stat(path, stbuf);
if (res == -1) {
perror("Stat failed");
perror("[ICFS] Stat failed");
return -errno;
}
@@ -106,17 +100,39 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
}
static int xmp_access(const char *path, int mask) {
int res;
int res = -1;
res = access(path, mask);
if (res == -1)
// if mask is F_OK, then we don't need to check the permissions
// (is that possible?)
if (mask != F_OK) {
struct process_info proc_info;
struct fuse_context *context = fuse_get_context();
proc_info.PID = context->pid;
proc_info.name = get_process_name_by_pid(proc_info.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, proc_info, 0)) {
free((void *)proc_info.name);
return -EACCES;
}
free((void *)proc_info.name);
}
res = source_access(path, mask);
if (res == -1) {
return -errno;
}
return 0;
}
static int xmp_readlink(const char *path, char *buf, size_t size) {
int res;
int res = -1;
res = readlink(path, buf, size - 1);
if (res == -1)
@@ -140,7 +156,7 @@ static int xmp_opendir(const char *path, struct fuse_file_info *fi) {
d->dp = source_opendir(path);
if (d->dp == NULL) {
perror("Opendir failed");
perror("[ICFS] Opendir failed");
res = -errno;
free(d);
return res;
@@ -243,28 +259,27 @@ static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) {
*/
static int xmp_mkdir(const char *path, mode_t mode) {
int res;
int res = -1;
res = source_mkdir(path, mode);
if (res == -1)
if (res == -1) {
return -errno;
}
return 0;
}
static int xmp_unlink(const char *path) {
int res;
int res = -1;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
// ask the user for the permission for deleting the file
pi.PID = fc->pid;
pi.UID = fc->uid;
pi.name = get_process_name_by_pid(pi.PID);
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (interactive_access(real_filename(path), pi)) {
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -304,6 +319,27 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
if (flags)
return -EINVAL;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(from, pi, 0)) {
free(pi.name);
return -EACCES;
}
// the "to" file may exist and the process needs to get persmission to modify
// it
if (source_access(to, F_OK) == 0 && !interactive_access(to, pi, 0)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
res = source_rename(from, to);
if (res == -1)
return -errno;
@@ -313,6 +349,20 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
static int xmp_link(const char *from, const char *to) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(from, pi, 0)) {
free(pi.name);
return -EACCES;
}
// no need to check the access to the "to" file, see link(2)
free(pi.name);
res = source_link(from, to);
if (res == -1)
@@ -323,6 +373,18 @@ static int xmp_link(const char *from, const char *to) {
static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
if (fi)
res = fchmod(fi->fh, mode);
@@ -334,9 +396,25 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
return 0;
}
/**
* This filesystem is not designed for multiuser operation (e.g. with
* allow_other) so there is little point in having chown implemnted
*/
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
struct fuse_file_info *fi) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
if (fi)
res = fchown(fi->fh, uid, gid);
@@ -382,19 +460,19 @@ static int xmp_utimens(const char *path, const struct timespec ts[2],
static int xmp_create(const char *path, mode_t mode,
struct fuse_file_info *fi) {
int fd;
int fd = -1;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.UID = fc->uid;
pi.name = get_process_name_by_pid(pi.PID);
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (interactive_access(real_filename(path), pi)) {
free(pi.name);
return -EACCES;
if (auto_create_perm != 0) {
if (!interactive_access(path, pi, auto_create_perm)) {
free(pi.name);
return -EACCES;
}
}
free(pi.name);
@@ -412,12 +490,10 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.UID = fc->uid;
pi.name = get_process_name_by_pid(pi.PID);
pi = get_process_info(fc->pid);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (interactive_access(real_filename(path), pi)) {
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -660,7 +736,7 @@ static off_t xmp_lseek(const char *path, off_t off, int whence,
static const struct fuse_operations xmp_oper = {
.init = xmp_init,
.getattr = xmp_getattr,
// .access = xmp_access,
.access = xmp_access,
.readlink = xmp_readlink,
.opendir = xmp_opendir,
.readdir = xmp_readdir,
@@ -676,7 +752,7 @@ static const struct fuse_operations xmp_oper = {
.chown = xmp_chown,
.truncate = xmp_truncate,
#ifdef HAVE_UTIMENSAT
// .utimens = xmp_utimens,
// .utimens = xmp_utimens,
#endif
.create = xmp_create,
.open = xmp_open,

View File

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

77
src/gui/Makefile Normal file
View File

@@ -0,0 +1,77 @@
SHELL=/bin/bash
# configurable options
ifndef ($(SOURCES_DIR))
SOURCES_DIR := .
endif
ifndef ($(TESTS_DIR))
TESTS_DIR := .
endif
ifndef ($(BUILD_DIR))
BUILD_DIR := .
endif
CC := gcc
CXX := g++
# dependencies
PACKAGE_NAMES := gtk4 libadwaita-1
ifeq ($(TEST), 1)
# PACKAGE_NAMES += check # TODO: use check?
endif
# set up cflags and libs
CFLAGS := -D_FILE_OFFSET_BITS=64
LDFLAGS :=
CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
ifeq ($(DEBUG),1)
CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \
-Wcast-qual -Wdisabled-optimization -Wformat=2 \
-Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
-Wsign-conversion -Wstrict-overflow=5 \
-Wswitch-default -Wundef -Wno-unused
LDFLAGS +=
else
CFLAGS += -O3
LDFLAGS +=
endif
# set up targets
TARGETS := $(BUILD_DIR)/icfs_dialogue
ifeq ($(TEST), 1)
TARGETS += icfs_dialogue_test
endif
# build!
default: $(TARGETS)
.PHONY: clean icfs_dialogue_test
icfs_dialogue_test: $(BUILD_DIR)/icfs_dialogue
$(BUILD_DIR)/icfs_dialogue 666 cat /home/fedir /Downloads
$(BUILD_DIR)/icfs_dialogue: $(BUILD_DIR)/icfs_dialogue.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs_dialogue
$(BUILD_DIR)/icfs_dialogue.o: $(SOURCES_DIR)/icfs_dialogue.c
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/icfs_dialogue.o
clean:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs_dialogue

161
src/gui/icfs_dialogue.c Normal file
View File

@@ -0,0 +1,161 @@
#include "gio/gio.h"
#include "glib-object.h"
#include "glib.h"
#include <adwaita.h>
#include <gtk/gtk.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define YES 0
#define NO 1
#define PERM 2
int exit_code = 0;
gboolean is_permanent = false;
GtkEntryBuffer *entry_buffer = NULL;
GtkWidget *checkbox = NULL;
static void positive_response(GtkWindow *window) {
fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer));
exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox)))
? YES | PERM
: YES;
gtk_window_close(window);
}
static void negative_response(GtkWindow *window) {
fprintf(stdout, "%s", gtk_entry_buffer_get_text(entry_buffer));
exit_code = (gtk_check_button_get_active(GTK_CHECK_BUTTON(checkbox)))
? NO | PERM
: NO;
gtk_window_close(window);
}
static void on_check_button_toggled(GtkToggleButton *button,
gpointer user_data) {
gboolean active = gtk_toggle_button_get_active(button);
}
static void on_activate(GtkApplication *app, gpointer user_data) {
// Create the main window
AdwWindow *window = ADW_WINDOW(adw_window_new());
gtk_window_set_application(GTK_WINDOW(window), app);
gtk_window_set_title(GTK_WINDOW(window), "icfs");
// gtk_window_set_default_size(GTK_WINDOW(window), 300, 150);
AdwStatusPage *content = ADW_STATUS_PAGE(adw_status_page_new());
adw_status_page_set_title(content, "Allow access?");
char *description = NULL;
asprintf(
&description,
"Allow process <tt>%s</tt> with PID <tt>%s</tt> to access <tt>%s</tt>",
g_object_get_data(G_OBJECT(app), "accessing_name"),
g_object_get_data(G_OBJECT(app), "accessing_pid"),
g_object_get_data(G_OBJECT(app), "access_dir"));
adw_status_page_set_description(content, description);
free(description);
entry_buffer = gtk_entry_buffer_new(
g_object_get_data(G_OBJECT(app), "access_dir"),
strlen(g_object_get_data(G_OBJECT(app), "access_dir")));
GtkWidget *entry = gtk_entry_new();
gtk_entry_set_buffer(GTK_ENTRY(entry), entry_buffer);
gtk_entry_set_placeholder_text(GTK_ENTRY(entry), "Enter filename");
gtk_widget_set_hexpand(entry, TRUE);
// Create a prefix label and box
GtkWidget *entry_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget *prefix_label =
gtk_label_new(g_object_get_data(G_OBJECT(app), "root_folder"));
gtk_box_append(GTK_BOX(entry_box), prefix_label);
gtk_box_append(GTK_BOX(entry_box), entry);
checkbox = gtk_check_button_new_with_label("Permanent");
gtk_check_button_set_active(GTK_CHECK_BUTTON(checkbox), false);
// gtk_widget_set_halign(checkbox, GTK_ALIGN_CENTER);
GtkWidget *yes_button = gtk_button_new_with_label("Yes");
gtk_widget_set_hexpand(yes_button, TRUE);
g_signal_connect_swapped(yes_button, "clicked", G_CALLBACK(positive_response),
window);
GtkWidget *no_button = gtk_button_new_with_label("No");
gtk_widget_set_hexpand(no_button, TRUE);
g_signal_connect_swapped(no_button, "clicked", G_CALLBACK(negative_response),
window);
GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_append(GTK_BOX(button_box), yes_button);
gtk_box_append(GTK_BOX(button_box), no_button);
gtk_widget_set_halign(button_box, GTK_ALIGN_FILL);
// Combine everything in a box
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
gtk_box_append(GTK_BOX(box), GTK_WIDGET(content));
gtk_box_append(GTK_BOX(box), entry_box);
gtk_box_append(GTK_BOX(box), checkbox);
gtk_box_append(GTK_BOX(box), button_box);
gtk_widget_set_margin_top(GTK_WIDGET(box), 12);
gtk_widget_set_margin_bottom(GTK_WIDGET(box), 12);
gtk_widget_set_margin_start(GTK_WIDGET(box), 12);
gtk_widget_set_margin_end(GTK_WIDGET(box), 12);
// g_signal_connect(window, "response", G_CALLBACK(gtk_window_close), window);
// Show the dialog
adw_window_set_content(window, box);
gtk_window_present(GTK_WINDOW(window));
}
static int on_command_line(GApplication *app, GApplicationCommandLine *cmdline,
gpointer user_data) {
gchar **argv;
gint argc;
argv = g_application_command_line_get_arguments(cmdline, &argc);
// Handle your arguments here
if (argc >= 4) {
fprintf(stderr, "%s\n", argv[1]);
g_object_set_data_full(G_OBJECT(app), "accessing_pid", g_strdup(argv[1]),
g_free);
g_object_set_data_full(G_OBJECT(app), "accessing_name", g_strdup(argv[2]),
g_free);
g_object_set_data_full(G_OBJECT(app), "root_folder", g_strdup(argv[3]),
g_free);
g_object_set_data_full(G_OBJECT(app), "access_dir", g_strdup(argv[4]),
g_free);
}
g_strfreev(argv);
// Activate the application
g_application_activate(app);
return 0;
}
int main(int argc, char **argv) {
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
fprintf(stdout, "icfs_dialogue 1.0.0");
}
// disable accessibility features to prevent attacks
g_setenv("NO_AT_BRIDGE", "1", TRUE);
// Create a new application
AdwApplication *app = adw_application_new("de.umbrasolis.icfs_dialogue",
G_APPLICATION_HANDLES_COMMAND_LINE);
g_signal_connect(app, "command-line", G_CALLBACK(on_command_line), NULL);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
// Run the application
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return (status == 0) ? exit_code : status;
}

View File

@@ -10,14 +10,16 @@
See the file LICENSE.
*/
#include <string.h>
#define FUSE_USE_VERSION 31
#define _GNU_SOURCE
#include <fuse3/fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "fuse_operations.h"
#include "sourcefs.h"
@@ -26,24 +28,66 @@
const char *mountpoint = NULL;
int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database] <ICFS "
"arguments>\n\t--no-perm-on-create - do not give any "
"access permissions on file creation"
"(incompatible with --temp-on-create)\n\t--perm-on-create "
"- automatically give permanent access permission to files "
"a process creates "
"(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 - do not give any "
"access permissions on file creation"
"(incompatible with --temp-on-create)\n\t--perm-on-create "
"- automatically give permanent access permission to files "
"a process creates "
"(incompatible with --no-perm-on-create)\n");
return EXIT_FAILURE;
}
// if umask != 0, the filesystem will create files with more restrictive
// permissions than it's caller reqested
umask(0);
mountpoint = realpath(argv[argc - 1], NULL);
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--;
}
int ret = source_init(mountpoint);
// ui socket should always be initialized before anything else, since it
// handles the setuid bits!
int ret = init_ui_socket(argv[argc - 1]);
if (ret != 0) {
perror("source_init");
fprintf(stderr, "[ICFS] Could not initalize ui-socket.\n");
exit(EXIT_FAILURE);
}
ret = init_ui_socket("/home/fedir/.icfs-sock");
mountpoint = realpath(argv[argc - 2], NULL);
ret = source_init(mountpoint);
if (ret != 0) {
perror("init_ui_socket");
perror("[ICFS] source_init");
exit(EXIT_FAILURE);
}
ret = fuse_main(argc, argv, get_fuse_operations(), NULL);
ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL);
free(mountpoint);
free((void *)mountpoint);
source_destroy();
destroy_ui_socket();
return ret;
}

View File

@@ -0,0 +1,317 @@
/*
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 "perm_permissions_table.h"
#include "access_t.h"
#include "proc_operations.h"
#include "process_info.h"
#include "set_mode_t.h"
#include <fcntl.h>
#include <pthread.h>
#include <sqlite3.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fsuid.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
sqlite3 *perm_database = NULL;
const char *const table_name = "permissions";
// one row corresponds to a permission to access one file for one executable
const int column_count = 3;
const char *const schema[] = {"executable", "filename", "mode"};
const char *const types[] = {"TEXT", "TEXT", "INTEGER"};
uid_t ruid, euid, current_uid;
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
void set_db_fsuid() {
pthread_mutex_lock(&uid_switch);
if (current_uid == ruid)
return;
int status = -1;
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", ruid);
exit(status);
}
pthread_mutex_unlock(&uid_switch);
}
void set_real_fsuid() {
pthread_mutex_lock(&uid_switch);
if (current_uid == ruid)
return;
int status = -1;
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", euid);
exit(status);
}
pthread_mutex_unlock(&uid_switch);
}
static int check_table_col_schema(void *notused, int argc, char **argv,
char **colname) {
(void)notused;
(void)colname;
if (argc < 3) {
fprintf(stderr,
"[ICFS] Unexpected amount of arguments given to the callback.\n");
return 1;
}
int column_num = atoi(argv[0]);
if (column_num >= column_count) {
fprintf(stderr, "[ICFS] Table contains unexpected amount of columns.\n");
return 1;
}
if (strcmp(schema[column_num], argv[1]) == 0 &&
strcmp(types[column_num], argv[2]) == 0) {
return 0;
}
fprintf(stderr, "[ICFS] Column %d does not conform to the schema.\n",
column_num);
return 1;
}
static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)colname;
if (argc < 3) {
fprintf(
stderr,
"[ICFS] Unexpected amount of arguments given to the callback: %d.\n",
argc);
return 1;
}
if (atoi(argv[2])) {
fprintf(stderr, "[ICFS] Third column was: %s\n", argv[2]);
*(int *)flag = 1;
} else {
*(int *)flag = -1;
}
return 0;
}
int create_database_schema() {
fprintf(stderr, "[ICFS] Creating table 'permissions'.\n");
const char *create_query =
"CREATE TABLE permissions(executable TEXT NOT "
"NULL, filename TEXT NOT NULL, mode INTEGER NOT NULL);";
char *err = NULL;
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "[ICFS] Database created successfully\n");
return 0;
}
/**
* Ensures that the database schema is correct.
*
* @return: 0 if the schema is correct, 1 if the schema could not be corrected.
*/
int ensure_database_schema() {
// Check for the table.
int result = sqlite3_table_column_metadata(
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
if (result == SQLITE_ERROR) {
fprintf(stderr, "[ICFS] Table '%s' does not exist.\n", table_name);
if (create_database_schema()) {
fprintf(stderr, "[ICFS] Table could not be created.\n");
return 1;
}
return 0;
} else if (result != SQLITE_OK) {
fprintf(stderr, "[ICFS] Database metadata could not be retrieved.\n");
return 1;
}
const char *pragma = "PRAGMA table_info(permissions);";
char *err = NULL;
int ret =
sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "[ICFS] sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "[ICFS] Schema is correct.\n");
return 0;
}
/**
* Initializes the permanent permissions table.
*
* @param db_filename: The filename of the permissions sqlite3 database
* @return: 0 on success, -1 on failure
*/
int init_perm_permissions_table(const char *db_filename) {
// we don't want the group and others to access the db
umask(0077);
ruid = getuid();
euid = geteuid();
fprintf(stderr, "[ICFS] Running with uid: %d, gid: %d\n", euid, getegid());
if (sqlite3_open_v2(db_filename, &perm_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_FULLMUTEX,
NULL)) {
perror("[ICFS] Can't open permanent permissions database");
return -1;
}
umask(0);
if (ensure_database_schema()) {
fprintf(stderr, "[ICFS] Database schema is not correct.\n");
return -1;
}
int status = seteuid(ruid);
if (status < 0) {
fprintf(stderr,
"[ICFS] Couldn't set euid to ruid during database setup.\n");
exit(status);
}
return 0;
}
/**
* Destroys the permanent permissions table.
*/
void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); }
/**
* Checks if the process has a permanent access to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found
*/
access_t check_perm_access_noparent(const char *filename,
struct process_info pi) {
if (pi.name == NULL)
return NDEF;
access_t ret = NDEF;
sqlite3_stmt *stmt = NULL;
const char *sql =
"SELECT mode FROM permissions WHERE executable = ?1 "
"AND (( ?2 LIKE (filename || \'%\') AND filename "
"GLOB \'*/\') OR filename = ?2 ) ORDER BY LENGTH( filename ) DESC;";
sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
int step_ret = sqlite3_step(stmt);
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 (step_ret == SQLITE_ROW) {
int mode_col = sqlite3_column_int(stmt, 0);
if (mode_col) {
ret = ALLOW;
} else {
ret = DENY;
}
}
sqlite3_finalize(stmt);
return ret;
}
/**
* Checks if the process or any of it's parents have permanent access to the
* file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP or DENY_TEMP.
* @note: In case one of the parent processes is killed while this function
* execution the result is not guranteed to be correct. It should only lead to
* false negatives, though.
*/
access_t check_perm_access(const char *filename, struct process_info pi) {
if (pi.PID == 0 || pi.name == NULL) {
return NDEF;
}
struct process_info current_pi = pi;
current_pi.name = strdup(current_pi.name);
while (current_pi.PID != 0) {
access_t access = check_perm_access_noparent(filename, current_pi);
free(current_pi.name);
if (access != NDEF) {
return access;
}
current_pi.name = NULL;
while (current_pi.name == NULL) {
current_pi.PID = get_main_thread_pid(get_parent_pid(current_pi.PID));
if (current_pi.PID != 0) {
current_pi.name = get_process_name_by_pid(current_pi.PID);
} else {
break;
}
}
}
return NDEF;
}
/**
* Gives permanent access to the process to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: 0 on success, 1 on failure
*/
int set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode) {
sqlite3_stmt *stmt = NULL;
char *sql = NULL;
if (mode == SET_ALLOW) {
sql = "INSERT INTO permissions VALUES (?1, ?2, TRUE);";
} else if (mode == SET_DENY) {
sql = "INSERT INTO permissions VALUES (?1, ?2, FALSE);";
} else {
return 1;
}
sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
int step_ret = sqlite3_step(stmt);
if (step_ret != SQLITE_DONE) {
fprintf(stderr, "[ICFS] SQLite error: %s\n", sqlite3_errstr(step_ret));
sqlite3_finalize(stmt);
return 1;
}
sqlite3_finalize(stmt);
return 0;
}

View File

@@ -0,0 +1,51 @@
/*
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 PERM_PERMISSION_TABLE_H
#define PERM_PERMISSION_TABLE_H
#include "access_t.h"
#include "process_info.h"
#include "set_mode_t.h"
/**
* Initializes the permanent permissions table.
*
* @param db_filename: The filename of the permissions sqlite3 database
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
int init_perm_permissions_table(const char *db_filename);
/**
* Destroys the permanent permissions table.
*/
void destroy_perm_permissions_table();
/**
* Checks if the process has a permanent access to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found
*/
access_t check_perm_access(const char *filename, struct process_info pi);
/**
* Gives permanent access to the process to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and
* SET_ALLOW to allow access.
* @return: 0 on success, 1 on failure
*/
int set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode);
#endif // #ifdef PERM_PERMISSION_TABLE_H

123
src/proc_operations.c Normal file
View File

@@ -0,0 +1,123 @@
/*
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 <linux/limits.h>
#include <stddef.h>
#include <stdio.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 path[1024];
sprintf(path, "/proc/%d/exe", pid);
size_t size = 128;
char *name = malloc(size);
if (name == NULL) {
fprintf(stderr, "[ICFS] Could not get process name by pid %d", pid);
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;
}
/**
* Finds the parent process ID of a given process.
*
* @param pid: The process ID of the process to find the parent of
* @return: The parent process ID, or 0 if the parent process ID could not be
* found
*/
pid_t get_parent_pid(pid_t pid) {
pid_t ppid = 0;
char path[256];
snprintf(path, sizeof(path), "/proc/%u/status", pid);
FILE *file = fopen(path, "r");
if (file == NULL) {
perror("[ICFS] Failed to open /proc/<pid>/status");
return 0;
}
char line[256];
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "PPid:\t%d", &ppid) == 1) {
fclose(file);
return ppid;
}
}
fclose(file);
return 0; // Parent PID not found
}

34
src/proc_operations.h Normal file
View File

@@ -0,0 +1,34 @@
/*
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
#define PROC_OPERATIONS
#include <time.h>
char *get_process_name_by_pid(const int pid);
/**
* Finds the parent process ID of a given process.
*
* @param pid: The process ID of the process to find the parent of
* @return: The parent process ID, or 0 if the parent process ID could not be
* found
*/
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

33
src/process_info.h Normal file
View File

@@ -0,0 +1,33 @@
/*
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 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 <unistd.h>
struct process_info {
pid_t PID;
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

15
src/real_filename.h Normal file
View File

@@ -0,0 +1,15 @@
/*
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
#define REAL_FILENAME_H
const char *real_filename(const char *filename);
const char *get_mountpoint(void);
#endif // !REAL_FILENAME_H

12
src/set_mode_t.h Normal file
View File

@@ -0,0 +1,12 @@
/*
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
#define SET_MODE_T_H
typedef enum { SET_DENY, SET_ALLOW } set_mode_t;
#endif // !SET_MODE_T_H

View File

@@ -10,14 +10,14 @@
#include "sourcefs.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static struct source_files_handle {
const char *mountpoint;
int root_fd;
} handle;
@@ -30,9 +30,20 @@ const char *source_filename_translate(const char *filename) {
}
int source_init(const char *root_path) {
handle.mountpoint = malloc(strlen(root_path) + 1);
if (handle.mountpoint == NULL) {
perror("[ICFS] Malloc failed");
return -1;
}
strcpy(handle.mountpoint, root_path);
int root_fd = open(root_path, O_PATH);
if (root_fd == -1) {
fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
root_path);
perror("");
return -1;
}
@@ -41,6 +52,32 @@ int source_init(const char *root_path) {
return 0;
}
void source_destroy(void) { free(handle.mountpoint); }
const char *get_mountpoint(void) { return handle.mountpoint; }
const char *real_filename(const char *filename) {
const char *mountpoint = get_mountpoint();
// Calculate required length
size_t len1 = strlen(mountpoint);
size_t len2 = strlen(filename);
size_t total_len = len1 + len2;
// Allocate memory (+1 for null terminator)
char *result = malloc(total_len + 1);
if (result == NULL) {
fprintf(stderr, "[ICFS] Memory allocation failed");
perror("");
return NULL;
}
// Copy strings
strcpy(result, mountpoint);
strcat(result, filename);
return result;
}
int source_mkdir(const char *filename, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return mkdirat(handle.root_fd, relative_filename, mode);
@@ -66,11 +103,16 @@ int source_symlink(const char *target, const char *linkpath) {
return symlinkat(target, handle.root_fd, relative_linkpath);
}
int source_access(const char *filename, int mode) {
const char *relative_filename = source_filename_translate(filename);
return faccessat(handle.root_fd, relative_filename, mode, 0);
}
DIR *source_opendir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) {
perror("Openat failed");
perror("[ICFS] Openat failed");
return NULL;
}
DIR *dir_pointer = fdopendir(fd);
@@ -107,7 +149,7 @@ int source_truncate(const char *filename, off_t length) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
if (fd < 0) {
perror("Openat failed");
perror("[ICFS] Openat failed");
return -1;
}
return ftruncate(fd, length);

View File

@@ -19,6 +19,7 @@
* @return 0 on success, -1 on failure.
*/
int source_init(const char *root_path);
void source_destroy(void);
/* All of the functions below are designed to behave exactly as their non-source
* counterparts. */
@@ -47,6 +48,8 @@ int source_chown(const char *filename, uid_t owner, gid_t group);
int source_truncate(const char *filename, off_t length);
int source_access(const char *filename, int mode);
/* `open` and `create` are designed to correspond to fuse operations, not the
* libc's `open(2)`. Both of them actually call `openat`. */

View File

@@ -0,0 +1,317 @@
/*
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 "temp_permissions_table.h"
#include "access_t.h"
#include "cc.h"
#include "proc_operations.h"
#include "process_info.h"
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
struct temp_process_permissions {
// yes, this is a correct type for start time in jiffies (see
// proc_pid_stat(5))
unsigned long long creation_time;
vec(char *) allowed_files;
vec(char *) denied_files;
};
map(pid_t, struct temp_process_permissions) temp_permissions_table;
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
* filesystem
*
* @param pid: The process ID of the process to get the creation time of
* @return: The process creation time in jiffies, or 0 on error
* @note: although nothing in the documentation says that the creation time is
* never really equal to 0, it exceptionally unlikely.
*/
unsigned long long get_process_creation_time(pid_t pid) {
char path[256];
FILE *fp = NULL;
unsigned long long creation_time = 0;
// Construct the path to the process's status file
snprintf(path, sizeof(path), "/proc/%u/stat", pid);
// Open the status file
fp = fopen(path, "r");
if (fp == NULL) {
perror("[ICFS] fopen");
return 0;
}
// Read the creation time (the 22nd field in the stat file)
for (int i = 1; i < 22; i++) {
if (fscanf(fp, "%*s") == EOF) {
fprintf(stderr,
"[ICFS] Error reading process stat file on the number %d\n", i);
fclose(fp);
return 0;
}
}
if (fscanf(fp, "%llu", &creation_time) != 1) {
fprintf(stderr, "[ICFS] Error reading creation time\n");
fclose(fp);
return 0;
}
// Close the file
fclose(fp);
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.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
int init_temp_permissions_table(void) {
init(&temp_permissions_table);
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.
*
* @note: the table is guranteed to be destroyed if it is already initialized.
* It does not indicate any errors whatsoever. If something goes wrong - you are
* screwed.
*/
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
for_each(&temp_permissions_table, entry) {
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
cleanup(&entry->allowed_files);
}
for_each(&temp_permissions_table, entry) {
for_each(&entry->denied_files, denied_file) { free(*denied_file); }
cleanup(&entry->denied_files);
}
cleanup(&temp_permissions_table);
}
/**
* Checks if the process has a temporary access to the file.
*
* @param filename: The file that the process is trying to access
* @param pid: PID of the process
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found is avaliable
*/
access_t check_temp_access_noparent(const char *filename, pid_t pid) {
// TODO: more efficient locking
pthread_rwlock_rdlock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry =
get(&temp_permissions_table, pid);
if (permission_entry != NULL) {
unsigned long long process_creation_time = get_process_creation_time(pid);
if (process_creation_time == 0) {
perror("[ICFS] Could not retrieve process creation time");
pthread_rwlock_unlock(&temp_permissions_table_lock);
return NDEF;
}
if (process_creation_time == permission_entry->creation_time) {
// the process is the same as the one that was granted temporary access
// to the file
size_t filename_len = strlen(filename);
access_t ret = NDEF;
size_t maxlen = 0;
for_each(&permission_entry->denied_files, denied_file) {
size_t denied_file_len = strlen(*denied_file);
if ((strncmp(*denied_file, filename, denied_file_len) == 0 &&
((denied_file_len < filename_len &&
(*denied_file)[denied_file_len - 1] == '/') ||
(denied_file_len == filename_len))) &&
denied_file_len > maxlen) {
maxlen = denied_file_len;
ret = DENY;
}
}
for_each(&permission_entry->allowed_files, allowed_file) {
size_t allowed_file_len = strlen(*allowed_file);
if ((strncmp(*allowed_file, filename, allowed_file_len) == 0 &&
((allowed_file_len < filename_len &&
(*allowed_file)[allowed_file_len - 1] == '/') ||
(allowed_file_len == filename_len))) &&
allowed_file > maxlen) {
maxlen = allowed_file_len;
ret = ALLOW;
}
}
pthread_rwlock_unlock(&temp_permissions_table_lock);
return ret;
}
}
pthread_rwlock_unlock(&temp_permissions_table_lock);
return NDEF;
}
/**
* Checks if the process or any of it's parents have temporary access to the
* file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP.
* @note: In case one of the parent processes is killed while this function
* execution the result is not guranteed to be correct. It should only lead to
* false negatives, though.
*/
access_t check_temp_access(const char *filename, struct process_info pi) {
pid_t current_pid = pi.PID;
while (current_pid != 0) {
access_t access = check_temp_access_noparent(filename, current_pid);
if (access != NDEF) {
return access;
}
current_pid = get_main_thread_pid(get_parent_pid(current_pid));
}
return NDEF;
}
/**
* Sets temporary access mode of the process to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and
* SET_ALLOW to allow access.
* @return: 0 on success, -1 on failure.
*/
int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode) {
if (pi.PID == 0)
return NDEF;
pthread_rwlock_wrlock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry =
get(&temp_permissions_table, pi.PID);
if (permission_entry != NULL) {
unsigned long long process_creation_time =
get_process_creation_time(pi.PID);
if (process_creation_time == 0) {
perror("[ICFS] Could not retrieve process creation time");
pthread_rwlock_unlock(&temp_permissions_table_lock);
return -1;
}
if (process_creation_time == permission_entry->creation_time) {
// the process is the same as the one that was granted temporary access
// to the file
if (mode == SET_ALLOW) {
push(&permission_entry->allowed_files, strdup(filename));
}
if (mode == SET_DENY) {
push(&permission_entry->denied_files, strdup(filename));
}
pthread_rwlock_unlock(&temp_permissions_table_lock);
return 0;
}
// we have an entry for the process, but the process is different
// delete the entry and create a new one
erase(&temp_permissions_table, pi.PID);
permission_entry = NULL;
}
// no entry is present
// construct the entry
struct temp_process_permissions new_permission_entry;
new_permission_entry.creation_time = get_process_creation_time(pi.PID);
init(&new_permission_entry.allowed_files);
init(&new_permission_entry.denied_files);
if (mode == SET_ALLOW) {
push(&new_permission_entry.allowed_files, strdup(filename));
}
if (mode == SET_DENY) {
push(&new_permission_entry.denied_files, strdup(filename));
}
insert(&temp_permissions_table, pi.PID, new_permission_entry);
pthread_rwlock_unlock(&temp_permissions_table_lock);
return 0;
}

View File

@@ -0,0 +1,65 @@
/*
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
#define TEMP_PERMISSIONS_TABLE_H
#include "access_t.h"
#include "process_info.h"
#include "set_mode_t.h"
/**
* Initializes the temporary permissions table.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
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.
*
* @note: the table is guranteed to be destroyed if it is already initialized.
* It does not indicate any errors whatsoever. If something goes wrong - you are
* screwed.
*/
void destroy_temp_permissions_table(void);
/**
* Checks if the process or any of it's parents have temporary access to the
* file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP.
* @note: In case one of the parent processes is killed while this function
* execution the result is not guranteed to be correct. It should only lead to
* false negatives, though.
*/
access_t check_temp_access(const char *filename, struct process_info pi);
/**
* Sets temporary access mode of the process to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and
* SET_ALLOW to allow access.
* @return: 0 on success, -1 on failure.
*/
int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode);
#endif // !TEMP_PERMISSIONS_TABLE_H

View File

@@ -6,11 +6,18 @@
See the file LICENSE.
*/
#include "access_t.h"
#include <stddef.h>
#include <sys/types.h>
#include <time.h>
#define _GNU_SOURCE
#include "cc.h"
#include "perm_permissions_table.h"
#include "real_filename.h"
#include "sourcefs.h"
#include "temp_permissions_table.h"
#include "ui-socket.h"
#include <errno.h>
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@@ -19,21 +26,46 @@
#include <sys/un.h>
#include <unistd.h>
int init_ui_socket(const char *filename) {
char line[256];
FILE *fp;
#define DIALOGUE_YES 0
#define DIALOGUE_NO 1
#define DIALOGUE_PERM 2
// Test if Zenity is installed (get version)
fp = popen("zenity --version", "r");
if (fp == NULL) {
perror("Pipe returned a error");
pthread_mutex_t access_check_mutex = PTHREAD_MUTEX_INITIALIZER;
struct dialogue_response {
access_t decision;
char *filename;
};
int init_ui_socket(const char *perm_permissions_db_filename) {
FILE *fp = NULL;
if (init_temp_permissions_table()) {
fprintf(stderr,
"[ICFS] Could not initialize temporary permissions table.\n");
return 1;
} else {
while (fgets(line, sizeof(line), fp))
printf("%s", line);
pclose(fp);
return 0;
}
if (init_perm_permissions_table(perm_permissions_db_filename)) {
fprintf(stderr,
"[ICFS] Could not initialize permanent permissions table.\n");
return 1;
}
// Test if dialogue is installed (get version)
fp = popen("icfs_dialogue --version", "r");
if (fp == NULL) {
perror("[ICFS] Pipe returned an error");
return 1;
}
pclose(fp);
return 0;
}
void destroy_ui_socket(void) {
destroy_temp_permissions_table();
destroy_perm_permissions_table();
}
/**
@@ -41,75 +73,88 @@ int init_ui_socket(const char *filename) {
* GUI
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed, 2 if access is allwed
* for the runtime of the process
* @param pi: The process information
* @return: access status - ALLOW, DENY or ALLOW_TEMP
* allowed for the runtime of the process
*/
int ask_access(const char *filename, struct process_info pi) {
struct dialogue_response ask_access(const char *filename,
struct process_info proc_info) {
FILE *fp = NULL;
char *command = NULL;
int ret = asprintf(&command, "icfs_dialogue \"%d\" \"%s\" \"%s\" \"%s\"",
proc_info.PID, proc_info.name, get_mountpoint(), filename);
FILE *fp;
size_t command_len =
139 + sizeof(pid_t) * 8 + strlen(pi.name) + strlen(filename);
char *command = (char *)malloc(command_len);
snprintf(command, command_len,
"zenity --question --extra-button \"Allow this time\" --title "
"\"Allow Access?\" --text \"Allow process "
"<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"",
pi.name, pi.PID, filename);
struct dialogue_response response;
response.decision = DENY;
response.filename = NULL;
// Zenity Question Message Popup
if (ret < 0) {
// If asprintf fails, the contents of command are undefined (see man
// 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
// justify preparing for this.
fprintf(stderr, "[ICFS] Could not create query on rule insertion");
perror("");
response.decision = DENY;
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
}
// dialogue Question Message Popup
fp = popen(command, "r");
free(command);
if (fp == NULL) {
perror("Pipe returned a error");
return -1;
perror("[ICFS] Pipe returned a error");
response.decision = DENY;
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
}
// if the user clicks the "Allow this time" button, `zenity` will only
// write it to `stdout`, but the exit code will still be `1`. So, we need
// to manually check the output.
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp)) {
if (strcmp(buffer, "Allow this time.\n") == 0) {
pclose(fp);
return 2;
}
str(char) dialogue_output;
init(&dialogue_output);
char line[1024]; // Buffer to read individual lines
// Read the command output line by line
while (fgets(line, sizeof(line), fp)) {
push_fmt(&dialogue_output, line);
}
int zenity_exit_code = WEXITSTATUS(pclose(fp));
return zenity_exit_code;
}
int dialogue_exit_code = WEXITSTATUS(pclose(fp));
/**
* Checks if the process has a temporary access to the file.
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed
*/
int check_temp_access(const char *filename, struct process_info pi) {
perror("Not implemented");
return 0;
}
fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
/**
* Checks if the process has a permanent access to the file.
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed
*/
int check_perm_access(const char *filename, struct process_info pi) {
perror("Not implemented");
return 0;
}
if (size(&dialogue_output) == 0) {
push(&dialogue_output, '/');
}
void give_temp_access(const char *filename, struct process_info pi) {
perror("Not implemented");
}
void give_perm_access(const char *filename, struct process_info pi) {
perror("Not implemented");
assert(strlen(first(&dialogue_output)) == size(&dialogue_output));
response.filename = malloc(size(&dialogue_output) + 1);
strcpy(response.filename, first(&dialogue_output));
// response.filename[size(&dialogue_output)] = 0;
// assert(0 == strcmp(response.filename, first(&dialogue_output)));
cleanup(&dialogue_output);
time_t now = time(0);
if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) {
response.decision = ALLOW;
} else if (dialogue_exit_code == DIALOGUE_YES) {
response.decision = ALLOW_TEMP;
} else if (dialogue_exit_code == (DIALOGUE_NO | DIALOGUE_PERM)) {
response.decision = DENY;
} else {
response.decision = DENY_TEMP;
}
return response;
}
/**
@@ -119,27 +164,133 @@ void give_perm_access(const char *filename, struct process_info pi) {
* 3. user descision
*
* @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)
* @return: 0 if access is denied, 1 if access is allowed
*/
int interactive_access(const char *filename, struct process_info pi) {
int interactive_access(const char *filename, struct process_info proc_info,
int opts) {
char *real_path = real_filename(filename);
pthread_mutex_lock(&access_check_mutex);
if (check_temp_access(filename, pi) || check_perm_access(filename, pi)) {
// access was already granted before
access_t access = check_temp_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(
stderr,
"[ICFS] Permission allowed to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
if (access == DENY) {
fprintf(
stderr,
"[ICFS] Permission denied to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 0;
}
access = check_perm_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(
stderr,
"[ICFS] Permission allowed to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
if (access == DENY) {
fprintf(
stderr,
"[ICFS] Permission denied to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 0;
}
// if noth GRANT_TEMP and GRANT_PERM are selected, then only permanent
// permissions are granted
if (opts & GRANT_PERM) {
fprintf(stderr, "[ICFS] Permission granted permanently to %s.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
if (opts & GRANT_TEMP) {
fprintf(stderr, "[ICFS] Permission granted temporarily to %s.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
int user_response = ask_access(filename, pi);
if (user_response == 1) {
// user said "yes"
give_perm_access(filename, pi);
return 1;
} else if (user_response == 2) {
// user said "yes, but only this time"
give_temp_access(filename, pi);
return 1;
struct dialogue_response response = ask_access(filename, proc_info);
// fprintf(stderr, "%s", response.filename);
// assert(0 != strlen(response.filename));
// the user might specify a different file in the dialogue, so we need to
// check if it is valid
while (source_access(response.filename, F_OK)) {
// if it is invalid, just ask again.
fprintf(stderr, "[ICFS] Filename returned by zenty wasn't correct: %s\n",
response.filename);
free(response.filename);
response = ask_access(filename, proc_info);
}
// otherwise "no"
return 0;
free(real_path);
real_path = real_filename(response.filename);
free(response.filename);
int ret = 0;
if (response.decision == ALLOW) {
fprintf(stderr,
"[ICFS] Permission granted permanently to %s based on zenty "
"response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
ret = 1;
} else if (response.decision == ALLOW_TEMP) {
fprintf(stderr,
"[ICFS] Permission granted temporarily to %s based on zenty "
"response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
ret = 1;
} else if (response.decision == DENY_TEMP) {
fprintf(
stderr,
"[ICFS] Permission denied temporarily to %s based on zenty response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_DENY);
ret = 0;
} else if (response.decision == DENY) {
fprintf(
stderr,
"[ICFS] Permission denied permanently to %s based on zenty response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_DENY);
ret = 0;
}
pthread_mutex_unlock(&access_check_mutex);
free(real_path);
// deny on unknown options.
return ret;
}

View File

@@ -13,17 +13,36 @@
#ifndef UI_SOCKET_H
#define UI_SOCKET_H
#include "process_info.h"
#include <sys/types.h>
struct process_info {
pid_t PID;
const char *name;
uid_t UID;
};
/**
* Initialize the GUI communication.
*
* @return: 0 on success, -1 on faliure.
*/
int init_ui_socket(const char *perm_permissions_db_filename);
// For default socket location, set socket_path = NULL.
int init_ui_socket(const char *socket_path);
/**
* Close the GUI communication.
*/
void destroy_ui_socket(void);
int interactive_access(const char *filename, struct process_info pi);
/**
* Check access according to:
* 1. temporary permission table
* 2. permanent permission table
* 3. user descision
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @param opts: options (GRANT_TEMP, GRANT_PERM)
* @return: 0 if access is denied, 1 if access is allowed
*/
int interactive_access(const char *filename, struct process_info pi, int opts);
#define GRANT_TEMP 1
#define GRANT_PERM 2
// #define TABLE_ONLY 4 // NOTE: Add this in the future?
#endif // !UI_SOCKET_H

41
test/mock/icfs_dialogue Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/bash
# fake-icfs_dialogue: script that mocks the behavior of icfs_dialogue based on the ./.fake-icfs_dialogue-response file
ICFS_DIALOGUE_YES=0
ICFS_DIALOGUE_NO=1
ICFS_DIALOGUE_PERM=2
if [[ $1 == "--set-fake-response" ]]; then
#someone knows we are fake :)
echo "$2" >~/.fake_icfs_dialogue_response
elif [[ $1 == "--set-fake-response-filename" ]]; then
echo "$2" >~/.fake_icfs_dialogue_response_filename
elif [[ $1 == "--reset-fake-response" ]]; then
rm ~/.fake_icfs_dialogue_response ~/.fake_icfs_dialogue_response_filename
else
if [ -f ~/.fake_icfs_dialogue_response ]; then
FAKE_ICFS_DIALOGUE_RESPONSE=$(cat ~/.fake_icfs_dialogue_response)
if [[ -f ~/.fake_icfs_dialogue_response_filename ]]; then
FAKE_ICFS_DIALOGUE_RESPONSE_FILENAME=$(cat ~/.fake_icfs_dialogue_response_filename)
if [[ $FAKE_ICFS_DIALOGUE_RESPONSE_FILENAME == "" ]]; then
printf "%s" "$4"
else
printf "%s" "$(cat ~/.fake_icfs_dialogue_response_filename)"
fi
fi
if [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes" ]]; then
exit "$ICFS_DIALOGUE_YES"
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no" ]]; then
exit "$ICFS_DIALOGUE_NO"
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "yes_perm" ]]; then
exit "$((ICFS_DIALOGUE_YES | ICFS_DIALOGUE_PERM))"
elif [[ $FAKE_ICFS_DIALOGUE_RESPONSE == "no_perm" ]]; then
exit "$((ICFS_DIALOGUE_NO | ICFS_DIALOGUE_PERM))"
fi
fi
fi
exit 255 # TODO: call actual icfs_dialogue here

View File

@@ -1,23 +0,0 @@
#!/bin/bash
# fake-zenity: script that mocks the behavior of zenity based on the ./.fake-zenity-response file
if [[ $1 == "--set-fake-response" ]]; then
#someone knows we are fake :)
echo $2 >~/.fake_zenity_response
else
if [ -f ~/.fake_zenity_response ]; then
FAKE_ZENITY_RESPONSE=$(cat ~/.fake_zenity_response)
if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then
printf "Allow this time\n"
exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then
exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then
exit 0
fi
fi
fi
exit -1 # TODO: call actual zenity here

81
test/opener/Makefile Normal file
View File

@@ -0,0 +1,81 @@
SHELL=/bin/bash
# configurable options
ifndef ($(SOURCES_DIR))
SOURCES_DIR := .
endif
ifndef ($(TESTS_DIR))
TESTS_DIR := .
endif
ifndef ($(BUILD_DIR))
BUILD_DIR := .
endif
CC := gcc
CXX := g++
NAME := opener
# dependencies
PACKAGE_NAMES :=
ifeq ($(TEST), 1)
# PACKAGE_NAMES += check # TODO: use check?
endif
# set up cflags and libs
CFLAGS :=
LDFLAGS :=
ifneq ($(PACKAGE_NAMES),)
CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES))
LDFLAGS += $(shell pkg-config --libs $(PACKAGE_NAMES))
endif
ifeq ($(DEBUG),1)
CFLAGS += -O0 -pedantic -g -Wall -Wextra -Wcast-align \
-Wcast-qual -Wdisabled-optimization -Wformat=2 \
-Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wredundant-decls -Wshadow \
-Wsign-conversion -Wstrict-overflow=5 \
-Wswitch-default -Wundef -Wno-unused
LDFLAGS +=
else
CFLAGS += -O3
LDFLAGS +=
endif
# set up targets
TARGETS := $(BUILD_DIR)/$(NAME)
ifeq ($(TEST), 1)
TARGETS += $(NAME)_test
endif
# build!
default: $(TARGETS)
.PHONY: clean $(NAME)_test
$(NAME)_test: $(BUILD_DIR)/$(NAME)
echo "No tests defined."
#$(BUILD_DIR)/$(NAME)
$(BUILD_DIR)/$(NAME): $(BUILD_DIR)/$(NAME).o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/$(NAME)
$(BUILD_DIR)/$(NAME).o: $(SOURCES_DIR)/$(NAME).c
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $(BUILD_DIR)/$(NAME).o
clean:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/$(NAME)

86
test/opener/opener.c Normal file
View File

@@ -0,0 +1,86 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define PATH_MAX 4096
int main(int argc, char *argv[]) {
// Check for correct usage
if (argc != 2) {
fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
return 1;
}
const char *path = argv[1];
struct stat statbuf;
// Stat the given path to determine if it's a directory
if (lstat(path, &statbuf) == -1) {
perror("lstat");
return 1;
}
// Case 1: The path is not a directory
if (!S_ISDIR(statbuf.st_mode)) {
int fd = open(path, O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
close(fd);
return 0;
}
// Case 2: The path is a directory
DIR *dirp = opendir(path);
if (dirp == NULL) {
perror("opendir");
return 1;
}
struct dirent *entry;
int success = 1;
while ((entry = readdir(dirp)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
// Construct the full path
char fullpath[PATH_MAX];
snprintf(fullpath, PATH_MAX, "%s/%s", path, entry->d_name);
// Stat the entry to check if it's a regular file
struct stat entry_stat;
if (lstat(fullpath, &entry_stat) == -1) {
perror("lstat");
success = 0;
break;
}
// Only process regular files
if (!S_ISREG(entry_stat.st_mode)) {
continue;
}
// Try to open and immediately close the file
int fd = open(fullpath, O_RDONLY);
if (fd == -1) {
perror("open");
success = 0;
break;
}
close(fd);
}
closedir(dirp);
return (success ? 0 : 1);
}

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

@@ -2,10 +2,30 @@
# clean what was left from previous tests
rm -f ./.pt.db
rm -rf ./protected
mkdir protected
touch ./protected/do-not-remove ./protected/should-be-removed
echo "Free code, free world." >./protected/this-only
touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth ./protected/perm000 ./protected/perm777 ./protected/should-be-renamed ./protected/do-not-rename
chmod 777 ./protected/perm777 ./protected/perm000
echo "Free code, free world." >./protected/motto
mkdir protected/haystack
for i in {1..100}; do
touch "./protected/haystack/hay$i"
done
touch ./protected/haystack/needle
echo "Liberty in every line." >./protected/haystack/needle
rm -rf ./openers
mkdir openers
make -C ./opener || (
echo "Could not make the opener program."
exit 1
)
for i in {1..12}; do
cp ./opener/opener "./openers/opener$i"
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
done
# set up the fake-zenity
@@ -14,60 +34,215 @@ PATH="$(realpath ./mock/):$PATH"
# mount the filesystem
echo "Run $(date -u +%Y-%m-%dT%H:%M:%S) "
valgrind -s ../build/icfs -o default_permissions ./protected &
if [[ $1 == "--setuid" ]]; then
echo "Setting the setuid bit..."
echo "root privilieges are required to create a special user and set correct ownership of the executable."
id -u icfs &>/dev/null || sudo useradd --system --user-group icfs
sudo chown icfs: ../build/icfs && sudo chmod 4777 ../build/icfs
chmod g+w . # needed for icfs to be able to create the database
echo "Valgrind will not be used due to setuid compatibility issues."
../build/icfs -o default_permissions ./protected ./.pt.db &
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
echo "Database protection will not be tested due to the lack of setuid capabilites."
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\|SQLite" &
valgrind --leak-check=full --show-leak-kinds=all -s ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5
fi
sleep 1
#valgrind -s ../build/icfs -o default_permissions ./protected &
# Try to touch files in the directory
echo "[ICFS-TEST]: You may see memory errors from valgrind at this stage. This is normal - memory isn't lost, the libfuse just forked the process."
#echo \"manual\" >./protected/manual
# WARN: please don't use `>` or `>>` operators. They force **this script** to open the file, **not the program you are trying to run**. This is probably not what you mean when you want to test a specific program's access.
# WARN: avoid using touch, since it generates errors because setting times is not implemented in icfs **yet**.
zenity --set-fake-response no
echo "first" >./protected/first 2>/dev/null &&
echo "[ICFS-TEST]: echo can create protected/first despite access being denied!" ||
# create files
icfs_dialogue --set-fake-response no
truncate -s 0 ./protected/should-exist-anyway 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK
icfs_dialogue --set-fake-response yes
truncate -s 0 ./protected/should-exist 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK
# write to files
icfs_dialogue --set-fake-response no
sed -e 'a\'"Linux is a cancer that attaches itself in an intellectual property sense to everything it touches." "./protected/truth" 2>/dev/null &&
echo "[ICFS-TEST]: echo can write to protected/lie despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp
echo "second" >./protected/second 2>/dev/null &&
icfs_dialogue --set-fake-response yes
sed -e 'a\'"Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something." "./protected/truth" 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot create protected/second despite access being permitted!" # OK
echo "[ICFS-TEST]: echo cannot write to protected/truth despite access being permitted!" # OK
# Test whether permissons work
# Read files
zenity --set-fake-response yes_tmp
cat ./protected/first >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: cat can read a non-existant file ./protected/first!" ||
echo "[ICFS-TEST]: OK" # ENOENT
icfs_dialogue --set-fake-response no
cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: cat can read protected/this-only despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp
cat ./protected/second >/dev/null 2>/dev/null &&
icfs_dialogue --set-fake-response yes
cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: cat cannot open protected/second despite access being permitted!" # "second"
echo "[ICFS-TEST]: echo cannot create protected/this-only despite access being permitted!" # "Free code, free world."
zenity --set-fake-response yes_tmp
cat ./protected/this-only >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot create protected/second despite access being permitted!" # "Free code, free world."
# remove files
#parallel ::: "cat ./protected/sudo-only > /dev/null 2> /dev/null \
# && echo \"[ICFS-TEST]: cat can access files owned by root!\" \
# || echo \"[ICFS-TEST]: OK\"" # EACCESS
# test the removal
zenity --set-fake-response no
icfs_dialogue --set-fake-response no
rm ./protected/do-not-remove >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: rm can unlink protected/do-not-remove despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp
icfs_dialogue --set-fake-response yes
rm ./protected/should-be-removed >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: rm cannot unlink protected/should-be-removed despite access being permitted!" # OK
# rename files
icfs_dialogue --set-fake-response no
mv ./protected/do-not-rename ./protected/terrible-name 2>/dev/null &&
echo "[ICFS-TEST]: mv can rename protected/truth despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes
mv ./protected/should-be-renamed ./protected/great-name 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: mv cannot rename should-be-removed to renamed-file despite access being permitted!" # OK
# change permissions
icfs_dialogue --set-fake-response no
chmod 000 ./protected/perm777 2>/dev/null &&
echo "[ICFS-TEST]: chmod can change permissions of protected/perm777 despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes
chmod 000 ./protected/perm000 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: chmod cannot change permissions of protected/perm000 despite access being permitted!" # OK
# test permanent permissions
icfs_dialogue --set-fake-response yes_perm
openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener1 cannot read protected/motto despite access being permitted!" # OK
icfs_dialogue --set-fake-response no # this should be ignored
openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener1 cannot read protected/motto despite access being permitted!" # OK
icfs_dialogue --set-fake-response no # this should be ignored
openers/symlinked_opener1 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/symlinked_opener1 cannot read protected/motto despite access being permitted!" # OK
icfs_dialogue --set-fake-response no_perm
openers/opener2 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener2 can read protected/motto despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes # this should be ignored
openers/opener2 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener2 can read protected/motto despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes # this should be ignored
openers/symlinked_opener2 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/symlinked_opener2 can read protected/motto despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
# test permission globbing
icfs_dialogue --set-fake-response yes
icfs_dialogue --set-fake-response-filename "/"
openers/opener3 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener3 cannot read protected/haystack/needle despite access being permitted!" # OK
icfs_dialogue --set-fake-response no
icfs_dialogue --set-fake-response-filename "/"
openers/opener4 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener4 can read files in protected/haystack despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes_perm
icfs_dialogue --set-fake-response-filename "/"
openers/opener5 ./protected/haystack/needle >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener5 cannot read protected/haystack/needle despite access being permitted!" # OK
icfs_dialogue --set-fake-response no # this should be ignored
openers/opener5 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener5 cannot read files in protected/haystack despite access being permitted!" # OK
icfs_dialogue --set-fake-response no_perm
icfs_dialogue --set-fake-response-filename "/"
openers/opener6 ./protected/haystack/needle >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener6 can read protected/haystack/needle despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes # this should be ignored
openers/opener6 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener6 can read files in protected/haystack despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
# test database access
if [[ $1 == '--setuid' ]]; then
if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then
echo "[ICFS-TEST]: permanent permissions database is accessible!"
else
echo "[ICFS-TEST]: OK"
fi
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"
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
sleep 0.5
#lsof +f -- $(realpath ./protected)
umount $(realpath ./protected)
sleep 0.5
umount "$(realpath ./protected)"
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