From 5ccde49f9901548378f0efcc9e188b14d070fc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 3 Nov 2026 12:41:10 +0100 Subject: [PATCH 1/2] fix: Reject login with NUL bytes rather than converting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Côme Chilliet --- lib/private/User/Manager.php | 7 ++++++- tests/lib/User/ManagerTest.php | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 33744ad7ffe45..dd36c250ea21f 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -243,7 +243,12 @@ public function checkPassword($loginName, $password) { * @return IUser|false the User object on success, false otherwise */ public function checkPasswordNoLogging($loginName, $password) { - $loginName = str_replace("\0", '', $loginName); + // A NUL byte in the login name must not turn it into a second name for an + // account: the checks done before the password check (e.g. 2FA enforcement + // in Session::logClientIn()) see the name as sent, so the name has to stay the same + if (str_contains($loginName, "\0")) { + return false; + } $password = str_replace("\0", '', $password); $cachedBackend = $this->cache->get($loginName); diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php index c8c1430d583e4..c74e2c26d68f5 100644 --- a/tests/lib/User/ManagerTest.php +++ b/tests/lib/User/ManagerTest.php @@ -198,7 +198,27 @@ public function testCheckPasswordNotSupported() { $this->assertFalse($manager->checkPassword('foo', 'bar')); } - public function testGetOneBackendExists() { + public function testCheckPasswordNullByteInLoginName(): void { + $backend = $this->createMock(\Test\Util\User\Dummy::class); + $backend->expects($this->never()) + ->method('checkPassword'); + + $backend->expects($this->any()) + ->method('implementsActions') + ->willReturnCallback(function ($actions) { + if ($actions === BACKEND::CHECK_PASSWORD) { + return true; + } else { + return false; + } + }); + + $this->manager->registerBackend($backend); + + $this->assertFalse($this->manager->checkPassword("foo\0", 'bar')); + } + + public function testGetOneBackendExists(): void { /** * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend */ From 8456b891b84234bf6a27157c0154187da1c8d4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Wed, 4 Nov 2026 08:36:38 +0100 Subject: [PATCH 2/2] chore(tests): Fix user manager tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- tests/lib/User/ManagerTest.php | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/lib/User/ManagerTest.php b/tests/lib/User/ManagerTest.php index c74e2c26d68f5..8fb1610857c83 100644 --- a/tests/lib/User/ManagerTest.php +++ b/tests/lib/User/ManagerTest.php @@ -64,8 +64,7 @@ public function testGetBackends() { $this->assertEquals([$userDummyBackend, $dummyDatabaseBackend], $manager->getBackends()); } - - public function testUserExistsSingleBackendExists() { + public function testUserExistsSingleBackendExists(): void { /** * @var \Test\Util\User\Dummy | \PHPUnit\Framework\MockObject\MockObject $backend */ @@ -213,9 +212,10 @@ public function testCheckPasswordNullByteInLoginName(): void { } }); - $this->manager->registerBackend($backend); + $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger); + $manager->registerBackend($backend); - $this->assertFalse($this->manager->checkPassword("foo\0", 'bar')); + $this->assertFalse($manager->checkPassword("foo\0", 'bar')); } public function testGetOneBackendExists(): void { @@ -369,8 +369,7 @@ public function testCreateUserInvalid($uid, $password, $exception) { ->with(\OC\User\Backend::CREATE_USER) ->willReturn(true); - - $manager = new \OC\User\Manager($this->config, $this->oldDispatcher, $this->cacheFactory, $this->eventDispatcher); + $manager = new Manager($this->config, $this->cacheFactory, $this->eventDispatcher, $this->logger); $manager->registerBackend($backend); $this->expectException(\InvalidArgumentException::class, $exception); @@ -404,8 +403,7 @@ public function testCreateUserSingleBackendNotExists() { $this->assertEquals('foo', $user->getUID()); } - - public function testCreateUserSingleBackendExists() { + public function testCreateUserSingleBackendExists(): void { $this->expectException(\Exception::class); /** @@ -457,8 +455,7 @@ public function testCreateUserNoBackends() { $this->assertFalse($manager->createUser('foo', 'bar')); } - - public function testCreateUserFromBackendWithBackendError() { + public function testCreateUserFromBackendWithBackendError(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Could not create user'); @@ -476,8 +473,7 @@ public function testCreateUserFromBackendWithBackendError() { $manager->createUserFromBackend('MyUid', 'MyPassword', $backend); } - - public function testCreateUserTwoBackendExists() { + public function testCreateUserTwoBackendExists(): void { $this->expectException(\Exception::class); /**