Skip to content

rc.d: use absolute path for python3 in maltrailsensor + maltrailserver#5466

Merged
fichtner merged 1 commit into
opnsense:masterfrom
TokamakTX:fix-maltrail-rc-d-python3-absolute-path
Jun 1, 2026
Merged

rc.d: use absolute path for python3 in maltrailsensor + maltrailserver#5466
fichtner merged 1 commit into
opnsense:masterfrom
TokamakTX:fix-maltrail-rc-d-python3-absolute-path

Conversation

@TokamakTX
Copy link
Copy Markdown
Contributor

Problem

service opnsense-maltrailsensor start fails silently on a stock OPNsense install: the command exits 0, prints Starting maltrailsensor., but no sensor.py process is left running, no pidfile is written, and no log entry is produced. The same failure mode affects opnsense-maltrailserver. The GUI Save path (System → Services → Maltrail → Sensor → Enabled → Apply) appears to work the first time, then subsequent service start attempts fail — masking the underlying bug.

Reproduction

On OPNsense 26.4 / FreeBSD 14.3-RELEASE-p12 with os-maltrail-1.10_1:

service opnsense-maltrailsensor stop    # ensure stopped
service opnsense-maltrailsensor start   # exit 0, prints "Starting maltrailsensor."
pgrep -f maltrail/sensor.py             # empty — no process
ls /var/run/maltrailsensor.pid          # ENOENT

Root cause

The rc.d script passes python3 as a bare name to daemon(8):

command_args="-f -P /var/run/maltrailsensor.pid python3 /usr/local/share/maltrail/sensor.py"

daemon(8) calls execvp("python3", ...) after its double-fork detach (FreeBSD daemon.c stable/14 line 366), which performs a PATH search. When the rc.d is invoked via service(8), the PATH is reset narrow per service.sh stable/14 lines 196/198:

exec /usr/bin/env -i -L -/daemon HOME=/ PATH=/sbin:/bin:/usr/sbin:/usr/bin ${VARS} "$dir/$script" "$@"

env -i clears the inherited environment, then PATH is set to exactly four directories — none of which contain python3 (which lives at /usr/local/bin/python3 on a standard FreeBSD/OPNsense install). All four execvp lookups ENOENT, the daemon supervisor writes its error to an internal pipe that never reaches syslog, the supervisor exits 1, but rc.subr's _doit already returned 0 from the pre-detach parent. Net: exit 0, nothing running.

service(8) man page (verbatim):

When used to run rc.d scripts the service command sets HOME to / and PATH to /sbin:/bin:/usr/sbin:/usr/bin which is how they are set in /etc/rc at boot time.

This means the bug also breaks boot-time startup when maltrailsensor_enable="YES" — the rc system at boot uses the same narrow PATH.

Why the GUI Save path appeared to work

The GUI Save → Apply flow dispatches via configd, which sets a wider PATH from configd.conf [environment]:

[environment]
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin

configd.py replaces the child process environment entirely (subprocess.run(..., env=self.config_environment)), so the same rc.d script invoked via configctl maltrailsensor start sees /usr/local/bin on PATH and execvp("python3") resolves correctly. Different invoker, different PATH, same script.

Syscall-level evidence

truss -f on a failing service opnsense-maltrailsensor start (captured during local investigation):

<pid>: execve("/sbin/python3",...)      ERR#2 'No such file or directory'
<pid>: execve("/bin/python3",...)       ERR#2
<pid>: execve("/usr/sbin/python3",...)  ERR#2
<pid>: execve("/usr/bin/python3",...)   ERR#2
<pid>: write(2,"daemon: python3: No such file or directory\n",...)
<pid>: exit(0x1)

The four ENOENT paths match service(8)'s PATH literal byte-for-byte.

Project convention

Peer OPNsense plugins consistently use absolute paths to /usr/local/-installed binaries in their rc.d scripts. The two os-maltrail rc.d files are outliers:

Plugin Python invocation Absolute path?
security/maltrail opnsense-maltrailsensor bare python3 in command_args NO
security/maltrail opnsense-maltrailserver bare python3 in command_args NO
security/stunnel identd_stunnel command_interpreter=/usr/local/bin/python3 YES
dns/ddclient ddclient_opn command_interpreter=/usr/local/bin/python3 YES
security/tinc opnsense-tincd absolute path via start_cmd YES
security/openconnect opnsense-openconnect /usr/local/sbin/openconnect YES

FreeBSD's own rc-scripting guide (rc-scripting article §5) demonstrates the canonical pattern as absolute path:

