From: Fabien Potencier <fabien.potencier@gmail.com>
Date: Sun, 1 May 2016 06:13:23 +0200
Subject: CVE-2016-4423: Large username storage in session

Origin: upstream, https://github.com/symfony/symfony/pull/18733
---
 .../Security/Core/SecurityContextInterface.php     |  1 +
 .../UsernamePasswordFormAuthenticationListener.php |  5 ++
 ...rnamePasswordFormAuthenticationListenerTest.php | 78 ++++++++++++++++++++++
 3 files changed, 84 insertions(+)
 create mode 100644 src/Symfony/Component/Security/Tests/Http/Firewall/UsernamePasswordFormAuthenticationListenerTest.php

diff --git a/src/Symfony/Component/Security/Core/SecurityContextInterface.php b/src/Symfony/Component/Security/Core/SecurityContextInterface.php
index ca816a8..16d6d78 100644
--- a/src/Symfony/Component/Security/Core/SecurityContextInterface.php
+++ b/src/Symfony/Component/Security/Core/SecurityContextInterface.php
@@ -23,6 +23,7 @@ interface SecurityContextInterface
     const ACCESS_DENIED_ERROR  = '_security.403_error';
     const AUTHENTICATION_ERROR = '_security.last_error';
     const LAST_USERNAME        = '_security.last_username';
+    const MAX_USERNAME_LENGTH = 4096;
 
     /**
      * Returns the current security token.
diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
index 81c2b37..4f8a380 100644
--- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
@@ -20,6 +20,7 @@ use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterfa
 use Symfony\Component\Security\Http\HttpUtils;
 use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
 use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
 use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
 use Symfony\Component\Security\Core\SecurityContextInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -83,6 +84,10 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
             $password = $request->get($this->options['password_parameter'], null, true);
         }
 
+        if (strlen($username) > SecurityContextInterface::MAX_USERNAME_LENGTH) {
+            throw new BadCredentialsException('Invalid username.');
+        }
+
         $request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);
 
         return $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey));
diff --git a/src/Symfony/Component/Security/Tests/Http/Firewall/UsernamePasswordFormAuthenticationListenerTest.php b/src/Symfony/Component/Security/Tests/Http/Firewall/UsernamePasswordFormAuthenticationListenerTest.php
new file mode 100644
index 0000000..b7c6ab9
--- /dev/null
+++ b/src/Symfony/Component/Security/Tests/Http/Firewall/UsernamePasswordFormAuthenticationListenerTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Tests\Http\Firewall;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener;
+use Symfony\Component\Security\Core\SecurityContextInterface;
+
+class UsernamePasswordFormAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider getUsernameForLength
+     */
+    public function testHandleWhenUsernameLength($username, $ok)
+    {
+        $request = Request::create('/login_check', 'POST', array('_username' => $username));
+        $request->setSession($this->getMock('Symfony\Component\HttpFoundation\Session\SessionInterface'));
+
+        $httpUtils = $this->getMock('Symfony\Component\Security\Http\HttpUtils');
+        $httpUtils
+            ->expects($this->any())
+            ->method('checkRequestPath')
+            ->will($this->returnValue(true))
+        ;
+
+        $failureHandler = $this->getMock('Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface');
+        $failureHandler
+            ->expects($ok ? $this->never() : $this->once())
+            ->method('onAuthenticationFailure')
+            ->will($this->returnValue(new Response()))
+        ;
+
+        $authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager')->disableOriginalConstructor()->getMock();
+        $authenticationManager
+            ->expects($ok ? $this->once() : $this->never())
+            ->method('authenticate')
+            ->will($this->returnValue(new Response()))
+        ;
+
+        $listener = new UsernamePasswordFormAuthenticationListener(
+            $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'),
+            $authenticationManager,
+            $this->getMock('Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface'),
+            $httpUtils,
+            'TheProviderKey',
+            $this->getMock('Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface'),
+            $failureHandler,
+            array('require_previous_session' => false)
+        );
+
+        $event = $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false);
+        $event
+            ->expects($this->any())
+            ->method('getRequest')
+            ->will($this->returnValue($request))
+        ;
+
+        $listener->handle($event);
+    }
+
+    public function getUsernameForLength()
+    {
+        return array(
+            array(str_repeat('x', SecurityContextInterface::MAX_USERNAME_LENGTH + 1), false),
+            array(str_repeat('x', SecurityContextInterface::MAX_USERNAME_LENGTH - 1), true),
+        );
+    }
+}
