Offer to detect non-SSL/TLS network traffic.

Introduces new module that provides network-related features for
the StrictMode developer API.  The first feature offers to detect
sockets sending data not wrapped inside a layer of SSL/TLS
encryption.

This carefully only adds overhead to UIDs that have requested
detection, and it uses CONNMARK to quickly accept/reject packets
from streams that have already been inspected.  Detection is done
by looking for a well-known TLS handshake header; it's not future
proof, but it's a good start.  Handles both IPv4 and IPv6.

When requested, we also log the triggering packet through NFLOG and
back up to the framework to aid investigation.

Bug: 18335678
Change-Id: Ie8fab785139dfb55a71b6dc7a0f3c75a8408224b
diff --git a/server/StrictController.cpp b/server/StrictController.cpp
new file mode 100644
index 0000000..20232ea
--- /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;
+}