command="/usr/sbin/${name}"

Fix

One-line change in each of the two rc.d scripts: replace bare python3 with /usr/local/bin/python3 in command_args.

security/maltrail/src/etc/rc.d/opnsense-maltrailsensor

-command_args="-f -P /var/run/maltrailsensor.pid python3 /usr/local/share/maltrail/sensor.py"
+command_args="-f -P /var/run/maltrailsensor.pid /usr/local/bin/python3 /usr/local/share/maltrail/sensor.py"

security/maltrail/src/etc/rc.d/opnsense-maltrailserver

-command_args="-f -P /var/run/maltrailserver.pid python3 /usr/local/share/maltrail/server.py"
+command_args="-f -P /var/run/maltrailserver.pid /usr/local/bin/python3 /usr/local/share/maltrail/server.py"

No other lines change. No behavioural change to rcvar, pidfile, command, or any rc.subr defaults.

Validation

Local fix applied on a test OPNsense 26.4 / FreeBSD 14.3-RELEASE-p12 host running os-maltrail-1.10_1:

Runtime validation (3/3 start/stop cycles passed):

Cycle service start exit pidfile written workers at t=30s service stop exit post-stop state
1 0 yes 8 0 clean (no procs, no pidfile)
2 0 yes 1+ 0 clean
3 0 yes 1+ 0 clean
  • truss re-trace of fixed path: 0 ENOENT for python3 (vs 4 in original failing trace).
  • service status works correctly under default rc.subr behaviour (pidfile-direct verification).
  • A/B test integration: an automated test script that invokes service opnsense-maltrailsensor start/stop 3× completed without failure post-fix (had failed every attempt pre-fix).

Boot-time validation — full cold-start reboot test:

A separate persistence test was run after the runtime fix had been validated: full OS reboot via shutdown -r now, then post-boot inspection to confirm the rc system at boot correctly auto-starts the sensor via maltrailsensor_enable="YES". Results from one reboot cycle:

  • Reboot completed in ~90 seconds (BIOS + FreeBSD boot + PPPoE negotiate + service startup).
  • Post-boot pgrep -f maltrail/sensor.py showed the daemon-supervised parent + worker children running.
  • /var/run/maltrailsensor.pid was present with valid PID (the daemon supervisor PID).
  • service opnsense-maltrailsensor status returned is running as pid N with exit 0.
  • /var/log/maltrail/error.log had no SIGTERM or execvp errors from the boot window.
  • The rc.d script MD5 was unchanged across reboot (the fix persisted as expected).

This empirically confirms the fix repairs boot-time autostart, not just runtime service start invocations. Without the fix, the rc system's narrow PATH at boot triggers the same silent failure as the runtime service path.

Compatibility / risk

  • Zero behavioural change when /usr/local/bin/python3 resolves on PATH (which is the post-fix configd path). The new line still invokes the same interpreter.
  • No change to other rc.subr metadata (name, rcvar, pidfile, command).
  • No change to config.xml schema or any plugin templates.
  • Affects rc-system boot startup positively: also fixes boot-time autostart when maltrailsensor_enable="YES" (previously broken by the same narrow PATH from /etc/rc). Empirically validated via full cold-start reboot test.
  • Sibling opnsense-maltrailserver has the identical bug and is fixed in the same PR.

Files changed

security/maltrail/src/etc/rc.d/opnsense-maltrailsensor   (1 line)
security/maltrail/src/etc/rc.d/opnsense-maltrailserver   (1 line)

References

service(8) invokes rc.d scripts with PATH=/sbin:/bin:/usr/sbin:/usr/bin
via `env -i`, which does not include /usr/local/bin where python3 lives.
daemon(8)'s execvp("python3") ENOENTs all four PATH entries and the
supervisor exits silently after the pre-detach parent has already
returned 0 to rc.subr. Net result: service start exits 0 with nothing
running, and boot-time autostart is broken when maltrailsensor_enable=YES.

Use absolute path /usr/local/bin/python3 to bypass the PATH lookup,
matching the convention used by other OPNsense plugin rc.d scripts
(stunnel, ddclient, tinc, openconnect).

Validated on OPNsense 26.4 / FreeBSD 14.3-RELEASE-p12 across
3 start/stop cycles + truss re-trace + full cold-start reboot.
@fichtner fichtner self-assigned this Jun 1, 2026
@fichtner fichtner merged commit 2b98f07 into opnsense:master Jun 1, 2026
@fichtner
Copy link
Copy Markdown
Member

fichtner commented Jun 1, 2026

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants