diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php
index df736b0..2052256 100644
--- a/core/lib/Drupal/Core/Form/FormErrorHandler.php
+++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php
@@ -83,8 +83,10 @@ protected function displayErrorMessages(array $form, FormStateInterface $form_st
       }
       elseif ($is_visible_element && $has_title && $has_id) {
         // We need to pass this through SafeMarkup::escape() so
-        // drupal_set_message() does not encode the links.
-        $error_links[] = SafeMarkup::escape($this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE])));
+        // drupal_set_message() does not encode the links. The element title,
+        // however, can contain HTML so we use a markup element array so that at
+        // least malicious tags are stripped.
+        $error_links[] = SafeMarkup::escape($this->l(['#markup' => $title], Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE])));
         unset($errors[$name]);
       }
     }
diff --git a/core/modules/config_translation/src/FormElement/FormElementBase.php b/core/modules/config_translation/src/FormElement/FormElementBase.php
index 7db01e1..ef597a6 100644
--- a/core/modules/config_translation/src/FormElement/FormElementBase.php
+++ b/core/modules/config_translation/src/FormElement/FormElementBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\config_translation\FormElement;
 
 use Drupal\Core\Config\Config;
+use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\TypedData\TypedDataInterface;
@@ -67,7 +68,22 @@ public function getTranslationBuild(LanguageInterface $source_language, Language
     // Therefore we make the 'source' and 'translation' keys the top-level
     // keys in $form_state['values'].
     $build['source'] = $this->getSourceElement($source_language, $source_config);
-    $build['translation'] = $this->getTranslationElement($translation_language, $source_config, $translation_config);
+    // If the source configuration contains unsafe markup, it cannot be
+    // translated because the translation cannot pass
+    // FormElementBase::validateInput().
+    if ($this->isSafeConfig($source_config)) {
+      $build['translation'] = $this->getTranslationElement($translation_language, $source_config, $translation_config);
+    }
+    else {
+      $build['translation'] = [
+        '#type' => 'item',
+        '#title' => $this->t('!label <span class="visually-hidden">(!source_language)</span>', array(
+          '!label' => $this->t($this->definition['label']),
+          '!source_language' => $translation_language->getName(),
+        )),
+        '#markup' => '<div class="messages messages--error">' . $this->t('This element cannot be translated because the source configuration contains unsafe HTML.') . '</div>',
+      ];
+    }
 
     $build['source']['#parents'] = array_merge(array('source'), $parents);
     $build['translation']['#parents'] = array_merge(array('translation'), $parents);
@@ -109,6 +125,24 @@ protected function getSourceElement(LanguageInterface $source_language, $source_
   }
 
   /**
+   * Determines whether or not configuration does not contain malicious HTML.
+   *
+   * This is used to ensure the source configuration passes
+   * locale_string_is_safe().
+   *
+   * @param mixed $config
+   *   The configuration value of the element in the source language.
+   *
+   * @return bool
+   *   TRUE if the configuration is safe; FALSE otherwise.
+   *
+   * @see locale_string_is_safe()
+   */
+  protected function isSafeConfig($config) {
+    return locale_string_is_safe($config);
+  }
+
+  /**
    * Returns the translation form element for a given configuration definition.
    *
    * For complex data structures (such as mappings) that are translatable
@@ -146,6 +180,12 @@ protected function getSourceElement(LanguageInterface $source_language, $source_
    * translation of complex data, similar access logic must be implemented
    * manually.
    *
+   * Note that because configuration values might be output on the page the form
+   * element is also responsible for validating that the input does not contain
+   * malicious HTML input. FormElementBase::validateInput(), which uses
+   * locale_string_is_safe() for the validation, is provided for that purpose
+   * and is used for the default form element.
+   *
    * @param \Drupal\Core\Language\LanguageInterface $translation_language
    *   The language to display the translation form for.
    * @param mixed $source_config
@@ -158,6 +198,8 @@ protected function getSourceElement(LanguageInterface $source_language, $source_
    *
    * @see \Drupal\config_translation\FormElement\TextFormat
    * @see filter_process_format()
+   * @see \Drupal\config_translation\FormElement\FormElementBase::validateInput()
+   * @see locale_string_is_safe()
    */
   protected function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
     // Add basic properties that apply to all form elements.
@@ -168,10 +210,25 @@ protected function getTranslationElement(LanguageInterface $translation_language
       )),
       '#default_value' => $translation_config,
       '#attributes' => array('lang' => $translation_language->getId()),
