From f0285d72f92eb498e566e7be07ca19261415664f Mon Sep 17 00:00:00 2001 From: George Joseph Date: Wed, 20 Jan 2016 09:55:26 -0700 Subject: [PATCH] res_pjsip: Validate that URIs don't exceed pjproject's limit If pjproject isn't compiled with -DNDEBUG in CFLAGS, and Asterisk passes a URI that exceeds pjproject's limits, pjproject will call assert and crash the Asterisk process. If pjproject IS compiled with -DNDEBUG, pjproject will SEGV. This not only affects URIs provided in pjsip.conf but also URIs received in REGISTER messages. This patch checks that the URI total length doesn't exceed PJSIP_MAX_URL_SIZE and that the host portion of the URI doesn't exceed PJ_MAX_HOSTNAME. The former can only be checked against the compile-time value but the latter is checked against the run-time value. NOTE: The maximum length of the hostname Asterisk can pass is actually less than PJ_MAX_HOSTNAME by the length of SRV prefixes like '_sips.tcp.'. This is because pjproject automatically adds on the SRV prefix when doing DNS reolution. ASTERISK-25707 #close Change-Id: Id3f02d382f688b54d955900363712fe578e9e2af --- res/res_pjsip/include/res_pjsip_private.h | 7 +++++ res/res_pjsip/location.c | 50 +++++++++++++++++++++++++++++++ res/res_pjsip_outbound_registration.c | 8 +++++ res/res_pjsip_registrar.c | 29 ++++++++++++++++-- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h index c1f7e23..72a4387 100644 --- a/res/res_pjsip/include/res_pjsip_private.h +++ b/res/res_pjsip/include/res_pjsip_private.h @@ -318,4 +318,11 @@ int internal_sip_unregister_endpoint_formatter(struct ast_sip_endpoint_formatter * \brief Finds or creates contact_status for a contact */ struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact); + +/*! + * \internal + * \brief Validate that the uri meets pjproject length restrictions + */ +int ast_sip_validate_uri_length(const char *uri); + #endif /* RES_PJSIP_PRIVATE_H_ */ diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c index 2908f6f..677d85b 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -27,6 +27,10 @@ #include "include/res_pjsip_private.h" #include "asterisk/res_pjsip_cli.h" #include "asterisk/statsd.h" +#include "asterisk/res_pjproject.h" + +static int pj_max_hostname = PJ_MAX_HOSTNAME; +static int pjsip_max_url_size = PJSIP_MAX_URL_SIZE; /*! \brief Destructor for AOR */ static void aor_destroy(void *obj) @@ -369,6 +373,43 @@ static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, in return cmp; } +int ast_sip_validate_uri_length(const char *contact_uri) +{ + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + pj_pool_t *pool; + int max_length = pj_max_hostname - 1; + + if (strlen(contact_uri) > pjsip_max_url_size - 1) { + return -1; + } + + if (!(pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri validation", 512, 512))) { + ast_log(LOG_ERROR, "Unable to allocate pool for uri validation\n"); + return -1; + } + + if (!(uri = pjsip_parse_uri(pool, (char *)contact_uri, strlen(contact_uri), 0)) || + (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))) { + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + return -1; + } + + sip_uri = pjsip_uri_get_uri(uri); + if (sip_uri->port == 0) { + max_length -= strlen("_sips.tcp."); + } + + if (sip_uri->host.slen > max_length) { + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + return -1; + } + + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + + return 0; +} + /*! \brief Custom handler for permanent URIs */ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { @@ -388,6 +429,11 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab char hash[33]; char contact_id[strlen(aor_id) + sizeof(hash) + 2]; + if (ast_sip_validate_uri_length(contact_uri)) { + ast_log(LOG_ERROR, "Contact uri or hostname length exceeds pjproject limit: %s\n", contact_uri); + return -1; + } + if (!aor->permanent_contacts) { aor->permanent_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL); @@ -958,6 +1004,10 @@ int ast_sip_initialize_sorcery_location(void) struct ast_sorcery *sorcery = ast_sip_get_sorcery(); int i; + ast_pjproject_get_buildopt("PJ_MAX_HOSTNAME", "%d", &pj_max_hostname); + /* As of pjproject 2.4.5, PJSIP_MAX_URL_SIZE isn't exposed yet but we try anyway. */ + ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size); + ast_sorcery_apply_default(sorcery, "contact", "astdb", "registrar"); ast_sorcery_apply_default(sorcery, "aor", "config", "pjsip.conf,criteria=type=aor"); diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c index a2fefde..ee40efc 100644 --- a/res/res_pjsip_outbound_registration.c +++ b/res/res_pjsip_outbound_registration.c @@ -1296,10 +1296,18 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo ast_log(LOG_ERROR, "No server URI specified on outbound registration '%s'\n", ast_sorcery_object_get_id(applied)); return -1; + } else if (ast_sip_validate_uri_length(applied->server_uri)) { + ast_log(LOG_ERROR, "Server URI or hostname length exceeds pjpropject limit '%s'\n", + ast_sorcery_object_get_id(applied)); + return -1; } else if (ast_strlen_zero(applied->client_uri)) { ast_log(LOG_ERROR, "No client URI specified on outbound registration '%s'\n", ast_sorcery_object_get_id(applied)); return -1; + } else if (ast_sip_validate_uri_length(applied->client_uri)) { + ast_log(LOG_ERROR, "Client URI or hostname length exceeds pjpropject limit '%s'\n", + ast_sorcery_object_get_id(applied)); + return -1; } else if (applied->line && ast_strlen_zero(applied->endpoint)) { ast_log(LOG_ERROR, "Line support has been enabled on outbound registration '%s' without providing an endpoint\n", ast_sorcery_object_get_id(applied)); diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index b0f8d66..ae894b8 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -18,6 +18,7 @@ /*** MODULEINFO pjproject + res_pjproject res_pjsip core ***/ @@ -32,6 +33,7 @@ #include "asterisk/test.h" #include "asterisk/taskprocessor.h" #include "asterisk/manager.h" +#include "asterisk/res_pjproject.h" #include "res_pjsip/include/res_pjsip_private.h" /*** DOCUMENTATION @@ -51,6 +53,9 @@ ***/ +static int pj_max_hostname = PJ_MAX_HOSTNAME; +static int pjsip_max_url_size = PJSIP_MAX_URL_SIZE; + /*! \brief Internal function which returns the expiration time for a contact */ static int registrar_get_expiration(const struct ast_sip_aor *aor, const pjsip_contact_hdr *contact, const pjsip_rx_data *rdata) { @@ -85,7 +90,7 @@ struct registrar_contact_details { /*! \brief Pool used for parsing URI */ pj_pool_t *pool; /*! \brief URI being looked for */ - pjsip_uri *uri; + pjsip_sip_uri *uri; }; /*! \brief Callback function for finding a contact */ @@ -113,6 +118,7 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) { int expiration = registrar_get_expiration(aor, contact, rdata); RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup); + char contact_uri[pjsip_max_url_size]; if (contact->star) { /* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */ @@ -134,6 +140,19 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co details.uri = pjsip_uri_get_uri(contact->uri); + /* pjsip_uri_print returns -1 if there's not enough room in the buffer */ + if (pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)) < 0) { + /* If the total length of the uri is greater than pjproject can handle, go no further */ + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); + return -1; + } + + if (details.uri->host.slen >= pj_max_hostname) { + /* If the length of the hostname is greater than pjproject can handle, go no further */ + pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool); + return -1; + } + /* Determine if this is an add, update, or delete for policy enforcement purposes */ if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) { if (expiration) { @@ -477,7 +496,7 @@ static int rx_task(void *data) /* Iterate each provided Contact header and add, update, or delete */ while ((contact_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) { int expiration; - char contact_uri[PJSIP_MAX_URL_SIZE]; + char contact_uri[pjsip_max_url_size]; RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup); if (contact_hdr->star) { @@ -796,6 +815,12 @@ static int load_module(void) { const pj_str_t STR_REGISTER = { "REGISTER", 8 }; + CHECK_PJPROJECT_MODULE_LOADED(); + + ast_pjproject_get_buildopt("PJ_MAX_HOSTNAME", "%d", &pj_max_hostname); + /* As of pjproject 2.4.5, PJSIP_MAX_URL_SIZE isn't exposed yet but we try anyway. */ + ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size); + CHECK_PJSIP_MODULE_LOADED(); if (!(serializers = ao2_container_alloc( -- 2.5.0