File Access Control and Protection

Access control and protection form the core of file system security. With a sensible access control mechanism in place, a system can preserve the confidentiality, integrity, and availability of its files, and keep unauthorized access and malicious operations at bay.

Basic Concepts of Access Control

What Access Control Is

Access control is the management and enforcement of the rights users have over file resources. It makes sure that only authorized users can reach the files they are entitled to.

Goals of Access Control

  1. Confidentiality: make sure sensitive information is not reached by unauthorized users
  2. Integrity: prevent file contents from being modified without authorization
  3. Availability: make sure authorized users can reach the files normally
  4. Accountability: record and trace file access behaviour

The Basic Elements of Access Control

  • Subject: the user or process that requests access
  • Object: the file or directory that is being accessed
  • Permission: the kind of operation that is allowed
  • Policy: the rule that decides whether access is granted

Access Control Models

1. Discretionary Access Control (DAC)

Discretionary access control is the most common access control model. The owner of a file freely decides what other users may do with it.

Basic Concepts

// The DAC permission structure
struct dac_permissions {
    int owner_read;      // owner read permission
    int owner_write;     // owner write permission
    int owner_execute;   // owner execute permission
    int group_read;      // group read permission
    int group_write;     // group write permission
    int group_execute;   // group execute permission
    int other_read;      // read permission for other users
    int other_write;     // write permission for other users
    int other_execute;   // execute permission for other users
};

struct file_dac {
    int owner_id;        // the owner's ID
    int group_id;        // the group's ID
    struct dac_permissions permissions;
};

Implementing the Permission Check

// The DAC permission check function
int check_dac_permission(const char* file_path, int user_id, int operation) {
    struct file_dac* dac = get_file_dac(file_path);
    if (dac == NULL) return 0;

    // Look up the user
    struct user_info* user = get_user_info(user_id);
    if (user == NULL) return 0;

    // Check whether the user is the owner
    if (user_id == dac->owner_id) {
        switch (operation) {
            case READ_OP:
                return dac->permissions.owner_read;
            case WRITE_OP:
                return dac->permissions.owner_write;
            case EXECUTE_OP:
                return dac->permissions.owner_execute;
            default:
                return 0;
        }
    }

    // Check whether the user belongs to the group
    if (is_user_in_group(user_id, dac->group_id)) {
        switch (operation) {
            case READ_OP:
                return dac->permissions.group_read;
            case WRITE_OP:
                return dac->permissions.group_write;
            case EXECUTE_OP:
                return dac->permissions.group_execute;
            default:
                return 0;
        }
    }

    // Permissions for everyone else
    switch (operation) {
        case READ_OP:
            return dac->permissions.other_read;
        case WRITE_OP:
            return dac->permissions.other_write;
        case EXECUTE_OP:
            return dac->permissions.other_execute;
        default:
            return 0;
    }
}

Changing Permissions

// Changing the permissions of a file
int chmod_file(const char* file_path, int user_id, struct dac_permissions* new_permissions) {
    struct file_dac* dac = get_file_dac(file_path);
    if (dac == NULL) return -1;

    // Only the owner or the superuser may change the permissions
    if (user_id != dac->owner_id && !is_superuser(user_id)) {
        return -1;
    }

    // Update the permissions
    dac->permissions = *new_permissions;
    update_file_dac(file_path, dac);

    return 0;
}

// Changing the owner of a file
int chown_file(const char* file_path, int user_id, int new_owner_id, int new_group_id) {
    // Only the superuser may change the owner
    if (!is_superuser(user_id)) {
        return -1;
    }

    struct file_dac* dac = get_file_dac(file_path);
    if (dac == NULL) return -1;

    dac->owner_id = new_owner_id;
    dac->group_id = new_group_id;
    update_file_dac(file_path, dac);

    return 0;
}

2. Mandatory Access Control (MAC)

Mandatory access control is driven by a system-wide security policy, and users cannot change access permissions on their own.

The Security Label System

// The security label structure
struct security_label {
    int classification;   // the security classification level
    char categories[64];  // the security categories
    int integrity_level;  // the integrity level
};

