antispam: rspamd 4.2.2-0, no capabilities, only the milter on the network (audit S4)
Security audit 2026-10-17, finding S4 (low), stage 4. The sidecar ran
rspamd 3.11.1 (December 2024) with default capabilities; upstream is 4.2.2
(2026-10-10), and the line between fixed a heap overflow in
util.lower_utf8, a use-after-free on reload and parser crashes - rspamd
reads every byte a stranger sends to port 25.
A read-only reading of rspamd 3.11.1 and 4.2.2 and of rspamd-docker
v3.11.1+3 and v4.2.2+0, side by side (sources in dev/upstream), found every
function SelfPost relies on unchanged: milter_headers (skip_wanted, the
custom routine, remove 0, create_specific_keyed, get_queue_id from {i}),
the X-Spamd-Result text, multimap (from/smtp, ip, the reject pre-result,
file-map rereading), the image's uid 11333 and CMD. It also found that the
image binds the normal worker (which takes client-named file and shm
inputs) and the controller (default password "q1") on every address,
reachable from any container on the compose network - so:
- image rspamd/rspamd:4.2.2-0 (the one build of 4.2.2; the others float);
- cap_drop: [ALL], no-new-privileges (rspamd starts unprivileged, ports
above 1024, nothing setuid);
- RSPAMD_LOCAL_ADDR=localhost keeps every worker on loopback, and a new
antispam/rspamd/worker-proxy.inc puts the proxy alone on *:11332. rspamd
expands its template syntax in comments too - the first version of that
comment quoted it and the config failed to load; it quotes none now.
Checked in a throwaway container on the test server with the three files
and the fragment's options (dev/server/rspamd-try.sh): proxy on any
address, 11333/11334 on loopback; from a private address a sender's own
verdict headers removed and rspamd's added; X-SelfPost-Filter equal to
openssl's HMAC-SHA256 of the queue id; a denied address refused with the
deny list's message; healthy. The e2e has no rspamd; the live sidecar is
checked at the rollout. Citations re-pointed to 4.2.2.
Docs: guide § Inbound relay (the third file; copy antispam/ again);
CHANGELOG; plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
# rspamd removes headers of these names that a sender wrote himself before it
|
||||
# adds its own - for every client, because of the two lines below. Without
|
||||
# them it would do neither for a client it counts as local or one that
|
||||
# authenticated: rspamd 3.11.1, src/plugins/lua/milter_headers.lua,
|
||||
# authenticated: rspamd 4.2.2 (as 3.11.1), src/plugins/lua/milter_headers.lua,
|
||||
# skip_wanted() - skip_local and skip_authenticated are on by default, and
|
||||
# "local" is loopback, a unix socket, or an address in local_addrs, the
|
||||
# private ranges (src/lua/lua_ip.c, lua_ip_is_local(); conf/options.inc). The
|
||||
@@ -20,7 +20,9 @@
|
||||
# message carrying its own X-Spamd-Result and X-Rspamd-Action): without the
|
||||
# two lines, from 10.0.0.5 or as an authenticated user, the milter reply
|
||||
# removed neither header, added neither, and added the signature; with
|
||||
# them, both removed and rspamd's own added, as from a public address.
|
||||
# them, both removed and rspamd's own added, as from a public address. The
|
||||
# same again on 2026-10-17 in rspamd/rspamd:4.2.2-0 (from 10.0.0.5), and the
|
||||
# signature equal to HMAC-SHA256 of the queue id computed with openssl.
|
||||
#
|
||||
# Without this file the journal still lists accepted mail, with no verdict
|
||||
# next to it.
|
||||
@@ -34,7 +36,7 @@
|
||||
# signature travels on with the mail and is of no use to whoever sees it: it
|
||||
# fits that one queue id.
|
||||
#
|
||||
# rspamd 3.11.1, src/plugins/lua/milter_headers.lua: a custom routine is called
|
||||
# rspamd 4.2.2 (as 3.11.1), src/plugins/lua/milter_headers.lua: a custom routine is called
|
||||
# as function(task, common_meta) and returns error, headers to add, headers to
|
||||
# remove, metadata; a remove value of 0 removes every header of that name
|
||||
# (docs.rspamd.com/modules/milter_headers, "Custom routines").
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# The allow and deny lists kept in the panel (Inbound > Filter lists). SelfPost
|
||||
# writes them as six files under data/antispam/, which the fragment mounts at
|
||||
# /etc/rspamd/selfpost; rspamd rereads a file when it changes, no restart.
|
||||
# (rspamd 3.11.1, src/libserver/maps/map.c: a file map is watched with libev's
|
||||
# (rspamd 4.2.2 (as 3.11.1), src/libserver/maps/map.c: a file map is watched with libev's
|
||||
# ev_stat - inotify where the kernel has it, else a stat poll every
|
||||
# map_watch_interval x map_file_watch_multiplier, 5min x 0.1, conf/options.inc;
|
||||
# docs.rspamd.com/configuration/maps asks for an atomic rename, which
|
||||
@@ -14,7 +14,7 @@
|
||||
# allowed one is not accepted outright - rspamd would then let whichever of
|
||||
# the two rules ran first decide - but given a weight no spam score
|
||||
# outweighs, which a refusal by the deny list still overrides. (rspamd
|
||||
# 3.11.1, src/plugins/lua/multimap.lua: every rule is registered as a
|
||||
# 4.2.2 (as 3.11.1), src/plugins/lua/multimap.lua: every rule is registered as a
|
||||
# "normal" symbol, one with an action with the "passthrough" augmentation;
|
||||
# no "prefilter" setting is read. Measured with this file in a throwaway
|
||||
# rspamd/rspamd:3.11.1-3, 2026-10-15: every mix of an allow and a deny entry
|
||||
@@ -29,7 +29,7 @@
|
||||
# bounce whose From: domain is on the deny list was refused by default and
|
||||
# is not with this setting; a sender on the list is refused either way.
|
||||
#
|
||||
# A domain entry is that domain, not its subdomains. (rspamd 3.11.1,
|
||||
# A domain entry is that domain, not its subdomains. (rspamd 4.2.2 (as 3.11.1),
|
||||
# src/libserver/maps/map_helpers.c: a hash map is looked up by the whole key,
|
||||
# case-insensitively - rspamd_icase_hash, rspamd_lc_cmp; "email:domain" gives
|
||||
# the address's domain part, src/plugins/lua/multimap.lua, apply_addr_filter().)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Mounted into the sidecar as /etc/rspamd/local.d/worker-proxy.inc.
|
||||
#
|
||||
# The milter - the proxy worker - is the one listener SelfPost needs on the
|
||||
# compose network. The fragment sets RSPAMD_LOCAL_ADDR=localhost, which keeps
|
||||
# every worker on the container's loopback: the normal worker (11333), whose
|
||||
# scan accepts a client-named file or shared-memory input, and the controller
|
||||
# (11334), whose default password is the well-known "q1" (rspamd 4.2.2,
|
||||
# conf/rspamd.conf: each worker's bind_socket is the env variable LOCAL_ADDR,
|
||||
# localhost by default, and its port; conf/worker-controller.inc: password =
|
||||
# "q1"; the rspamd-docker v4.2.2+0 Dockerfile sets RSPAMD_LOCAL_ADDR=* for all
|
||||
# three). This line puts the proxy alone back on every address: a local.d
|
||||
# scalar at priority 1 replaces the default one (contrib/libucl/ucl_parser.c,
|
||||
# UCL_DUPLICATE_MERGE). The proxy still reaches the normal worker on
|
||||
# localhost, as before (security audit 2026-10-17, S4).
|
||||
#
|
||||
# rspamd expands its template syntax in these files, comments included:
|
||||
# quote none of it here.
|
||||
bind_socket = "*:11332";
|
||||
Reference in New Issue
Block a user