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 <string>
18 #include <vector>
19 
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #define LOG_TAG "StrictController"
26 #define LOG_NDEBUG 0
27 #include <log/log.h>
28 
29 #include <android-base/stringprintf.h>
30 #include <android-base/strings.h>
31 
32 #include "ConnmarkFlags.h"
33 #include "NetdConstants.h"
34 #include "StrictController.h"
35 
36 auto StrictController::execIptablesRestore = ::execIptablesRestore;
37 
38 const char* StrictController::LOCAL_OUTPUT = "st_OUTPUT";
39 const char* StrictController::LOCAL_CLEAR_DETECT = "st_clear_detect";
40 const char* StrictController::LOCAL_CLEAR_CAUGHT = "st_clear_caught";
41 const char* StrictController::LOCAL_PENALTY_LOG = "st_penalty_log";
42 const char* StrictController::LOCAL_PENALTY_REJECT = "st_penalty_reject";
43 
44 using android::base::Join;
45 using android::base::StringPrintf;
46 
StrictController(void)47 StrictController::StrictController(void) {
48 }
49 
setupIptablesHooks(void)50 int StrictController::setupIptablesHooks(void) {
51     char connmarkFlagAccept[16];
52     char connmarkFlagReject[16];
53     char connmarkFlagTestAccept[32];
54     char connmarkFlagTestReject[32];
55     sprintf(connmarkFlagAccept, "0x%x", ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
56     sprintf(connmarkFlagReject, "0x%x", ConnmarkFlags::STRICT_RESOLVED_REJECT);
57     sprintf(connmarkFlagTestAccept, "0x%x/0x%x",
58             ConnmarkFlags::STRICT_RESOLVED_ACCEPT,
59             ConnmarkFlags::STRICT_RESOLVED_ACCEPT);
60     sprintf(connmarkFlagTestReject, "0x%x/0x%x",
61             ConnmarkFlags::STRICT_RESOLVED_REJECT,
62             ConnmarkFlags::STRICT_RESOLVED_REJECT);
63 
64     resetChains();
65 
66     int res = 0;
67     std::vector<std::string> v4, v6;
68 
69 #define CMD_V4(...) { auto cmd = StringPrintf(__VA_ARGS__); v4.push_back(cmd); }
70 #define CMD_V6(...) { auto cmd = StringPrintf(__VA_ARGS__); v6.push_back(cmd); }
71 #define CMD_V4V6(...) { CMD_V4(__VA_ARGS__); CMD_V6(__VA_ARGS__); };
72 
73     CMD_V4V6("*filter");
74 
75     // Chain triggered when cleartext socket detected and penalty is log
76     CMD_V4V6("-A %s -j CONNMARK --or-mark %s", LOCAL_PENALTY_LOG, connmarkFlagAccept);
77     CMD_V4V6("-A %s -j NFLOG --nflog-group 0", LOCAL_PENALTY_LOG);
78 
79     // Chain triggered when cleartext socket detected and penalty is reject
80     CMD_V4V6("-A %s -j CONNMARK --or-mark %s", LOCAL_PENALTY_REJECT, connmarkFlagReject);
81     CMD_V4V6("-A %s -j NFLOG --nflog-group 0", LOCAL_PENALTY_REJECT);
82     CMD_V4V6("-A %s -j REJECT", LOCAL_PENALTY_REJECT);
83 
84     // We use a high-order mark bit to keep track of connections that we've already resolved.
85     // Quickly skip connections that we've already resolved
86     CMD_V4V6("-A %s -m connmark --mark %s -j REJECT", LOCAL_CLEAR_DETECT, connmarkFlagTestReject);
87     CMD_V4V6("-A %s -m connmark --mark %s -j RETURN", LOCAL_CLEAR_DETECT, connmarkFlagTestAccept);
88 
89     // Look for IPv4 TCP/UDP connections with TLS/DTLS header
90     const char *u32;
91     u32 = "0>>22&0x3C@ 12>>26&0x3C@ 0&0xFFFF0000=0x16030000 &&"
92           "0>>22&0x3C@ 12>>26&0x3C@ 4&0x00FF0000=0x00010000";
93     CMD_V4("-A %s -p tcp -m u32 --u32 \"%s\" -j CONNMARK --or-mark %s",
94            LOCAL_CLEAR_DETECT, u32, connmarkFlagAccept);
95 
96     u32 = "0>>22&0x3C@ 8&0xFFFF0000=0x16FE0000 &&"
97           "0>>22&0x3C@ 20&0x00FF0000=0x00010000";
98     CMD_V4("-A %s -p udp -m u32 --u32 \"%s\" -j CONNMARK --or-mark %s",
99            LOCAL_CLEAR_DETECT, u32, connmarkFlagAccept);
100 
101     // Look for IPv6 TCP/UDP connections with TLS/DTLS header.  The IPv6 header
102     // doesn't have an IHL field to shift with, so we have to manually add in
103     // the 40-byte offset at every step.
104     u32 = "52>>26&0x3C@ 40&0xFFFF0000=0x16030000 &&"
105           "52>>26&0x3C@ 44&0x00FF0000=0x00010000";
106     CMD_V6("-A %s -p tcp -m u32 --u32 \"%s\" -j CONNMARK --or-mark %s",
107            LOCAL_CLEAR_DETECT, u32, connmarkFlagAccept);
108 
109     u32 = "48&0xFFFF0000=0x16FE0000 &&"
110           "60&0x00FF0000=0x00010000";
111     CMD_V6("-A %s -p udp -m u32 --u32 \"%s\" -j CONNMARK --or-mark %s",
112            LOCAL_CLEAR_DETECT, u32, connmarkFlagAccept);
113 
114     // Skip newly classified connections from above
115     CMD_V4V6("-A %s -m connmark --mark %s -j RETURN", LOCAL_CLEAR_DETECT, connmarkFlagTestAccept);
116 
117     // Handle TCP/UDP payloads that didn't match TLS/DTLS filters above,
118     // which means we've probably found cleartext data.  The TCP variant
119     // depends on u32 returning false when we try reading into the message
120     // body to ignore empty ACK packets.
121     u32 = "0>>22&0x3C@ 12>>26&0x3C@ 0&0x0=0x0";
122     CMD_V4("-A %s -p tcp -m state --state ESTABLISHED -m u32 --u32 \"%s\" -j %s",
123            LOCAL_CLEAR_DETECT, u32, LOCAL_CLEAR_CAUGHT);
124 
125     u32 = "52>>26&0x3C@ 40&0x0=0x0";
126     CMD_V6("-A %s -p tcp -m state --state ESTABLISHED -m u32 --u32 \"%s\" -j %s",
127            LOCAL_CLEAR_DETECT, u32, LOCAL_CLEAR_CAUGHT);
128 
129     CMD_V4V6("-A %s -p udp -j %s", LOCAL_CLEAR_DETECT, LOCAL_CLEAR_CAUGHT);
130     CMD_V4V6("COMMIT\n");
131 
132     res |= execIptablesRestore(V4, Join(v4, '\n'));
133     res |= execIptablesRestore(V6, Join(v6, '\n'));
134 
135 #undef CMD_V4
136 #undef CMD_V6
137 #undef CMD_V4V6
138 
139     return res ? -EREMOTEIO : 0;
140 }
141 
resetChains(void)142 int StrictController::resetChains(void) {
143     // Flush any existing rules
144 #define CLEAR_CHAIN(x) StringPrintf(":%s -", (x))
145     std::vector<std::string> commandList = {
146         "*filter",
147         CLEAR_CHAIN(LOCAL_OUTPUT),
148         CLEAR_CHAIN(LOCAL_PENALTY_LOG),
149         CLEAR_CHAIN(LOCAL_PENALTY_REJECT),
150         CLEAR_CHAIN(LOCAL_CLEAR_CAUGHT),
151         CLEAR_CHAIN(LOCAL_CLEAR_DETECT),
152         "COMMIT\n"
153     };
154     const std::string commands = Join(commandList, '\n');
155     return (execIptablesRestore(V4V6, commands) == 0) ? 0 : -EREMOTEIO;
156 #undef CLEAR_CHAIN
157 }
158 
setUidCleartextPenalty(uid_t uid,StrictPenalty penalty)159 int StrictController::setUidCleartextPenalty(uid_t uid, StrictPenalty penalty) {
160     // When a penalty is set, we don't know what penalty the UID previously had. In order to be able
161     // to clear the previous penalty without causing an iptables error by deleting rules that don't
162     // exist, put each UID's rules in a chain specific to that UID. That way, the commands we need
163     // to run to clear the previous penalty don't depend on what the penalty actually was - all we
164     // need to do is clear the chain.
165     std::string perUidChain = StringPrintf("st_clear_caught_%u", uid);
166 
167     std::vector<std::string> commands;
168     if (penalty == ACCEPT) {
169         // Clean up any old rules
170         commands = {
171             "*filter",
172             StringPrintf("-D %s -m owner --uid-owner %d -j %s",
173                          LOCAL_OUTPUT, uid, LOCAL_CLEAR_DETECT),
174             StringPrintf("-D %s -m owner --uid-owner %d -j %s",
175                          LOCAL_CLEAR_CAUGHT, uid, perUidChain.c_str()),
176             StringPrintf("-F %s", perUidChain.c_str()),
177             StringPrintf("-X %s", perUidChain.c_str()),
178         };
179     } else {
180         // Always take a detour to investigate this UID
181         commands.push_back("*filter");
182         commands.push_back(StringPrintf(":%s -", perUidChain.c_str()));
183         commands.push_back(StringPrintf("-I %s -m owner --uid-owner %d -j %s",
184                                         LOCAL_OUTPUT, uid, LOCAL_CLEAR_DETECT));
185         commands.push_back(StringPrintf("-I %s -m owner --uid-owner %d -j %s",
186                                         LOCAL_CLEAR_CAUGHT, uid, perUidChain.c_str()));
187 
188         if (penalty == LOG) {
189             commands.push_back(StringPrintf("-A %s -j %s", perUidChain.c_str(), LOCAL_PENALTY_LOG));
190         } else if (penalty == REJECT) {
191             commands.push_back(StringPrintf("-A %s -j %s", perUidChain.c_str(),
192                                             LOCAL_PENALTY_REJECT));
193         }
194     }
195     commands.push_back("COMMIT\n");
196 
197     return (execIptablesRestore(V4V6, Join(commands, "\n")) == 0) ? 0 : -EREMOTEIO;
198 }
199