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.
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
- Confidentiality: make sure sensitive information is not reached by unauthorized users
- Integrity: prevent file contents from being modified without authorization
- Availability: make sure authorized users can reach the files normally
- 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
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;
}
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
}
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;
}
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;
}
| Term | Abbreviation | Explanation |
|---|---|---|
| Access control | - | Controlling access to files |
| Access control list | ACL | A 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 |
| Read | r | Reading the contents of a file |
| Write | w | Modifying the contents of a file |
| Execute | x | Executing a file |
| Owner | - | The owner of the file |
| Group | - | The group the file belongs to |
| Others | - | Every other user |
| User ID | UID | The identifier of a user |
| Group ID | GID | The identifier of a user group |
| Effective UID | EUID | The effective UID of a process |
| Set User ID | SUID | Sets the EUID at execution time |
| Set Group ID | SGID | Sets 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 control | RBAC | Access control based on roles |
| Mandatory access control | MAC | Access control enforced by the system |
| Discretionary access control | DAC | Access control left to the owner’s discretion |
| Audit | - | Recording access operations |
