diff --git a/includes/form.inc b/includes/form.inc
index da1caa8..67a6d27 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -2451,6 +2451,15 @@ function form_type_password_confirm_value($element, $input = FALSE) {
     $element += array('#default_value' => array());
     return $element['#default_value'] + array('pass1' => '', 'pass2' => '');
   }
+  $value = array('pass1' => '', 'pass2' => '');
+  // Throw out all invalid array keys, we only allow pass1 and pass2.
+  foreach ($value as $allowed_key => $default) {
+    // Only strings are acceptable, any nested array values are ignored.
+    if (isset($input[$allowed_key]) && is_string($input[$allowed_key])) {
+      $value[$allowed_key] = $input[$allowed_key];
+    }
+  }
+  return $value;
 }
 
 /**
@@ -2495,6 +2504,25 @@ function form_type_select_value($element, $input = FALSE) {
 }
 
 /**
+ * Determines the value for a textarea form element.
+ *
+ * @param array $element
+ *   The form element whose value is being populated.
+ * @param mixed $input
+ *   The incoming input to populate the form element. If this is FALSE,
+ *   the element's default value should be returned.
+ *
+ * @return string
+ *   The data that will appear in the $element_state['values'] collection
+ *   for this element. Return nothing to use the default.
+ */
+function form_type_textarea_value($element, $input = FALSE) {
+  if ($input !== FALSE) {
+    return is_string($input) ? $input : '';
+  }
+}
+
+/**
  * Determines the value for a textfield form element.
  *
  * @param $element
@@ -2509,9 +2537,12 @@ function form_type_select_value($element, $input = FALSE) {
  */
 function form_type_textfield_value($element, $input = FALSE) {
   if ($input !== FALSE && $input !== NULL) {
-    // Equate $input to the form value to ensure it's marked for
-    // validation.
-    return str_replace(array("\r", "\n"), '', $input);
+    // This should be a string, but allow other scalars since they might
+    // be valid input in programmatic form submissions.
+    if (!is_scalar($input)) {
+      $input = '';
+    }
+    return str_replace(array("\r", "\n"), '', (string) $input);
   }
 }
 
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index f90b854..a95d117 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -470,6 +470,53 @@ class FormsTestCase extends DrupalWebTestCase {
     $this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit'));
     $this->assertText('An illegal choice has been detected.', 'Input forgery was detected.');
   }
+
+  /**
+   * Tests that submitted values are converted to scalar strings for textfields.
+   */
+  public function testTextfieldStringValue() {
+    $multivalue = array('evil' => 'multivalue', 'not so' => 'good');
+    $this->checkFormValue('textfield', $multivalue, '');
+    $this->checkFormValue('password', $multivalue, '');
+    $this->checkFormValue('textarea', $multivalue, '');
+    $this->checkFormValue('machine_name', $multivalue, '');
+    $this->checkFormValue('password_confirm', $multivalue, array('pass1' => '', 'pass2' => ''));
+  }
+
+  /**
+   * Checks that a given form input value is sanitized to the expected result.
+   *
+   * @param string $element_type
+   *   The form element type, example: textfield.
+   * @param mixed $input_value
+   *   The submitted user input value for the form element.
+   * @param mixed $expected_value
+   *   The sanitized result value in the form state after calling
+   *   form_builder().
+   */
+  protected function checkFormValue($element_type, $input_value, $expected_value) {
+    $form_id = $this->randomName();
+    $form = array();
+    $form_state = form_state_defaults();
+    $form['op'] = array('#type' => 'submit', '#value' => t('Submit'));
+    $form[$element_type] = array(
+      '#type' => $element_type,
+      '#title' => 'test',
+    );
+
+    $form_state['input'][$element_type] = $input_value;
+    $form_state['input']['form_id'] = $form_id;
+    $form_state['method'] = 'post';
+    $form_state['values'] = array();
+    drupal_prepare_form($form_id, $form, $form_state);
+
+    // This is the main function we want to test: it is responsible for
+    // populating user supplied $form_state['input'] to sanitized
+    // $form_state['values'].
+    form_builder($form_id, $form, $form_state);
+
+    $this->assertIdentical($form_state['values'][$element_type], $expected_value, "Malicious multi-value array form submissions for $element_type have been sanitized");
+  }
 }
 
 /**
diff --git a/modules/system/system.module b/modules/system/system.module
index 8940ad0..253204b 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -374,6 +374,9 @@ function system_element_info() {
     '#element_validate' => array('form_validate_machine_name'),
     '#theme' => 'textfield',
     '#theme_wrappers' => array('form_element'),
+    // Use the same value callback as for textfield, this ensures that we only
+    // get string values.
+    '#value_callback' => 'form_type_textfield_value',
   );
   $types['password'] = array(
     '#input' => TRUE,
@@ -382,6 +385,9 @@ function system_element_info() {
     '#process' => array('ajax_process_form'),
     '#theme' => 'password',
     '#theme_wrappers' => array('form_element'),
+    // Use the same value callback as for textfield, this ensures that we only
+    // get string values.
+    '#value_callback' => 'form_type_textfield_value',
   );
   $types['password_confirm'] = array(
     '#input' => TRUE,