struct file_mac {
    struct security_label label;
    int mandatory_flags;
};

Implementing the Access Check

// The MAC access control check
int check_mac_permission(const char* file_path, int user_id, int operation) {
    struct file_mac* mac = get_file_mac(file_path);
    struct user_security* user_sec = get_user_security(user_id);

    if (mac == NULL || user_sec == NULL) return 0;

    // Check the security classification level
    if (user_sec->classification < mac->label.classification) {
        return 0;  // The user's security level is not high enough
    }

    // Check the security categories
    if (!has_category_access(user_sec, mac->label.categories)) {
        return 0;  // The user has no rights in the matching categories
    }

    // Check the integrity level
    if (operation == WRITE_OP && user_sec->integrity_level < mac->label.integrity_level) {
        return 0;  // A write requires a higher integrity level
    }

    return 1;
}

3. Role-Based Access Control (RBAC)

Role-based access control manages permissions through roles: users receive the permissions attached to the roles they are assigned to.

The Role and Permission Structure

// The role and permission structure
struct role_permission {
    char role_name[64];
    int permissions[MAX_PERMISSIONS];
    int permission_count;
};

struct user_role {
    int user_id;
    char roles[MAX_ROLES][64];
    int role_count;
};

// The RBAC permission check
int check_rbac_permission(const char* file_path, int user_id, int operation) {
    struct user_role* user_roles = get_user_roles(user_id);
    if (user_roles == NULL) return 0;

    // Check every role the user holds
    for (int i = 0; i < user_roles->role_count; i++) {
        struct role_permission* role = get_role_permission(user_roles->roles[i]);
        if (role == NULL) continue;

        // Check whether the role carries the permission in question
        for (int j = 0; j < role->permission_count; j++) {
            if (role->permissions[j] == operation) {
                return 1;
            }
        }
    }

    return 0;
}

Access Control Lists (ACLs)

An access control list keeps a detailed list of user and group permissions for every file.

The ACL Structure

// An ACL entry structure
struct acl_entry {
    int entry_type;      // user, group, or other
    int id;              // the user ID or the group ID
    int permissions;     // the permission bits
    int inheritance;     // the inheritance flag
};

struct file_acl {
    int entry_count;
    struct acl_entry entries[MAX_ACL_ENTRIES];
};

ACL Operations

// Adding an ACL entry
int add_acl_entry(const char* file_path, struct acl_entry* entry) {
    struct file_acl* acl = get_file_acl(file_path);
    if (acl == NULL) return -1;

    if (acl->entry_count >= MAX_ACL_ENTRIES) {
        return -1;  // The ACL is full
    }

    // Check whether an identical entry already exists
    for (int i = 0; i < acl->entry_count; i++) {
        if (acl->entries[i].entry_type == entry->entry_type &&
            acl->entries[i].id == entry->id) {
            // Update the existing entry
            acl->entries[i] = *entry;
            update_file_acl(file_path, acl);
            return 0;
        }
    }

    // Add the new entry
    acl->entries[acl->entry_count] = *entry;
    acl->entry_count++;
    update_file_acl(file_path, acl);

    return 0;
}

// Removing an ACL entry
int remove_acl_entry(const char* file_path, int entry_type, int id) {
    struct file_acl* acl = get_file_acl(file_path);
    if (acl == NULL) return -1;

    for (int i = 0; i < acl->entry_count; i++) {
        if (acl->entries[i].entry_type == entry_type &&
            acl->entries[i].id == id) {
            // Remove the entry
            for (int j = i; j < acl->entry_count - 1; j++) {
                acl->entries[j] = acl->entries[j + 1];
            }
            acl->entry_count--;
            update_file_acl(file_path, acl);
            return 0;
        }
    }

    return -1;  // No such entry
}

File Protection Mechanisms

1. File Encryption

Symmetric Encryption

// The file encryption structure
struct file_encryption {
    int algorithm;       // the encryption algorithm
    char key_hash[64];   // the hash of the key
    int key_length;      // the key length
    char iv[16];         // the initialization vector
};

