Browse Source

Integrate and test linux privilege drop code (from contributor PR). It works now if a "zerotier-one" user is present on a Linux system. Does everything automagically.

Adam Ierymenko 8 years ago
parent
commit
3361b4030b
4 changed files with 148 additions and 183 deletions
  1. 2 2
      make-linux.mk
  2. 146 8
      one.cpp
  3. 0 164
      osdep/LinuxDropPrivileges.cpp
  4. 0 9
      osdep/LinuxDropPrivileges.hpp

+ 2 - 2
make-linux.mk

@@ -107,8 +107,8 @@ endif
 
 all:	one
 
-one:	$(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o osdep/LinuxDropPrivileges.o
-	$(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o osdep/LinuxDropPrivileges.o $(LDLIBS)
+one:	$(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o
+	$(CXX) $(CXXFLAGS) $(LDFLAGS) -o zerotier-one $(OBJS) service/OneService.o one.o osdep/LinuxEthernetTap.o $(LDLIBS)
 	$(STRIP) zerotier-one
 	ln -sf zerotier-one zerotier-idtool
 	ln -sf zerotier-one zerotier-cli

+ 146 - 8
one.cpp

@@ -43,10 +43,15 @@
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/uio.h>
+#include <dirent.h>
 #include <signal.h>
-
-#ifdef __linux__
-#include "osdep/LinuxDropPrivileges.hpp"
+#ifdef __LINUX__
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <linux/capability.h>
+#include <linux/securebits.h>
 #endif
 #endif
 
@@ -875,6 +880,142 @@ static void _sighandlerQuit(int sig)
 }
 #endif
 
+// Drop privileges on Linux, if supported by libc etc. and "zerotier-one" user exists on system
+#ifdef __LINUX__
+#ifdef PR_CAP_AMBIENT
+#define ZT_LINUX_USER "zerotier-one"
+#define ZT_HAVE_DROP_PRIVILEGES 1
+namespace {
+
+// libc doesn't export capset, it is instead located in libcap
+// We ignore libcap and call it manually.
+struct cap_header_struct {
+	__u32 version;
+	int pid;
+};
+struct cap_data_struct {
+	__u32 effective;
+	__u32 permitted;
+	__u32 inheritable;
+};
+static inline int _zt_capset(cap_header_struct* hdrp, cap_data_struct* datap) { return syscall(SYS_capset, hdrp, datap); }
+
+static void _notDropping(const char *procName,const std::string &homeDir)
+{
+	struct stat buf;
+	if (lstat(homeDir.c_str(),&buf) < 0) {
+		if (buf.st_uid != 0 || buf.st_gid != 0) {
+			fprintf(stderr, "%s: FATAL: failed to drop privileges and can't run as root since privileges were previously dropped (home directory not owned by root)" ZT_EOL_S,procName);
+			exit(1);
+		}
+	}
+	fprintf(stderr, "%s: WARNING: failed to drop privileges (kernel may not support required prctl features), running as root" ZT_EOL_S,procName);
+}
+
+static 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 _zt_capset(&capheader, &capdata);
+}
+
+static void _recursiveChown(const char *path,uid_t uid,gid_t gid)
+{
+	struct dirent de;
+	struct dirent *dptr;
+	lchown(path,uid,gid);
+	DIR *d = opendir(path);
+	if (!d)
+		return;
+	dptr = (struct dirent *)0;
+	for(;;) {
+		if (readdir_r(d,&de,&dptr) != 0)
+			break;
+		if (!dptr)
+			break;
+		if ((strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)&&(strlen(dptr->d_name) > 0)) {
+			std::string p(path);
+			p.push_back(ZT_PATH_SEPARATOR);
+			p.append(dptr->d_name);
+			_recursiveChown(p.c_str(),uid,gid); // will just fail and return on regular files
+		}
+	}
+	closedir(d);
+}
+
+static void dropPrivileges(const char *procName,const std::string &homeDir)
+{
+	if (getuid() != 0)
+		return;
+
+	// dropPrivileges switches to zerotier-one user while retaining CAP_NET_ADMIN
+	// and CAP_NET_RAW capabilities.
+	struct passwd *targetUser = getpwnam(ZT_LINUX_USER);
+	if (!targetUser)
+		return;
+
+	if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_NET_RAW, 0, 0) < 0) {
+		// Kernel has no support for ambient capabilities.
+		_notDropping(procName,homeDir);
+		return;
+	}
+	if (prctl(PR_SET_SECUREBITS, SECBIT_KEEP_CAPS | SECBIT_NOROOT) < 0) {
+		_notDropping(procName,homeDir);
+		return;
+	}
+
+	// Change ownership of our home directory if everything looks good (does nothing if already chown'd)
+	_recursiveChown(homeDir.c_str(),targetUser->pw_uid,targetUser->pw_gid);
+
+	if (_setCapabilities((1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_SETUID) | (1 << CAP_SETGID)) < 0) {
+		_notDropping(procName,homeDir);
+		return;
+	}
+
+	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)
+		fprintf(stderr,"%s: FATAL: prctl(PR_SET_DUMPABLE) failed while attempting to relinquish root permissions" ZT_EOL_S,procName);
+		exit(1);
+	}
+
+	// Relinquish root
+	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) {
+		fprintf(stderr,"%s: FATAL: unable to drop capabilities after relinquishing root" ZT_EOL_S,procName);
+		exit(1);
+	}
+
+	if (prctl(PR_SET_DUMPABLE, oldDumpable) < 0) {
+		fprintf(stderr,"%s: FATAL: prctl(PR_SET_DUMPABLE) failed while attempting to relinquish root permissions" ZT_EOL_S,procName);
+		exit(1);
+	}
+
+	if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_ADMIN, 0, 0) < 0) {
+		fprintf(stderr,"%s: FATAL: prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_RAISE,CAP_NET_ADMIN) failed while attempting to relinquish root permissions" ZT_EOL_S,procName);
+		exit(1);
+	}
+	if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_RAW, 0, 0) < 0) {
+		fprintf(stderr,"%s: FATAL: prctl(PR_CAP_AMBIENT,PR_CAP_AMBIENT_RAISE,CAP_NET_RAW) failed while attempting to relinquish root permissions" ZT_EOL_S,procName);
+		exit(1);
+	}
+}
+
+} // anonymous namespace
+#endif // PR_CAP_AMBIENT
+#endif // __LINUX__
+
 /****************************************************************************/
 /* Windows helper functions and signal handlers                             */
 /****************************************************************************/
@@ -1283,11 +1424,8 @@ int main(int argc,char **argv)
 
 #ifdef __UNIX_LIKE__
 
-#ifndef ZT_ONE_RUN_AS_ROOT
-#ifdef __linux__
-	if (!skipRootCheck)
-		dropPrivileges(homeDir);
-#endif
+#ifdef ZT_HAVE_DROP_PRIVILEGES
+	dropPrivileges(argv[0],homeDir);
 #endif
 
 	std::string pidPath(homeDir + ZT_PATH_SEPARATOR_S + ZT_PID_PATH);

+ 0 - 164
osdep/LinuxDropPrivileges.cpp

@@ -1,164 +0,0 @@
-#include "LinuxDropPrivileges.hpp"
-#include <linux/capability.h>
-#include <linux/securebits.h>
-#include <sys/prctl.h>
-#include <sys/stat.h>
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <pwd.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-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
-}

+ 0 - 9
osdep/LinuxDropPrivileges.hpp

@@ -1,9 +0,0 @@
-#ifndef ZT_LINUXDROPPRIVILEGES_HPP
-#define ZT_LINUXDROPPRIVILEGES_HPP
-#include <string>
-
-namespace ZeroTier {
-    void dropPrivileges(std::string homeDir);
-}
-
-#endif