1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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;
}
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
}
|