Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 293522

Remove html => TRUE option from l() and link generator.

$
0
0

Problem/Motivation

Follow-up to #1825952: Turn on twig autoescape by default

Rather than benefiting from Drupal 8's autoescaping, LinkGenerator::generate() (and by extension, Drupal::l() and LinkGeneratorTrait::l()) still use Drupal 7's legacy approach of requiring the caller to pass an 'html' => TRUE option in order to not escape the link text. This is not ideal, because:

  • To prevent double-escaping of plain text, the caller must know if the variable has already been escaped.
  • The caller can set that option, but pass in a variable containing user-submitted text that hasn't been filtered, resulting in a XSS vulnerability.

Both of the above are especially fragile in cases where the code that sets the option is separate from the code that sets the text, such as in tablesort_header() or (Views)DisplayPluginBase::optionLink().

Proposed resolution

Remove the html option and change LinkGenerator::generate() from conditionally escaping based on that option to autoescaping (in other words, conditionally escaping based on whether SafeMarkup knows the string to already be safe).

In most places, this results in the caller simply being able to remove setting that option. Examples:

  • "Read more" link in NodeViewBuilder::buildLinks(): The text includes a SPAN tag for accessibility, but is produced via t(), so is already marked safe.
  • Actions::preRenderActionsDropbutton(): The text is the result of a drupal_render() so is already marked safe.

However, in some cases, there is code that passes a variable that is safe, but SafeMarkup doesn't know that. There are several categories of such situations, and here is what is proposed for each of them:

Combining two variables

Use String::format() to combine them in a way that registers the result as safe. Examples:

  • Within tablesort_header(), change
    \Drupal::l($cell_content . $image, ...

    to

    \Drupal::l(String::format('!cell_content!image', array('!cell_content' => $cell_content, '!image' => $image)), ...

Note that in HEAD, the use of ! still requires the caller to ensure the corresponding variable is already safe, but see #2399037: (change notice, etc.) String::format() marks a resulting string as safe even when passed an unsafe passthrough argument about hardening that.

Combining literal HTML with a variable

Use String::format() to combine them in a way that registers the result as safe. Examples:

  • Within template_preprocess_views_ui_rearrange_filter_form(), change
    '#title' => '<span>' . t('Remove') . '</span>',

    to

    '#title' => String::format('<span>@text</span>', array('@text' => t('Remove'))),
  • Within shortcut_preprocess_page(), change
    '#title' => '<span class="icon"></span><span class="text">'. $link_text .'</span>',

    to

    '#title' => String::format('<span class="icon"></span><span class="text">!text</span>', array('!text' => $link_text)),

The above two examples only differ on the @ vs ! prefix.

A literal containing HTML without any text

There are no examples of this in core. An example might be an image, but core always renders images with a render array rather than a literal <img> tag. If there were an example of this, it could be implemented with SafeMarkup::set(SOME_LITERAL_STRING), but #2297703: [meta] Refactor and remove as many SafeMarkup::set() calls as possible claims SafeMarkup::set() is for internal use only, so if we accept that claim, then it could alternatively be implemented with String::format(SOME_LITERAL_STRING), though it seems odd to call String::format() without the 2nd argument. Let's discuss this further in that issue.

A literal containing HTML and text

There should be no example of this in non-test code, because all literal text should pass through t(), at which point it's no longer a literal and is covered by the "literal HTML with a variable" example above. Within test code, however, this can occur, such as within Drupal\dblog\Tests\Views\ViewsIntegrationTest::testIntegration(), but within test code, it's okay to use SafeMarkup::set() despite its warning of being for internal use only.

Remaining tasks

Review. Commit.

User interface changes

n/a

API changes

See "Proposed resolution".

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue priorityMajor because this security hardens one of Drupal's most commonly called functions. Not critical because this is merely security hardening, not fixing an actual vulnerability, which only occurs if the caller makes a mistake. Also, the lack of this hardening didn't stop any prior version of Drupal from being released.
Prioritized changesThe main goal of this issue is security improvement.
DisruptionDisruptive for contributed and custom modules, because there are some cases in which it leads to escaping strings that the module author does not want escaped, and therefore requires the module author to change the code that constructs that string (see "Proposed resolution" section for details). However, this is the same condition that already exists in other places, such as setting the value of a 'data' element of a table cell, or any variable passed to a Twig template, so this is just applying the same requirement to link text.

Viewing all articles
Browse latest Browse all 293522

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>