// The file encryption function
int encrypt_file(const char* file_path, const char* password) {
    // Generate the key
    unsigned char key[32];
    generate_key_from_password(password, key, 32);

    // Generate the initialization vector
    unsigned char iv[16];
    generate_random_iv(iv, 16);

    // Read the contents of the file
    char* file_content = read_file_content(file_path);
    int content_length = strlen(file_content);

    // Encrypt the contents of the file
    char* encrypted_content = malloc(content_length + 16);
    int encrypted_length = encrypt_aes256(file_content, content_length, key, iv, encrypted_content);

    // Store the encryption information
    struct file_encryption* enc_info = malloc(sizeof(struct file_encryption));
    enc_info->algorithm = AES256;
    hash_key(key, 32, enc_info->key_hash);
    enc_info->key_length = 32;
    memcpy(enc_info->iv, iv, 16);

    // Write the encrypted file
    write_encrypted_file(file_path, encrypted_content, encrypted_length, enc_info);

    free(file_content);
    free(encrypted_content);
    free(enc_info);

    return 0;
}

Asymmetric Encryption

// File protection with asymmetric encryption
struct asymmetric_encryption {
    char public_key[256];
    char encrypted_key[256];
    int algorithm;
};

// Encrypting a file with a public key
int encrypt_file_with_public_key(const char* file_path, const char* public_key) {
    // Generate the symmetric session key
    unsigned char session_key[32];
    generate_random_key(session_key, 32);

    // Encrypt the session key with the public key
    char encrypted_session_key[256];
    rsa_encrypt(session_key, 32, public_key, encrypted_session_key);

    // Encrypt the file with the session key
    encrypt_file_with_session_key(file_path, session_key);

    // Store the encryption information
    struct asymmetric_encryption* enc_info = malloc(sizeof(struct asymmetric_encryption));
    strcpy(enc_info->public_key, public_key);
    strcpy(enc_info->encrypted_key, encrypted_session_key);
    enc_info->algorithm = RSA_AES;

    save_encryption_info(file_path, enc_info);
    free(enc_info);

    return 0;
}

2. Digital Signatures

// A digital signature on a file
struct file_signature {
    char algorithm[32];      // the signature algorithm
    char signature[256];     // the digital signature
    char certificate[1024];  // the certificate information
    int timestamp;           // the time the signature was made
};

// Signing a file digitally
int sign_file(const char* file_path, const char* private_key) {
    // Compute the hash of the file
    char file_hash[64];
    calculate_file_hash(file_path, file_hash);

    // Sign the hash with the private key
    char signature[256];
    rsa_sign(file_hash, 64, private_key, signature);

    // Store the signature information
    struct file_signature* sig_info = malloc(sizeof(struct file_signature));
    strcpy(sig_info->algorithm, "RSA-SHA256");
    strcpy(sig_info->signature, signature);
    sig_info->timestamp = time(NULL);

    save_signature_info(file_path, sig_info);
    free(sig_info);

    return 0;
}

// Verifying the signature of a file
int verify_file_signature(const char* file_path, const char* public_key) {
    // Compute the hash of the file
    char file_hash[64];
    calculate_file_hash(file_path, file_hash);

    // Fetch the signature information
    struct file_signature* sig_info = get_signature_info(file_path);
    if (sig_info == NULL) return -1;

    // Verify the signature
    int result = rsa_verify(file_hash, 64, sig_info->signature, public_key);

    free(sig_info);
    return result;
}

3. File Integrity Checking

// The file integrity record
struct file_integrity {
    char hash_algorithm[32];
    char file_hash[64];
    int last_modified;
    int check_frequency;
};

// Computing the integrity hash of a file
int calculate_file_integrity(const char* file_path) {
    struct file_integrity* integrity = malloc(sizeof(struct file_integrity));

    // Compute the hash of the file
    calculate_sha256_hash(file_path, integrity->file_hash);
    strcpy(integrity->hash_algorithm, "SHA256");
    integrity->last_modified = get_file_modification_time(file_path);
    integrity->check_frequency = 3600;  // Check once an hour

    save_integrity_info(file_path, integrity);
    free(integrity);

    return 0;
}

