1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <errno.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21
22#define LOG_TAG "StrictController"
23#define LOG_NDEBUG 0
24
25#include <cutils/log.h>
26
27#include "ConnmarkFlags.h"
28#include "NetdConstants.h"
29#include "StrictController.h"
30
31const char* StrictController::LOCAL_OUTPUT = "st_OUTPUT";
32const char* StrictController::LOCAL_CLEAR_DETECT = "st_clear_detect";
33const char* StrictController::LOCAL_CLEAR_CAUGHT = "st_clear_caught";
34const char* StrictController::LOCAL_PENALTY_LOG = "st_penalty_log";
35const char* StrictController::LOCAL_PENALTY_REJECT = "st_penalty_reject";
36
37StrictController::StrictController(void) {
38}
39
40int StrictController::enableStrict(void) {
41    char connmarkFlagAccept[16];
42    char connmarkFlagReject[16];
43    char connmarkFlagTestAccept[32];
44    char connmarkFlagTestReject[32];
45    sprintf(connmarkFlagAccept, "0x%x", ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
46    sprintf(connmarkFlagReject, "0x%x", ConnmarkFlags::STRICT_RESOLVED_REJECT);
47    sprintf(connmarkFlagTestAccept, "0x%x/0x%x",
48            ConnmarkFlags::STRICT_RESOLVED_ACCEPT,
49            ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
50    sprintf(connmarkFlagTestReject, "0x%x/0x%x",
51            ConnmarkFlags::STRICT_RESOLVED_REJECT,
52            ConnmarkFlags::STRICT_RESOLVED_REJECT);
53
54    int res = 0;
55
56    disableStrict();
57
58    // Chain triggered when cleartext socket detected and penalty is log
59    res |= execIptables(V4V6, "-N", LOCAL_PENALTY_LOG, NULL);
60    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_LOG,
61            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
62    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_LOG,
63            "-j", "NFLOG", "--nflog-group", "0", NULL);
64
65    // Chain triggered when cleartext socket detected and penalty is reject
66    res |= execIptables(V4V6, "-N", LOCAL_PENALTY_REJECT, NULL);
67    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT,
68            "-j", "CONNMARK", "--or-mark", connmarkFlagReject, NULL);
69    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT,
70            "-j", "NFLOG", "--nflog-group", "0", NULL);
71    res |= execIptables(V4V6, "-A", LOCAL_PENALTY_REJECT,
72            "-j", "REJECT", NULL);
73
74    // Create chain to detect non-TLS traffic. We use a high-order
75    // mark bit to keep track of connections that we've already resolved.
76    res |= execIptables(V4V6, "-N", LOCAL_CLEAR_DETECT, NULL);
77    res |= execIptables(V4V6, "-N", LOCAL_CLEAR_CAUGHT, NULL);
78
79    // Quickly skip connections that we've already resolved
80    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
81            "-m", "connmark", "--mark", connmarkFlagTestReject,
82            "-j", "REJECT", NULL);
83    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
84            "-m", "connmark", "--mark", connmarkFlagTestAccept,
85            "-j", "RETURN", NULL);
86
87    // Look for IPv4 TCP/UDP connections with TLS/DTLS header
88    res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
89            "-m", "u32", "--u32", "0>>22&0x3C@ 12>>26&0x3C@ 0&0xFFFF0000=0x16030000 &&"
90                                  "0>>22&0x3C@ 12>>26&0x3C@ 4&0x00FF0000=0x00010000",
91            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
92    res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
93            "-m", "u32", "--u32", "0>>22&0x3C@ 8&0xFFFF0000=0x16FE0000 &&"
94                                  "0>>22&0x3C@ 20&0x00FF0000=0x00010000",
95            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
96
97    // Look for IPv6 TCP/UDP connections with TLS/DTLS header.  The IPv6 header
98    // doesn't have an IHL field to shift with, so we have to manually add in
99    // the 40-byte offset at every step.
100    res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
101            "-m", "u32", "--u32", "52>>26&0x3C@ 40&0xFFFF0000=0x16030000 &&"
102                                  "52>>26&0x3C@ 44&0x00FF0000=0x00010000",
103            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
104    res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
105            "-m", "u32", "--u32", "48&0xFFFF0000=0x16FE0000 &&"
106                                  "60&0x00FF0000=0x00010000",
107            "-j", "CONNMARK", "--or-mark", connmarkFlagAccept, NULL);
108
109    // Skip newly classified connections from above
110    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT,
111            "-m", "connmark", "--mark", connmarkFlagTestAccept,
112            "-j", "RETURN", NULL);
113
114    // Handle TCP/UDP payloads that didn't match TLS/DTLS filters above,
115    // which means we've probably found cleartext data.  The TCP variant
116    // depends on u32 returning false when we try reading into the message
117    // body to ignore empty ACK packets.
118    res |= execIptables(V4, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
119            "-m", "state", "--state", "ESTABLISHED",
120            "-m", "u32", "--u32", "0>>22&0x3C@ 12>>26&0x3C@ 0&0x0=0x0",
121            "-j", LOCAL_CLEAR_CAUGHT, NULL);
122    res |= execIptables(V6, "-A", LOCAL_CLEAR_DETECT, "-p", "tcp",
123            "-m", "state", "--state", "ESTABLISHED",
124            "-m", "u32", "--u32", "52>>26&0x3C@ 40&0x0=0x0",
125            "-j", LOCAL_CLEAR_CAUGHT, NULL);
126
127    res |= execIptables(V4V6, "-A", LOCAL_CLEAR_DETECT, "-p", "udp",
128            "-j", LOCAL_CLEAR_CAUGHT, NULL);
129
130    return res;
131}
132
133int StrictController::disableStrict(void) {
134    int res = 0;
135
136    // Flush any existing rules
137    res |= execIptables(V4V6, "-F", LOCAL_OUTPUT, NULL);
138
139    res |= execIptables(V4V6, "-F", LOCAL_PENALTY_LOG, NULL);
140    res |= execIptables(V4V6, "-F", LOCAL_PENALTY_REJECT, NULL);
141    res |= execIptables(V4V6, "-F", LOCAL_CLEAR_CAUGHT, NULL);
142    res |= execIptables(V4V6, "-F", LOCAL_CLEAR_DETECT, NULL);
143
144    res |= execIptables(V4V6, "-X", LOCAL_PENALTY_LOG, NULL);
145    res |= execIptables(V4V6, "-X", LOCAL_PENALTY_REJECT, NULL);
146    res |= execIptables(V4V6, "-X", LOCAL_CLEAR_CAUGHT, NULL);
147    res |= execIptables(V4V6, "-X", LOCAL_CLEAR_DETECT, NULL);
148
149    return res;
150}
151
152int StrictController::setUidCleartextPenalty(uid_t uid, StrictPenalty penalty) {
153    char uidStr[16];
154    sprintf(uidStr, "%d", uid);
155
156    int res = 0;
157    if (penalty == ACCEPT) {
158        // Clean up any old rules
159        execIptables(V4V6, "-D", LOCAL_OUTPUT,
160                "-m", "owner", "--uid-owner", uidStr,
161                "-j", LOCAL_CLEAR_DETECT, NULL);
162        execIptables(V4V6, "-D", LOCAL_CLEAR_CAUGHT,
163                "-m", "owner", "--uid-owner", uidStr,
164                "-j", LOCAL_PENALTY_LOG, NULL);
165        execIptables(V4V6, "-D", LOCAL_CLEAR_CAUGHT,
166                "-m", "owner", "--uid-owner", uidStr,
167                "-j", LOCAL_PENALTY_REJECT, NULL);
168
169    } else {
170        // Always take a detour to investigate this UID
171        res |= execIptables(V4V6, "-I", LOCAL_OUTPUT,
172                "-m", "owner", "--uid-owner", uidStr,
173                "-j", LOCAL_CLEAR_DETECT, NULL);
174
175        if (penalty == LOG) {
176            res |= execIptables(V4V6, "-I", LOCAL_CLEAR_CAUGHT,
177                    "-m", "owner", "--uid-owner", uidStr,
178                    "-j", LOCAL_PENALTY_LOG, NULL);
179        } else if (penalty == REJECT) {
180            res |= execIptables(V4V6, "-I", LOCAL_CLEAR_CAUGHT,
181                    "-m", "owner", "--uid-owner", uidStr,
182                    "-j", LOCAL_PENALTY_REJECT, NULL);
183        }
184    }
185
186    return res;
187}
188