Commit message: Don't get confused by UDP packets with a source port that is zero

This is a fix for rhbz 475394.

Problem identified by Hugo Dias.
CVE: CVE-2008-5081
CWE: CWE-399
Vulnerability description: The originates_from_local_legacy_unicast_socket function (avahi-core/server.c) in avahi-daemon in Avahi before 0.6.24 allows remote attackers to cause a denial of service (crash) via a crafted mDNS packet with a source port of 0, which triggers an assertion failure.

Project: avahi
File: server.c

static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, const AvahiAddress *src_address, uint16_t port, const AvahiAddress *dst_address, AvahiIfIndex iface, int ttl) {
    AvahiInterface *i;
    int from_local_iface = 0;

    assert(s);
    assert(p);
    assert(src_address);
    assert(dst_address);
    assert(iface > 0);
    assert(src_address->proto == dst_address->proto);

    if (!(i = avahi_interface_monitor_get_interface(s->monitor, iface, src_address->proto)) ||
        !i->announcing) {
        avahi_log_warn("Received packet from invalid interface.");
        return;
    }

    if (port <= 0) {
        /* This fixes RHBZ #475394 */
        avahi_log_warn("Received packet from invalid source port.");
        return;
    }

    if (avahi_address_is_ipv4_in_ipv6(src_address))
        /* This is an IPv4 address encapsulated in IPv6, so let's ignore it. */
        return;

    if (originates_from_local_legacy_unicast_socket(s, src_address, port))
        /* This originates from our local reflector, so let's ignore it */
        return;

    /* We don't want to reflect local traffic, so we check if this packet is generated locally. */
    if (s->config.enable_reflector)
        from_local_iface = originates_from_local_iface(s, iface, src_address, port);

    if (avahi_dns_packet_check_valid_multicast(p) < 0) {
        avahi_log_warn("Received invalid packet.");
        return;
    }

    if (avahi_dns_packet_is_query(p)) {
        int legacy_unicast = 0;

        if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT) != 0) {
            avahi_log_warn("Invalid query packet.");
            return;
        }

        if (port != AVAHI_MDNS_PORT) {
            /* Legacy Unicast */

            if ((avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) != 0 ||
                 avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) != 0)) {
                avahi_log_warn("Invalid legacy unicast query packet.");
                return;
            }

            legacy_unicast = 1;
        }

        if (legacy_unicast)
            reflect_legacy_unicast_query_packet(s, p, i, src_address, port);

        handle_query_packet(s, p, i, src_address, port, legacy_unicast, from_local_iface);

    } else {
        char t[AVAHI_ADDRESS_STR_MAX];

        if (port != AVAHI_MDNS_PORT) {
            avahi_log_warn("Received response from host %s with invalid source port %u on interface '%s.%i'", avahi_address_snprint(t, sizeof(t), src_address), port, i->hardware->name, i->protocol);
            return;
        }

        if (ttl != 255 && s->config.check_response_ttl) {
            avahi_log_warn("Received response from host %s with invalid TTL %u on interface '%s.%i'.", avahi_address_snprint(t, sizeof(t), src_address), ttl, i->hardware->name, i->protocol);
            return;
        }

        if (!is_mdns_mcast_address(dst_address) &&
            !avahi_interface_address_on_link(i, src_address)) {

            avahi_log_warn("Received non-local response from host %s on interface '%s.%i'.", avahi_address_snprint(t, sizeof(t), src_address), i->hardware->name, i->protocol);
            return;
        }

        if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT) != 0 ||
            avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0 ||
            avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) != 0) {

            avahi_log_warn("Invalid response packet from host %s.", avahi_address_snprint(t, sizeof(t), src_address));
            return;
        }

        handle_response_packet(s, p, i, src_address, from_local_iface);
    }
}

Project: avahi
File: server.c

