From 92378c1cde6b4e06535a3288947ae04bb67c4cf1 Mon Sep 17 00:00:00 2001 From: fedir Date: Tue, 15 Apr 2025 18:57:57 +0200 Subject: [PATCH] add exerimental deny flag in the tables For now, perm table does not set the thrid parameter to anything useful --- src/perm_permissions_table.c | 88 +++++++++++++++++++----------------- src/perm_permissions_table.h | 6 ++- src/temp_permissions_table.c | 74 +++++++++++++++++++++--------- src/temp_permissions_table.h | 43 ++++++++++++------ src/ui-socket.c | 48 +++++++++++++------- 5 files changed, 163 insertions(+), 96 deletions(-) diff --git a/src/perm_permissions_table.c b/src/perm_permissions_table.c index d254013..04bcb37 100644 --- a/src/perm_permissions_table.c +++ b/src/perm_permissions_table.c @@ -7,6 +7,7 @@ */ #include "perm_permissions_table.h" +#include "access_t.h" #include "process_info.h" #include #include @@ -23,9 +24,9 @@ sqlite3 *perm_database = NULL; const char *const table_name = "permissions"; // one row corresponds to a permission to access one file for one executable -const int column_count = 2; -const char *const schema[] = {"executable", "filename"}; -const char *const types[] = {"TEXT", "TEXT"}; +const int column_count = 3; +const char *const schema[] = {"executable", "filename", "mode"}; +const char *const types[] = {"TEXT", "TEXT", "INTEGER"}; uid_t ruid, euid, current_pid; pthread_mutex_t uid_switch = PTHREAD_MUTEX_INITIALIZER; @@ -69,9 +70,10 @@ static int check_table_col_schema(void *notused, int argc, char **argv, } int column_num = atoi(argv[0]); if (column_num >= column_count) { - fprintf(stderr, "Table contains more columns than expected.\n"); + fprintf(stderr, "Table contains unexpected amount of columns.\n"); return 1; } + if (strcmp(schema[column_num], argv[1]) == 0 && strcmp(types[column_num], argv[2]) == 0) { return 0; @@ -90,8 +92,9 @@ static int set_flag(void *flag, int argc, char **argv, char **colname) { int create_database_schema() { fprintf(stderr, "Creating table 'permissions'.\n"); - const char *create_query = "CREATE TABLE permissions(executable TEXT NOT " - "NULL, filename TEXT NOT NULL);"; + const char *create_query = + "CREATE TABLE permissions(executable TEXT NOT " + "NULL, filename TEXT NOT NULL, mode INTEGER NOT NULL);"; char *err = NULL; int ret = sqlite3_exec(perm_database, create_query, NULL, NULL, &err); @@ -186,37 +189,41 @@ void destroy_perm_permissions_table() { sqlite3_close(perm_database); } * * @param filename: The file that the process is trying to access * @pram pi: The process information - * @return: 0 if access is denied, 1 if access is allowed + * @return: access status - ALLOW, DENY or NDEF in case if no information was + * found */ -int check_perm_access(const char *filename, struct process_info pi) { - size_t query_len = - 56 + strlen(table_name) + strlen(filename) + strlen(pi.name); - const char *query = malloc(query_len); - size_t should_be_written = snprintf( - query, query_len, - "SELECT * FROM %s WHERE executable = \'%s\' AND filename = \'%s\';", - table_name, pi.name, filename); - // -1 for the \0 - if (should_be_written != query_len - 1) { - fprintf(stderr, - "Unexpected query size while permanent access rule check: " - "Expected %lu, but snprintf returned %lu. The query: %s\n", - query_len, should_be_written, query); - return 0; +access_t check_perm_access(const char *filename, struct process_info pi) { + + char *query = NULL; + int ret = asprintf(&query, + "SELECT * FROM %s WHERE executable = \'%s\' " + "AND filename = \'%s\' AND mode = TRUE;", + table_name, pi.name, filename); + + if (ret < 0) { + // If asprintf fails, the contents of query are undefined (see man + // asprintf). That does not explicitly rule out that query will be a valid + // pointer. But the risk of freeing a non-allocated pointer is too much to + // justify preparing for this. + fprintf(stderr, "Could not create query on access check"); + perror(""); + return NDEF; } char *sqlite_error = NULL; int flag = 0; - int ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error); + ret = sqlite3_exec(perm_database, query, set_flag, &flag, &sqlite_error); + free((void *)query); if (ret != SQLITE_OK) { fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error); sqlite3_free(sqlite_error); - free(query); - return 0; + return NDEF; } - free(query); - return flag; + if (flag) { + return ALLOW; + } + return NDEF; } /** @@ -227,23 +234,23 @@ int check_perm_access(const char *filename, struct process_info pi) { * @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); + char *query = NULL; + int ret = asprintf(&query, "INSERT INTO %s VALUES (\'%s\', \'%s\', TRUE);", + table_name, pi.name, filename); + + if (ret < 0) { + // If asprintf fails, the contents of query are undefined (see man + // asprintf). That does not explicitly rule out that query will be a valid + // pointer. But the risk of freeing a non-allocated pointer is too much to + // justify preparing for this. + fprintf(stderr, "Could not create query on rule insertion"); + perror(""); return 1; } char *sqlite_error = NULL; - int ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error); + ret = sqlite3_exec(perm_database, query, NULL, NULL, &sqlite_error); + free(query); if (ret != SQLITE_OK) { fprintf(stderr, "SQLite returned an error: %s\n", sqlite_error); sqlite3_free(sqlite_error); @@ -251,6 +258,5 @@ int give_perm_access(const char *filename, struct process_info pi) { return 1; } - free(query); return 0; } diff --git a/src/perm_permissions_table.h b/src/perm_permissions_table.h index e5a76c3..f42b4fe 100644 --- a/src/perm_permissions_table.h +++ b/src/perm_permissions_table.h @@ -9,6 +9,7 @@ #ifndef PERM_PERMISSION_TABLE_H #define PERM_PERMISSION_TABLE_H +#include "access_t.h" #include "process_info.h" /** @@ -29,9 +30,10 @@ void destroy_perm_permissions_table(); * * @param filename: The file that the process is trying to access * @pram pi: The process information - * @return: 0 if access is denied, 1 if access is allowed + * @return: access status - ALLOW, DENY or NDEF in case if no information was + * found */ -int check_perm_access(const char *filename, struct process_info pi); +access_t check_perm_access(const char *filename, struct process_info pi); /** * Gives permanent access to the process to the file. diff --git a/src/temp_permissions_table.c b/src/temp_permissions_table.c index 3167142..e3c8e47 100644 --- a/src/temp_permissions_table.c +++ b/src/temp_permissions_table.c @@ -7,6 +7,7 @@ */ #include "temp_permissions_table.h" +#include "access_t.h" #include "cc.h" #include "process_info.h" #include @@ -18,6 +19,7 @@ struct temp_process_permissions { // proc_pid_stat(5)) unsigned long long creation_time; vec(char *) allowed_files; + vec(char *) denied_files; }; map(pid_t, struct temp_process_permissions) temp_permissions_table; @@ -33,8 +35,8 @@ pthread_mutex_t temp_permissions_table_lock; * never really equal to 0, it exceptionally unlikely. */ unsigned long long get_process_creation_time(pid_t pid) { - char path[32]; - FILE *fp; + char path[256]; + FILE *fp = NULL; unsigned long long creation_time = 0; // Construct the path to the process's status file @@ -72,7 +74,7 @@ unsigned long long get_process_creation_time(pid_t pid) { * * @return: 0 on success, -1 on failure (e.g. ENOMEM) */ -int init_temp_permissions_table() { +int init_temp_permissions_table(void) { pthread_mutex_init(&temp_permissions_table_lock, PTHREAD_MUTEX_DEFAULT); init(&temp_permissions_table); return 0; @@ -81,14 +83,21 @@ int init_temp_permissions_table() { /** * Destroys the temporary permissions table. * - * @note: the table is guranteed to be destroyed if it is already initialized + * @note: the table is guranteed to be destroyed if it is already initialized. + * It does not indicate any errors whatsoever. If something goes wrong - you are + * screwed. */ -void destroy_temp_permissions_table() { +void destroy_temp_permissions_table(void) { // free the memory allocated for the table for_each(&temp_permissions_table, entry) { for_each(&entry->allowed_files, allowed_file) { free(*allowed_file); } cleanup(&entry->allowed_files); } + for_each(&temp_permissions_table, entry) { + for_each(&entry->denied_files, denied_file) { free(*denied_file); } + cleanup(&entry->denied_files); + } + cleanup(&temp_permissions_table); pthread_mutex_destroy(&temp_permissions_table_lock); } @@ -98,10 +107,10 @@ void destroy_temp_permissions_table() { * * @param filename: The file that the process is trying to access * @pram pid: PID of the process - * @return: 0 if access is denied, 1 if access is allowed + * @return: access status - ALLOW, DENY or NDEF in case if no information was + * found is avaliable */ - -int check_temp_access_noparent(const char *filename, pid_t pid) { +access_t check_temp_access_noparent(const char *filename, pid_t pid) { // TODO: more efficient locking pthread_mutex_lock(&temp_permissions_table_lock); struct temp_process_permissions *permission_entry = @@ -111,22 +120,28 @@ int check_temp_access_noparent(const char *filename, pid_t pid) { if (process_creation_time == 0) { perror("Could not retrieve process creation time"); pthread_mutex_unlock(&temp_permissions_table_lock); - return 0; + return NDEF; } if (process_creation_time == permission_entry->creation_time) { // the process is the same as the one that was granted temporary access // to the file + for_each(&permission_entry->denied_files, denied_file) { + if (strncmp(*denied_file, filename, strlen(filename)) == 0) { + pthread_mutex_unlock(&temp_permissions_table_lock); + return DENY; + } + } for_each(&permission_entry->allowed_files, allowed_file) { if (strncmp(*allowed_file, filename, strlen(filename)) == 0) { pthread_mutex_unlock(&temp_permissions_table_lock); - return 1; + return ALLOW; } } } } pthread_mutex_unlock(&temp_permissions_table_lock); - return 0; + return NDEF; } /** @@ -165,31 +180,36 @@ pid_t get_parent_pid(pid_t pid) { * * @param filename: The file that the process is trying to access * @pram pi: The process information - * @return: 0 if access is denied, 1 if access is allowed + * @return: access status - ALLOW, DENY or NDEF in case if no information was + * found. Does not return ALLOW_TEMP. * @note: In case one of the parent processes is killed while this function * execution the result is not guranteed to be correct. It should only lead to * false negatives, though. */ -int check_temp_access(const char *filename, struct process_info pi) { +access_t check_temp_access(const char *filename, struct process_info pi) { pid_t current_pid = pi.PID; while (current_pid != 0) { - if (check_temp_access_noparent(filename, current_pid)) { - return 1; + access_t access = check_temp_access_noparent(filename, current_pid); + if (access != NDEF) { + return access; } current_pid = get_parent_pid(current_pid); } - return 0; + return NDEF; } /** - * Gives temporary access to the process to the file. + * Sets temporary access mode of the process to the file. * * @param filename: The file that the process is trying to access * @param pi: The process information - * @return: 0 on success, -1 on failure (e.g. ENOMEM) + * @param mode: Kind of access rule to be set - SET_DENY to deny access, and + * SET_ALLOW to allow access. + * @return: 0 on success, -1 on failure. */ -int give_temp_access(const char *filename, struct process_info pi) { +int set_temp_access(const char *filename, struct process_info pi, + set_mode_t mode) { pthread_mutex_lock(&temp_permissions_table_lock); struct temp_process_permissions *permission_entry = get(&temp_permissions_table, pi.PID); @@ -207,7 +227,13 @@ int give_temp_access(const char *filename, struct process_info pi) { if (process_creation_time == permission_entry->creation_time) { // the process is the same as the one that was granted temporary access // to the file - push(&permission_entry->allowed_files, strdup(filename)); + if (mode == SET_ALLOW) { + push(&permission_entry->allowed_files, strdup(filename)); + } + if (mode == SET_DENY) { + push(&permission_entry->denied_files, strdup(filename)); + } + pthread_mutex_unlock(&temp_permissions_table_lock); return 0; } @@ -223,7 +249,13 @@ int give_temp_access(const char *filename, struct process_info pi) { new_permission_entry.creation_time = get_process_creation_time(pi.PID); init(&new_permission_entry.allowed_files); - push(&new_permission_entry.allowed_files, strdup(filename)); + init(&new_permission_entry.denied_files); + if (mode == SET_ALLOW) { + push(&new_permission_entry.allowed_files, strdup(filename)); + } + if (mode == SET_DENY) { + push(&new_permission_entry.denied_files, strdup(filename)); + } insert(&temp_permissions_table, pi.PID, new_permission_entry); diff --git a/src/temp_permissions_table.h b/src/temp_permissions_table.h index 32773bd..9320e27 100644 --- a/src/temp_permissions_table.h +++ b/src/temp_permissions_table.h @@ -2,6 +2,7 @@ #ifndef TEMP_PERMISSIONS_TABLE_H #define TEMP_PERMISSIONS_TABLE_H +#include "access_t.h" #include "process_info.h" /** @@ -9,31 +10,43 @@ * * @return: 0 on success, -1 on failure (e.g. ENOMEM) */ -int init_temp_permissions_table(); +int init_temp_permissions_table(void); /** * Destroys the temporary permissions table. * - * @note: the table is guranteed to be destroyed if it is already initialized + * @note: the table is guranteed to be destroyed if it is already initialized. + * It does not indicate any errors whatsoever. If something goes wrong - you are + * screwed. */ -void destroy_temp_permissions_table(); +void destroy_temp_permissions_table(void); /** - * Checks if the process has a temporary access to the file. + * Checks if the process or any of it's parents have temporary access to the + * file. + * + * @param filename: The file that the process is trying to access + * @pram pi: The process information + * @return: access status - ALLOW, DENY or NDEF in case if no information was + * found. Does not return ALLOW_TEMP. + * @note: In case one of the parent processes is killed while this function + * execution the result is not guranteed to be correct. It should only lead to + * false negatives, though. + */ +access_t check_temp_access(const char *filename, struct process_info pi); + +typedef enum { SET_DENY, SET_ALLOW } set_mode_t; + +/** + * Sets temporary access mode of the process to the file. * * @param filename: The file that the process is trying to access * @param pi: The process information - * @return: 0 if access is denied, 1 if access is allowed + * @param mode: Kind of access rule to be set - SET_DENY to deny access, and + * SET_ALLOW to allow access. + * @return: 0 on success, -1 on failure. */ -int check_temp_access(const char *filename, struct process_info pi); - -/** - * Gives temporary access to the process to the file. - * - * @param filename: The file that the process is trying to access - * @param pi: The process information - * @return: 0 on success, -1 on failure (e.g. ENOMEM) - */ -int give_temp_access(const char *filename, struct process_info pi); +int set_temp_access(const char *filename, struct process_info pi, + set_mode_t mode); #endif // !TEMP_PERMISSIONS_TABLE_H diff --git a/src/ui-socket.c b/src/ui-socket.c index 8731697..166227b 100644 --- a/src/ui-socket.c +++ b/src/ui-socket.c @@ -6,6 +6,7 @@ See the file LICENSE. */ +#include "access_t.h" #include #include #include @@ -58,10 +59,10 @@ void destroy_ui_socket(void) { * * @param filename: The file that the process is trying to access * @param pi: The process information - * @return: 0 if access is denied, 1 if access is allowed, 2 if access is + * @return: access status - ALLOW, DENY or ALLOW_TEMP * allowed for the runtime of the process */ -int ask_access(const char *filename, struct process_info proc_info) { +access_t ask_access(const char *filename, struct process_info proc_info) { FILE *fp = NULL; size_t command_len = 139 + sizeof(pid_t) * 8 + strlen(proc_info.name) + strlen(filename); @@ -78,7 +79,7 @@ int ask_access(const char *filename, struct process_info proc_info) { if (fp == NULL) { perror("Pipe returned a error"); - return -1; + return DENY; } // if the user clicks the "Allow this time" button, `zenity` will only @@ -89,7 +90,7 @@ int ask_access(const char *filename, struct process_info proc_info) { printf("%s", buffer); if (strcmp(buffer, ZENITY_TEMP_ALLOW_MESSAGE) == 0) { pclose(fp); - return 2; + return ALLOW_TEMP; } } @@ -97,10 +98,10 @@ int ask_access(const char *filename, struct process_info proc_info) { fprintf(stderr, "zenity returned %d\n", zenity_exit_code); // zenity returns 1 on "No" >:( if (zenity_exit_code == 0) { - return 1; + return ALLOW; } - return 0; + return DENY; } /** @@ -117,11 +118,21 @@ int ask_access(const char *filename, struct process_info proc_info) { int interactive_access(const char *filename, struct process_info proc_info, int opts) { - if (check_temp_access(filename, proc_info) || - check_perm_access(filename, proc_info)) { - // access was already granted before + access_t access = check_temp_access(filename, proc_info); + if (access == ALLOW) { return 1; } + if (access == DENY) { + return 0; + } + + access = check_perm_access(filename, proc_info); + if (access == ALLOW) { + return 1; + } + if (access == DENY) { + return 0; + } // if noth GRANT_TEMP and GRANT_PERM are selected, then only permanent // permissions are granted @@ -131,23 +142,26 @@ int interactive_access(const char *filename, struct process_info proc_info, return 1; } if (opts & GRANT_TEMP) { - give_temp_access(filename, proc_info); + set_temp_access(filename, proc_info, SET_ALLOW); return 1; } - int user_response = ask_access(filename, proc_info); - if (user_response == 1) { - // user said "yes" + access_t user_response = ask_access(filename, proc_info); + if (user_response == ALLOW) { give_perm_access(filename, proc_info); return 1; } - if (user_response == 2) { - // user said "yes, but only this time" - give_temp_access(filename, proc_info); + if (user_response == ALLOW_TEMP) { + set_temp_access(filename, proc_info, SET_ALLOW); return 1; } - // otherwise "no" + if (user_response == DENY) { + set_temp_access(filename, proc_info, SET_DENY); + return 0; + } + + // deny on unknown options. return 0; }