26 Commits

Author SHA1 Message Date
6fd6907121 Tried making the perm permissions faster by perparing queries 2025-04-14 18:47:56 +02:00
1bc25af6f1 Added profiling logic to tests with perf 2025-04-14 18:47:32 +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
15 changed files with 703 additions and 111 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,7 @@ build/*
.clang-tidy .clang-tidy
.cache .cache
test/protected/* test/protected/*
test/.pt.db
compile_commands.json compile_commands.json
test/perf*
test/callgraph*

View File

@@ -12,7 +12,7 @@ CXX := g++
# dependencies # dependencies
PACKAGE_NAMES := fuse3 PACKAGE_NAMES := fuse3 sqlite3
ifeq ($(TEST), 1) ifeq ($(TEST), 1)
# PACKAGE_NAMES += check # TODO: use check? # PACKAGE_NAMES += check # TODO: use check?
@@ -21,14 +21,14 @@ endif
# set up cflags and libs # set up cflags and libs
CFLAGS := -D_FILE_OFFSET_BITS=64 CFLAGS := -D_FILE_OFFSET_BITS=64 -g
LDFLAGS := LDFLAGS :=
CFLAGS += $(shell pkg-config --cflags $(PACKAGE_NAMES)) 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 += -O0 -pedantic -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 \
@@ -43,7 +43,7 @@ endif
# set up targets # set up targets
TARGETS := icfs TARGETS := $(BUILD_DIR)/icfs
ifeq ($(TEST), 1) ifeq ($(TEST), 1)
TARGETS += icfs_test TARGETS += icfs_test
@@ -56,12 +56,11 @@ default: $(TARGETS)
.PHONY: clean .PHONY: clean
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)/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
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs $(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 icfs_test: $(BUILD_DIR)/icfs
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs_test cd ./test && ./test.bash
# $(BUILD_DIR)/icfs_test # TODO: implement testing
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c $(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
@@ -81,5 +80,9 @@ $(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h
$(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h $(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ $(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 $@
clean: clean:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs* rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs*

View File

@@ -11,6 +11,7 @@
See the file LICENSE. See the file LICENSE.
*/ */
#include <stddef.h>
#define FUSE_USE_VERSION 31 #define FUSE_USE_VERSION 31
#define _GNU_SOURCE #define _GNU_SOURCE
@@ -39,23 +40,58 @@
#include "sourcefs.h" #include "sourcefs.h"
#include "ui-socket.h" #include "ui-socket.h"
// TODO: move this to other file
const char *get_process_name_by_pid(const int pid) { const char *get_process_name_by_pid(const int pid) {
char *name = (char *)calloc(1024, sizeof(char)); char path[1024];
if (name) { sprintf(path, "/proc/%d/exe", pid);
sprintf(name, "/proc/%d/cmdline", pid);
FILE *f = fopen(name, "r"); char *name = realpath(path, NULL);
if (f) { if (name == NULL) {
size_t size; fprintf(stderr, "Could not get process name by pid %d", pid);
size = fread(name, sizeof(char), 1024, f); perror("");
if (size > 0) { }
if ('\n' == name[size - 1])
name[size - 1] = '\0'; /*
} size_t namelen = 32;
fclose(f); ssize_t readret = 0;
char *name = NULL;
while (namelen >= (size_t)readret && readret > 0) {
namelen *= 2;
name = calloc(namelen, sizeof(char));
if (name == NULL) {
free(path);
fprintf(stderr, "Could not get get process name by pid %d", pid);
perror("");
return NULL;
}
readret = readlink(path, name, namelen);
if (readret < 0) {
free(name);
free(path);
fprintf(stderr, "Couldn't get process name by pid %d", pid);
perror("");
return NULL;
}
if (namelen >= (size_t)readret) {
free(name);
} }
} }
*/
return name; return name;
/*
FILE *file = fopen(path, "r");
if (file) {
size_t size = 0;
size = fread(path, sizeof(char), 1024, file);
if (size > 0) {
if ('\n' == path[size - 1]) {
path[size - 1] = '\0';
}
}
fclose(file);
}
*/
} }
// TODO: move this somewhere else // TODO: move this somewhere else
@@ -70,8 +106,8 @@ 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 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 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;
// cfg->parallel_direct_writes = 1; cfg->parallel_direct_writes = 1;
/* 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
@@ -83,18 +119,19 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
cfg->entry_timeout = 0; cfg->entry_timeout = 0;
cfg->attr_timeout = 0; cfg->attr_timeout = 0;
cfg->negative_timeout = 0; cfg->negative_timeout = 0;
fprintf(stderr, "%d\n", getpid());
return NULL; return NULL;
} }
static int xmp_getattr(const char *path, struct stat *stbuf, static int xmp_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi) { struct fuse_file_info *file_info) {
int res; int res;
(void)path; (void)path;
if (fi) if (file_info)
res = fstat(fi->fh, stbuf); res = fstat(file_info->fh, stbuf);
else else
res = source_stat(path, stbuf); res = source_stat(path, stbuf);
if (res == -1) { if (res == -1) {
@@ -106,17 +143,39 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
} }
static int xmp_access(const char *path, int mask) { static int xmp_access(const char *path, int mask) {
int res; int res = -1;
res = access(path, mask); // if mask is F_OK, then we don't need to check the permissions
if (res == -1) // (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(real_filename(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 -errno;
}
return 0; return 0;
} }
static int xmp_readlink(const char *path, char *buf, size_t size) { static int xmp_readlink(const char *path, char *buf, size_t size) {
int res; int res = -1;
res = readlink(path, buf, size - 1); res = readlink(path, buf, size - 1);
if (res == -1) if (res == -1)
@@ -243,28 +302,28 @@ static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) {
*/ */
static int xmp_mkdir(const char *path, mode_t mode) { static int xmp_mkdir(const char *path, mode_t mode) {
int res; int res = -1;
res = source_mkdir(path, mode); res = source_mkdir(path, mode);
if (res == -1) if (res == -1) {
return -errno; return -errno;
}
return 0; return 0;
} }
static int xmp_unlink(const char *path) { static int xmp_unlink(const char *path) {
int res; int res = -1;
struct process_info pi; struct process_info pi;
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.PID = fc->pid;
pi.UID = fc->uid;
pi.name = get_process_name_by_pid(pi.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(real_filename(path), pi)) { if (!interactive_access(real_filename(path), pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@@ -304,6 +363,29 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
if (flags) if (flags)
return -EINVAL; return -EINVAL;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(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(real_filename(to), pi, 0)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
res = source_rename(from, to); res = source_rename(from, to);
if (res == -1) if (res == -1)
return -errno; return -errno;
@@ -313,6 +395,21 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
static int xmp_link(const char *from, const char *to) { static int xmp_link(const char *from, const char *to) {
int res; int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(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); res = source_link(from, to);
if (res == -1) if (res == -1)
@@ -323,6 +420,19 @@ 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) { static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
int res; int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(path), pi, 0)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
if (fi) if (fi)
res = fchmod(fi->fh, mode); res = fchmod(fi->fh, mode);
@@ -334,9 +444,26 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
return 0; 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, static int xmp_chown(const char *path, uid_t uid, gid_t gid,
struct fuse_file_info *fi) { struct fuse_file_info *fi) {
int res; int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.name = get_process_name_by_pid(pi.PID);
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(path), pi, 0)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
if (fi) if (fi)
res = fchown(fi->fh, uid, gid); res = fchown(fi->fh, uid, gid);
@@ -382,17 +509,16 @@ static int xmp_utimens(const char *path, const struct timespec ts[2],
static int xmp_create(const char *path, mode_t mode, static int xmp_create(const char *path, mode_t mode,
struct fuse_file_info *fi) { struct fuse_file_info *fi) {
int fd; int fd = -1;
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.PID = fc->pid;
pi.UID = fc->uid;
pi.name = get_process_name_by_pid(pi.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(real_filename(path), pi)) { if (!interactive_access(real_filename(path), pi, GRANT_PERM)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@@ -413,11 +539,10 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
struct fuse_context *fc = fuse_get_context(); struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid; pi.PID = fc->pid;
pi.UID = fc->uid;
pi.name = get_process_name_by_pid(pi.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(real_filename(path), pi)) { if (!interactive_access(real_filename(path), pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@@ -660,7 +785,7 @@ static off_t xmp_lseek(const char *path, off_t off, int whence,
static const struct fuse_operations xmp_oper = { static const struct fuse_operations xmp_oper = {
.init = xmp_init, .init = xmp_init,
.getattr = xmp_getattr, .getattr = xmp_getattr,
// .access = xmp_access, .access = xmp_access,
.readlink = xmp_readlink, .readlink = xmp_readlink,
.opendir = xmp_opendir, .opendir = xmp_opendir,
.readdir = xmp_readdir, .readdir = xmp_readdir,
@@ -676,7 +801,7 @@ static const struct fuse_operations xmp_oper = {
.chown = xmp_chown, .chown = xmp_chown,
.truncate = xmp_truncate, .truncate = xmp_truncate,
#ifdef HAVE_UTIMENSAT #ifdef HAVE_UTIMENSAT
// .utimens = xmp_utimens, // .utimens = xmp_utimens,
#endif #endif
.create = xmp_create, .create = xmp_create,
.open = xmp_open, .open = xmp_open,

View File

@@ -15,9 +15,10 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include <fuse3/fuse.h> #include <fuse3/fuse.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "fuse_operations.h" #include "fuse_operations.h"
#include "sourcefs.h" #include "sourcefs.h"
@@ -26,25 +27,35 @@
const char *mountpoint = NULL; const char *mountpoint = NULL;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Usage: icfs <FUSE arguments> [target directory] [path to "
"the permanent permissions database\n");
return EXIT_FAILURE;
}
// if umask != 0, the filesystem will create files with more restrictive
// permissions than it's caller reqested
umask(0); umask(0);
mountpoint = realpath(argv[argc - 1], NULL); // 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) {
fprintf(stderr, "Could not initalize ui-socket.\n");
exit(EXIT_FAILURE);
}
int ret = source_init(mountpoint); mountpoint = realpath(argv[argc - 2], NULL);
ret = source_init(mountpoint);
if (ret != 0) { if (ret != 0) {
perror("source_init"); perror("source_init");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
ret = init_ui_socket(); ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL);
if (ret != 0) {
perror("init_ui_socket");
exit(EXIT_FAILURE);
}
ret = fuse_main(argc, argv, get_fuse_operations(), NULL); free((void *)mountpoint);
free(mountpoint);
destroy_ui_socket(); destroy_ui_socket();
return ret; return ret;
} }

View File

@@ -0,0 +1,303 @@
/*
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 "process_info.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 = 2;
const char *const schema[] = {"executable", "filename"};
const char *const types[] = {"TEXT", "TEXT"};
uid_t ruid, euid, current_pid;
sqlite3_stmt *perm_check_statement = NULL;
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
void set_db_fsuid() {
pthread_mutex_lock(&uid_switch);
if (current_pid == ruid)
return;
int status = -1;
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "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_pid == ruid)
return;
int status = -1;
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "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, "Unexpected amount of arguments given to the callback.\n");
return 1;
}
int column_num = atoi(argv[0]);
if (column_num >= column_count) {
fprintf(stderr, "Table contains more columns than expected.\n");
return 1;
}
if (strcmp(schema[column_num], argv[1]) == 0 &&
strcmp(types[column_num], argv[2]) == 0) {
return 0;
}
fprintf(stderr, "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)argc;
(void)argv;
(void)colname;
*(int *)flag = 1;
return 0;
}
int create_database_schema() {
fprintf(stderr, "Creating table 'permissions'.\n");
const char *create_query = "CREATE TABLE permissions(executable TEXT NOT "
"NULL, filename TEXT NOT NULL);";
char *err = NULL;
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "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, "Table '%s' does not exist.\n", table_name);
if (create_database_schema()) {
fprintf(stderr, "Table could not be created.\n");
return 1;
}
return 0;
} else if (result != SQLITE_OK) {
fprintf(stderr, "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, "sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "Schema is correct.\n");
return 0;
}
int prepare_sql_queries() {
const char *query_template =
"SELECT * FROM %s WHERE executable = ? AND filename = ?;";
char *query_string = NULL;
int query_len = snprintf(NULL, 0, query_template, table_name) + 1;
if (query_len < 0) {
fprintf(stderr, "Failed to prepare statement");
perror("");
return 1;
}
query_string = malloc(query_len);
if (query_string == NULL) {
fprintf(stderr, "Failed to allocate memory for the query");
perror("");
return 1;
}
int ret = snprintf(query_string, query_len, query_template, table_name);
if (ret < 0) {
fprintf(stderr, "Failed to prepare statement");
perror("");
free(query_string);
return 1;
}
if (sqlite3_prepare_v2(perm_database, query_string, -1, &perm_check_statement,
NULL) != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s\n",
sqlite3_errmsg(perm_database));
free(query_string);
return 1;
}
free(query_string);
return 0;
}
void free_sql_queries(void) { sqlite3_finalize(perm_check_statement); }
/**
* 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, "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("Can't open permanent permissions database");
return -1;
}
umask(0);
if (ensure_database_schema()) {
fprintf(stderr, "Database schema is not correct.\n");
return -1;
}
int status = seteuid(ruid);
if (status < 0) {
fprintf(stderr, "Couldn't set euid to ruid.\n");
exit(status);
}
if (prepare_sql_queries()) {
fprintf(stderr, "Couldn't prepare sql queries.\n");
exit(status);
}
return 0;
}
/**
* Destroys the permanent permissions table.
*/
void destroy_perm_permissions_table(void) {
free_sql_queries();
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
* @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) {
size_t query_len =
56 + strlen(table_name) + strlen(filename) + strlen(pi.name);
const char *query = malloc(query_len);
size_t should_be_written = snprintf(
query, query_len,
"SELECT * FROM %s WHERE executable = \'%s\' AND filename = \'%s\';",
table_name, pi.name, filename);
// -1 for the \0
if (should_be_written != query_len - 1) {
fprintf(stderr,
"Unexpected query size while permanent access rule check: "
"Expected %lu, but snprintf returned %lu. The query: %s\n",
query_len, should_be_written, query);
return 0;
}
char *sqlite_error = NULL;
int flag = 0;
int ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
free(query);
return 0;
}
free(query);
return flag;
}
/**
* 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 give_perm_access(const char *filename, struct process_info pi) {
size_t query_len =
30 + strlen(table_name) + strlen(filename) + strlen(pi.name);
const char *query = malloc(query_len);
size_t should_be_written =
snprintf(query, query_len, "INSERT INTO %s VALUES (\'%s\', \'%s\');",
table_name, pi.name, filename);
// -1 for the \0
if (should_be_written != query_len - 1) {
fprintf(stderr,
"Unexpected query size while permanent access rule insertion: "
"Expected %lu, but snprintf returned %lu\n",
query_len, should_be_written);
return 1;
}
char *sqlite_error = NULL;
int ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
free(query);
return 1;
}
free(query);
return 0;
}

View File

@@ -0,0 +1,45 @@
/*
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 "process_info.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
* @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);
/**
* 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 give_perm_access(const char *filename, struct process_info pi);
#endif // #ifdef PERM_PERMISSION_TABLE_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 PROCESS_INFO_H #ifndef PROCESS_INFO_H
#define PROCESS_INFO_H #define PROCESS_INFO_H
@@ -6,7 +13,6 @@
struct process_info { struct process_info {
pid_t PID; pid_t PID;
const char *name; const char *name;
uid_t UID;
}; };
#endif // PROCESS_INFO_H #endif // PROCESS_INFO_H

View File

@@ -33,6 +33,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);
perror("");
return -1; return -1;
} }
@@ -66,6 +68,11 @@ int source_symlink(const char *target, const char *linkpath) {
return symlinkat(target, handle.root_fd, relative_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) { 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);

View File

@@ -47,6 +47,8 @@ int source_chown(const char *filename, uid_t owner, gid_t group);
int source_truncate(const char *filename, off_t length); 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 /* `open` and `create` are designed to correspond to fuse operations, not the
* libc's `open(2)`. Both of them actually call `openat`. */ * libc's `open(2)`. Both of them actually call `openat`. */

View File

@@ -75,6 +75,7 @@ unsigned long long get_process_creation_time(pid_t pid) {
int init_temp_permissions_table() { int init_temp_permissions_table() {
pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT);
init(&temp_permissions_table); init(&temp_permissions_table);
return 0;
} }
/** /**
@@ -225,7 +226,6 @@ int give_temp_access(const char *filename, struct process_info pi) {
push(&new_permission_entry.allowed_files, strdup(filename)); push(&new_permission_entry.allowed_files, strdup(filename));
insert(&temp_permissions_table, pi.PID, new_permission_entry); insert(&temp_permissions_table, pi.PID, new_permission_entry);
printf("temp_permissions_table size: %ld\n", size(&temp_permissions_table));
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_mutex_unlock(&temp_permissions_table_lock);
return 0; return 0;

View File

@@ -10,10 +10,9 @@
#include <sys/types.h> #include <sys/types.h>
#include <time.h> #include <time.h>
#define _GNU_SOURCE #define _GNU_SOURCE
#include "cc.h" #include "perm_permissions_table.h"
#include "temp_permissions_table.h" #include "temp_permissions_table.h"
#include "ui-socket.h" #include "ui-socket.h"
#include <errno.h>
#include <pthread.h> #include <pthread.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -22,11 +21,20 @@
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
int init_ui_socket() { #define ZENITY_TEMP_ALLOW_MESSAGE "Allow this time\n"
char line[256];
FILE *fp;
init_temp_permissions_table(); int init_ui_socket(const char *perm_permissions_db_filename) {
FILE *fp = NULL;
if (init_temp_permissions_table()) {
fprintf(stderr, "Could not initialize temporary permissions table.\n");
return 1;
}
if (init_perm_permissions_table(perm_permissions_db_filename)) {
fprintf(stderr, "Could not initialize permanent permissions table.\n");
return 1;
}
// Test if Zenity is installed (get version) // Test if Zenity is installed (get version)
fp = popen("zenity --version", "r"); fp = popen("zenity --version", "r");
@@ -35,34 +43,34 @@ int init_ui_socket() {
return 1; return 1;
} }
while (fgets(line, sizeof(line), fp))
printf("%s", line);
pclose(fp); pclose(fp);
return 0; return 0;
} }
void destroy_ui_socket() { destroy_temp_permissions_table(); } void destroy_ui_socket(void) {
destroy_temp_permissions_table();
destroy_perm_permissions_table();
}
/** /**
* Asks the user if the process should be allowed to access the file using the * Asks the user if the process should be allowed to access the file using the
* GUI * GUI
* *
* @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: 0 if access is denied, 1 if access is allowed, 2 if access is allwed * @return: 0 if access is denied, 1 if access is allowed, 2 if access is
* for the runtime of the process * allowed for the runtime of the process
*/ */
int ask_access(const char *filename, struct process_info pi) { int ask_access(const char *filename, struct process_info proc_info) {
FILE *fp = NULL;
FILE *fp;
size_t command_len = size_t command_len =
139 + sizeof(pid_t) * 8 + strlen(pi.name) + strlen(filename); 139 + sizeof(pid_t) * 8 + strlen(proc_info.name) + strlen(filename);
char *command = (char *)malloc(command_len); char *command = (char *)malloc(command_len);
snprintf(command, command_len, snprintf(command, command_len,
"zenity --question --extra-button \"Allow this time\" --title " "zenity --question --extra-button \"Allow this time\" --title "
"\"Allow Access?\" --text \"Allow process " "\"Allow Access?\" --text \"Allow process "
"<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"", "<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"",
pi.name, pi.PID, filename); proc_info.name, proc_info.PID, filename);
// Zenity Question Message Popup // Zenity Question Message Popup
fp = popen(command, "r"); fp = popen(command, "r");
@@ -76,16 +84,17 @@ int ask_access(const char *filename, struct process_info pi) {
// if the user clicks the "Allow this time" button, `zenity` will only // 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 // write it to `stdout`, but the exit code will still be `1`. So, we need
// to manually check the output. // to manually check the output.
char buffer[1024]; char buffer[sizeof(ZENITY_TEMP_ALLOW_MESSAGE) + 1];
while (fgets(buffer, sizeof(buffer), fp)) { while (fgets(buffer, sizeof(buffer), fp)) {
printf("%s", buffer); printf("%s", buffer);
if (strcmp(buffer, "Allow this time\n") == 0) { if (strcmp(buffer, ZENITY_TEMP_ALLOW_MESSAGE) == 0) {
pclose(fp); pclose(fp);
return 2; return 2;
} }
} }
int zenity_exit_code = WEXITSTATUS(pclose(fp)); int zenity_exit_code = WEXITSTATUS(pclose(fp));
fprintf(stderr, "zenity returned %d\n", zenity_exit_code);
// zenity returns 1 on "No" >:( // zenity returns 1 on "No" >:(
if (zenity_exit_code == 0) { if (zenity_exit_code == 0) {
return 1; return 1;
@@ -94,22 +103,6 @@ int ask_access(const char *filename, struct process_info pi) {
return 0; return 0;
} }
/**
* 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;
}
int give_perm_access(const char *filename, struct process_info pi) {
perror("Not implemented");
return -1;
}
/** /**
* Check access according to: * Check access according to:
* 1. temp permission table * 1. temp permission table
@@ -118,23 +111,40 @@ int give_perm_access(const char *filename, struct process_info pi) {
* *
* @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 * @pram pi: The process information
* @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 pi) { int interactive_access(const char *filename, struct process_info proc_info,
int opts) {
if (check_temp_access(filename, pi) || check_perm_access(filename, pi)) { if (check_temp_access(filename, proc_info) ||
check_perm_access(filename, proc_info)) {
// access was already granted before // access was already granted before
return 1; return 1;
} }
int user_response = ask_access(filename, pi); // if noth GRANT_TEMP and GRANT_PERM are selected, then only permanent
// permissions are granted
if (opts & GRANT_PERM) {
give_perm_access(filename, proc_info);
return 1;
}
if (opts & GRANT_TEMP) {
give_temp_access(filename, proc_info);
return 1;
}
int user_response = ask_access(filename, proc_info);
if (user_response == 1) { if (user_response == 1) {
// user said "yes" // user said "yes"
give_perm_access(filename, pi); give_perm_access(filename, proc_info);
return 1; return 1;
} else if (user_response == 2) { }
if (user_response == 2) {
// user said "yes, but only this time" // user said "yes, but only this time"
give_temp_access(filename, pi); give_temp_access(filename, proc_info);
return 1; return 1;
} }

View File

@@ -21,7 +21,7 @@
* *
* @return: 0 on success, -1 on faliure. * @return: 0 on success, -1 on faliure.
*/ */
int init_ui_socket(void); int init_ui_socket(const char *perm_permissions_db_filename);
/** /**
* Close the GUI communication. * Close the GUI communication.
@@ -36,8 +36,13 @@ void destroy_ui_socket(void);
* *
* @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 * @pram pi: The process information
* @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 pi); 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 #endif // !UI_SOCKET_H

View File

@@ -12,12 +12,19 @@ else
if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then if [[ $FAKE_ZENITY_RESPONSE == "yes_tmp" ]]; then
printf "Allow this time\n" printf "Allow this time\n"
exit 1 exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "yes_tmp_alt" ]]; then
printf "Allow this time\n"
echo "yes_alt" >~/.fake_zenity_response
exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then
exit 1 exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then
exit 0 exit 0
elif [[ $FAKE_ZENITY_RESPONSE == "yes_alt" ]]; then
echo "yes_tmp_alt" >~/.fake_zenity_response
exit 0
fi fi
fi fi
fi fi
exit -1 # TODO: call actual zenity here exit 255 # TODO: call actual zenity here

View File

@@ -2,9 +2,10 @@
# clean what was left from previous tests # clean what was left from previous tests
rm -f ./.pt.db
rm -rf ./protected rm -rf ./protected
mkdir protected mkdir protected
touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth ./protected/perm000 ./protected/perm777 ./protected/this-name-is-wrong 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 chmod 777 ./protected/perm777 ./protected/perm000
echo "Free code, free world." >./protected/motto echo "Free code, free world." >./protected/motto
@@ -15,31 +16,56 @@ PATH="$(realpath ./mock/):$PATH"
# mount the filesystem # mount the filesystem
echo "Run $(date -u +%Y-%m-%dT%H:%M:%S) " 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 == "--perf" ]]; then
echo "Profiling with perf..."
../build/icfs -o default_permissions ./protected ./.pt.db &
echo "Profiling will require root privilieges."
sleep 3
echo "Attaching to $(pgrep icfs)"
sudo perf record -g -e cycles:u --call-graph dwarf -p $(pgrep icfs) &
sleep 10
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 ./protected ./.pt.db &
sleep 5
fi
sleep 1 #valgrind -s ../build/icfs -o default_permissions ./protected &
# 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**.
# create files # create files
zenity --set-fake-response no zenity --set-fake-response no
touch ./protected/should-not-exist 2>/dev/null && truncate -s 0 ./protected/should-exist-anyway 2>/dev/null &&
echo "[ICFS-TEST]: touch can create protected/should-not-exist despite access being denied!" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: OK" # EACCESS echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK
zenity --set-fake-response yes_tmp zenity --set-fake-response yes_tmp
touch ./protected/should-exist 2>/dev/null && truncate -s 0 ./protected/should-exist 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: touch cannot create protected/should-exist despite access being permitted!" # OK echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # OK
# write to files # write to files
zenity --set-fake-response no zenity --set-fake-response no
echo "Linux is a cancer that attaches itself in an intellectual property sense to everything it touches." >./protected/truth 2>/dev/null && 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]: echo can write to protected/lie despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp zenity --set-fake-response yes_tmp
echo "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 && 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]: OK" ||
echo "[ICFS-TEST]: echo cannot write to protected/truth despite access being permitted!" # OK echo "[ICFS-TEST]: echo cannot write to protected/truth despite access being permitted!" # OK
@@ -70,11 +96,11 @@ rm ./protected/should-be-removed >/dev/null 2>/dev/null &&
# rename files # rename files
zenity --set-fake-response no zenity --set-fake-response no
mv ./protected/truth ./protected/lie 2>/dev/null && 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]: mv can rename protected/truth despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp zenity --set-fake-response yes_tmp
mv ./protected/this-name-is-wrong ./protected/this-name-is-correct 2>/dev/null && mv ./protected/should-be-renamed ./protected/great-name 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: mv cannot rename should-be-removed to renamed-file despite access being permitted!" # OK echo "[ICFS-TEST]: mv cannot rename should-be-removed to renamed-file despite access being permitted!" # OK
@@ -89,9 +115,48 @@ chmod 000 ./protected/perm000 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: chmod cannot change permissions of protected/perm000 despite access being permitted!" # OK echo "[ICFS-TEST]: chmod cannot change permissions of protected/perm000 despite access being permitted!" # OK
# test permanent permissions
zenity --set-fake-response yes
cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK
zenity --set-fake-response no # this should be ignored
cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK
# test database access
if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then
echo "[ICFS-TEST]: permanent permissions is accessible!"
else
echo "[ICFS-TEST]: OK"
fi
if [[ $1 == "--perf" ]]; then
zenity --set-fake-response yes_tmp
rm -rf ./protected/*
zenity --set-fake-response yes_alt
bonnie++ -p 4
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null &
bonnie++ -d ./protected -c 4 -r 256 -y s >/dev/null
bonnie++ -p -1
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 0.5
if [[ $1 == "--perf" ]]; then
mv ./callgraph.png ./callgraph_old.png
real_user=$USER
sudo chown "$real_user" ./perf.data
perf script --dsos=icfs | gprof2dot -f perf | dot -Tpng -o callgraph.png
echo "Profile graph was written to \"callgraph.png\""
fi