Przejdź do treści

🛡️ OpenBSD Pledge and Unveil

OpenBSD's pledge and unveil mechanisms provide powerful capabilities for restricting program capabilities. Understanding these security features is essential for secure application development on OpenBSD.


🎯 Introduction to Pledge

What is Pledge?

Pledge is a system call that allows programs to declare which resources they will use, providing defense in depth against potential vulnerabilities.

1
2
3
#include <unistd.h>

int pledge(const char *promises, const char *paths[]);

Pledge restricts a program's access to: - System calls (e.g., read, write, fork, exec) - Path access - Networking capabilities - Process and thread operations

Basic Pledge Usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Restrict to basic operations only
int main(void) {
    // Initial pledge - allow only stdio
    if (pledge("stdio", NULL) == -1) {
        err(1, "pledge");
    }

    // Your code here

    return 0;
}

🔧 Pledge Promises

Available Promises

OpenBSD defines several promise categories:

Promise Description
stdio Allow stdio operations (printf, scanf)
rpath Allow reading from paths
wpath Allow writing to paths
cpath Allow creating files in paths
dpath Allow creating directories
fpath Allow file descriptor operations
getpw Allow password file access
sendfd Allow sending file descriptors
recvfd Allow receiving file descriptors
proc Allow process control (fork, exec)
exec Allow program execution
settime Allow setting time
inet Allow IPv4 network operations
inet6 Allow IPv6 network operations
unix Allow Unix domain sockets
dns Allow DNS lookups
pf Allow packet filter operations
tty Allow TTY operations
chown Allow changing file ownership
chgrp Allow changing file group
unveil Allow unveil operations
error Report pledge violations
vminfo Allow vm.stat information
prot_exec Allow executable memory

Promise Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Web server - network and file access
if (pledge("stdio rpath inet unix", NULL) == -1)
    err(1, "pledge");

// File processing utility
if (pledge("stdio rpath wpath cpath", NULL) == -1)
    err(1, "pledge");

// Simple calculation tool
if (pledge("stdio", NULL) == -1)
    err(1, "pledge");

📋 Pledge Paths

Path Restrictions

The second argument to pledge allows restricting specific paths:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Allow reading only from /var/log
const char *paths[] = {"/var/log", NULL};
if (pledge("stdio rpath", paths) == -1)
    err(1, "pledge");

// Allow reading from multiple paths
const char *paths[] = {
    "/etc",
    "/var/db",
    "/tmp",
    NULL
};
if (pledge("stdio rpath", paths) == -1)
    err(1, "pledge");

Path Combination Examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Read-only access to specific directories
const char *paths[] = {
    "/etc/myapp",
    "/var/myapp/data",
    NULL
};
if (pledge("stdio rpath", paths) == -1)
    err(1, "pledge");

// Write access to specific directories
const char *wpaths[] = {
    "/var/log",
    "/tmp",
    NULL
};
if (pledge("stdio rpath wpath", wpaths) == -1)
    err(1, "pledge");

// Read and write to different paths
const char *allpaths[] = {
    "/etc/myapp",
    "/var/myapp",
    "/tmp",
    NULL
};
if (pledge("stdio rpath wpath cpath", allpaths) == -1)
    err(1, "pledge");

🔍 Unveil Mechanism

What is Unveil?

Unveil restricts filesystem access by allowing programs to specify exactly which filesystem paths they can access.

1
2
3
#include <unistd.h>

int unveil(const char *path, const char *permissions);

Unveil Permissions

Permission Description
r Read access
w Write access
x Execute access
c Create files
b Build (create directories)
d Delete files

Basic Unveil Usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Allow reading from /etc
if (unveil("/etc", "r") == -1)
    err(1, "unveil");

// Allow writing to /var/log
if (unveil("/var/log", "rwc") == -1)
    err(1, "unveil");

// Lock unveil - no more changes allowed
if (unveil(NULL, NULL) == -1)
    err(1, "unveil");

// After lock, call pledge
if (pledge("stdio rpath wpath", NULL) == -1)
    err(1, "pledge");

🛠️ Combining Pledge and Unveil

Order of Operations

  1. Call unveil() for each allowed path
  2. Call unveil(NULL, NULL) to lock filesystem access
  3. Call pledge() to restrict system call access
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
int main(void) {
    // First, unveil filesystem access
    unveil("/etc/myapp.conf", "r");
    unveil("/var/myapp", "rwc");
    unveil("/tmp", "rwc");

    // Lock filesystem access
    if (unveil(NULL, NULL) == -1)
        err(1, "unveil");

    // Now pledge system calls
    if (pledge("stdio rpath wpath cpath", NULL) == -1)
        err(1, "pledge");

    // Program execution with restrictions
    // ...
}

