Code readability improvements

This commit is contained in:
fedir 2025-05-23 20:35:40 +02:00
parent 9b0eb10965
commit 22cb958b4f
Signed by: fedir
GPG Key ID: C959EE85F0C9362C
12 changed files with 587 additions and 296 deletions

View File

@ -23,45 +23,27 @@
#include <sys/types.h>
#include <unistd.h>
// Global pointer to the SQLite database storing permanent permissions
sqlite3 *perm_database = NULL;
const char *const table_name = "permissions";
// one row corresponds to a permission to access one file for one executable
// Each row represents a permission to access a specific file for an executable
const int column_count = 3;
// Column names in the permissions table
const char *const schema[] = {"executable", "filename", "mode"};
// Expected SQL data types for each column
const char *const types[] = {"TEXT", "TEXT", "INTEGER"};
uid_t ruid, euid, current_uid;
pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER;
void set_db_fsuid() {
pthread_mutex_lock(&uid_switch);
if (current_uid == ruid)
return;
int status = -1;
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", ruid);
exit(status);
}
pthread_mutex_unlock(&uid_switch);
}
void set_real_fsuid() {
pthread_mutex_lock(&uid_switch);
if (current_uid == ruid)
return;
int status = -1;
status = setfsuid(ruid);
if (status < 0) {
fprintf(stderr, "[ICFS] Couldn't set uid to %d.\n", euid);
exit(status);
}
pthread_mutex_unlock(&uid_switch);
}
/**
* Callback to validate the database schema matches expectations.
* Verifies column order, names, and types during schema checks.
*
* @param unused: Ignored context pointer
* @param argc: Number of columns returned (should be 3)
* @param argv: Column data: index 0=name, 1=type, 2=notnull flag
* @param colname: Column names (unused)
* @return 0 on success, 1 if schema mismatch
*/
static int check_table_col_schema(void *notused, int argc, char **argv,
char **colname) {
(void)notused;
@ -86,27 +68,13 @@ static int check_table_col_schema(void *notused, int argc, char **argv,
return 1;
}
static int set_flag(void *flag, int argc, char **argv, char **colname) {
(void)colname;
if (argc < 3) {
fprintf(
stderr,
"[ICFS] Unexpected amount of arguments given to the callback: %d.\n",
argc);
return 1;
}
if (atoi(argv[2])) {
fprintf(stderr, "[ICFS] Third column was: %s\n", argv[2]);
*(int *)flag = 1;
} else {
*(int *)flag = -1;
}
return 0;
}
int create_database_schema() {
/**
* Creates the permissions table schema in the database.
* Called when the table doesn't exist yet.
*
* @return 0 on success, 1 on failure
*/
int create_database_schema(void) {
fprintf(stderr, "[ICFS] Creating table 'permissions'.\n");
const char *create_query =
"CREATE TABLE permissions(executable TEXT NOT "
@ -125,11 +93,12 @@ int create_database_schema() {
}
/**
* Ensures that the database schema is correct.
* Ensures the database schema matches expected structure.
* Validates table existence and column definitions.
*
* @return: 0 if the schema is correct, 1 if the schema could not be corrected.
* @return 0 if schema is valid, 1 if validation failed or repair failed
*/
int ensure_database_schema() {
int ensure_database_schema(void) {
// Check for the table.
int result = sqlite3_table_column_metadata(
perm_database, NULL, table_name, NULL, NULL, NULL, NULL, NULL, NULL);
@ -145,6 +114,7 @@ int ensure_database_schema() {
return 1;
}
// Verify column definitions
const char *pragma = "PRAGMA table_info(permissions);";
char *err = NULL;
int ret =
@ -161,18 +131,20 @@ int ensure_database_schema() {
}
/**
* Initializes the permanent permissions table.
* Initializes the permanent permissions database.
* Creates/opens the database file and sets up schema.
*
* @param db_filename: The filename of the permissions sqlite3 database
* @return: 0 on success, -1 on failure
* @param db_filename Path to the SQLite database file
* @return 0 on success, -1 on failure
*/
int init_perm_permissions_table(const char *db_filename) {
// we don't want the group and others to access the db
// Prevent group/others access during file creation
umask(0077);
ruid = getuid();
euid = geteuid();
fprintf(stderr, "[ICFS] Running with uid: %d, gid: %d\n", euid, getegid());
// Open database with read/write access and full mutex protection
if (sqlite3_open_v2(db_filename, &perm_database,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_FULLMUTEX,
@ -180,12 +152,16 @@ int init_perm_permissions_table(const char *db_filename) {
perror("[ICFS] Can't open permanent permissions database");
return -1;
}
umask(0);
umask(0); // Restore default umask
// Verify and initialize schema
if (ensure_database_schema()) {
fprintf(stderr, "[ICFS] Database schema is not correct.\n");
return -1;
}
// Switch to real UID, since we started with the icfs user to open the
// database
int status = seteuid(ruid);
if (status < 0) {
fprintf(stderr,
@ -197,17 +173,18 @@ int init_perm_permissions_table(const char *db_filename) {
}
/**
* Destroys the permanent permissions table.
* Closes the database connection and releases resources.
* Should be called when the module is shutting down.
*/
void destroy_perm_permissions_table(void) { sqlite3_close(perm_database); }
/**
* Checks if the process has a permanent access to the file.
* Checks if a specific process has permanent access to a file.
* Does not check parent processes.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found
* @param filename Path to the file being accessed
* @param pi Process information structure
* @return ALLOW (explicit allow), DENY (explicit deny), or NDEF (no info)
*/
access_t check_perm_access_noparent(const char *filename,
struct process_info pi) {
@ -216,10 +193,16 @@ access_t check_perm_access_noparent(const char *filename,
access_t ret = NDEF;
sqlite3_stmt *stmt = NULL;
// Query checks:
// 1. Exact match for filename
// 2. Directory prefix match (filename starts with stored path + '/')
// Ordered by longest path for most specific match first
const char *sql =
"SELECT mode FROM permissions WHERE executable = ?1 "
"AND (( ?2 LIKE (filename || \'%\') AND filename "
"GLOB \'*/\') OR filename = ?2 ) ORDER BY LENGTH( filename ) DESC;";
sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
@ -230,30 +213,24 @@ access_t check_perm_access_noparent(const char *filename,
sqlite3_finalize(stmt);
return ret;
}
if (step_ret == SQLITE_ROW) {
int mode_col = sqlite3_column_int(stmt, 0);
if (mode_col) {
ret = ALLOW;
} else {
ret = DENY;
}
ret = mode_col ? ALLOW : DENY;
}
sqlite3_finalize(stmt);
sqlite3_finalize(stmt);
return ret;
}
/**
* Checks if the process or any of it's parents have permanent access to the
* file.
* Checks if a process or any of its ancestors have permanent access.
* Handles hierarchical permission inheritance.
*
* @param filename: The file that the process is trying to access
* @param 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.
* @param filename Path to the file being accessed
* @param pi Process information structure
* @return ALLOW/DENY/NDEF with NDEF meaning no explicit rule found
* @note May return false negatives if parent processes terminate during check
*/
access_t check_perm_access(const char *filename, struct process_info pi) {
if (pi.PID == 0 || pi.name == NULL) {
@ -262,13 +239,17 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
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;
// Traverse to parent process
while (current_pi.name == NULL) {
current_pi.PID = get_main_thread_pid(get_parent_pid(current_pi.PID));
if (current_pi.PID != 0) {
@ -283,11 +264,13 @@ access_t check_perm_access(const char *filename, struct process_info pi) {
}
/**
* Gives permanent access to the process to the file.
* Sets a permanent access rule for a process-file combination.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: 0 on success, 1 on failure
* @param filename Path to the file needing access
* @param pi 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 set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode) {
@ -299,19 +282,20 @@ int set_perm_access(const char *filename, struct process_info pi,
} else if (mode == SET_DENY) {
sql = "INSERT INTO permissions VALUES (?1, ?2, FALSE);";
} else {
return 1;
return 1; // Invalid mode
}
sqlite3_prepare_v2(perm_database, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, pi.name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, filename, -1, SQLITE_STATIC);
int step_ret = sqlite3_step(stmt);
if (step_ret != SQLITE_DONE) {
fprintf(stderr, "[ICFS] SQLite error: %s\n", sqlite3_errstr(step_ret));
sqlite3_finalize(stmt);
return 1;
}
sqlite3_finalize(stmt);
sqlite3_finalize(stmt);
return 0;
}

View File

@ -14,36 +14,38 @@
#include "set_mode_t.h"
/**
* Initializes the permanent permissions table.
* Initializes the permanent permissions database.
* Creates/opens the database file and sets up schema.
*
* @param db_filename: The filename of the permissions sqlite3 database
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
* @param db_filename Path to the SQLite database file
* @return 0 on success, -1 on failure
*/
int init_perm_permissions_table(const char *db_filename);
/**
* Destroys the permanent permissions table.
* Closes the database connection and releases resources.
* Should be called when the module is shutting down.
*/
void destroy_perm_permissions_table();
/**
* Checks if the process has a permanent access to the file.
* Checks if a process or any of its ancestors have permanent access.
* Handles hierarchical permission inheritance.
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found
* @param filename Path to the file being accessed
* @param pi Process information structure
* @return ALLOW/DENY/NDEF with NDEF meaning no explicit rule found
* @note May return false negatives if parent processes terminate during check
*/
access_t check_perm_access(const char *filename, struct process_info pi);
/**
* Gives permanent access to the process to the file.
* Sets a permanent access rule for a process-file combination.
*
* @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
* @param filename Path to the file needing access
* @param pi Process information
* @param mode SET_ALLOW (whitelist) or SET_DENY (blacklist)
* @return 0 on success, 1 on failure
*/
int set_perm_access(const char *filename, struct process_info pi,
set_mode_t mode);

View File

@ -5,7 +5,6 @@
This program can be distributed under the terms of the GNU GPLv2.
See the file LICENSE.
*/
#include "proc_operations.h"
#include <linux/limits.h>
#include <stddef.h>
@ -17,13 +16,19 @@
* @brief Returns the PID of the main thread (i.e., the process ID) of the
* process that the given thread ID (tid) belongs to.
*
* In Linux, threads within the same process share the same thread group ID
* (TGID), which is equal to the PID of the main thread. This function retrieves
* the TGID by parsing `/proc/<tid>/status`, effectively returning the main
* thread's PID.
*
* @param tid The thread ID (TID) of any thread in the process.
* @return pid_t The process ID (main thread's PID), or -1 on error.
* @return pid_t The process ID (main thread's PID), or 0 on error (invalid tid
* or file read failure).
*/
pid_t get_main_thread_pid(pid_t tid) {
// Validate input
if (tid <= 0) {
// errno = EINVAL;
// Invalid TID: TIDs are always positive in Linux
return 0;
}
@ -45,14 +50,31 @@ pid_t get_main_thread_pid(pid_t tid) {
}
fclose(fp);
// Debugging check: If the provided tid is not the main thread's tid,
// this logs a message but does not affect the return value.
// This is useful for detecting cases where non-main threads are being
// inspected.
if (tgid != tid) {
fprintf(stderr,
"[ICFS] The tid and and pid wasn't equal. tid:%d, pid:%d.\n", tid,
tgid);
}
return tgid;
}
/**
* @brief Retrieves the full path of the executable for a given process ID.
*
* This function reads the `/proc/<pid>/exe` symbolic link, which points to the
* executable file of the process. The returned string is dynamically allocated
* and must be freed by the caller.
*
* @param pid The process ID to query.
* @return char* Dynamically allocated string containing the executable path, or
* NULL on failure.
*/
char *get_process_name_by_pid(const int pid) {
char path[1024];
sprintf(path, "/proc/%d/exe", pid);
@ -74,6 +96,8 @@ char *get_process_name_by_pid(const int pid) {
free(name);
return NULL;
}
// If the buffer was too small, double its size and try again
if ((size_t)len >= size) {
size *= 2;
char *new_name = realloc(name, size);
@ -83,8 +107,8 @@ char *get_process_name_by_pid(const int pid) {
}
name = new_name;
} else {
// readlink does not set the null character
name[len] = 0;
// readlink does not null-terminate, so we must do it manually
name[len] = '\0';
break;
}
}
@ -93,16 +117,21 @@ char *get_process_name_by_pid(const int pid) {
}
/**
* Finds the parent process ID of a given process.
* @brief 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
* This function parses the `/proc/<pid>/status` file to extract the `PPid`
* field, which represents the parent process ID. This is useful for tracing
* process lineage.
*
* @param pid The process ID of the process to find the parent of.
* @return pid_t The parent process ID, or 0 if the parent could not be
* determined.
*/
pid_t get_parent_pid(pid_t pid) {
pid_t ppid = 0;
char path[256];
snprintf(path, sizeof(path), "/proc/%u/status", pid);
snprintf(path, sizeof(path), "/proc/%d/status",
pid); // Use %d for signed pid_t
FILE *file = fopen(path, "r");
if (file == NULL) {
@ -112,6 +141,7 @@ pid_t get_parent_pid(pid_t pid) {
char line[256];
while (fgets(line, sizeof(line), file)) {
// The "PPid:" field in `/proc/<pid>/status` is followed by a tab character
if (sscanf(line, "PPid:\t%d", &ppid) == 1) {
fclose(file);
return ppid;

View File

@ -11,14 +11,29 @@
#include <time.h>
/**
* @brief Retrieves the full path of the executable for a given process ID.
*
* This function reads the `/proc/<pid>/exe` symbolic link, which points to the
* executable file of the process. The returned string is dynamically allocated
* and must be freed by the caller.
*
* @param pid The process ID to query.
* @return char* Dynamically allocated string containing the executable path, or
* NULL on failure.
*/
char *get_process_name_by_pid(const int pid);
/**
* Finds the parent process ID of a given process.
* @brief 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
* This function parses the `/proc/<pid>/status` file to extract the `PPid`
* field, which represents the parent process ID. This is useful for tracing
* process lineage.
*
* @param pid The process ID of the process to find the parent of.
* @return pid_t The parent process ID, or 0 if the parent could not be
* determined.
*/
pid_t get_parent_pid(pid_t pid);
@ -26,8 +41,14 @@ pid_t get_parent_pid(pid_t pid);
* @brief Returns the PID of the main thread (i.e., the process ID) of the
* process that the given thread ID (tid) belongs to.
*
* In Linux, threads within the same process share the same thread group ID
* (TGID), which is equal to the PID of the main thread. This function retrieves
* the TGID by parsing `/proc/<tid>/status`, effectively returning the main
* thread's PID.
*
* @param tid The thread ID (TID) of any thread in the process.
* @return pid_t The process ID (main thread's PID), or -1 on error.
* @return pid_t The process ID (main thread's PID), or 0 on error (invalid tid
* or file read failure).
*/
pid_t get_main_thread_pid(pid_t tid);

View File

@ -9,7 +9,18 @@
#ifndef REAL_FILENAME_H
#define REAL_FILENAME_H
/**
* @brief Build a real path to the file.
*
* @param filename Relative path within the filesystem
* @return Newly allocated absolute path (caller must free)
*/
const char *real_filename(const char *filename);
/**
* @brief Get the current mount point path
* @return Absolute path to the filesystem root
*/
const char *get_mountpoint(void);
#endif // !REAL_FILENAME_H

View File

@ -16,20 +16,45 @@
#include <string.h>
#include <unistd.h>
/**
* @brief Global handle for the source filesystem implementation
*
* Stores the root directory information to enable relative path operations.
*/
static struct source_files_handle {
const char *mountpoint;
int root_fd;
const char *mountpoint; // Absolute path to the mounted filesystem root
int root_fd; // File descriptor for the root directory (O_PATH)
} handle;
/**
* @brief Translate FUSE paths to real paths in the underlying filesystem
*
* FUSE passes paths starting with '/' (e.g., "/dir/file"), but we need to
* operate relative to our root_fd. This function converts:
* - "/" -> "." (current directory)
* - "/dir/file" -> "dir/file"
*
* @param filename Path from FUSE (always starts with '/')
* @return Relative path suitable for use with root_fd
*/
const char *source_filename_translate(const char *filename) {
if (strcmp("/", filename) == 0) {
return ".";
} else {
return filename + 1;
}
return filename + 1; // Skip leading slash
}
/**
* @brief Initialize the source filesystem with a root directory
*
* Sets up the mount point and opens a protected file descriptor to the
* root directory for safe relative operations.
*
* @param root_path Absolute path to the physical root directory
* @return 0 on success, -1 on failure
*/
int source_init(const char *root_path) {
// Allocate memory for mount point path
handle.mountpoint = malloc(strlen(root_path) + 1);
if (handle.mountpoint == NULL) {
perror("[ICFS] Malloc failed");
@ -38,8 +63,9 @@ int source_init(const char *root_path) {
strcpy(handle.mountpoint, root_path);
// Open root directory with O_PATH to prevent accidental reads/writes
// while maintaining a valid descriptor for relative operations
int root_fd = open(root_path, O_PATH);
if (root_fd == -1) {
fprintf(stderr, "[ICFS] Could not initialize source file system at %s",
root_path);
@ -48,66 +74,115 @@ int source_init(const char *root_path) {
}
handle.root_fd = root_fd;
return 0;
}
void source_destroy(void) { free(handle.mountpoint); }
/**
* @brief Clean up resources used by the source filesystem
*/
void source_destroy(void) {
free(handle.mountpoint); // Free allocated mount point path
}
/**
* @brief Get the current mount point path
* @return Absolute path to the filesystem root
*/
const char *get_mountpoint(void) { return handle.mountpoint; }
/**
* @brief Build a real path to the file.
*
* @param filename Relative path within the filesystem
* @return Newly allocated absolute path (caller must free)
*/
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);
// Allocate space for combined path + null terminator
char *result = malloc(len1 + len2 + 1);
if (result == NULL) {
fprintf(stderr, "[ICFS] Memory allocation failed");
perror("");
return NULL;
}
// Copy strings
strcpy(result, mountpoint);
strcat(result, filename);
return result;
}
/**
* @brief Create a directory in the filesystem
*
* Uses mkdirat() to safely create directories relative to root_fd,
* preventing race conditions from concurrent path modifications.
*/
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);
}
/**
* @brief Remove a file from the filesystem
*
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove files relative
* to the root directory.
*/
int source_unlink(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
return unlinkat(handle.root_fd, relative_filename, 0);
}
/**
* @brief Get file status information
*
* Uses fstatat() to retrieve metadata relative to root_fd. Follows symlinks
* by default (flags=0).
*/
int source_stat(const char *restrict filename, struct stat *restrict statbuf) {
const char *relative_filename = source_filename_translate(filename);
return fstatat(handle.root_fd, relative_filename, statbuf, 0);
}
/**
* @brief Remove an empty directory
*
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove directories.
*/
int source_rmdir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
return unlinkat(handle.root_fd, relative_filename, AT_REMOVEDIR);
}
/**
* @brief Create a symbolic link
*
* Creates symlinks relative to the root_fd directory for safety.
*/
int source_symlink(const char *target, const char *linkpath) {
const char *relative_linkpath = source_filename_translate(linkpath);
return symlinkat(target, handle.root_fd, relative_linkpath);
}
/**
* @brief Check file access permissions
*
* Uses faccessat() to check access rights relative to root_fd.
*/
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);
}
/**
* @brief Open a directory for reading
*
* Combines openat() and fdopendir() to safely access directories relative
* to the root_fd.
*/
DIR *source_opendir(const char *filename) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
@ -119,6 +194,11 @@ DIR *source_opendir(const char *filename) {
return dir_pointer;
}
/**
* @brief Rename a file or directory
*
* Uses renameat() to safely rename within the same root_fd namespace.
*/
int source_rename(const char *oldpath, const char *newpath) {
const char *relative_oldpath = source_filename_translate(oldpath);
const char *relative_newpath = source_filename_translate(newpath);
@ -126,25 +206,48 @@ int source_rename(const char *oldpath, const char *newpath) {
relative_newpath);
}
/**
* @brief Create a hard link
*
* Uses linkat() with flags=0 (default behavior). May need AT_SYMLINK_NOFOLLOW
* if symlink handling should be modified.
*/
int source_link(const char *oldpath, const char *newpath) {
const char *relative_oldpath = source_filename_translate(oldpath);
const char *relative_newpath = source_filename_translate(newpath);
return linkat(handle.root_fd, relative_oldpath, handle.root_fd,
relative_newpath, 0);
// NOTE: perhaps the flags here need to be reevaluated.
}
/**
* @brief Change file access mode
*
* Uses fchmodat() with flags=0 (follow symlinks). Consider using
* AT_SYMLINK_NOFOLLOW if symlink metadata should be modified directly.
*/
int source_chmod(const char *filename, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return fchmodat(handle.root_fd, relative_filename, mode, 0);
// NOTE: perhaps the flags here need to be reevaluated.
}
/**
* @brief Change file owner and group
*
* Uses fchownat() with AT_SYMLINK_NOFOLLOW to modify symlink metadata
* rather than its target.
*/
int source_chown(const char *filename, uid_t owner, gid_t group) {
const char *relative_filename = source_filename_translate(filename);
return fchownat(handle.root_fd, filename, owner, group, AT_SYMLINK_NOFOLLOW);
}
/**
* @brief Truncate a file to a specified length
*
* Opens the file with read-only access then truncates it. This may fail
* if the file wasn't opened with write permissions. Consider changing
* openat() flags to O_WRONLY for reliability.
*/
int source_truncate(const char *filename, off_t length) {
const char *relative_filename = source_filename_translate(filename);
int fd = openat(handle.root_fd, relative_filename, 0);
@ -155,12 +258,22 @@ int source_truncate(const char *filename, off_t length) {
return ftruncate(fd, length);
}
/**
* @brief Open a file with specified flags
*
* Uses openat() to safely access files relative to root_fd.
*/
int source_open(const char *filename, int flags) {
const char *relative_filename = source_filename_translate(filename);
return openat(handle.root_fd, relative_filename, flags);
}
/**
* @brief Create and open a new file
*
* Uses openat() with O_CREAT to create files relative to root_fd.
*/
int source_create(const char *filename, int flags, mode_t mode) {
const char *relative_filename = source_filename_translate(filename);
return openat(handle.root_fd, relative_filename, flags, mode);
return openat(handle.root_fd, relative_filename, flags | O_CREAT, mode);
}

View File

@ -13,48 +13,129 @@
#include <sys/stat.h>
/**
* Initializes the source file handling.
* @brief Initialize the source filesystem with a root directory
*
* @param root_path The root of the source files folder.
* @return 0 on success, -1 on failure.
* Sets up the mount point and opens a protected file descriptor to the
* root directory for safe relative operations.
*
* @param root_path Absolute path to the physical root directory
* @return 0 on success, -1 on failure
*/
int source_init(const char *root_path);
/**
* @brief Clean up resources used by the source filesystem
*/
void source_destroy(void);
/* All of the functions below are designed to behave exactly as their non-source
* counterparts. */
/**
* @brief Get file status information
*
* Uses fstatat() to retrieve metadata relative to root_fd. Follows symlinks
* by default (flags=0).
*/
int source_stat(const char *restrict filename, struct stat *restrict statbuf);
struct dirent *source_readdir(DIR *dirp);
/**
* @brief Open a directory for reading
*
* Combines openat() and fdopendir() to safely access directories relative
* to the root_fd.
*/
DIR *source_opendir(const char *filename);
/**
* @brief Remove a file from the filesystem
*
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove files relative
* to the root directory.
*/
int source_unlink(const char *filename);
/**
* @brief Create a directory in the filesystem
*
* Uses mkdirat() to safely create directories relative to root_fd,
* preventing race conditions from concurrent path modifications.
*/
int source_mkdir(const char *filename, mode_t mode);
/**
* @brief Remove an empty directory
*
* Uses unlinkat() with AT_REMOVEDIR flag to safely remove directories.
*/
int source_rmdir(const char *filename);
/**
* @brief Create a symbolic link
*
* Creates symlinks relative to the root_fd directory for safety.
*/
int source_symlink(const char *target, const char *linkpath);
/**
* @brief Rename a file or directory
*
* Uses renameat() to safely rename within the same root_fd namespace.
*/
int source_rename(const char *oldpath, const char *newpath);
/**
* @brief Create a hard link
*
* Uses linkat() with flags=0 (default behavior). May need AT_SYMLINK_NOFOLLOW
* if symlink handling should be modified.
*/
int source_link(const char *oldpath, const char *newpath);
/**
* @brief Change file access mode
*
* Uses fchmodat() with flags=0 (follow symlinks). Consider using
* AT_SYMLINK_NOFOLLOW if symlink metadata should be modified directly.
*/
int source_chmod(const char *filename, mode_t mode);
/**
* @brief Change file owner and group
*
* Uses fchownat() with AT_SYMLINK_NOFOLLOW to modify symlink metadata
* rather than its target.
*/
int source_chown(const char *filename, uid_t owner, gid_t group);
/**
* @brief Truncate a file to a specified length
*
* Opens the file with read-only access then truncates it. This may fail
* if the file wasn't opened with write permissions. Consider changing
* openat() flags to O_WRONLY for reliability.
*/
int source_truncate(const char *filename, off_t length);
/**
* @brief Check file access permissions
*
* Uses faccessat() to check access rights relative to root_fd.
*/
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`. */
/**
* @brief Open a file with specified flags
*
* Uses openat() to safely access files relative to root_fd.
*/
int source_open(const char *filename, int flags);
/**
* @brief Create and open a new file
*
* Uses openat() with O_CREAT to create files relative to root_fd.
*/
int source_create(const char *filename, int flags, mode_t mode);
#endif // !SOURCEFS_H

View File

@ -19,26 +19,61 @@
#include <time.h>
#include <unistd.h>
#define GC_INTERVAL 1 // Garbage collection time interval.
/**
* @struct temp_process_permissions
* @brief Stores temporary file access permissions for a process
*
* This structure tracks temporary file access permissions for a specific
* process. The permissions are tied to the process's lifetime using its
* creation time.
*/
struct temp_process_permissions {
// yes, this is a correct type for start time in jiffies (see
// proc_pid_stat(5))
unsigned long long creation_time;
vec(char *) allowed_files;
vec(char *) denied_files;
unsigned long long creation_time; /**< Process creation time */
vec(char *) allowed_files; /**< List of allowed file paths (prefixes) */
vec(char *) denied_files; /**< List of denied file paths (prefixes) */
};
/**
* @brief Global table mapping PIDs to temporary permissions
*
* This map stores the temporary file access permissions for processes.
* Entries are keyed by process ID (pid_t) and contain temp_process_permissions
* structures with the access rules.
*/
map(pid_t, struct temp_process_permissions) temp_permissions_table;
/**
* @brief Read-write lock for thread-safe access to temp_permissions_table
*
* A read-write lock to ensure thread-safe operations on the
* temp_permissions_table. Allows concurrent reads but exclusive writes.
*/
pthread_rwlock_t temp_permissions_table_lock = PTHREAD_RWLOCK_INITIALIZER;
/**
* @brief Thread handle for the garbage collector
*
* This thread handles cleanup of stale entries in the temp_permissions_table.
*/
pthread_t gc_thread;
/**
* @brief Flag indicating whether garbage collector is active
*
* When non-zero, indicates that the garbage collector thread should continue
* running.
*/
int is_gc_active = 0;
/**
* Function to get the process creation time (in jiffies) from the proc
* Function to get the process creation time from the proc
* filesystem
*
* @param pid: The process ID of the process to get the creation time of
* @return: The process creation time in jiffies, or 0 on error
* @note: although nothing in the documentation says that the creation time is
* @param pid The process ID of the process to get the creation time of
* @return The process creation time, or 0 on error
* @note although nothing in the documentation says that the creation time is
* never really equal to 0, it exceptionally unlikely.
*/
unsigned long long get_process_creation_time(pid_t pid) {
@ -77,12 +112,23 @@ unsigned long long get_process_creation_time(pid_t pid) {
return creation_time;
}
/**
* @brief Validates if a process entry is still valid
*
* Checks if the given process entry matches the current process state
* by comparing creation times.
*
* @param pid Process ID to validate
* @param entry Pointer to the permission entry to validate
* @return 1 if valid, 0 if invalid or error
*/
int is_valid(pid_t pid, struct temp_process_permissions *entry) {
unsigned long long creation_time = get_process_creation_time(pid);
if (creation_time == 0) {
return 0;
}
// If the creation time doesn't match, this is a different process instance
if (creation_time != entry->creation_time) {
return 0;
}
@ -90,19 +136,30 @@ int is_valid(pid_t pid, struct temp_process_permissions *entry) {
return 1;
}
/**
* @brief Garbage collector thread for cleaning up stale entries
*
* Periodically scans the permissions table and removes entries for processes
* that no longer exist. Runs every second while is_gc_active is true.
*
* @param arg Unused thread argument parameter
* @return Always NULL
*/
void *garbage_collector(void *arg) {
(void)arg;
while (is_gc_active) {
sleep(1);
sleep(GC_INTERVAL); // Check once per second for stale entries
pthread_rwlock_wrlock(&temp_permissions_table_lock);
vec(pid_t) blacklist;
init(&blacklist);
// Identify stale entries
for_each(&temp_permissions_table, pid, entry) {
if (!is_valid(*pid, entry)) {
push(&blacklist, *pid);
// Free memory for this entry's file lists
for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); }
cleanup(&entry->allowed_files);
for_each(&entry->denied_files, denied_file) { free(*denied_file); }
@ -110,6 +167,7 @@ void *garbage_collector(void *arg) {
}
}
// Remove all stale entries from the table
for_each(&blacklist, pid) { erase(&temp_permissions_table, *pid); }
cleanup(&blacklist);
@ -123,7 +181,7 @@ void *garbage_collector(void *arg) {
/**
* Initializes the temporary permissions table.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
* @return 0 on success, -1 on failure
*/
int init_temp_permissions_table(void) {
init(&temp_permissions_table);
@ -132,7 +190,7 @@ int init_temp_permissions_table(void) {
/**
* Starts the temporary permissions table garbage_collector.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
* @return 0 on success, -1 on failure
*/
int init_garbage_collector(void) {
is_gc_active = 1;
@ -145,7 +203,7 @@ int init_garbage_collector(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.
*/
@ -155,7 +213,7 @@ void destroy_temp_permissions_table(void) {
pthread_join(gc_thread, NULL);
}
// free the memory allocated for the table
// 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);
@ -169,15 +227,17 @@ void destroy_temp_permissions_table(void) {
}
/**
* Checks if the process has a temporary access to the file.
* @brief Checks if a specific process has temporary access to a file
*
* @param filename: The file that the process is trying to access
* @param pid: PID of the process
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* found is avaliable
* Checks only the specified process (not its parents) for temporary access
* permissions to the given file. Uses a longest-match algorithm for path
* prefixes.
*
* @param filename Path to the file being accessed
* @param pid Process ID of the process to check
* @return Access status ALLOW, DENY, or NDEF (no information)
*/
access_t check_temp_access_noparent(const char *filename, pid_t pid) {
// TODO: more efficient locking
pthread_rwlock_rdlock(&temp_permissions_table_lock);
struct temp_process_permissions *permission_entry =
get(&temp_permissions_table, pid);
@ -190,13 +250,15 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
}
if (process_creation_time == permission_entry->creation_time) {
// the process is the same as the one that was granted temporary access
// to the file
// The process is the same as the one that was granted temporary access
size_t filename_len = strlen(filename);
access_t ret = NDEF;
size_t maxlen = 0;
// Check denied files first (deny takes precedence over allow)
for_each(&permission_entry->denied_files, denied_file) {
size_t denied_file_len = strlen(*denied_file);
// Check if this denied path prefix matches the requested file
if ((strncmp(*denied_file, filename, denied_file_len) == 0 &&
((denied_file_len < filename_len &&
(*denied_file)[denied_file_len - 1] == '/') ||
@ -206,17 +268,21 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
ret = DENY;
}
}
// Check allowed files
for_each(&permission_entry->allowed_files, allowed_file) {
size_t allowed_file_len = strlen(*allowed_file);
// Check if this allowed path prefix matches the requested 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))) &&
allowed_file > maxlen) {
allowed_file_len > maxlen) {
maxlen = allowed_file_len;
ret = ALLOW;
}
}
pthread_rwlock_unlock(&temp_permissions_table_lock);
return ret;
}
@ -229,12 +295,12 @@ access_t check_temp_access_noparent(const char *filename, pid_t pid) {
* 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
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* @param filename The file that the process is trying to access
* @param 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
* @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
* false negatives, though.
*/
access_t check_temp_access(const char *filename, struct process_info pi) {
@ -253,11 +319,11 @@ 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
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and
* @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.
* @return 0 on success, -1 on failure.
*/
int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode) {
@ -278,8 +344,7 @@ int set_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
// The process is the same as the one that was granted temporary access
if (mode == SET_ALLOW) {
push(&permission_entry->allowed_files, strdup(filename));
}
@ -290,19 +355,19 @@ int set_temp_access(const char *filename, struct process_info pi,
pthread_rwlock_unlock(&temp_permissions_table_lock);
return 0;
}
// we have an entry for the process, but the process is different
// delete the entry and create a new one
// We have an entry for the process, but the process is different
// Delete the entry and create a new one
erase(&temp_permissions_table, pi.PID);
permission_entry = NULL;
}
// no entry is present
// construct the entry
// No entry is present - construct a new one
struct temp_process_permissions new_permission_entry;
new_permission_entry.creation_time = get_process_creation_time(pi.PID);
init(&new_permission_entry.allowed_files);
init(&new_permission_entry.denied_files);
if (mode == SET_ALLOW) {
push(&new_permission_entry.allowed_files, strdup(filename));
}

View File

@ -16,21 +16,21 @@
/**
* Initializes the temporary permissions table.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
* @return: 0 on success, -1 on failure
*/
int init_temp_permissions_table(void);
/**
* Starts the temporary permissions table garbage_collector.
*
* @return: 0 on success, -1 on failure (e.g. ENOMEM)
* @return 0 on success, -1 on failure
*/
int init_garbage_collector(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.
*/
@ -40,12 +40,12 @@ void destroy_temp_permissions_table(void);
* 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
* @param pi: The process information
* @return: access status - ALLOW, DENY or NDEF in case if no information was
* @param filename The file that the process is trying to access
* @param 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
* @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
* false negatives, though.
*/
access_t check_temp_access(const char *filename, struct process_info pi);
@ -53,11 +53,11 @@ 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
* @param mode: Kind of access rule to be set - SET_DENY to deny access, and
* @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.
* @return 0 on success, -1 on failure.
*/
int set_temp_access(const char *filename, struct process_info pi,
set_mode_t mode);

View File

@ -10,7 +10,7 @@
#include <stddef.h>
#include <sys/types.h>
#include <time.h>
#define _GNU_SOURCE
#define _GNU_SOURCE // Required for certain POSIX extensions
#include "cc.h"
#include "perm_permissions_table.h"
#include "real_filename.h"
@ -26,34 +26,45 @@
#include <sys/un.h>
#include <unistd.h>
// Exit status codes for icfs_dialogue process interaction
#define DIALOGUE_YES 1
#define DIALOGUE_NO 0
#define DIALOGUE_PERM 2
#define DIALOGUE_TEMP 0
#define DIALOGUE_TEMP 0 // Bitmask position, not value
// Mutex to protect concurrent access to permission tables
pthread_mutex_t access_check_mutex = PTHREAD_MUTEX_INITIALIZER;
// Structure to hold user permission decision response
struct dialogue_response {
access_t decision;
char *filename;
};
/**
* Initialize UI socket and required permission tables
*
* @param perm_permissions_db_filename Path to persistent permissions DB
* @return 0 on success, 1 on failure
*/
int init_ui_socket(const char *perm_permissions_db_filename) {
FILE *fp = NULL;
// Initialize in-memory temporary permissions table
if (init_temp_permissions_table()) {
fprintf(stderr,
"[ICFS] Could not initialize temporary permissions table.\n");
return 1;
}
// Initialize permanent permissions database
if (init_perm_permissions_table(perm_permissions_db_filename)) {
fprintf(stderr,
"[ICFS] Could not initialize permanent permissions table.\n");
return 1;
}
// Test if dialogue is installed (get version)
// Verify dialogue utility is available
fp = popen("icfs_dialogue --version", "r");
if (fp == NULL) {
perror("[ICFS] Pipe returned an error");
@ -64,19 +75,23 @@ int init_ui_socket(const char *perm_permissions_db_filename) {
return 0;
}
/**
* Clean up UI socket resources
*/
void destroy_ui_socket(void) {
destroy_temp_permissions_table();
destroy_perm_permissions_table();
}
/**
* Asks the user if the process should be allowed to access the file using the
* GUI
* Query user for file access permission through GUI dialogue
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @return: access status - ALLOW, DENY or ALLOW_TEMP
* allowed for the runtime of the process
* Constructs and executes the icfs_dialogue command to get user consent.
* Handles memory allocation errors gracefully and parses the response.
*
* @param filename File being accessed
* @param proc_info Process requesting access
* @return Struct containing access decision and resolved filename
*/
struct dialogue_response ask_access(const char *filename,
struct process_info proc_info) {
@ -90,26 +105,21 @@ struct dialogue_response ask_access(const char *filename,
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.
// Memory allocation failed - create minimal fallback response
fprintf(stderr, "[ICFS] 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
// Execute permission dialogue
fp = popen(command, "r");
free(command);
if (fp == NULL) {
perror("[ICFS] Pipe returned a error");
response.decision = DENY;
response.filename = malloc(2);
response.filename[0] = '/';
response.filename[1] = 0;
@ -119,9 +129,9 @@ struct dialogue_response ask_access(const char *filename,
str(char) dialogue_output;
init(&dialogue_output);
char line[1024]; // Buffer to read individual lines
char line[1024];
// Read the command output line by line
// Read entire command output
while (fgets(line, sizeof(line), fp)) {
push_fmt(&dialogue_output, line);
}
@ -131,20 +141,20 @@ struct dialogue_response ask_access(const char *filename,
fprintf(stderr, "[ICFS] dialogue wrote out %s\n", first(&dialogue_output));
fprintf(stderr, "[ICFS] dialogue returned %d\n", dialogue_exit_code);
// Handle empty output case
if (size(&dialogue_output) == 0) {
push(&dialogue_output, '/');
}
// Validate string length consistency
assert(strlen(first(&dialogue_output)) == size(&dialogue_output));
// Allocate and copy final filename
response.filename = malloc(size(&dialogue_output) + 1);
strcpy(response.filename, first(&dialogue_output));
// response.filename[size(&dialogue_output)] = 0;
// assert(0 == strcmp(response.filename, first(&dialogue_output)));
cleanup(&dialogue_output);
time_t now = time(0);
// Parse exit code combination
if (dialogue_exit_code == (DIALOGUE_YES | DIALOGUE_PERM)) {
response.decision = ALLOW;
} else if (dialogue_exit_code == DIALOGUE_YES | DIALOGUE_TEMP) {
@ -159,68 +169,58 @@ struct dialogue_response ask_access(const char *filename,
}
/**
* Check access according to:
* 1. temp permission table
* 2. permanent permission table
* 3. user descision
* Determine file access based on permission tables and user input
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @param opts: options (GRANT_TEMP, GRANT_PERM)
* @return: 0 if access is denied, 1 if access is allowed
* Checks permissions in order:
* 1. Temporary permission table
* 2. Permanent permission table
* 3. User decision via GUI
*
* @param filename File being accessed
* @param pi Process information
* @param opts Flags to force permission (GRANT_TEMP/GRANT_PERM)
* @return 1 if access allowed, 0 if denied
*/
int interactive_access(const char *filename, struct process_info proc_info,
int opts) {
char *real_path = real_filename(filename);
pthread_mutex_lock(&access_check_mutex);
// First check temporary permissions
access_t access = check_temp_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(
stderr,
"[ICFS] Permission allowed to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
fprintf(stderr, "[ICFS] Permission allowed to %s based on temp table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
if (access == DENY) {
fprintf(
stderr,
"[ICFS] Permission denied to %s based on a rule present in the temp "
"permission table.\n",
proc_info.name);
fprintf(stderr, "[ICFS] Permission denied to %s based on temp table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 0;
}
// Then check permanent permissions
access = check_perm_access(real_path, proc_info);
if (access == ALLOW) {
fprintf(
stderr,
"[ICFS] Permission allowed to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
fprintf(stderr, "[ICFS] Permission allowed to %s based on perm table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 1;
}
if (access == DENY) {
fprintf(
stderr,
"[ICFS] Permission denied to %s based on a rule present in the perm "
"permission table.\n",
proc_info.name);
fprintf(stderr, "[ICFS] Permission denied to %s based on perm table.\n",
proc_info.name);
free(real_path);
pthread_mutex_unlock(&access_check_mutex);
return 0;
}
// if noth GRANT_TEMP and GRANT_PERM are selected, then only permanent
// permissions are granted
// Handle forced permission grants
if (opts & GRANT_PERM) {
fprintf(stderr, "[ICFS] Permission granted permanently to %s.\n",
proc_info.name);
@ -238,66 +238,59 @@ int interactive_access(const char *filename, struct process_info proc_info,
return 1;
}
// Get user decision
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
// Validate returned filename meets requirements:
// 1. Must exist
// 2. Must be prefix of original filename with trailing slash
// or exact match
while (
source_access(response.filename, F_OK) ||
!(strncmp(response.filename, filename, strlen(response.filename)) == 0 &&
((strlen(response.filename) < strlen(filename) &&
response.filename[strlen(response.filename) - 1] == '/') ||
(strlen(response.filename) == strlen(filename))))) {
// if it is invalid, just ask again.
fprintf(stderr,
"[ICFS] Filename returned by access dialogue wasn't correct: %s\n",
fprintf(stderr, "[ICFS] Invalid filename returned by dialogue: %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);
int ret = 0;
// Apply user decision to appropriate permission table
if (response.decision == ALLOW) {
fprintf(stderr,
"[ICFS] Permission granted permanently to %s based on zenty "
"response.\n",
"[ICFS] Permission granted permanently to %s based on response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_ALLOW);
ret = 1;
} else if (response.decision == ALLOW_TEMP) {
fprintf(stderr,
"[ICFS] Permission granted temporarily to %s based on zenty "
"response.\n",
"[ICFS] Permission granted temporarily to %s based on response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_ALLOW);
ret = 1;
} else if (response.decision == DENY_TEMP) {
fprintf(
stderr,
"[ICFS] Permission denied temporarily to %s based on zenty response.\n",
proc_info.name);
fprintf(stderr,
"[ICFS] Permission denied temporarily to %s based on response.\n",
proc_info.name);
set_temp_access(real_path, proc_info, SET_DENY);
ret = 0;
} else if (response.decision == DENY) {
fprintf(
stderr,
"[ICFS] Permission denied permanently to %s based on zenty response.\n",
proc_info.name);
fprintf(stderr,
"[ICFS] Permission denied permanently to %s based on response.\n",
proc_info.name);
set_perm_access(real_path, proc_info, SET_DENY);
ret = 0;
}
pthread_mutex_unlock(&access_check_mutex);
pthread_mutex_unlock(&access_check_mutex);
free(real_path);
// deny on unknown options.
return ret;
}

View File

@ -6,10 +6,6 @@
See the file LICENSE.
*/
/*
* Interface for controlling communication with the UI.
*/
#ifndef UI_SOCKET_H
#define UI_SOCKET_H
@ -17,27 +13,30 @@
#include <sys/types.h>
/**
* Initialize the GUI communication.
* Initialize UI socket and required permission tables
*
* @return: 0 on success, -1 on faliure.
* @param perm_permissions_db_filename Path to persistent permissions DB
* @return 0 on success, 1 on failure
*/
int init_ui_socket(const char *perm_permissions_db_filename);
/**
* Close the GUI communication.
* Clean up UI socket resources
*/
void destroy_ui_socket(void);
/**
* Check access according to:
* 1. temporary permission table
* 2. permanent permission table
* 3. user descision
* Determine file access based on permission tables and user input
*
* @param filename: The file that the process is trying to access
* @param pi: The process information
* @param opts: options (GRANT_TEMP, GRANT_PERM)
* @return: 0 if access is denied, 1 if access is allowed
* Checks permissions in order:
* 1. Temporary permission table
* 2. Permanent permission table
* 3. User decision via GUI
*
* @param filename File being accessed
* @param pi Process information
* @param opts Flags to force permission (GRANT_TEMP/GRANT_PERM)
* @return 1 if access allowed, 0 if denied
*/
int interactive_access(const char *filename, struct process_info pi, int opts);

View File

@ -52,8 +52,7 @@ elif [[ $1 == "--performance" ]]; then
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 --leak-check=full -s ../build/icfs -o default_permissions -o debug ./protected ./.pt.db 2>&1 | grep "==\|zenity\|Permission\|column\|callback\|SQLite" &
valgrind --leak-check=full --show-leak-kinds=all -s ../build/icfs -o default_permissions ./protected ./.pt.db &
valgrind --leak-check=full -s ../build/icfs -o default_permissions ./protected ./.pt.db &
sleep 5
fi
@ -213,14 +212,10 @@ else
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
# performance testing code
RUNS_NUM=500
if [[ $1 == '--performance' ]]; then
#warmup
icfs_dialogue --set-fake-response yes
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener10"
icfs_dialogue --set-fake-response yes
echo "[ICFS-TEST]: temp permissions"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener7"
@ -228,7 +223,6 @@ if [[ $1 == '--performance' ]]; then
icfs_dialogue --set-fake-response yes_perm
echo "[ICFS-TEST]: perm permissions"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener8"
fi
# unmount
@ -238,11 +232,9 @@ sleep 0.5
umount "$(realpath ./protected)"
sleep 3
# test the same thing, but without ICFS mounted
if [[ $1 == '--performance' ]]; then
#warmup
parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
echo "[ICFS-TEST]: bare filesystem"
time parallel -j8 ::: "./stress.bash $RUNS_NUM openers/opener9"
fi