36 Commits

Author SHA1 Message Date
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
23 changed files with 4923 additions and 1031 deletions

10
.gitignore vendored
View File

@@ -3,4 +3,12 @@ build/*
.cache
test/protected/*
test/.pt.db
compile_commands.json
*compile_commands.json
test/perf*
test/callgraph*
test/openers
test/opener/opener
test/opener/opener.o
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++
@@ -49,12 +57,18 @@ 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
$(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
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs
@@ -71,7 +85,7 @@ $(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
@@ -83,6 +97,16 @@ $(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $
$(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
CLEAN_TARGETS=clean-icfs
clean:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/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))

6
src/access_t.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef ACCESS_T_H
#define ACCESS_T_H
typedef enum { DENY, ALLOW, ALLOW_TEMP, DENY_TEMP, NDEF } access_t;
#endif // !ACCESS_T_H

4769
src/cc.h

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,9 @@
See the file LICENSE.
*/
#include "real_filename.h"
#include <assert.h>
#include <stddef.h>
#define FUSE_USE_VERSION 31
#define _GNU_SOURCE
@@ -39,27 +42,59 @@
#include "sourcefs.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);
char *get_process_name_by_pid(const int pid) {
char path[1024];
sprintf(path, "/proc/%d/exe", pid);
char *name = realpath(path, NULL);
if (name == NULL) {
fprintf(stderr, "Could not get process name by pid %d", pid);
perror("");
}
/*
size_t namelen = 32;
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;
}
*/
// TODO: move this somewhere else
const char *real_filename(const char *filename) { return filename; }
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);
}
*/
}
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
(void)conn;
@@ -70,8 +105,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
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;
cfg->parallel_direct_writes = 1;
/* Pick up changes from lower filesystem right away. This is
also necessary for better hardlink support. When the kernel
@@ -83,18 +118,20 @@ 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);
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) {
@@ -106,38 +143,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;
// if mask is F_OK, then we don't need to check the permissions
// (is that possible?)
if (mask != F_OK) {
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
struct process_info proc_info;
struct fuse_context *context = fuse_get_context();
pi.PID = fc->pid;
pi.name = get_process_name_by_pid(pi.PID);
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), pi, 0)) {
free(pi.name);
if (!interactive_access(path, proc_info, 0)) {
free((void *)proc_info.name);
return -EACCES;
}
free(pi.name);
free((void *)proc_info.name);
}
res = source_access(path, mask);
if (res == -1)
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)
@@ -264,17 +302,18 @@ 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();
@@ -284,7 +323,7 @@ static int xmp_unlink(const char *path) {
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(path), pi, 0)) {
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -332,15 +371,14 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(from), pi, 0)) {
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(real_filename(to), pi, 0)) {
if (source_access(to, F_OK) == 0 && !interactive_access(to, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -363,7 +401,7 @@ static int xmp_link(const char *from, const char *to) {
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)) {
if (!interactive_access(from, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -388,7 +426,7 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
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)) {
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -419,7 +457,7 @@ static int xmp_chown(const char *path, uid_t uid, gid_t gid,
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)) {
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}
@@ -479,7 +517,7 @@ static int xmp_create(const char *path, mode_t mode,
// fprintf(stderr, "%s, %d\n", path, ask_access(path, pi));
if (!interactive_access(real_filename(path), pi, GRANT_PERM)) {
if (!interactive_access(path, pi, GRANT_PERM)) {
free(pi.name);
return -EACCES;
}
@@ -503,7 +541,7 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
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)) {
if (!interactive_access(path, pi, 0)) {
free(pi.name);
return -EACCES;
}

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

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

@@ -0,0 +1,158 @@
#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");
}
// 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,16 +10,15 @@
See the file LICENSE.
*/
#include <sys/types.h>
#include <unistd.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"
@@ -28,15 +27,25 @@
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\n");
return EXIT_FAILURE;
}
// if umask != 0, the filesystem will create files with more restrictive
// permissions than it's caller reqested
umask(0);
int ret = init_ui_socket();
// 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);
}
mountpoint = realpath(argv[argc - 1], NULL);
mountpoint = realpath(argv[argc - 2], NULL);
ret = source_init(mountpoint);
if (ret != 0) {
@@ -44,9 +53,10 @@ int main(int argc, char *argv[]) {
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

@@ -7,7 +7,9 @@
*/
#include "perm_permissions_table.h"
#include "access_t.h"
#include "process_info.h"
#include "set_mode_t.h"
#include <fcntl.h>
#include <pthread.h>
#include <sqlite3.h>
@@ -23,9 +25,9 @@
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"};
const int column_count = 3;
const char *const schema[] = {"executable", "filename", "mode"};
const char *const types[] = {"TEXT", "TEXT", "INTEGER"};
uid_t ruid, euid, current_pid;
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
@@ -69,9 +71,10 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
}
int column_num = atoi(argv[0]);
if (column_num >= column_count) {
fprintf(stderr, "Table contains more columns than expected.\n");
fprintf(stderr, "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;
@@ -81,17 +84,29 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
}
static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)argc;
(void)argv;
(void)colname;
*(int *)flag = 1;
if (argc < 3) {
fprintf(stderr,
"Unexpected amount of arguments given to the callback: %d.\n",
argc);
return 1;
}
if (atoi(argv[2])) {
fprintf(stderr, "Third column was: %s\n", argv[2]);
*(int *)flag = 1;
} else {
*(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);";
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);
@@ -154,8 +169,11 @@ int init_perm_permissions_table(const char *db_filename) {
euid = geteuid();
fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid());
if (sqlite3_open(db_filename, &perm_database)) {
perror("Can't open permanent permissions database:");
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);
@@ -176,44 +194,51 @@ int init_perm_permissions_table(const char *db_filename) {
/**
* Destroys the permanent permissions table.
*/
void destroy_perm_permissions_table() { sqlite3_close(perm_database); }
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
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found
*/
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;
access_t check_perm_access(const char *filename, struct process_info pi) {
char *query = NULL;
int ret = asprintf(&query,
"SELECT * FROM %s WHERE executable = \'%s\' "
"AND filename = \'%s\';",
table_name, pi.name, filename);
if (ret < 0) {
// If asprintf fails, the contents of query are undefined (see man
// asprintf). That does not explicitly rule out that query will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this.
fprintf(stderr, "Could not create query on access check");
perror("");
return NDEF;
}
char *sqlite_error = NULL;
int flag = 0;
int ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error);
ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error);
free((void *)query);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
free(query);
return 0;
return NDEF;
}
free(query);
return flag;
if (flag == 1) {
return ALLOW;
}
if (flag == -1) {
return DENY;
}
return NDEF;
}
/**
@@ -223,24 +248,33 @@ int check_perm_access(const char *filename, struct process_info pi) {
* @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);
int set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode) {
char *query = NULL;
int ret = -1;
if (mode == SET_ALLOW) {
ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', TRUE);",
table_name, pi.name, filename);
} else if (mode == SET_DENY) {
ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', FALSE);",
table_name, pi.name, filename);
} else {
return 1;
}
if (ret < 0) {
// If asprintf fails, the contents of query are undefined (see man
// asprintf). That does not explicitly rule out that query will be a valid
// pointer. But the risk of freeing a non-allocated pointer is too much to
// justify preparing for this.
fprintf(stderr, "Could not create query on rule insertion");
perror("");
return 1;
}
char *sqlite_error = NULL;
int ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
free(query);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
@@ -248,6 +282,5 @@ int give_perm_access(const char *filename, struct process_info pi) {
return 1;
}
free(query);
return 0;
}

View File

@@ -9,7 +9,9 @@
#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.
@@ -29,17 +31,21 @@ void destroy_perm_permissions_table();
*
* @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
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found
*/
int check_perm_access(const char *filename, struct process_info pi);
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 give_perm_access(const char *filename, struct process_info pi);
int set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode);
#endif // #ifdef PERM_PERMISSION_TABLE_H