Real-World Example: HTTP Server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int main(void) {
    // Configure filesystem access
    unveil("/var/www/htdocs", "r");  // Static files
    unveil("/var/www/logs", "rwc");  // Logging
    unveil(NULL, NULL);               // Lock filesystem

    // Configure system call access
    if (pledge("stdio rpath inet unix", NULL) == -1)
        err(1, "pledge");

    // Server implementation
    // ...
}

Real-World Example: File Converter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <input> <output>\n", argv[0]);
        return 1;
    }

    // Verify paths are accessible
    const char *input_path = argv[1];
    const char *output_path = argv[2];

    // Unveil input file
    if (unveil(input_path, "r") == -1)
        err(1, "unveil");

    // Unveil output file
    if (unveil(output_path, "c") == -1)
        err(1, "unveil");

    // Lock filesystem
    if (unveil(NULL, NULL) == -1)
        err(1, "unveil");

    // Pledge restricted operations
    if (pledge("stdio rpath wpath cpath", NULL) == -1)
        err(1, "pledge");

    // Perform conversion
    // ...
}

🎨 Advanced Patterns

Progressive Pledge Relaxation

Some programs need to start with restricted capabilities and expand later:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(void) {
    // Initial restrictive pledge
    if (pledge("stdio", NULL) == -1)
        err(1, "pledge");

    // Parse command line options
    parse_arguments();

    // Expand based on requirements
    if (need_network) {
        if (pledge("stdio inet", NULL) == -1)
            err(1, "pledge");
    }

    if (need_file_write) {
        if (pledge("stdio rpath wpath", NULL) == -1)
            err(1, "pledge");
    }

    // Main execution
    // ...
}

Conditional Promises

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[]) {
    // Base promises always needed
    const char *promises = "stdio";

    // Check if we need network
    if (needs_network_access()) {
        promises = "stdio inet";
    }

    // Check if we need file writing
    if (needs_write_access()) {
        promises = "stdio rpath wpath";
    }

    if (pledge(promises, NULL) == -1)
        err(1, "pledge");

    // Execution
    // ...
}

Error Handling

1
2
3
4
5
6
7
8
9
// Check pledge return value
if (pledge("stdio rpath", NULL) == -1) {
    if (errno == EPERM) {
        // Already pledged
        warnx("program already pledged");
    } else {
        err(1, "pledge failed");
    }
}

🛡️ Security Best Practices

Design Principles

  1. Principle of Least Privilege: Request only the minimum capabilities needed
  2. Early Restriction: Apply pledge/unveil as early as possible
  3. Specific Paths: Always specify paths when possible
  4. Progressive Restriction: Start restrictive, expand as needed
  5. Defense in Depth: Use both pledge and unveil together

Common Patterns

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ✅ Good: Specific promises and paths
if (pledge("stdio rpath wpath", allowed_paths) == -1)
    err(1, "pledge");

// ❌ Bad: Too permissive
if (pledge("stdio rpath wpath inet unix", NULL) == -1)
    err(1, "pledge");

// ✅ Good: Lock filesystem first
unveil("/etc/config", "r");
unveil("/var/data", "rwc");
unveil(NULL, NULL);
if (pledge("stdio rpath wpath", NULL) == -1)
    err(1, "pledge");

// ❌ Bad: No filesystem restrictions
if (pledge("stdio rpath wpath", NULL) == -1)
    err(1, "pledge");

Audit Your Promises

1
2
3
4
5
6
7
8
9
// Log pledge violations in debug builds
#ifdef DEBUG
if (pledge("stdio rpath", paths) == -1)
    err(1, "pledge");
#else
// In production, use error promise
if (pledge("stdio rpath error", paths) == -1)
    err(1, "pledge");
#endif

📊 Pledge Violations

Handling Violations

When a program violates its pledge, it is killed by the kernel. You can enable error reporting:

1
2
3
4
5
6
// Enable error reporting
if (pledge("stdio rpath", paths) == -1)
    err(1, "pledge");

// Program will be killed if it violates promises
// The kernel logs pledge violations to syslog

Debugging Violations

1
2
3
4
5
6
7
8
# Check system log for pledge violations
grep pledge /var/log/messages

# Use dmesg for recent violations
dmesg | grep pledge

# Enable pledge auditing in kernel (development only)
options PLPAGENT  # Not for production

🧾 Summary

Pledge/Unveil Quick Reference

Operation Purpose
pledge("stdio", NULL) Allow only stdio operations
pledge("rpath", paths) Allow reading from specific paths
pledge("inet", NULL) Allow IPv4 networking
unveil("/path", "r") Allow reading path
unveil(NULL, NULL) Lock filesystem access

Security Benefits

Defense in Depth: Multiple layers of protection ✅ Vulnerability Mitigation: Limits damage from exploits ✅ Capability Control: Programs only access what they need ✅ Audit Trail: Violations are logged by the kernel

Common Pitfalls

❌ Requesting too many promises ❌ Not using unveil with pledge ❌ Calling pledge too late in program execution ❌ Forgetting to lock unveil with unveil(NULL, NULL) ❌ Not handling pledge failures


🧾 See Also