X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fdns%2Fgnunet-helper-hijack-dns.c;h=833bda8adfb832368682571a7c43e22d06a3e883;hb=4c82e28a40c038caef74a1a08ab0df3257615c4b;hp=70da96477f87070fde02e04c6a94675a0b7783a9;hpb=66ffc809472f27d69b9ad7361f8ba29c2674f716;p=oweals%2Fgnunet.git 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 @@ /* This file is part of GNUnet. - (C) 2010 Christian Grothoff + (C) 2010, 2011, 2012 Christian Grothoff GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -19,138 +19,310 @@ */ /** - * @file vpn/gnunet-helper-hijack-dns.c - * @brief + * @file dns/gnunet-helper-hijack-dns.c + * @brief helper to install firewall rules to hijack all DNS traffic + * and send it to our virtual interface except for DNS traffic + * that originates on the specified port. * @author Philipp Tölke + * @author Christian Grothoff + * + * This program alters the Linux firewall rules so that DNS traffic + * that ordinarily exits the system can be intercepted and managed by + * a virtual interface. In order to achieve this, DNS traffic is + * marked with the DNS_MARK given in below and re-routed to a custom + * table with the DNS_TABLE ID given below. Systems and + * administrators must take care to not cause conflicts with these + * values (it was deemed safest to hardcode them as passing these + * values as arguments might permit messing with arbitrary firewall + * rules, which would be dangerous). + * + * Note that having this binary SUID is only partially safe: it will + * allow redirecting (and intercepting / mangling) of all DNS traffic + * originating from this system by any user who can create a virtual + * interface (and this is again enabled by other GNUnet SUID + * binaries). Furthermore, even without the ability to create a + * tunnel interface, this code will make it possible to DoS all DNS + * traffic originating from the current system, simply by sending it + * to nowhere. + * + * Naturally, neither of these problems can be helped as this is the + * fundamental purpose of the binary. Certifying that this code is + * "safe" thus only means that it doesn't allow anything else (such + * as local priv. escalation, etc.). + * + * The following list of people have reviewed this code and considered + * it safe (within specifications) since the last modification (if you + * reviewed it, please have your name added to the list): + * + * - Christian Grothoff */ -#include +#include "platform.h" -#include "gnunet_common.h" +/** + * Name and full path of IPTABLES binary. + */ +#define SBIN_IPTABLES "/sbin/iptables" + +/** + * Name and full path of IPTABLES binary. + */ +#define SBIN_IP "/sbin/ip" + +/** + * Port for DNS traffic. + */ +#define DNS_PORT "53" + +/** + * Marker we set for our hijacked DNS traffic. We use GNUnet's + * port (2086) plus the DNS port (53) in HEX to make a 32-bit mark + * (which is hopefully long enough to not collide); so + * 0x08260035 = 136708149 (hopefully unique enough...). + */ +#define DNS_MARK "136708149" + +/** + * Table we use for our DNS rules. 0-255 is the range and + * 0, 253, 254 and 255 are already reserved. As this is about + * DNS and as "53" is likely (fingers crossed!) high enough to + * not usually conflict with a normal user's setup, we use 53 + * to give a hint that this has something to do with DNS. + */ +#define DNS_TABLE "53" + +/** + * Run the given command and wait for it to complete. + * + * @param file name of the binary to run + * @param cmd command line arguments (as given to 'execv') + * @return 0 on success, 1 on any error + */ static int -fork_and_exec (char *file, char *cmd[]) +fork_and_exec (const char *file, + char *const cmd[]) { - pid_t pid = fork (); + int status; + pid_t pid; + pid_t ret; - if (pid < 0) + pid = fork (); + if (-1 == pid) { - fprintf (stderr, "could not fork: %s\n", strerror (errno)); - return GNUNET_SYSERR; + fprintf (stderr, + "fork failed: %s\n", + strerror (errno)); + return 1; } - - int st = 0; - - if (pid == 0) + if (0 == pid) { - execv (file, cmd); + /* we are the child process */ + (void) execv (file, cmd); + /* can only get here on error */ + fprintf (stderr, + "exec `%s' failed: %s\n", + file, + strerror (errno)); + _exit (1); } - else + /* keep running waitpid as long as the only error we get is 'EINTR' */ + while ( (-1 == (ret = waitpid (pid, &status, 0))) && + (errno == EINTR) ); + if (-1 == ret) { - waitpid (pid, &st, 0); + fprintf (stderr, + "waitpid failed: %s\n", + strerror (errno)); + return 1; } - return WIFEXITED (st) && (WEXITSTATUS (st) == 0); + if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status)))) + return 1; + /* child process completed and returned success, we're happy */ + return 0; } + +/** + * Main function of "gnunet-helper-hijack-dns". + * Use "-d" as the first argument to remove the firewall rules. + * The other arguments are the DNS source port to NOT affect + * by the rules, followed by the name of the virtual interface + * to redirect all of the remaining DNS traffic to. + * + * @param argc number of arguments + * @param argv ["-d"] PORT VTUN + * @return 0 on success, otherwise code indicating type of error: + * 1 wrong number of arguments + * 2 invalid port number + * 3 iptables not executable + * 4 ip not executable + * 8 failed to change routing table, cleanup successfull + * 16-31 failed to undo some changes to routing table + * 31-47 failed to fully change routing table and then might have failed to undo everything + */ int -main (int argc, char **argv) +main (int argc, char *const*argv) { - int delete = 0; - int port = 0; + int delete; + unsigned int port; char *virt_dns; + char localport[6]; + int r; + /* check command-line arguments */ if (argc < 3) - return GNUNET_SYSERR; - - if (strncmp (argv[1], "-d", 2) == 0) { - if (argc < 3) - return GNUNET_SYSERR; - delete = 1; - port = atoi (argv[2]); - virt_dns = argv[3]; + fprintf (stderr, + "Syntax: gnunet-helper-hijack-dns [-d] PORT INTERFACENAME\n"); + return 1; } + if (0 == strcmp (argv[1], "-d")) + delete = 1; else + delete = 0; + if (argc != 3 + delete) { - port = atoi (argv[1]); - virt_dns = argv[2]; + fprintf (stderr, + "Syntax: gnunet-helper-hijack-dns [-d] PORT INTERFACENAME\n"); + return 1; + } + port = atoi (argv[1 + delete]); + virt_dns = argv[2 + delete]; + if ( (port == 0) || (port >= 65536) ) + { + fprintf (stderr, + "Port `%u' is invalid\n", + port); + return 2; } - - if (port == 0) - return GNUNET_SYSERR; - - struct stat s; - - if (stat ("/sbin/iptables", &s) < 0) + /* verify that the binaries were care about are executable */ + if (0 != access (SBIN_IPTABLES, X_OK)) { - fprintf (stderr, "stat on /sbin/iptables failed: %s\n", strerror (errno)); - return GNUNET_SYSERR; + fprintf (stderr, + "`%s' is not executable: %s\n", + SBIN_IPTABLES, + strerror (errno)); + return 3; } - if (stat ("/sbin/ip", &s) < 0) + if (0 != access (SBIN_IP, X_OK)) { - fprintf (stderr, "stat on /sbin/ip failed: %s\n", strerror (errno)); - return GNUNET_SYSERR; + fprintf (stderr, + "`%s' is not executable: %s\n", + SBIN_IP, + strerror (errno)); + return 4; } - char localport[7]; - - snprintf (localport, 7, "%d", port); + /* print port number to string for command-line use*/ + (void) snprintf (localport, + sizeof (localport), + "%u", + port); - int r; - - if (delete) + /* update routing tables -- this is why we are SUID! */ + if (! delete) { -e4: - r = fork_and_exec ("/sbin/ip", (char *[]) - { - "ip", "route", "del", "default", "via", virt_dns, - "table", "2", NULL}); -e3: - r = fork_and_exec ("/sbin/ip", (char *[]) - { - "ip", "rule", "del", "fwmark", "3", "table", "2", NULL}); -e2: - r = fork_and_exec ("/sbin/iptables", (char *[]) - { - "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", - "--dport", "53", "-j", "MARK", "--set-mark", "3", NULL}); -e1: - r = fork_and_exec ("/sbin/iptables", (char *[]) - { - "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", - "--sport", localport, "--dport", "53", "-j", "ACCEPT", - NULL}); - if (!delete) - r = 0; + /* Forward everything from the given local port (with destination + to port 53, and only for UDP) without hijacking */ + { + char *const mangle_args[] = + { + "iptables", "-t", "mangle", "-I", "OUTPUT", "1", "-p", + "udp", "--sport", localport, "--dport", DNS_PORT, "-j", + "ACCEPT", NULL + }; + if (0 != fork_and_exec (SBIN_IPTABLES, mangle_args)) + goto cleanup_mangle_1; + } + /* Mark all of the other DNS traffic using our mark DNS_MARK */ + { + char *const mark_args[] = + { + "iptables", "-t", "mangle", "-I", "OUTPUT", DNS_TABLE, "-p", + "udp", "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, + NULL + }; + if (0 != fork_and_exec (SBIN_IPTABLES, mark_args)) + goto cleanup_mark_2; + } + /* Forward all marked DNS traffic to our DNS_TABLE */ + { + char *const forward_args[] = + { + "ip", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL + }; + if (0 != fork_and_exec (SBIN_IP, forward_args)) + goto cleanup_forward_3; + } + /* Finally, add rule in our forwarding table to pass to our virtual interface */ + { + char *const route_args[] = + { + "ip", "route", "add", "default", "via", virt_dns, + "table", DNS_TABLE, NULL + }; + if (0 != fork_and_exec (SBIN_IP, route_args)) + goto cleanup_route_4; + } } else { - r = fork_and_exec ("/sbin/iptables", (char *[]) - { - "iptables", "-t", "mangle", "-I", "OUTPUT", "1", "-p", - "udp", "--sport", localport, "--dport", "53", "-j", - "ACCEPT", NULL}); - if (!r) - goto e1; - r = fork_and_exec ("/sbin/iptables", (char *[]) - { - "iptables", "-t", "mangle", "-I", "OUTPUT", "2", "-p", - "udp", "--dport", "53", "-j", "MARK", "--set-mark", "3", - NULL}); - if (!r) - goto e2; - r = fork_and_exec ("/sbin/ip", (char *[]) - { - "ip", "rule", "add", "fwmark", "3", "table", "2", NULL}); - if (!r) - goto e3; - r = fork_and_exec ("/sbin/ip", (char *[]) - { - "ip", "route", "add", "default", "via", virt_dns, - "table", "2", NULL}); - if (!r) - goto e4; - } - if (r) - return GNUNET_YES; - return GNUNET_SYSERR; + r = 0; + /* delete or clean-up-on-error case */ +cleanup_route_4: + { + char *const route_clean_args[] = + { + "ip", "route", "del", "default", "via", virt_dns, + "table", DNS_TABLE, NULL + }; + if (0 != fork_and_exec (SBIN_IP, route_clean_args)) + r += 1; + } +cleanup_forward_3: + { + char *const forward_clean_args[] = + { + "ip", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL + }; + if (0 != fork_and_exec (SBIN_IP, forward_clean_args)) + r += 2; + } +cleanup_mark_2: + { + char *const mark_clean_args[] = + { + "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", + "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL + }; + if (0 != fork_and_exec (SBIN_IPTABLES, mark_clean_args)) + r += 4; + } +cleanup_mangle_1: + { + char *const mangle_clean_args[] = + { + "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", + "--sport", localport, "--dport", DNS_PORT, "-j", "ACCEPT", + NULL + }; + if (0 != fork_and_exec (SBIN_IPTABLES, mangle_clean_args)) + r += 8; + } + if (r != 0) + { + if (delete) + return 16 + r; /* failed to delete */ + return 32 + r; /* first failed to install, then also failed to clean up! */ + } + if (! delete) + { + /* got here via goto to clean up handler, failed to install, succeeded with clean up */ + return 8; + } + } + /* success ! */ + return 0; } + +/* end of gnunet-helper-hijack-dns.c */