diff options
author | Christian Grothoff <christian@grothoff.org> | 2012-01-02 03:51:28 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2012-01-02 03:51:28 +0000 |
commit | 1fd2ff9b321277b55444c2a074e6476cc10099c2 (patch) | |
tree | c505d53cacd7b7c0fc27abe98e539940a0d36749 /src | |
parent | 46db89062f906a1d4f33cd24973f89e67d0b5a89 (diff) | |
download | gnunet-1fd2ff9b321277b55444c2a074e6476cc10099c2.tar.gz gnunet-1fd2ff9b321277b55444c2a074e6476cc10099c2.zip |
dns hijacker code review
Diffstat (limited to 'src')
-rw-r--r-- | src/dns/gnunet-helper-hijack-dns.c | 372 |
1 files changed, 272 insertions, 100 deletions
diff --git a/src/dns/gnunet-helper-hijack-dns.c b/src/dns/gnunet-helper-hijack-dns.c index 70da96477..833bda8ad 100644 --- a/src/dns/gnunet-helper-hijack-dns.c +++ b/src/dns/gnunet-helper-hijack-dns.c | |||
@@ -1,6 +1,6 @@ | |||
1 | /* | 1 | /* |
2 | This file is part of GNUnet. | 2 | This file is part of GNUnet. |
3 | (C) 2010 Christian Grothoff | 3 | (C) 2010, 2011, 2012 Christian Grothoff |
4 | 4 | ||
5 | GNUnet is free software; you can redistribute it and/or modify | 5 | GNUnet is free software; you can redistribute it and/or modify |
6 | it under the terms of the GNU General Public License as published | 6 | it under the terms of the GNU General Public License as published |
@@ -19,138 +19,310 @@ | |||
19 | */ | 19 | */ |
20 | 20 | ||
21 | /** | 21 | /** |
22 | * @file vpn/gnunet-helper-hijack-dns.c | 22 | * @file dns/gnunet-helper-hijack-dns.c |
23 | * @brief | 23 | * @brief helper to install firewall rules to hijack all DNS traffic |
24 | * and send it to our virtual interface except for DNS traffic | ||
25 | * that originates on the specified port. | ||
24 | * @author Philipp Tölke | 26 | * @author Philipp Tölke |
27 | * @author Christian Grothoff | ||
28 | * | ||
29 | * This program alters the Linux firewall rules so that DNS traffic | ||
30 | * that ordinarily exits the system can be intercepted and managed by | ||
31 | * a virtual interface. In order to achieve this, DNS traffic is | ||
32 | * marked with the DNS_MARK given in below and re-routed to a custom | ||
33 | * table with the DNS_TABLE ID given below. Systems and | ||
34 | * administrators must take care to not cause conflicts with these | ||
35 | * values (it was deemed safest to hardcode them as passing these | ||
36 | * values as arguments might permit messing with arbitrary firewall | ||
37 | * rules, which would be dangerous). | ||
38 | * | ||
39 | * Note that having this binary SUID is only partially safe: it will | ||
40 | * allow redirecting (and intercepting / mangling) of all DNS traffic | ||
41 | * originating from this system by any user who can create a virtual | ||
42 | * interface (and this is again enabled by other GNUnet SUID | ||
43 | * binaries). Furthermore, even without the ability to create a | ||
44 | * tunnel interface, this code will make it possible to DoS all DNS | ||
45 | * traffic originating from the current system, simply by sending it | ||
46 | * to nowhere. | ||
47 | * | ||
48 | * Naturally, neither of these problems can be helped as this is the | ||
49 | * fundamental purpose of the binary. Certifying that this code is | ||
50 | * "safe" thus only means that it doesn't allow anything else (such | ||
51 | * as local priv. escalation, etc.). | ||
52 | * | ||
53 | * The following list of people have reviewed this code and considered | ||
54 | * it safe (within specifications) since the last modification (if you | ||
55 | * reviewed it, please have your name added to the list): | ||
56 | * | ||
57 | * - Christian Grothoff | ||
25 | */ | 58 | */ |
26 | #include <platform.h> | 59 | #include "platform.h" |
27 | 60 | ||
28 | #include "gnunet_common.h" | 61 | /** |
62 | * Name and full path of IPTABLES binary. | ||
63 | */ | ||
64 | #define SBIN_IPTABLES "/sbin/iptables" | ||
65 | |||
66 | /** | ||
67 | * Name and full path of IPTABLES binary. | ||
68 | */ | ||
69 | #define SBIN_IP "/sbin/ip" | ||
70 | |||
71 | /** | ||
72 | * Port for DNS traffic. | ||
73 | */ | ||
74 | #define DNS_PORT "53" | ||
75 | |||
76 | /** | ||
77 | * Marker we set for our hijacked DNS traffic. We use GNUnet's | ||
78 | * port (2086) plus the DNS port (53) in HEX to make a 32-bit mark | ||
79 | * (which is hopefully long enough to not collide); so | ||
80 | * 0x08260035 = 136708149 (hopefully unique enough...). | ||
81 | */ | ||
82 | #define DNS_MARK "136708149" | ||
83 | |||
84 | /** | ||
85 | * Table we use for our DNS rules. 0-255 is the range and | ||
86 | * 0, 253, 254 and 255 are already reserved. As this is about | ||
87 | * DNS and as "53" is likely (fingers crossed!) high enough to | ||
88 | * not usually conflict with a normal user's setup, we use 53 | ||
89 | * to give a hint that this has something to do with DNS. | ||
90 | */ | ||
91 | #define DNS_TABLE "53" | ||
29 | 92 | ||
93 | |||
94 | /** | ||
95 | * Run the given command and wait for it to complete. | ||
96 | * | ||
97 | * @param file name of the binary to run | ||
98 | * @param cmd command line arguments (as given to 'execv') | ||
99 | * @return 0 on success, 1 on any error | ||
100 | */ | ||
30 | static int | 101 | static int |
31 | fork_and_exec (char *file, char *cmd[]) | 102 | fork_and_exec (const char *file, |
103 | char *const cmd[]) | ||
32 | { | 104 | { |
33 | pid_t pid = fork (); | 105 | int status; |
106 | pid_t pid; | ||
107 | pid_t ret; | ||
34 | 108 | ||
35 | if (pid < 0) | 109 | pid = fork (); |
110 | if (-1 == pid) | ||
36 | { | 111 | { |
37 | fprintf (stderr, "could not fork: %s\n", strerror (errno)); | 112 | fprintf (stderr, |
38 | return GNUNET_SYSERR; | 113 | "fork failed: %s\n", |
114 | strerror (errno)); | ||
115 | return 1; | ||
39 | } | 116 | } |
40 | 117 | if (0 == pid) | |
41 | int st = 0; | ||
42 | |||
43 | if (pid == 0) | ||
44 | { | 118 | { |
45 | execv (file, cmd); | 119 | /* we are the child process */ |
120 | (void) execv (file, cmd); | ||
121 | /* can only get here on error */ | ||
122 | fprintf (stderr, | ||
123 | "exec `%s' failed: %s\n", | ||
124 | file, | ||
125 | strerror (errno)); | ||
126 | _exit (1); | ||
46 | } | 127 | } |
47 | else | 128 | /* keep running waitpid as long as the only error we get is 'EINTR' */ |
129 | while ( (-1 == (ret = waitpid (pid, &status, 0))) && | ||
130 | (errno == EINTR) ); | ||
131 | if (-1 == ret) | ||
48 | { | 132 | { |
49 | waitpid (pid, &st, 0); | 133 | fprintf (stderr, |
134 | "waitpid failed: %s\n", | ||
135 | strerror (errno)); | ||
136 | return 1; | ||
50 | } | 137 | } |
51 | return WIFEXITED (st) && (WEXITSTATUS (st) == 0); | 138 | if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status)))) |
139 | return 1; | ||
140 | /* child process completed and returned success, we're happy */ | ||
141 | return 0; | ||
52 | } | 142 | } |
53 | 143 | ||
144 | |||
145 | /** | ||
146 | * Main function of "gnunet-helper-hijack-dns". | ||
147 | * Use "-d" as the first argument to remove the firewall rules. | ||
148 | * The other arguments are the DNS source port to NOT affect | ||
149 | * by the rules, followed by the name of the virtual interface | ||
150 | * to redirect all of the remaining DNS traffic to. | ||
151 | * | ||
152 | * @param argc number of arguments | ||
153 | * @param argv ["-d"] PORT VTUN | ||
154 | * @return 0 on success, otherwise code indicating type of error: | ||
155 | * 1 wrong number of arguments | ||
156 | * 2 invalid port number | ||
157 | * 3 iptables not executable | ||
158 | * 4 ip not executable | ||
159 | * 8 failed to change routing table, cleanup successfull | ||
160 | * 16-31 failed to undo some changes to routing table | ||
161 | * 31-47 failed to fully change routing table and then might have failed to undo everything | ||
162 | */ | ||
54 | int | 163 | int |
55 | main (int argc, char **argv) | 164 | main (int argc, char *const*argv) |
56 | { | 165 | { |
57 | int delete = 0; | 166 | int delete; |
58 | int port = 0; | 167 | unsigned int port; |
59 | char *virt_dns; | 168 | char *virt_dns; |
169 | char localport[6]; | ||
170 | int r; | ||
60 | 171 | ||
172 | /* check command-line arguments */ | ||
61 | if (argc < 3) | 173 | if (argc < 3) |
62 | return GNUNET_SYSERR; | ||
63 | |||
64 | if (strncmp (argv[1], "-d", 2) == 0) | ||
65 | { | 174 | { |
66 | if (argc < 3) | 175 | fprintf (stderr, |
67 | return GNUNET_SYSERR; | 176 | "Syntax: gnunet-helper-hijack-dns [-d] PORT INTERFACENAME\n"); |
68 | delete = 1; | 177 | return 1; |
69 | port = atoi (argv[2]); | ||
70 | virt_dns = argv[3]; | ||
71 | } | 178 | } |
179 | if (0 == strcmp (argv[1], "-d")) | ||
180 | delete = 1; | ||
72 | else | 181 | else |
182 | delete = 0; | ||
183 | if (argc != 3 + delete) | ||
73 | { | 184 | { |
74 | port = atoi (argv[1]); | 185 | fprintf (stderr, |
75 | virt_dns = argv[2]; | 186 | "Syntax: gnunet-helper-hijack-dns [-d] PORT INTERFACENAME\n"); |
187 | return 1; | ||
188 | } | ||
189 | port = atoi (argv[1 + delete]); | ||
190 | virt_dns = argv[2 + delete]; | ||
191 | if ( (port == 0) || (port >= 65536) ) | ||
192 | { | ||
193 | fprintf (stderr, | ||
194 | "Port `%u' is invalid\n", | ||
195 | port); | ||
196 | return 2; | ||
76 | } | 197 | } |
77 | 198 | /* verify that the binaries were care about are executable */ | |
78 | if (port == 0) | 199 | if (0 != access (SBIN_IPTABLES, X_OK)) |
79 | return GNUNET_SYSERR; | ||
80 | |||
81 | struct stat s; | ||
82 | |||
83 | if (stat ("/sbin/iptables", &s) < 0) | ||
84 | { | 200 | { |
85 | fprintf (stderr, "stat on /sbin/iptables failed: %s\n", strerror (errno)); | 201 | fprintf (stderr, |
86 | return GNUNET_SYSERR; | 202 | "`%s' is not executable: %s\n", |
203 | SBIN_IPTABLES, | ||
204 | strerror (errno)); | ||
205 | return 3; | ||
87 | } | 206 | } |
88 | if (stat ("/sbin/ip", &s) < 0) | 207 | if (0 != access (SBIN_IP, X_OK)) |
89 | { | 208 | { |
90 | fprintf (stderr, "stat on /sbin/ip failed: %s\n", strerror (errno)); | 209 | fprintf (stderr, |
91 | return GNUNET_SYSERR; | 210 | "`%s' is not executable: %s\n", |
211 | SBIN_IP, | ||
212 | strerror (errno)); | ||
213 | return 4; | ||
92 | } | 214 | } |
93 | 215 | ||
94 | char localport[7]; | 216 | /* print port number to string for command-line use*/ |
95 | 217 | (void) snprintf (localport, | |
96 | snprintf (localport, 7, "%d", port); | 218 | sizeof (localport), |
219 | "%u", | ||
220 | port); | ||
97 | 221 | ||
98 | int r; | 222 | /* update routing tables -- this is why we are SUID! */ |
99 | 223 | if (! delete) | |
100 | if (delete) | ||
101 | { | 224 | { |
102 | e4: | 225 | /* Forward everything from the given local port (with destination |
103 | r = fork_and_exec ("/sbin/ip", (char *[]) | 226 | to port 53, and only for UDP) without hijacking */ |
104 | { | 227 | { |
105 | "ip", "route", "del", "default", "via", virt_dns, | 228 | char *const mangle_args[] = |
106 | "table", "2", NULL}); | 229 | { |
107 | e3: | 230 | "iptables", "-t", "mangle", "-I", "OUTPUT", "1", "-p", |
108 | r = fork_and_exec ("/sbin/ip", (char *[]) | 231 | "udp", "--sport", localport, "--dport", DNS_PORT, "-j", |
109 | { | 232 | "ACCEPT", NULL |
110 | "ip", "rule", "del", "fwmark", "3", "table", "2", NULL}); | 233 | }; |
111 | e2: | 234 | if (0 != fork_and_exec (SBIN_IPTABLES, mangle_args)) |
112 | r = fork_and_exec ("/sbin/iptables", (char *[]) | 235 | goto cleanup_mangle_1; |
113 | { | 236 | } |
114 | "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", | 237 | /* Mark all of the other DNS traffic using our mark DNS_MARK */ |
115 | "--dport", "53", "-j", "MARK", "--set-mark", "3", NULL}); | 238 | { |
116 | e1: | 239 | char *const mark_args[] = |
117 | r = fork_and_exec ("/sbin/iptables", (char *[]) | 240 | { |
118 | { | 241 | "iptables", "-t", "mangle", "-I", "OUTPUT", DNS_TABLE, "-p", |
119 | "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", | 242 | "udp", "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, |
120 | "--sport", localport, "--dport", "53", "-j", "ACCEPT", | 243 | NULL |
121 | NULL}); | 244 | }; |
122 | if (!delete) | 245 | if (0 != fork_and_exec (SBIN_IPTABLES, mark_args)) |
123 | r = 0; | 246 | goto cleanup_mark_2; |
247 | } | ||
248 | /* Forward all marked DNS traffic to our DNS_TABLE */ | ||
249 | { | ||
250 | char *const forward_args[] = | ||
251 | { | ||
252 | "ip", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL | ||
253 | }; | ||
254 | if (0 != fork_and_exec (SBIN_IP, forward_args)) | ||
255 | goto cleanup_forward_3; | ||
256 | } | ||
257 | /* Finally, add rule in our forwarding table to pass to our virtual interface */ | ||
258 | { | ||
259 | char *const route_args[] = | ||
260 | { | ||
261 | "ip", "route", "add", "default", "via", virt_dns, | ||
262 | "table", DNS_TABLE, NULL | ||
263 | }; | ||
264 | if (0 != fork_and_exec (SBIN_IP, route_args)) | ||
265 | goto cleanup_route_4; | ||
266 | } | ||
124 | } | 267 | } |
125 | else | 268 | else |
126 | { | 269 | { |
127 | r = fork_and_exec ("/sbin/iptables", (char *[]) | 270 | r = 0; |
128 | { | 271 | /* delete or clean-up-on-error case */ |
129 | "iptables", "-t", "mangle", "-I", "OUTPUT", "1", "-p", | 272 | cleanup_route_4: |
130 | "udp", "--sport", localport, "--dport", "53", "-j", | 273 | { |
131 | "ACCEPT", NULL}); | 274 | char *const route_clean_args[] = |
132 | if (!r) | 275 | { |
133 | goto e1; | 276 | "ip", "route", "del", "default", "via", virt_dns, |
134 | r = fork_and_exec ("/sbin/iptables", (char *[]) | 277 | "table", DNS_TABLE, NULL |
135 | { | 278 | }; |
136 | "iptables", "-t", "mangle", "-I", "OUTPUT", "2", "-p", | 279 | if (0 != fork_and_exec (SBIN_IP, route_clean_args)) |
137 | "udp", "--dport", "53", "-j", "MARK", "--set-mark", "3", | 280 | r += 1; |
138 | NULL}); | 281 | } |
139 | if (!r) | 282 | cleanup_forward_3: |
140 | goto e2; | 283 | { |
141 | r = fork_and_exec ("/sbin/ip", (char *[]) | 284 | char *const forward_clean_args[] = |
142 | { | 285 | { |
143 | "ip", "rule", "add", "fwmark", "3", "table", "2", NULL}); | 286 | "ip", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL |
144 | if (!r) | 287 | }; |
145 | goto e3; | 288 | if (0 != fork_and_exec (SBIN_IP, forward_clean_args)) |
146 | r = fork_and_exec ("/sbin/ip", (char *[]) | 289 | r += 2; |
147 | { | 290 | } |
148 | "ip", "route", "add", "default", "via", virt_dns, | 291 | cleanup_mark_2: |
149 | "table", "2", NULL}); | 292 | { |
150 | if (!r) | 293 | char *const mark_clean_args[] = |
151 | goto e4; | 294 | { |
152 | } | 295 | "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", |
153 | if (r) | 296 | "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL |
154 | return GNUNET_YES; | 297 | }; |
155 | return GNUNET_SYSERR; | 298 | if (0 != fork_and_exec (SBIN_IPTABLES, mark_clean_args)) |
299 | r += 4; | ||
300 | } | ||
301 | cleanup_mangle_1: | ||
302 | { | ||
303 | char *const mangle_clean_args[] = | ||
304 | { | ||
305 | "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", | ||
306 | "--sport", localport, "--dport", DNS_PORT, "-j", "ACCEPT", | ||
307 | NULL | ||
308 | }; | ||
309 | if (0 != fork_and_exec (SBIN_IPTABLES, mangle_clean_args)) | ||
310 | r += 8; | ||
311 | } | ||
312 | if (r != 0) | ||
313 | { | ||
314 | if (delete) | ||
315 | return 16 + r; /* failed to delete */ | ||
316 | return 32 + r; /* first failed to install, then also failed to clean up! */ | ||
317 | } | ||
318 | if (! delete) | ||
319 | { | ||
320 | /* got here via goto to clean up handler, failed to install, succeeded with clean up */ | ||
321 | return 8; | ||
322 | } | ||
323 | } | ||
324 | /* success ! */ | ||
325 | return 0; | ||
156 | } | 326 | } |
327 | |||
328 | /* end of gnunet-helper-hijack-dns.c */ | ||