Skip to content

PS-9704 [8.0]: Escape all RFC 4515 special chars in LDAP filter substitutions - #5928

Open
VarunNagaraju wants to merge 2 commits into
percona:8.0from
VarunNagaraju:PS-9704-8.0
Open

PS-9704 [8.0]: Escape all RFC 4515 special chars in LDAP filter substitutions#5928
VarunNagaraju wants to merge 2 commits into
percona:8.0from
VarunNagaraju:PS-9704-8.0

Conversation

@VarunNagaraju

@VarunNagaraju VarunNagaraju commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

https://perconadev.atlassian.net/browse/PS-9704

User-supplied values (user_name, user_dn) substituted into LDAP search
filters via {UA} and {UD} were not properly escaped. Only double-quote
received a partial treatment; parentheses, asterisk, and backslash were
passed through verbatim, producing malformed filters that silently broke
group lookups for users whose DN contained those characters.

Add ldap_filter_escape() which applies the mandatory RFC 4515 §3 \XX
hex encoding for LPAREN (\28), RPAREN (\29), ASTERISK (\2a), and
ESC (\5c). Apply it to all three substitution sites: search_dn() for
user_name, and search_groups() for both {UA} (user_name) and {UD}
(user_dn).

Fix heap-use-after-free when granting external roles during login.
acl_authenticate() was calling grant_role() with the acl_user pointer
from mpvio, which is a copy allocated on the connection's mem_root and
freed when the command ends. grant_role() stores ACL_USER by value in
the role graph, retaining the raw user/host char* pointers. Subsequent
DROP USER walks that graph and dereferences those freed pointers, causing
corrupted reads and Warning 1366 on the role_edges table columns.

Fix: look up the durable ACL cache entry via find_acl_user() and pass
that to grant_role() instead of the mem_root copy. This ensures the
role graph holds pointers that remain valid for the lifetime of the
ACL cache.

MTR test exercises DN containing a double-quote, parentheses, an
asterisk, and a backslash — all expected to authenticate and receive
their group role via the dn_test_group groupOfNames entry.

@VarunNagaraju VarunNagaraju changed the title PS-9704 (8.0) Fix auth_ldap bug with unescaped parentheses in user DN PS-9704 [8.0: Fix auth_ldap bug with unescaped parentheses in user DN May 7, 2026
@VarunNagaraju
VarunNagaraju marked this pull request as ready for review May 7, 2026 10:55
@VarunNagaraju VarunNagaraju changed the title PS-9704 [8.0: Fix auth_ldap bug with unescaped parentheses in user DN PS-9704 [8.0]: Escape all RFC 4515 special chars in LDAP filter substitutions May 7, 2026
Comment thread plugin/auth_ldap/src/connection.cc Outdated
Comment thread plugin/auth_ldap/src/connection.cc Outdated
Comment thread plugin/auth_ldap/src/connection.cc Outdated
Comment thread sql/auth/sql_authorization.cc
Comment thread plugin/auth_ldap/src/connection.cc Outdated
std::string out;
out.reserve(input.size() * 3); // worst case every byte becomes "\XX"

for (unsigned char c : input) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have unsigned char here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically, raw byte manipulation is done using unsigned char and It covers the cases in the future if the input contains any non-ascii values, then the comparison would be accurate and the default case casts back to char.
But, we could modify it to char since MySQL usernames and LDAP DNs, both are constrained to valid UTF-8.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point here is that you have a sequence of chars (std::string), than you iterate over individual characters performing conversions to unsigned chars, and after that you use switch statement in which you have case: clauses with literals of type char ('(' for instance). Moreover, in the default: clause you do static_cast back to original char again. This can work (I do not see any problems that these conversions will cause any value-changing side effects), but I just don't see the reason why we need it as we do not do any computations on the unsigned values here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. The sole reason was to handle any non-ascii values in the future. But, it's not entirely necessary for now. Will update it to char.

@jankowsk jankowsk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend having sql/auth/sql_authentication.cc fix in a separate commit as it is not directly linked to this PS. You may use a proper description of the problem and link to PS in the commit message. See 60a906a

VarunNagaraju and others added 2 commits June 10, 2026 15:54
…ions

https://perconadev.atlassian.net/browse/PS-9704

User-supplied values (user_name, user_dn) substituted into LDAP search
filters via {UA} and {UD} were not properly escaped. Only double-quote
received a partial treatment; parentheses, asterisk, and backslash were
passed through verbatim, producing malformed filters that silently broke
group lookups for users whose DN contained those characters.

Add ldap_filter_escape() which applies the mandatory RFC 4515 §3 \XX
hex encoding for LPAREN (\28), RPAREN (\29), ASTERISK (\2a), and
ESC (\5c). Apply it to all three substitution sites: search_dn() for
user_name, and search_groups() for both {UA} (user_name) and {UD}
(user_dn).

MTR test exercises DN containing a double-quote, parentheses, an
asterisk, and a backslash — all expected to authenticate and receive
their group role via the dn_test_group groupOfNames entry.
https://perconadev.atlassian.net/browse/PS-11253

Fix heap-use-after-free when granting external roles during login.
acl_authenticate() was calling grant_role() with the acl_user pointer
from mpvio, which is a copy allocated on the connection's mem_root and
freed when the command ends. grant_role() stores ACL_USER by value in
the role graph, retaining the raw user/host char* pointers. Subsequent
DROP USER walks that graph and dereferences those freed pointers, causing
corrupted reads and Warning 1366 on the role_edges table columns.

Fix: look up the durable ACL cache entry via find_acl_user() and pass
that to grant_role() instead of the mem_root copy. This ensures the
role graph holds pointers that remain valid for the lifetime of the
ACL cache.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants