#include "LinuxDropPrivileges.hpp" #include #include #include #include #include #include #include #include #include #include namespace ZeroTier { #ifndef PR_CAP_AMBIENT // if we are on old libc, dropPrivileges is nop void dropPrivileges(std::string homeDir) {} #else const char* TARGET_USER_NAME = "zerotier-one"; struct cap_header_struct { __u32 version; int pid; }; struct cap_data_struct { __u32 effective; __u32 permitted; __u32 inheritable; }; // libc doesn't export capset, it is instead located in libcap // We ignore libcap and call it manually. int capset(cap_header_struct* hdrp, cap_data_struct* datap) { return syscall(SYS_capset, hdrp, datap); } void notDropping(std::string homeDir) { struct stat buf; if (lstat(homeDir.c_str(), &buf) < 0) { if (buf.st_uid != 0 || buf.st_gid != 0) { fprintf(stderr, "ERROR: failed to drop privileges. Refusing to run as root, because %s was already used in nonprivileged mode.\n", homeDir.c_str()); exit(1); } } fprintf(stderr, "WARNING: failed to drop privileges, running as root\n"); } int setCapabilities(int flags) { cap_header_struct capheader = {_LINUX_CAPABILITY_VERSION_1, 0}; cap_data_struct capdata; capdata.inheritable = capdata.permitted = capdata.effective = flags; return capset(&capheader, &capdata); } void createOwnedHomedir(std::string homeDir, struct passwd* targetUser) { struct stat buf; if (lstat(homeDir.c_str(), &buf) < 0) { if (errno == ENOENT) { mkdir(homeDir.c_str(), 0755); } else { perror("cannot access home directory"); exit(1); } } if (buf.st_uid != 0 || buf.st_gid != 0) { // should be already owned by zerotier-one if (targetUser->pw_uid != buf.st_uid) { fprintf(stderr, "ERROR: %s not owned by zerotier-one or root\n", homeDir.c_str()); exit(1); } return; } // 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. long p = (long)fork(); int exitcode = -1; if (p > 0) { waitpid(p, &exitcode, 0); } else if (p == 0) { std::string ownerString = std::to_string(targetUser->pw_uid) + ":" + std::to_string(targetUser->pw_gid); execlp("chown", "chown", "-R", ownerString.c_str(), "--", homeDir.c_str(), NULL); _exit(-1); } if (exitcode != 0) { fprintf(stderr, "failed to change owner of %s to %s\n", homeDir.c_str(), targetUser->pw_name); exit(1); } } void dropPrivileges(std::string homeDir) { // dropPrivileges switches to zerotier-one user while retaining CAP_NET_ADMIN // and CAP_NET_RAW capabilities. struct passwd* targetUser = getpwnam(TARGET_USER_NAME); if (targetUser == NULL) { // zerotier-one user not configured by package return; } createOwnedHomedir(homeDir, targetUser); if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) { // Kernel has no support for ambient capabilities. notDropping(homeDir); return; } if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS | SECBIT_NOROOT) < 0) { notDropping(homeDir); return; } if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) { fprintf(stderr, "ERROR: failed to set capabilities (not running as real root?)\n"); exit(1); } int oldDumpable = prctl(PR_GET_DUMPABLE); if (prctl(PR_SET_DUMPABLE, 0) < 0) { // Disable ptracing. Otherwise there is a small window when previous // compromised ZeroTier process could ptrace us, when we still have CAP_SETUID. // (this is mitigated anyway on most distros by ptrace_scope=1) perror("prctl(PR_SET_DUMPABLE)"); exit(1); } if (setgid(targetUser->pw_gid) < 0) { perror("setgid"); exit(1); } if (setuid(targetUser->pw_uid) < 0) { perror("setuid"); exit(1); } if (setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW)) < 0) { perror("could not drop capabilities after setuid"); exit(1); } if (prctl(PR_SET_DUMPABLE, oldDumpable) < 0) { perror("could not restore dumpable flag"); exit(1); } if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) < 0) { perror("could not raise ambient CAP_NET_ADMIN"); exit(1); } if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0) < 0) { perror("could not raise ambient CAP_NET_RAW"); exit(1); } } #endif }