--- src/ne_gnutls.c +++ src/ne_gnutls.c @@ -350,7 +350,7 @@ case GNUTLS_SAN_DNSNAME: name[len] = '\0'; if (identity && !found) *identity = ne_strdup(name); - match = ne__ssl_match_hostname(name, hostname); + match = ne__ssl_match_hostname(name, len, hostname); found = 1; break; case GNUTLS_SAN_IPADDRESS: { @@ -419,7 +419,7 @@ seq, 0, name, &len); if (ret == 0) { if (identity) *identity = ne_strdup(name); - match = ne__ssl_match_hostname(name, hostname); + match = ne__ssl_match_hostname(name, len, hostname); } } else { return -1; --- src/ne_openssl.c +++ src/ne_openssl.c @@ -92,10 +92,16 @@ int len; switch (str->type) { - case V_ASN1_UTF8STRING: case V_ASN1_IA5STRING: /* definitely ASCII */ case V_ASN1_VISIBLESTRING: /* probably ASCII */ case V_ASN1_PRINTABLESTRING: /* subset of ASCII */ + ne__buffer_qappend(buf, str->data, str->length); + break; + case V_ASN1_UTF8STRING: + /* Fail for embedded NUL bytes. */ + if (strlen((char *)str->data) != (size_t)str->length) { + return -1; + } ne_buffer_append(buf, (char *)str->data, str->length); break; case V_ASN1_UNIVERSALSTRING: @@ -103,8 +109,15 @@ case V_ASN1_BMPSTRING: len = ASN1_STRING_to_UTF8(&tmp, str); if (len > 0) { - ne_buffer_append(buf, (char *)tmp, len); - OPENSSL_free(tmp); + /* Fail if there were embedded NUL bytes. */ + if (strlen((char *)tmp) != (size_t)len) { + OPENSSL_free(tmp); + return -1; + } + else { + ne_buffer_append(buf, (char *)tmp, len); + OPENSSL_free(tmp); + } break; } else { ERR_clear_error(); @@ -119,13 +132,11 @@ return 0; } -/* Returns a malloc-allocate version of IA5 string AS. Really only - * here to prevent char * vs unsigned char * type mismatches without - * losing all hope at type-safety. */ +/* Returns a malloc-allocated version of IA5 string AS, escaped for + * safety. */ static char *dup_ia5string(const ASN1_IA5STRING *as) { - unsigned char *data = as->data; - return ne_strndup((char *)data, as->length); + return ne__strnqdup(as->data, as->length); } char *ne_ssl_readable_dname(const ne_ssl_dname *name) @@ -236,7 +247,7 @@ if (nm->type == GEN_DNS) { char *name = dup_ia5string(nm->d.ia5); if (identity && !found) *identity = ne_strdup(name); - match = ne__ssl_match_hostname(name, hostname); + match = ne__ssl_match_hostname(name, strlen(name), hostname); ne_free(name); found = 1; } @@ -320,7 +331,7 @@ return -1; } if (identity) *identity = ne_strdup(cname->data); - match = ne__ssl_match_hostname(cname->data, hostname); + match = ne__ssl_match_hostname(cname->data, cname->used - 1, hostname); ne_buffer_destroy(cname); } --- src/ne_private.h +++ src/ne_private.h @@ -128,8 +128,17 @@ void ne__ssl_set_verify_err(ne_session *sess, int failures); /* Return non-zero if hostname from certificate (cn) matches hostname - * used for session (hostname); follows RFC2818 logic. cn is modified - * in-place. */ -int ne__ssl_match_hostname(char *cn, const char *hostname); + * used for session (hostname); follows RFC2818 logic. */ +int ne__ssl_match_hostname(const char *cn, size_t cnlen, const char *hostname); + +/* Return a malloc-allocated copy of 'data', of length 'len', with all + * non-ASCII bytes, and ASCII control characters escaped. (Note that + * the escaping includes the NUL byte). */ +char *ne__strnqdup(const unsigned char *data, size_t len); + +/* Append 'len' bytes of 'data' to buf. All non-ASCII bytes, and + * ASCII control characters, are escaped. (Note that this includes + * the NUL byte). */ +void ne__buffer_qappend(ne_buffer *buf, const unsigned char *data, size_t len); #endif /* HTTP_PRIVATE_H */ --- src/ne_session.c +++ src/ne_session.c @@ -403,24 +403,21 @@ /* This doesn't actually implement complete RFC 2818 logic; omits * "f*.example.com" support for simplicity. */ -int ne__ssl_match_hostname(char *cn, const char *hostname) +int ne__ssl_match_hostname(const char *cn, size_t cnlen, const char *hostname) { const char *dot; - dot = strchr(hostname, '.'); - if (dot == NULL) { - char *pnt = strchr(cn, '.'); - /* hostname is not fully-qualified; unqualify the cn. */ - if (pnt != NULL) { - *pnt = '\0'; - } - } - else if (strncmp(cn, "*.", 2) == 0) { + NE_DEBUG(NE_DBG_SSL, "ssl: Match common name '%s' against '%s'\n", + cn, hostname); + + if (strncmp(cn, "*.", 2) == 0 && cnlen > 2 + && (dot = strchr(hostname, '.')) != NULL) { hostname = dot + 1; cn += 2; + cnlen -= 2; } - return !ne_strcasecmp(cn, hostname); + return cnlen == strlen(hostname) && !ne_strcasecmp(cn, hostname); } #endif /* NE_HAVE_SSL */ --- src/ne_socket.c +++ src/ne_socket.c @@ -1261,6 +1261,7 @@ ne_inet_addr *ne_sock_peer(ne_socket *sock, unsigned int *port) { union saun { + struct sockaddr sa; struct sockaddr_in sin; #if defined(USE_GETADDRINFO) && defined(AF_INET6) struct sockaddr_in6 sin6; @@ -1287,13 +1288,13 @@ ia->ai_addr = ne_malloc(sizeof *ia); ia->ai_addrlen = len; memcpy(ia->ai_addr, sad, len); - ia->ai_family = sad->sa_family; + ia->ai_family = saun.sa.sa_family; #else memcpy(ia, &saun.sin.sin_addr.s_addr, sizeof *ia); #endif #if defined(USE_GETADDRINFO) && defined(AF_INET6) - *port = ntohs(sad->sa_family == AF_INET ? + *port = ntohs(saun.sa.sa_family == AF_INET ? saun.sin.sin_port : saun.sin6.sin6_port); #else *port = ntohs(saun.sin.sin_port); --- src/ne_string.c +++ src/ne_string.c @@ -38,6 +38,8 @@ #include "ne_alloc.h" #include "ne_string.h" +/* hack for 0.28.x backport of ne_strnqdup, ne_buffer_qappend */ +#include "ne_private.h" char *ne_token(char **str, char separator) { @@ -252,6 +254,98 @@ buf->used = strlen(buf->data) + 1; } + +/* ascii_quote[n] gives the number of bytes needed by + * ne_buffer_qappend() to append character 'n'. */ +static const unsigned char ascii_quote[256] = { + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 +}; + +static const char hex_chars[16] = "0123456789ABCDEF"; + +/* Return the expected number of bytes needed to append the string + * beginning at byte 's', where 'send' points to the last byte after + * 's'. */ +static size_t qappend_count(const unsigned char *s, const unsigned char *send) +{ + const unsigned char *p; + size_t ret; + + for (p = s, ret = 0; p < send; p++) { + ret += ascii_quote[*p]; + } + + return ret; +} + +/* Append the string 's', up to but not including 'send', to string + * 'dest', quoting along the way. Returns pointer to NUL. */ +static char *quoted_append(char *dest, const unsigned char *s, + const unsigned char *send) +{ + const unsigned char *p; + char *q = dest; + + for (p = s; p < send; p++) { + if (ascii_quote[*p] == 1) { + *q++ = *p; + } + else { + *q++ = '\\'; + *q++ = 'x'; + *q++ = hex_chars[(*p >> 4) & 0x0f]; + *q++ = hex_chars[*p & 0x0f]; + } + } + + /* NUL terminate after the last character */ + *q = '\0'; + + return q; +} + +void ne__buffer_qappend(ne_buffer *buf, const unsigned char *data, size_t len) +{ + const unsigned char *dend = data + len; + char *q, *qs; + + ne_buffer_grow(buf, buf->used + qappend_count(data, dend)); + + /* buf->used >= 1, so this is safe. */ + qs = buf->data + buf->used - 1; + + q = quoted_append(qs, data, dend); + + /* used already accounts for a NUL, so increment by number of + * characters appended, *before* the NUL. */ + buf->used += q - qs; +} + +char *ne__strnqdup(const unsigned char *data, size_t len) +{ + const unsigned char *dend = data + len; + char *dest = malloc(qappend_count(data, dend) + 1); + + quoted_append(dest, data, dend); + + return dest; +} + static const char b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" @@ -345,9 +439,9 @@ return outp - *out; } -/* Character map array; array[n] = isprint(n) ? 0x20 : n. Used by - * ne_strclean as a locale-independent isprint(). */ -static const unsigned char ascii_printable[256] = { +/* Character map array; ascii_clean[n] = isprint(n) ? n : 0x20. Used + * by ne_strclean as a locale-independent isprint(). */ +static const unsigned char ascii_clean[256] = { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, @@ -387,7 +481,7 @@ unsigned char *pnt; for (pnt = (unsigned char *)str; *pnt; pnt++) - *pnt = (char)ascii_printable[*pnt]; + *pnt = (char)ascii_clean[*pnt]; return str; } --- src/ne_xml.c +++ src/ne_xml.c @@ -405,6 +405,28 @@ destroy_element(elm); } +#if defined(HAVE_EXPAT) && XML_MAJOR_VERSION > 1 +/* Stop the parser if an entity declaration is hit. */ +static void entity_declaration(void *userData, const XML_Char *entityName, + int is_parameter_entity, const XML_Char *value, + int value_length, const XML_Char *base, + const XML_Char *systemId, const XML_Char *publicId, + const XML_Char *notationName) +{ + ne_xml_parser *parser = userData; + + NE_DEBUG(NE_DBG_XMLPARSE, "XML: entity declaration [%s]. Failing.\n", + entityName); + + XML_StopParser(parser->parser, XML_FALSE); +} +#elif defined(HAVE_EXPAT) +/* A noop default_handler. */ +static void default_handler(void *userData, const XML_Char *s, int len) +{ +} +#endif + /* Find a namespace definition for 'prefix' in given element, where * length of prefix is 'pfxlen'. Returns the URI or NULL. */ static const char *resolve_nspace(const struct element *elm, @@ -459,14 +481,34 @@ XML_SetCharacterDataHandler(p->parser, char_data); XML_SetUserData(p->parser, (void *) p); XML_SetXmlDeclHandler(p->parser, decl_handler); + + /* Prevent the "billion laughs" attack against expat by disabling + * internal entity expansion. With 2.x, forcibly stop the parser + * if an entity is declared - this is safer and a more obvious + * failure mode. With older versions, installing a noop + * DefaultHandler means that internal entities will be expanded as + * the empty string, which is also sufficient to prevent the + * attack. */ +#if XML_MAJOR_VERSION > 1 + XML_SetEntityDeclHandler(p->parser, entity_declaration); #else + XML_SetDefaultHandler(p->parser, default_handler); +#endif + +#else /* HAVE_LIBXML */ p->parser = xmlCreatePushParserCtxt(&sax_handler, (void *)p, NULL, 0, NULL); if (p->parser == NULL) { abort(); } +#if LIBXML_VERSION < 20602 p->parser->replaceEntities = 1; +#else + /* Enable expansion of entities, and disable network access. */ + xmlCtxtUseOptions(p->parser, XML_PARSE_NOENT | XML_PARSE_NONET); #endif + +#endif /* HAVE_LIBXML || HAVE_EXPAT */ return p; }