Commit 535b94fa authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Gerrit Code Review
Browse files

Merge "Offer to detect non-SSL/TLS network traffic."

parents 1a3c689b fbe497fc
......@@ -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 \
......
......@@ -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") {
}
......
......@@ -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();
......
......@@ -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);
}
......@@ -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
......@@ -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;
}
......@@ -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();
......
......@@ -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
/*
* 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;
}
/*
* 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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment