diff options
Diffstat (limited to 'osdep')
| -rw-r--r-- | osdep/LinuxDropPrivileges.cpp | 164 | ||||
| -rw-r--r-- | osdep/LinuxDropPrivileges.hpp | 9 | ||||
| -rw-r--r-- | osdep/ManagedRoute.cpp | 4 | ||||
| -rw-r--r-- | osdep/NeighborDiscovery.cpp | 264 | ||||
| -rw-r--r-- | osdep/NeighborDiscovery.hpp | 76 | ||||
| -rw-r--r-- | osdep/OSUtils.cpp | 8 |
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); |
