xref: /trafficserver/src/tscore/ink_cap.cc (revision 4cfd5a73)
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