/* * 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 #include #include #include #define LOG_TAG "StrictController" #define LOG_NDEBUG 0 #include #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; }