summaryrefslogtreecommitdiff
path: root/osdep
diff options
context:
space:
mode:
Diffstat (limited to 'osdep')
-rw-r--r--osdep/LinuxDropPrivileges.cpp164
-rw-r--r--osdep/LinuxDropPrivileges.hpp9
-rw-r--r--osdep/ManagedRoute.cpp4
-rw-r--r--osdep/NeighborDiscovery.cpp264
-rw-r--r--osdep/NeighborDiscovery.hpp76
-rw-r--r--osdep/OSUtils.cpp8
6 files changed, 520 insertions, 5 deletions
diff --git a/osdep/LinuxDropPrivileges.cpp b/osdep/LinuxDropPrivileges.cpp
new file mode 100644
index 00000000..dab85bd8
--- /dev/null
+++ b/osdep/LinuxDropPrivileges.cpp
@@ -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
+}
diff --git a/osdep/LinuxDropPrivileges.hpp b/osdep/LinuxDropPrivileges.hpp
new file mode 100644
index 00000000..111f682e
--- /dev/null
+++ b/osdep/LinuxDropPrivileges.hpp
@@ -0,0 +1,9 @@
+#ifndef ZT_LINUXDROPPRIVILEGES_HPP
+#define ZT_LINUXDROPPRIVILEGES_HPP
+#include <string>
+
+namespace ZeroTier {
+ void dropPrivileges(std::string homeDir);
+}
+
+#endif
diff --git a/osdep/ManagedRoute.cpp b/osdep/ManagedRoute.cpp
index 127f1b7d..1fc6c78e 100644
--- a/osdep/ManagedRoute.cpp
+++ b/osdep/ManagedRoute.cpp
@@ -524,11 +524,11 @@ void ManagedRoute::remove()
#endif // __BSD__ ------------------------------------------------------------
#ifdef __LINUX__ // ----------------------------------------------------------
- _routeCmd("del",*r,_via,(_via) ? (const char *)0 : _device);
+ _routeCmd("del",r->first,_via,(_via) ? (const char *)0 : _device);
#endif // __LINUX__ ----------------------------------------------------------
#ifdef __WINDOWS__ // --------------------------------------------------------
- _winRoute(true,interfaceLuid,interfaceIndex,*r,_via);
+ _winRoute(true,interfaceLuid,interfaceIndex,r->first,_via);
#endif // __WINDOWS__ --------------------------------------------------------
}
diff --git a/osdep/NeighborDiscovery.cpp b/osdep/NeighborDiscovery.cpp
new file mode 100644
index 00000000..68b67794
--- /dev/null
+++ b/osdep/NeighborDiscovery.cpp
@@ -0,0 +1,264 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "NeighborDiscovery.hpp"
+#include "OSUtils.hpp"
+
+#include "../include/ZeroTierOne.h"
+
+#include <assert.h>
+
+namespace ZeroTier {
+
+uint16_t calc_checksum (uint16_t *addr, int len)
+{
+ int count = len;
+ register uint32_t sum = 0;
+ uint16_t answer = 0;
+
+ // Sum up 2-byte values until none or only one byte left.
+ while (count > 1) {
+ sum += *(addr++);
+ count -= 2;
+ }
+
+ // Add left-over byte, if any.
+ if (count > 0) {
+ sum += *(uint8_t *) addr;
+ }
+
+ // Fold 32-bit sum into 16 bits; we lose information by doing this,
+ // increasing the chances of a collision.
+ // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits)
+ while (sum >> 16) {
+ sum = (sum & 0xffff) + (sum >> 16);
+ }
+
+ // Checksum is one's compliment of sum.
+ answer = ~sum;
+
+ return (answer);
+}
+
+struct _pseudo_header {
+ uint8_t sourceAddr[16];
+ uint8_t targetAddr[16];
+ uint32_t length;
+ uint8_t zeros[3];
+ uint8_t next; // 58
+};
+
+struct _option {
+ _option(int optionType)
+ : type(optionType)
+ , length(8)
+ {
+ memset(mac, 0, sizeof(mac));
+ }
+
+ uint8_t type;
+ uint8_t length;
+ uint8_t mac[6];
+};
+
+struct _neighbor_solicitation {
+ _neighbor_solicitation()
+ : type(135)
+ , code(0)
+ , checksum(0)
+ , option(1)
+ {
+ memset(&reserved, 0, sizeof(reserved));
+ memset(target, 0, sizeof(target));
+ }
+
+ void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) {
+ _pseudo_header ph;
+ memset(&ph, 0, sizeof(_pseudo_header));
+ const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp;
+ const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp;
+
+ memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr));
+ memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr));
+ ph.next = 58;
+ ph.length = htonl(sizeof(_neighbor_solicitation));
+
+ size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_solicitation);
+ uint8_t *tmp = (uint8_t*)malloc(len);
+ memcpy(tmp, &ph, sizeof(_pseudo_header));
+ memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_solicitation));
+
+ checksum = calc_checksum((uint16_t*)tmp, len);
+
+ free(tmp);
+ tmp = NULL;
+ }
+
+ uint8_t type; // 135
+ uint8_t code; // 0
+ uint16_t checksum;
+ uint32_t reserved;
+ uint8_t target[16];
+ _option option;
+};
+
+struct _neighbor_advertisement {
+ _neighbor_advertisement()
+ : type(136)
+ , code(0)
+ , checksum(0)
+ , rso(0x40)
+ , option(2)
+ {
+ memset(padding, 0, sizeof(padding));
+ memset(target, 0, sizeof(target));
+ }
+
+ void calculateChecksum(const sockaddr_storage &sourceIp, const sockaddr_storage &destIp) {
+ _pseudo_header ph;
+ memset(&ph, 0, sizeof(_pseudo_header));
+ const sockaddr_in6 *src = (const sockaddr_in6*)&sourceIp;
+ const sockaddr_in6 *dest = (const sockaddr_in6*)&destIp;
+
+ memcpy(ph.sourceAddr, &src->sin6_addr, sizeof(struct in6_addr));
+ memcpy(ph.targetAddr, &dest->sin6_addr, sizeof(struct in6_addr));
+ ph.next = 58;
+ ph.length = htonl(sizeof(_neighbor_advertisement));
+
+ size_t len = sizeof(_pseudo_header) + sizeof(_neighbor_advertisement);
+ uint8_t *tmp = (uint8_t*)malloc(len);
+ memcpy(tmp, &ph, sizeof(_pseudo_header));
+ memcpy(tmp+sizeof(_pseudo_header), this, sizeof(_neighbor_advertisement));
+
+ checksum = calc_checksum((uint16_t*)tmp, len);
+
+ free(tmp);
+ tmp = NULL;
+ }
+
+ uint8_t type; // 136
+ uint8_t code; // 0
+ uint16_t checksum;
+ uint8_t rso;
+ uint8_t padding[3];
+ uint8_t target[16];
+ _option option;
+};
+
+NeighborDiscovery::NeighborDiscovery()
+ : _cache(256)
+ , _lastCleaned(OSUtils::now())
+{}
+
+void NeighborDiscovery::addLocal(const sockaddr_storage &address, const MAC &mac)
+{
+ _NDEntry &e = _cache[InetAddress(address)];
+ e.lastQuerySent = 0;
+ e.lastResponseReceived = 0;
+ e.mac = mac;
+ e.local = true;
+}
+
+void NeighborDiscovery::remove(const sockaddr_storage &address)
+{
+ _cache.erase(InetAddress(address));
+}
+
+sockaddr_storage NeighborDiscovery::processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest)
+{
+ assert(sizeof(_neighbor_solicitation) == 28);
+ assert(sizeof(_neighbor_advertisement) == 32);
+
+ const uint64_t now = OSUtils::now();
+ sockaddr_storage ip = ZT_SOCKADDR_NULL;
+
+ if (len >= sizeof(_neighbor_solicitation) && nd[0] == 0x87) {
+ // respond to Neighbor Solicitation request for local address
+ _neighbor_solicitation solicitation;
+ memcpy(&solicitation, nd, len);
+ InetAddress targetAddress(solicitation.target, 16, 0);
+ _NDEntry *targetEntry = _cache.get(targetAddress);
+ if (targetEntry && targetEntry->local) {
+ _neighbor_advertisement adv;
+ targetEntry->mac.copyTo(adv.option.mac, 6);
+ memcpy(adv.target, solicitation.target, 16);
+ adv.calculateChecksum(localIp, targetAddress);
+ memcpy(response, &adv, sizeof(_neighbor_advertisement));
+ responseLen = sizeof(_neighbor_advertisement);
+ responseDest.setTo(solicitation.option.mac, 6);
+ }
+ } else if (len >= sizeof(_neighbor_advertisement) && nd[0] == 0x88) {
+ _neighbor_advertisement adv;
+ memcpy(&adv, nd, len);
+ InetAddress responseAddress(adv.target, 16, 0);
+ _NDEntry *queryEntry = _cache.get(responseAddress);
+ if(queryEntry && !queryEntry->local && (now - queryEntry->lastQuerySent <= ZT_ND_QUERY_MAX_TTL)) {
+ queryEntry->lastResponseReceived = now;
+ queryEntry->mac.setTo(adv.option.mac, 6);
+ ip = responseAddress;
+ }
+ }
+
+ if ((now - _lastCleaned) >= ZT_ND_EXPIRE) {
+ _lastCleaned = now;
+ Hashtable<InetAddress, _NDEntry>::Iterator i(_cache);
+ InetAddress *k = NULL;
+ _NDEntry *v = NULL;
+ while (i.next(k, v)) {
+ if(!v->local && (now - v->lastResponseReceived) >= ZT_ND_EXPIRE) {
+ _cache.erase(*k);
+ }
+ }
+ }
+
+ return ip;
+}
+
+MAC NeighborDiscovery::query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest)
+{
+ const uint64_t now = OSUtils::now();
+
+ InetAddress localAddress(localIp);
+ localAddress.setPort(0);
+ InetAddress targetAddress(targetIp);
+ targetAddress.setPort(0);
+
+ _NDEntry &e = _cache[targetAddress];
+
+ if ( (e.mac && ((now - e.lastResponseReceived) >= (ZT_ND_EXPIRE / 3))) ||
+ (!e.mac && ((now - e.lastQuerySent) >= ZT_ND_QUERY_INTERVAL))) {
+ e.lastQuerySent = now;
+
+ _neighbor_solicitation ns;
+ memcpy(ns.target, targetAddress.rawIpData(), 16);
+ localMac.copyTo(ns.option.mac, 6);
+ ns.calculateChecksum(localIp, targetIp);
+ if (e.mac) {
+ queryDest = e.mac;
+ } else {
+ queryDest = (uint64_t)0xffffffffffffULL;
+ }
+ } else {
+ queryLen = 0;
+ queryDest.zero();
+ }
+
+ return e.mac;
+}
+
+}
diff --git a/osdep/NeighborDiscovery.hpp b/osdep/NeighborDiscovery.hpp
new file mode 100644
index 00000000..47831bda
--- /dev/null
+++ b/osdep/NeighborDiscovery.hpp
@@ -0,0 +1,76 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2016 ZeroTier, Inc. https://www.zerotier.com/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ZT_NEIGHBORDISCOVERY_HPP
+#define ZT_NEIGHBORDISCOVERY_HPP
+
+#include "../node/Hashtable.hpp"
+#include "../node/MAC.hpp"
+#include "../node/InetAddress.hpp"
+
+
+#define ZT_ND_QUERY_INTERVAL 2000
+
+#define ZT_ND_QUERY_MAX_TTL 5000
+
+#define ZT_ND_EXPIRE 600000
+
+
+namespace ZeroTier {
+
+class NeighborDiscovery
+{
+public:
+ NeighborDiscovery();
+
+ /**
+ * Set a local IP entry that we should respond to Neighbor Requests withPrefix64k
+ *
+ * @param mac Our local MAC address
+ * @param ip Our IPv6 address
+ */
+ void addLocal(const sockaddr_storage &address, const MAC &mac);
+
+ /**
+ * Delete a local IP entry or cached Neighbor entry
+ *
+ * @param address IPv6 address to remove
+ */
+ void remove(const sockaddr_storage &address);
+
+ sockaddr_storage processIncomingND(const uint8_t *nd, unsigned int len, const sockaddr_storage &localIp, uint8_t *response, unsigned int &responseLen, MAC &responseDest);
+
+ MAC query(const MAC &localMac, const sockaddr_storage &localIp, const sockaddr_storage &targetIp, uint8_t *query, unsigned int &queryLen, MAC &queryDest);
+
+private:
+ struct _NDEntry
+ {
+ _NDEntry() : lastQuerySent(0), lastResponseReceived(0), mac(), local(false) {}
+ uint64_t lastQuerySent;
+ uint64_t lastResponseReceived;
+ MAC mac;
+ bool local;
+ };
+
+ Hashtable<InetAddress, _NDEntry> _cache;
+ uint64_t _lastCleaned;
+};
+
+} // namespace ZeroTier
+
+#endif
diff --git a/osdep/OSUtils.cpp b/osdep/OSUtils.cpp
index 086bb269..c652e272 100644
--- a/osdep/OSUtils.cpp
+++ b/osdep/OSUtils.cpp
@@ -170,9 +170,11 @@ bool OSUtils::rmDashRf(const char *path)
return true;
dptr = (struct dirent *)0;
for(;;) {
- if (readdir_r(d,&de,&dptr))
+ if (readdir_r(d,&de,&dptr) != 0)
+ break;
+ if (!dptr)
break;
- if ((dptr)&&(strcmp(dptr->d_name,".") != 0)&&(strcmp(dptr->d_name,"..") != 0)) {
+ 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);
@@ -180,7 +182,7 @@ bool OSUtils::rmDashRf(const char *path)
if (!rmDashRf(p.c_str()))
return false;
}
- } else break;
+ }
}
closedir(d);
return (rmdir(path) == 0);