diff --git a/agent/php_autorum.c b/agent/php_autorum.c index bd701b0d6..767409885 100644 --- a/agent/php_autorum.c +++ b/agent/php_autorum.c @@ -24,6 +24,28 @@ static char* nr_php_rum_malloc(int len) { return (char*)emalloc(len); } +static char* nr_php_rum_produce_header_with_ini_nonce(nrtxn_t* txn, + int tags, + int autorum) { + char* nonce = nr_php_zend_ini_string( + NR_PSTR("newrelic.browser_monitoring.nonce_rum_header"), 0); + if (nonce && '\0' != nonce[0]) { + return nr_rum_produce_header_with_nonce(txn, tags, autorum, nonce); + } + return nr_rum_produce_header(txn, tags, autorum); +} + +static char* nr_php_rum_produce_footer_with_ini_nonce(nrtxn_t* txn, + int tags, + int autorum) { + char* nonce = nr_php_zend_ini_string( + NR_PSTR("newrelic.browser_monitoring.nonce_rum_footer"), 0); + if (nonce && '\0' != nonce[0]) { + return nr_rum_produce_footer_with_nonce(txn, tags, autorum, nonce); + } + return nr_rum_produce_footer(txn, tags, autorum); +} + /* * Callback function for iterating the list of response headers that * prints the value of the Content-Type header, if present. This is so @@ -91,8 +113,8 @@ void nr_php_rum_output_handler( } nr_memset(&control_block, 0, sizeof(control_block)); - control_block.produce_header = nr_rum_produce_header; - control_block.produce_footer = nr_rum_produce_footer; + control_block.produce_header = nr_php_rum_produce_header_with_ini_nonce; + control_block.produce_footer = nr_php_rum_produce_footer_with_ini_nonce; control_block.malloc_worker = nr_php_rum_malloc; has_response_content_length = nr_php_has_response_content_length(TSRMLS_C); diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 7cd574328..ff0992c74 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -2897,6 +2897,16 @@ STD_PHP_INI_ENTRY_EX("newrelic.browser_monitoring.attributes.exclude", zend_newrelic_globals, newrelic_globals, 0) +PHP_INI_ENTRY_EX("newrelic.browser_monitoring.nonce_rum_header", + "", + NR_PHP_REQUEST, + 0, + 0) +PHP_INI_ENTRY_EX("newrelic.browser_monitoring.nonce_rum_footer", + "", + NR_PHP_REQUEST, + 0, + 0) /* * newrelic.browser_monitoring.ssl_for_http is omitted. */ diff --git a/axiom/nr_rum.c b/axiom/nr_rum.c index c712948a1..e6426f2d9 100644 --- a/axiom/nr_rum.c +++ b/axiom/nr_rum.c @@ -42,6 +42,21 @@ static char* nr_rum_obfuscate(const char* input, const char* key) { return nr_obfuscate(input, key, NR_RUM_OBFUSCATION_KEY_LENGTH); } +char* nr_rum_encode_nonce(const char* nonce) { + char* encoded = 0; + if ((0 == nonce) || ('\0' == nonce[0])) { + encoded = (char*)nr_malloc(1); + encoded[0] = '\0'; + return encoded; + } + { + size_t needed = (size_t)snprintf(NULL, 0, " nonce=\"%s\"", nonce) + 1; + encoded = (char*)nr_malloc(needed); + snprintf(encoded, needed, " nonce=\"%s\"", nonce); + } + return encoded; +} + /* * Do not terminate these strings with a \n. If the fragments below end up * being inserted into the middle of a JavaScript string (for example the user @@ -50,7 +65,7 @@ static char* nr_rum_obfuscate(const char* input, const char* key) { * Otherwise they will end up breaking the string in the middle of a line which * will cause JavaScript errors, which will break a user's web site. */ -static const char rum_start_tag[] = ""; /* @@ -59,10 +74,11 @@ static const char rum_end_tag[] = ""; */ const char nr_rum_footer_prefix[] = "window.NREUM||(NREUM={});NREUM.info="; -char* nr_rum_produce_header(nrtxn_t* txn, int tags, int autorum) { +static char* nr_rum_produce_header_impl(nrtxn_t* txn, int tags, int autorum, const char* nonce) { char* return_header; size_t header_length; const char* loader; + char* formatted_rum_start_tag = 0; if (0 == txn) { return 0; @@ -89,18 +105,37 @@ char* nr_rum_produce_header(nrtxn_t* txn, int tags, int autorum) { txn->status.rum_header = 1 + (autorum != 0); - header_length - = (tags ? (nr_strlen(rum_start_tag) + nr_strlen(rum_end_tag)) : 0) - + nr_strlen(loader) + 1; + if (tags) { + char* encoded_nonce = nr_rum_encode_nonce(nonce); + size_t needed = (size_t)snprintf(NULL, 0, rum_start_tag, encoded_nonce) + 1; + formatted_rum_start_tag = (char*)nr_malloc(needed); + snprintf(formatted_rum_start_tag, needed, rum_start_tag, encoded_nonce); + nr_free(encoded_nonce); + } + + header_length = (tags ? (nr_strlen(formatted_rum_start_tag) + nr_strlen(rum_end_tag)) : 0) + + nr_strlen(loader) + 1; return_header = (char*)nr_malloc(header_length); return_header[0] = '\0'; - snprintf(return_header, header_length, "%s%s%s", tags ? rum_start_tag : "", + snprintf(return_header, header_length, "%s%s%s", tags ? formatted_rum_start_tag : "", loader, tags ? rum_end_tag : ""); + if (formatted_rum_start_tag) { + nr_free(formatted_rum_start_tag); + } + return return_header; } +char* nr_rum_produce_header(nrtxn_t* txn, int tags, int autorum) { + return nr_rum_produce_header_impl(txn, tags, autorum, NULL); +} + +char* nr_rum_produce_header_with_nonce(nrtxn_t* txn, int tags, int autorum, const char* nonce) { + return nr_rum_produce_header_impl(txn, tags, autorum, nonce); +} + char* nr_rum_get_attributes(const nr_attributes_t* attributes) { char* json; nrobj_t* user; @@ -152,13 +187,14 @@ static char* nr_rum_get_attributes_obfuscated(const nr_attributes_t* attributes, return json_obfuscated; } -char* nr_rum_produce_footer(nrtxn_t* txn, int tags, int autorum) { +static char* nr_rum_produce_footer_impl(nrtxn_t* txn, int tags, int autorum, const char* nonce) { char* txn_name; nrtime_t queue_time; nrtime_t app_time; nrobj_t* hash; char* hash_json; char* obfuscated_attributes; + char* formatted_rum_start_tag = 0; if (0 == txn) { return 0; @@ -222,21 +258,40 @@ char* nr_rum_produce_footer(nrtxn_t* txn, int tags, int autorum) { char* footer; size_t footer_len; - footer_len = (tags ? (sizeof(rum_start_tag) + sizeof(rum_end_tag)) : 0) - + sizeof(nr_rum_footer_prefix) + nr_strlen(hash_json) + 1; + if (tags) { + char* encoded_nonce = nr_rum_encode_nonce(nonce); + size_t needed = (size_t)snprintf(NULL, 0, rum_start_tag, encoded_nonce) + 1; + formatted_rum_start_tag = (char*)nr_malloc(needed); + snprintf(formatted_rum_start_tag, needed, rum_start_tag, encoded_nonce); + nr_free(encoded_nonce); + } + + footer_len = (tags ? (nr_strlen(formatted_rum_start_tag) + nr_strlen(rum_end_tag)) : 0) + + sizeof(nr_rum_footer_prefix) - 1 + nr_strlen(hash_json) + 1; footer = (char*)nr_malloc(footer_len); footer[0] = '\0'; - snprintf(footer, footer_len, "%s%s%s%s", tags ? rum_start_tag : "", + snprintf(footer, footer_len, "%s%s%s%s", tags ? formatted_rum_start_tag : "", nr_rum_footer_prefix, hash_json, tags ? rum_end_tag : ""); nr_free(hash_json); + if (formatted_rum_start_tag) { + nr_free(formatted_rum_start_tag); + } txn->status.rum_footer = 1 + (autorum != 0); return footer; } } +char* nr_rum_produce_footer(nrtxn_t* txn, int tags, int autorum) { + return nr_rum_produce_footer_impl(txn, tags, autorum, NULL); +} + +char* nr_rum_produce_footer_with_nonce(nrtxn_t* txn, int tags, int autorum, const char* nonce) { + return nr_rum_produce_footer_impl(txn, tags, autorum, nonce); +} + static const char nr_rum_x_ua_compatible_regex[] = "<\\s*meta[^>]+http-equiv\\s*=\\s*['\"]x-ua-compatible['\"][^>]*>"; static const char nr_rum_charset_regex[] = "<\\s*meta[^>]+charset\\s*=[^>]*>"; diff --git a/axiom/nr_rum.h b/axiom/nr_rum.h index 3e4c03989..b55fa04c9 100644 --- a/axiom/nr_rum.h +++ b/axiom/nr_rum.h @@ -24,12 +24,14 @@ extern int nr_rum_do_autorum(const nrtxn_t* txn); * Params : 1. The transaction pointer. * 2. Whether or not to use start tags. * 3. Whether or not this is being inserted by auto-RUM. + * 4. (optional) See nr_rum_produce_header_with_nonce. * * Returns : The string to insert, allocated. If the header has been produced * before, returns NULL. That is, this function can only be called * once per transaction. */ extern char* nr_rum_produce_header(nrtxn_t* txn, int tags, int autorum); +extern char* nr_rum_produce_header_with_nonce(nrtxn_t* txn, int tags, int autorum, const char* nonce); /* * Purpose : Produce the RUM footer for a transaction. @@ -37,12 +39,24 @@ extern char* nr_rum_produce_header(nrtxn_t* txn, int tags, int autorum); * Params : 1. The transaction pointer. * 2. Whether or not to use tags. * 3. Whether or not this is being inserted by auto-RUM. + * 4. (optional) See nr_rum_produce_footer_with_nonce. * * Returns : The string to insert, allocated. If the header has been produced * before, returns NULL. That is, this function can only be called * once per transaction. */ extern char* nr_rum_produce_footer(nrtxn_t* txn, int tags, int autorum); +extern char* nr_rum_produce_footer_with_nonce(nrtxn_t* txn, int tags, int autorum, const char* nonce); + +/* + * Purpose : Encode a CSP nonce attribute for insertion into a script tag. + * + * Params : 1. The raw nonce value, or NULL/empty. + * + * Returns : An allocated string: "nonce=\"VALUE\"" if nonce is provided, + * otherwise "" (empty). Caller must free with nr_free. + */ +extern char* nr_rum_encode_nonce(const char* nonce); /* * Purpose : Scan html looking for a heuristically good place in to put