View File

@@ -12,7 +12,7 @@
#include <sys/types.h>
struct process_info {
pid_t PID;
const char *name;
char *name;
};
#endif // PROCESS_INFO_H

7
src/real_filename.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef REAL_FILENAME_H
#define REAL_FILENAME_H
const char *real_filename(const char *filename);
const char *get_mountpoint(void);
#endif // !REAL_FILENAME_H

5
src/set_mode_t.h Normal file
View File

@@ -0,0 +1,5 @@
#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,6 +30,14 @@ 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("Malloc failed");
return -1;
}
strcpy(handle.mountpoint, root_path);
int root_fd = open(root_path, O_PATH);
if (root_fd == -1) {
@@ -43,6 +51,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, "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);

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. */

View File

@@ -7,6 +7,7 @@
*/
#include "temp_permissions_table.h"
#include "access_t.h"
#include "cc.h"
#include "process_info.h"
#include <pthread.h>
@@ -18,6 +19,7 @@ struct temp_process_permissions {
// 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;
@@ -33,8 +35,8 @@ pthread_mutex_t temp_permissions_table_lock;
* never really equal to 0, it exceptionally unlikely.
*/
unsigned long long get_process_creation_time(pid_t pid) {
char path[32];
FILE *fp;
char path[256];
FILE *fp = NULL;
unsigned long long creation_time = 0;
// Construct the path to the process's status file
@@ -72,22 +74,30 @@ unsigned long long get_process_creation_time(pid_t pid) {
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
int init_temp_permissions_table() {
int init_temp_permissions_table(void) {
pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT);
init(&temp_permissions_table);
return 0;
}
/**
* Destroys the temporary permissions table.
*
* @note: the table is guranteed to be destroyed if it is already initialized
* @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 destroy_temp_permissions_table(void) {
// 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);
pthread_mutex_destroy(&temp_permissions_table_lock);
}
@@ -97,10 +107,10 @@ void destroy_temp_permissions_table() {
*
* @param filename: The file that the process is trying to access
* @pram pid: PID of the process
* @return: 0 if access is denied, 1 if access is allowed
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found is avaliable
*/
int check_temp_access_noparent(const char *filename, pid_t pid) {
access_t check_temp_access_noparent(const char *filename, pid_t pid) {
// TODO: more efficient locking
pthread_mutex_lock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry =
@@ -110,22 +120,28 @@ int check_temp_access_noparent(const char *filename, pid_t pid) {
if (process_creation_time == 0) {
perror("Could not retrieve process creation time");
pthread_mutex_unlock(&temp_permissions_table_lock);
return 0;
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
for_each(&permission_entry->denied_files, denied_file) {
if (strncmp(*denied_file, filename, strlen(filename)) == 0) {
pthread_mutex_unlock(&temp_permissions_table_lock);
return DENY;
}
}
for_each(&permission_entry->allowed_files, allowed_file) {
if (strncmp(*allowed_file, filename, strlen(filename)) == 0) {
pthread_mutex_unlock(&temp_permissions_table_lock);
return 1;
return ALLOW;
}
}
}
}
pthread_mutex_unlock(&temp_permissions_table_lock);
return 0;
return NDEF;
}
/**
@@ -164,31 +180,36 @@ pid_t get_parent_pid(pid_t pid) {
*
* @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
* @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.
*/
int check_temp_access(const char *filename, struct process_info pi) {
access_t check_temp_access(const char *filename, struct process_info pi) {
pid_t current_pid = pi.PID;
while (current_pid != 0) {
if (check_temp_access_noparent(filename, current_pid)) {
return 1;
access_t access = check_temp_access_noparent(filename, current_pid);
if (access != NDEF) {
return access;
}
current_pid = get_parent_pid(current_pid);
}
return 0;
return NDEF;
}
/**
* Gives temporary access to the process to the file.
* 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
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
* @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 give_temp_access(const char *filename, struct process_info pi) {
int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode) {
pthread_mutex_lock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry =
get(&temp_permissions_table, pi.PID);
@@ -206,7 +227,13 @@ int give_temp_access(const char *filename, struct process_info pi) {
if (process_creation_time == permission_entry->creation_time) {
// the process is the same as the one that was granted temporary access
// to the file
push(&permission_entry->allowed_files, strdup(filename));
if (mode == SET_ALLOW) {
push(&permission_entry->allowed_files, strdup(filename));
}
if (mode == SET_DENY) {
push(&permission_entry->denied_files, strdup(filename));
}
pthread_mutex_unlock(&temp_permissions_table_lock);
return 0;
}
@@ -222,10 +249,15 @@ int give_temp_access(const char *filename, struct process_info pi) {
new_permission_entry.creation_time = get_process_creation_time(pi.PID);
init(&new_permission_entry.allowed_files);
push(&new_permission_entry.allowed_files, strdup(filename));
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);
printf("temp_permissions_table size: %ld\n", size(&temp_permissions_table));
pthread_mutex_unlock(&temp_permissions_table_lock);
return 0;

View File

@@ -2,38 +2,50 @@
#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();
int init_temp_permissions_table(void);
/**
* Destroys the temporary permissions table.
*
* @note: the table is guranteed to be destroyed if it is already initialized
* @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 destroy_temp_permissions_table(void);
/**
* Checks if the process has a temporary access to the file.
* 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
* @pram 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
* @return: 0 if access is denied, 1 if access is allowed
* @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 check_temp_access(const char *filename, struct process_info pi);
/**
* Gives temporary 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 (e.g. ENOMEM)
*/
int give_temp_access(const char *filename, struct process_info pi);
int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode);
#endif // !TEMP_PERMISSIONS_TABLE_H

View File

@@ -6,14 +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>
@@ -22,35 +26,40 @@
#include <sys/un.h>
#include <unistd.h>
int init_ui_socket() {
char line[256];
FILE *fp;
#define DIALOGUE_YES 0
#define DIALOGUE_NO 1
#define DIALOGUE_PERM 2
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, "Could not initialize temporary permissions table.\n");
return 1;
}
if (init_perm_permissions_table(
"/home/fedir/Developement/uni/ICFS/test/.pt.db")) {
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)
fp = popen("zenity --version", "r");
// Test if dialogue is installed (get version)
fp = popen("icfs_dialogue --version", "r");
if (fp == NULL) {
perror("Pipe returned an error");
return 1;
}
while (fgets(line, sizeof(line), fp))
printf("%s", line);
pclose(fp);
return 0;
}
void destroy_ui_socket() {
void destroy_ui_socket(void) {
destroy_temp_permissions_table();
destroy_perm_permissions_table();
}
@@ -61,49 +70,85 @@ void destroy_ui_socket() {
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: 0 if access is denied, 1 if access is allowed, 2 if access is
* @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) {
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 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);
// Zenity Question Message Popup
struct dialogue_response response;
response.decision = DENY;
response.filename = NULL;
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, "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;
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)) {
printf("%s", buffer);
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));
fprintf(stderr, "zenity returned %d\n", zenity_exit_code);
// zenity returns 1 on "No" >:(
if (zenity_exit_code == 0) {
return 1;
int dialogue_exit_code = WEXITSTATUS(pclose(fp));
fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code);
// if (size(&dialogue_output) == 0) {
// push(&dialogue_output, '.');
// }
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);
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 0;
return response;
}
/**
@@ -117,36 +162,120 @@ int ask_access(const char *filename, struct process_info pi) {
* @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) {
int interactive_access(const char *filename, struct process_info proc_info,
int opts) {
char *real_path = real_filename(filename);
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,
"Permission allowed to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
free(real_path);
return 1;
}
if (access == DENY) {
fprintf(stderr,
"Permission denied to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
free(real_path);
return 0;
}
access = check_perm_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(stderr,
"Permission allowed to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
free(real_path);
return 1;
}
if (access == DENY) {
fprintf(stderr,
"Permission denied to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
free(real_path);
return 0;
}
// if noth GRANT_TEMP and GRANT_PERM are selected, then only permanent
// permissions are granted
if (opts & GRANT_PERM) {
give_perm_access(filename, pi);
fprintf(stderr, "Permission granted permanently to %s.\n", proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
free(real_path);
return 1;
}
if (opts & GRANT_TEMP) {
give_temp_access(filename, pi);
fprintf(stderr, "Permission granted temporarily to %s.\n", proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
free(real_path);
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);
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, "Filename returned by zenty wasn't correct: %s\n",
response.filename);
free(response.filename);
response = ask_access(filename, proc_info);
}
*/
free(real_path);
real_path = real_filename(response.filename);
free(response.filename);
if (response.decision == ALLOW) {
fprintf(stderr,
"Permission granted permanently to %s based on zenty response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
free(real_path);
return 1;
}
// otherwise "no"
if (response.decision == ALLOW_TEMP) {
fprintf(stderr,
"Permission granted temporarily to %s based on zenty response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
free(real_path);
return 1;
}
if (response.decision == DENY_TEMP) {
fprintf(stderr,
"Permission denied temporarily to %s based on zenty response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_DENY);
free(real_path);
return 0;
}
if (response.decision == DENY) {
fprintf(stderr,
"Permission denied permanently to %s based on zenty response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_DENY);
free(real_path);
return 0;
}
free(real_path);
// deny on unknown options.
return 0;
}

View File

@@ -21,7 +21,7 @@
*
* @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.

View File

@@ -2,20 +2,26 @@
# fake-zenity: script that mocks the behavior of zenity based on the ./.fake-zenity-response file
ZENITY_YES=0
ZENITY_NO=1
ZENITY_PERM=2
if [[ $1 == "--set-fake-response" ]]; then
#someone knows we are fake :)
echo $2 >~/.fake_zenity_response
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
printf "%s" "$4"
if [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then
exit "$ZENITY_YES"
elif [[ $FAKE_ZENITY_RESPONSE == "no" ]]; then
exit 1
elif [[ $FAKE_ZENITY_RESPONSE == "yes" ]]; then
exit 0
exit "$ZENITY_NO"
elif [[ $FAKE_ZENITY_RESPONSE == "yes_perm" ]]; then
exit "$((ZENITY_YES | ZENITY_PERM))"
elif [[ $FAKE_ZENITY_RESPONSE == "no_perm" ]]; then
exit "$((ZENITY_NO | ZENITY_PERM))"
fi
fi
fi

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)

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

@@ -0,0 +1,20 @@
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: ./opener [FILENAME]");
return 1;
}
int fd = open(argv[1], O_RDWR | O_CREAT);
if (fd == -1) {
perror("Cannot open file");
return 1;
}
close(fd);
return 0;
}

View File

@@ -9,6 +9,17 @@ touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth
chmod 777 ./protected/perm777 ./protected/perm000
echo "Free code, free world." >./protected/motto
rm -rf ./openers
mkdir openers
make -C ./opener || (
echo "Could not make the opener program."
exit 1
)
for i in {1..10}; do
cp ./opener/opener "./openers/opener$i"
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
done
# set up the fake-zenity
PATH="$(realpath ./mock/):$PATH"
@@ -23,12 +34,13 @@ if [[ $1 == "--setuid" ]]; then
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 &
../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 1
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 -s ../build/icfs -o default_permissions ./protected &
#valgrind --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback" &
valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5
fi
@@ -39,85 +51,105 @@ fi
# create files
zenity --set-fake-response no
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
zenity --set-fake-response yes_tmp
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
zenity --set-fake-response no
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
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 write to protected/truth despite access being permitted!" # OK
# Read files
zenity --set-fake-response no
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
icfs_dialogue --set-fake-response yes
cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot create protected/this-only despite access being permitted!" # "Free code, free world."
# remove files
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
zenity --set-fake-response no
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
zenity --set-fake-response yes_tmp
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
zenity --set-fake-response no
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
zenity --set-fake-response yes_tmp
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
zenity --set-fake-response yes
cat ./protected/motto >/dev/null 2>/dev/null &&
icfs_dialogue --set-fake-response yes_perm
openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot read protected/motto despite access being permitted!" # OK
echo "[ICFS-TEST]: openers/opener1 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 &&
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]: echo cannot read protected/motto despite access being permitted!" # 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 database access
if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then