Skip to content

fix: free the MonitorLink each linked-monitor token creates - #5219

Merged
connortechnology merged 2 commits into
ZoneMinder:masterfrom
nabbi:fix/b77-monitorlink-token-leak
Oct 12, 2026
Merged

connortechnology merged 2 commits into
ZoneMinder:masterfrom
nabbi:fix/b77-monitorlink-token-leak

Conversation

@nabbi

@nabbi nabbi commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Automated PR, tested in a zm-holodeck sandbox. An AI agent (Claude Opus 5.5) reproduced
the bug on a disposable ZoneMinder built from upstream master c3d2e542b with
simulated cameras, wrote this fix and ran the checks below on the same lab. Minimal
human review before opening: please review it as you would any contributor's PR.

TL;DR

  • Fixes: zmc leaks a file descriptor and a shared memory mapping per linked monitor on every reconnect or reload #5218: #: zmc leaks a MonitorLink (with an fd and a shared memory mapping once used) per linked monitor on every reconnect or SIGHUP reload.
  • Change: Token holds its MonitorLink in a std::shared_ptr, so the token copies share it and the last one frees it with the expression tree; 1 file, +6/-5
  • Risk: linked-monitor alarms go through the same objects; the events check's linked scenario (motion on the source records a "Linked" event on the target) passed on the fixed build
  • Tested: reproduced before (+1 fd and mapping per reload), flat after (1 fd, 1 mapping over 10 reloads); checks covering the diff green
Details: reproduction, change, checks

Before

Upstream master c3d2e542b, clean plain build; monitor 2 linked to monitor 1, ten SIGHUP
reloads of zmc -m 2 (zmdc.pl reload), 8 s apart:

round=0 pid=36847 fds_on_zm.mmap.1=1 maps_of_zm.mmap.1=1 open_fds=11 rss_kB=150216
round=1 pid=36847 fds_on_zm.mmap.1=2 maps_of_zm.mmap.1=2 open_fds=12 rss_kB=156824
...
round=10 pid=36847 fds_on_zm.mmap.1=11 maps_of_zm.mmap.1=11 open_fds=21 rss_kB=156620

Change

  • src/zm_monitorlink_token.h: monitor_link_ becomes std::shared_ptr<Monitor::MonitorLink>,
    made with std::make_shared. Tokens are copied (parse() copies them into the tree), which is
    why a delete in ~Token alone would double-free; a shared owner fits that. The defaulted
    copy and move constructors lose constexpr (not allowed with a shared_ptr member in C++17).
  • Test: no unit test: a monitor-link token loads a monitor from the database
    (Monitor::Load), which the unit tests don't have. Covered by the probe above and the events
    check's linked scenario.

Behaviour changes

Change Who sees it Intended / collateral Alternatives looked into Chosen, why Tested by
The MonitorLink, its Monitor, fd and mapping are freed when the expression is replaced (each connect) or deleted (disconnect) zmc with linked monitors intended delete in ~Token (double free: tokens are copied); unique_ptr (needs the tree to move tokens instead of copying them, a larger change to the parser) shared_ptr probe: fds/maps flat over 10 reloads; events linked PASS
Linking behaviour (when a linked monitor's alarm triggers) users of Linked Monitors no visible change events linked PASS

Side fixes in this PR: none.

After

Branch fix/b77-monitorlink-token-leak at 91526d2e9 (base c3d2e542b), tested in a merge with
two other fixes from the same lab session (combo = master + this + fix/b55-usr-level-signal-safe

  • fix/b65-zmc-stop-during-startup; no shared files with this one):
"head": "69fa500a6683a16c937ae8be4d249a7eb52ba861", "branch": "combo", "dirty": false (plain)
round=0 pid=373 fds_on_zm.mmap.1=1 maps_of_zm.mmap.1=1 open_fds=11 rss_kB=151824
round=1 pid=373 fds_on_zm.mmap.1=1 maps_of_zm.mmap.1=1 open_fds=11 rss_kB=190292
...
round=10 pid=373 fds_on_zm.mmap.1=1 maps_of_zm.mmap.1=1 open_fds=11 rss_kB=159196
Check Result
holodeck checks for the diff (check-changed.py: unit-tests, script-tests, smoke, resilience, events, dblog --quick, signals --quick) unit-tests ok (14725 assertions in 251 test cases), script-tests PASS=31, smoke PASS=11 XFAIL=1 (stock zmonvif-probe.pl discovery, known), resilience PASS=5, events PASS=10 (record, mocord, modect-only, nodect, monitor, linked, jpegs, reload, filters, purge), dblog --quick PASS=1 XFAIL=2 (thread-error, contention: known on master, #5197/#5208), signals --quick PASS=4 (hup-default, usr, usr-threads, startup-stop)
pyzmNg / ha-zoneminder / zmNinjaNg not run: no API, web or stream change
contract baselines (master) not run: no PHP change

Not run: the deep set. Backport to release-1.38: same code, the commit cherry-picks cleanly (not built).

Tested as 7cde4e059; 91526d2e9 is the same tree with fixes #5218 added to the message.

fixes #5218

🤖 Generated with Claude Code

nabbi and others added 2 commits October 12, 2026 10:36
Token's constructor allocates a Monitor::MonitorLink with new for every
monitor in a monitor's LinkedMonitors expression and keeps it in a raw
pointer that nothing deletes (~Token is defaulted, and parse() copies
tokens into the tree, so the pointer is shared). Monitor::connect()
re-parses the expression through ReloadLinkedMonitors() on every
connect, so each reconnect or SIGHUP reload of zmc leaks one MonitorLink
per linked monitor, with the Monitor it holds, and once the link has
been used, the open file descriptor and the mapping of the linked
monitor's shared memory.

Hold it in a std::shared_ptr instead, so the copies share it and the
last one frees it when the expression tree is deleted (disconnect() and
ReloadLinkedMonitors() delete it). The defaulted copy and move
constructors can no longer be constexpr.

fixes ZoneMinder#5218

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Token::hasAlarmed(), Token::score() and MonitorLinkExpression::Node(Token
const &) were constexpr; with monitor_link_ a std::shared_ptr they call
non-constexpr functions, which the -Werror builds reject
(-Winvalid-constexpr, gcc and clang). None of them is used in a constant
expression.

refs ZoneMinder#5218

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@connortechnology
connortechnology merged commit ca8c2b2 into ZoneMinder:master Oct 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zmc leaks a file descriptor and a shared memory mapping per linked monitor on every reconnect or reload

2 participants