// Checking the integrity of a file
int check_file_integrity(const char* file_path) {
    struct file_integrity* stored_integrity = get_integrity_info(file_path);
    if (stored_integrity == NULL) return -1;

    // Compute the current hash of the file
    char current_hash[64];
    calculate_sha256_hash(file_path, current_hash);

    // Compare the two hashes
    int result = strcmp(stored_integrity->file_hash, current_hash);

    free(stored_integrity);
    return result == 0 ? 0 : -1;
}

Auditing and Logging

Recording Access

// A file access log entry
struct file_access_log {
    int user_id;
    char file_path[256];
    int operation;
    int result;
    int timestamp;
    char ip_address[16];
    char user_agent[256];
};

// Recording a file access
int log_file_access(const char* file_path, int user_id, int operation, int result) {
    struct file_access_log* log_entry = malloc(sizeof(struct file_access_log));

    log_entry->user_id = user_id;
    strcpy(log_entry->file_path, file_path);
    log_entry->operation = operation;
    log_entry->result = result;
    log_entry->timestamp = time(NULL);

    // Look up the IP address of the user
    get_user_ip_address(user_id, log_entry->ip_address);

    // Look up the user agent of the user
    get_user_agent(user_id, log_entry->user_agent);

    // Write the log
    write_access_log(log_entry);

    free(log_entry);
    return 0;
}

Generating Audit Reports

// Generating an access audit report
int generate_audit_report(const char* output_file, int start_time, int end_time) {
    FILE* report = fopen(output_file, "w");
    if (report == NULL) return -1;

    fprintf(report, "File Access Audit Report\n");
    fprintf(report, "Time range: %d - %d\n\n", start_time, end_time);

    // Count the accesses
    int total_access = 0;
    int successful_access = 0;
    int failed_access = 0;

    // Read the log entries
    struct file_access_log* log_entries = read_access_logs(start_time, end_time);
    int entry_count = get_log_entry_count();

    for (int i = 0; i < entry_count; i++) {
        total_access++;
        if (log_entries[i].result == 0) {
            successful_access++;
        } else {
            failed_access++;
        }

        // Write out the detailed log
        fprintf(report, "Time: %s", ctime(&log_entries[i].timestamp));
        fprintf(report, "User: %d\n", log_entries[i].user_id);
        fprintf(report, "File: %s\n", log_entries[i].file_path);
        fprintf(report, "Operation: %s\n", get_operation_name(log_entries[i].operation));
        fprintf(report, "Result: %s\n", log_entries[i].result == 0 ? "success" : "failure");
        fprintf(report, "IP address: %s\n", log_entries[i].ip_address);
        fprintf(report, "---\n");
    }

    // Write out the statistics
    fprintf(report, "\nStatistics:\n");
    fprintf(report, "Total accesses: %d\n", total_access);
    fprintf(report, "Successful accesses: %d\n", successful_access);
    fprintf(report, "Failed accesses: %d\n", failed_access);
    fprintf(report, "Success rate: %.2f%%\n", (float)successful_access / total_access * 100);

    fclose(report);
    free(log_entries);

    return 0;
}

Glossary

TermAbbreviationExplanation
Access control-Controlling access to files
Access control listACLA list that records the permissions of users
Access control matrix-A subject-object permission matrix
Capability list-A list of the permissions a subject holds
File permission-The access rights of a file
ReadrReading the contents of a file
WritewModifying the contents of a file
ExecutexExecuting a file
Owner-The owner of the file
Group-The group the file belongs to
Others-Every other user
User IDUIDThe identifier of a user
Group IDGIDThe identifier of a user group
Effective UIDEUIDThe effective UID of a process
Set User IDSUIDSets the EUID at execution time
Set Group IDSGIDSets the EGID at execution time
Sticky bit-In a directory, only the owner may delete files
Permission bits-The nine-bit representation of permissions
Octal permission-Permissions written in octal
Symbolic permission-Permissions written symbolically
Role-A role in RBAC
Role-based access controlRBACAccess control based on roles
Mandatory access controlMACAccess control enforced by the system
Discretionary access controlDACAccess control left to the owner’s discretion
Audit-Recording access operations

Found an issue with this content?

Tell us what could be improved.