7 Commits

Author SHA1 Message Date
BritishTeapot
608943d685 Added new permanent permissions tests. 2025-03-30 19:07:32 +02:00
BritishTeapot
7e111b16b7 Added permanent permissions
Finally implemented the permanent permission tables using sqlite3. For
now, performance wasn't a consideration. There are a lot of
optimizations that could be made, like having prepared queries. The code
remains fairly untested.
2025-03-30 19:06:57 +02:00
BritishTeapot
4ce97555e4 Fixed a testing bug
The script was correctly opening the `truth` file by piping `echo` to
it, but then it tried to deny another operation on it. But since pipes
are opened by the script process, the permission was given to the
script. And since the permissions are preserved for the entire runtime
of a process, and child processes inherit permissions of their parents,
any command executed later would also have the necessary permissions to
open `truth` (which was the case for the second operation). Now the
second operation is performed on a different file.
2025-03-24 17:17:33 +01:00
BritishTeapot
da37376fde Added permission checks for chmod, link, rename and chown
Those clearly need to ask for permissions.
2025-03-24 17:11:01 +01:00
BritishTeapot
6342de0dd3 Added tests to Makefile 2025-03-24 16:28:56 +01:00
BritishTeapot
2e21ae7b18 Deleted a useless file. 2025-03-18 16:50:53 +01:00
2d76dc6596 Merge pull request 'Temp_permission_table' (#5) from Temp_permission_table into main
Reviewed-on: #5
2025-03-18 15:47:08 +01:00
11 changed files with 378 additions and 34 deletions

View File

@@ -12,7 +12,7 @@ CXX := g++
# dependencies
PACKAGE_NAMES := fuse3
PACKAGE_NAMES := fuse3 sqlite3
ifeq ($(TEST), 1)
# PACKAGE_NAMES += check # TODO: use check?
@@ -43,7 +43,7 @@ endif
# set up targets
TARGETS := icfs
TARGETS := $(BUILD_DIR)/icfs
ifeq ($(TEST), 1)
TARGETS += icfs_test
@@ -56,12 +56,11 @@ default: $(TARGETS)
.PHONY: clean
icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o
$(BUILD_DIR)/icfs: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o $(BUILD_DIR)/temp_permissions_table.o $(BUILD_DIR)/perm_permissions_table.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs
icfs_test: $(BUILD_DIR)/main.o $(BUILD_DIR)/fuse_operations.o $(BUILD_DIR)/sourcefs.o $(BUILD_DIR)/ui-socket.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $(BUILD_DIR)/icfs_test
# $(BUILD_DIR)/icfs_test # TODO: implement testing
icfs_test: $(BUILD_DIR)/icfs
cd ./test && ./test.bash
$(BUILD_DIR)/test_access_control.o: $(TESTS_DIR)/test_access_control.c
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
@@ -81,5 +80,9 @@ $(BUILD_DIR)/ui-socket.o: $(SOURCES_DIR)/ui-socket.c $(SOURCES_DIR)/ui-socket.h
$(BUILD_DIR)/temp_permissions_table.o: $(SOURCES_DIR)/temp_permissions_table.c $(SOURCES_DIR)/temp_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
$(BUILD_DIR)/perm_permissions_table.o: $(SOURCES_DIR)/perm_permissions_table.c $(SOURCES_DIR)/perm_permissions_table.h
$(CC) $(CFLAGS) -c $< $(LDFLAGS) -o $@
clean:
rm $(BUILD_DIR)/*.o $(BUILD_DIR)/icfs*

View File

@@ -108,7 +108,29 @@ static int xmp_getattr(const char *path, struct stat *stbuf,
static int xmp_access(const char *path, int mask) {
int res;
res = access(path, mask);
// 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();
pi.PID = fc->pid;
pi.UID = fc->uid;
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)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
}
res = source_access(path, mask);
if (res == -1)
return -errno;
@@ -304,6 +326,30 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
if (flags)
return -EINVAL;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.UID = fc->uid;
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)) {
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)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
res = source_rename(from, to);
if (res == -1)
return -errno;
@@ -313,6 +359,22 @@ static int xmp_rename(const char *from, const char *to, unsigned int flags) {
static int xmp_link(const char *from, const char *to) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.UID = fc->uid;
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)) {
free(pi.name);
return -EACCES;
}
// no need to check the access to the "to" file, see link(2)
free(pi.name);
res = source_link(from, to);
if (res == -1)
@@ -323,6 +385,20 @@ static int xmp_link(const char *from, const char *to) {
static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.UID = fc->uid;
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)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
if (fi)
res = fchmod(fi->fh, mode);
@@ -334,9 +410,27 @@ static int xmp_chmod(const char *path, mode_t mode, struct fuse_file_info *fi) {
return 0;
}
/**
* This filesystem is not designed for multiuser operation (e.g. with
* allow_other) so there is little point in having chown implemnted
*/
static int xmp_chown(const char *path, uid_t uid, gid_t gid,
struct fuse_file_info *fi) {
int res;
struct process_info pi;
struct fuse_context *fc = fuse_get_context();
pi.PID = fc->pid;
pi.UID = fc->uid;
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)) {
free(pi.name);
return -EACCES;
}
free(pi.name);
if (fi)
res = fchown(fi->fh, uid, gid);
@@ -660,7 +754,7 @@ static off_t xmp_lseek(const char *path, off_t off, int whence,
static const struct fuse_operations xmp_oper = {
.init = xmp_init,
.getattr = xmp_getattr,
// .access = xmp_access,
.access = xmp_access,
.readlink = xmp_readlink,
.opendir = xmp_opendir,
.readdir = xmp_readdir,
@@ -676,7 +770,7 @@ static const struct fuse_operations xmp_oper = {
.chown = xmp_chown,
.truncate = xmp_truncate,
#ifdef HAVE_UTIMENSAT
// .utimens = xmp_utimens,
// .utimens = xmp_utimens,
#endif
.create = xmp_create,
.open = xmp_open,

View File

@@ -38,7 +38,7 @@ int main(int argc, char *argv[]) {
ret = init_ui_socket();
if (ret != 0) {
perror("init_ui_socket");
fprintf(stderr, "Could not initalize ui-socket.\n");
exit(EXIT_FAILURE);
}

View File

@@ -0,0 +1,192 @@
#include "perm_permissions_table.h"
#include "process_info.h"
#include <sqlite3.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
sqlite3 *perm_database = NULL;
const char *const table_name = "permissions";
// one row corresponds to a permission to access one file for one executable
const int column_count = 2;
const char *const schema[] = {"executable", "filename"};
const char *const types[] = {"TEXT", "TEXT"};
static int check_table_col_schema(void *notused, int argc, char **argv,
char **colname) {
(void)notused;
(void)colname;
if (argc < 3) {
fprintf(stderr, "Unexpected amount of arguments given to the callback.\n");
return 1;
}
int i = atoi(argv[0]);
if (i >= column_count) {
fprintf(stderr, "Table contains more columns than expected.\n");
return 1;
}
if (strcmp(schema[i], argv[1]) == 0 && strcmp(types[i], argv[2]) == 0) {
return 0;
}
fprintf(stderr, "Column %d does not conform to the schema.\n", i);
return 1;
}
static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)argc;
(void)argv;
(void)colname;
*(int *)flag = 1;
return 0;
}
int create_database_schema() {
fprintf(stderr, "Creating table 'permissions'.\n");
const char *create_query = "CREATE TABLE permissions(executable TEXT NOT "
"NULL, filename TEXT NOT NULL);";
char *err = NULL;
int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "Database created successfully\n");
return 0;
}
/**
* Ensures that the database schema is correct.
*
* @return: 0 if the schema is correct, 1 if the schema could not be corrected.
*/
int ensure_database_schema() {
// Check for the table.
int result = sqlite3_table_column_metadata(
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
if (result == SQLITE_ERROR) {
fprintf(stderr, "Table '%s' does not exist.\n", table_name);
if (create_database_schema()) {
fprintf(stderr, "Table could not be created.\n");
return 1;
}
return 0;
} else if (result != SQLITE_OK) {
fprintf(stderr, "Database metadata could not be retrieved.\n");
return 1;
}
const char *pragma = "PRAGMA table_info(permissions);";
char *err = NULL;
int ret =
sqlite3_exec(perm_database, pragma, check_table_col_schema, NULL, &err);
if (ret != SQLITE_OK) {
fprintf(stderr, "sqlite3 error: %s\n", err);
sqlite3_free(err);
return 1;
}
fprintf(stderr, "Schema is correct.\n");
return 0;
}
/**
* Initializes the permanent permissions table.
*
* @param db_filename: The filename of the permissions sqlite3 database
* @return: 0 on success, -1 on failure
*/
int init_perm_permissions_table(const char *db_filename) {
if (sqlite3_open(db_filename, &perm_database)) {
perror("Can't open permanent permissions database:");
return -1;
}
if (ensure_database_schema()) {
fprintf(stderr, "Database schema is not correct.\n");
return -1;
}
return 0;
}
/**
* Destroys the permanent permissions table.
*/
void destroy_perm_permissions_table() { sqlite3_close(perm_database); }
/**
* Checks if the process has a permanent access to the file.
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed
*/
int check_perm_access(const char *filename, struct process_info pi) {
size_t query_len =
56 + strlen(table_name) + strlen(filename) + strlen(pi.name);
const char *query = malloc(query_len);
size_t should_be_written = snprintf(
query, query_len,
"SELECT * FROM %s WHERE executable = \'%s\' AND filename = \'%s\';",
table_name, pi.name, filename);
// -1 for the \0
if (should_be_written != query_len - 1) {
fprintf(stderr,
"Unexpected query size while permanent access rule check: "
"Expected %lu, but snprintf returned %lu. The query: %s\n",
query_len, should_be_written, query);
return 0;
}
char *sqlite_error = NULL;
int flag = 0;
int ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
free(query);
return 0;
}
free(query);
return flag;
}
/**
* Gives permanent access to the process to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: 0 on success, 1 on failure
*/
int give_perm_access(const char *filename, struct process_info pi) {
size_t query_len =
30 + strlen(table_name) + strlen(filename) + strlen(pi.name);
const char *query = malloc(query_len);
size_t should_be_written =
snprintf(query, query_len, "INSERT INTO %s VALUES (\'%s\', \'%s\');",
table_name, pi.name, filename);
// -1 for the \0
if (should_be_written != query_len - 1) {
fprintf(stderr,
"Unexpected query size while permanent access rule insertion: "
"Expected %lu, but snprintf returned %lu\n",
query_len, should_be_written);
return 1;
}
char *sqlite_error = NULL;
int ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error);
if (ret != SQLITE_OK) {
fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error);
sqlite3_free(sqlite_error);
free(query);
return 1;
}
free(query);
return 0;
}

