Add basic pledge/unveil sandbox on OpenBSD
[tinc] / src / bsd / openbsd / tincd.c
1 #include "../../system.h"
2
3 #include <libgen.h>
4 #include <assert.h>
5
6 #include "sandbox.h"
7 #include "../../device.h"
8 #include "../../logger.h"
9 #include "../../names.h"
10 #include "../../net.h"
11 #include "../../sandbox.h"
12 #include "../../script.h"
13 #include "../../xalloc.h"
14 #include "../../proxy.h"
15
16 static sandbox_level_t current_level = SANDBOX_NONE;
17 static bool can_use_new_paths = true;
18 static bool entered = false;
19
20 static bool chrooted(void) {
21         return !(confbase && *confbase);
22 }
23
24 static void create_conf_subdir(const char *name, mode_t mode) {
25         char path[PATH_MAX];
26         snprintf(path, sizeof(path), "%s/%s", confbase, name);
27         mkdir(path, mode);
28 }
29
30 static void open_conf_subdir(const char *name, const char *privs) {
31         char path[PATH_MAX];
32         snprintf(path, sizeof(path), "%s/%s", confbase, name);
33         allow_path(path, privs);
34 }
35
36 static void open_common_paths(bool can_exec) {
37         // Dummy device uses a fake path, skip it
38         const char *dev = strcasecmp(device, DEVICE_DUMMY) ? device : NULL;
39
40         // These calls must be done before the first unveil() for two reasons:
41         //   1. the first unveil() blocks access to all other paths.
42         //   2. unveil() remembers the exact directory and won't allow access if it's (re)created.
43         create_conf_subdir("cache", 0777);
44         create_conf_subdir("hosts", 0777);
45         create_conf_subdir("invitations", 0700);
46
47         const unveil_path_t paths[] = {
48                 {"/dev/random",  "r"},
49                 {"/dev/urandom", "r"},
50                 {confbase,       can_exec ? "rx" : "r"},
51                 {dev,            "rw"},
52                 {logfilename,    "rwc"},
53                 {pidfilename,    "rwc"},
54                 {unixsocketname, "rwc"},
55                 {NULL,           NULL},
56         };
57         allow_paths(paths);
58
59         open_conf_subdir("cache", "rwc");
60         open_conf_subdir("hosts", can_exec ? "rwxc" : "rwc");
61         open_conf_subdir("invitations", "rwc");
62 }
63
64 static void open_exec_paths(void) {
65         // proxyhost was checked previously. If we're here, proxyhost
66         // contains the path to the executable, and nothing else.
67         const char *proxy_exec = proxytype == PROXY_EXEC ? proxyhost : NULL;
68
69         const unveil_path_t bin_paths[] = {
70                 {"/bin",            "rx"},
71                 {"/sbin",           "rx"},
72                 {"/usr/bin",        "rx"},
73                 {"/usr/sbin",       "rx"},
74                 {"/usr/local/bin",  "rx"},
75                 {"/usr/local/sbin", "rx"},
76                 {scriptinterpreter, "rx"},
77                 {proxy_exec,        "rx"},
78                 {NULL,              NULL},
79         };
80         allow_paths(bin_paths);
81 }
82
83 static bool sandbox_privs(bool can_exec) {
84         // no mcast since multicasting should be set up by now
85         char promises[512] =
86                 "stdio"  // General I/O, both disk and network
87                 " rpath" // Read files and directories
88                 " wpath" // Write files and directories
89                 " cpath" // Create new ones
90                 " dns"   // Resolve domain names
91                 " inet"  // Make network connections
92                 " unix"; // Control socket connections from tinc CLI
93
94         if(can_exec) {
95                 // fork() and execve() for scripts and exec proxies
96                 const char *exec = " proc exec";
97                 size_t n = strlcat(promises, exec, sizeof(promises));
98                 assert(n < sizeof(promises));
99         }
100
101         return restrict_privs(promises, can_exec ? PROMISES_ALL : PROMISES_NONE);
102 }
103
104 static void sandbox_paths(bool can_exec) {
105         if(chrooted()) {
106                 logger(DEBUG_ALWAYS, LOG_DEBUG, "chroot is used. Disabling path sandbox.");
107                 return;
108         }
109
110         open_common_paths(can_exec);
111         can_use_new_paths = false;
112
113         if(can_exec) {
114                 if(proxytype == PROXY_EXEC && !access(proxyhost, X_OK)) {
115                         logger(DEBUG_ALWAYS, LOG_WARNING, "Looks like a shell expression was used for exec proxy. Using weak path sandbox.");
116                         allow_path("/", "rx");
117                 } else {
118                         open_exec_paths();
119                 }
120         }
121 }
122
123 static bool sandbox_can_after_enter(sandbox_action_t action) {
124         switch(action) {
125         case START_PROCESSES:
126                 return current_level < SANDBOX_HIGH;
127
128         case USE_NEW_PATHS:
129                 return can_use_new_paths;
130
131         default:
132                 abort();
133         }
134 }
135
136 bool sandbox_can(sandbox_action_t action, sandbox_time_t when) {
137         if(when == AFTER_SANDBOX || entered) {
138                 return sandbox_can_after_enter(action);
139         } else {
140                 return true;
141         }
142 }
143
144 void sandbox_set_level(sandbox_level_t level) {
145         assert(!entered);
146         current_level = level;
147 }
148
149 bool sandbox_enter() {
150         assert(!entered);
151         entered = true;
152
153         if(current_level == SANDBOX_NONE) {
154                 logger(DEBUG_ALWAYS, LOG_DEBUG, "Sandbox is disabled");
155                 return true;
156         }
157
158         bool can_exec = sandbox_can_after_enter(START_PROCESSES);
159
160         sandbox_paths(can_exec);
161
162         if(sandbox_privs(can_exec)) {
163                 logger(DEBUG_ALWAYS, LOG_DEBUG, "Entered sandbox at level %d", current_level);
164                 return true;
165         }
166
167         logger(DEBUG_ALWAYS, LOG_ERR, "Could not enter sandbox. Set a lower level or disable it in tinc.conf");
168         current_level = SANDBOX_NONE;
169
170         return false;
171 }