From: Vsevolod Stakhov Date: Sun, 5 Jul 2026 11:43:13 +0000 (+0100) Subject: [Fix] lua_selectors: don't crash on a missing method call X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eb7fc38bec41111ec5b29c3ccdef032ee09145d3;p=thirdparty%2Frspamd.git [Fix] lua_selectors: don't crash on a missing method call Selectors like `time:digest` (method syntax used where a transform was meant) aborted the whole selector at scan time with an uncaught Lua error: Cannot run callback: .../lua_selectors/init.lua: attempt to call a nil value The `:` separator always compiles to a method call on the extracted value; when the value has no such method, the lookup yields nil and the unguarded call crashed the callback. Guard the lookup (via pcall, since indexing some types raises) and yield no value instead, logging an error that hints at the `.name` transform syntax when the name matches a known transform. Valid method calls (e.g. `:gsub`, `:lower`) and table field access are unaffected. --- diff --git a/lualib/lua_selectors/init.lua b/lualib/lua_selectors/init.lua index bfb012a692..a9632bafab 100644 --- a/lualib/lua_selectors/init.lua +++ b/lualib/lua_selectors/init.lua @@ -47,6 +47,11 @@ local function pure_type(ltype) return ltype:match('^(.*)_list$') end +-- Indexing values of arbitrary types can raise (e.g. numbers), so it is done via pcall +local function safe_index(obj, key) + return obj[key] +end + local function implicit_tostring(t, ud_or_table) if t == 'table' then -- Table (very special) @@ -382,8 +387,20 @@ exports.parse_selector = function(cfg, str) -- Plain table field ret = inp[method_name] else + local ok, meth = pcall(safe_index, inp, method_name) + + if not ok or type(meth) ~= 'function' then + if transform_function[method_name] then + logger.errx('cannot call method `%s` on %s value; use `.%s` to apply the transform of the same name', + method_name, t, method_name) + else + logger.errx('cannot call method `%s` on %s value', method_name, t) + end + return nil + end + -- We call method unpacking arguments and dropping all but the first result returned - ret = (inp[method_name](inp, unpack_function(args or E))) + ret = (meth(inp, unpack_function(args or E))) end local ret_type = type(ret) diff --git a/test/lua/unit/selectors.lua b/test/lua/unit/selectors.lua index 72876339bc..1d4f6dd523 100644 --- a/test/lua/unit/selectors.lua +++ b/test/lua/unit/selectors.lua @@ -420,6 +420,27 @@ context("Selectors test", function() assert_rspamd_table_eq_sorted({actual = elts, expect = case.expect}) end) end + + -- Method syntax (`:name`) on a value that has no such method must yield + -- nothing instead of raising a runtime error + local cases_nil = { + ["transform used as method"] = { + selector = "time:digest", + }, + ["missing method on string"] = { + selector = "header('Subject'):totally_missing_method(1)", + }, + } + for case_name, case in lua_util.spairs(cases_nil) do + test("nil case " .. case_name, function() + local sels = lua_selectors.create_selector_closure_fn(nil, cfg, case.selector, nil, + function(_, res, _) return res end) + assert_not_nil(sels) + local ok, res = pcall(sels, task) + assert_true(ok) + assert_nil(res) + end) + end end)