14 Commits

Author SHA1 Message Date
79e9f2fb9a Fixed permission on access problem. 2025-06-24 13:18:27 +02:00
91621605b1 Fixed buffer overflow 2025-06-23 20:56:03 +02:00
ea2e41693c Fixed code errors 2025-06-23 20:47:54 +02:00
4909a35a6a Add utime operations 2025-06-12 08:08:34 +02:00
eec782057c Fixed Makefile typo 2025-06-11 21:06:49 +02:00
29ce8f6cff Fixed Makefile typo 2025-06-11 21:05:02 +02:00
cbe2bf81eb Fixed wrong permissions on the dialogue executable 2025-06-11 21:03:36 +02:00
0a6faab7d7 Fixed wrong Debian adwaita package name 2025-06-11 20:44:56 +02:00
01e72e4aac Fixed README typo 2025-06-11 20:27:19 +02:00
5700238509 Fixed dialog arguments error. 2025-06-11 20:16:34 +02:00
b3e71d13aa Fixed dialogue arguments error. 2025-06-11 20:00:16 +02:00
012fa05e8f Updated README 2025-06-11 19:59:28 +02:00
2ebc450132 Fixed Makefile errors 2025-06-11 18:11:14 +02:00
2f4f1a0a56 Updated readme 2025-05-23 20:52:27 +02:00
9 changed files with 172 additions and 40 deletions

View File

