From 00c7c629771af48e4bc0756ac9b2eb2bba26d583 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Mon, 28 May 2012 17:26:38 +0400 Subject: [PATCH] * Avoid DoS while sending a message with a lot of recipients in 'To' header. Fix possible deadlock in lua_redis. Version is now 0.4.8 as there are enough changes. --- CMakeLists.txt | 2 +- conf/lua/regexp/headers.lua | 45 ++++++++++++++++++++++------------- src/lua/lua_redis.c | 19 +++++++++++---- src/lua/lua_task.c | 17 +++++++++++++ src/plugins/lua/ratelimit.lua | 13 +++++++++- 5 files changed, 73 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc15f86c8..690ac352d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ PROJECT(rspamd C) SET(RSPAMD_VERSION_MAJOR 0) SET(RSPAMD_VERSION_MINOR 4) -SET(RSPAMD_VERSION_PATCH 7) +SET(RSPAMD_VERSION_PATCH 8) SET(RSPAMD_VERSION "${RSPAMD_VERSION_MAJOR}.${RSPAMD_VERSION_MINOR}.${RSPAMD_VERSION_PATCH}") diff --git a/conf/lua/regexp/headers.lua b/conf/lua/regexp/headers.lua index 0af26646d..11460961d 100644 --- a/conf/lua/regexp/headers.lua +++ b/conf/lua/regexp/headers.lua @@ -415,30 +415,37 @@ reconf['FORGED_GENERIC_RECEIVED3'] = 'Received=/^\\s*(.+\\n)*by \\d{1,3}\\.\\d{1 reconf['FORGED_GENERIC_RECEIVED4'] = 'Received=/^\\s*(.+\\n)*from localhost by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0[\\s\\r\\n]*$/X' reconf['FORGED_GENERIC_RECEIVED5'] = function (task) - local regexp_text = 'Received:\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*Received:\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0\\n' + local regexp_text = '^\\s*from \\[(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\\].*\\n(.+\\n)*\\s*from \\1 by \\S+;\\s+\\w{3}, \\d+ \\w{3} 20\\d\\d \\d\\d\\:\\d\\d\\:\\d\\d [+-]\\d\\d\\d0$' local re = regexp.get_cached(regexp_text) if not re then re = regexp.create(regexp_text, 'i') end - local res = re:match(task:get_raw_headers()) - if res then - return true - else - return false - end + local headers_recv = task:get_raw_header('Received') + if headers_recv then + for _,header_r in ipairs(headers_recv) do + if re:match(header_r['value']) then + return true + end + end + end + return false end reconf['INVALID_POSTFIX_RECEIVED'] = 'Received=/ \\(Postfix\\) with ESMTP id [A-Z\\d]+([\\s\\r\\n]+for <\\S+?>)?;[\\s\\r\\n]*[A-Z][a-z]{2}, \\d{1,2} [A-Z][a-z]{2} \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d [\\+\\-]\\d\\d\\d\\d$/X' reconf['INVALID_EXIM_RECEIVED'] = function (task) local headers_to = task:get_message():get_header('To') - if headers_to then - local raw_headers = task:get_raw_headers() + if headers_to and table.maxn(headers_to) < 5 then + local headers_recv = task:get_raw_header('Received') local regexp_text = '^[^\\n]*??\\|.*from \\d+\\.\\d+\\.\\d+\\.\\d+ \\(HELO \\S+\\)[\\s\\r\\n]*by \\1 with esmtp \\(\\S*?[\\?\\@\\(\\)\\s\\.\\+\\*\'\'\\/\\\\,]\\S*\\)[\\s\\r\\n]+id \\S*?[\\)\\(<>\\/\\\\,\\-:=]' local re = regexp.get_cached(regexp_text) if not re then re = regexp.create(regexp_text, 's') end - for _,header_to in ipairs(headers_to) do - if re:match(header_to.."|"..raw_headers) then + if headers_recv then + for _,header_to in ipairs(headers_to) do + for _,header_r in ipairs(headers_recv) do + if re:match(header_to.."|"..header_r['value']) then return true - end + end + end + end end end return false @@ -446,15 +453,19 @@ end reconf['INVALID_EXIM_RECEIVED2'] = function (task) local headers_to = task:get_message():get_header('To') - if headers_to then - local raw_headers = task:get_raw_headers() + if headers_to and table.maxn(headers_to) < 5 then + local headers_recv = task:get_raw_header('Received') local regexp_text = '^[^\\n]*??\\|.*from \\d+\\.\\d+\\.\\d+\\.\\d+ \\(HELO \\S+\\)[\\s\\r\\n]*by \\1 with esmtp \\([A-Z]{9,12} [A-Z]{5,6}\\)[\\s\\r\\n]+id [a-zA-Z\\d]{6}-[a-zA-Z\\d]{6}-[a-zA-Z\\d]{2}[\\s\\r\\n]+' local re = regexp.get_cached(regexp_text) if not re then re = regexp.create(regexp_text, 's') end - for _,header_to in ipairs(headers_to) do - if re:match(header_to.."|"..raw_headers) then + if headers_recv then + for _,header_to in ipairs(headers_to) do + for _,header_r in ipairs(headers_recv) do + if re:match(header_to.."|"..header_r['value']) then return true - end + end + end + end end end return false diff --git a/src/lua/lua_redis.c b/src/lua/lua_redis.c index 0358a6960..0c1934345 100644 --- a/src/lua/lua_redis.c +++ b/src/lua/lua_redis.c @@ -98,8 +98,12 @@ static void lua_redis_push_error (const gchar *err, struct lua_redis_userdata *ud, gboolean connected) { struct worker_task **ptask; + gboolean need_unlock = FALSE; - g_mutex_lock (lua_mtx); + /* Avoid LOR here as mutex can be acquired before in lua_call */ + if (g_mutex_trylock (lua_mtx)) { + need_unlock = TRUE; + } /* Push error */ lua_rawgeti (ud->L, LUA_REGISTRYINDEX, ud->cbref); ptask = lua_newuserdata (ud->L, sizeof (struct worker_task *)); @@ -113,7 +117,9 @@ lua_redis_push_error (const gchar *err, struct lua_redis_userdata *ud, gboolean if (lua_pcall (ud->L, 3, 0, 0) != 0) { msg_info ("call to callback failed: %s", lua_tostring (ud->L, -1)); } - g_mutex_unlock (lua_mtx); + if (need_unlock) { + g_mutex_unlock (lua_mtx); + } if (connected) { remove_normal_event (ud->task->s, lua_redis_fin, ud); @@ -130,8 +136,11 @@ static void lua_redis_push_data (const redisReply *r, struct lua_redis_userdata *ud) { struct worker_task **ptask; + gboolean need_unlock = FALSE; - g_mutex_lock (lua_mtx); + if (g_mutex_trylock (lua_mtx)) { + need_unlock = TRUE; + } /* Push error */ lua_rawgeti (ud->L, LUA_REGISTRYINDEX, ud->cbref); ptask = lua_newuserdata (ud->L, sizeof (struct worker_task *)); @@ -161,7 +170,9 @@ lua_redis_push_data (const redisReply *r, struct lua_redis_userdata *ud) if (lua_pcall (ud->L, 3, 0, 0) != 0) { msg_info ("call to callback failed: %s", lua_tostring (ud->L, -1)); } - g_mutex_unlock (lua_mtx); + if (need_unlock) { + g_mutex_unlock (lua_mtx); + } remove_normal_event (ud->task->s, lua_redis_fin, ud); } diff --git a/src/lua/lua_task.c b/src/lua/lua_task.c index 5695b6f42..e44b873c0 100644 --- a/src/lua/lua_task.c +++ b/src/lua/lua_task.c @@ -69,6 +69,7 @@ LUA_FUNCTION_DEF (task, get_helo); LUA_FUNCTION_DEF (task, get_images); LUA_FUNCTION_DEF (task, get_symbol); LUA_FUNCTION_DEF (task, get_date); +LUA_FUNCTION_DEF (task, get_message_id); LUA_FUNCTION_DEF (task, get_timeval); LUA_FUNCTION_DEF (task, get_metric_score); LUA_FUNCTION_DEF (task, get_metric_action); @@ -101,6 +102,7 @@ static const struct luaL_reg tasklib_m[] = { LUA_INTERFACE_DEF (task, get_images), LUA_INTERFACE_DEF (task, get_symbol), LUA_INTERFACE_DEF (task, get_date), + LUA_INTERFACE_DEF (task, get_message_id), LUA_INTERFACE_DEF (task, get_timeval), LUA_INTERFACE_DEF (task, get_metric_score), LUA_INTERFACE_DEF (task, get_metric_action), @@ -1193,6 +1195,21 @@ lua_task_get_date (lua_State *L) return 1; } +static gint +lua_task_get_message_id (lua_State *L) +{ + struct worker_task *task = lua_check_task (L); + + if (task != NULL && task->message_id != NULL) { + lua_pushstring (L, task->message_id); + } + else { + lua_pushnil (L); + } + + return 1; +} + static gint lua_task_get_timeval (lua_State *L) { diff --git a/src/plugins/lua/ratelimit.lua b/src/plugins/lua/ratelimit.lua index 7b1ea3eff..39bac5534 100644 --- a/src/plugins/lua/ratelimit.lua +++ b/src/plugins/lua/ratelimit.lua @@ -21,6 +21,7 @@ local bounce_senders = {'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-da -- Do not check ratelimits for these senders local whitelisted_rcpts = {'postmaster', 'mailer-daemon'} local whitelisted_ip = nil +local max_rcpt = 5 local upstreams = nil --- Parse atime and bucket of limit @@ -167,6 +168,11 @@ local function rate_test_set(task, func) rcpts = task:get_recipients_headers() end if rcpts then + if table.maxn(rcpts) > max_rcpt then + rspamd_logger.info(string.format('message <%s> contains %d recipients, maximum is %d', + task:get_message_id(), table.maxn(rcpts), max_rcpt)) + return + end for i,r in ipairs(rcpts) do rcpts_user[i] = get_local_part(r['addr']) end @@ -276,6 +282,7 @@ if rspamd_config:get_api_version() >= 9 then rspamd_config:register_module_option('ratelimit', 'whitelisted_rcpts', 'string') rspamd_config:register_module_option('ratelimit', 'whitelisted_ip', 'map') rspamd_config:register_module_option('ratelimit', 'limit', 'string') + rspamd_config:register_module_option('ratelimit', 'max_rcpt', 'uint') end local function parse_whitelisted_rcpts(str) @@ -301,6 +308,10 @@ if opts then whitelisted_ip = rspamd_config:add_hash_map (opts['whitelisted_ip']) end + if opts['max_rcpt'] then + max_rcpt = tonumber (opts['max_rcpt']) + end + if not opts['servers'] then rspamd_logger.err('no servers are specified') else @@ -312,4 +323,4 @@ if opts then rspamd_config:register_post_filter(rate_set) end end -end +end \ No newline at end of file -- 2.39.5