View File

@@ -0,0 +1,38 @@
#ifndef PERM_PERMISSION_TABLE_H
#define PERM_PERMISSION_TABLE_H
#include "process_info.h"
/**
* Initializes the permanent permissions table.
*
* @param db_filename: The filename of the permissions sqlite3 database
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
*/
int init_perm_permissions_table(const char *db_filename);
/**
* Destroys the permanent permissions table.
*/
void destroy_perm_permissions_table();
/**
* Checks if the process has a permanent access to the file.
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed
*/
int check_perm_access(const char *filename, struct process_info pi);
/**
* Gives permanent access to the process to the file.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: 0 on success, -1 on failure
*/
int give_perm_access(const char *filename, struct process_info pi);
#endif // #ifdef PERM_PERMISSION_TABLE_H

View File

@@ -66,6 +66,11 @@ int source_symlink(const char *target, const char *linkpath) {
return symlinkat(target, handle.root_fd, relative_linkpath);
}
int source_access(const char *filename, int mode) {
const char *relative_filename = source_filename_translate(filename);
return faccessat(handle.root_fd, relative_filename, mode, 0);
}
DIR *source_opendir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);

View File

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

View File

@@ -11,6 +11,7 @@
#include <time.h>
#define _GNU_SOURCE
#include "cc.h"
#include "perm_permissions_table.h"
#include "temp_permissions_table.h"
#include "ui-socket.h"
#include <errno.h>
@@ -26,7 +27,16 @@ int init_ui_socket() {
char line[256];
FILE *fp;
init_temp_permissions_table();
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")) {
fprintf(stderr, "Could not initialize permanent permissions table.\n");
return 1;
}
// Test if Zenity is installed (get version)
fp = popen("zenity --version", "r");
@@ -41,7 +51,10 @@ int init_ui_socket() {
return 0;
}
void destroy_ui_socket() { destroy_temp_permissions_table(); }
void destroy_ui_socket() {
destroy_temp_permissions_table();
destroy_perm_permissions_table();
}
/**
* Asks the user if the process should be allowed to access the file using the
@@ -53,7 +66,6 @@ void destroy_ui_socket() { destroy_temp_permissions_table(); }
* 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);
@@ -86,6 +98,7 @@ int ask_access(const char *filename, struct process_info pi) {
}
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;
@@ -94,22 +107,6 @@ int ask_access(const char *filename, struct process_info pi) {
return 0;
}
/**
* Checks if the process has a permanent access to the file.
*
* @param filename: The file that the process is trying to access
* @pram pi: The process information
* @return: 0 if access is denied, 1 if access is allowed
*/
int check_perm_access(const char *filename, struct process_info pi) {
perror("Not implemented");
return 0;
}
int give_perm_access(const char *filename, struct process_info pi) {
perror("Not implemented");
return -1;
}
/**
* Check access according to:
* 1. temp permission table

View File

@@ -20,4 +20,4 @@ else
fi
fi
exit -1 # TODO: call actual zenity here
exit 255 # TODO: call actual zenity here

View File

@@ -2,9 +2,10 @@
# clean what was left from previous tests
rm -f ./.pt.db
rm -rf ./protected
mkdir protected
touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth ./protected/perm000 ./protected/perm777 ./protected/this-name-is-wrong
touch ./protected/do-not-remove ./protected/should-be-removed ./protected/truth ./protected/perm000 ./protected/perm777 ./protected/should-be-renamed ./protected/do-not-rename
chmod 777 ./protected/perm777 ./protected/perm000
echo "Free code, free world." >./protected/motto
@@ -17,7 +18,7 @@ PATH="$(realpath ./mock/):$PATH"
echo "Run $(date -u +%Y-%m-%dT%H:%M:%S) "
valgrind -s ../build/icfs -o default_permissions ./protected &
sleep 1
sleep 5
# create files
@@ -70,11 +71,11 @@ rm ./protected/should-be-removed >/dev/null 2>/dev/null &&
# rename files
zenity --set-fake-response no
mv ./protected/truth ./protected/lie 2>/dev/null &&
mv ./protected/do-not-rename ./protected/terrible-name 2>/dev/null &&
echo "[ICFS-TEST]: mv can rename protected/truth despite access being denied!" ||
echo "[ICFS-TEST]: OK" # EACCESS
zenity --set-fake-response yes_tmp
mv ./protected/this-name-is-wrong ./protected/this-name-is-correct 2>/dev/null &&
mv ./protected/should-be-renamed ./protected/great-name 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: mv cannot rename should-be-removed to renamed-file despite access being permitted!" # OK
@@ -89,6 +90,18 @@ 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
# create files with permanent permissions
zenity --set-fake-response yes
touch ./protected/friendly 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: touch cannot create protected/friendly despite access being permitted!" # OK
zenity --set-fake-response no # this should be ignored
touch ./protected/friendly-again 2>/dev/null &&
echo "[ICFS-TEST]: OK" ||
echo "[ICFS-TEST]: touch cannot create protected/friendly-again despite access being permitted!" # OK
# unmount
sleep 0.5