From 6a532292d853e2d294db542ee0a45d5b8763d421 Mon Sep 17 00:00:00 2001 From: albertlast Date: Mon, 10 Aug 2026 13:28:49 +0200 Subject: [PATCH] Loads the guest data when verification demotes a member to one User::loadMe() asks loadUserData() for the member the cookie claims, so User::$profiles[0] is only ever built when that claim is already 0. Both verifyPassword() and verifyTfa() can then reset User::$my_id back to 0, and everything after them -- initializeGuest(), setProperties() -- reads User::$profiles[0], which was never there. setProperties() ends at $this->dataset = $profile['dataset']; and assigning null to a typed property is fatal, so the request dies with "Cannot assign null to property SMF\User::$dataset of type SMF\UserDataset". Two ways in, both of which a member can reach without doing anything odd: - Anyone with a tfa_secret is sent to ?action=logintfa, where verifyTfa() resets the ID on purpose so the action can check the code. The page cannot render at all, so two factor authentication cannot be completed. - Anyone deactivated or banned while holding a valid login cookie fails verifyPassword() on their next page load and gets the same fatal instead of being turned back into a guest. Loads the guest's data at the point we learn we need it, which is what a visitor who arrived as a guest already gets. Signed-off-by: Mathias Albert Signed-off-by: albertlast --- Sources/User.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/User.php b/Sources/User.php index 8cac685d35..634c987043 100644 --- a/Sources/User.php +++ b/Sources/User.php @@ -2914,6 +2914,16 @@ public static function loadMe(): self self::$me->verifyPassword(); self::$me->verifyTfa(); + /* + * Either of those can decide this is a guest after all, and the + * guest's data has not been loaded: the call above asked only for + * the member they claimed to be. Everything below here reads + * self::$profiles[0], so load it now that we know we need it. + */ + if (empty(self::$my_id) && !isset(self::$profiles[0])) { + self::loadUserData([0], self::LOAD_BY_ID, UserDataset::Minimal); + } + // At this point, we know the user ID for sure. self::$me->id = self::$my_id; self::$cookie_id = self::$my_id;