diff --git a/server/Android.mk b/server/Android.mk index 55aa87cde6838c9946e7a6a272a703aa1e02fceb..e7b01a367ca9c8b396f89180059f46112eaa77a2 100644 --- a/server/Android.mk +++ b/server/Android.mk @@ -21,7 +21,6 @@ LOCAL_C_INCLUDES := \ bionic/libc/dns/include \ external/libcxx/include \ external/mdnsresponder/mDNSShared \ - external/openssl/include \ system/netd/include \ LOCAL_CLANG := true @@ -37,8 +36,12 @@ LOCAL_SHARED_LIBRARIES := \ liblogwrap \ libmdnssd \ libnetutils \ + libnl \ libsysutils \ +LOCAL_STATIC_LIBRARIES := \ + libpcap \ + LOCAL_SRC_FILES := \ BandwidthController.cpp \ ClatdController.cpp \ @@ -62,6 +65,7 @@ LOCAL_SRC_FILES := \ ResolverController.cpp \ RouteController.cpp \ SoftapController.cpp \ + StrictController.cpp \ TetherController.cpp \ UidRanges.cpp \ VirtualNetwork.cpp \ diff --git a/server/CommandListener.cpp b/server/CommandListener.cpp index e2d2308d52e758cf8bdda22fb51e5191bd1c5732..d9acce9de231030c5612bb78bcbd0c0b80ed8274 100644 --- a/server/CommandListener.cpp +++ b/server/CommandListener.cpp @@ -92,6 +92,7 @@ InterfaceController *CommandListener::sInterfaceCtrl = NULL; ResolverController *CommandListener::sResolverCtrl = NULL; FirewallController *CommandListener::sFirewallCtrl = NULL; ClatdController *CommandListener::sClatdCtrl = NULL; +StrictController *CommandListener::sStrictCtrl = NULL; /** * List of module chains to be created, along with explicit ordering. ORDERING @@ -116,6 +117,7 @@ static const char* FILTER_FORWARD[] = { static const char* FILTER_OUTPUT[] = { OEM_IPTABLES_FILTER_OUTPUT, FirewallController::LOCAL_OUTPUT, + StrictController::LOCAL_OUTPUT, BandwidthController::LOCAL_OUTPUT, NULL, }; @@ -182,6 +184,7 @@ CommandListener::CommandListener() : registerCmd(new FirewallCmd()); registerCmd(new ClatdCmd()); registerCmd(new NetworkCommand()); + registerCmd(new StrictCmd()); if (!sNetCtrl) sNetCtrl = new NetworkController(); @@ -205,6 +208,8 @@ CommandListener::CommandListener() : sInterfaceCtrl = new InterfaceController(); if (!sClatdCtrl) sClatdCtrl = new ClatdController(sNetCtrl); + if (!sStrictCtrl) + sStrictCtrl = new StrictController(); /* * This is the only time we touch top-level chains in iptables; controllers @@ -1353,6 +1358,76 @@ int CommandListener::ClatdCmd::runCommand(SocketClient *cli, int argc, return 0; } +CommandListener::StrictCmd::StrictCmd() : + NetdCommand("strict") { +} + +int CommandListener::StrictCmd::sendGenericOkFail(SocketClient *cli, int cond) { + if (!cond) { + cli->sendMsg(ResponseCode::CommandOkay, "Strict command succeeded", false); + } else { + cli->sendMsg(ResponseCode::OperationFailed, "Strict command failed", false); + } + return 0; +} + +StrictPenalty CommandListener::StrictCmd::parsePenalty(const char* arg) { + if (!strcmp(arg, "reject")) { + return REJECT; + } else if (!strcmp(arg, "log")) { + return LOG; + } else if (!strcmp(arg, "accept")) { + return ACCEPT; + } else { + return INVALID; + } +} + +int CommandListener::StrictCmd::runCommand(SocketClient *cli, int argc, + char **argv) { + if (argc < 2) { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Missing command", false); + return 0; + } + + if (!strcmp(argv[1], "enable")) { + int res = sStrictCtrl->enableStrict(); + return sendGenericOkFail(cli, res); + } + if (!strcmp(argv[1], "disable")) { + int res = sStrictCtrl->disableStrict(); + return sendGenericOkFail(cli, res); + } + + if (!strcmp(argv[1], "set_uid_cleartext_policy")) { + if (argc != 4) { + cli->sendMsg(ResponseCode::CommandSyntaxError, + "Usage: strict set_uid_cleartext_policy <uid> <accept|log|reject>", + false); + return 0; + } + + errno = 0; + unsigned long int uid = strtoul(argv[2], NULL, 0); + if (errno || uid > UID_MAX) { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid UID", false); + return 0; + } + + StrictPenalty penalty = parsePenalty(argv[3]); + if (penalty == INVALID) { + cli->sendMsg(ResponseCode::CommandSyntaxError, "Invalid penalty argument", false); + return 0; + } + + int res = sStrictCtrl->setUidCleartextPenalty((uid_t) uid, penalty); + return sendGenericOkFail(cli, res); + } + + cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown command", false); + return 0; +} + CommandListener::NetworkCommand::NetworkCommand() : NetdCommand("network") { } diff --git a/server/CommandListener.h b/server/CommandListener.h index f60564756570718deafa0bc08f251f25286b1304..b71bc45e2760fae9a1b411ae60aeeab3886b89cf 100644 --- a/server/CommandListener.h +++ b/server/CommandListener.h @@ -31,6 +31,7 @@ #include "ResolverController.h" #include "FirewallController.h" #include "ClatdController.h" +#include "StrictController.h" class CommandListener : public FrameworkListener { static TetherController *sTetherCtrl; @@ -43,6 +44,7 @@ class CommandListener : public FrameworkListener { static ResolverController *sResolverCtrl; static FirewallController *sFirewallCtrl; static ClatdController *sClatdCtrl; + static StrictController *sStrictCtrl; public: static NetworkController *sNetCtrl; @@ -143,6 +145,16 @@ private: int runCommand(SocketClient *c, int argc, char ** argv); }; + class StrictCmd : public NetdCommand { + public: + StrictCmd(); + virtual ~StrictCmd() {} + int runCommand(SocketClient *c, int argc, char ** argv); + protected: + int sendGenericOkFail(SocketClient *cli, int cond); + static StrictPenalty parsePenalty(const char* arg); + }; + class NetworkCommand : public NetdCommand { public: NetworkCommand(); diff --git a/server/NetlinkHandler.cpp b/server/NetlinkHandler.cpp index 6c81c18b69008eccf9a865a64a5fd7d0fc5105d0..0a5a3f02a1956f271963a0d249bbc1d506a0aad5 100644 --- a/server/NetlinkHandler.cpp +++ b/server/NetlinkHandler.cpp @@ -109,6 +109,11 @@ void NetlinkHandler::onEvent(NetlinkEvent *evt) { const char *iface = evt->findParam("INTERFACE"); notifyQuotaLimitReached(alertName, iface); + } else if (!strcmp(subsys, "strict")) { + const char *uid = evt->findParam("UID"); + const char *hex = evt->findParam("HEX"); + notifyStrictCleartext(uid, hex); + } else if (!strcmp(subsys, "xt_idletimer")) { const char *label = evt->findParam("INTERFACE"); const char *state = evt->findParam("STATE"); @@ -196,3 +201,7 @@ void NetlinkHandler::notifyRouteChange(int action, const char *route, *iface ? " dev " : "", iface); } + +void NetlinkHandler::notifyStrictCleartext(const char* uid, const char* hex) { + notify(ResponseCode::StrictCleartext, "%s %s", uid, hex); +} diff --git a/server/NetlinkHandler.h b/server/NetlinkHandler.h index 83baa2b8834554b255495d5ce7ed654f75b4ded1..bee52dcb9ed3b0da9d92e6079bbe46500dc285a9 100644 --- a/server/NetlinkHandler.h +++ b/server/NetlinkHandler.h @@ -46,5 +46,6 @@ protected: void notifyInterfaceDnsServers(const char *iface, const char *lifetime, const char *servers); void notifyRouteChange(int action, const char *route, const char *gateway, const char *iface); + void notifyStrictCleartext(const char* uid, const char* hex); }; #endif diff --git a/server/NetlinkManager.cpp b/server/NetlinkManager.cpp index 1d731acb8de7eaa51ef61a10e4227c7b87bb33fd..79b00ee3e41040dc2b62a3e1d5b4e87680dcadf5 100644 --- a/server/NetlinkManager.cpp +++ b/server/NetlinkManager.cpp @@ -30,10 +30,24 @@ #include <cutils/log.h> +#include <netlink/attr.h> +#include <netlink/genl/genl.h> +#include <netlink/handlers.h> +#include <netlink/msg.h> + +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nfnetlink_log.h> +#include <linux/netfilter/nfnetlink_compat.h> + +#include <arpa/inet.h> + #include "NetlinkManager.h" #include "NetlinkHandler.h" +#include "pcap-netfilter-linux-android.h" + const int NetlinkManager::NFLOG_QUOTA_GROUP = 1; +const int NetlinkManager::NETFILTER_STRICT_GROUP = 2; NetlinkManager *NetlinkManager::sInstance = NULL; @@ -112,11 +126,27 @@ int NetlinkManager::start() { } if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG, - NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) { - ALOGE("Unable to open quota2 logging socket"); + NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY)) == NULL) { + ALOGE("Unable to open quota socket"); // TODO: return -1 once the emulator gets a new kernel. } + if ((mStrictHandler = setupSocket(&mStrictSock, NETLINK_NETFILTER, + 0, NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST)) == NULL) { + ALOGE("Unable to open strict socket"); + // TODO: return -1 once the emulator gets a new kernel. + } else { + if (android_nflog_send_config_cmd(mStrictSock, 0, NFULNL_CFG_CMD_PF_UNBIND, AF_INET) < 0) { + ALOGE("Failed NFULNL_CFG_CMD_PF_UNBIND: %s", strerror(errno)); + } + if (android_nflog_send_config_cmd(mStrictSock, 0, NFULNL_CFG_CMD_PF_BIND, AF_INET) < 0) { + ALOGE("Failed NFULNL_CFG_CMD_PF_BIND: %s", strerror(errno)); + } + if (android_nflog_send_config_cmd(mStrictSock, 0, NFULNL_CFG_CMD_BIND, AF_UNSPEC) < 0) { + ALOGE("Failed NFULNL_CFG_CMD_BIND: %s", strerror(errno)); + } + } + return 0; } @@ -158,5 +188,18 @@ int NetlinkManager::stop() { mQuotaSock = -1; } + if (mStrictHandler) { + if (mStrictHandler->stop()) { + ALOGE("Unable to stop strict NetlinkHandler: %s", strerror(errno)); + status = -1; + } + + delete mStrictHandler; + mStrictHandler = NULL; + + close(mStrictSock); + mStrictSock = -1; + } + return status; } diff --git a/server/NetlinkManager.h b/server/NetlinkManager.h index 5187a59a514a08260c3733439119b3745f76edee..2bfaee922594652eb3555c125001fb69663d0d2b 100644 --- a/server/NetlinkManager.h +++ b/server/NetlinkManager.h @@ -32,9 +32,11 @@ private: NetlinkHandler *mUeventHandler; NetlinkHandler *mRouteHandler; NetlinkHandler *mQuotaHandler; + NetlinkHandler *mStrictHandler; int mUeventSock; int mRouteSock; int mQuotaSock; + int mStrictSock; public: virtual ~NetlinkManager(); @@ -47,11 +49,10 @@ public: static NetlinkManager *Instance(); - /* This is the nflog group arg that the xt_quota2 neftiler will use. */ + /* Group used by xt_quota2 */ static const int NFLOG_QUOTA_GROUP; - - /* This is the group that the xt_IDLETIMER netfilter will use. */ - static const int IDLETIMER_GROUP; + /* Group used by StrictController rules */ + static const int NETFILTER_STRICT_GROUP; private: NetlinkManager(); diff --git a/server/ResponseCode.h b/server/ResponseCode.h index 6a0c22c3364d9904b214eaa45866c262bdeef8df..19d76c38b55bf7ae54c9f3b9b5bff5c9b756ceaa 100644 --- a/server/ResponseCode.h +++ b/server/ResponseCode.h @@ -77,5 +77,6 @@ public: static const int InterfaceAddressChange = 614; static const int InterfaceDnsInfo = 615; static const int RouteChange = 616; + static const int StrictCleartext = 617; }; #endif diff --git a/server/StrictController.cpp b/server/StrictController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..20232ea7a4adadd38130ea6e16ac973483858f31 --- /dev/null +++ b/server/StrictController.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define LOG_TAG "StrictController" +#define LOG_NDEBUG 0 + +#include <cutils/log.h> + +#include "NetdConstants.h" +#include "StrictController.h" + +const char* StrictController::LOCAL_OUTPUT = "st_OUTPUT"; +const char* StrictController::LOCAL_CLEAR_DETECT = "st_clear_detect"; +const char* StrictController::LOCAL_CLEAR_CAUGHT = "st_clear_caught"; +const char* StrictController::LOCAL_PENALTY_LOG = "st_penalty_log"; +const char* StrictController::LOCAL_PENALTY_REJECT = "st_penalty_reject"; + +StrictController::StrictController(void) { +} + +int StrictController::enableStrict(void) { + int res = 0; + + disableStrict(); + + // Mark 0x01 means resolved and ACCEPT + // Mark 0x02 means resolved and REJECT + + // Chain triggered when cleartext socket detected and penalty is log + res |= execIptables(V4V6, "-N", LOCAL_PENALTY_LOG, NULL); + res |= execIptables(V4V6, "-A", LOCAL_PENALTY_LOG, + "-j", "CONNMARK", "--or-mark", "0x01000000", NULL); + res |= execIptables(V4V6, "-A", LOCAL_PENALTY_LOG, + "-j", "NFLOG", "--nflog-group", "0", NULL); + + // Chain triggered when cleartext socket detected and penalty is reject + res |= execIptables(V4V6, "-N", LOCAL_PENALTY_REJECT, NULL); + res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT, + "-j", "CONNMARK", "--or-mark", "0x02000000", NULL); + res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT, + "-j", "NFLOG", "--nflog-group", "0", NULL); + res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT, + "-j", "REJECT", NULL); + + // Create chain to detect non-TLS traffic. We use a high-order + // mark bit to keep track of connections that we've already resolved. + res |= execIptables(V4V6, "-N", LOCAL_CLEAR_DETECT, NULL); + res |= execIptables(V4V6, "-N", LOCAL_CLEAR_CAUGHT, NULL); + + // Quickly skip connections that we've already resolved + res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT, + "-m", "connmark", "--mark", "0x02000000/0x02000000", + "-j", "REJECT", NULL); + res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT, + "-m", "connmark", "--mark", "0x01000000/0x01000000", + "-j", "RETURN", NULL); + + // Look for IPv4 TCP/UDP connections with TLS/DTLS header + res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp", + "-m", "u32", "--u32", "0>>22&0x3C@ 12>>26&0x3C@ 0&0xFFFF0000=0x16030000 &&" + "0>>22&0x3C@ 12>>26&0x3C@ 4&0x00FF0000=0x00010000", + "-j", "CONNMARK", "--or-mark", "0x01000000", NULL); + res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "udp", + "-m", "u32", "--u32", "0>>22&0x3C@ 8&0xFFFF0000=0x16FE0000 &&" + "0>>22&0x3C@ 20&0x00FF0000=0x00010000", + "-j", "CONNMARK", "--or-mark", "0x01000000", NULL); + + // Look for IPv6 TCP/UDP connections with TLS/DTLS header. The IPv6 header + // doesn't have an IHL field to shift with, so we have to manually add in + // the 40-byte offset at every step. + res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp", + "-m", "u32", "--u32", "52>>26&0x3C@ 40&0xFFFF0000=0x16030000 &&" + "52>>26&0x3C@ 44&0x00FF0000=0x00010000", + "-j", "CONNMARK", "--or-mark", "0x01000000", NULL); + res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp", + "-m", "u32", "--u32", "48&0xFFFF0000=0x16FE0000 &&" + "60&0x00FF0000=0x00010000", + "-j", "CONNMARK", "--or-mark", "0x01000000", NULL); + + // Skip newly classified connections from above + res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT, + "-m", "connmark", "--mark", "0x01000000/0x01000000", + "-j", "RETURN", NULL); + + // Handle TCP/UDP payloads that didn't match TLS/DTLS filters above, + // which means we've probably found cleartext data. The TCP variant + // depends on u32 returning false when we try reading into the message + // body to ignore empty ACK packets. + res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp", + "-m", "state", "--state", "ESTABLISHED", + "-m", "u32", "--u32", "0>>22&0x3C@ 12>>26&0x3C@ 0&0x0=0x0", + "-j", LOCAL_CLEAR_CAUGHT, NULL); + res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp", + "-m", "state", "--state", "ESTABLISHED", + "-m", "u32", "--u32", "52>>26&0x3C@ 40&0x0=0x0", + "-j", LOCAL_CLEAR_CAUGHT, NULL); + + res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp", + "-j", LOCAL_CLEAR_CAUGHT, NULL); + + return res; +} + +int StrictController::disableStrict(void) { + int res = 0; + + // Flush any existing rules + res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL); + + res |= execIptables(V4V6, "-F", LOCAL_PENALTY_LOG, NULL); + res |= execIptables(V4V6, "-F", LOCAL_PENALTY_REJECT, NULL); + res |= execIptables(V4V6, "-F", LOCAL_CLEAR_CAUGHT, NULL); + res |= execIptables(V4V6, "-F", LOCAL_CLEAR_DETECT, NULL); + + res |= execIptables(V4V6, "-X", LOCAL_PENALTY_LOG, NULL); + res |= execIptables(V4V6, "-X", LOCAL_PENALTY_REJECT, NULL); + res |= execIptables(V4V6, "-X", LOCAL_CLEAR_CAUGHT, NULL); + res |= execIptables(V4V6, "-X", LOCAL_CLEAR_DETECT, NULL); + + return res; +} + +int StrictController::setUidCleartextPenalty(uid_t uid, StrictPenalty penalty) { + char uidStr[16]; + sprintf(uidStr, "%d", uid); + + int res = 0; + if (penalty == ACCEPT) { + // Clean up any old rules + execIptables(V4V6, "-D", LOCAL_OUTPUT, + "-m", "owner", "--uid-owner", uidStr, + "-j", LOCAL_CLEAR_DETECT, NULL); + execIptables(V4V6, "-D", LOCAL_CLEAR_CAUGHT, + "-m", "owner", "--uid-owner", uidStr, + "-j", LOCAL_PENALTY_LOG, NULL); + execIptables(V4V6, "-D", LOCAL_CLEAR_CAUGHT, + "-m", "owner", "--uid-owner", uidStr, + "-j", LOCAL_PENALTY_REJECT, NULL); + + } else { + // Always take a detour to investigate this UID + res |= execIptables(V4V6, "-I", LOCAL_OUTPUT, + "-m", "owner", "--uid-owner", uidStr, + "-j", LOCAL_CLEAR_DETECT, NULL); + + if (penalty == LOG) { + res |= execIptables(V4V6, "-I", LOCAL_CLEAR_CAUGHT, + "-m", "owner", "--uid-owner", uidStr, + "-j", LOCAL_PENALTY_LOG, NULL); + } else if (penalty == REJECT) { + res |= execIptables(V4V6, "-I", LOCAL_CLEAR_CAUGHT, + "-m", "owner", "--uid-owner", uidStr, + "-j", LOCAL_PENALTY_REJECT, NULL); + } + } + + return res; +} diff --git a/server/StrictController.h b/server/StrictController.h new file mode 100644 index 0000000000000000000000000000000000000000..52a67794027e93c7aaafeffc8d07358d5ed51d86 --- /dev/null +++ b/server/StrictController.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _STRICT_CONTROLLER_H +#define _STRICT_CONTROLLER_H + +#include <string> + +enum StrictPenalty { INVALID, ACCEPT, LOG, REJECT }; + +/* + * Help apps catch unwanted low-level networking behavior, like + * connections not wrapped in TLS. + */ +class StrictController { +public: + StrictController(); + + int enableStrict(void); + int disableStrict(void); + + int setUidCleartextPenalty(uid_t, StrictPenalty); + + static const char* LOCAL_OUTPUT; + static const char* LOCAL_CLEAR_DETECT; + static const char* LOCAL_CLEAR_CAUGHT; + static const char* LOCAL_PENALTY_LOG; + static const char* LOCAL_PENALTY_REJECT; +}; + +#endif