new-dialogue #8

Merged
fedir merged 33 commits from new-dialogue into main 2025-05-06 18:10:44 +02:00
25 changed files with 4928 additions and 1011 deletions

10
.gitignore vendored
View File

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

View File

@ -2,9 +2,17 @@ SHELL=/bin/bash
# configurable options # configurable options
SOURCES_DIR := ./src ifndef ($(SOURCES_DIR))
TESTS_DIR := ./tests SOURCES_DIR := ./src
BUILD_DIR := ./build endif
ifndef ($(TESTS_DIR))
TESTS_DIR := ./tests
endif
ifndef ($(BUILD_DIR))
BUILD_DIR := ./build
endif
CC := gcc CC := gcc
CXX := g++ CXX := g++
@ -49,14 +57,20 @@ ifeq ($(TEST), 1)
TARGETS += icfs_test TARGETS += icfs_test
endif endif
ifneq ($(DIALOGUE), 0)
TARGETS += $(BUILD_DIR)/icfs_dialogue
endif
# build! # build!
default: $(TARGETS) default: $(TARGETS)
.PHONY: clean .PHONY: clean icfs_test clean-icfs clean-icfs_dialogue
$(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)/icfs_dialogue:
make -C $(SOURCES_DIR)/gui TEST=$(TEST) DEBUG=$(shell realpath $(DEBUG)) SOURCES_DIR=$(shell realpath $(SOURCES_DIR)/gui) BUILD_DIR=$(shell realpath $(BUILD_DIR)) TESTS_DIR=$(shell realpath $(TESTS_DIR))
$(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o $(BUILD_DIR)/proc_operations.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs
icfs_test: $(BUILD_DIR)/icfs icfs_test: $(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 $(BUILD_DIR)/fuse_operations.o: $(SOURCES_DIR)/fuse_operations.c $(SOURCES_DIR)/fuse_operations.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ $(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 $@ $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h $(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h
@ -83,6 +97,19 @@ $(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 $(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@ $(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/proc_operations.o: $(SOURCES_DIR)/proc_operations.c $(SOURCES_DIR)/proc_operations.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
clean: CLEAN_TARGETS=clean-icfs
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))

View File

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

4769
src/cc.h

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,8 @@
See the file LICENSE. See the file LICENSE.
*/ */
#include "real_filename.h"
#include <assert.h>
#include <stddef.h> #include <stddef.h>
#define FUSE_USE_VERSION 31 #define FUSE_USE_VERSION 31
@ -37,66 +39,10 @@
#include <sys/file.h> /* flock(2) */ #include <sys/file.h> /* flock(2) */
#include "fuse_operations.h" #include "fuse_operations.h"
#include "proc_operations.h"
#include "sourcefs.h" #include "sourcefs.h"
#include "ui-socket.h" #include "ui-socket.h"
const 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;
/*
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
const char *real_filename(const char *filename) { return filename; }
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
(void)conn; (void)conn;
cfg->use_ino = 1; cfg->use_ino = 1;
@ -120,6 +66,7 @@ static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) {
cfg->attr_timeout = 0; cfg->attr_timeout = 0;
cfg->negative_timeout = 0; cfg->negative_timeout = 0;
fprintf(stderr, "%d\n", getpid()); fprintf(stderr, "%d\n", getpid());
assert(get_mountpoint() != NULL);
return NULL; return NULL;
} }
@ -157,7 +104,7 @@ static int xmp_access(const char *path, int mask) {
// 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), proc_info, 0)) { if (!interactive_access(path, proc_info, 0)) {
free((void *)proc_info.name); free((void *)proc_info.name);
return -EACCES; return -EACCES;
} }
@ -323,7 +270,7 @@ static int xmp_unlink(const char *path) {
// 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, 0)) { if (!interactive_access(path, pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@ -371,15 +318,14 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
// 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(from), pi, 0)) { if (!interactive_access(from, pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
// the "to" file may exist and the process needs to get persmission to modify // the "to" file may exist and the process needs to get persmission to modify
// it // it
if (source_access(to, F_OK) == 0 && if (source_access(to, F_OK) == 0 && !interactive_access(to, pi, 0)) {
!interactive_access(real_filename(to), pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@ -402,7 +348,7 @@ static int xmp_link(const char *from, const char *to) {
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(from), pi, 0)) { if (!interactive_access(from, pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@ -427,7 +373,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); 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, 0)) { if (!interactive_access(path, pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@ -458,7 +404,7 @@ static int xmp_chown(const char *path, uid_t uid, gid_t gid,
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, 0)) { if (!interactive_access(path, pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@ -518,7 +464,7 @@ static int xmp_create(const char *path, mode_t mode,
// 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, GRANT_PERM)) { if (!interactive_access(path, pi, GRANT_PERM)) {
free(pi.name); free(pi.name);
return -EACCES; return -EACCES;
} }
@ -542,7 +488,7 @@ static int xmp_open(const char *path, struct fuse_file_info *fi) {
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, 0)) { if (!interactive_access(path, pi, 0)) {
free(pi.name); free(pi.name);
return -EACCES; 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

@ -56,6 +56,7 @@ int main(int argc, char *argv[]) {
ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL); ret = fuse_main(argc - 1, argv, get_fuse_operations(), NULL);
free((void *)mountpoint); free((void *)mountpoint);
source_destroy();
destroy_ui_socket(); destroy_ui_socket();
return ret; return ret;
} }

View File

@ -8,7 +8,9 @@
#include "perm_permissions_table.h" #include "perm_permissions_table.h"
#include "access_t.h" #include "access_t.h"
#include "proc_operations.h"
#include "process_info.h" #include "process_info.h"
#include "set_mode_t.h"
#include <fcntl.h> #include <fcntl.h>
#include <pthread.h> #include <pthread.h>
#include <sqlite3.h> #include <sqlite3.h>
@ -83,10 +85,21 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
} }
static int set_flag(void *flag, int argc, char **argv, char **colname) { static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)argc;
(void)argv;
(void)colname; (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; return 0;
} }
@ -182,7 +195,7 @@ int init_perm_permissions_table(const char *db_filename) {
/** /**
* Destroys the permanent permissions table. * 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. * Checks if the process has a permanent access to the file.
@ -192,13 +205,16 @@ void destroy_perm_permissions_table() { sqlite3_close(perm_database); }
* @return: access status - ALLOW, DENY or NDEF in case if no information was * @return: access status - ALLOW, DENY or NDEF in case if no information was
* found * found
*/ */
access_t check_perm_access(const char *filename, struct process_info pi) { access_t check_perm_access_noparent(const char *filename,
struct process_info pi) {
char *query = NULL; char *query = NULL;
int ret = asprintf(&query, int ret = asprintf(&query,
"SELECT * FROM %s WHERE executable = \'%s\' " "SELECT * FROM %s WHERE executable = \'%s\' "
"AND filename = \'%s\' AND mode = TRUE;", "AND ((\'%s\' LIKE CONCAT(filename, \'%%\') AND filename "
table_name, pi.name, filename); "GLOB \'*/\') OR filename = \'%s\');",
table_name, pi.name, filename, filename);
fprintf(stderr, "query: %s\n", query);
if (ret < 0) { if (ret < 0) {
// If asprintf fails, the contents of query are undefined (see man // If asprintf fails, the contents of query are undefined (see man
@ -220,9 +236,51 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
return NDEF; return NDEF;
} }
if (flag) { if (flag == 1) {
return ALLOW; return ALLOW;
} }
if (flag == -1) {
return DENY;
}
return NDEF;
}
/**
* Checks if the process or any of it's parents have permanent access to the
* file.
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found. Does not return ALLOW_TEMP or DENY_TEMP.
* @note: In case one of the parent processes is killed while this function
* execution the result is not guranteed to be correct. It should only lead to
* false negatives, though.
*/
access_t check_perm_access(const char *filename, struct process_info pi) {
if (pi.PID == 0) {
return NDEF;
}
struct process_info current_pi = pi;
current_pi.name = strdup(current_pi.name);
while (current_pi.PID != 0) {
access_t access = check_perm_access_noparent(filename, current_pi);
free(current_pi.name);
if (access != NDEF) {
return access;
}
current_pi.name = NULL;
while (current_pi.name == NULL) {
current_pi.PID = get_parent_pid(current_pi.PID);
if (current_pi.PID != 0) {
current_pi.name = get_process_name_by_pid(current_pi.PID);
} else {
break;
}
}
}
return NDEF; return NDEF;
} }
@ -233,10 +291,19 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
* @param pi: The process information * @param pi: The process information
* @return: 0 on success, 1 on failure * @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) {
char *query = NULL; char *query = NULL;
int ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', TRUE);", int ret = -1;
table_name, pi.name, filename); 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 (ret < 0) {
// If asprintf fails, the contents of query are undefined (see man // If asprintf fails, the contents of query are undefined (see man

View File

@ -11,6 +11,7 @@
#include "access_t.h" #include "access_t.h"
#include "process_info.h" #include "process_info.h"
#include "set_mode_t.h"
/** /**
* Initializes the permanent permissions table. * Initializes the permanent permissions table.
@ -40,8 +41,11 @@ access_t check_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
* @param pi: The process information * @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 * @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 #endif // #ifdef PERM_PERMISSION_TABLE_H

45
src/proc_operations.c Normal file
View File

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

17
src/proc_operations.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef PROC_OPERATIONS
#define PROC_OPERATIONS
#include <time.h>
char *get_process_name_by_pid(const int pid);
/**
* Finds the parent process ID of a given process.
*
* @param pid: The process ID of the process to find the parent of
* @return: The parent process ID, or 0 if the parent process ID could not be
* found
*/
pid_t get_parent_pid(pid_t pid);
#endif // !PROC_OPERATIONS

View File

@ -12,7 +12,7 @@
#include <sys/types.h> #include <sys/types.h>
struct process_info { struct process_info {
pid_t PID; pid_t PID;
const char *name; char *name;
}; };
#endif // PROCESS_INFO_H #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 "sourcefs.h"
#include <dirent.h> #include <dirent.h>
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
static struct source_files_handle { static struct source_files_handle {
const char *mountpoint;
int root_fd; int root_fd;
} handle; } handle;
@ -30,6 +30,14 @@ const char *source_filename_translate(const char *filename) {
} }
int source_init(const char *root_path) { 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); int root_fd = open(root_path, O_PATH);
if (root_fd == -1) { if (root_fd == -1) {
@ -43,6 +51,32 @@ int source_init(const char *root_path) {
return 0; 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) { int source_mkdir(const char *filename, mode_t mode) {
const char *relative_filename = source_filename_translate(filename); const char *relative_filename = source_filename_translate(filename);
return mkdirat(handle.root_fd, relative_filename, mode); return mkdirat(handle.root_fd, relative_filename, mode);

View File

@ -19,6 +19,7 @@
* @return 0 on success, -1 on failure. * @return 0 on success, -1 on failure.
*/ */
int source_init(const char *root_path); 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 /* All of the functions below are designed to behave exactly as their non-source
* counterparts. */ * counterparts. */

View File

@ -9,9 +9,12 @@
#include "temp_permissions_table.h" #include "temp_permissions_table.h"
#include "access_t.h" #include "access_t.h"
#include "cc.h" #include "cc.h"
#include "proc_operations.h"
#include "process_info.h" #include "process_info.h"
#include <pthread.h> #include <pthread.h>
#include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <sys/types.h> #include <sys/types.h>
struct temp_process_permissions { struct temp_process_permissions {
@ -126,14 +129,23 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
if (process_creation_time == permission_entry->creation_time) { if (process_creation_time == permission_entry->creation_time) {
// the process is the same as the one that was granted temporary access // the process is the same as the one that was granted temporary access
// to the file // to the file
size_t filename_len = strlen(filename);
for_each(&permission_entry->denied_files, denied_file) { for_each(&permission_entry->denied_files, denied_file) {
if (strncmp(*denied_file, filename, strlen(filename)) == 0) { size_t denied_file_len = strlen(*denied_file);
if (strncmp(*denied_file, filename, denied_file_len) == 0 &&
((denied_file_len < filename_len &&
(*denied_file)[denied_file_len - 1] == '/') ||
(denied_file_len == filename_len))) {
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_mutex_unlock(&temp_permissions_table_lock);
return DENY; return DENY;
} }
} }
for_each(&permission_entry->allowed_files, allowed_file) { for_each(&permission_entry->allowed_files, allowed_file) {
if (strncmp(*allowed_file, filename, strlen(filename)) == 0) { size_t allowed_file_len = strlen(*allowed_file);
if (strncmp(*allowed_file, filename, allowed_file_len) == 0 &&
((allowed_file_len < filename_len &&
(*allowed_file)[allowed_file_len - 1] == '/') ||
(allowed_file_len == filename_len))) {
pthread_mutex_unlock(&temp_permissions_table_lock); pthread_mutex_unlock(&temp_permissions_table_lock);
return ALLOW; return ALLOW;
} }
@ -144,36 +156,6 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
return NDEF; return NDEF;
} }
/**
* Finds the parent process ID of a given process.
*
* @param pid: The process ID of the process to find the parent of
* @return: The parent process ID, or 0 if the parent process ID could not be
* found
*/
pid_t get_parent_pid(pid_t pid) {
pid_t ppid = 0;
char path[256];
snprintf(path, sizeof(path), "/proc/%u/status", pid);
FILE *file = fopen(path, "r");
if (file == NULL) {
perror("Failed to open /proc/<pid>/status");
return 0;
}
char line[256];
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "PPid:\t%d", &ppid) == 1) {
fclose(file);
return ppid;
}
}
fclose(file);
return 0; // Parent PID not found
}
/** /**
* Checks if the process or any of it's parents have temporary access to the * Checks if the process or any of it's parents have temporary access to the
* file. * file.

View File

@ -4,6 +4,7 @@
#include "access_t.h" #include "access_t.h"
#include "process_info.h" #include "process_info.h"
#include "set_mode_t.h"
/** /**
* Initializes the temporary permissions table. * Initializes the temporary permissions table.
@ -35,8 +36,6 @@ void destroy_temp_permissions_table(void);
*/ */
access_t check_temp_access(const char *filename, struct process_info pi); access_t check_temp_access(const char *filename, struct process_info pi);
typedef enum { SET_DENY, SET_ALLOW } set_mode_t;
/** /**
* Sets temporary access mode of the process to the file. * Sets temporary access mode of the process to the file.
* *

View File

@ -11,9 +11,13 @@
#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 "perm_permissions_table.h"
#include "real_filename.h"
#include "sourcefs.h"
#include "temp_permissions_table.h" #include "temp_permissions_table.h"
#include "ui-socket.h" #include "ui-socket.h"
#include <assert.h>
#include <pthread.h> #include <pthread.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -22,7 +26,14 @@
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#define ZENITY_TEMP_ALLOW_MESSAGE "Allow this time\n" #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) { int init_ui_socket(const char *perm_permissions_db_filename) {
FILE *fp = NULL; FILE *fp = NULL;
@ -37,8 +48,8 @@ int init_ui_socket(const char *perm_permissions_db_filename) {
return 1; return 1;
} }
// Test if Zenity is installed (get version) // Test if dialogue is installed (get version)
fp = popen("zenity --version", "r"); fp = popen("icfs_dialogue --version", "r");
if (fp == NULL) { if (fp == NULL) {
perror("Pipe returned an error"); perror("Pipe returned an error");
return 1; return 1;
@ -62,15 +73,16 @@ void destroy_ui_socket(void) {
* @return: access status - ALLOW, DENY or ALLOW_TEMP * @return: access status - ALLOW, DENY or ALLOW_TEMP
* allowed for the runtime of the process * allowed for the runtime of the process
*/ */
access_t ask_access(const char *filename, struct process_info proc_info) { struct dialogue_response ask_access(const char *filename,
struct process_info proc_info) {
FILE *fp = NULL; FILE *fp = NULL;
char *command = NULL; char *command = NULL;
int ret = int ret = asprintf(&command, "icfs_dialogue \"%d\" \"%s\" \"%s\" \"%s\"",
asprintf(&command, proc_info.PID, proc_info.name, get_mountpoint(), filename);
"zenity --question --extra-button \"Allow this time\" --title "
"\"Allow Access?\" --text \"Allow process " struct dialogue_response response;
"<tt>%s</tt> with PID <tt>%d</tt> to access <tt>%s</tt>\"", response.decision = DENY;
proc_info.name, proc_info.PID, filename); response.filename = NULL;
if (ret < 0) { if (ret < 0) {
// If asprintf fails, the contents of command are undefined (see man // If asprintf fails, the contents of command are undefined (see man
@ -79,38 +91,64 @@ access_t ask_access(const char *filename, struct process_info proc_info) {
// justify preparing for this. // justify preparing for this.
fprintf(stderr, "Could not create query on rule insertion"); fprintf(stderr, "Could not create query on rule insertion");
perror(""); perror("");
return 1; response.decision = DENY;
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
return response;
} }
// Zenity Question Message Popup // dialogue Question Message Popup
fp = popen(command, "r"); fp = popen(command, "r");
free(command); free(command);
if (fp == NULL) { if (fp == NULL) {
perror("Pipe returned a error"); perror("Pipe returned a error");
return DENY; 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 str(char) dialogue_output;
// write it to `stdout`, but the exit code will still be `1`. So, we need init(&dialogue_output);
// to manually check the output.
char buffer[sizeof(ZENITY_TEMP_ALLOW_MESSAGE) + 1]; char line[1024]; // Buffer to read individual lines
while (fgets(buffer, sizeof(buffer), fp)) {
printf("%s", buffer); // Read the command output line by line
if (strcmp(buffer, ZENITY_TEMP_ALLOW_MESSAGE) == 0) { while (fgets(line, sizeof(line), fp)) {
pclose(fp); push_fmt(&dialogue_output, line);
return ALLOW_TEMP;
}
} }
int zenity_exit_code = WEXITSTATUS(pclose(fp)); int dialogue_exit_code = WEXITSTATUS(pclose(fp));
fprintf(stderr, "zenity returned %d\n", zenity_exit_code); fprintf(stderr, "dialogue wrote out %s\n", first(&dialogue_output));
// zenity returns 1 on "No" >:( fprintf(stderr, "dialogue returned %d\n", dialogue_exit_code);
if (zenity_exit_code == 0) {
return ALLOW; if (size(&dialogue_output) == 0) {
push(&dialogue_output, '/');
} }
return DENY; 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 response;
} }
/** /**
@ -126,20 +164,41 @@ access_t ask_access(const char *filename, struct process_info proc_info) {
*/ */
int interactive_access(const char *filename, struct process_info proc_info, int interactive_access(const char *filename, struct process_info proc_info,
int opts) { int opts) {
char *real_path = real_filename(filename);
access_t access = check_temp_access(filename, proc_info); access_t access = check_temp_access(real_path, proc_info);
if (access == ALLOW) { 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; return 1;
} }
if (access == DENY) { 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; return 0;
} }
access = check_perm_access(filename, proc_info); access = check_perm_access(real_path, proc_info);
if (access == ALLOW) { 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; return 1;
} }
if (access == DENY) { 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; return 0;
} }
@ -147,30 +206,75 @@ int interactive_access(const char *filename, struct process_info proc_info,
// permissions are granted // permissions are granted
if (opts & GRANT_PERM) { if (opts & GRANT_PERM) {
give_perm_access(filename, proc_info); 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; return 1;
} }
if (opts & GRANT_TEMP) { if (opts & GRANT_TEMP) {
set_temp_access(filename, proc_info, SET_ALLOW); 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; return 1;
} }
access_t user_response = ask_access(filename, proc_info); struct dialogue_response response = ask_access(filename, proc_info);
if (user_response == ALLOW) { // fprintf(stderr, "%s", response.filename);
give_perm_access(filename, proc_info); // 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; return 1;
} }
if (user_response == ALLOW_TEMP) { if (response.decision == ALLOW_TEMP) {
set_temp_access(filename, proc_info, SET_ALLOW); 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; return 1;
} }
if (user_response == DENY) { if (response.decision == DENY_TEMP) {
set_temp_access(filename, proc_info, SET_DENY); 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; 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. // deny on unknown options.
return 0; return 0;
} }

41
test/mock/icfs_dialogue Executable file
View File

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

View File

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

81
test/opener/Makefile Normal file
View File

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

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

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

View File

@ -9,6 +9,24 @@ touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth
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
mkdir protected/haystack
for i in {1..10}; do
touch "./protected/haystack/hay$i"
done
touch ./protected/haystack/needle
echo "Liberty in every line." >./protected/haystack/needle
rm -rf ./openers
mkdir openers
make -C ./opener || (
echo "Could not make the opener program."
exit 1
)
for i in {1..10}; do
cp ./opener/opener "./openers/opener$i"
ln --symbolic "$(realpath "./openers/opener$i")" "./openers/symlinked_opener$i"
done
# set up the fake-zenity # set up the fake-zenity
PATH="$(realpath ./mock/):$PATH" PATH="$(realpath ./mock/):$PATH"
@ -28,7 +46,8 @@ if [[ $1 == "--setuid" ]]; then
else else
echo "Database protection will not be tested due to the lack of setuid capabilites." echo "Database protection will not be tested due to the lack of setuid capabilites."
echo "To test it, run this script with '--setuid'." echo "To test it, run this script with '--setuid'."
valgrind -s ../build/icfs -o default_permissions ./protected ./.pt.db & #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 sleep 5
fi fi
@ -39,96 +58,156 @@ fi
# create files # create files
zenity --set-fake-response no icfs_dialogue --set-fake-response no
truncate -s 0 ./protected/should-exist-anyway 2>/dev/null && truncate -s 0 ./protected/should-exist-anyway 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: truncate cannot create protected/should-exist despite access being permitted!" # 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 && truncate -s 0 ./protected/should-exist 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: truncate 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 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 && 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 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 && 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
# Read files # Read files
zenity --set-fake-response no icfs_dialogue --set-fake-response no
cat ./protected/motto >/dev/null 2>/dev/null && cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: cat can read protected/this-only despite access being denied!" || echo "[ICFS-TEST]: cat can read protected/this-only despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS 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 && cat ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: echo cannot create protected/this-only despite access being permitted!" # "Free code, free world." echo "[ICFS-TEST]: echo cannot create protected/this-only despite access being permitted!" # "Free code, free world."
# remove files # remove files
zenity --set-fake-response no icfs_dialogue --set-fake-response no
rm ./protected/do-not-remove >/dev/null 2>/dev/null && 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]: rm can unlink protected/do-not-remove despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS 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 && rm ./protected/should-be-removed >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" || echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: rm cannot unlink protected/should-be-removed despite access being permitted!" # OK echo "[ICFS-TEST]: rm cannot unlink protected/should-be-removed despite access being permitted!" # OK
# rename files # rename files
zenity --set-fake-response no icfs_dialogue --set-fake-response no
mv ./protected/do-not-rename ./protected/terrible-name 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 icfs_dialogue --set-fake-response yes
mv ./protected/should-be-renamed ./protected/great-name 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
# change permissions # change permissions
zenity --set-fake-response no icfs_dialogue --set-fake-response no
chmod 000 ./protected/perm777 2>/dev/null && chmod 000 ./protected/perm777 2>/dev/null &&
echo "[ICFS-TEST]: chmod can change permissions of protected/perm777 despite access being denied!" || echo "[ICFS-TEST]: chmod can change permissions of protected/perm777 despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp icfs_dialogue --set-fake-response yes
chmod 000 ./protected/perm000 2>/dev/null && 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 # test permanent permissions
zenity --set-fake-response yes icfs_dialogue --set-fake-response yes_perm
cat ./protected/motto >/dev/null 2>/dev/null && openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" || 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 icfs_dialogue --set-fake-response no # this should be ignored
cat ./protected/motto >/dev/null 2>/dev/null && openers/opener1 ./protected/motto >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" || 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 permission globbing
icfs_dialogue --set-fake-response yes
icfs_dialogue --set-fake-response-filename "/"
openers/opener3 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener3 cannot read protected/haystack/needle despite access being permitted!" # OK
icfs_dialogue --set-fake-response no
icfs_dialogue --set-fake-response-filename "/"
openers/opener4 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener4 can read files in protected/haystack despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes_perm
icfs_dialogue --set-fake-response-filename "/"
openers/opener5 ./protected/haystack/needle >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener5 cannot read protected/haystack/needle despite access being permitted!" # OK
icfs_dialogue --set-fake-response no # this should be ignored
openers/opener5 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: openers/opener5 cannot read files in protected/haystack despite access being permitted!" # OK
icfs_dialogue --set-fake-response no_perm
icfs_dialogue --set-fake-response-filename "/"
openers/opener6 ./protected/haystack/needle >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener6 can read protected/haystack/needle despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
icfs_dialogue --set-fake-response yes # this should be ignored
openers/opener6 ./protected/haystack >/dev/null 2>/dev/null &&
echo "[ICFS-TEST]: openers/opener6 can read files in protected/haystack despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
# test database access # test database access
if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then if [[ $1 == '--setuid' ]]; then
echo "[ICFS-TEST]: permanent permissions is accessible!" if [[ -r "./.pt.db" || -w "./.pt.db" ]]; then
echo "[ICFS-TEST]: permanent permissions database is accessible!"
else
echo "[ICFS-TEST]: OK"
fi
else else
echo "[ICFS-TEST]: OK" echo "[ICFS-TEST]: permanent permissions database access was not tested due to the lack of seuid bit setting capabilites. To test this, run the script with '--setuid' flag"
fi 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