Change record status: 
Project: 
Introduced in branch: 
8.8.x
Introduced in version: 
8.8.0
Description: 

Recent security releases have shown that the render system needs to be stricter about what it allow to be called by a callback. See:

If you have code that adds a render callback - #access_callback, #lazy_builder, #pre_render or #post_render it might need to be updated. This will initially result in deprecation errors, but will eventually result in code throwing an exception.

Procedural function callbacks

If the callback is to a procedural function, for example, color_block_view_pre_render() you will need to move it to an object that implements \Drupal\Core\Security\TrustedCallbackInterface. For example:

class ColorBlock implements TrustedCallbackInterface {

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['preRender'];
  }

  /**
   * #pre_render callback: Sets color preset logo.
   */
  public static function preRender($build) {
    // Do what the color_block_view_pre_render() function did. For the minimal change you can call the procedural function.
  }

}

In core:

  • drupal_pre_render_links() replaced with \Drupal\Core\Render\Element\Link::preRenderLinks()
  • color_block_view_pre_render() replaced with \Drupal\color\ColorBlock::preRender()
  • filter_form_access_denied() replaced with \Drupal\filter\Element\TextFormat::accessDeniedCallback()
  • history_attach_timestamp() replaced with \Drupal\history\HistoryRenderCallback::lazyBuilder()
  • _toolbar_do_get_rendered_subtrees() replaced with \Drupal\toolbar\Controller\ToolbarController::preRenderGetRenderedSubtrees()
  • toolbar_prerender_toolbar_administration_tray() replaced with \Drupal\toolbar\Controller\ToolbarController::preRenderAdministrationTray()
  • views_pre_render_views_form_views_form() replaced with \Drupal\views\Form\ViewsFormMainForm::preRenderViewsForm()

Object method callbacks

If the callback is a RenderCallbackInterface object no update is necessary. Otherwise the object needs to implement \Drupal\Core\Security\TrustedCallbackInterface. For example, \Drupal\node\Plugin\Search\NodeSearch has #pre_render callback to ::removeSubmittedInfo(). You need to add TrustedCallbackInterface to NodeSearch and implement TrustedCallbackInterface::trustedCallbacks() like so:

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['removeSubmittedInfo'];
  }

Anonymous function callbacks

These are not recommended because they cannot be serialized. But they do not require updating as they are trusted.

Impacts: 
Module developers
Themers

Comments

pingwin4eg’s picture

Here's an example of what could happen with an anonymous function as a #pre_render callback:

Exception: Serialization of 'Closure' is not allowed in serialize() (line 245 of core/lib/Drupal/Core/Cache/DatabaseBackend.php).

Drupal\Core\Cache\DatabaseBackend->doSetMultiple(Array) (Line: 193)
Drupal\Core\Cache\DatabaseBackend->setMultiple(Array) (Line: 181)
Drupal\Core\Cache\DatabaseBackend->set('element_info_build:styleswitcher_test_theme', Array, -1, Array) (Line: 180)
Drupal\Core\Cache\ChainedFastBackend->set('element_info_build:styleswitcher_test_theme', Array, -1, Array) (Line: 130)
Drupal\Core\Render\ElementInfoManager->buildInfo('styleswitcher_test_theme') (Line: 77)
Drupal\Core\Render\ElementInfoManager->getInfo('html') (Line: 300)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 200)
Drupal\Core\Render\Renderer->render(Array) (Line: 147)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 573)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 148)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object) (Line: 78)
Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy->dispatch(Object, 'kernel.view') (Line: 163)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 191)
Drupal\page_cache\StackMiddleware\PageCache->fetch(Object, 1, 1) (Line: 128)
Drupal\page_cache\StackMiddleware\PageCache->lookup(Object, 1, 1) (Line: 82)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 705)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
arafalov’s picture

I cannot seem to find any mentions or implementations of RenderElementInterface. Was RenderCallbackInterface by chance the one that was meant instead?

johnalbin’s picture

Good catch, Alexandre! I've updated the change record to use the correct interface.

  - John (JohnAlbin)