LinuxDropPrivileges.cpp 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #include "LinuxDropPrivileges.hpp"
  2. #include <linux/capability.h>
  3. #include <linux/securebits.h>
  4. #include <sys/prctl.h>
  5. #include <sys/stat.h>
  6. #include <sys/syscall.h>
  7. #include <sys/types.h>
  8. #include <sys/wait.h>
  9. #include <pwd.h>
  10. #include <stdlib.h>
  11. #include <unistd.h>
  12. namespace ZeroTier {
  13. #ifndef PR_CAP_AMBIENT
  14. // if we are on old libc, dropPrivileges is nop
  15. void dropPrivileges(std::string homeDir) {}
  16. #else
  17. const char* TARGET_USER_NAME = "zerotier-one";
  18. struct cap_header_struct {
  19. __u32 version;
  20. int pid;
  21. };
  22. struct cap_data_struct {
  23. __u32 effective;
  24. __u32 permitted;
  25. __u32 inheritable;
  26. };
  27. // libc doesn't export capset, it is instead located in libcap
  28. // We ignore libcap and call it manually.
  29. int capset(cap_header_struct* hdrp, cap_data_struct* datap) {
  30. return syscall(SYS_capset, hdrp, datap);
  31. }
  32. void notDropping(std::string homeDir) {
  33. struct stat buf;
  34. if (lstat(homeDir.c_str(), &buf) < 0) {
  35. if (buf.st_uid != 0 || buf.st_gid != 0) {
  36. fprintf(stderr, "ERROR: failed to drop privileges. Refusing to run as root, because %s was already used in nonprivileged mode.\n", homeDir.c_str());
  37. exit(1);
  38. }
  39. }
  40. fprintf(stderr, "WARNING: failed to drop privileges, running as root\n");
  41. }
  42. int setCapabilities(int flags) {
  43. cap_header_struct capheader = {_LINUX_CAPABILITY_VERSION_1, 0};
  44. cap_data_struct capdata;
  45. capdata.inheritable = capdata.permitted = capdata.effective = flags;
  46. return capset(&capheader, &capdata);
  47. }
  48. void createOwnedHomedir(std::string homeDir, struct passwd* targetUser) {
  49. struct stat buf;
  50. if (lstat(homeDir.c_str(), &buf) < 0) {
  51. if (errno == ENOENT) {
  52. mkdir(homeDir.c_str(), 0755);
  53. } else {
  54. perror("cannot access home directory");
  55. exit(1);
  56. }
  57. }
  58. if (buf.st_uid != 0 || buf.st_gid != 0) {
  59. // should be already owned by zerotier-one
  60. if (targetUser->pw_uid != buf.st_uid) {
  61. fprintf(stderr, "ERROR: %s not owned by zerotier-one or root\n", homeDir.c_str());
  62. exit(1);
  63. }
  64. return;
  65. }
  66. // Change homedir owner to zerotier-one user. This is safe, because this directory is writable only by root, so no one could have created malicious hardlink.
  67. long p = (long)fork();
  68. int exitcode = -1;
  69. if (p > 0) {
  70. waitpid(p, &exitcode, 0);
  71. } else if (p == 0) {
  72. std::string ownerString = std::to_string(targetUser->pw_uid) + ":" + std::to_string(targetUser->pw_gid);
  73. execlp("chown", "chown", "-R", ownerString.c_str(), "--", homeDir.c_str(), NULL);
  74. _exit(-1);
  75. }
  76. if (exitcode != 0) {
  77. fprintf(stderr, "failed to change owner of %s to %s\n", homeDir.c_str(), targetUser->pw_name);
  78. exit(1);
  79. }
  80. }
  81. void dropPrivileges(std::string homeDir) {
  82. // dropPrivileges switches to zerotier-one user while retaining CAP_NET_ADMIN
  83. // and CAP_NET_RAW capabilities.
  84. struct passwd* targetUser = getpwnam(TARGET_USER_NAME);
  85. if (targetUser == NULL) {
  86. // zerotier-one user not configured by package
  87. return;
  88. }
  89. createOwnedHomedir(homeDir, targetUser);
  90. if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) {
  91. // Kernel has no support for ambient capabilities.
  92. notDropping(homeDir);
  93. return;
  94. }
  95. if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS | SECBIT_NOROOT) < 0) {
  96. notDropping(homeDir);
  97. return;
  98. }
  99. if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) {
  100. fprintf(stderr, "ERROR: failed to set capabilities (not running as real root?)\n");
  101. exit(1);
  102. }
  103. int oldDumpable = prctl(PR_GET_DUMPABLE);
  104. if (prctl(PR_SET_DUMPABLE, 0) < 0) {
  105. // Disable ptracing. Otherwise there is a small window when previous
  106. // compromised ZeroTier process could ptrace us, when we still have CAP_SETUID.
  107. // (this is mitigated anyway on most distros by ptrace_scope=1)
  108. perror("prctl(PR_SET_DUMPABLE)");
  109. exit(1);
  110. }
  111. if (setgid(targetUser->pw_gid) < 0) {
  112. perror("setgid");
  113. exit(1);
  114. }
  115. if (setuid(targetUser->pw_uid) < 0) {
  116. perror("setuid");
  117. exit(1);
  118. }
  119. if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW)) < 0) {
  120. perror("could not drop capabilities after setuid");
  121. exit(1);
  122. }
  123. if (prctl(PR_SET_DUMPABLE, oldDumpable) < 0) {
  124. perror("could not restore dumpable flag");
  125. exit(1);
  126. }
  127. if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) < 0) {
  128. perror("could not raise ambient CAP_NET_ADMIN");
  129. exit(1);
  130. }
  131. if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0) < 0) {
  132. perror("could not raise ambient CAP_NET_RAW");
  133. exit(1);
  134. }
  135. }
  136. #endif
  137. }