Filter DEAD_PROCESS utmpx entries out of macOS logged-in users provider - #39313
Conversation
Reproduced on a live 4.14.7 macOS agentConfirmed the pre-fix defect is real (not just static analysis) on a macOS 15.7.2 agent running Wazuh v4.14.7. Repro steps:
Result:
Cross-checked against ground truth at query time:
So With this PR's fix ( |
Post-fix verification on a live 4.14.9 agent (fixed code)Repeated the exact same reproduction as the earlier pre-fix comment, this time against a macOS agent upgraded to v4.14.9 with this PR's fix applied. Repro steps (identical to before):
Result: bug no longer reproducible.
This matches the expected effect of the fix: |
…ilter Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cborla
left a comment
There was a problem hiding this comment.
LGTM
- Code changes reviewed
- Relevant evidence provided
- Tests cover the new functionality
- Configuration changes documented
- Developer documentation reflects the changes
- Meets requirements and/or definition of done
- No unresolved dependencies with other issues
Description
On macOS,
LoggedInUsersProvider::collect()walks the utmpx database and returns every record except the one withut_pid == 1, with no filter onut_type.utmpxkeepsDEAD_PROCESSentries for sessions that have already ended, soSysInfo::getUsers()(which matches purely by username, with no type check) can populatelogin_tty,process_pidanduser_last_loginfrom a session that is no longer running.Closes #39170
Proposed Changes
src/data_provider/src/extended_sources/users/src/logged_in_users_darwin.cpp:LoggedInUsersProvider::collect()now skips any utmpx entry whoseut_typeis notUSER_PROCESS, in addition to the existingut_pid == 1skip. Only live sessions are emitted.SysInfo::getUsers()(sysInfoMac.cpp) or theloginTypeslabel map — this is the darwin provider's only consumer, so narrowing whatcollect()returns is sufficient and keeps the diff minimal.Results and Evidence
Reproduced the pre-fix defect live on a macOS 15.7.2 agent running Wazuh v4.14.7 (same bug, present since at least that branch). Full repro steps and query output: see this comment.
Summary: closed a short SSH pty session (
ttys002), leaving aDEAD_PROCESSutmpx record newer than the two genuinely still-open sessions. After a syscollector rescan, the local cache showed:login_status = 1(reported logged in) viattys002/pid13701, both already dead at query time (ps -p 13701→ no such process;whoshowed nottys002). This isgetUsers()picking the newest-but-dead utmpx record over the older-but-live ones — exactly the failure mode #39170 describes.Post-fix verification: repeated the identical reproduction against the same host upgraded to v4.14.9 with this fix applied. Full details: see this follow-up comment.
Summary: same steps (closed a short SSH pty session on
ttys002, newer than the livettys001session), then rescanned:No
dead-typed rows anywhere in the table, andvagrant's record correctly reflects the genuinely livettys001session (pid18253, verified live viaps -p 18253) instead of the deadttys002one. Bug not reproducible post-fix.The fix is additionally covered by two new unit tests (see "Tests Introduced" below), which mock the utmpx source directly with
DEAD_PROCESSandUSER_PROCESSentries and assert oncollect()'s output.Manual tests with their corresponding evidence
logged_in_users_darwin.cppis only added to the build underAPPLE(src/data_provider/src/extended_sources/users/CMakeLists.txt), so it isn't compiled on Linux.Memory tests for Linux
Memory tests for Windows
Memory tests for macOS
Decoder/Rule tests (Wazuh v4.x) — N/A, no decoder/rule changes
runtests.pyexecuted without errorsEngine (Wazuh v5.x and above) — N/A, not an engine change
Wazuh server API/Framework — N/A, not an API/framework change
Artifacts Affected
data_providerlibrary consumed by syscollector'sgetUsers()inventory).Configuration Changes
N/A — no configuration parameters added, removed, or changed.
Tests Introduced
Added to
src/data_provider/src/extended_sources/users/tests/test_logged_in_users_darwin.cpp:CollectFiltersOutDeadProcessEntries: a loneDEAD_PROCESSutmpx entry now yields an empty result fromcollect().CollectKeepsLiveSessionAmongDeadOnes: given a mix of aDEAD_PROCESSand aUSER_PROCESSentry,collect()returns only the liveUSER_PROCESSrow.Both mock
IUtmpxWrapperthe same way as the existingCollectReturnsExpectedJsontest.Review Checklist