Merge "Fix crash caused by decryption response delay" into tizen
[platform/upstream/connman.git] / src / dnsproxy.c
index 8bda3c7..625ec06 100755 (executable)
@@ -472,7 +472,7 @@ static void send_cached_response(int sk, unsigned char *buf, int len,
        hdr->nscount = 0;
        hdr->arcount = 0;
 
-       /* if this is a negative reply, we are authorative */
+       /* if this is a negative reply, we are authoritative */
        if (answers == 0)
                hdr->aa = 1;
        else
@@ -1819,6 +1819,7 @@ static char *uncompress(int16_t field_count, char *start, char *end,
                        char **uncompressed_ptr)
 {
        char *uptr = *uncompressed_ptr; /* position in result buffer */
+       char * const uncomp_end = uncompressed + uncomp_len - 1;
 
        debug("count %d ptr %p end %p uptr %p", field_count, ptr, end, uptr);
 
@@ -1839,14 +1840,16 @@ static char *uncompress(int16_t field_count, char *start, char *end,
                 * tmp buffer.
                 */
 
-               ulen = strlen(name);
-               strncpy(uptr, name, uncomp_len - (uptr - uncompressed));
+               ulen = strlen(name) + 1;
+               if ((uptr + ulen) > uncomp_end)
+                       goto out;
+
+               memcpy(uptr, name, ulen);
 
                debug("pos %d ulen %d left %d name %s", pos, ulen,
-                       (int)(uncomp_len - (uptr - uncompressed)), uptr);
+                       (int)(uncomp_end - (uptr + ulen)), uptr);
 
                uptr += ulen;
-               *uptr++ = '\0';
 
                ptr += pos;
 
@@ -1854,6 +1857,10 @@ static char *uncompress(int16_t field_count, char *start, char *end,
                 * We copy also the fixed portion of the result (type, class,
                 * ttl, address length and the address)
                 */
+               if ((uptr + NS_RRFIXEDSZ) > uncomp_end) {
+                       debug("uncompressed data too large for buffer");
+                       goto out;
+               }
                memcpy(uptr, ptr, NS_RRFIXEDSZ);
 
                dns_type = uptr[0] << 8 | uptr[1];
@@ -1885,7 +1892,7 @@ static char *uncompress(int16_t field_count, char *start, char *end,
                } else if (dns_type == ns_t_a || dns_type == ns_t_aaaa) {
                        dlen = uptr[-2] << 8 | uptr[-1];
 
-                       if (ptr + dlen > end) {
+                       if ((ptr + dlen) > end || (uptr + dlen) > uncomp_end) {
                                debug("data len %d too long", dlen);
                                goto out;
                        }
@@ -1924,6 +1931,10 @@ static char *uncompress(int16_t field_count, char *start, char *end,
                         * refresh interval, retry interval, expiration
                         * limit and minimum ttl). They are 20 bytes long.
                         */
+                       if ((uptr + 20) > uncomp_end || (ptr + 20) > end) {
+                               debug("soa record too long");
+                               goto out;
+                       }
                        memcpy(uptr, ptr, 20);
                        uptr += 20;
                        ptr += 20;
@@ -1993,6 +2004,12 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
 
        if (offset < 0)
                return offset;
+       if (reply_len < 0)
+               return -EINVAL;
+       if (reply_len < offset + 1)
+               return -EINVAL;
+       if ((size_t)reply_len < sizeof(struct domain_hdr))
+               return -EINVAL;
 
        hdr = (void *)(reply + offset);
        dns_id = reply[offset] | reply[offset + 1] << 8;
@@ -2028,23 +2045,31 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                 */
                if (req->append_domain && ntohs(hdr->qdcount) == 1) {
                        uint16_t domain_len = 0;
-                       uint16_t header_len;
+                       uint16_t header_len, payload_len;
                        uint16_t dns_type, dns_class;
                        uint8_t host_len, dns_type_pos;
                        char uncompressed[NS_MAXDNAME], *uptr;
                        char *ptr, *eom = (char *)reply + reply_len;
+                       char *domain;
 
                        /*
                         * ptr points to the first char of the hostname.
                         * ->hostname.domain.net
                         */
                        header_len = offset + sizeof(struct domain_hdr);
+                       if (reply_len < header_len)
+                               return -EINVAL;
+                       payload_len = reply_len - header_len;
+
                        ptr = (char *)reply + header_len;
 
                        host_len = *ptr;
+                       domain = ptr + 1 + host_len;
+                       if (domain > eom)
+                               return -EINVAL;
+
                        if (host_len > 0)
-                               domain_len = strnlen(ptr + 1 + host_len,
-                                               reply_len - header_len);
+                               domain_len = strnlen(domain, eom - domain);
 
                        /*
                         * If the query type is anything other than A or AAAA,
@@ -2053,6 +2078,8 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                         */
                        dns_type_pos = host_len + 1 + domain_len + 1;
 
+                       if (ptr + (dns_type_pos + 3) > eom)
+                               return -EINVAL;
                        dns_type = ptr[dns_type_pos] << 8 |
                                                        ptr[dns_type_pos + 1];
                        dns_class = ptr[dns_type_pos + 2] << 8 |
@@ -2082,6 +2109,8 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                                int new_len, fixed_len;
                                char *answers;
 
+                               if (len > payload_len)
+                                       return -EINVAL;
                                /*
                                 * First copy host (without domain name) into
                                 * tmp buffer.
@@ -2096,6 +2125,8 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                                 * Copy type and class fields of the question.
                                 */
                                ptr += len + domain_len + 1;
+                               if (ptr + NS_QFIXEDSZ > eom)
+                                       return -EINVAL;
                                memcpy(uptr, ptr, NS_QFIXEDSZ);
 
                                /*
@@ -2105,6 +2136,8 @@ static int forward_dns_reply(unsigned char *reply, int reply_len, int protocol,
                                uptr += NS_QFIXEDSZ;
                                answers = uptr;
                                fixed_len = answers - uncompressed;
+                               if (ptr + offset > eom)
+                                       return -EINVAL;
 
                                /*
                                 * We then uncompress the result to buffer
@@ -2215,6 +2248,9 @@ out:
                        err = sendto(sk, req->resp, req->resplen, 0,
                                &req->sa, req->sa_len);
        } else {
+               uint16_t tcp_len = htons(req->resplen - 2);
+               /* correct TCP message length */
+               memcpy(req->resp, &tcp_len, sizeof(tcp_len));
                sk = req->client_sk;
                err = send(sk, req->resp, req->resplen, MSG_NOSIGNAL);
        }
@@ -2303,14 +2339,15 @@ static gboolean udp_server_event(GIOChannel *channel, GIOCondition condition,
 
        len = recv(sk, buf, sizeof(buf), 0);
 
-       if (len >= 12)
-               forward_dns_reply(buf, len, IPPROTO_UDP, data);
+       forward_dns_reply(buf, len, IPPROTO_UDP, data);
 
 #if defined TIZEN_EXT
        GSList *list;
 
-       for (list = server_list_sec; list; list = list->next) {
+       list = server_list_sec;
+       while (list) {
                struct server_data *new_data = list->data;
+               list = list->next;
 
                if (new_data == data) {
                        destroy_server_sec(data);
@@ -2398,14 +2435,21 @@ hangup:
                        }
                }
 
+               /*
+                * Remove the G_IO_OUT flag from the watch, otherwise we end
+                * up in a busy loop, because the socket is constantly writable.
+                *
+                * There seems to be no better way in g_io to do that than
+                * re-adding the watch.
+                */
+               g_source_remove(server->watch);
+               server->watch = g_io_add_watch(server->channel,
+                       G_IO_IN | G_IO_HUP | G_IO_NVAL | G_IO_ERR,
+                       tcp_server_event, server);
+
                server->connected = true;
                server_list = g_slist_append(server_list, server);
 
-               if (server->timeout > 0) {
-                       g_source_remove(server->timeout);
-                       server->timeout = 0;
-               }
-
                for (list = request_list; list; ) {
                        struct request_data *req = list->data;
                        int status;
@@ -2635,6 +2679,8 @@ static void destroy_server_sec(struct server_data *server)
 {
        GList *list;
        int fd;
+       if (server == NULL)
+               return;
 
        if (server->channel)
                fd = g_io_channel_unix_get_fd(server->channel);
@@ -2654,12 +2700,9 @@ static void destroy_server_sec(struct server_data *server)
                DBG("Removing DNS server %s", server->server);
 
        g_free(server->server);
-       for (list = server->domains; list; list = list->next) {
-               char *domain = list->data;
 
-               server->domains = g_list_remove(server->domains, domain);
-               g_free(domain);
-       }
+       g_list_free_full(server->domains, g_free);
+
        g_free(server->server_addr);
 
        /*
@@ -2683,8 +2726,10 @@ static void destroy_all_server_sec()
 
        DBG("remove all dns server");
 
-       for (list = server_list_sec; list; list = list->next) {
+       list = server_list_sec;
+       while (list) {
                struct server_data *server = list->data;
+               list = list->next;
                destroy_server_sec(server);
        }
        server_list_sec = NULL;
@@ -2940,7 +2985,7 @@ static void update_domain(int index, const char *domain, bool append)
        for (list = server_list; list; list = list->next) {
                struct server_data *data = list->data;
                GList *dom_list;
-               char *dom;
+               char *dom = NULL;
                bool dom_found = false;
 
                if (data->index < 0)
@@ -3124,6 +3169,7 @@ static void dnsproxy_default_changed(struct connman_service *service)
        bool server_enabled = false;
        GSList *list;
        int index;
+       int vpn_index;
 
        DBG("service %p", service);
 
@@ -3140,6 +3186,13 @@ static void dnsproxy_default_changed(struct connman_service *service)
        if (index < 0)
                return;
 
+       /*
+        * In case non-split-routed VPN is set as split routed the DNS servers
+        * the VPN must be enabled as well, when the transport becomes the
+        * default service.
+        */
+       vpn_index = __connman_connection_get_vpn_index(index);
+
        for (list = server_list; list; list = list->next) {
                struct server_data *data = list->data;
 
@@ -3147,6 +3200,9 @@ static void dnsproxy_default_changed(struct connman_service *service)
                        DBG("Enabling DNS server %s", data->server);
                        data->enabled = true;
                        server_enabled = true;
+               } else if (data->index == vpn_index) {
+                       DBG("Enabling DNS server of VPN %s", data->server);
+                       data->enabled = true;
                } else {
                        DBG("Disabling DNS server %s", data->server);
                        data->enabled = false;
@@ -3159,10 +3215,46 @@ static void dnsproxy_default_changed(struct connman_service *service)
        cache_refresh();
 }
 
+static void dnsproxy_service_state_changed(struct connman_service *service,
+                       enum connman_service_state state)
+{
+       GSList *list;
+       int index;
+
+       switch (state) {
+       case CONNMAN_SERVICE_STATE_DISCONNECT:
+       case CONNMAN_SERVICE_STATE_IDLE:
+               break;
+       case CONNMAN_SERVICE_STATE_ASSOCIATION:
+       case CONNMAN_SERVICE_STATE_CONFIGURATION:
+       case CONNMAN_SERVICE_STATE_FAILURE:
+       case CONNMAN_SERVICE_STATE_ONLINE:
+       case CONNMAN_SERVICE_STATE_READY:
+       case CONNMAN_SERVICE_STATE_UNKNOWN:
+               return;
+       }
+
+       index = __connman_service_get_index(service);
+       list = server_list;
+
+       while (list) {
+               struct server_data *data = list->data;
+
+               /* Get next before the list is changed by destroy_server() */
+               list = list->next;
+
+               if (data->index == index) {
+                       DBG("removing server data of index %d", index);
+                       destroy_server(data);
+               }
+       }
+}
+
 static const struct connman_notifier dnsproxy_notifier = {
        .name                   = "dnsproxy",
        .default_changed        = dnsproxy_default_changed,
        .offline_mode           = dnsproxy_offline_mode,
+       .service_state_changed  = dnsproxy_service_state_changed,
 };
 
 static const unsigned char opt_edns0_type[2] = { 0x00, 0x29 };