Linux Security Deep Dive: Capabilities, Namespaces, and Advanced Access Control
Linux security has evolved far beyond traditional Unix permissions. Modern Linux provides sophisticated security mechanisms including capabilities, mandatory access controls, user namespaces, and fine-grained privilege separation. Understanding these mechanisms is crucial for building secure applications and systems in today’s threat landscape.
Linux Security Deep Dive
Linux Capabilities System
Understanding Capabilities
// capabilities_demo.c - Linux capabilities programming
#include <sys/capability.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
// List all capabilities
void list_all_capabilities(void) {
printf("=== All Linux Capabilities ===\n");
for (int cap = 0; cap <= CAP_LAST_CAP; cap++) {
char *name = cap_to_name(cap);
if (name) {
printf("CAP_%s (%d): %s\n", name, cap,
cap_to_text(&cap, 1));
cap_free(name);
}
}
}
// Check current process capabilities
void check_current_capabilities(void) {
cap_t caps;
char *caps_text;
caps = cap_get_proc();
if (caps == NULL) {
perror("cap_get_proc");
return;
}
caps_text = cap_to_text(caps, NULL);
if (caps_text == NULL) {
perror("cap_to_text");
cap_free(caps);
return;
}
printf("Current process capabilities: %s\n", caps_text);
cap_free(caps);
cap_free(caps_text);
}
// Set specific capabilities
int set_capabilities(cap_value_t *cap_list, int num_caps) {
cap_t caps;
// Get current capabilities
caps = cap_get_proc();
if (caps == NULL) {
perror("cap_get_proc");
return -1;
}
// Clear all capabilities
if (cap_clear(caps) == -1) {
perror("cap_clear");
cap_free(caps);
return -1;
}
// Set specific capabilities
if (cap_set_flag(caps, CAP_EFFECTIVE, num_caps, cap_list, CAP_SET) == -1 ||
cap_set_flag(caps, CAP_PERMITTED, num_caps, cap_list, CAP_SET) == -1) {
perror("cap_set_flag");
cap_free(caps);
return -1;
}
// Apply capabilities
if (cap_set_proc(caps) == -1) {
perror("cap_set_proc");
cap_free(caps);
return -1;
}
cap_free(caps);
return 0;
}
// Drop all capabilities
void drop_all_capabilities(void) {
cap_t caps;
caps = cap_init();
if (caps == NULL) {
perror("cap_init");
return;
}
if (cap_set_proc(caps) == -1) {
perror("cap_set_proc");
}
cap_free(caps);
printf("All capabilities dropped\n");
}
// Capability-aware privilege dropping
void safe_privilege_drop(uid_t new_uid, gid_t new_gid) {
// Keep only necessary capabilities
cap_value_t caps_to_keep[] = {CAP_NET_BIND_SERVICE, CAP_DAC_OVERRIDE};
// Set capabilities before dropping privileges
if (set_capabilities(caps_to_keep, 2) == -1) {
fprintf(stderr, "Failed to set capabilities\n");
exit(1);
}
// Drop group privileges
if (setgid(new_gid) == -1) {
perror("setgid");
exit(1);
}
// Drop user privileges
if (setuid(new_uid) == -1) {
perror("setuid");
exit(1);
}
printf("Dropped privileges to uid=%d, gid=%d\n", new_uid, new_gid);
check_current_capabilities();
}
// File capabilities demonstration
void demonstrate_file_capabilities(const char *filename) {
cap_t file_caps;
char *caps_text;
// Get file capabilities
file_caps = cap_get_file(filename);
if (file_caps == NULL) {
if (errno == ENODATA) {
printf("File %s has no capabilities\n", filename);
} else {
perror("cap_get_file");
}
return;
}
caps_text = cap_to_text(file_caps, NULL);
if (caps_text) {
printf("File %s capabilities: %s\n", filename, caps_text);
cap_free(caps_text);
}
cap_free(file_caps);
}
// Set file capabilities
int set_file_capabilities(const char *filename, const char *cap_string) {
cap_t caps;
caps = cap_from_text(cap_string);
if (caps == NULL) {
perror("cap_from_text");
return -1;
}
if (cap_set_file(filename, caps) == -1) {
perror("cap_set_file");
cap_free(caps);
return -1;
}
cap_free(caps);
printf("Set capabilities on %s: %s\n", filename, cap_string);
return 0;
}
int main(int argc, char *argv[]) {
printf("=== Linux Capabilities Demo ===\n\n");
// Check if running as root
if (getuid() == 0) {
printf("Running as root - demonstrating capability operations\n\n");
check_current_capabilities();
printf("\n");
// Demonstrate privilege dropping with capabilities
safe_privilege_drop(1000, 1000);
printf("\n");
if (argc > 1) {
demonstrate_file_capabilities(argv[1]);
}
} else {
printf("Running as non-root user\n");
check_current_capabilities();
}
return 0;
}
Capability Management Tools
#!/bin/bash
# capability_management.sh - Capability management utilities
# Check process capabilities
check_process_caps() {
local pid=${1:-$$}
echo "=== Process Capabilities (PID: $pid) ==="
if [ -f "/proc/$pid/status" ]; then
grep -E "^Cap" /proc/$pid/status
echo
# Decode capability masks
echo "Decoded capabilities:"
capsh --decode=$(grep CapEff /proc/$pid/status | awk '{print $2}')
else
echo "Process $pid not found"
fi
}
# Set file capabilities
set_file_caps() {
local file=$1
local caps=$2
if [ ! -f "$file" ]; then
echo "File not found: $file"
return 1
fi
echo "Setting capabilities on $file: $caps"
setcap "$caps" "$file"
if [ $? -eq 0 ]; then
echo "Capabilities set successfully"
getcap "$file"
else
echo "Failed to set capabilities"
return 1
fi
}
# Remove file capabilities
remove_file_caps() {
local file=$1
echo "Removing capabilities from $file"
setcap -r "$file"
if [ $? -eq 0 ]; then
echo "Capabilities removed successfully"
else
echo "Failed to remove capabilities"
fi
}
# Audit capabilities across system
audit_capabilities() {
echo "=== System Capability Audit ==="
# Find files with capabilities
echo "Files with capabilities:"
find /usr /bin /sbin -type f -exec getcap {} + 2>/dev/null | \
grep -v "= $" | head -20
echo
# Check running processes with capabilities
echo "Processes with capabilities:"
for pid in $(ps -eo pid --no-headers); do
if [ -f "/proc/$pid/status" ]; then
caps=$(grep CapEff /proc/$pid/status 2>/dev/null | awk '{print $2}')
if [ "$caps" != "0000000000000000" ] && [ -n "$caps" ]; then
cmd=$(ps -p $pid -o comm= 2>/dev/null)
echo "PID $pid ($cmd): $caps"
fi
fi
done | head -10
}
# Capability-aware service wrapper
run_with_caps() {
local caps=$1
shift
local command="$@"
echo "Running with capabilities: $caps"
echo "Command: $command"
# Use capsh to run with specific capabilities
capsh --caps="$caps" --user=$(whoami) -- -c "$command"
}
# Create capability-restricted environment
create_restricted_env() {
local user=$1
local caps=$2
echo "Creating restricted environment for $user with caps: $caps"
# Create a script that drops to user with specific caps
cat > /tmp/restricted_shell << EOF
#!/bin/bash
exec capsh --caps="$caps" --user="$user" --shell=/bin/bash
EOF
chmod +x /tmp/restricted_shell
echo "Restricted shell created at /tmp/restricted_shell"
}
# Test capability requirements
test_capability_requirements() {
local program=$1
echo "=== Testing Capability Requirements for $program ==="
# Test different capability combinations
local test_caps=(
"cap_net_bind_service=ep"
"cap_dac_override=ep"
"cap_sys_admin=ep"
"cap_net_raw=ep"
"cap_setuid,cap_setgid=ep"
)
for cap in "${test_caps[@]}"; do
echo "Testing with: $cap"
# Copy program to test location
cp "$program" "/tmp/test_$(basename $program)"
# Set capability
setcap "$cap" "/tmp/test_$(basename $program)" 2>/dev/null
if [ $? -eq 0 ]; then
echo " ✓ Capability set successfully"
# Test execution
timeout 5 "/tmp/test_$(basename $program)" --version >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo " ✓ Program runs with this capability"
else
echo " ✗ Program fails with this capability"
fi
else
echo " ✗ Failed to set capability"
fi
# Cleanup
rm -f "/tmp/test_$(basename $program)"
echo
done
}
Mandatory Access Control (MAC)
SELinux Programming
// selinux_demo.c - SELinux programming interface
#include <selinux/selinux.h>
#include <selinux/context.h>
#include <selinux/label.h>
#include <selinux/restorecon.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
// Check SELinux status
void check_selinux_status(void) {
printf("=== SELinux Status ===\n");
if (is_selinux_enabled()) {
printf("SELinux is enabled\n");
// Get current mode
int mode = security_getenforce();
switch (mode) {
case 1:
printf("Mode: Enforcing\n");
break;
case 0:
printf("Mode: Permissive\n");
break;
default:
printf("Mode: Unknown\n");
}
// Get policy version
int policy_version = security_policyvers();
printf("Policy version: %d\n", policy_version);
// Get SELinux mount point
const char *selinux_mnt = selinux_mnt();
printf("SELinux filesystem: %s\n", selinux_mnt);
} else {
printf("SELinux is disabled\n");
}
}
// Get and display security context
void show_security_context(const char *path) {
char *context;
if (getfilecon(path, &context) == -1) {
perror("getfilecon");
return;
}
printf("Security context of %s: %s\n", path, context);
// Parse context components
context_t con = context_new(context);
if (con) {
printf(" User: %s\n", context_user_get(con));
printf(" Role: %s\n", context_role_get(con));
printf(" Type: %s\n", context_type_get(con));
printf(" Level: %s\n", context_range_get(con));
context_free(con);
}
freecon(context);
}
// Set security context
int set_security_context(const char *path, const char *context) {
if (setfilecon(path, context) == -1) {
perror("setfilecon");
return -1;
}
printf("Set security context of %s to %s\n", path, context);
return 0;
}
// Check access permissions
void check_access_permissions(const char *path, const char *avc_class) {
char *user_context;
char *file_context;
// Get current process context
if (getcon(&user_context) == -1) {
perror("getcon");
return;
}
// Get file context
if (getfilecon(path, &file_context) == -1) {
perror("getfilecon");
freecon(user_context);
return;
}
printf("Checking access: %s -> %s (%s)\n",
user_context, file_context, avc_class);
// Check various permissions
const char *permissions[] = {"read", "write", "execute", "open"};
for (int i = 0; i < 4; i++) {
int result = security_compute_av(user_context, file_context,
string_to_security_class(avc_class),
string_to_av_perm(string_to_security_class(avc_class),
permissions[i]),
NULL);
printf(" %s: %s\n", permissions[i],
(result == 0) ? "ALLOWED" : "DENIED");
}
freecon(user_context);
freecon(file_context);
}
// Restore file contexts
void restore_file_contexts(const char *path) {
struct selabel_handle *hnd;
char *context;
struct stat st;
// Initialize labeling handle
hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0);
if (!hnd) {
perror("selabel_open");
return;
}
// Get file stats
if (stat(path, &st) == -1) {
perror("stat");
selabel_close(hnd);
return;
}
// Get expected context
if (selabel_lookup(hnd, &context, path, st.st_mode) == 0) {
printf("Expected context for %s: %s\n", path, context);
// Set the context
if (setfilecon(path, context) == -1) {
perror("setfilecon");
} else {
printf("Restored context for %s\n", path);
}
freecon(context);
} else {
printf("No default context found for %s\n", path);
}
selabel_close(hnd);
}
// Domain transition example
void demonstrate_domain_transition(void) {
char *current_context;
char *exec_context;
// Get current context
if (getcon(¤t_context) == -1) {
perror("getcon");
return;
}
printf("Current domain: %s\n", current_context);
// Check what domain we would transition to
if (getexeccon(&exec_context) == 0 && exec_context) {
printf("Exec context: %s\n", exec_context);
freecon(exec_context);
} else {
printf("No exec context set\n");
}
freecon(current_context);
}
int main(int argc, char *argv[]) {
printf("=== SELinux Programming Demo ===\n\n");
check_selinux_status();
printf("\n");
if (!is_selinux_enabled()) {
printf("SELinux not enabled, exiting\n");
return 1;
}
// Demonstrate with a file
const char *test_file = (argc > 1) ? argv[1] : "/etc/passwd";
show_security_context(test_file);
printf("\n");
check_access_permissions(test_file, "file");
printf("\n");
demonstrate_domain_transition();
return 0;
}
SELinux Policy Management
#!/bin/bash
# selinux_management.sh - SELinux policy and context management
# Check SELinux status
check_selinux() {
echo "=== SELinux Status ==="
if command -v getenforce >/dev/null; then
echo "Status: $(getenforce)"
echo "Config: $(grep ^SELINUX= /etc/selinux/config 2>/dev/null || echo 'Not configured')"
if [ "$(getenforce)" != "Disabled" ]; then
echo "Policy: $(selinuxenabled && sestatus | grep 'Policy from config')"
echo "Mode from config: $(grep ^SELINUXTYPE= /etc/selinux/config 2>/dev/null)"
fi
else
echo "SELinux tools not available"
fi
echo
}
# Show current context
show_current_context() {
echo "=== Current Security Context ==="
if command -v id >/dev/null; then
id -Z 2>/dev/null || echo "Context not available"
fi
echo
}
# File context analysis
analyze_file_contexts() {
local path=${1:-"/etc"}
echo "=== File Context Analysis: $path ==="
# Show contexts
ls -lZ "$path" 2>/dev/null | head -10
echo
# Show mismatched contexts
echo "Files with mismatched contexts:"
restorecon -n -v "$path"/* 2>/dev/null | head -5
echo
}
# Process context analysis
analyze_process_contexts() {
echo "=== Process Context Analysis ==="
echo "Running processes with contexts:"
ps auxZ 2>/dev/null | head -10
echo
# System services
echo "Systemd services and their contexts:"
systemctl list-units --type=service --state=active | head -5 | \
while read service _; do
if [[ $service =~ \.service$ ]]; then
echo -n "$service: "
systemctl show -p ExecMainPID --value "$service" | \
xargs -I {} sh -c 'ps -p {} -o label= 2>/dev/null || echo "No context"'
fi
done
}
# Boolean management
manage_booleans() {
echo "=== SELinux Booleans ==="
if command -v getsebool >/dev/null; then
echo "Active booleans (showing first 10):"
getsebool -a | head -10
echo
echo "Booleans that are 'on':"
getsebool -a | grep " on$" | head -5
else
echo "SELinux boolean tools not available"
fi
echo
}
# Port context management
manage_port_contexts() {
echo "=== Port Context Management ==="
if command -v semanage >/dev/null; then
echo "Port contexts:"
semanage port -l | head -10
echo
echo "Custom port contexts:"
semanage port -l -C 2>/dev/null || echo "None"
else
echo "semanage not available"
fi
echo
}
# AVC denial analysis
analyze_avc_denials() {
local logfile=${1:-"/var/log/audit/audit.log"}
echo "=== AVC Denial Analysis ==="
if [ -f "$logfile" ]; then
echo "Recent AVC denials:"
grep "avc.*denied" "$logfile" 2>/dev/null | tail -5
echo
# Use audit2allow if available
if command -v audit2allow >/dev/null; then
echo "Suggested policy (last 10 denials):"
grep "avc.*denied" "$logfile" 2>/dev/null | tail -10 | \
audit2allow 2>/dev/null || echo "No denials to analyze"
fi
else
echo "Audit log not found: $logfile"
fi
echo
}
# Create custom policy module
create_policy_module() {
local module_name=$1
local te_content=$2
if [ -z "$module_name" ] || [ -z "$te_content" ]; then
echo "Usage: create_policy_module <name> <te_content>"
return 1
fi
echo "Creating SELinux policy module: $module_name"
# Create .te file
cat > "${module_name}.te" << EOF
module $module_name 1.0;
require {
type unconfined_t;
class file { read write open };
}
$te_content
EOF
# Compile and install
if command -v checkmodule >/dev/null && command -v semodule_package >/dev/null; then
checkmodule -M -m -o "${module_name}.mod" "${module_name}.te"
semodule_package -o "${module_name}.pp" -m "${module_name}.mod"
echo "Policy module created: ${module_name}.pp"
echo "Install with: semodule -i ${module_name}.pp"
else
echo "SELinux development tools not available"
fi
}
# Security context restoration
restore_contexts() {
local path=${1:-"/"}
local recursive=${2:-"false"}
echo "=== Restoring Security Contexts ==="
echo "Path: $path"
echo "Recursive: $recursive"
if [ "$recursive" = "true" ]; then
restorecon -R -v "$path" 2>/dev/null | head -10
else
restorecon -v "$path" 2>/dev/null
fi
}
# SELinux troubleshooting
troubleshoot_selinux() {
echo "=== SELinux Troubleshooting ==="
# Check if SELinux is causing issues
echo "1. Check current enforcement:"
getenforce 2>/dev/null || echo "getenforce not available"
echo
echo "2. Recent denials:"
journalctl -t setroubleshoot --since "1 hour ago" 2>/dev/null | head -5 || \
echo "No setroubleshoot entries found"
echo
echo "3. Temporary enforcement change (for testing):"
echo " setenforce 0 # Permissive mode"
echo " setenforce 1 # Enforcing mode"
echo
echo "4. Common fixes:"
echo " - Restore contexts: restorecon -R /path"
echo " - Set boolean: setsebool boolean_name on"
echo " - Add file context: semanage fcontext -a -t type_t '/path(/.*)?'"
echo " - Relabel filesystem: touch /.autorelabel && reboot"
}
# Main function
case "${1:-status}" in
"status")
check_selinux
show_current_context
;;
"files")
analyze_file_contexts "$2"
;;
"processes")
analyze_process_contexts
;;
"booleans")
manage_booleans
;;
"ports")
manage_port_contexts
;;
"denials")
analyze_avc_denials "$2"
;;
"restore")
restore_contexts "$2" "$3"
;;
"troubleshoot")
troubleshoot_selinux
;;
*)
echo "Usage: $0 {status|files|processes|booleans|ports|denials|restore|troubleshoot} [path]"
exit 1
;;
esac
User Namespaces and Privilege Separation
User Namespace Programming
// user_namespace.c - User namespace programming
#define _GNU_SOURCE
#include <sched.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// Create user namespace with UID/GID mapping
int create_user_namespace(uid_t inside_uid, gid_t inside_gid,
uid_t outside_uid, gid_t outside_gid) {
pid_t child_pid;
char map_path[256];
char map_line[256];
int fd;
// Create new user namespace
child_pid = fork();
if (child_pid == -1) {
perror("fork");
return -1;
}
if (child_pid == 0) {
// Child process - inside the new namespace
printf("Child: PID=%d, UID=%d, GID=%d\n",
getpid(), getuid(), getgid());
// Wait for parent to set up mappings
sleep(1);
printf("Child: After mapping - UID=%d, GID=%d\n",
getuid(), getgid());
// Try to access privileged operations
if (geteuid() == 0) {
printf("Child: Running as root inside namespace\n");
// Create a file
int fd = open("/tmp/namespace_test", O_CREAT | O_WRONLY, 0644);
if (fd >= 0) {
write(fd, "Created by namespace root\n", 26);
close(fd);
printf("Child: Created file successfully\n");
} else {
perror("Child: Failed to create file");
}
}
return 0;
} else {
// Parent process - set up UID/GID mappings
// Set UID mapping
snprintf(map_path, sizeof(map_path), "/proc/%d/uid_map", child_pid);
snprintf(map_line, sizeof(map_line), "%d %d 1", inside_uid, outside_uid);
fd = open(map_path, O_WRONLY);
if (fd >= 0) {
write(fd, map_line, strlen(map_line));
close(fd);
} else {
perror("Failed to write uid_map");
}
// Deny setgroups for GID mapping
snprintf(map_path, sizeof(map_path), "/proc/%d/setgroups", child_pid);
fd = open(map_path, O_WRONLY);
if (fd >= 0) {
write(fd, "deny", 4);
close(fd);
}
// Set GID mapping
snprintf(map_path, sizeof(map_path), "/proc/%d/gid_map", child_pid);
snprintf(map_line, sizeof(map_line), "%d %d 1", inside_gid, outside_gid);
fd = open(map_path, O_WRONLY);
if (fd >= 0) {
write(fd, map_line, strlen(map_line));
close(fd);
} else {
perror("Failed to write gid_map");
}
// Wait for child
wait(NULL);
return 0;
}
}
// Create container-like environment with multiple namespaces
int create_container(void) {
pid_t child_pid;
// Clone with multiple namespace flags
child_pid = clone(container_main,
malloc(4096) + 4096, // Stack for new process
CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNET |
CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC,
NULL);
if (child_pid == -1) {
perror("clone");
return -1;
}
printf("Container created with PID %d\n", child_pid);
// Set up UID/GID mappings (as in previous function)
// ... mapping code here ...
wait(NULL);
return 0;
}
int container_main(void *arg) {
printf("Container: PID=%d (should be 1), UID=%d, GID=%d\n",
getpid(), getuid(), getgid());
// Set hostname
sethostname("container", 9);
// Mount new filesystem
if (mount("none", "/proc", "proc", 0, NULL) == -1) {
perror("mount /proc");
}
// Create a simple shell environment
execl("/bin/sh", "sh", NULL);
perror("execl");
return -1;
}
// Secure application launcher
int launch_secure_app(const char *app_path, char *const argv[]) {
pid_t child_pid;
child_pid = fork();
if (child_pid == -1) {
perror("fork");
return -1;
}
if (child_pid == 0) {
// Child: Create security boundaries
// Create new user namespace
if (unshare(CLONE_NEWUSER) == -1) {
perror("unshare user namespace");
exit(1);
}
// Set up UID/GID mappings to run as non-root
// (Mapping code would go here)
// Create new mount namespace
if (unshare(CLONE_NEWNS) == -1) {
perror("unshare mount namespace");
exit(1);
}
// Make root filesystem read-only
if (mount("none", "/", NULL, MS_REMOUNT | MS_RDONLY, NULL) == -1) {
perror("remount root read-only");
}
// Create private /tmp
if (mount("tmpfs", "/tmp", "tmpfs", 0, "size=100m") == -1) {
perror("mount private /tmp");
}
// Execute application
execv(app_path, argv);
perror("execv");
exit(1);
} else {
// Parent: Monitor child
int status;
wait(&status);
if (WIFEXITED(status)) {
printf("Application exited with status %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Application killed by signal %d\n", WTERMSIG(status));
}
return WEXITSTATUS(status);
}
}
// Demonstrate namespace isolation
void demonstrate_isolation(void) {
printf("=== Namespace Isolation Demo ===\n");
printf("Original namespaces:\n");
system("ls -la /proc/self/ns/");
// Create user namespace
if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) {
printf("\nAfter creating new namespaces:\n");
system("ls -la /proc/self/ns/");
printf("\nNetwork interfaces in new namespace:\n");
system("ip link show 2>/dev/null || echo 'No network interfaces'");
} else {
perror("unshare");
}
}
int main(int argc, char *argv[]) {
printf("=== User Namespace Demo ===\n\n");
if (getuid() != 0) {
printf("Running as non-root user (UID: %d)\n", getuid());
printf("Creating user namespace with root mapping...\n\n");
create_user_namespace(0, 0, getuid(), getgid());
} else {
printf("Running as root\n");
demonstrate_isolation();
}
return 0;
}
Secure Application Development
Privilege Separation Patterns
// privilege_separation.c - Secure application architecture
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
// Message types for IPC
enum msg_type {
MSG_READ_FILE,
MSG_WRITE_FILE,
MSG_NET_CONNECT,
MSG_RESPONSE,
MSG_ERROR
};
struct ipc_message {
enum msg_type type;
size_t data_len;
char data[];
};
// Privileged helper process
int privileged_helper(int sock_fd) {
struct ipc_message *msg;
char buffer[4096];
ssize_t n;
printf("Privileged helper started (UID: %d)\n", getuid());
while (1) {
// Receive message header
n = recv(sock_fd, buffer, sizeof(struct ipc_message), 0);
if (n <= 0) break;
msg = (struct ipc_message *)buffer;
// Receive message data if any
if (msg->data_len > 0) {
n = recv(sock_fd, buffer + sizeof(struct ipc_message),
msg->data_len, 0);
if (n <= 0) break;
}
// Process request based on type
switch (msg->type) {
case MSG_READ_FILE: {
printf("Helper: Reading file %s\n", msg->data);
// Validate file path (security check)
if (strstr(msg->data, "..") || msg->data[0] != '/') {
send_error(sock_fd, "Invalid file path");
break;
}
// Read file and send response
FILE *fp = fopen(msg->data, "r");
if (fp) {
char content[1024];
size_t len = fread(content, 1, sizeof(content) - 1, fp);
content[len] = '\0';
fclose(fp);
send_response(sock_fd, content, len);
} else {
send_error(sock_fd, "File not found");
}
break;
}
case MSG_NET_CONNECT: {
printf("Helper: Network connect to %s\n", msg->data);
// Implement network connection logic
// This would be done with elevated privileges
send_response(sock_fd, "Connected", 9);
break;
}
default:
send_error(sock_fd, "Unknown message type");
break;
}
}
return 0;
}
// Unprivileged main process
int unprivileged_main(int sock_fd) {
char response[1024];
printf("Main process started (UID: %d)\n", getuid());
// Request file read through privileged helper
send_request(sock_fd, MSG_READ_FILE, "/etc/hostname", strlen("/etc/hostname"));
if (receive_response(sock_fd, response, sizeof(response)) > 0) {
printf("Main: Received file content: %s\n", response);
}
// Request network operation
send_request(sock_fd, MSG_NET_CONNECT, "example.com:80", strlen("example.com:80"));
if (receive_response(sock_fd, response, sizeof(response)) > 0) {
printf("Main: Network response: %s\n", response);
}
return 0;
}
// Helper functions for IPC
void send_request(int sock_fd, enum msg_type type, const char *data, size_t len) {
struct ipc_message *msg = malloc(sizeof(struct ipc_message) + len);
msg->type = type;
msg->data_len = len;
memcpy(msg->data, data, len);
send(sock_fd, msg, sizeof(struct ipc_message) + len, 0);
free(msg);
}
void send_response(int sock_fd, const char *data, size_t len) {
struct ipc_message *msg = malloc(sizeof(struct ipc_message) + len);
msg->type = MSG_RESPONSE;
msg->data_len = len;
memcpy(msg->data, data, len);
send(sock_fd, msg, sizeof(struct ipc_message) + len, 0);
free(msg);
}
void send_error(int sock_fd, const char *error) {
send_response(sock_fd, error, strlen(error));
}
ssize_t receive_response(int sock_fd, char *buffer, size_t buf_size) {
struct ipc_message msg_header;
ssize_t n;
// Receive header
n = recv(sock_fd, &msg_header, sizeof(msg_header), 0);
if (n <= 0) return n;
// Receive data
if (msg_header.data_len > 0 && msg_header.data_len < buf_size) {
n = recv(sock_fd, buffer, msg_header.data_len, 0);
if (n > 0) {
buffer[n] = '\0';
}
return n;
}
return 0;
}
// Drop privileges safely
int drop_privileges(const char *username) {
struct passwd *pw;
// Look up user
pw = getpwnam(username);
if (!pw) {
fprintf(stderr, "User %s not found\n", username);
return -1;
}
// Change group first
if (setgid(pw->pw_gid) == -1) {
perror("setgid");
return -1;
}
// Initialize supplementary groups
if (initgroups(username, pw->pw_gid) == -1) {
perror("initgroups");
return -1;
}
// Change user
if (setuid(pw->pw_uid) == -1) {
perror("setuid");
return -1;
}
// Verify we can't regain privileges
if (setuid(0) == 0) {
fprintf(stderr, "ERROR: Could regain root privileges!\n");
return -1;
}
printf("Successfully dropped privileges to %s (UID: %d, GID: %d)\n",
username, getuid(), getgid());
return 0;
}
int main(int argc, char *argv[]) {
int sock_pair[2];
pid_t child_pid;
printf("=== Privilege Separation Demo ===\n");
// Create socket pair for IPC
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair) == -1) {
perror("socketpair");
return 1;
}
// Fork into privileged helper and unprivileged main
child_pid = fork();
if (child_pid == -1) {
perror("fork");
return 1;
}
if (child_pid == 0) {
// Child: Privileged helper
close(sock_pair[1]);
// Keep running as root for privileged operations
return privileged_helper(sock_pair[0]);
} else {
// Parent: Unprivileged main process
close(sock_pair[0]);
// Drop privileges
if (getuid() == 0) {
if (drop_privileges("nobody") == -1) {
kill(child_pid, SIGTERM);
return 1;
}
}
// Run main application logic
int result = unprivileged_main(sock_pair[1]);
// Clean up
close(sock_pair[1]);
wait(NULL);
return result;
}
}
Secure Coding Patterns
// secure_coding.c - Secure coding patterns and techniques
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <limits.h>
// Secure string handling
char* secure_strdup(const char* src, size_t max_len) {
if (!src) return NULL;
size_t len = strnlen(src, max_len);
if (len == max_len) {
errno = E2BIG;
return NULL;
}
char* dest = malloc(len + 1);
if (!dest) return NULL;
memcpy(dest, src, len);
dest[len] = '\0';
return dest;
}
// Secure buffer operations
int secure_concat(char* dest, size_t dest_size, const char* src) {
size_t dest_len = strnlen(dest, dest_size);
size_t src_len = strnlen(src, dest_size);
if (dest_len == dest_size) {
return -1; // dest not null-terminated
}
if (dest_len + src_len >= dest_size) {
return -1; // would overflow
}
strncat(dest, src, dest_size - dest_len - 1);
return 0;
}
// Secure memory allocation
void* secure_malloc(size_t size) {
// Check for integer overflow
if (size == 0 || size > SIZE_MAX / 2) {
errno = EINVAL;
return NULL;
}
void* ptr = malloc(size);
if (ptr) {
// Clear allocated memory
memset(ptr, 0, size);
}
return ptr;
}
// Secure memory cleanup
void secure_free(void* ptr, size_t size) {
if (ptr && size > 0) {
// Clear sensitive data
explicit_bzero(ptr, size);
free(ptr);
}
}
// Secure password handling
typedef struct {
char* data;
size_t length;
size_t capacity;
} secure_string_t;
secure_string_t* secure_string_new(size_t initial_capacity) {
secure_string_t* str = malloc(sizeof(secure_string_t));
if (!str) return NULL;
// Use mlock to prevent swapping to disk
str->data = mmap(NULL, initial_capacity,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (str->data == MAP_FAILED) {
free(str);
return NULL;
}
// Lock memory to prevent swapping
if (mlock(str->data, initial_capacity) == -1) {
munmap(str->data, initial_capacity);
free(str);
return NULL;
}
str->length = 0;
str->capacity = initial_capacity;
return str;
}
void secure_string_destroy(secure_string_t* str) {
if (str) {
if (str->data) {
// Clear sensitive data
explicit_bzero(str->data, str->capacity);
munlock(str->data, str->capacity);
munmap(str->data, str->capacity);
}
explicit_bzero(str, sizeof(secure_string_t));
free(str);
}
}
// Input validation
int validate_input(const char* input, size_t max_len) {
if (!input) return 0;
size_t len = strnlen(input, max_len + 1);
if (len > max_len) return 0;
// Check for null bytes (potential null byte injection)
if (strlen(input) != len) return 0;
// Validate characters (example: alphanumeric only)
for (size_t i = 0; i < len; i++) {
if (!((input[i] >= 'a' && input[i] <= 'z') ||
(input[i] >= 'A' && input[i] <= 'Z') ||
(input[i] >= '0' && input[i] <= '9') ||
input[i] == '_' || input[i] == '-')) {
return 0;
}
}
return 1;
}
// Safe file operations
FILE* secure_fopen(const char* filename, const char* mode) {
// Validate filename
if (!filename || !validate_input(filename, PATH_MAX)) {
errno = EINVAL;
return NULL;
}
// Prevent path traversal
if (strstr(filename, "..") || filename[0] != '/') {
errno = EINVAL;
return NULL;
}
// Open with O_NOFOLLOW to prevent symlink attacks
int flags = O_NOFOLLOW;
if (strchr(mode, 'r')) flags |= O_RDONLY;
if (strchr(mode, 'w')) flags |= O_WRONLY | O_CREAT | O_TRUNC;
if (strchr(mode, 'a')) flags |= O_WRONLY | O_CREAT | O_APPEND;
int fd = open(filename, flags, 0644);
if (fd == -1) return NULL;
return fdopen(fd, mode);
}
// Timing-safe comparison
int timing_safe_compare(const void* a, const void* b, size_t len) {
const unsigned char* ua = a;
const unsigned char* ub = b;
unsigned char result = 0;
for (size_t i = 0; i < len; i++) {
result |= ua[i] ^ ub[i];
}
return result == 0;
}
// Random number generation
int secure_random_bytes(void* buf, size_t len) {
FILE* fp = fopen("/dev/urandom", "rb");
if (!fp) return -1;
size_t read_bytes = fread(buf, 1, len, fp);
fclose(fp);
return (read_bytes == len) ? 0 : -1;
}
// Demonstration of secure patterns
int main() {
printf("=== Secure Coding Patterns Demo ===\n\n");
// Secure string handling
char buffer[256] = "Hello, ";
if (secure_concat(buffer, sizeof(buffer), "World!") == 0) {
printf("Secure concatenation: %s\n", buffer);
}
// Secure memory
char* secure_data = secure_malloc(1024);
if (secure_data) {
strcpy(secure_data, "Sensitive data");
printf("Allocated secure memory\n");
secure_free(secure_data, 1024);
}
// Secure string for passwords
secure_string_t* password = secure_string_new(256);
if (password) {
printf("Created secure string for password storage\n");
secure_string_destroy(password);
}
// Input validation
const char* test_input = "valid_input_123";
if (validate_input(test_input, 50)) {
printf("Input validation: PASSED\n");
} else {
printf("Input validation: FAILED\n");
}
// Timing-safe comparison
char hash1[] = "secret_hash";
char hash2[] = "secret_hash";
if (timing_safe_compare(hash1, hash2, strlen(hash1))) {
printf("Timing-safe comparison: MATCH\n");
} else {
printf("Timing-safe comparison: NO MATCH\n");
}
// Random bytes
unsigned char random_data[16];
if (secure_random_bytes(random_data, sizeof(random_data)) == 0) {
printf("Generated %zu random bytes\n", sizeof(random_data));
}
return 0;
}
Security Auditing and Monitoring
Security Event Monitoring
#!/bin/bash
# security_monitor.sh - Security event monitoring and alerting
# Monitor authentication events
monitor_auth_events() {
echo "=== Authentication Monitoring ==="
# Failed login attempts
echo "Recent failed login attempts:"
journalctl -u ssh --since "1 hour ago" | grep "Failed password" | \
awk '{print $1, $2, $3, $11}' | sort | uniq -c | sort -nr | head -10
echo
# Successful logins
echo "Recent successful logins:"
journalctl -u ssh --since "1 hour ago" | grep "Accepted" | \
awk '{print $1, $2, $3, $9, $11}' | tail -10
echo
# Root login attempts
echo "Root login attempts:"
journalctl --since "1 hour ago" | grep -i "root" | grep -E "(login|su|sudo)" | tail -5
echo
}
# Monitor privilege escalation
monitor_privilege_escalation() {
echo "=== Privilege Escalation Monitoring ==="
# Sudo usage
echo "Recent sudo usage:"
journalctl -u sudo --since "1 hour ago" | head -10
echo
# SUID/SGID execution
echo "SUID/SGID programs executed:"
ausearch -m avc,user_cmd -ts recent 2>/dev/null | grep -E "(suid|sgid)" | head -5 || \
echo "No audit events found"
echo
# New SUID/SGID files
echo "Checking for new SUID/SGID files:"
find /usr /bin /sbin -type f \( -perm -4000 -o -perm -2000 \) -newer /var/log/suid_sgid_baseline 2>/dev/null | \
head -10 || echo "No baseline file found"
}
# Monitor file system changes
monitor_filesystem_changes() {
echo "=== File System Monitoring ==="
# Critical system files
echo "Changes to critical system files:"
find /etc -name "*.conf" -newer /var/log/fs_baseline -type f 2>/dev/null | head -10 || \
echo "No baseline found"
echo
# New executable files
echo "New executable files:"
find /tmp /var/tmp /home -type f -executable -newer /var/log/exec_baseline 2>/dev/null | \
head -10 || echo "No new executables found"
echo
# World-writable files
echo "World-writable files (potential security risk):"
find /usr /bin /sbin -type f -perm -002 2>/dev/null | head -5 || \
echo "No world-writable files found"
}
# Monitor network activity
monitor_network_activity() {
echo "=== Network Activity Monitoring ==="
# Listening services
echo "Listening network services:"
ss -tlnp | awk 'NR>1 {print $1, $4, $7}' | head -10
echo
# New network connections
echo "Active network connections:"
ss -tnp | grep ESTAB | awk '{print $4, $5, $6}' | head -10
echo
# Check for suspicious network activity
echo "Checking for suspicious connections:"
netstat -tan | awk '{print $5}' | grep -E '^[0-9]+\.' | \
cut -d: -f1 | sort | uniq -c | sort -nr | head -5 | \
while read count ip; do
if [ $count -gt 10 ]; then
echo "High connection count from $ip: $count connections"
fi
done
}
# Monitor process activity
monitor_process_activity() {
echo "=== Process Activity Monitoring ==="
# Processes running as root
echo "Processes running as root:"
ps aux | awk '$1=="root" && $11!~/^\[/ {print $2, $11}' | head -10
echo
# High CPU/Memory processes
echo "Resource-intensive processes:"
ps aux --sort=-%cpu | awk 'NR<=6 {print $1, $2, $3, $4, $11}'
echo
# Processes with unusual names
echo "Checking for suspicious process names:"
ps aux | awk '{print $11}' | grep -E '^[^/]' | sort | uniq | \
while read proc; do
if [[ $proc =~ ^[0-9]+$ ]] || [[ ${#proc} -eq 1 ]]; then
echo "Suspicious process name: $proc"
fi
done | head -5
}
# Check for rootkits and malware
check_rootkits() {
echo "=== Rootkit Detection ==="
# Check for common rootkit indicators
echo "Checking for hidden processes:"
for pid in /proc/[0-9]*; do
if [ -d "$pid" ] && ! ps -p "$(basename $pid)" >/dev/null 2>&1; then
echo "Hidden process found: $(basename $pid)"
fi
done | head -5
echo
echo "Checking for modified system binaries:"
for binary in /bin/ls /bin/ps /usr/bin/netstat /bin/login; do
if [ -f "$binary" ]; then
if file "$binary" | grep -q "dynamically linked"; then
echo "Binary $binary: OK"
else
echo "Binary $binary: SUSPICIOUS (not dynamically linked)"
fi
fi
done
}
# Generate security report
generate_security_report() {
local output="/tmp/security_report_$(date +%Y%m%d_%H%M%S).txt"
echo "Generating comprehensive security report..."
{
echo "=== Security Report ==="
echo "Generated: $(date)"
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo
monitor_auth_events
monitor_privilege_escalation
monitor_filesystem_changes
monitor_network_activity
monitor_process_activity
check_rootkits
echo "=== System Hardening Status ==="
# Check firewall status
echo "Firewall status:"
systemctl is-active ufw iptables firewalld 2>/dev/null || echo "No firewall active"
echo
# Check SELinux/AppArmor
echo "Mandatory Access Control:"
if command -v getenforce >/dev/null; then
echo "SELinux: $(getenforce)"
elif command -v aa-status >/dev/null; then
echo "AppArmor: $(aa-status --enabled && echo "enabled" || echo "disabled")"
else
echo "No MAC system detected"
fi
echo
# Check for security updates
echo "Security updates:"
if command -v apt >/dev/null; then
apt list --upgradable 2>/dev/null | grep -i security | wc -l | \
awk '{print $1 " security updates available"}'
elif command -v yum >/dev/null; then
yum --security check-update 2>/dev/null | grep -c "Needed" || echo "0 security updates"
fi
} > "$output"
echo "Security report saved to: $output"
}
# Real-time security monitoring
realtime_monitoring() {
echo "=== Real-time Security Monitoring ==="
echo "Press Ctrl+C to stop"
echo
# Monitor critical log files
tail -F /var/log/auth.log /var/log/secure /var/log/audit/audit.log 2>/dev/null | \
while read line; do
# Highlight security events
if echo "$line" | grep -q -E "(Failed|Invalid|Illegal|Attack|Intrusion)"; then
echo "[ALERT] $line"
elif echo "$line" | grep -q -E "(Accepted|Opened|Started)"; then
echo "[INFO] $line"
fi
done
}
# Main function
case "${1:-report}" in
"auth")
monitor_auth_events
;;
"privesc")
monitor_privilege_escalation
;;
"filesystem")
monitor_filesystem_changes
;;
"network")
monitor_network_activity
;;
"processes")
monitor_process_activity
;;
"rootkits")
check_rootkits
;;
"realtime")
realtime_monitoring
;;
"report")
generate_security_report
;;
*)
echo "Usage: $0 {auth|privesc|filesystem|network|processes|rootkits|realtime|report}"
exit 1
;;
esac
Best Practices
- Principle of Least Privilege: Grant minimal necessary permissions
- Defense in Depth: Use multiple security layers
- Input Validation: Validate all external input rigorously
- Secure Defaults: Default to secure configurations
- Regular Audits: Monitor and audit security configurations
- Capability-Based Security: Use capabilities instead of SUID when possible
- Namespace Isolation: Isolate processes using namespaces
Conclusion
Linux security has evolved into a sophisticated ecosystem of complementary technologies. From capabilities and mandatory access controls to user namespaces and privilege separation, modern Linux provides powerful tools for building secure systems. Understanding these mechanisms—and how to combine them effectively—is essential for developing secure applications and maintaining robust system security.
The techniques covered here provide the foundation for implementing defense-in-depth security strategies, from basic privilege separation to advanced container-like isolation. By mastering these security mechanisms, you can build systems that are resilient against modern threats while maintaining functionality and performance.