Skip to content

fix: Several flaws in the ldap filter parser - #634

Draft
cpuschma wants to merge 7 commits into
go-ldap:masterfrom
cpuschma:fix/ldap-filter
Draft

cpuschma wants to merge 7 commits into
go-ldap:masterfrom
cpuschma:fix/ldap-filter

Conversation

@cpuschma

Copy link
Copy Markdown
Member
  • Check the byte closing a filter list or nested filter
  • Reject substring filters that would encode an empty SEQUENCE
  • Match the dnattrs marker case-insensitively
  • Accept a literal U+FFFD in filter values
  • Validate attribute descriptions when compiling filters
  • Require an attribute or matching rule in extensible filters

Closes #633.

compileFilterSet skipped the byte at pos as the closing parenthesis of an
AND/OR filter set, and compileFilter's '(' case skipped the byte after a
nested filter, both without looking at it. A filter truncated after a child
and padded with any character, or one with a typo in place of its closing
parenthesis, was therefore accepted and compiled as a different filter.

Reject those with "unexpected end of filter" instead.
A condition of two or more '*' and nothing else (for example (sn=**)) is not
the present filter, but contains '*', so compileFilter built a Substrings
packet whose SEQUENCE had every empty part skipped. RFC 4511 4.5.1.7.2
requires substrings ::= SEQUENCE SIZE (1..MAX), and DecompileFilter turned
the result back into (sn=), an equality match with the empty value.

Reject a substring filter with no substrings instead.
RFC 4515 defines dnattrs = COLON "dn", and ABNF string literals are
case-insensitive (RFC 5234 2.3), but compileFilter compared the bytes with
strings.HasPrefix literally. Filters such as (o:DN:=x) were compiled as a
matching rule named "DN" and sent to the server as a request for an unknown
matching rule. Compare with strings.EqualFold instead.
Detecting invalid UTF-8 by comparing the decoded rune with utf8.RuneError
ignores the width returned by DecodeRuneInString: a genuine U+FFFD (bytes
EF BF BD, a valid UTFMB under RFC 4515) is reported as "error reading rune
at position N". The escaped form (cn=a\ef\bf\bdb) compiled, so the round
trip only worked one way.

Check for a width of 1 as well, in compileFilter and decodeEscapedSymbols.
compileFilter accumulated every rune up to the operator as the attribute, so
(=v), (a b=v) and (a\2ab=v) compiled and went on the wire. Since the
attribute is not unescaped, CompileFilter and DecompileFilter disagreed
about it: (a\2ab=v) compiled with the literal attribute a\2ab and
decompiled as (a\5c2ab=v), which compiles to yet another attribute.

Validate the attribute description against RFC 4512 (descr or numericoid,
with optional ";" options) before building the packet, for both regular and
extensible filters that carry an attribute.
RFC 4515 requires a matching rule when the attribute is absent from an
extensible match, but compileFilter's ":=" and ":dn:=" cases never checked
that anything preceded them. (:=a) compiled to a MatchingRuleAssertion with
only a matchValue, and (:dn:=a) to one that adds dnAttributes; with both the
type and matchingRule absent the assertion is meaningless.

Reject an extensible filter that has neither.
@cpuschma cpuschma added bug go Pull requests that update go code labels Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CompileFilter accepts (&(a=b)x and compiles it as (&(a=b))

1 participant