static void dispatch_legacy_unicast_packet(AvahiServer *s, AvahiDnsPacket *p) {
    AvahiInterface *j;
    AvahiLegacyUnicastReflectSlot *slot;

    assert(s);
    assert(p);

    if (avahi_dns_packet_check_valid(p) < 0 || avahi_dns_packet_is_query(p)) {
        avahi_log_warn("Received invalid packet.");
        return;
    }

    if (!(slot = find_slot(s, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID)))) {
        avahi_log_warn("Received legacy unicast response with unknown id");
        return;
    }

    if (!(j = avahi_interface_monitor_get_interface(s->monitor, slot->interface, slot->address.proto)) ||
        !j->announcing)
        return;

    /* Patch the original ID into this response */
    avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->original_id);

    /* Forward the response to the correct client */
    avahi_interface_send_packet_unicast(j, p, &slot->address, slot->port);

    /* Undo changes to packet */
    avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->id);
}

Project: avahi
File: server.c

int avahi_server_set_host_name(AvahiServer *s, const char *host_name) {
    char *hn = NULL;
    assert(s);

    AVAHI_CHECK_VALIDITY(s, !host_name || avahi_is_valid_host_name(host_name), AVAHI_ERR_INVALID_HOST_NAME);

    if (!host_name) {
        hn = avahi_get_host_name_strdup();
        hn[strcspn(hn, ".")] = 0;
        host_name = hn;
    }

    if (avahi_domain_equal(s->host_name, host_name) && s->state != AVAHI_SERVER_COLLISION) {
        avahi_free(hn);
        return avahi_server_set_errno(s, AVAHI_ERR_NO_CHANGE);
    }

    withdraw_host_rrs(s);

    avahi_free(s->host_name);
    s->host_name = hn ? hn : avahi_strdup(host_name);

    update_fqdn(s);

    register_stuff(s);
    return AVAHI_OK;
}

Project: avahi
File: server.c

int avahi_server_set_domain_name(AvahiServer *s, const char *domain_name) {
    char *dn = NULL;
    assert(s);

    AVAHI_CHECK_VALIDITY(s, !domain_name || avahi_is_valid_domain_name(domain_name), AVAHI_ERR_INVALID_DOMAIN_NAME);

    if (!domain_name) {
        dn = avahi_strdup("local");
        domain_name = dn;
    }

    if (avahi_domain_equal(s->domain_name, domain_name)) {
        avahi_free(dn);
        return avahi_server_set_errno(s, AVAHI_ERR_NO_CHANGE);
    }

    withdraw_host_rrs(s);

    avahi_free(s->domain_name);
    s->domain_name = avahi_normalize_name_strdup(domain_name);
    update_fqdn(s);

    register_stuff(s);

    avahi_free(dn);
    return AVAHI_OK;
}

Project: avahi
File: server.c

AvahiServerConfig* avahi_server_config_init(AvahiServerConfig *c) {
    assert(c);

    memset(c, 0, sizeof(AvahiServerConfig));
    c->use_ipv6 = 1;
    c->use_ipv4 = 1;
    c->host_name = NULL;
    c->domain_name = NULL;
    c->check_response_ttl = 0;
    c->publish_hinfo = 1;
    c->publish_addresses = 1;
    c->publish_workstation = 1;
    c->publish_domain = 1;
    c->use_iff_running = 0;
    c->enable_reflector = 0;
    c->reflect_ipv = 0;
    c->add_service_cookie = 0;
    c->enable_wide_area = 0;
    c->n_wide_area_servers = 0;
    c->disallow_other_stacks = 0;
    c->browse_domains = NULL;
    c->disable_publishing = 0;
    c->allow_point_to_point = 0;
    c->publish_aaaa_on_ipv4 = 1;
    c->publish_a_on_ipv6 = 0;

    return c;
}

Project: avahi
File: server.c

