/src/strongswan/src/libstrongswan/utils/capabilities.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2012-2015 Tobias Brunner |
3 | | * Copyright (C) 2012 Martin Willi |
4 | | * |
5 | | * Copyright (C) secunet Security Networks AG |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify it |
8 | | * under the terms of the GNU General Public License as published by the |
9 | | * Free Software Foundation; either version 2 of the License, or (at your |
10 | | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. |
11 | | * |
12 | | * This program is distributed in the hope that it will be useful, but |
13 | | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
14 | | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
15 | | * for more details. |
16 | | */ |
17 | | |
18 | | #include "capabilities.h" |
19 | | |
20 | | #include <utils/debug.h> |
21 | | |
22 | | #include <errno.h> |
23 | | #include <string.h> |
24 | | #include <sys/types.h> |
25 | | #include <unistd.h> |
26 | | |
27 | | #ifndef WIN32 |
28 | | #include <pwd.h> |
29 | | #include <grp.h> |
30 | | #ifdef HAVE_PRCTL |
31 | | # include <sys/prctl.h> |
32 | | #endif /* HAVE_PRCTL */ |
33 | | |
34 | | #if !defined(HAVE_GETPWNAM_R) || \ |
35 | | !defined(HAVE_GETGRNAM_R) || \ |
36 | | !defined(HAVE_GETPWUID_R) |
37 | | # include <threading/mutex.h> |
38 | | # define EMULATE_R_FUNCS |
39 | | #endif |
40 | | #endif /* !WIN32 */ |
41 | | |
42 | | typedef struct private_capabilities_t private_capabilities_t; |
43 | | |
44 | | /** |
45 | | * Private data of an capabilities_t object. |
46 | | */ |
47 | | struct private_capabilities_t { |
48 | | |
49 | | /** |
50 | | * Public capabilities_t interface. |
51 | | */ |
52 | | capabilities_t public; |
53 | | |
54 | | /** |
55 | | * user ID to switch during rights dropping |
56 | | */ |
57 | | uid_t uid; |
58 | | |
59 | | /** |
60 | | * group ID to switch during rights dropping |
61 | | */ |
62 | | gid_t gid; |
63 | | |
64 | | /** |
65 | | * capabilities to keep |
66 | | */ |
67 | | #ifdef CAPABILITIES_LIBCAP |
68 | | cap_t caps; |
69 | | #endif /* CAPABILITIES_LIBCAP */ |
70 | | #ifdef CAPABILITIES_NATIVE |
71 | | struct __user_cap_data_struct caps[2]; |
72 | | #endif /* CAPABILITIES_NATIVE */ |
73 | | |
74 | | #ifdef EMULATE_R_FUNCS |
75 | | /** |
76 | | * mutex to emulate get(pw|gr)nam_r functions |
77 | | */ |
78 | | mutex_t *mutex; |
79 | | #endif |
80 | | }; |
81 | | |
82 | | #ifndef WIN32 |
83 | | |
84 | | /** |
85 | | * Returns TRUE if the current process/user is member of the given group |
86 | | */ |
87 | | static bool has_group(gid_t group) |
88 | 0 | { |
89 | 0 | gid_t *groups; |
90 | 0 | long ngroups, i; |
91 | 0 | bool found = FALSE; |
92 | |
|
93 | 0 | if (group == getegid()) |
94 | 0 | { /* it's unspecified if this is part of the list below or not */ |
95 | 0 | return TRUE; |
96 | 0 | } |
97 | 0 | ngroups = sysconf(_SC_NGROUPS_MAX); |
98 | 0 | if (ngroups == -1) |
99 | 0 | { |
100 | 0 | DBG1(DBG_LIB, "getting groups for current process failed: %s", |
101 | 0 | strerror(errno)); |
102 | 0 | return FALSE; |
103 | 0 | } |
104 | 0 | groups = calloc(ngroups + 1, sizeof(gid_t)); |
105 | 0 | ngroups = getgroups(ngroups, groups); |
106 | 0 | if (ngroups == -1) |
107 | 0 | { |
108 | 0 | DBG1(DBG_LIB, "getting groups for current process failed: %s", |
109 | 0 | strerror(errno)); |
110 | 0 | free(groups); |
111 | 0 | return FALSE; |
112 | 0 | } |
113 | 0 | for (i = 0; i < ngroups; i++) |
114 | 0 | { |
115 | 0 | if (group == groups[i]) |
116 | 0 | { |
117 | 0 | found = TRUE; |
118 | 0 | break; |
119 | 0 | } |
120 | 0 | } |
121 | 0 | free(groups); |
122 | 0 | return found; |
123 | 0 | } |
124 | | |
125 | | /** |
126 | | * Verify that the current process has the given capability |
127 | | */ |
128 | | static bool has_capability(private_capabilities_t *this, u_int cap, |
129 | | bool *ignore) |
130 | 0 | { |
131 | 0 | if (cap == CAP_CHOWN) |
132 | 0 | { /* if new files/UNIX sockets are created they should be owned by the |
133 | | * configured user and group. This requires a call to chown(2). But |
134 | | * CAP_CHOWN is not always required. */ |
135 | 0 | if (!this->uid || geteuid() == this->uid) |
136 | 0 | { /* if the owner does not change CAP_CHOWN is not needed */ |
137 | 0 | if (!this->gid || has_group(this->gid)) |
138 | 0 | { /* the same applies if the owner is a member of the group */ |
139 | 0 | if (ignore) |
140 | 0 | { /* we don't have to keep this, if requested */ |
141 | 0 | *ignore = TRUE; |
142 | 0 | } |
143 | 0 | return TRUE; |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | 0 | #ifndef CAPABILITIES |
148 | | /* if we can't check the actual capabilities assume only root has it */ |
149 | 0 | return geteuid() == 0; |
150 | 0 | #endif /* !CAPABILITIES */ |
151 | | #ifdef CAPABILITIES_LIBCAP |
152 | | cap_flag_value_t val; |
153 | | cap_t caps; |
154 | | bool ok; |
155 | | |
156 | | caps = cap_get_proc(); |
157 | | if (!caps) |
158 | | { |
159 | | return FALSE; |
160 | | } |
161 | | ok = cap_get_flag(caps, cap, CAP_PERMITTED, &val) == 0 && val == CAP_SET; |
162 | | cap_free(caps); |
163 | | return ok; |
164 | | #endif /* CAPABILITIES_LIBCAP */ |
165 | | #ifdef CAPABILITIES_NATIVE |
166 | | struct __user_cap_header_struct header = { |
167 | | #if defined(_LINUX_CAPABILITY_VERSION_3) |
168 | | .version = _LINUX_CAPABILITY_VERSION_3, |
169 | | #elif defined(_LINUX_CAPABILITY_VERSION_2) |
170 | | .version = _LINUX_CAPABILITY_VERSION_2, |
171 | | #elif defined(_LINUX_CAPABILITY_VERSION_1) |
172 | | .version = _LINUX_CAPABILITY_VERSION_1, |
173 | | #else |
174 | | .version = _LINUX_CAPABILITY_VERSION, |
175 | | #endif |
176 | | }; |
177 | | struct __user_cap_data_struct caps[2]; |
178 | | int i = 0; |
179 | | |
180 | | if (cap >= 32) |
181 | | { |
182 | | i++; |
183 | | cap -= 32; |
184 | | } |
185 | | return capget(&header, caps) == 0 && caps[i].permitted & (1 << cap); |
186 | | #endif /* CAPABILITIES_NATIVE */ |
187 | 0 | } |
188 | | |
189 | | #else /* WIN32 */ |
190 | | |
191 | | /** |
192 | | * Verify that the current process has the given capability, dummy variant |
193 | | */ |
194 | | static bool has_capability(private_capabilities_t *this, u_int cap, |
195 | | bool *ignore) |
196 | | { |
197 | | return TRUE; |
198 | | } |
199 | | |
200 | | #endif /* WIN32 */ |
201 | | |
202 | | /** |
203 | | * Keep the given capability if it is held by the current process. Returns |
204 | | * FALSE, if this is not the case. |
205 | | */ |
206 | | static bool keep_capability(private_capabilities_t *this, u_int cap) |
207 | 0 | { |
208 | | #ifdef CAPABILITIES_LIBCAP |
209 | | cap_set_flag(this->caps, CAP_EFFECTIVE, 1, &cap, CAP_SET); |
210 | | cap_set_flag(this->caps, CAP_INHERITABLE, 1, &cap, CAP_SET); |
211 | | cap_set_flag(this->caps, CAP_PERMITTED, 1, &cap, CAP_SET); |
212 | | #endif /* CAPABILITIES_LIBCAP */ |
213 | | #ifdef CAPABILITIES_NATIVE |
214 | | int i = 0; |
215 | | |
216 | | if (cap >= 32) |
217 | | { |
218 | | i++; |
219 | | cap -= 32; |
220 | | } |
221 | | this->caps[i].effective |= 1 << cap; |
222 | | this->caps[i].permitted |= 1 << cap; |
223 | | this->caps[i].inheritable |= 1 << cap; |
224 | | #endif /* CAPABILITIES_NATIVE */ |
225 | 0 | return TRUE; |
226 | 0 | } |
227 | | |
228 | | METHOD(capabilities_t, keep, bool, |
229 | | private_capabilities_t *this, u_int cap) |
230 | 0 | { |
231 | 0 | bool ignore = FALSE; |
232 | |
|
233 | 0 | if (!has_capability(this, cap, &ignore)) |
234 | 0 | { |
235 | 0 | return FALSE; |
236 | 0 | } |
237 | 0 | else if (ignore) |
238 | 0 | { /* don't keep capabilities that are not required */ |
239 | 0 | return TRUE; |
240 | 0 | } |
241 | 0 | return keep_capability(this, cap); |
242 | 0 | } |
243 | | |
244 | | METHOD(capabilities_t, check, bool, |
245 | | private_capabilities_t *this, u_int cap) |
246 | 0 | { |
247 | 0 | return has_capability(this, cap, NULL); |
248 | 0 | } |
249 | | |
250 | | METHOD(capabilities_t, get_uid, uid_t, |
251 | | private_capabilities_t *this) |
252 | 0 | { |
253 | | #ifdef WIN32 |
254 | | return this->uid; |
255 | | #else |
256 | 0 | return this->uid ?: geteuid(); |
257 | 0 | #endif |
258 | 0 | } |
259 | | |
260 | | METHOD(capabilities_t, get_gid, gid_t, |
261 | | private_capabilities_t *this) |
262 | 0 | { |
263 | | #ifdef WIN32 |
264 | | return this->gid; |
265 | | #else |
266 | 0 | return this->gid ?: getegid(); |
267 | 0 | #endif |
268 | 0 | } |
269 | | |
270 | | METHOD(capabilities_t, set_uid, void, |
271 | | private_capabilities_t *this, uid_t uid) |
272 | 0 | { |
273 | 0 | this->uid = uid; |
274 | 0 | } |
275 | | |
276 | | METHOD(capabilities_t, set_gid, void, |
277 | | private_capabilities_t *this, gid_t gid) |
278 | 0 | { |
279 | 0 | this->gid = gid; |
280 | 0 | } |
281 | | |
282 | | METHOD(capabilities_t, resolve_uid, bool, |
283 | | private_capabilities_t *this, char *username) |
284 | 0 | { |
285 | 0 | #ifndef WIN32 |
286 | 0 | struct passwd *pwp; |
287 | 0 | int err; |
288 | |
|
289 | 0 | #ifdef HAVE_GETPWNAM_R |
290 | 0 | struct passwd passwd; |
291 | 0 | size_t buflen = 1024; |
292 | 0 | char *buf = NULL; |
293 | |
|
294 | 0 | while (TRUE) |
295 | 0 | { |
296 | 0 | buf = realloc(buf, buflen); |
297 | 0 | err = getpwnam_r(username, &passwd, buf, buflen, &pwp); |
298 | 0 | if (err == ERANGE) |
299 | 0 | { |
300 | 0 | buflen *= 2; |
301 | 0 | continue; |
302 | 0 | } |
303 | 0 | if (pwp) |
304 | 0 | { |
305 | 0 | this->uid = pwp->pw_uid; |
306 | 0 | } |
307 | 0 | break; |
308 | 0 | } |
309 | 0 | free(buf); |
310 | | #else /* HAVE GETPWNAM_R */ |
311 | | this->mutex->lock(this->mutex); |
312 | | pwp = getpwnam(username); |
313 | | if (pwp) |
314 | | { |
315 | | this->uid = pwp->pw_uid; |
316 | | } |
317 | | err = errno; |
318 | | this->mutex->unlock(this->mutex); |
319 | | #endif /* HAVE GETPWNAM_R */ |
320 | 0 | if (pwp) |
321 | 0 | { |
322 | 0 | return TRUE; |
323 | 0 | } |
324 | 0 | DBG1(DBG_LIB, "resolving user '%s' failed: %s", username, |
325 | 0 | err ? strerror(err) : "user not found"); |
326 | 0 | #endif /* !WIN32 */ |
327 | 0 | return FALSE; |
328 | 0 | } |
329 | | |
330 | | METHOD(capabilities_t, resolve_gid, bool, |
331 | | private_capabilities_t *this, char *groupname) |
332 | 0 | { |
333 | 0 | #ifndef WIN32 |
334 | 0 | struct group *grp; |
335 | 0 | int err; |
336 | |
|
337 | 0 | #ifdef HAVE_GETGRNAM_R |
338 | 0 | struct group group; |
339 | 0 | size_t buflen = 1024; |
340 | 0 | char *buf = NULL; |
341 | |
|
342 | 0 | while (TRUE) |
343 | 0 | { |
344 | 0 | buf = realloc(buf, buflen); |
345 | 0 | err = getgrnam_r(groupname, &group, buf, buflen, &grp); |
346 | 0 | if (err == ERANGE) |
347 | 0 | { |
348 | 0 | buflen *= 2; |
349 | 0 | continue; |
350 | 0 | } |
351 | 0 | if (grp) |
352 | 0 | { |
353 | 0 | this->gid = grp->gr_gid; |
354 | 0 | } |
355 | 0 | break; |
356 | 0 | } |
357 | 0 | free(buf); |
358 | | #else /* HAVE_GETGRNAM_R */ |
359 | | this->mutex->lock(this->mutex); |
360 | | grp = getgrnam(groupname); |
361 | | if (grp) |
362 | | { |
363 | | this->gid = grp->gr_gid; |
364 | | } |
365 | | err = errno; |
366 | | this->mutex->unlock(this->mutex); |
367 | | #endif /* HAVE_GETGRNAM_R */ |
368 | 0 | if (grp) |
369 | 0 | { |
370 | 0 | return TRUE; |
371 | 0 | } |
372 | 0 | DBG1(DBG_LIB, "resolving user '%s' failed: %s", groupname, |
373 | 0 | err ? strerror(err) : "group not found"); |
374 | 0 | #endif /* !WIN32 */ |
375 | 0 | return FALSE; |
376 | 0 | } |
377 | | |
378 | | #ifndef WIN32 |
379 | | /** |
380 | | * Initialize supplementary groups for unprivileged user |
381 | | */ |
382 | | static bool init_supplementary_groups(private_capabilities_t *this) |
383 | 0 | { |
384 | 0 | struct passwd *pwp; |
385 | 0 | int res = -1; |
386 | |
|
387 | 0 | #ifdef HAVE_GETPWUID_R |
388 | 0 | struct passwd pwd; |
389 | 0 | size_t buflen = 1024; |
390 | 0 | char *buf = NULL; |
391 | |
|
392 | 0 | while (TRUE) |
393 | 0 | { |
394 | 0 | buf = realloc(buf, buflen); |
395 | 0 | if (getpwuid_r(this->uid, &pwd, buf, buflen, &pwp) == ERANGE) |
396 | 0 | { |
397 | 0 | buflen *= 2; |
398 | 0 | continue; |
399 | 0 | } |
400 | 0 | if (pwp) |
401 | 0 | { |
402 | 0 | res = initgroups(pwp->pw_name, this->gid); |
403 | 0 | } |
404 | 0 | break; |
405 | 0 | } |
406 | 0 | free(buf); |
407 | | #else /* HAVE_GETPWUID_R */ |
408 | | this->mutex->lock(this->mutex); |
409 | | pwp = getpwuid(this->uid); |
410 | | if (pwp) |
411 | | { |
412 | | res = initgroups(pwp->pw_name, this->gid); |
413 | | } |
414 | | this->mutex->unlock(this->mutex); |
415 | | #endif /* HAVE_GETPWUID_R */ |
416 | 0 | return res == 0; |
417 | 0 | } |
418 | | #endif /* WIN32 */ |
419 | | |
420 | | METHOD(capabilities_t, drop, bool, |
421 | | private_capabilities_t *this) |
422 | 0 | { |
423 | 0 | #ifndef WIN32 |
424 | 0 | #ifdef HAVE_PRCTL |
425 | 0 | if (has_capability(this, CAP_SETPCAP, NULL)) |
426 | 0 | { |
427 | 0 | prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); |
428 | 0 | } |
429 | 0 | #endif |
430 | |
|
431 | 0 | if (this->uid && !init_supplementary_groups(this)) |
432 | 0 | { |
433 | 0 | DBG1(DBG_LIB, "initializing supplementary groups for %u failed", |
434 | 0 | this->uid); |
435 | 0 | return FALSE; |
436 | 0 | } |
437 | 0 | if (this->gid && setgid(this->gid) != 0) |
438 | 0 | { |
439 | 0 | DBG1(DBG_LIB, "change to unprivileged group %u failed: %s", |
440 | 0 | this->gid, strerror(errno)); |
441 | 0 | return FALSE; |
442 | 0 | } |
443 | 0 | if (this->uid && setuid(this->uid) != 0) |
444 | 0 | { |
445 | 0 | DBG1(DBG_LIB, "change to unprivileged user %u failed: %s", |
446 | 0 | this->uid, strerror(errno)); |
447 | 0 | return FALSE; |
448 | 0 | } |
449 | | |
450 | | #ifdef CAPABILITIES_LIBCAP |
451 | | if (cap_set_proc(this->caps) != 0) |
452 | | { |
453 | | DBG1(DBG_LIB, "dropping capabilities failed: %s", strerror(errno)); |
454 | | return FALSE; |
455 | | } |
456 | | #endif /* CAPABILITIES_LIBCAP */ |
457 | | #ifdef CAPABILITIES_NATIVE |
458 | | struct __user_cap_header_struct header = { |
459 | | #if defined(_LINUX_CAPABILITY_VERSION_3) |
460 | | .version = _LINUX_CAPABILITY_VERSION_3, |
461 | | #elif defined(_LINUX_CAPABILITY_VERSION_2) |
462 | | .version = _LINUX_CAPABILITY_VERSION_2, |
463 | | #elif defined(_LINUX_CAPABILITY_VERSION_1) |
464 | | .version = _LINUX_CAPABILITY_VERSION_1, |
465 | | #else |
466 | | .version = _LINUX_CAPABILITY_VERSION, |
467 | | #endif |
468 | | }; |
469 | | if (capset(&header, this->caps) != 0) |
470 | | { |
471 | | DBG1(DBG_LIB, "dropping capabilities failed: %s", strerror(errno)); |
472 | | return FALSE; |
473 | | } |
474 | | #endif /* CAPABILITIES_NATIVE */ |
475 | | #ifdef CAPABILITIES |
476 | | DBG1(DBG_LIB, "dropped capabilities, running as uid %u, gid %u", |
477 | | geteuid(), getegid()); |
478 | | #endif /* CAPABILITIES */ |
479 | 0 | #endif /*!WIN32 */ |
480 | 0 | return TRUE; |
481 | 0 | } |
482 | | |
483 | | METHOD(capabilities_t, destroy, void, |
484 | | private_capabilities_t *this) |
485 | 3.51k | { |
486 | | #ifdef EMULATE_R_FUNCS |
487 | | this->mutex->destroy(this->mutex); |
488 | | #endif /* EMULATE_R_FUNCS */ |
489 | | #ifdef CAPABILITIES_LIBCAP |
490 | | cap_free(this->caps); |
491 | | #endif /* CAPABILITIES_LIBCAP */ |
492 | 3.51k | free(this); |
493 | 3.51k | } |
494 | | |
495 | | /** |
496 | | * See header |
497 | | */ |
498 | | capabilities_t *capabilities_create() |
499 | 3.51k | { |
500 | 3.51k | private_capabilities_t *this; |
501 | | |
502 | 3.51k | INIT(this, |
503 | 3.51k | .public = { |
504 | 3.51k | .keep = _keep, |
505 | 3.51k | .check = _check, |
506 | 3.51k | .get_uid = _get_uid, |
507 | 3.51k | .get_gid = _get_gid, |
508 | 3.51k | .set_uid = _set_uid, |
509 | 3.51k | .set_gid = _set_gid, |
510 | 3.51k | .resolve_uid = _resolve_uid, |
511 | 3.51k | .resolve_gid = _resolve_gid, |
512 | 3.51k | .drop = _drop, |
513 | 3.51k | .destroy = _destroy, |
514 | 3.51k | }, |
515 | 3.51k | ); |
516 | | |
517 | | #ifdef CAPABILITIES_LIBCAP |
518 | | this->caps = cap_init(); |
519 | | #endif /* CAPABILITIES_LIBCAP */ |
520 | | |
521 | | #ifdef EMULATE_R_FUNCS |
522 | | this->mutex = mutex_create(MUTEX_TYPE_DEFAULT); |
523 | | #endif /* EMULATE_R_FUNCS */ |
524 | | |
525 | 3.51k | return &this->public; |
526 | 3.51k | } |