Skip to content

ROX-33036: add mount-related operations - #1059

Open
Molter73 wants to merge 4 commits into
mauro/refactor/cleanup-file-data-accesorsfrom
mauro/ROX-33036/track-mount
Open

ROX-33036: add mount-related operations#1059
Molter73 wants to merge 4 commits into
mauro/refactor/cleanup-file-data-accesorsfrom
mauro/ROX-33036/track-mount

Conversation

@Molter73

@Molter73 Molter73 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Description

This was originally going to be about adding just lsm/sb_mount, however while adding this hook it became pretty clear we needed lsm/sb_umount and lsm/move_mount for a comprehensive implementation and it really didn't add too much code, so they are all added in.

These operations are not currently intended to be forwarded via gRPC, they only trigger inode tracking related behavior (scans on new/moved mounts, inode map cleanups on moved/unmounted directories).

The move mount operation shares quite a bit of similarities with rename, so there is a bit of refactoring mixed in so they can be reused while keeping the code clean.

Checklist

  • Patch has a change log entry OR does not need one.
  • Investigated and inspected CI test results
  • Updated documentation accordingly

Automated testing

  • Added unit tests
  • Added integration tests
  • Added regression tests

If any of these don't apply, please comment below.

Testing Performed

Added integration tests.

Summary by CodeRabbit

  • New Features

    • Added monitoring for mount, unmount, and mount-move activity.
    • Added automatic host filesystem rescans when mount-related changes occur.
    • Added metrics for the new mount activity types.
    • Added coverage for tracking paths across mount and unmount operations.
  • Bug Fixes

    • Updated rename event handling and output to correctly represent old and new paths.
  • Documentation

    • Updated the changelog with the new mount-related operations.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Mount, unmount, and move-mount hooks now emit events with dedicated metrics. Rust event handling, host scanning, integration-test container setup, and mount tracking tests support these operations.

Changes

Mount event tracking

