Skip to content

[3.0] Keeps a reported profile's name inside the column that stores it - #9618

Open
albertlast wants to merge 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/report-membername-overflow
Open

[3.0] Keeps a reported profile's name inside the column that stores it#9618
albertlast wants to merge 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/report-membername-overflow

Conversation

@albertlast

@albertlast albertlast commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Description

Found while working on #9616, and kept separate from it because it is a different
column, a different code path and a different cause.

Reporting a profile labels the report with the member's display name and their
username in brackets after it:

$user_name = $user['real_name'] . ($user['real_name'] != $user['member_name'] ? ' (' . $user['member_name'] . ')' : '');

That goes into log_reported.membername, which is varchar(255). But
members.real_name is 255 wide on its own and members.member_name is 80, so the
pair has no guarantee of fitting. When it does not, the insert fails with

MySQL:      ERROR 1406 (22001): Data too long for column 'membername' at row 1
PostgreSQL: ERROR: value too long for type character varying(255)

How a display name gets long enough

real_name is limited to 60 characters, but the limit is counted on the characters
that were typed while the column stores the entity encoded form of them:

  • Sources/Actions/Profile/Main.php runs Utils::htmlspecialcharsRecursive($_POST, ENT_QUOTES)
    before validation, so by the time the value is checked a double quote is already
    " — six characters.
  • The validator is Utils::entityStrlen($value) > 60, which decodes first, so it
    sees the six characters as one again.

A display name of 42 double quotes is therefore 42 characters to the validator, well
inside the limit, and 252 characters in the column, inside varchar(255). Verified
against the real methods:

typed display name:       42 characters
validator (entityStrlen): 42 <= 60?  true
stored in real_name:      252 <= 255? true
report membername:        259 <= 255? false

Any username at all then pushes the label past 255 and the report cannot be filed.

Are those characters actually allowed in a name?

In a username, no. Security::validateUsername() rejects <>&"'=\ outright. But that
rule is only applied to member_name. The display name has its own validator, and the
only character it forbids is *, in Security::isReservedName().

Running that validator against a real database, as the admin, with the value encoded the
way Profile\Main encodes it before validation:

42 x "  -> encoded  252 chars, validator says: true
42 x &  -> encoded  210 chars, validator says: true
42 x <  -> encoded  168 chars, validator says: true
42 x '  -> encoded  210 chars, validator says: true
42 x *  -> rejected: 'The username you tried to use contains the reserved name "*"'

So the display name is the vector, and * being the one rejection confirms the check is
the real gate rather than a no-op.

This is adversarial rather than accidental — you do not reach it by having a long
name, you reach it by having a name made mostly of characters that need entities. It
is still a display name a member is allowed to set, and the failure lands on whoever
tries to report them, which is exactly the wrong person to punish for it.

The fix

Add the username only when there is room for it. The display name is what identifies
the member, it is what the moderation centre shows, and it came out of a column the
same width as this one, so on its own it always fits.

membername is only read as the fallback for a member who no longer exists —
ReportedContent selects COALESCE(mem.real_name, lr.membername) — so dropping the
bracketed username in this case costs very little, and costs it only for names that
would otherwise make the report impossible to file.

Measuring with mb_strlen() rather than Utils::entityStrlen() is deliberate: both
names arrive from the database already encoded, so the encoded length is what the
column has to hold. Config.php uses mb_strlen() the same way for the same kind of
"does this fit" question.

Verification

composer lint and vendor/bin/phpunit (280 tests, 495 assertions) pass.

No unit test: ReportToMod::reportMember() queries the database on its first line and
Utils::htmlspecialchars() is the only part of the path reachable from the suite. The
numbers above were produced by running the real Utils methods in the Docker
environment.

Issues References (Fixes|Related|Closes)

  1. Related to [3.0] Widens the report comment column to fit an entity encoded report #9616 — same file, same class of mistake, different column.

🤖 Generated with Claude Code

Reporting a profile labels the report with the member's display name and
their username in brackets after it. Both names come out of the database
entity encoded, and both are wide enough on their own that the pair does
not necessarily fit the 255 characters log_reported.membername holds:
real_name is 255 wide and member_name 80.

The display name gets there because the profile validator counts the
characters that were typed while the column stores the encoded form of
them. A display name of 42 double quotes is 42 characters to the
validator, well inside the limit of 60, and 252 characters in the
column. Reporting that profile builds a 259 character label, and the
insert fails with "Data too long for column 'membername'".

Adds the username only when there is room for it. The display name is
what identifies the member, and it came out of a column the same width,
so it always fits on its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant