Origin: vendor
Forwarded: not-needed
From: Gunnar Wolf <gwolf@debian.org>
Last-updatE: 2015-06-17
Description: Fixes SA-CORE-2015-002 (Multiple vulnerabilities)
 Backporting the diff between 7.37 and 7.38, applying it to the
 version in the Stable Debian release (7.32). For further details, the
 advisory is in:
 .
 http://drupal.org/SA-CORE-2015-002
 CVE IDs assigned as follows:
 + Impersonation (OpenID module - Drupal 6 and 7): CVE-2015-3234
 + Open redirect (Field UI module - Drupal 7): CVE-2015-3232
 + Open redirect (Overlay module - Drupal 7: CVE-2015-3233
 + Information disclosure (Render cache system - Drupal 7): CVE-2015-3231

Index: drupal7/includes/common.inc
===================================================================
--- drupal7.orig/includes/common.inc
+++ drupal7/includes/common.inc
@@ -6262,13 +6262,21 @@ function drupal_render_cid_parts($granul
   }
 
   if (!empty($granularity)) {
+    $cache_per_role = $granularity & DRUPAL_CACHE_PER_ROLE;
+    $cache_per_user = $granularity & DRUPAL_CACHE_PER_USER;
+    // User 1 has special permissions outside of the role system, so when
+    // caching per role is requested, it should cache per user instead.
+    if ($user->uid == 1 && $cache_per_role) {
+      $cache_per_user = TRUE;
+      $cache_per_role = FALSE;
+    }
     // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
     // resource drag for sites with many users, so when a module is being
     // equivocal, we favor the less expensive 'PER_ROLE' pattern.
-    if ($granularity & DRUPAL_CACHE_PER_ROLE) {
+    if ($cache_per_role) {
       $cid_parts[] = 'r.' . implode(',', array_keys($user->roles));
     }
-    elseif ($granularity & DRUPAL_CACHE_PER_USER) {
+    elseif ($cache_per_user) {
       $cid_parts[] = "u.$user->uid";
     }
 
Index: drupal7/modules/field_ui/field_ui.admin.inc
===================================================================
--- drupal7.orig/modules/field_ui/field_ui.admin.inc
+++ drupal7/modules/field_ui/field_ui.admin.inc
@@ -2105,6 +2105,10 @@ function field_ui_next_destination($enti
   $destinations = !empty($_REQUEST['destinations']) ? $_REQUEST['destinations'] : array();
   if (!empty($destinations)) {
     unset($_REQUEST['destinations']);
+  }
+  // Remove any external URLs.
+  $destinations = array_diff($destinations, array_filter($destinations, 'url_is_external'));
+  if ($destinations) {
     return field_ui_get_destinations($destinations);
   }
   $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle);
Index: drupal7/modules/field_ui/field_ui.test
===================================================================
--- drupal7.orig/modules/field_ui/field_ui.test
+++ drupal7/modules/field_ui/field_ui.test
@@ -445,6 +445,19 @@ class FieldUIManageFieldsTestCase extend
     $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
     $this->assertUrl($url, array(), 'Stayed on the same page.');
   }
+
+  /**
+   * Tests that external URLs in the 'destinations' query parameter are blocked.
+   */
+  function testExternalDestinations() {
+    $path = 'admin/structure/types/manage/article/fields/field_tags/field-settings';
+    $options = array(
+      'query' => array('destinations' => array('http://example.com')),
+    );
+    $this->drupalPost($path, NULL, t('Save field settings'), $options);
+
+    $this->assertUrl('admin/structure/types/manage/article/fields', array(), 'Stayed on the same site.');
+  }
 }
 
 /**
Index: drupal7/modules/openid/openid.module
===================================================================
--- drupal7.orig/modules/openid/openid.module
+++ drupal7/modules/openid/openid.module
@@ -365,14 +365,20 @@ function openid_complete($response = arr
             // to the OpenID Provider, we need to do discovery on the returned
             // identififer to make sure that the provider is authorized to
             // respond on behalf of this.
-            if ($response_claimed_id != $claimed_id) {
+            if ($response_claimed_id != $claimed_id || $response_claimed_id != $response['openid.identity']) {
               $discovery = openid_discovery($response['openid.claimed_id']);
+              $uris = array();
               if ($discovery && !empty($discovery['services'])) {
-                $uris = array();
                 foreach ($discovery['services'] as $discovered_service) {
-                  if (in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) || in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
-                    $uris[] = $discovered_service['uri'];
+                  if (!in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) && !in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
+                    continue;
                   }
+                  // The OP-Local Identifier (if different than the Claimed
+                  // Identifier) must be present in the XRDS document.
+                  if ($response_claimed_id != $response['openid.identity'] && (!isset($discovered_service['identity']) || $discovered_service['identity'] != $response['openid.identity'])) {
+                    continue;
+                  }
+                  $uris[] = $discovered_service['uri'];
                 }
               }
               if (!in_array($service['uri'], $uris)) {
Index: drupal7/modules/openid/openid.test
===================================================================
--- drupal7.orig/modules/openid/openid.test
+++ drupal7/modules/openid/openid.test
@@ -94,7 +94,7 @@ class OpenIDFunctionalTestCase extends O
     $identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE, 'fragment' => $this->randomName()));
     // Tell openid_test.module to respond with this identifier. If the fragment
     // part is present in the identifier, it should be retained.
-    variable_set('openid_test_response', array('openid.claimed_id' => $identity));
+    variable_set('openid_test_response', array('openid.claimed_id' => $identity, 'openid.identity' => openid_normalize($identity)));
     $this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity);
     variable_set('openid_test_response', array());
 
Index: drupal7/modules/openid/tests/openid_test.module
===================================================================
--- drupal7.orig/modules/openid/tests/openid_test.module
+++ drupal7/modules/openid/tests/openid_test.module
@@ -150,6 +150,7 @@ function openid_test_yadis_xrds() {
           <Service priority="20">
             <Type>http://specs.openid.net/auth/2.0/server</Type>
             <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
+            <LocalID>' . url('openid-test/yadis/xrds/server', array('absolute' => TRUE)) . '</LocalID>
           </Service>';
     }
     elseif (arg(3) == 'delegate') {
Index: drupal7/modules/overlay/overlay-parent.js
===================================================================
--- drupal7.orig/modules/overlay/overlay-parent.js
+++ drupal7/modules/overlay/overlay-parent.js
@@ -390,6 +390,27 @@ Drupal.overlay.isExternalLink = function
 };
 
 /**
+ * Constructs an internal URL (relative to this site) from the provided path.
+ *
+ * For example, if the provided path is 'admin' and the site is installed at
+ * http://example.com/drupal, this function will return '/drupal/admin'.
+ *
+ * @param path
+ *   The internal path, without any leading slash.
+ *
+ * @return
+ *   The internal URL derived from the provided path, or null if a valid
+ *   internal path cannot be constructed (for example, if an attempt to create
+ *   an external link is detected).
+ */
+Drupal.overlay.getInternalUrl = function (path) {
+  var url = Drupal.settings.basePath + path;
+  if (!this.isExternalLink(url)) {
+    return url;
+  }
+};
+
+/**
  * Event handler: resizes overlay according to the size of the parent window.
  *
  * @param event
@@ -577,7 +598,7 @@ Drupal.overlay.eventhandlerOverrideLink
       // If the link contains the overlay-restore class and the overlay-context
       // state is set, also update the parent window's location.
       var parentLocation = ($target.hasClass('overlay-restore') && typeof $.bbq.getState('overlay-context') == 'string')
-        ? Drupal.settings.basePath + $.bbq.getState('overlay-context')
+        ? this.getInternalUrl($.bbq.getState('overlay-context'))
         : null;
       href = this.fragmentizeLink($target.get(0), parentLocation);
       // Only override default behavior when left-clicking and user is not
@@ -657,11 +678,15 @@ Drupal.overlay.eventhandlerOperateByURLF
   }
 
   // Get the overlay URL from the current URL fragment.
+  var internalUrl = null;
   var state = $.bbq.getState('overlay');
   if (state) {
+    internalUrl = this.getInternalUrl(state);
+  }
+  if (internalUrl) {
     // Append render variable, so the server side can choose the right
     // rendering and add child frame code to the page if needed.
-    var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' });
+    var url = $.param.querystring(internalUrl, { render: 'overlay' });
 
     this.open(url);
     this.resetActiveClass(this.getPath(Drupal.settings.basePath + state));
Index: drupal7/modules/simpletest/tests/common.test
===================================================================
--- drupal7.orig/modules/simpletest/tests/common.test
+++ drupal7/modules/simpletest/tests/common.test
@@ -1913,7 +1913,7 @@ class DrupalRenderTestCase extends Drupa
   }
 
   /**
-   * Tests caching of an empty render item.
+   * Tests caching of render items.
    */
   function testDrupalRenderCache() {
     // Force a request via GET.
@@ -1939,6 +1939,59 @@ class DrupalRenderTestCase extends Drupa
     drupal_render($element);
     $this->assertFalse(isset($element['#printed']), 'Cache hit');
 
+    // Test that user 1 does not share the cache with other users who have the
+    // same roles, even when DRUPAL_CACHE_PER_ROLE is used.
+    $user1 = user_load(1);
+    $first_authenticated_user = $this->drupalCreateUser();
+    $second_authenticated_user = $this->drupalCreateUser();
+    $user1->roles = array_intersect_key($user1->roles, array(DRUPAL_AUTHENTICATED_RID => TRUE));
+    user_save($user1);
+    // Load all the accounts again, to make sure we have complete account
+    // objects.
+    $user1 = user_load(1);
+    $first_authenticated_user = user_load($first_authenticated_user->uid);
+    $second_authenticated_user = user_load($second_authenticated_user->uid);
+    $this->assertEqual($user1->roles, $first_authenticated_user->roles, 'User 1 has the same roles as an authenticated user.');
+    // Impersonate user 1 and render content that only user 1 should have
+    // permission to see.
+    $original_user = $GLOBALS['user'];
+    $original_session_state = drupal_save_session();
+    drupal_save_session(FALSE);
+    $GLOBALS['user'] = $user1;
+    $test_element = array(
+      '#cache' => array(
+        'keys' => array('test'),
+        'granularity' => DRUPAL_CACHE_PER_ROLE,
+      ),
+    );
+    $element = $test_element;
+    $element['#markup'] = 'content for user 1';
+    $output = drupal_render($element);
+    $this->assertEqual($output, 'content for user 1');
+    // Verify the cache is working by rendering the same element but with
+    // different markup passed in; the result should be the same.
+    $element = $test_element;
+    $element['#markup'] = 'should not be used';
+    $output = drupal_render($element);
+    $this->assertEqual($output, 'content for user 1');
+    // Verify that the first authenticated user does not see the same content
+    // as user 1.
+    $GLOBALS['user'] = $first_authenticated_user;
+    $element = $test_element;
+    $element['#markup'] = 'content for authenticated users';
+    $output = drupal_render($element);
+    $this->assertEqual($output, 'content for authenticated users');
+    // Verify that the second authenticated user shares the cache with the
+    // first authenticated user.
+    $GLOBALS['user'] = $second_authenticated_user;
+    $element = $test_element;
+    $element['#markup'] = 'should not be used';
+    $output = drupal_render($element);
+    $this->assertEqual($output, 'content for authenticated users');
+    // Restore the original logged-in user.
+    $GLOBALS['user'] = $original_user;
+    drupal_save_session($original_session_state);
+
     // Restore the previous request method.
     $_SERVER['REQUEST_METHOD'] = $request_method;
   }
