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:
@@ -34,7 +34,7 @@ type ListStore interface {
|
||||
// a file when it changes. Which list wins when both match is the filter's
|
||||
// configuration: deny.
|
||||
//
|
||||
// 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, or a stat poll every map_watch_interval x
|
||||
// map_file_watch_multiplier = 30 s, conf/options.inc), so a change takes
|
||||
// effect within that, not at once; docs.rspamd.com/configuration/maps asks
|
||||
@@ -164,7 +164,7 @@ func (l *Lists) sync() error {
|
||||
// Values were validated on the way in; this is the writer's own guard
|
||||
// against a row that got into the table some other way. In a map file a
|
||||
// newline ends the entry, whitespace starts its value and # a comment
|
||||
// (rspamd 3.11.1, src/libserver/maps/map_helpers.c,
|
||||
// (rspamd 4.2.2 (as 3.11.1), src/libserver/maps/map_helpers.c,
|
||||
// rspamd_parse_kv_list()). An IP map takes bare v4 and v6 addresses and
|
||||
// CIDR networks (docs.rspamd.com/configuration/maps, IP maps).
|
||||
if err := configsafe.Token("filter list entry", e.Value, " \t\r\n#"); err != nil {
|
||||
@@ -174,7 +174,7 @@ func (l *Lists) sync() error {
|
||||
}
|
||||
// The filter runs under another user in another container: the directory
|
||||
// and the files are readable by anyone. They hold addresses, not secrets.
|
||||
// (The rspamd image runs as uid 11333: rspamd-docker v3.11.1+3,
|
||||
// (The rspamd image runs as uid 11333: rspamd-docker v4.2.2+0 (as v3.11.1+3),
|
||||
// Dockerfile, USER 11333:11333.)
|
||||
if err := os.MkdirAll(l.dir, 0o755); err != nil {
|
||||
return fmt.Errorf("create %s: %w", l.dir, err)
|
||||
@@ -309,7 +309,7 @@ func normalizeNote(raw string) (string, error) {
|
||||
// rejected row. An allow rule adds a symbol named after the kind of entry,
|
||||
// which the journal milter keeps in the verdict of an accepted row.
|
||||
//
|
||||
// rspamd 3.11.1: the deny message reaches Postfix as the milter reply "554
|
||||
// rspamd 4.2.2 (as 3.11.1): the deny message reaches Postfix as the milter reply "554
|
||||
// 5.7.1 <message>" (src/libserver/milter.c, rspamd_milter_send_task_results());
|
||||
// src/plugins/lua/milter_headers.lua, routine x-spamd-result, writes each
|
||||
// symbol as "%s(%.2f)[%s]" - name, score, options.
|
||||
|
||||
Reference in New Issue
Block a user