/*
* 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 "ConnmarkFlags.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) {
char connmarkFlagAccept[16];
char connmarkFlagReject[16];
char connmarkFlagTestAccept[32];
char connmarkFlagTestReject[32];
sprintf(connmarkFlagAccept, "0x%x", ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
sprintf(connmarkFlagReject, "0x%x", ConnmarkFlags::STRICT_RESOLVED_REJECT);
sprintf(connmarkFlagTestAccept, "0x%x/0x%x",
ConnmarkFlags::STRICT_RESOLVED_ACCEPT,
ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
sprintf(connmarkFlagTestReject, "0x%x/0x%x",
ConnmarkFlags::STRICT_RESOLVED_REJECT,
ConnmarkFlags::STRICT_RESOLVED_REJECT);
int res = 0;
disableStrict();
// 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", connmarkFlagAccept, 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", connmarkFlagReject, 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", connmarkFlagTestReject,
"-j", "REJECT", NULL);
res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
"-m", "connmark", "--mark", connmarkFlagTestAccept,
"-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", connmarkFlagAccept, 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", connmarkFlagAccept, 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", connmarkFlagAccept, NULL);
res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
"-m", "u32", "--u32", "48&0xFFFF0000=0x16FE0000 &&"
"60&0x00FF0000=0x00010000",
"-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
// Skip newly classified connections from above
res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
"-m", "connmark", "--mark", connmarkFlagTestAccept,
"-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;
}