static void legacy_unicast_socket_event(AvahiWatch *w, int fd, AvahiWatchEvent events, void *userdata) {
    AvahiServer *s = userdata;
    AvahiDnsPacket *p = NULL;

    assert(w);
    assert(fd >= 0);
    assert(events & AVAHI_WATCH_IN);

    if (fd == s->fd_legacy_unicast_ipv4)
        p = avahi_recv_dns_packet_ipv4(s->fd_legacy_unicast_ipv4, NULL, NULL, NULL, NULL, NULL);
    else {
        assert(fd == s->fd_legacy_unicast_ipv6);
        p = avahi_recv_dns_packet_ipv6(s->fd_legacy_unicast_ipv6, NULL, NULL, NULL, NULL, NULL);
    }

    if (p) {
        dispatch_legacy_unicast_packet(s, p);
        avahi_dns_packet_free(p);

        cleanup_dead(s);
    }
}

Project: avahi
File: server.c

static void handle_response_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, int from_local_iface) {
    unsigned n;

    assert(s);
    assert(p);
    assert(i);
    assert(a);

    for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) +
             avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT); n > 0; n--) {
        AvahiRecord *record;
        int cache_flush = 0;
/*         char *txt; */

        if (!(record = avahi_dns_packet_consume_record(p, &cache_flush))) {
            avahi_log_warn(__FILE__": Packet too short or invalid while reading response record. (Maybe a UTF-8 problem?)");
            break;
        }

        if (!avahi_key_is_pattern(record->key)) {

            if (handle_conflict(s, i, record, cache_flush)) {
                if (!from_local_iface)
                    reflect_response(s, i, record, cache_flush);
                avahi_cache_update(i->cache, record, cache_flush, a);
                avahi_response_scheduler_incoming(i->response_scheduler, record, cache_flush);
            }
        }

        avahi_record_unref(record);
    }

    /* If the incoming response contained a conflicting record, some
       records have been scheduling for sending. We need to flush them
       here. */
    if (!avahi_record_list_is_empty(s->record_list))
        avahi_server_generate_response(s, i, NULL, NULL, 0, 0, 1);
}

Project: avahi
File: server.c

AvahiServerConfig* avahi_server_config_copy(AvahiServerConfig *ret, const AvahiServerConfig *c) {
    char *d = NULL, *h = NULL;
    AvahiStringList *l = NULL;
    assert(ret);
    assert(c);

    if (c->host_name)
        if (!(h = avahi_strdup(c->host_name)))
            return NULL;

    if (c->domain_name)
        if (!(d = avahi_strdup(c->domain_name))) {
            avahi_free(h);
            return NULL;
        }

    if (!(l = avahi_string_list_copy(c->browse_domains)) && c->browse_domains) {
        avahi_free(h);
        avahi_free(d);
        return NULL;
    }

    *ret = *c;
    ret->host_name = h;
    ret->domain_name = d;
    ret->browse_domains = l;

    return ret;
}

Project: avahi
File: server.c

static void register_stuff(AvahiServer *s) {
    assert(s);

    server_set_state(s, AVAHI_SERVER_REGISTERING);
    s->n_host_rr_pending ++; /** Make sure that the state isn't changed tp AVAHI_SERVER_RUNNING too early */

    register_hinfo(s);
    register_browse_domain(s);
    avahi_interface_monitor_update_rrs(s->monitor, 0);

    s->n_host_rr_pending --;

    if (s->n_host_rr_pending == 0)
        server_set_state(s, AVAHI_SERVER_RUNNING);
}

Project: avahi
File: server.c

int avahi_server_set_browse_domains(AvahiServer *s, AvahiStringList *domains) {
    AvahiStringList *l;

    assert(s);

    for (l = s->config.browse_domains; l; l = l->next)
        if (!avahi_is_valid_domain_name((char*) l->text))
            return avahi_server_set_errno(s, AVAHI_ERR_INVALID_DOMAIN_NAME);

    avahi_string_list_free(s->config.browse_domains);
    s->config.browse_domains = avahi_string_list_copy(domains);

    return AVAHI_OK;
}

