From: Vsevolod Stakhov Date: Fri, 10 Jul 2026 13:16:11 +0000 (+0100) Subject: [Fix] lua_selectors: reject selectors that parse only as a prefix X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5eb40ae6315504bbfefdca9bd274c6d68f2c705a;p=thirdparty%2Frspamd.git [Fix] lua_selectors: reject selectors that parse only as a prefix The selector grammar was not anchored to the end of input, so lpeg matched the longest valid prefix and silently dropped the rest of the string. E.g. 'from("smtp"):domain:lower' (a second ':' cannot parse) was accepted and evaluated as 'from("smtp"):domain', and any trailing garbage after a selector or a ';' list element was ignored. All consumers (multimap, rbl, ratelimit, settings, reputation, the controller selector check used by the WebUI) reported such selectors as valid while evaluating only the prefix. Append an lpeg.Cp() capture after the grammar (plus optional trailing whitespace) and make parse_selector require the whole input to be consumed, logging the position and the unparsed tail otherwise. User-visible change: selectors that previously loaded thanks to the silent truncation now fail configuration load with an error pointing at the offending token. Such selectors were never evaluating as written, so failing loudly is the correct behaviour. --- diff --git a/lualib/lua_selectors/init.lua b/lualib/lua_selectors/init.lua index a9632bafab..5d3860967b 100644 --- a/lualib/lua_selectors/init.lua +++ b/lualib/lua_selectors/init.lua @@ -280,7 +280,7 @@ local function make_grammar() NAMED_ARG = (l.Ct("") * l.Cg(argument * eqsign * (argument + l.V("LIST_ARGS")) * comma ^ 0) ^ 0), LIST_ARGS = l.Ct(tbl_obrace * l.V("LIST_ARG") * tbl_ebrace), LIST_ARG = l.Cg(argument * comma ^ 0) ^ 0, - } + } * spc * l.Cp() end local parser = make_grammar() @@ -296,6 +296,16 @@ exports.parse_selector = function(cfg, str) return nil end + -- The last capture is the parse end position (lpeg.Cp); lpeg matches the + -- longest valid prefix, so anything left after that position is a parse + -- error, not a selector to be silently ignored + local last_pos = table.remove(parsed) + if last_pos <= #str then + logger.errx(cfg, "invalid selector '%s': unexpected token at position %d ('%s')", + str, last_pos, str:sub(last_pos)) + return nil + end + local function check_args(name, schema, args) if schema then if getmetatable(schema) then