gresolver: More robust parsing of DNS responses

* Handle truncated responses, and invalid names

https://bugzilla.gnome.org/show_bug.cgi?id=675966
This commit is contained in:
Stef Walter 2012-05-13 07:44:57 +02:00
parent 49e5075707
commit 8ed955ceba

View File

@ -994,18 +994,47 @@ _g_resolver_name_from_nameinfo (GInetAddress *address,
} }
#if defined(G_OS_UNIX) #if defined(G_OS_UNIX)
static gboolean
parse_short (guchar **p,
guchar *end,
guint16 *value)
{
if (*p + 2 > end)
return FALSE;
GETSHORT (*value, *p);
return TRUE;
}
static gboolean
parse_long (guchar **p,
guchar *end,
guint32 *value)
{
if (*p + 4 > end)
return FALSE;
GETLONG (*value, *p);
return TRUE;
}
static GVariant * static GVariant *
parse_res_srv (guchar *answer, parse_res_srv (guchar *answer,
guchar *end, guchar *end,
guchar **p) guchar *p)
{ {
gchar namebuf[1024]; gchar namebuf[1024];
guint16 priority, weight, port; guint16 priority, weight, port;
gint n;
GETSHORT (priority, *p); if (!parse_short (&p, end, &priority) ||
GETSHORT (weight, *p); !parse_short (&p, end, &weight) ||
GETSHORT (port, *p); !parse_short (&p, end, &port))
*p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf)); return NULL;
n = dn_expand (answer, end, p, namebuf, sizeof (namebuf));
if (n < 0)
return NULL;
*p += n;
return g_variant_new ("(qqqs)", return g_variant_new ("(qqqs)",
priority, priority,
@ -1017,20 +1046,29 @@ parse_res_srv (guchar *answer,
static GVariant * static GVariant *
parse_res_soa (guchar *answer, parse_res_soa (guchar *answer,
guchar *end, guchar *end,
guchar **p) guchar *p)
{ {
gchar mnamebuf[1024]; gchar mnamebuf[1024];
gchar rnamebuf[1024]; gchar rnamebuf[1024];
guint32 serial, refresh, retry, expire, ttl; guint32 serial, refresh, retry, expire, ttl;
gint n;
*p += dn_expand (answer, end, *p, mnamebuf, sizeof (mnamebuf)); n = dn_expand (answer, end, p, mnamebuf, sizeof (mnamebuf));
*p += dn_expand (answer, end, *p, rnamebuf, sizeof (rnamebuf)); if (n < 0)
return NULL;
p += n;
GETLONG (serial, *p); n = dn_expand (answer, end, p, rnamebuf, sizeof (rnamebuf));
GETLONG (refresh, *p); if (n < 0)
GETLONG (retry, *p); return NULL;
GETLONG (expire, *p); p += n;
GETLONG (ttl, *p);
if (!parse_long (&p, end, &serial) ||
!parse_long (&p, end, &refresh) ||
!parse_long (&p, end, &retry) ||
!parse_long (&p, end, &expire) ||
!parse_long (&p, end, &ttl))
return NULL;
return g_variant_new ("(ssuuuuu)", return g_variant_new ("(ssuuuuu)",
mnamebuf, mnamebuf,
@ -1045,11 +1083,14 @@ parse_res_soa (guchar *answer,
static GVariant * static GVariant *
parse_res_ns (guchar *answer, parse_res_ns (guchar *answer,
guchar *end, guchar *end,
guchar **p) guchar *p)
{ {
gchar namebuf[1024]; gchar namebuf[1024];
gint n;
*p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf)); n = dn_expand (answer, end, p, namebuf, sizeof (namebuf));
if (n < 0)
return NULL;
return g_variant_new ("(s)", namebuf); return g_variant_new ("(s)", namebuf);
} }
@ -1057,14 +1098,19 @@ parse_res_ns (guchar *answer,
static GVariant * static GVariant *
parse_res_mx (guchar *answer, parse_res_mx (guchar *answer,
guchar *end, guchar *end,
guchar **p) guchar *p)
{ {
gchar namebuf[1024]; gchar namebuf[1024];
guint16 preference; guint16 preference;
gint n;
GETSHORT (preference, *p); if (!parse_short (&p, end, &preference))
return NULL;
*p += dn_expand (answer, end, *p, namebuf, sizeof (namebuf)); n = dn_expand (answer, end, p, namebuf, sizeof (namebuf));
if (n < 0)
return NULL;
p += n;
return g_variant_new ("(qs)", return g_variant_new ("(qs)",
preference, preference,
@ -1074,24 +1120,22 @@ parse_res_mx (guchar *answer,
static GVariant * static GVariant *
parse_res_txt (guchar *answer, parse_res_txt (guchar *answer,
guchar *end, guchar *end,
guchar **p) guchar *p)
{ {
GVariant *record; GVariant *record;
GPtrArray *array; GPtrArray *array;
guchar *at = *p;
gsize len; gsize len;
array = g_ptr_array_new_with_free_func (g_free); array = g_ptr_array_new_with_free_func (g_free);
while (at < end) while (p < end)
{ {
len = *(at++); len = *(p++);
if (len > at - end) if (len > p - end)
break; break;
g_ptr_array_add (array, g_strndup ((gchar *)at, len)); g_ptr_array_add (array, g_strndup ((gchar *)p, len));
at += len; p += len;
} }
*p = at;
record = g_variant_new ("(@as)", record = g_variant_new ("(@as)",
g_variant_new_strv ((const gchar **)array->pdata, array->len)); g_variant_new_strv ((const gchar **)array->pdata, array->len));
g_ptr_array_free (array, TRUE); g_ptr_array_free (array, TRUE);
@ -1127,13 +1171,13 @@ _g_resolver_records_from_res_query (const gchar *rrname,
GError **error) GError **error)
{ {
gint count; gint count;
gchar namebuf[1024];
guchar *end, *p; guchar *end, *p;
guint16 type, qclass, rdlength; guint16 type, qclass, rdlength;
guint32 ttl; guint32 ttl;
HEADER *header; HEADER *header;
GList *records; GList *records;
GVariant *record; GVariant *record;
gint n, i;
if (len <= 0) if (len <= 0)
{ {
@ -1168,48 +1212,61 @@ _g_resolver_records_from_res_query (const gchar *rrname,
/* Skip query */ /* Skip query */
count = ntohs (header->qdcount); count = ntohs (header->qdcount);
while (count-- && p < end) for (i = 0; i < count && p < end; i++)
{ {
p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); n = dn_skipname (p, end);
if (n < 0)
break;
p += n;
p += 4; p += 4;
}
/* To silence gcc warnings */ /* Incomplete response */
namebuf[0] = namebuf[1]; if (i < count)
{
g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_TEMPORARY_FAILURE,
_("Incomplete data received for '%s'"), rrname);
return NULL;
} }
/* Read answers */ /* Read answers */
count = ntohs (header->ancount); count = ntohs (header->ancount);
while (count-- && p < end) for (i = 0; i < count && p < end; i++)
{ {
p += dn_expand (answer, end, p, namebuf, sizeof (namebuf)); n = dn_skipname (p, end);
GETSHORT (type, p); if (n < 0)
GETSHORT (qclass, p); break;
GETLONG (ttl, p); p += n;
if (!parse_short (&p, end, &type) ||
!parse_short (&p, end, &qclass) ||
!parse_long (&p, end, &ttl) ||
!parse_short (&p, end, &rdlength))
break;
ttl = ttl; /* To avoid -Wunused-but-set-variable */ ttl = ttl; /* To avoid -Wunused-but-set-variable */
GETSHORT (rdlength, p);
if (type != rrtype || qclass != C_IN) if (p + rdlength > end)
break;
if (type == rrtype && qclass == C_IN)
{ {
p += rdlength;
continue;
}
switch (rrtype) switch (rrtype)
{ {
case T_SRV: case T_SRV:
record = parse_res_srv (answer, end, &p); record = parse_res_srv (answer, end, p);
break; break;
case T_MX: case T_MX:
record = parse_res_mx (answer, end, &p); record = parse_res_mx (answer, end, p);
break; break;
case T_SOA: case T_SOA:
record = parse_res_soa (answer, end, &p); record = parse_res_soa (answer, end, p);
break; break;
case T_NS: case T_NS:
record = parse_res_ns (answer, end, &p); record = parse_res_ns (answer, end, p);
break; break;
case T_TXT: case T_TXT:
record = parse_res_txt (answer, p + rdlength, &p); record = parse_res_txt (answer, p + rdlength, p);
break; break;
default: default:
g_warn_if_reached (); g_warn_if_reached ();
@ -1221,6 +1278,18 @@ _g_resolver_records_from_res_query (const gchar *rrname,
records = g_list_prepend (records, record); records = g_list_prepend (records, record);
} }
p += rdlength;
}
/* Somehow got a truncated response */
if (i < count)
{
g_list_free_full (records, (GDestroyNotify)g_variant_unref);
g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_TEMPORARY_FAILURE,
_("Incomplete data received for '%s'"), rrname);
return NULL;
}
return records; return records;
} }