1 /** @file
2
3 A brief file description
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 #include "tscore/ink_config.h"
25 #include "tscore/Diags.h"
26 #include "tscore/ink_cap.h"
27 #include "tscore/ink_thread.h"
28
29 #include <grp.h>
30
31 #if HAVE_SYS_CAPABILITY_H
32 #include <sys/capability.h>
33 #endif
34
35 #if HAVE_SYS_PRCTL_H
36 #include <sys/prctl.h>
37 #endif
38
39 // NOTE: Failing to acquire or release privileges is a fatal error. This is because that should never happen
40 // and if it does, it is likely that some fundamental security assumption has been violated. In that case
41 // it is dangerous to continue.
42
43 #if !TS_USE_POSIX_CAP
44 ink_mutex ElevateAccess::lock = INK_MUTEX_INIT;
45 #endif
46
47 #define DEBUG_CREDENTIALS(tag) \
48 do { \
49 if (is_debug_tag_set(tag)) { \
50 uid_t uid = -1, euid = -1, suid = -1; \
51 gid_t gid = -1, egid = -1, sgid = -1; \
52 getresuid(&uid, &euid, &suid); \
53 getresgid(&gid, &egid, &sgid); \
54 Debug(tag, "uid=%ld, gid=%ld, euid=%ld, egid=%ld, suid=%ld, sgid=%ld", static_cast<long>(uid), static_cast<long>(gid), \
55 static_cast<long>(euid), static_cast<long>(egid), static_cast<long>(suid), static_cast<long>(sgid)); \
56 } \
57 } while (0)
58
59 #if TS_USE_POSIX_CAP
60
61 #define DEBUG_PRIVILEGES(tag) \
62 do { \
63 if (is_debug_tag_set(tag)) { \
64 cap_t caps = cap_get_proc(); \
65 char *caps_text = cap_to_text(caps, nullptr); \
66 Debug(tag, "caps='%s', core=%s, death signal=%d, thread=0x%llx", caps_text, is_dumpable(), death_signal(), \
67 (unsigned long long)pthread_self()); \
68 cap_free(caps_text); \
69 cap_free(caps); \
70 } \
71 } while (0)
72
73 #else /* TS_USE_POSIX_CAP */
74
75 #define DEBUG_PRIVILEGES(tag) \
76 do { \
77 if (is_debug_tag_set(tag)) { \
78 Debug(tag, "caps='', core=%s, death signal=%d, thread=0x%llx", is_dumpable(), death_signal(), \
79 (unsigned long long)pthread_self()); \
80 } \
81 } while (0)
82
83 #endif /* TS_USE_POSIX_CAP */
84
85 #if !HAVE_GETRESUID
86 static int
getresuid(uid_t * uid,uid_t * euid,uid_t * suid)87 getresuid(uid_t *uid, uid_t *euid, uid_t *suid)
88 {
89 *uid = getuid();
90 *euid = geteuid();
91 return 0;
92 }
93 #endif /* !HAVE_GETRESUID */
94
95 #if !HAVE_GETRESGID
96 static int
getresgid(gid_t * gid,gid_t * egid,gid_t * sgid)97 getresgid(gid_t *gid, gid_t *egid, gid_t *sgid)
98 {
99 *gid = getgid();
100 *egid = getegid();
101 return 0;
102 }
103 #endif /* !HAVE_GETRESGID */
104
105 static unsigned
max_passwd_size()106 max_passwd_size()
107 {
108 #if defined(_SC_GETPW_R_SIZE_MAX)
109 long val = sysconf(_SC_GETPW_R_SIZE_MAX);
110 if (val > 0) {
111 return static_cast<unsigned>(val);
112 }
113 #endif
114
115 return 4096;
116 }
117
118 static const char *
is_dumpable()119 is_dumpable()
120 {
121 #if defined(PR_GET_DUMPABLE)
122 return (prctl(PR_GET_DUMPABLE) != 1) ? "disabled" : "enabled";
123 #else
124 return "unknown";
125 #endif
126 }
127
128 static int
death_signal()129 death_signal()
130 {
131 int signum = -1;
132
133 #if defined(PR_GET_PDEATHSIG)
134 prctl(PR_GET_PDEATHSIG, &signum, 0, 0, 0);
135 #endif
136
137 return signum;
138 }
139
140 void
DebugCapabilities(const char * tag)141 DebugCapabilities(const char *tag)
142 {
143 DEBUG_CREDENTIALS(tag);
144 DEBUG_PRIVILEGES(tag);
145 }
146
147 static void
impersonate(const struct passwd * pwd,ImpersonationLevel level)148 impersonate(const struct passwd *pwd, ImpersonationLevel level)
149 {
150 int deathsig = death_signal();
151 bool dumpable = false;
152
153 DEBUG_CREDENTIALS("privileges");
154 DEBUG_PRIVILEGES("privileges");
155
156 ink_release_assert(pwd != nullptr);
157
158 #if defined(PR_GET_DUMPABLE)
159 dumpable = (prctl(PR_GET_DUMPABLE) == 1);
160 #endif
161
162 // Always repopulate the supplementary group list for the new user.
163 initgroups(pwd->pw_name, pwd->pw_gid);
164
165 switch (level) {
166 case IMPERSONATE_PERMANENT:
167 if (setregid(pwd->pw_gid, pwd->pw_gid) != 0) {
168 Fatal("switching to user %s, failed to set group ID %ld", pwd->pw_name, (long)pwd->pw_gid);
169 }
170
171 if (setreuid(pwd->pw_uid, pwd->pw_uid) != 0) {
172 Fatal("switching to user %s, failed to set user ID %ld", pwd->pw_name, (long)pwd->pw_uid);
173 }
174 break;
175
176 case IMPERSONATE_EFFECTIVE:
177 if (setegid(pwd->pw_gid) != 0) {
178 Fatal("switching to user %s, failed to set group ID %ld", pwd->pw_name, (long)pwd->pw_gid);
179 }
180
181 if (seteuid(pwd->pw_uid) != 0) {
182 Fatal("switching to user %s, failed to set effective user ID %ld", pwd->pw_name, (long)pwd->pw_gid);
183 }
184 break;
185 }
186
187 // Reset process flags if necessary. Elevating privilege using capabilities does not reset process
188 // flags, so we don't have to bother with this in elevateFileAccess().
189
190 EnableCoreFile(dumpable);
191
192 if (deathsig > 0) {
193 EnableDeathSignal(deathsig);
194 }
195
196 DEBUG_CREDENTIALS("privileges");
197 DEBUG_PRIVILEGES("privileges");
198 }
199
200 void
ImpersonateUserID(uid_t uid,ImpersonationLevel level)201 ImpersonateUserID(uid_t uid, ImpersonationLevel level)
202 {
203 struct passwd *pwd;
204 struct passwd pbuf;
205 char buf[max_passwd_size()];
206
207 if (getpwuid_r(uid, &pbuf, buf, sizeof(buf), &pwd) != 0) {
208 Fatal("missing password database entry for UID %ld: %s", (long)uid, strerror(errno));
209 }
210
211 if (pwd == nullptr) {
212 // Password entry not found ...
213 Fatal("missing password database entry for UID %ld", (long)uid);
214 }
215
216 impersonate(pwd, level);
217 }
218
219 void
ImpersonateUser(const char * user,ImpersonationLevel level)220 ImpersonateUser(const char *user, ImpersonationLevel level)
221 {
222 struct passwd *pwd;
223 struct passwd pbuf;
224 char buf[max_passwd_size()];
225
226 if (*user == '#') {
227 // Numeric user notation.
228 uid_t uid = static_cast<uid_t>(atoi(&user[1]));
229 if (getpwuid_r(uid, &pbuf, buf, sizeof(buf), &pwd) != 0) {
230 Fatal("missing password database entry for UID %ld: %s", (long)uid, strerror(errno));
231 }
232 } else {
233 if (getpwnam_r(user, &pbuf, buf, sizeof(buf), &pwd) != 0) {
234 Fatal("missing password database entry for username '%s': %s", user, strerror(errno));
235 }
236 }
237
238 if (pwd == nullptr) {
239 // Password entry not found ...
240 Fatal("missing password database entry for '%s'", user);
241 }
242
243 impersonate(pwd, level);
244 }
245
246 bool
PreserveCapabilities()247 PreserveCapabilities()
248 {
249 int zret = 0;
250 #if TS_USE_POSIX_CAP
251 zret = prctl(PR_SET_KEEPCAPS, 1);
252 #endif
253 Debug("privileges", "[PreserveCapabilities] zret : %d", zret);
254 return zret == 0;
255 }
256
257 // Adjust the capabilities to only those needed.
258 bool
RestrictCapabilities()259 RestrictCapabilities()
260 {
261 int zret = 0; // return value.
262 #if TS_USE_POSIX_CAP
263 cap_t caps_good = cap_init(); // Start with nothing
264 cap_t caps_orig = cap_get_proc();
265
266 // Capabilities we need.
267 cap_value_t perm_list[] = {CAP_NET_ADMIN, CAP_NET_BIND_SERVICE, CAP_IPC_LOCK, CAP_DAC_OVERRIDE, CAP_FOWNER};
268 static int const PERM_CAP_COUNT = sizeof(perm_list) / sizeof(*perm_list);
269 cap_value_t eff_list[] = {CAP_NET_ADMIN, CAP_NET_BIND_SERVICE, CAP_IPC_LOCK};
270 static int const EFF_CAP_COUNT = sizeof(eff_list) / sizeof(*eff_list);
271
272 // Request capabilities one at a time. If one capability fails
273 // the rest may succeed. If this scenario does not need that capability
274 // Must start with the current privileges in case we fail we can get back in
275 // that is ok.
276 for (int i = 0; i < PERM_CAP_COUNT; i++) {
277 cap_t caps = cap_get_proc();
278 if (cap_set_flag(caps, CAP_PERMITTED, 1, perm_list + i, CAP_SET) < 0) {
279 } else {
280 if (cap_set_proc(caps) == -1) { // it failed, back out
281 Warning("CAP_PERMITTED failed for option %d", i);
282 } else {
283 if (cap_set_flag(caps_good, CAP_PERMITTED, 1, perm_list + i, CAP_SET) < 0) {
284 }
285 }
286 }
287 if (cap_set_proc(caps_orig) < 0) {
288 ink_release_assert(0);
289 }
290 cap_free(caps);
291 }
292 for (int i = 0; i < EFF_CAP_COUNT; i++) {
293 cap_t caps = cap_get_proc();
294 if (cap_set_flag(caps, CAP_EFFECTIVE, 1, eff_list + i, CAP_SET) < 0) {
295 } else {
296 if (cap_set_proc(caps) == -1) { // it failed, back out
297 Warning("CAP_EFFECTIVE failed for option %d", i);
298 } else {
299 if (cap_set_flag(caps_good, CAP_EFFECTIVE, 1, eff_list + i, CAP_SET) < 0) {
300 }
301 }
302 }
303 if (cap_set_proc(caps_orig) < 0) {
304 ink_release_assert(0);
305 }
306 cap_free(caps);
307 }
308
309 if (cap_set_proc(caps_good) == -1) { // it failed, back out
310 ink_release_assert(0);
311 }
312
313 for (int i = 0; i < PERM_CAP_COUNT; i++) {
314 cap_flag_value_t val;
315 if (cap_get_flag(caps_good, perm_list[i], CAP_PERMITTED, &val) < 0) {
316 } else {
317 Warning("CAP_PERMITTED offiset %d is %s", i, val == CAP_SET ? "set" : "unset");
318 }
319 }
320 for (int i = 0; i < EFF_CAP_COUNT; i++) {
321 cap_flag_value_t val;
322 if (cap_get_flag(caps_good, eff_list[i], CAP_EFFECTIVE, &val) < 0) {
323 } else {
324 Warning("CAP_EFFECTIVE offiset %d is %s", i, val == CAP_SET ? "set" : "unset");
325 }
326 }
327
328 cap_free(caps_good);
329 cap_free(caps_orig);
330 #endif
331 Debug("privileges", "[RestrictCapabilities] zret : %d", zret);
332 return zret == 0;
333 }
334
335 bool
EnableCoreFile(bool flag)336 EnableCoreFile(bool flag)
337 {
338 int zret = 0;
339
340 #if defined(PR_SET_DUMPABLE)
341 int state = flag ? 1 : 0;
342 if (0 > (zret = prctl(PR_SET_DUMPABLE, state, 0, 0, 0))) {
343 Warning("Unable to set PR_DUMPABLE : %s", strerror(errno));
344 } else if (state != prctl(PR_GET_DUMPABLE)) {
345 zret = ENOSYS; // best guess
346 Warning("Call to set PR_DUMPABLE was ineffective");
347 }
348 #endif // linux check
349
350 Debug("privileges", "[EnableCoreFile] zret : %d", zret);
351 return zret == 0;
352 }
353
354 void
EnableDeathSignal(int signum)355 EnableDeathSignal(int signum)
356 {
357 (void)signum;
358
359 #if defined(PR_SET_PDEATHSIG)
360 if (prctl(PR_SET_PDEATHSIG, signum, 0, 0, 0) != 0) {
361 Debug("privileges", "prctl(PR_SET_PDEATHSIG) failed: %s", strerror(errno));
362 }
363 #endif
364 }
365
366 int
elevating_open(const char * path,unsigned int flags,unsigned int fperms)367 elevating_open(const char *path, unsigned int flags, unsigned int fperms)
368 {
369 int fd = open(path, flags, fperms);
370 if (fd < 0 && (EPERM == errno || EACCES == errno)) {
371 ElevateAccess access(ElevateAccess::FILE_PRIVILEGE);
372 fd = open(path, flags, fperms);
373 }
374 return fd;
375 }
376
377 int
elevating_open(const char * path,unsigned int flags)378 elevating_open(const char *path, unsigned int flags)
379 {
380 int fd = open(path, flags);
381 if (fd < 0 && (EPERM == errno || EACCES == errno)) {
382 ElevateAccess access(ElevateAccess::FILE_PRIVILEGE);
383 fd = open(path, flags);
384 }
385 return fd;
386 }
387
388 FILE *
elevating_fopen(const char * path,const char * mode)389 elevating_fopen(const char *path, const char *mode)
390 {
391 FILE *f = fopen(path, mode);
392 if (nullptr == f && (EPERM == errno || EACCES == errno)) {
393 ElevateAccess access(ElevateAccess::FILE_PRIVILEGE);
394 f = fopen(path, mode);
395 }
396 return f;
397 }
398
399 int
elevating_chmod(const char * path,int perm)400 elevating_chmod(const char *path, int perm)
401 {
402 int ret = chmod(path, perm);
403 if (ret != 0 && (EPERM == errno || EACCES == errno)) {
404 ElevateAccess access(ElevateAccess::OWNER_PRIVILEGE);
405 return chmod(path, perm);
406 }
407 return ret;
408 }
409
410 int
elevating_stat(const char * path,struct stat * buff)411 elevating_stat(const char *path, struct stat *buff)
412 {
413 int ret = stat(path, buff);
414 if (ret != 0 && (EPERM == errno || EACCES == errno)) {
415 ElevateAccess access(ElevateAccess::FILE_PRIVILEGE);
416 return stat(path, buff);
417 }
418 return ret;
419 }
420
421 #if TS_USE_POSIX_CAP
422 /** Acquire file access privileges to bypass DAC.
423 @a level is a mask of the specific file access capabilities to acquire.
424 */
425 void
acquirePrivilege(unsigned priv_mask)426 ElevateAccess::acquirePrivilege(unsigned priv_mask)
427 {
428 unsigned cap_count = 0;
429 cap_value_t cap_list[3];
430 cap_t new_cap_state;
431
432 Debug("privileges", "[acquirePrivilege] level= %x", level);
433
434 ink_assert(nullptr == cap_state);
435
436 // Some privs aren't checked or used here because they are kept permanently in the
437 // the capability list. See @a eff_list in @c RestrictCapabilities
438 // It simplifies things elsewhere to be able to specify them so that the cases for
439 // POSIX capabilities and user impersonation have the same interface.
440
441 if (priv_mask & ElevateAccess::FILE_PRIVILEGE) {
442 cap_list[cap_count] = CAP_DAC_OVERRIDE;
443 ++cap_count;
444 }
445
446 if (priv_mask & ElevateAccess::TRACE_PRIVILEGE) {
447 cap_list[cap_count] = CAP_SYS_PTRACE;
448 ++cap_count;
449 }
450
451 if (priv_mask & ElevateAccess::OWNER_PRIVILEGE) {
452 cap_list[cap_count] = CAP_FOWNER;
453 ++cap_count;
454 }
455
456 ink_release_assert(cap_count <= sizeof(cap_list));
457
458 if (cap_count > 0) {
459 this->cap_state = cap_get_proc(); // save current capabilities
460 new_cap_state = cap_get_proc(); // and another instance to modify.
461 cap_set_flag(new_cap_state, CAP_EFFECTIVE, cap_count, cap_list, CAP_SET);
462
463 if (cap_set_proc(new_cap_state) != 0) {
464 Fatal("failed to acquire privileged capabilities: %s", strerror(errno));
465 }
466
467 cap_free(new_cap_state);
468 elevated = true;
469 }
470 }
471 /** Restore previous capabilities.
472 */
473 void
releasePrivilege()474 ElevateAccess::releasePrivilege()
475 {
476 Debug("privileges", "[releaseFileAccessCap]");
477
478 if (this->cap_state) {
479 if (cap_set_proc(static_cast<cap_t>(cap_state)) != 0) {
480 Fatal("failed to restore privileged capabilities: %s", strerror(errno));
481 }
482 cap_free(this->cap_state);
483 cap_state = nullptr;
484 }
485 }
486 #endif
487
ElevateAccess(unsigned lvl)488 ElevateAccess::ElevateAccess(unsigned lvl)
489 : saved_uid(geteuid()),
490 level(lvl)
491 #if TS_USE_POSIX_CAP
492 ,
493 cap_state(nullptr)
494 #endif
495 {
496 elevate(level);
497 #if !TS_USE_POSIX_CAP
498 DEBUG_CREDENTIALS("privileges");
499 #endif
500 DEBUG_PRIVILEGES("privileges");
501 }
502
~ElevateAccess()503 ElevateAccess::~ElevateAccess()
504 {
505 if (elevated) {
506 demote();
507 #if !TS_USE_POSIX_CAP
508 DEBUG_CREDENTIALS("privileges");
509 #endif
510 DEBUG_PRIVILEGES("privileges");
511 }
512 }
513
514 void
elevate(unsigned priv_mask)515 ElevateAccess::elevate(unsigned priv_mask)
516 {
517 #if TS_USE_POSIX_CAP
518 acquirePrivilege(priv_mask);
519 #else
520 if (priv_mask) {
521 // Since we are setting a process-wide credential, we have to block any other thread
522 // attempting to elevate until this one demotes.
523 ink_mutex_acquire(&lock);
524 ImpersonateUserID(0, IMPERSONATE_EFFECTIVE);
525 elevated = true;
526 }
527 #endif
528 }
529
530 void
demote()531 ElevateAccess::demote()
532 {
533 if (elevated) {
534 #if TS_USE_POSIX_CAP
535 releasePrivilege();
536 #else
537 ImpersonateUserID(saved_uid, IMPERSONATE_EFFECTIVE);
538 ink_mutex_release(&lock);
539 #endif
540 elevated = false;
541 }
542 }
543