Layer / File(s) Summary
Kernel mount hooks and event contracts
fact-ebpf/src/bpf/*, fact-ebpf/src/lib.rs, fact/src/metrics/kernel_metrics.rs
Adds mount-related event types, path handling, LSM probes, event submission helpers, and per-hook metrics.
Rust event model and serialization
fact/src/event/mod.rs
Adds mount, unmount, and move-mount variants; updates accessors, conversions, equality, and rename representation.
Host scanner mount handling
fact/src/host_scanner.rs
Triggers a full host scan for mount-related events and skips normal event forwarding.
Mount integration validation
tests/conftest.py, tests/test_mount.py, tests/test_config_hotreload.py, CHANGELOG.md
Adds mount tracking coverage, container mount propagation settings, an introspection endpoint setting, a hot-reload assertion update, and a changelog entry.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant KernelHooks
  participant RingBuffer
  participant FileData
  participant HostScanner
  KernelHooks->>RingBuffer: submit mount-related event
  RingBuffer->>FileData: construct Mount, Umount, or MoveMount
  FileData->>HostScanner: classify mount-related event
  HostScanner->>HostScanner: perform full host scan
Loading

Possibly related PRs

  • stackrox/fact#806: Introduces the macro-driven metrics paths extended here for mount-related hooks.

Suggested reviewers: stringy

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and matches the main change: adding mount-related operations.
Description check ✅ Passed The description explains the feature, checklist, automated integration tests, and testing performed, matching the repo template well.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mauro/ROX-33036/track-mount

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 35.04%. Comparing base (631caae) to head (5f72749).

Files with missing lines Patch % Lines
fact/src/event/mod.rs 0.00% 53 Missing ⚠️
fact/src/host_scanner.rs 0.00% 9 Missing ⚠️
Additional details and impacted files
@@                              Coverage Diff                              @@
##           mauro/refactor/cleanup-file-data-accesors    #1059      +/-   ##
=============================================================================
- Coverage                                      35.48%   35.04%   -0.45%     
=============================================================================
  Files                                             22       22              
  Lines                                           3235     3276      +41     
  Branches                                        3235     3276      +41     
=============================================================================
  Hits                                            1148     1148              
- Misses                                          2082     2123      +41     
  Partials                                           5        5              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Molter73
Molter73 force-pushed the mauro/ROX-33036/track-mount branch from 3cc1980 to b15f103 Compare July 20, 2026 09:12
@Molter73
Molter73 force-pushed the mauro/ROX-33036/track-mount branch from b15f103 to 0eb199b Compare July 27, 2026 14:50
@Molter73
Molter73 changed the base branch from main to mauro/feat/inodes-introspection July 27, 2026 14:50
@Molter73
Molter73 force-pushed the mauro/ROX-33036/track-mount branch from 0eb199b to 5e41f99 Compare July 28, 2026 09:37
@Molter73

Copy link
Copy Markdown
Member Author

/retest

@Molter73
Molter73 force-pushed the mauro/ROX-33036/track-mount branch from a0d0d78 to edc7efa Compare July 28, 2026 16:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
fact/src/event/mod.rs (1)

657-687: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

PartialEq for FileData missing arms for Mount, Umount, MoveMount.

Every other variant (including all pre-existing ones) has an explicit equality arm, but the three new mount variants fall through to _ => false. This means two identical Mount/Umount/MoveMount events will never compare equal.

🐛 Proposed fix
             (FileData::AclSet(this), FileData::AclSet(other)) => {
                 this.inner == other.inner
                     && this.acl_type == other.acl_type
                     && this.entries == other.entries
             }
+            (FileData::Mount(this), FileData::Mount(other)) => this == other,
+            (FileData::Umount(this), FileData::Umount(other)) => this == other,
+            (
+                FileData::MoveMount { to: l_to, from: l_from },
+                FileData::MoveMount { to: r_to, from: r_from },
+            ) => l_to == r_to && l_from == r_from,
             _ => false,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fact/src/event/mod.rs` around lines 657 - 687, Add explicit equality arms to
PartialEq::eq for FileData covering Mount, Umount, and MoveMount, comparing each
variant’s corresponding fields consistently with the other variant arms. Keep
the fallback _ => false for differing variants.
🧹 Nitpick comments (3)
fact/src/host_scanner.rs (1)

429-439: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Mount events trigger a synchronous full scan per event, with no coalescing for bursts.

handle_mount_event calls self.scan() directly and inline in the select! branch, blocking the task for the scan's duration and running once per mount-related event with no debouncing. The existing scan_trigger/Notify mechanism used for periodic and paths.changed() scans already coalesces repeated triggers (multiple notify_one() calls before consumption collapse to a single pending scan), but that path isn't reused here. Under a burst of mount/unmount activity this could serialize many redundant full scans and delay processing of other events in the loop (e.g. introspection queries).

Also applies to: 493-497

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fact/src/host_scanner.rs` around lines 429 - 439, Update handle_mount_event
to signal the existing scan_trigger/Notify mechanism instead of calling
self.scan() synchronously. Route mount events through the same coalesced scan
path used by periodic and paths.changed() triggers, preserving a single pending
scan during event bursts and keeping the select loop responsive.
fact/src/event/mod.rs (1)

437-535: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider adding Rust-side test coverage for the new Mount/Umount/MoveMount variants.

Codecov flags 130 uncovered lines in this file for this PR, and the PartialEq gap above went unnoticed likely due to this. EventTestData (used by the #[cfg(all(test, feature = "bpf-test"))] constructor) doesn't have variants for the new mount events either.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fact/src/event/mod.rs` around lines 437 - 535, Add Rust-side tests covering
FileData::Mount, FileData::Umount, and FileData::MoveMount, including their
constructed fields and equality behavior. Extend EventTestData and its bpf-test
constructor with corresponding mount-event variants so these cases can be
exercised through the existing test path, and ensure the tests cover both source
and destination data for MoveMount.
tests/test_mount.py (1)

44-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Subprocess calls don't check exit status.

mount/umount failures (e.g. wrong tmpfs support, misconfigured environment) will pass silently, and the test would then just fail (or worse, pass) based on stale inode state rather than surfacing the real cause.

♻️ Proposed fix
-    subprocess.run(
-        ['mount', '-t', 'tmpfs', '-o', 'size=10M', 'tmpfs', monitored_dir]
-    )
+    subprocess.run(
+        ['mount', '-t', 'tmpfs', '-o', 'size=10M', 'tmpfs', monitored_dir],
+        check=True,
+    )
     assert_tracked_path(monitored_dir)

-    subprocess.run(['umount', monitored_dir])
+    subprocess.run(['umount', monitored_dir], check=True)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_mount.py` around lines 44 - 50, Update the subprocess.run calls in
the mount/umount test to enforce successful exit status, so mount or unmount
failures raise immediately instead of allowing assertions to use stale inode
state. Preserve the existing command arguments and tracking assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 9: Update the ROX-33036 changelog entry to hyphenate “mount-related” in
the description of the operations.

In `@fact-ebpf/src/bpf/main.c`:
- Around line 557-603: Update trace_move_mount so events are emitted for every
monitored classification except MONITORED_NOT, matching the sb_mount/sb_umount
handling and allowing parent/path-monitored moves to reach rescan. In the same
function, derive args.parent_inode from to’s actual parent inode rather than
reusing to->dentry->d_inode, and pass that value to is_monitored.

---

Outside diff comments:
In `@fact/src/event/mod.rs`:
- Around line 657-687: Add explicit equality arms to PartialEq::eq for FileData
covering Mount, Umount, and MoveMount, comparing each variant’s corresponding
fields consistently with the other variant arms. Keep the fallback _ => false
for differing variants.

---

Nitpick comments:
In `@fact/src/event/mod.rs`:
- Around line 437-535: Add Rust-side tests covering FileData::Mount,
FileData::Umount, and FileData::MoveMount, including their constructed fields
and equality behavior. Extend EventTestData and its bpf-test constructor with
corresponding mount-event variants so these cases can be exercised through the
existing test path, and ensure the tests cover both source and destination data
for MoveMount.

In `@fact/src/host_scanner.rs`:
- Around line 429-439: Update handle_mount_event to signal the existing
scan_trigger/Notify mechanism instead of calling self.scan() synchronously.
Route mount events through the same coalesced scan path used by periodic and
paths.changed() triggers, preserving a single pending scan during event bursts
and keeping the select loop responsive.

In `@tests/test_mount.py`:
- Around line 44-50: Update the subprocess.run calls in the mount/umount test to
enforce successful exit status, so mount or unmount failures raise immediately
instead of allowing assertions to use stale inode state. Preserve the existing
command arguments and tracking assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 05fc42f0-315c-46df-9f9b-5b00f13b4fd4

📥 Commits

Reviewing files that changed from the base of the PR and between 91987cb and 4a5ee00.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • fact-ebpf/src/bpf/bound_path.h
  • fact-ebpf/src/bpf/events.h
  • fact-ebpf/src/bpf/main.c
  • fact-ebpf/src/bpf/types.h
  • fact-ebpf/src/lib.rs
  • fact/src/event/mod.rs
  • fact/src/host_scanner.rs
  • fact/src/metrics/kernel_metrics.rs
  • tests/conftest.py
  • tests/test_config_hotreload.py
  • tests/test_mount.py

Comment thread CHANGELOG.md Outdated
Comment thread fact-ebpf/src/bpf/main.c
@Molter73 Molter73 changed the title ROX-33036: add mount related operations ROX-33036: add mount-related operations Jul 29, 2026
@Molter73
Molter73 force-pushed the mauro/ROX-33036/track-mount branch from 4a5ee00 to 72aa4a2 Compare July 29, 2026 08:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_mount.py`:
- Line 17: Update the requests.get call for FACT_INTROSPECTION_INODES in the
test to include a finite timeout, using the project’s established timeout
convention if available, so the test fails promptly when the local service is
unavailable or unresponsive.
- Around line 25-35: Update the retry loop around get_inodes so it refreshes res
by calling get_inodes() at the start of each attempt, rather than reusing the
initial snapshot. Preserve the existing assertion, KeyError logging, and retry
delay behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 37bf14d9-5342-4cbd-b40a-6d0974b27ddf

📥 Commits

Reviewing files that changed from the base of the PR and between 4a5ee00 and 72aa4a2.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • fact-ebpf/src/bpf/bound_path.h
  • fact-ebpf/src/bpf/events.h
  • fact-ebpf/src/bpf/main.c
  • fact-ebpf/src/bpf/types.h
  • fact-ebpf/src/lib.rs
  • fact/src/event/mod.rs
  • fact/src/host_scanner.rs
  • fact/src/metrics/kernel_metrics.rs
  • tests/conftest.py
  • tests/test_config_hotreload.py
  • tests/test_mount.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • CHANGELOG.md
  • fact/src/metrics/kernel_metrics.rs
  • fact-ebpf/src/bpf/bound_path.h
  • fact-ebpf/src/bpf/events.h
  • fact/src/host_scanner.rs
  • fact-ebpf/src/bpf/types.h
  • fact-ebpf/src/bpf/main.c
  • tests/test_config_hotreload.py
  • fact/src/event/mod.rs

Comment thread tests/test_mount.py Outdated

def get_inodes() -> dict[str, str]:
FACT_INTROSPECTION_INODES = 'http://127.0.0.1:9000/inodes'
res = requests.get(FACT_INTROSPECTION_INODES)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Set a bounded introspection timeout.

If the local service is unavailable or wedged, this test can block indefinitely rather than fail.

Proposed fix
-    res = requests.get(FACT_INTROSPECTION_INODES)
+    res = requests.get(FACT_INTROSPECTION_INODES, timeout=5)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res = requests.get(FACT_INTROSPECTION_INODES)
res = requests.get(FACT_INTROSPECTION_INODES, timeout=5)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_mount.py` at line 17, Update the requests.get call for
FACT_INTROSPECTION_INODES in the test to include a finite timeout, using the
project’s established timeout convention if available, so the test fails
promptly when the local service is unavailable or unresponsive.

Source: Linters/SAST tools

Comment thread tests/test_mount.py Outdated
Comment on lines +25 to +35
res = get_inodes()

for _ in range(3):
try:
assert res[inode] == monitored_dir
return
except KeyError as e:
print(f'Failed to find inode: {e}')
print(f'{json.dumps(res)}')
sleep(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Refresh inode data on every retry.

res is read once before the loop, so retries repeatedly inspect the same snapshot. A delayed mount scan can never make this assertion pass.

Proposed fix
-    res = get_inodes()
-
     for _ in range(3):
         try:
+            res = get_inodes()
             assert res[inode] == monitored_dir
             return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
res = get_inodes()
for _ in range(3):
try:
assert res[inode] == monitored_dir
return
except KeyError as e:
print(f'Failed to find inode: {e}')
print(f'{json.dumps(res)}')
sleep(1)
for _ in range(3):
try:
res = get_inodes()
assert res[inode] == monitored_dir
return
except KeyError as e:
print(f'Failed to find inode: {e}')
print(f'{json.dumps(res)}')
sleep(1)
🧰 Tools
🪛 ast-grep (0.45.0)

[info] 32-32: use jsonify instead of json.dumps for JSON output
Context: json.dumps(res)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_mount.py` around lines 25 - 35, Update the retry loop around
get_inodes so it refreshes res by calling get_inodes() at the start of each
attempt, rather than reusing the initial snapshot. Preserve the existing
assertion, KeyError logging, and retry delay behavior.

Molter73 added a commit that referenced this pull request Jul 29, 2026
This is a small refactor done ahead of #1059, cleaning up
getters in the `FileData` type by using better pattern matching. The
`Rename` variant is also streamlined by using a struct variant instead
of a tuple holding `RenameFileData`.
Molter73 added a commit that referenced this pull request Jul 29, 2026
This is a small refactor done ahead of #1059, cleaning up
getters in the `FileData` type by using better pattern matching. The
`Rename` variant is also streamlined by using a struct variant instead
of a tuple holding `RenameFileData`.
Molter73 added 3 commits July 29, 2026 11:24
This was originally going to be about adding just `lsm/sb_mount`,
however while adding this hook it became pretty clear we needed
`lsm/sb_umount` and `lsm/move_mount` for a comprehensive implementation
and it really didn't add too much code, so they are all added in.

These operations are not currently intended to be forwarded via gRPC,
they only trigger inode tracking related behavior (scans on new/moved
mounts, inode map cleanups on moved/unmounted directories).

The move mount operation shares quite a bit of similarities with rename,
so there is a bit of refactoring mixed in so they can be reused while
keeping the code clean.

TODO: add integration tests.
Add a basic test for checking mount operations are properly tracked.
This test works by checking the monitored directory is tracked using the
inodes introspection endpoint, then a tmpfs is mounted on top of this
directory and we validate we see the new inode, then we unmount and
check the introspection endpoint one last time.
@Molter73
Molter73 force-pushed the mauro/ROX-33036/track-mount branch from 72aa4a2 to 3d17ab3 Compare July 29, 2026 09:25
@Molter73
Molter73 changed the base branch from mauro/feat/inodes-introspection to mauro/refactor/cleanup-file-data-accesors July 29, 2026 09:28
@Molter73
Molter73 marked this pull request as ready for review July 29, 2026 10:33
@Molter73
Molter73 requested a review from a team as a code owner July 29, 2026 10:33
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.

3 participants