Compare commits
14 Commits
22cb958b4f
...
main
Author | SHA1 | Date | |
---|---|---|---|
79e9f2fb9a
|
|||
91621605b1
|
|||
ea2e41693c
|
|||
4909a35a6a
|
|||
eec782057c
|
|||
29ce8f6cff
|
|||
cbe2bf81eb
|
|||
0a6faab7d7
|
|||
01e72e4aac
|
|||
5700238509
|
|||
b3e71d13aa
|
|||
012fa05e8f
|
|||
2ebc450132
|
|||
2f4f1a0a56
|
6
Makefile
6
Makefile
@@ -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:
|
||||
|
40
README.md
40
README.md
@@ -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.
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
18
src/main.c
18
src/main.c
@@ -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--;
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@
|
||||
* 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
|
||||
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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user