diff --git a/core/includes/common.inc b/core/includes/common.inc
index b78b7f3..ead2e4a 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -5172,13 +5172,9 @@ function drupal_cron_run() {
   // Allow execution to continue even if the request gets canceled.
   @ignore_user_abort(TRUE);
 
-  // Prevent session information from being saved while cron is running.
-  drupal_save_session(FALSE);
-
   // Force the current user to anonymous to ensure consistent permissions on
   // cron runs.
-  $original_user = $GLOBALS['user'];
-  $GLOBALS['user'] = drupal_anonymous_user();
+  user_impersonate_user(drupal_anonymous_user());
 
   // Try to allocate enough time to run all the hook_cron implementations.
   drupal_set_time_limit(240);
@@ -5238,8 +5234,7 @@ function drupal_cron_run() {
     }
   }
   // Restore the user.
-  $GLOBALS['user'] = $original_user;
-  drupal_save_session(TRUE);
+  user_revert_user();
 
   return $return;
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index 902fe6a..f362de9 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -622,7 +622,10 @@ abstract class TestBase {
     // Save further contextual information.
     $this->originalFileDirectory = variable_get('file_public_path', conf_path() . '/files');
     $this->originalProfile = drupal_get_profile();
-    $this->originalUser = $user;
+
+    // Impersonate the current user so it we can use user_revert_user() to
+    // revert back to that user after the test finishes.
+    user_impersonate_user($user);
 
     // Save and clean the shutdown callbacks array because it is static cached
     // and will be changed by the test run. Otherwise it will contain callbacks
@@ -713,8 +716,7 @@ abstract class TestBase {
     $callbacks = $this->originalShutdownCallbacks;
 
     // Restore original user session.
-    $user = $this->originalUser;
-    drupal_save_session(TRUE);
+    user_revert_user();
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php
index 7a71d33..8455476 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/FormatDateTest.php
@@ -103,11 +103,10 @@ class FormatDateTest extends WebTestBase {
     $edit = array('preferred_langcode' => self::LANGCODE, 'mail' => $test_user->mail, 'timezone' => 'America/Los_Angeles');
     $this->drupalPost('user/' . $test_user->uid . '/edit', $edit, t('Save'));
 
-    // Disable session saving as we are about to modify the global $user.
+    // Switch to the test user.
+    user_impersonate_user(user_load($test_user->uid));
+
     drupal_save_session(FALSE);
-    // Save the original user and language and then replace it with the test user and language.
-    $real_user = $user;
-    $user = user_load($test_user->uid, TRUE);
     $real_language = $language_interface->langcode;
     $language_interface->langcode = $user->preferred_langcode;
     // Simulate a Drupal bootstrap with the logged-in user.
@@ -130,7 +129,7 @@ class FormatDateTest extends WebTestBase {
     $this->assertIdentical(format_date($timestamp, 'html_year'), '2007', t('Test html_year date format.'));
 
     // Restore the original user and language, and enable session saving.
-    $user = $real_user;
+    user_revert_user();
     $language_interface->langcode = $real_language;
     // Restore default time zone.
     date_default_timezone_set(drupal_get_user_timezone());
diff --git a/core/modules/system/lib/Drupal/system/Tests/File/ValidatorTest.php b/core/modules/system/lib/Drupal/system/Tests/File/ValidatorTest.php
index 3f8e548..8cfbe75 100644
--- a/core/modules/system/lib/Drupal/system/Tests/File/ValidatorTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/File/ValidatorTest.php
@@ -131,19 +131,17 @@ class ValidatorTest extends WebTestBase {
    * Test file_validate_size().
    */
   function testFileValidateSize() {
-    global $user;
-    $original_user = $user;
-    drupal_save_session(FALSE);
-
     // Run these test as uid = 1.
-    $user = user_load(1);
+    user_impersonate_user(user_load(1));
 
     $file = entity_create('file', array('filesize' => 999999));
     $errors = file_validate_size($file, 1, 1);
     $this->assertEqual(count($errors), 0, t('No size limits enforced on uid=1.'), 'File');
+    user_revert_user();
 
     // Run these tests as a regular user.
     $user = $this->drupalCreateUser();
+    user_impersonate_user($user);
 
     // Create a file with a size of 1000 bytes, and quotas of only 1 byte.
     $file = entity_create('file', array('filesize' => 1000));
@@ -156,7 +154,6 @@ class ValidatorTest extends WebTestBase {
     $errors = file_validate_size($file, 1, 1);
     $this->assertEqual(count($errors), 2, t('Errors for both the file and their limit.'), 'File');
 
-    $user = $original_user;
-    drupal_save_session(TRUE);
+    user_revert_user();
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index e990fe7..0f65d44 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -77,7 +77,7 @@ abstract class UpgradePathTestBase extends WebTestBase {
    * @see Drupal\simpletest\WebTestBase::prepareEnvironment()
    */
   protected function setUp() {
-    global $user, $conf;
+    global $conf;
 
     // Load the Update API.
     require_once DRUPAL_ROOT . '/core/includes/update.inc';
@@ -125,11 +125,9 @@ abstract class UpgradePathTestBase extends WebTestBase {
 
     $this->pass('Finished loading the dump.');
 
-    // Ensure that the session is not written to the new environment and replace
-    // the global $user session with uid 1 from the new test site.
-    drupal_save_session(FALSE);
-    // Login as uid 1.
-    $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject();
+    // A direct query is used instead of user_load() because the latter would
+    // trigger hook_entity_info(), which is not available yet.
+    user_impersonate_user(db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject());
 
     // Generate and set a D8-compatible session cookie.
     $this->prepareD8Session();
@@ -143,6 +141,16 @@ abstract class UpgradePathTestBase extends WebTestBase {
   }
 
   /**
+   * Overrides Drupal\simpletest\WebTestBase::tearDown() for upgrade testing.
+   */
+  function tearDown() {
+    // Revert to the original user.
+    user_revert_user();
+
+    parent::tearDown();
+  }
+
+  /**
    * Specialized variable_set() that works even if the child site is not upgraded.
    *
    * @param $name
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserImpersonatingUserTest.php b/core/modules/user/lib/Drupal/user/Tests/UserImpersonatingUserTest.php
new file mode 100644
index 0000000..c6d78f0
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Tests/UserImpersonatingUserTest.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\Tests\UserImpersonatingUserTest.
+ */
+
+namespace Drupal\user\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test case for impersonating users.
+ */
+class UserImpersonatingUserTest extends WebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Impersonate users',
+      'description' => 'Temporarily impersonate another user, and then restore the original user.',
+      'group' => 'User',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+  }
+
+  function testUserImpersonateUser() {
+    global $user;
+    $original_user = $user;
+
+    // If not currently logged in, use user_user_impersonate_user() to switch to
+    // user 1. If logged in, switch to the anonymous user instead.
+    if (user_is_anonymous()) {
+      user_impersonate_user(1);
+    }
+    else {
+      user_impersonate_user(0);
+    }
+
+    // Verify that the active user has changed, and that session saving is
+    // disabled.
+    $this->assertEqual($user->uid, ($original_user->uid == 0 ? 1 : 0), t('User switched'));
+    $this->assertFalse(drupal_save_session(), t('Session saving is disabled.'));
+
+    // Perform a second (nested) impersonation.
+    user_impersonate_user(1);
+    $this->assertEqual($user->uid, 1, t('User switched.'));
+
+    // Revert to the user which was active between the first and second
+    // impersonation attempt.
+    user_revert_user();
+
+    // Since we are still impersonating the user from the first attempt,
+    // session handling still needs to be disabled.
+    $this->assertEqual($user->uid, ($original_user->uid == 0 ? 1 : 0), t('User switched.'));
+    $this->assertFalse(drupal_save_session(), t('Session saving is disabled.'));
+
+    // Revert to the original user which was active before the first
+    // impersonation attempt.
+    user_revert_user();
+
+    // Assert that the original user is the active user again, and that session
+    // saving has been re-enabled.
+    $this->assertEqual($user->uid, $original_user->uid, t('Original user successfully restored.'));
+
+    // Simpletest uses user_impersonate_user() too, revert the impersonation by
+    // Simpletest to enable session saving again. This is safe because calling
+    // user_revert_user() too often simply results in returning the active user.
+    user_revert_user();
+    $this->assertTrue(drupal_save_session(), t('Session saving is enabled.'));
+  }
+}
\ No newline at end of file
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 89706c4..9cada07 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -3726,3 +3726,63 @@ function user_file_download_access($field, $entity_type, $entity) {
     return user_view_access($entity);
   }
 }
+
+/**
+ * Impersonates another user.
+ *
+ * Each time this function is called, the active user is saved and $new_user
+ * becomes the active user. Multiple calls to this function can be nested,
+ * and session saving will be disabled until all impersonation attempts have
+ * been reverted using user_revert_user().
+ *
+ * @param $new_user
+ *   User to impersonate, either a UID or a user object.
+ *
+ * @return
+ *   Current user object.
+ *
+ * @see user_revert_user()
+ */
+function user_impersonate_user($new_user = NULL) {
+  global $user;
+  $user_original = &drupal_static(__FUNCTION__);
+
+  if (!isset($new_user)) {
+    if (isset($user_original) && !empty($user_original)) {
+      // Restore the previous user from the stack.
+      $user = array_pop($user_original);
+
+      // Re-enable session saving if we are no longer impersonating a user.
+      if (empty($user_original)) {
+        drupal_save_session(TRUE);
+      }
+    }
+  }
+  else {
+    // Push the original user onto the stack and prevent session saving.
+    $user_original[] = $user;
+    drupal_save_session(FALSE);
+
+    if (is_numeric($new_user)) {
+      $user = user_load($new_user);
+    }
+    else {
+      $user = is_object($new_user) ? $new_user : (object) $new_user;
+    }
+  }
+
+  return $user;
+}
+
+/**
+ * Reverts to the previous user after impersonating.
+ *
+ * @return
+ *   The user object for the user that was active prior to the currently
+ *   impersonated user.
+ *
+ * @see user_impersonate_user()
+ */
+function user_revert_user() {
+  return user_impersonate_user();
+}
\ No newline at end of file