Project: avahi
File: server.c

AvahiServer *avahi_server_new(const AvahiPoll *poll_api, const AvahiServerConfig *sc, AvahiServerCallback callback, void* userdata, int *error) {
    AvahiServer *s;
    int e;

    if (sc && (e = valid_server_config(sc)) < 0) {
        if (error)
            *error = e;
        return NULL;
    }

    if (!(s = avahi_new(AvahiServer, 1))) {
        if (error)
            *error = AVAHI_ERR_NO_MEMORY;

        return NULL;
    }

    s->poll_api = poll_api;

    if (sc)
        avahi_server_config_copy(&s->config, sc);
    else
        avahi_server_config_init(&s->config);

    if ((e = setup_sockets(s)) < 0) {
        if (error)
            *error = e;

        avahi_server_config_free(&s->config);
        avahi_free(s);

        return NULL;
    }

    s->n_host_rr_pending = 0;
    s->need_entry_cleanup = 0;
    s->need_group_cleanup = 0;
    s->need_browser_cleanup = 0;
    s->hinfo_entry_group = NULL;
    s->browse_domain_entry_group = NULL;
    s->error = AVAHI_OK;
    s->state = AVAHI_SERVER_INVALID;

    s->callback = callback;
    s->userdata = userdata;

    s->time_event_queue = avahi_time_event_queue_new(poll_api);

    s->entries_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL);
    AVAHI_LLIST_HEAD_INIT(AvahiEntry, s->entries);
    AVAHI_LLIST_HEAD_INIT(AvahiGroup, s->groups);

    s->record_browser_hashmap = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL);
    AVAHI_LLIST_HEAD_INIT(AvahiSRecordBrowser, s->record_browsers);
    AVAHI_LLIST_HEAD_INIT(AvahiSHostNameResolver, s->host_name_resolvers);
    AVAHI_LLIST_HEAD_INIT(AvahiSAddressResolver, s->address_resolvers);
    AVAHI_LLIST_HEAD_INIT(AvahiSDomainBrowser, s->domain_browsers);
    AVAHI_LLIST_HEAD_INIT(AvahiSServiceTypeBrowser, s->service_type_browsers);
    AVAHI_LLIST_HEAD_INIT(AvahiSServiceBrowser, s->service_browsers);
    AVAHI_LLIST_HEAD_INIT(AvahiSServiceResolver, s->service_resolvers);
    AVAHI_LLIST_HEAD_INIT(AvahiSDNSServerBrowser, s->dns_server_browsers);

    s->legacy_unicast_reflect_slots = NULL;
    s->legacy_unicast_reflect_id = 0;

    s->record_list = avahi_record_list_new();

    /* Get host name */
    s->host_name = s->config.host_name ? avahi_normalize_name_strdup(s->config.host_name) : avahi_get_host_name_strdup();
    s->host_name[strcspn(s->host_name, ".")] = 0;
    s->domain_name = s->config.domain_name ? avahi_normalize_name_strdup(s->config.domain_name) : avahi_strdup("local");
    s->host_name_fqdn = NULL;
    update_fqdn(s);

    do {
        s->local_service_cookie = (uint32_t) rand() * (uint32_t) rand();
    } while (s->local_service_cookie == AVAHI_SERVICE_COOKIE_INVALID);

    if (s->config.enable_wide_area) {
        s->wide_area_lookup_engine = avahi_wide_area_engine_new(s);
        avahi_wide_area_set_servers(s->wide_area_lookup_engine, s->config.wide_area_servers, s->config.n_wide_area_servers);
    } else
        s->wide_area_lookup_engine = NULL;

    s->multicast_lookup_engine = avahi_multicast_lookup_engine_new(s);

    s->monitor = avahi_interface_monitor_new(s);
    avahi_interface_monitor_sync(s->monitor);

    register_localhost(s);
    register_stuff(s);

    return s;
}