+      '#element_validate' => [[get_class($this), 'validateInput']],
     );
   }
 
   /**
+   * Validates that the form element does not contain malicious HTML input.
+   *
+   * @param array $element
+   *   The form element to validate.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   */
+  public static function validateInput(array $element, FormStateInterface $form_state) {
+    if (isset($element['#value']) && !locale_string_is_safe($element['#value'])) {
+      $form_state->setError($element, t('The submitted string contains disallowed HTML.'));
+    }
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function setConfig(Config $base_config, LanguageConfigOverride $config_translation, $config_values, $base_key = NULL) {
diff --git a/core/modules/config_translation/src/FormElement/PluralVariants.php b/core/modules/config_translation/src/FormElement/PluralVariants.php
index ec6b977..39bb78b 100644
--- a/core/modules/config_translation/src/FormElement/PluralVariants.php
+++ b/core/modules/config_translation/src/FormElement/PluralVariants.php
@@ -66,6 +66,7 @@ protected function getTranslationElement(LanguageInterface $translation_language
         '#title' => $i == 0 ? $this->t('Singular form') : $this->formatPlural($i, 'First plural form', '@count. plural form'),
         '#default_value' => isset($values[$i]) ? $values[$i] : '',
         '#attributes' => array('lang' => $translation_language->getId()),
+        '#element_validate' => [[get_class($this), 'validateInput']],
       );
     }
     return $element;
diff --git a/core/modules/config_translation/src/FormElement/TextFormat.php b/core/modules/config_translation/src/FormElement/TextFormat.php
index c06c449..686503a 100644
--- a/core/modules/config_translation/src/FormElement/TextFormat.php
+++ b/core/modules/config_translation/src/FormElement/TextFormat.php
@@ -31,6 +31,13 @@ public function getSourceElement(LanguageInterface $source_language, $source_con
   /**
    * {@inheritdoc}
    */
+  protected function isSafeConfig($config) {
+    return locale_string_is_safe($config['value']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getTranslationElement(LanguageInterface $translation_language, $source_config, $translation_config) {
     return array(
       '#type' => 'text_format',
diff --git a/core/modules/system/src/Tests/Element/PathElementFormTest.php b/core/modules/system/src/Tests/Element/PathElementFormTest.php
index 5698951..758fcb6 100644
--- a/core/modules/system/src/Tests/Element/PathElementFormTest.php
+++ b/core/modules/system/src/Tests/Element/PathElementFormTest.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element\PathElement;
+use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Url;
 use Drupal\simpletest\KernelTestBase;
 use Drupal\user\Entity\Role;
@@ -147,6 +148,9 @@ public function validateForm(array &$form, FormStateInterface $form_state) {}
    * Tests that default handlers are added even if custom are specified.
    */
   public function testPathElement() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+
     $form_state = (new FormState())
       ->setValues([
         'required_validate' => 'user/' . $this->testUser->id(),
@@ -155,7 +159,9 @@ public function testPathElement() {
         'required_validate_url' => 'user/' . $this->testUser->id(),
       ]);
     $form_builder = $this->container->get('form_builder');
-    $form_builder->submitForm($this, $form_state);
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($form_builder, &$form_state) {
+      $form_builder->submitForm($this, $form_state);
+    });
 
     // Valid form state.
     $this->assertEqual(count($form_state->getErrors()), 0);
@@ -180,7 +186,9 @@ public function testPathElement() {
         'required_validate_route' => 'user/' . $this->testUser->id(),
         'required_validate_url' => 'user/' . $this->testUser->id(),
       ]);
-    $form_builder->submitForm($this, $form_state);
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($form_builder, &$form_state) {
+      $form_builder->submitForm($this, $form_state);
+    });
     $errors = $form_state->getErrors();
     // Should be missing 'required_validate' field.
     $this->assertEqual(count($errors), 1);
@@ -195,7 +203,9 @@ public function testPathElement() {
         'required_validate_url' => 'user/74',
       ]);
     $form_builder = $this->container->get('form_builder');
-    $form_builder->submitForm($this, $form_state);
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($form_builder, &$form_state) {
+      $form_builder->submitForm($this, $form_state);
+    });
 
     // Valid form state.
     $errors = $form_state->getErrors();
diff --git a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php b/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
index 22622e7..befcefd 100644
--- a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
+++ b/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Form;
 
 use Drupal\Core\Form\FormState;
+use Drupal\Core\Render\RenderContext;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Core\Form\FormStateInterface;
 
@@ -227,7 +228,11 @@ private function formSubmitHelper($form, $edit) {
 
     \Drupal::formBuilder()->prepareForm($form_id, $form, $form_state);
 
-    \Drupal::formBuilder()->processForm($form_id, $form, $form_state);
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($form_id, &$form, &$form_state) {
+      \Drupal::formBuilder()->processForm($form_id, $form, $form_state);
+    });
 
     $errors = $form_state->getErrors();
 
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index e556b20..c754957 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\RenderContext;
 use Drupal\form_test\Form\FormTestDisabledElementsForm;
 use Drupal\simpletest\WebTestBase;
 use Drupal\user\RoleInterface;
@@ -118,13 +119,17 @@ function testRequiredFields() {
           // so we bypass it by setting the token to FALSE.
           $form['#token'] = FALSE;
           \Drupal::formBuilder()->prepareForm($form_id, $form, $form_state);
-          \Drupal::formBuilder()->processForm($form_id, $form, $form_state);
+          /** @var \Drupal\Core\Render\RendererInterface $renderer */
+          $renderer = $this->container->get('renderer');
+          $renderer->executeInRenderContext(new RenderContext(), function () use ($form_id, &$form, &$form_state) {
+            \Drupal::formBuilder()->processForm($form_id, $form, $form_state);
+          });
           $errors = $form_state->getErrors();
           // Form elements of type 'radios' throw all sorts of PHP notices
           // when you try to render them like this, so we ignore those for
           // testing the required marker.
           // @todo Fix this work-around (https://www.drupal.org/node/588438).
-          $form_output = ($type == 'radios') ? '' : \Drupal::service('renderer')->renderRoot($form);
+          $form_output = ($type == 'radios') ? '' : $renderer->renderRoot($form);
           if ($required) {
             // Make sure we have a form error for this element.
             $this->assertTrue(isset($errors[$element]), "Check empty($key) '$type' field '$element'");
diff --git a/core/modules/system/src/Tests/Form/ProgrammaticTest.php b/core/modules/system/src/Tests/Form/ProgrammaticTest.php
index b138d16..b810d0e 100644
--- a/core/modules/system/src/Tests/Form/ProgrammaticTest.php
+++ b/core/modules/system/src/Tests/Form/ProgrammaticTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Form;
 
 use Drupal\Core\Form\FormState;
+use Drupal\Core\Render\RenderContext;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -73,7 +74,11 @@ function testSubmissionWorkflow() {
   private function submitForm($values, $valid_input) {
     // Programmatically submit the given values.
     $form_state = (new FormState())->setValues($values);
-    \Drupal::formBuilder()->submitForm('\Drupal\form_test\Form\FormTestProgrammaticForm', $form_state);
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    $renderer->executeInRenderContext(new RenderContext(), function () use (&$form_state) {
+      \Drupal::formBuilder()->submitForm('\Drupal\form_test\Form\FormTestProgrammaticForm', $form_state);
+    });
 
     // Check that the form returns an error when expected, and vice versa.
     $errors = $form_state->getErrors();
diff --git a/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php b/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
index f7cffa1..f190bc0 100644
--- a/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
+++ b/core/modules/system/src/Tests/Form/TriggeringElementProgrammedUnitTest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Form\FormInterface;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\RenderContext;
 use Drupal\simpletest\KernelTestBase;
 
 /**
@@ -90,7 +91,11 @@ function testLimitValidationErrors() {
     $form_state = new FormState();
     $form_state->setValue('section', 'one');
     $form_builder = $this->container->get('form_builder');
-    $form_builder->submitForm($this, $form_state);
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = $this->container->get('renderer');
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($form_builder, &$form_state) {
+      $form_builder->submitForm($this, $form_state);
+    });
 
     // Verify that only the specified section was validated.
     $errors = $form_state->getErrors();
diff --git a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
index 53d7e76..86367ef 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Form;
 
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Form\FormState;
 use Drupal\Tests\UnitTestCase;
 
@@ -24,7 +25,11 @@ public function testDisplayErrorMessages() {
     $link_generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface');
     $link_generator->expects($this->any())
       ->method('generate')
-      ->willReturnArgument(0);
+      ->willReturnCallback(function (array $build) {
+        // @todo This should not be necessary after
+        //   https://www.drupal.org/node/2501975
+        return SafeMarkup::set($build['#markup']);
+      });
     $form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
       ->setConstructorArgs([$this->getStringTranslationStub(), $link_generator])
       ->setMethods(['drupalSetMessage'])
@@ -41,7 +46,7 @@ public function testDisplayErrorMessages() {
       ->with('this missing element is invalid', 'error');
     $form_error_handler->expects($this->at(3))
       ->method('drupalSetMessage')
-      ->with('3 errors have been found: Test 1, Test 2 &amp; a half, Test 3', 'error');
+      ->with('3 errors have been found: Test 1, Test 2 & a half, Test 3', 'error');
 
     $form = [
       '#parents' => [],
