Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2015 L. A. F. Pereira <l@tia.mat.br>
4 : *
5 : * This program is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU General Public License
7 : * as published by the Free Software Foundation; either version 2
8 : * of the License, or any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 : * USA.
19 : */
20 :
21 : #define _GNU_SOURCE
22 : #include <dirent.h>
23 : #include <errno.h>
24 : #include <grp.h>
25 : #include <limits.h>
26 : #include <linux/capability.h>
27 : #include <pwd.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 : #include <sys/stat.h>
31 : #include <sys/types.h>
32 : #include <unistd.h>
33 :
34 : #include "lwan-private.h"
35 :
36 : #include "lwan-config.h"
37 : #include "lwan-status.h"
38 :
39 0 : static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid)
40 : {
41 0 : struct passwd pwd = {};
42 : struct passwd *result;
43 : char *buf;
44 0 : long pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
45 : int r;
46 :
47 0 : if (pw_size_max < 0) {
48 : /* This constant is returned for sysconf(_SC_GETPW_R_SIZE_MAX) in glibc,
49 : * and it seems to be a reasonable size (1024). Use it as a fallback in
50 : * the (very unlikely) case where sysconf() fails. */
51 0 : pw_size_max = NSS_BUFLEN_PASSWD;
52 : }
53 :
54 0 : buf = malloc((size_t)pw_size_max);
55 0 : if (!buf) {
56 0 : lwan_status_error("Could not allocate buffer for passwd struct");
57 0 : return false;
58 : }
59 :
60 0 : r = getpwnam_r(user, &pwd, buf, (size_t)pw_size_max, &result);
61 0 : *uid = pwd.pw_uid;
62 0 : *gid = pwd.pw_gid;
63 0 : free(buf);
64 :
65 0 : if (result)
66 0 : return true;
67 :
68 0 : if (!r) {
69 0 : lwan_status_error("Username not found: %s", user);
70 : } else {
71 0 : errno = r;
72 0 : lwan_status_perror("Could not obtain uid/gid for user %s", user);
73 : }
74 :
75 0 : return false;
76 : }
77 :
78 0 : static bool switch_to_user(uid_t uid, gid_t gid, const char *username)
79 : {
80 : uid_t ruid, euid, suid;
81 : gid_t rgid, egid, sgid;
82 :
83 0 : lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", uid, gid,
84 : username);
85 :
86 0 : if (setresgid(gid, gid, gid) < 0)
87 0 : return false;
88 : #if defined(__APPLE__)
89 : if (initgroups(username, (int)gid) < 0)
90 : return false;
91 : #else
92 0 : if (initgroups(username, gid) < 0)
93 0 : return false;
94 : #endif
95 0 : if (setresuid(uid, uid, uid) < 0)
96 0 : return false;
97 :
98 0 : if (getresuid(&ruid, &euid, &suid) < 0)
99 0 : return false;
100 0 : if (ruid != euid || euid != suid || suid != uid)
101 0 : return false;
102 :
103 0 : if (getresgid(&rgid, &egid, &sgid) < 0)
104 0 : return false;
105 0 : if (rgid != egid || egid != sgid || sgid != gid)
106 0 : return false;
107 :
108 0 : return true;
109 : }
110 :
111 : #ifdef __linux__
112 0 : static void abort_on_open_directories(void)
113 : {
114 : /* This is racy, but is a way to detect misconfiguration. Since it's
115 : * called just once during boot time, before threads are created, this
116 : * should be fine (maybe not if Lwan is used as a library.)
117 : */
118 0 : DIR *dir = opendir("/proc/self/fd");
119 : struct dirent *ent;
120 : char own_fd[3 * sizeof(int)];
121 : int ret;
122 :
123 0 : if (!dir) {
124 0 : lwan_status_critical_perror(
125 : "Could not determine if there are open directory fds");
126 : }
127 :
128 0 : ret = snprintf(own_fd, sizeof(own_fd), "%d", dirfd(dir));
129 0 : if (ret < 0 || ret >= (int)sizeof(own_fd)) {
130 0 : lwan_status_critical("Could not get descriptor of /proc/self/fd");
131 : }
132 :
133 0 : while ((ent = readdir(dir))) {
134 : char path[PATH_MAX];
135 : struct stat st;
136 : ssize_t len;
137 :
138 0 : if (streq(ent->d_name, own_fd))
139 0 : continue;
140 0 : if (streq(ent->d_name, ".") || streq(ent->d_name, ".."))
141 0 : continue;
142 :
143 0 : len = readlinkat(dirfd(dir), ent->d_name, path, sizeof(path));
144 0 : if (len < 0) {
145 0 : lwan_status_critical_perror("Could not get information about fd %s",
146 : ent->d_name);
147 : }
148 0 : path[len] = '\0';
149 :
150 0 : if (path[0] != '/') {
151 : /* readlink() there will point to the realpath() of a file, so
152 : * if it's on a filesystem, it starts with '/'. Sockets, for
153 : * instance, begin with "socket:" instead... so no need for
154 : * stat(). */
155 0 : continue;
156 : }
157 :
158 0 : if (stat(path, &st) < 0) {
159 0 : lwan_status_critical_perror(
160 : "Could not get information about open file: %s", path);
161 : }
162 :
163 0 : if (S_ISDIR(st.st_mode)) {
164 0 : closedir(dir);
165 :
166 0 : lwan_status_critical(
167 : "The directory '%s' is open (fd %s), can't chroot", path,
168 : ent->d_name);
169 : return;
170 : }
171 : }
172 :
173 0 : closedir(dir);
174 : }
175 : #else
176 : static void abort_on_open_directories(void) {}
177 : #endif
178 :
179 87 : void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj)
180 : {
181 87 : uid_t uid = 0;
182 87 : gid_t gid = 0;
183 87 : bool got_uid_gid = false;
184 :
185 87 : if (!sj->user_name && !sj->chroot_path)
186 87 : goto out;
187 :
188 0 : if (geteuid() != 0)
189 0 : lwan_status_critical("Straitjacket requires root privileges");
190 :
191 0 : if (sj->user_name && *sj->user_name) {
192 0 : if (!get_user_uid_gid(sj->user_name, &uid, &gid))
193 0 : lwan_status_critical("Unknown user: %s", sj->user_name);
194 0 : got_uid_gid = true;
195 : }
196 :
197 0 : if (sj->chroot_path) {
198 0 : abort_on_open_directories();
199 :
200 0 : if (chroot(sj->chroot_path) < 0) {
201 0 : lwan_status_critical_perror("Could not chroot() to %s",
202 : sj->chroot_path);
203 : }
204 :
205 0 : if (chdir("/") < 0)
206 0 : lwan_status_critical_perror("Could not chdir() to /");
207 :
208 0 : lwan_status_info("Jailed to %s", sj->chroot_path);
209 : }
210 :
211 0 : if (got_uid_gid && !switch_to_user(uid, gid, sj->user_name)) {
212 0 : lwan_status_critical("Could not drop privileges to %s, aborting",
213 : sj->user_name);
214 : }
215 :
216 0 : out:
217 87 : if (sj->drop_capabilities) {
218 87 : struct __user_cap_header_struct header = {
219 : .version = _LINUX_CAPABILITY_VERSION_1,
220 : };
221 87 : struct __user_cap_data_struct data = {};
222 :
223 87 : if (capset(&header, &data) < 0)
224 0 : lwan_status_critical_perror("Could not drop capabilities");
225 : }
226 87 : }
227 :
228 87 : void lwan_straitjacket_enforce_from_config(struct config *c)
229 : {
230 : const struct config_line *l;
231 87 : char *user_name = NULL;
232 87 : char *chroot_path = NULL;
233 87 : bool drop_capabilities = true;
234 :
235 174 : while ((l = config_read_line(c))) {
236 87 : switch (l->type) {
237 0 : case CONFIG_LINE_TYPE_LINE:
238 : /* TODO: limit_syscalls */
239 0 : if (streq(l->key, "user")) {
240 0 : if (user_name) {
241 0 : config_error(c, "`user' already specified");
242 87 : return;
243 : }
244 0 : user_name = strdupa(l->value);
245 0 : } else if (streq(l->key, "chroot")) {
246 0 : if (chroot_path) {
247 0 : config_error(c, "`chroot' already specified");
248 0 : return;
249 : }
250 0 : chroot_path = strdupa(l->value);
251 0 : } else if (streq(l->key, "drop_capabilities")) {
252 0 : drop_capabilities = parse_bool(l->value, true);
253 : } else {
254 0 : config_error(c, "Invalid key: %s", l->key);
255 0 : return;
256 : }
257 0 : break;
258 0 : case CONFIG_LINE_TYPE_SECTION:
259 0 : config_error(c, "Straitjacket accepts no sections");
260 0 : return;
261 87 : case CONFIG_LINE_TYPE_SECTION_END:
262 87 : lwan_straitjacket_enforce(&(struct lwan_straitjacket){
263 : .user_name = user_name,
264 : .chroot_path = chroot_path,
265 : .drop_capabilities = drop_capabilities,
266 : });
267 :
268 87 : return;
269 : }
270 : }
271 :
272 0 : config_error(c, "Expecting section end while parsing straitjacket");
273 : }
|