Project: avahi
File: server.c

static void mcast_socket_event(AvahiWatch *w, int fd, AvahiWatchEvent events, void *userdata) {
    AvahiServer *s = userdata;
    AvahiAddress dest, src;
    AvahiDnsPacket *p = NULL;
    AvahiIfIndex iface;
    uint16_t port;
    uint8_t ttl;

    assert(w);
    assert(fd >= 0);
    assert(events & AVAHI_WATCH_IN);

    if (fd == s->fd_ipv4) {
        dest.proto = src.proto = AVAHI_PROTO_INET;
        p = avahi_recv_dns_packet_ipv4(s->fd_ipv4, &src.data.ipv4, &port, &dest.data.ipv4, &iface, &ttl);
    } else {
        assert(fd == s->fd_ipv6);
        dest.proto = src.proto = AVAHI_PROTO_INET6;
        p = avahi_recv_dns_packet_ipv6(s->fd_ipv6, &src.data.ipv6, &port, &dest.data.ipv6, &iface, &ttl);
    }

    if (p) {
        if (iface == AVAHI_IF_UNSPEC)
            iface = avahi_find_interface_for_address(s->monitor, &dest);

        if (iface != AVAHI_IF_UNSPEC)
            dispatch_packet(s, p, &src, port, &dest, iface, ttl);
        else
            avahi_log_error("Incoming packet recieved on address that isn't local.");

        avahi_dns_packet_free(p);

        cleanup_dead(s);
    }
}

Project: avahi
File: server.c

void avahi_server_free(AvahiServer* s) {
    assert(s);

    /* Remove all browsers */

    while (s->dns_server_browsers)
        avahi_s_dns_server_browser_free(s->dns_server_browsers);
    while (s->host_name_resolvers)
        avahi_s_host_name_resolver_free(s->host_name_resolvers);
    while (s->address_resolvers)
        avahi_s_address_resolver_free(s->address_resolvers);
    while (s->domain_browsers)
        avahi_s_domain_browser_free(s->domain_browsers);
    while (s->service_type_browsers)
        avahi_s_service_type_browser_free(s->service_type_browsers);
    while (s->service_browsers)
        avahi_s_service_browser_free(s->service_browsers);
    while (s->service_resolvers)
        avahi_s_service_resolver_free(s->service_resolvers);
    while (s->record_browsers)
        avahi_s_record_browser_destroy(s->record_browsers);

    /* Remove all locally rgeistered stuff */

    while(s->entries)
        avahi_entry_free(s, s->entries);

    avahi_interface_monitor_free(s->monitor);

    while (s->groups)
        avahi_entry_group_free(s, s->groups);

    free_slots(s);

    avahi_hashmap_free(s->entries_by_key);
    avahi_record_list_free(s->record_list);
    avahi_hashmap_free(s->record_browser_hashmap);

    if (s->wide_area_lookup_engine)
        avahi_wide_area_engine_free(s->wide_area_lookup_engine);
    avahi_multicast_lookup_engine_free(s->multicast_lookup_engine);

    avahi_time_event_queue_free(s->time_event_queue);

    /* Free watches */

    if (s->watch_ipv4)
        s->poll_api->watch_free(s->watch_ipv4);
    if (s->watch_ipv6)
        s->poll_api->watch_free(s->watch_ipv6);

    if (s->watch_legacy_unicast_ipv4)
        s->poll_api->watch_free(s->watch_legacy_unicast_ipv4);
    if (s->watch_legacy_unicast_ipv6)
        s->poll_api->watch_free(s->watch_legacy_unicast_ipv6);

    /* Free sockets */

    if (s->fd_ipv4 >= 0)
        close(s->fd_ipv4);
    if (s->fd_ipv6 >= 0)
        close(s->fd_ipv6);

    if (s->fd_legacy_unicast_ipv4 >= 0)
        close(s->fd_legacy_unicast_ipv4);
    if (s->fd_legacy_unicast_ipv6 >= 0)
        close(s->fd_legacy_unicast_ipv6);

    /* Free other stuff */

    avahi_free(s->host_name);
    avahi_free(s->domain_name);
    avahi_free(s->host_name_fqdn);

    avahi_server_config_free(&s->config);

    avahi_free(s);
}

