mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2024-12-18 20:47:53 +00:00
Run as nonroot user on Linux (with CAP_NET_ADMIN and CAP_NET_RAW added).
- ZT will only drop root privileges if zerotier-one user exists. It is created by Debian postinst script - in other cases the user has to be created by administrator. - Linux >=4.3 with ambient capabilities is required, otherwise ZT will silently - "-U" option now also disables privileges dropping
This commit is contained in:
parent
88e3fe699c
commit
344a25c133
9
debian/postinst
vendored
Normal file
9
debian/postinst
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
adduser --system --group --home /var/lib/zerotier-one --no-create-home zerotier-one
|
||||
;;
|
||||
esac
|
||||
|
||||
#DEBHELPER#
|
@ -111,8 +111,8 @@ endif
|
||||
|
||||
all: one manpages
|
||||
|
||||
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)
|
||||
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)
|
||||
$(STRIP) zerotier-one
|
||||
ln -sf zerotier-one zerotier-idtool
|
||||
ln -sf zerotier-one zerotier-cli
|
||||
|
14
one.cpp
14
one.cpp
@ -44,6 +44,10 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include "osdep/LinuxDropPrivileges.hpp"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
@ -900,7 +904,7 @@ static void printHelp(const char *cn,FILE *out)
|
||||
fprintf(out,"Available switches:" ZT_EOL_S);
|
||||
fprintf(out," -h - Display this help" ZT_EOL_S);
|
||||
fprintf(out," -v - Show version" ZT_EOL_S);
|
||||
fprintf(out," -U - Run as unprivileged user (skip privilege check)" ZT_EOL_S);
|
||||
fprintf(out," -U - Skip privilege check and do not attempt to drop privileges" ZT_EOL_S);
|
||||
fprintf(out," -p<port> - Port for UDP and TCP/HTTP (default: 9993, 0 for random)" ZT_EOL_S);
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
@ -1141,6 +1145,14 @@ int main(int argc,char **argv)
|
||||
#endif // __WINDOWS__
|
||||
|
||||
#ifdef __UNIX_LIKE__
|
||||
|
||||
#ifndef ZT_ONE_RUN_AS_ROOT
|
||||
#ifdef __linux__
|
||||
if (!skipRootCheck)
|
||||
dropPrivileges(homeDir);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
std::string pidPath(homeDir + ZT_PATH_SEPARATOR_S + ZT_PID_PATH);
|
||||
{
|
||||
// Write .pid file to home folder
|
||||
|
164
osdep/LinuxDropPrivileges.cpp
Normal file
164
osdep/LinuxDropPrivileges.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
createOwnedHomedir(homeDir, targetUser);
|
||||
|
||||
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
|
||||
}
|
9
osdep/LinuxDropPrivileges.hpp
Normal file
9
osdep/LinuxDropPrivileges.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef ZT_LINUXDROPPRIVILEGES_HPP
|
||||
#define ZT_LINUXDROPPRIVILEGES_HPP
|
||||
#include <string>
|
||||
|
||||
namespace ZeroTier {
|
||||
void dropPrivileges(std::string homeDir);
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user