/* ICFS: Interactively Controlled File System Copyright (C) 2024-2025 Fedir Kovalov This program can be distributed under the terms of the GNU GPLv2. See the file LICENSE. */ #include "temp_permissions_table.h" #include "access_t.h" #include "cc.h" #include "process_info.h" #include #include #include 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; }; map(pid_t, struct temp_process_permissions) temp_permissions_table; pthread_mutex_t temp_permissions_table_lock; /** * Function to get the process creation time (in jiffies) 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 * never really equal to 0, it exceptionally unlikely. */ unsigned long long get_process_creation_time(pid_t pid) { char path[256]; FILE *fp = NULL; unsigned long long creation_time = 0; // Construct the path to the process's status file snprintf(path, sizeof(path), "/proc/%u/stat", pid); // Open the status file fp = fopen(path, "r"); if (fp == NULL) { perror("fopen"); return 0; } // Read the creation time (the 22nd field in the stat file) for (int i = 1; i < 22; i++) { if (fscanf(fp, "%*s") == EOF) { fprintf(stderr, "Error reading process stat file on the number %d\n", i); fclose(fp); return 0; } } if (fscanf(fp, "%llu", &creation_time) != 1) { fprintf(stderr, "Error reading creation time\n"); fclose(fp); return 0; } // Close the file fclose(fp); return creation_time; } /** * Initializes the temporary permissions table. * * @return: 0 on success, -1 on failure (e.g. ENOMEM) */ int init_temp_permissions_table(void) { pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT); init(&temp_permissions_table); return 0; } /** * Destroys the temporary permissions table. * * @note: the table is guranteed to be destroyed if it is already initialized. * It does not indicate any errors whatsoever. If something goes wrong - you are * screwed. */ void destroy_temp_permissions_table(void) { // free the memory allocated for the table for_each(&temp_permissions_table, entry) { for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); } cleanup(&entry->allowed_files); } for_each(&temp_permissions_table, entry) { for_each(&entry->denied_files, denied_file) { free(*denied_file); } cleanup(&entry->denied_files); } cleanup(&temp_permissions_table); pthread_mutex_destroy(&temp_permissions_table_lock); } /** * Checks if the process has a temporary access to the file. * * @param filename: The file that the process is trying to access * @pram pid: PID of the process * @return: access status - ALLOW, DENY or NDEF in case if no information was * found is avaliable */ access_t check_temp_access_noparent(const char *filename, pid_t pid) { // TODO: more efficient locking pthread_mutex_lock(&temp_permissions_table_lock); struct temp_process_permissions *permission_entry = get(&temp_permissions_table, pid); if (permission_entry != NULL) { unsigned long long process_creation_time = get_process_creation_time(pid); if (process_creation_time == 0) { perror("Could not retrieve process creation time"); pthread_mutex_unlock(&temp_permissions_table_lock); return NDEF; } if (process_creation_time == permission_entry->creation_time) { // the process is the same as the one that was granted temporary access // to the file for_each(&permission_entry->denied_files, denied_file) { if (strncmp(*denied_file, filename, strlen(filename)) == 0) { pthread_mutex_unlock(&temp_permissions_table_lock); return DENY; } } for_each(&permission_entry->allowed_files, allowed_file) { if (strncmp(*allowed_file, filename, strlen(filename)) == 0) { pthread_mutex_unlock(&temp_permissions_table_lock); return ALLOW; } } } } pthread_mutex_unlock(&temp_permissions_table_lock); 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//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 * file. * * @param filename: The file that the process is trying to access * @pram pi: The process information * @return: access status - ALLOW, DENY or NDEF in case if no information was * found. Does not return ALLOW_TEMP. * @note: In case one of the parent processes is killed while this function * execution the result is not guranteed to be correct. It should only lead to * false negatives, though. */ access_t check_temp_access(const char *filename, struct process_info pi) { pid_t current_pid = pi.PID; while (current_pid != 0) { access_t access = check_temp_access_noparent(filename, current_pid); if (access != NDEF) { return access; } current_pid = get_parent_pid(current_pid); } return NDEF; } /** * 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 * SET_ALLOW to allow access. * @return: 0 on success, -1 on failure. */ int set_temp_access(const char *filename, struct process_info pi, set_mode_t mode) { pthread_mutex_lock(&temp_permissions_table_lock); struct temp_process_permissions *permission_entry = get(&temp_permissions_table, pi.PID); if (permission_entry != NULL) { unsigned long long process_creation_time = get_process_creation_time(pi.PID); if (process_creation_time == 0) { perror("Could not retrieve process creation time"); pthread_mutex_unlock(&temp_permissions_table_lock); return -1; } if (process_creation_time == permission_entry->creation_time) { // the process is the same as the one that was granted temporary access // to the file if (mode == SET_ALLOW) { push(&permission_entry->allowed_files, strdup(filename)); } if (mode == SET_DENY) { push(&permission_entry->denied_files, strdup(filename)); } pthread_mutex_unlock(&temp_permissions_table_lock); return 0; } // 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 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)); } if (mode == SET_DENY) { push(&new_permission_entry.denied_files, strdup(filename)); } insert(&temp_permissions_table, pi.PID, new_permission_entry); pthread_mutex_unlock(&temp_permissions_table_lock); return 0; }