@@ -70,10 +70,10 @@ default: $(TARGETS)
.PHONY: clean icfs_test clean-icfs clean-icfs_dialogue install uninstall
$(BUILD_DIR):
if [[ ! -d "FILE" ]]; then mkdir $(BUILD_DIR) fi
if [[ ! -d "$(BUILD_DIR)" ]]; then mkdir $(BUILD_DIR); fi
$(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)
make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(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
@@ -126,7 +126,7 @@ install: $(BUILD_DIR)/icfs $(BUILD_DIR)/icfs_dialogue
@printf "\t3. Set the setuid bit of icfs executable.\n"
sudo cp $(BUILD_DIR)/icfs /usr/bin/icfs && sudo cp $(BUILD_DIR)/icfs_dialogue /usr/bin/icfs_dialogue
id -u icfs &>/dev/null || sudo useradd --system --user-group icfs
sudo chown icfs: /usr/bin/icfs && sudo chmod 4777 /usr/bin/icfs
sudo chown icfs: /usr/bin/icfs && sudo chmod 4755 /usr/bin/icfs && sudo chmod 755 /usr/bin/icfs_dialogue
@read -p "Create /etc/icfs directory for permission databases [y/N]: " permd; if [[ $$permd == "y" ]]; then echo "sudo mkdir /etc/icfs && sudo chown :icfs /etc/icfs && sudo chmod g+rw,o= /etc/icfs;"; sudo mkdir /etc/icfs && sudo chown icfs:icfs /etc/icfs && sudo chmod g+rw,o= /etc/icfs; fi
uninstall:

View File

@@ -1,5 +1,9 @@
# ICFS -- Interactively Controlled File System
> [!IMPORTANT]
> If you need the version that correponds to the thesis attachment, go [here](https://git.umbrasolis.de/fedir/ICFS/src/commit/2f4f1a0a569704b770f50b3e7cf39c09b9b8381a). This version contains corrections of errors that the opponent brought to my attention.
## Motivation
Traditional access control mechanisms in operating systems allow the same level of access to all processes running on behalf of the same user. This typically enables malicious processes to read and/or modify all data accessible to the user running a vulnerable application. It can be dealt using various mandatory access control mechanisms, but these are often complicated to configure and are rarely used in common user oriented scenarios. This thesis focuses on design and implementation of a file system layer which delegates the decision to allow or deny access to a file system object by a specific process to the user.
@@ -15,6 +19,10 @@ Traditional access control mechanisms in operating systems allow the same level
- Install dependencies
- libfuse3
- Debian: `sudo apt install fuse3 libfuse3-dev`
- SQLite3
- Debian: `sudo apt install libsqlite3-dev`
- GTK4, libadwaita
- Debian: `sudo apt install libgtk-4-dev libadwaita-1-dev`
- Build tools
- Debian: `sudo apt install gcc make pkg-config`
- Build using `make`:
@@ -32,7 +40,9 @@ Traditional access control mechanisms in operating systems allow the same level
## Usage
```
icfs <FUSE arguments> [target directory] [path to permanent permission database]
Usage: icfs <FUSE arguments> [target directory] [path to the permanent permissions database] <ICFS arguments>
--no-grant-on-create - do not give any access permissions on file creation(incompatible with --perm-on-create)
--perm-on-create - automatically give permanent access permission to files a process creates (incompatible with --no-grant-on-create)
```
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. If you have installed icfs along with `/etc/icfs` folder, you can create your permanent permission databases in this folder (you might want to do this, if your home folder does not have the "execute" permission for other users).
@@ -47,6 +57,30 @@ env PATH="$(realpath ./build):$PATH" build/icfs <FUSE arguments> [target directo
The `env PATH="$(realpath ./build):$PATH"` adds the access dialogue program to PATH, allowing ICFS to call it seamlessly.
#### Running tests
ICFS includes a testing script in the `test` directory.
You can run it **from `test` directory** by running:
```
./test.bash
```
All testing artifacts will be available in the appropriate folders after run. To test setuid capabilities too (**from `test` directory!!!**):
```
./test.bash --setuid
```
You can also test performance by adding `--performance` (**from `test` directory!!!**):
```
./test.bash --performance
```
***Important:*** **flags cannot be combined together (e.g. you can't add `--performance` and `--setuid`)**
## Docs
- [Initial idea and motivation](./docs/bc-thesis-idea.md)
@@ -55,6 +89,6 @@ The `env PATH="$(realpath ./build):$PATH"` adds the access dialogue program to P
## Credit
_Student:_ Fedir Kovalov
*Student:* Fedir Kovalov
_Supervisor:_ RNDr. Jaroslav Janáček, PhD.
*Supervisor:* RNDr. Jaroslav Janáček, PhD.

View File

@@ -48,6 +48,8 @@
int auto_create_perm = GRANT_TEMP;
#define HAVE_UTIMENSAT
/*
* Sets the default permission granted by file creation.
*
@@ -110,7 +112,11 @@ static int xmp_access(const char *path, int mask) {
// if mask is F_OK, then we don't need to check the permissions
// (is that possible?)
//
// EDIT: now lie to the program by not telling it whether it can actually
// access the file.
/*
if (mask != F_OK) {
struct process_info proc_info;
struct fuse_context *context = fuse_get_context();
@@ -127,6 +133,7 @@ static int xmp_access(const char *path, int mask) {
free((void *)proc_info.name);
}
*/
res = source_access(path, mask);
@@ -451,12 +458,21 @@ static int xmp_truncate(const char *path, off_t size,
static int xmp_utimens(const char *path, const struct timespec ts[2],
struct fuse_file_info *fi) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi = get_process_info(fc->pid);
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
/* don't use utime/utimes since they follow symlinks */
if (fi)
res = futimens(fi->fh, ts);
else
res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
res = source_utimens(path, ts, AT_SYMLINK_NOFOLLOW);
if (res == -1)
return -errno;
@@ -756,7 +772,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

@@ -119,7 +119,7 @@ static int on_command_line(GApplication *app, GApplicationCommandLine *cmdline,
argv = g_application_command_line_get_arguments(cmdline, &argc);
// Handle your arguments here
if (argc >= 4) {
if (argc >= 5) {
fprintf(stderr, "%s\n", argv[1]);
g_object_set_data_full(G_OBJECT(app), "accessing_pid", g_strdup(argv[1]),
g_free);
@@ -142,6 +142,13 @@ int main(int argc, char **argv) {
if (argc == 2 && strcmp(argv[1], "--version") == 0) {
fprintf(stdout, "icfs_dialogue 1.0.0");
return 0;
}
if (argc != 5) {
fprintf(stdout, "Usage: icfs_dialogue [accessing pid] [accessing name] "
"[root folder] [access dir]");
return 255;
}
// disable accessibility features to prevent attacks

View File

@@ -31,27 +31,27 @@ 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 "
"arguments>\n\t--no-grant-on-create - do not give any "
"access permissions on file creation"
"(incompatible with --temp-on-create)\n\t--perm-on-create "
"(incompatible with --perm-on-create)\n\t--perm-on-create "
"- automatically give permanent access permission to files "
"a process creates "
"(incompatible with --no-perm-on-create)\n");
"(incompatible with --no-grant-on-create)\n");
return EXIT_FAILURE;
}
if ((0 == strcmp(argv[argc - 1], "--no-perm-on-create") &&
if ((0 == strcmp(argv[argc - 1], "--no-grant-on-create") &&
0 == strcmp(argv[argc - 2], "--temp-on-create")) ||
(0 == strcmp(argv[argc - 2], "--no-perm-on-create") &&
(0 == strcmp(argv[argc - 2], "--no-grant-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 "
"arguments>\n\t--no-grant-on-create - do not give any "
"access permissions on file creation"
"(incompatible with --temp-on-create)\n\t--perm-on-create "
"(incompatible with --perm-on-create)\n\t--perm-on-create "
"- automatically give permanent access permission to files "
"a process creates "
"(incompatible with --no-perm-on-create)\n");
"(incompatible with --no-grant-on-create)\n");
return EXIT_FAILURE;
}
@@ -59,7 +59,7 @@ int main(int argc, char *argv[]) {
// permissions than it's caller reqested
umask(0);
if (0 == strcmp(argv[argc - 1], "--no-perm-on-create")) {
if (0 == strcmp(argv[argc - 1], "--no-grant-on-create")) {
set_auto_create_perm(0);
argc--;
}

View File

@@ -22,8 +22,8 @@
* Stores the root directory information to enable relative path operations.
*/
static struct source_files_handle {
const char *mountpoint; // Absolute path to the mounted filesystem root
int root_fd; // File descriptor for the root directory (O_PATH)
char *mountpoint; // Absolute path to the mounted filesystem root
int root_fd; // File descriptor for the root directory (O_PATH)
} handle;
/**
@@ -128,8 +128,7 @@ int source_mkdir(const char *filename, mode_t mode) {
/**
* @brief Remove a file from the filesystem
*
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove files relative
* to the root directory.
* Uses unlinkat() to safely remove files relative to the root directory.
*/
int source_unlink(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
@@ -277,3 +276,9 @@ int source_create(const char *filename, int flags, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return openat(handle.root_fd, relative_filename, flags | O_CREAT, mode);
}
int source_utimens(const char *filename, const struct timespec ts[2],
int flags) {
const char *relative_filename = source_filename_translate(filename);
return utimensat(handle.root_fd, relative_filename, ts, AT_SYMLINK_NOFOLLOW);
}

View File

@@ -138,4 +138,7 @@ int source_open(const char *filename, int flags);
*/
int source_create(const char *filename, int flags, mode_t mode);
int source_utimens(const char *filename, const struct timespec ts[2],
int flags);
#endif // !SOURCEFS_H

View File

@@ -7,6 +7,7 @@
*/
#include "access_t.h"
#include <signal.h>
#include <stddef.h>
#include <sys/types.h>
#include <time.h>
@@ -25,6 +26,7 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <wait.h>
// Exit status codes for icfs_dialogue process interaction
#define DIALOGUE_YES 1
@@ -96,30 +98,64 @@ void destroy_ui_socket(void) {
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);
struct dialogue_response response;
response.decision = DENY;
response.filename = NULL;
if (ret < 0) {
// Memory allocation failed - create minimal fallback response
fprintf(stderr, "[ICFS] Could not create query on rule insertion");
perror("");
// instead of popeen --------------
char pid_str[sizeof(pid_t) *
8]; // amount of bits should be enough for a decimal
snprintf(pid_str, sizeof(pid_str), "%d", proc_info.PID);
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("[ICFS] pipe returned a error");
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
}
// Execute permission dialogue
fp = popen(command, "r");
free(command);
pid_t pid = fork();
if (pid == -1) {
perror("[ICFS] fork returned a error");
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
}
if (pid == 0) {
// Child process
close(pipefd[0]); // Close read end
dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout
close(pipefd[1]); // Close original write end
// Prepare command and arguments
char *args[] = {"icfs_dialogue", // Command name (looked up in PATH)
pid_str,
(char *)proc_info.name,
(char *)get_mountpoint(),
(char *)filename,
NULL};
// Execute the command using execvp (uses PATH)
execvp("icfs_dialogue", args);
// If execvp fails
perror("execvp failed");
exit(EXIT_FAILURE);
}
// instead of popen ---------------
close(pipefd[1]); // Close write end
fp = fdopen(pipefd[0], "r");
if (fp == NULL) {
perror("[ICFS] Pipe returned a error");
perror("[ICFS] fdopen returned a error");
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
@@ -133,22 +169,45 @@ struct dialogue_response ask_access(const char *filename,
// Read entire command output
while (fgets(line, sizeof(line), fp)) {
push_fmt(&dialogue_output, line);
if (push_fmt(&dialogue_output, line) == NULL) {
cleanup(&dialogue_output);
perror("[ICFS] not enough memory for dialogue output.");
// kill the dialogue if it's still there
kill(pid, SIGQUIT);
fclose(fp);
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
}
}
int dialogue_exit_code = WEXITSTATUS(pclose(fp));
fclose(fp);
// Wait for the child to finish
int status;
if (waitpid(pid, &status, 0) == -1) {
cleanup(&dialogue_output);
perror("[ICFS] waitpid error");
// kill the dialogue if it is still there
kill(pid, SIGQUIT);
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
}
int dialogue_exit_code = WEXITSTATUS(status);
fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
// Handle empty output case
if (size(&dialogue_output) == 0) {
perror("[ICFS] empty dialogue output.");
push(&dialogue_output, '/');
}
// Validate string length consistency
assert(strlen(first(&dialogue_output)) == size(&dialogue_output));
// Allocate and copy final filename
response.filename = malloc(size(&dialogue_output) + 1);
strcpy(response.filename, first(&dialogue_output));

View File

@@ -54,24 +54,32 @@ int main(int argc, char *argv[]) {
}
// Construct the full path
char fullpath[PATH_MAX];
snprintf(fullpath, PATH_MAX, "%s/%s", path, entry->d_name);
char *fullpath = NULL;
if (asprintf(&fullpath, "%s/%s", path, entry->d_name) == -1 ||
fullpath == NULL) {
perror("asprintf");
success = 0;
break;
}
// 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;
free(fullpath);
break;
}
// Only process regular files
if (!S_ISREG(entry_stat.st_mode)) {
free(fullpath);
continue;
}
// Try to open and immediately close the file
int fd = open(fullpath, O_RDONLY);
free(fullpath);
if (fd == -1) {
perror("open");
success = 0;