Project: avahi
File: server.c

static void* reflect_cache_walk_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void* userdata) {
    AvahiServer *s = userdata;

    assert(c);
    assert(pattern);
    assert(e);
    assert(s);

    avahi_record_list_push(s->record_list, e->record, e->cache_flush, 0, 0);
    return NULL;
}

Project: avahi
File: server.c

void avahi_server_config_free(AvahiServerConfig *c) {
    assert(c);

    avahi_free(c->host_name);
    avahi_free(c->domain_name);
    avahi_string_list_free(c->browse_domains);
}

Project: avahi
File: server.c

static void handle_query_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, uint16_t port, int legacy_unicast, int from_local_iface) {
    size_t n;
    int is_probe;

    assert(s);
    assert(p);
    assert(i);
    assert(a);

    assert(avahi_record_list_is_empty(s->record_list));

    is_probe = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) > 0;

    /* Handle the questions */
    for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT); n > 0; n --) {
        AvahiKey *key;
        int unicast_response = 0;

        if (!(key = avahi_dns_packet_consume_key(p, &unicast_response))) {
            avahi_log_warn(__FILE__": Packet too short or invalid while reading question key. (Maybe a UTF-8 problem?)");
            goto fail;
        }

        if (!legacy_unicast && !from_local_iface) {
            reflect_query(s, i, key);
            if (!unicast_response)
              avahi_cache_start_poof(i->cache, key, a);
        }

        if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0 &&
            !(avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & AVAHI_DNS_FLAG_TC))
            /* Allow our own queries to be suppressed by incoming
             * queries only when they do not include known answers */
            avahi_query_scheduler_incoming(i->query_scheduler, key);

        avahi_server_prepare_matching_responses(s, i, key, unicast_response);
        avahi_key_unref(key);
    }

    if (!legacy_unicast) {

        /* Known Answer Suppression */
        for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT); n > 0; n --) {
            AvahiRecord *record;
            int unique = 0;

            if (!(record = avahi_dns_packet_consume_record(p, &unique))) {
                avahi_log_warn(__FILE__": Packet too short or invalid while reading known answer record. (Maybe a UTF-8 problem?)");
                goto fail;
            }

            avahi_response_scheduler_suppress(i->response_scheduler, record, a);
            avahi_record_list_drop(s->record_list, record);
            avahi_cache_stop_poof(i->cache, record, a);

            avahi_record_unref(record);
        }

        /* Probe record */
        for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT); n > 0; n --) {
            AvahiRecord *record;
            int unique = 0;

            if (!(record = avahi_dns_packet_consume_record(p, &unique))) {
                avahi_log_warn(__FILE__": Packet too short or invalid while reading probe record. (Maybe a UTF-8 problem?)");
                goto fail;
            }

            if (!avahi_key_is_pattern(record->key)) {
                if (!from_local_iface)
                    reflect_probe(s, i, record);
                incoming_probe(s, record, i);
            }

            avahi_record_unref(record);
        }
    }

    if (!avahi_record_list_is_empty(s->record_list))
        avahi_server_generate_response(s, i, p, a, port, legacy_unicast, is_probe);

    return;

fail:
    avahi_record_list_flush(s->record_list);
}

Project: avahi
File: server.c

static void cleanup_dead(AvahiServer *s) {
    assert(s);

    avahi_cleanup_dead_entries(s);
    avahi_browser_cleanup(s);
}