🛡️ 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.
| #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
| // 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
| // 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.
| #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
- Call
unveil() for each allowed path
- Call
unveil(NULL, NULL) to lock filesystem access
- 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
| // 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
- Principle of Least Privilege: Request only the minimum capabilities needed
- Early Restriction: Apply pledge/unveil as early as possible
- Specific Paths: Always specify paths when possible
- Progressive Restriction: Start restrictive, expand as needed
- 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
| // 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:
| // 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
| # 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