/* 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 "perm_permissions_table.h" #include "access_t.h" #include "process_info.h" #include #include #include #include #include #include #include #include #include #include #include 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 = 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; void set_db_fsuid() { pthread_mutex_lock(&uid_switch); if (current_pid == ruid) return; int status = -1; status = setfsuid(ruid); if (status < 0) { fprintf(stderr, "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_pid == ruid) return; int status = -1; status = setfsuid(ruid); if (status < 0) { fprintf(stderr, "Couldn't set uid to %d.\n", euid); exit(status); } pthread_mutex_unlock(&uid_switch); } 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 column_num = atoi(argv[0]); if (column_num >= column_count) { 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; } fprintf(stderr, "Column %d does not conform to the schema.\n", column_num); 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, mode INTEGER 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) { // we don't want the group and others to access the db umask(0077); ruid = getuid(); euid = geteuid(); fprintf(stderr, "Running with uid: %d, gid: %d\n", euid, getegid()); if (sqlite3_open_v2(db_filename, &perm_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) { perror("Can't open permanent permissions database"); return -1; } umask(0); if (ensure_database_schema()) { fprintf(stderr, "Database schema is not correct.\n"); return -1; } int status = seteuid(ruid); if (status < 0) { fprintf(stderr, "Couldn't set euid to ruid during database setup.\n"); exit(status); } 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: access status - ALLOW, DENY or NDEF in case if no information was * found */ 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; 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); return NDEF; } if (flag) { return ALLOW; } return NDEF; } /** * 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) { 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; 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); free(query); return 1; } return 0; }