Skip to content

Jv rox 33034 track xattr changes test all events#864

Open
JoukoVirtanen wants to merge 6 commits into
jv-ROX-33034-track-xattr-changesfrom
jv-ROX-33034-track-xattr-changes-test-all-events
Open

Jv rox 33034 track xattr changes test all events#864
JoukoVirtanen wants to merge 6 commits into
jv-ROX-33034-track-xattr-changesfrom
jv-ROX-33034-track-xattr-changes-test-all-events

Conversation

@JoukoVirtanen

@JoukoVirtanen JoukoVirtanen commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

A detailed explanation of the changes in your PR.

Feel free to remove this section if it is overkill for your PR, and the title of your PR is sufficiently descriptive.

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

TODO(replace-me)
Use this space to explain how you tested your PR, or, if you didn't test it, why you did not do so. (Valid reasons include "CI is sufficient" or "No testable changes")
In addition to reviewing your code, reviewers must also review your testing instructions, and make sure they are sufficient.

For more details, ref the Confluence page about this section.

Summary by CodeRabbit

  • New Features

    • Added end-to-end support for extended attribute (xattr) activity events, including SELinux xattrs.
    • Introduced new xattr set/remove event types, xattr-aware event handling, and gRPC/protobuf mapping.
    • Added corresponding per-hook xattr metrics.
  • Bug Fixes

    • Improved robustness of xattr event payloads when filename data is missing.
    • Fixed metrics aggregation to include the new xattr counters.
  • Tests

    • Added integration tests covering xattr set/remove, ignored/unmonitored behavior, and UTF-8/non-UTF-8 names.
    • Updated Docker/SELinux-based expectations to include extra xattr events.
  • Chores

    • Updated a pinned third-party dependency revision.

@JoukoVirtanen JoukoVirtanen requested a review from a team as a code owner June 22, 2026 17:17
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • main
  • ^release-*$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 6f071fdc-d074-498e-b937-ae00994e1dd2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds end-to-end tracking of Linux extended attribute (xattr) set and remove operations. New eBPF types, LSM hooks, a shared handle_xattr helper, and a submit_xattr_event function emit events from kernel space. The Rust layer gains XattrFileData, two FileData enum variants, accessor routing, protobuf conversion, and Prometheus metric counters. Integration tests validate the full pipeline, and Docker SELinux xattr fixtures are added to existing tests. The third_party/stackrox submodule is bumped independently.

Changes

Xattr Event Tracking Feature

Layer / File(s) Summary
eBPF xattr type definitions and event payload
fact-ebpf/src/bpf/types.h
Adds XATTR_NAME_MAX_LEN, FILE_ACTIVITY_SETXATTR/REMOVEXATTR enum values, an xattr.name payload union member in event_t, and two metrics_by_hook_t counters in metrics_t.
eBPF null-filename fix, xattr event helper, and LSM hooks
fact-ebpf/src/bpf/events.h, fact-ebpf/src/bpf/main.c
Fixes __submit_event for NULL filenames, adds submit_xattr_event and the shared handle_xattr dispatcher, and updates both LSM hook programs to delegate through it.
Rust metrics accumulation and Prometheus counters
fact-ebpf/src/lib.rs, fact/src/metrics/kernel_metrics.rs
Adds inode_setxattr/inode_removexattr to metrics_t::accumulate, and adds, registers, and collects corresponding EventCounter fields in KernelMetrics.
Rust XattrFileData struct, FileData variants, and Event accessor routing
fact/src/event/mod.rs
Introduces XattrFileData, FileData::SetXattr/RemoveXattr variants and their construction from kernel bytes, Event::is_xattr(), accessor routing for all Event getters/setters, protobuf conversion, and PartialEq.
Test event model xattr support, docker_selinux_xattr fixture, and selinux_xattr helper
tests/event.py, tests/conftest.py
Extends EventType, Event, and diff/str methods for xattr, adds selinux_xattr factory, and introduces get_dockerd_process() and docker_selinux_xattr fixture for Docker SELinux tracking.
Xattr integration test suite
tests/test_xattr.py
Seven integration tests covering set, remove, multiple xattrs, monitoring filtering, new-file tracking, UTF-8 filenames, and UTF-8 xattr names.
Docker SELinux xattr fixture integration into existing tests
tests/test_path_rename.py, tests/test_file_open.py, tests/test_misc.py, tests/test_path_chmod.py, tests/test_path_chown.py, tests/test_path_unlink.py
Updates container-related tests to accept and incorporate the docker_selinux_xattr fixture into their expected event sequences.

Third-party Submodule Update

Layer / File(s) Summary
stackrox submodule pointer update
third_party/stackrox
Bumps the subproject to a new pinned commit hash.

Sequence Diagram

sequenceDiagram
    participant LSM as LSM Hook (inode_setxattr / inode_removexattr)
    participant handle_xattr
    participant submit_xattr_event
    participant Ringbuf as eBPF Ringbuf
    participant fact_lib as fact Rust Library
    participant KernelMetrics

    LSM->>handle_xattr: dentry, xattr_name, FILE_ACTIVITY_SETXATTR/REMOVEXATTR
    handle_xattr->>handle_xattr: increment hook counter, derive inode keys, check monitored
    handle_xattr->>submit_xattr_event: args, event_type, xattr_name
    submit_xattr_event->>Ringbuf: reserve slot, write event_t with xattr.name
    Ringbuf->>fact_lib: event_t delivered to userspace
    fact_lib->>fact_lib: FileData::new → XattrFileData { inner, xattr_name }
    fact_lib->>KernelMetrics: accumulate inode_setxattr / inode_removexattr counters
    fact_lib-->>fact_lib: Event accessors route through XattrFileData.inner
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • ovalenti

Poem

🐇 Hoppin' through the kernel land,
Where xattrs hide at inode's hand,
I sniff the LSM hooks with glee,
Set and remove — now both tracked, you see!
The ringbuf sings, the metrics soar,
This little rabbit counts them more. 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description consists entirely of an unfilled template with placeholder content, unchecked checklist items, and a 'TODO(replace-me)' note in the Testing Performed section. Complete the description by explaining the xattr tracking implementation, marking relevant checklist items, detailing integration tests added (test_xattr.py with 7 test functions), and documenting how the changes were validated.
Docstring Coverage ⚠️ Warning Docstring coverage is 48.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and uses generic terms. "track xattr changes test all events" lacks clarity about what changes are being made and which events are being tested. Revise the title to be more specific and descriptive, such as "Add xattr change event tracking and comprehensive tests" or "Support SELinux xattr change monitoring with full test coverage".
✅ Passed checks (2 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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jv-ROX-33034-track-xattr-changes-test-all-events

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

@codecov-commenter

codecov-commenter commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 28.27%. Comparing base (5291250) to head (3a60638).

Additional details and impacted files
@@                        Coverage Diff                        @@
##           jv-ROX-33034-track-xattr-changes     #864   +/-   ##
=================================================================
  Coverage                             28.27%   28.27%           
=================================================================
  Files                                    21       21           
  Lines                                  2568     2568           
  Branches                               2568     2568           
=================================================================
  Hits                                    726      726           
  Misses                                 1839     1839           
  Partials                                  3        3           

☔ 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.

@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.

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)

498-510: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add the missing Chown equality branch in FileData::eq.

At Line 500-510, FileData::Chown is not matched, so two ownership events compare unequal even when identical.

Suggested fix
 impl PartialEq for FileData {
     fn eq(&self, other: &Self) -> bool {
         match (self, other) {
             (FileData::Open(this), FileData::Open(other)) => this == other,
             (FileData::Creation(this), FileData::Creation(other)) => this == other,
             (FileData::MkDir(this), FileData::MkDir(other)) => this == other,
             (FileData::RmDir(this), FileData::RmDir(other)) => this == other,
             (FileData::Unlink(this), FileData::Unlink(other)) => this == other,
             (FileData::Chmod(this), FileData::Chmod(other)) => this == other,
+            (FileData::Chown(this), FileData::Chown(other)) => this == other,
             (FileData::Rename(this), FileData::Rename(other)) => this == other,
             (FileData::SetXattr(this), FileData::SetXattr(other)) => this == other,
             (FileData::RemoveXattr(this), FileData::RemoveXattr(other)) => this == other,
             _ => 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 498 - 510, The PartialEq implementation
for FileData is missing a match arm for the Chown variant. Add a branch in the
eq method of FileData that matches FileData::Chown(this) against
FileData::Chown(other) and compares them with the equality operator, placing it
before the catch-all _ => false pattern so that two identical Chown variants
will correctly compare as equal instead of falling through to the default false
case.
🤖 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.

Outside diff comments:
In `@fact/src/event/mod.rs`:
- Around line 498-510: The PartialEq implementation for FileData is missing a
match arm for the Chown variant. Add a branch in the eq method of FileData that
matches FileData::Chown(this) against FileData::Chown(other) and compares them
with the equality operator, placing it before the catch-all _ => false pattern
so that two identical Chown variants will correctly compare as equal instead of
falling through to the default false case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: ed680bfc-18a7-4c4c-a340-26645efbe5c3

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9803c and 1a33c77.

📒 Files selected for processing (10)
  • 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/metrics/kernel_metrics.rs
  • tests/event.py
  • tests/test_path_rename.py
  • tests/test_xattr.py
  • third_party/stackrox

@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: 1

🤖 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/conftest.py`:
- Around line 20-29: The get_dockerd_process() function can raise exceptions
from the subprocess.run call when pgrep is unavailable and from the
Process.from_proc() call when there is a race condition with the process
disappearing from /proc, which causes tests to abort instead of gracefully
returning None. Wrap the subprocess.run() call and the Process.from_proc() call
in try-except blocks to catch any exceptions (such as FileNotFoundError or
exceptions from Process.from_proc when the pid no longer exists) and return None
on any failure, ensuring the function consistently returns None for all error
cases.
🪄 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: c78001fd-b64b-4bf7-95e5-ad83281b51c5

📥 Commits

Reviewing files that changed from the base of the PR and between 1a33c77 and ded77be.

📒 Files selected for processing (7)
  • tests/conftest.py
  • tests/test_file_open.py
  • tests/test_misc.py
  • tests/test_path_chmod.py
  • tests/test_path_chown.py
  • tests/test_path_rename.py
  • tests/test_path_unlink.py

Comment thread tests/conftest.py
Comment on lines +20 to +29
def get_dockerd_process() -> Process | None:
result = subprocess.run(
['pgrep', 'dockerd'],
capture_output=True,
text=True,
)
if result.returncode != 0:
return None
pid = int(result.stdout.strip().split('\n')[0])
proc = Process.from_proc(pid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden dockerd discovery failure paths.

Line 21 and Line 29 can raise (pgrep unavailable, PID race on /proc), which aborts tests instead of gracefully returning None like the rest of this helper intends.

Suggested fix
 def get_dockerd_process() -> Process | None:
-    result = subprocess.run(
-        ['pgrep', 'dockerd'],
-        capture_output=True,
-        text=True,
-    )
+    try:
+        result = subprocess.run(
+            ['pgrep', 'dockerd'],
+            capture_output=True,
+            text=True,
+            check=False,
+            timeout=2,
+        )
+    except (OSError, subprocess.TimeoutExpired):
+        return None
     if result.returncode != 0:
         return None
-    pid = int(result.stdout.strip().split('\n')[0])
-    proc = Process.from_proc(pid)
+    lines = [line for line in result.stdout.splitlines() if line]
+    if not lines:
+        return None
+    try:
+        pid = int(lines[0])
+        proc = Process.from_proc(pid)
+    except (ValueError, FileNotFoundError, PermissionError):
+        return None
     return Process(
         pid=None,
         uid=proc.uid,
🧰 Tools
🪛 ast-grep (0.44.0)

[error] 20-24: Command coming from incoming request
Context: subprocess.run(
['pgrep', 'dockerd'],
capture_output=True,
text=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🤖 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/conftest.py` around lines 20 - 29, The get_dockerd_process() function
can raise exceptions from the subprocess.run call when pgrep is unavailable and
from the Process.from_proc() call when there is a race condition with the
process disappearing from /proc, which causes tests to abort instead of
gracefully returning None. Wrap the subprocess.run() call and the
Process.from_proc() call in try-except blocks to catch any exceptions (such as
FileNotFoundError or exceptions from Process.from_proc when the pid no longer
exists) and return None on any failure, ensuring the function consistently
returns None for all error cases.

@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: 1

🤖 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/conftest.py`:
- Around line 33-38: The second pgrep lookup on lines 33-38 can return a
different dockerd process than the one identified at line 28, and the brittle
string parsing with split()[1] can raise an IndexError if the output is empty or
unexpected. Instead of running a new pgrep command that could match any dockerd
process, use the specific pid that was already determined at line 28 to get the
exe_path from that same process (such as by reading from /proc/{pid}/exe or by
incorporating the pid into the command), and add proper error handling to
gracefully handle unexpected or empty output without crashing the test setup.
🪄 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: 398c7110-e380-4587-b8a0-bce7827c3c91

📥 Commits

Reviewing files that changed from the base of the PR and between ded77be and 9e5e8fb.

📒 Files selected for processing (1)
  • tests/conftest.py

Comment thread tests/conftest.py
Comment on lines +33 to +38
result = subprocess.run(
['pgrep', '-a', 'dockerd'],
capture_output=True,
text=True,
)
exe_path = result.stdout.strip().split('\n')[0].split()[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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid mixed process identity and brittle parsing in the second pgrep lookup.

Line 33-38 can pair exe_path from a different dockerd process than the pid chosen at Line 28, and split()[1] can raise if output is empty/unexpected. That makes the fixture flaky and can crash test setup.

Suggested fix
-    result = subprocess.run(
+    result = subprocess.run(
         ['pgrep', '-a', 'dockerd'],
         capture_output=True,
         text=True,
     )
-    exe_path = result.stdout.strip().split('\n')[0].split()[1]
+    if result.returncode != 0:
+        return None
+
+    exe_path = None
+    for line in result.stdout.splitlines():
+        parts = line.split(maxsplit=1)
+        if len(parts) != 2:
+            continue
+        if parts[0] != str(pid):
+            continue
+        cmdline = parts[1].strip()
+        if not cmdline:
+            continue
+        exe_path = cmdline.split()[0]
+        break
+    if exe_path is None:
+        return None
📝 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
result = subprocess.run(
['pgrep', '-a', 'dockerd'],
capture_output=True,
text=True,
)
exe_path = result.stdout.strip().split('\n')[0].split()[1]
result = subprocess.run(
['pgrep', '-a', 'dockerd'],
capture_output=True,
text=True,
)
if result.returncode != 0:
return None
exe_path = None
for line in result.stdout.splitlines():
parts = line.split(maxsplit=1)
if len(parts) != 2:
continue
if parts[0] != str(pid):
continue
cmdline = parts[1].strip()
if not cmdline:
continue
exe_path = cmdline.split()[0]
break
if exe_path is None:
return None
🤖 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/conftest.py` around lines 33 - 38, The second pgrep lookup on lines
33-38 can return a different dockerd process than the one identified at line 28,
and the brittle string parsing with split()[1] can raise an IndexError if the
output is empty or unexpected. Instead of running a new pgrep command that could
match any dockerd process, use the specific pid that was already determined at
line 28 to get the exe_path from that same process (such as by reading from
/proc/{pid}/exe or by incorporating the pid into the command), and add proper
error handling to gracefully handle unexpected or empty output without crashing
the test setup.

@JoukoVirtanen JoukoVirtanen changed the base branch from main to jv-ROX-33034-track-xattr-changes June 23, 2026 06:17
@JoukoVirtanen JoukoVirtanen force-pushed the jv-ROX-33034-track-xattr-changes branch from 4770b44 to 5291250 Compare June 23, 2026 18:16
@JoukoVirtanen JoukoVirtanen force-pushed the jv-ROX-33034-track-xattr-changes-test-all-events branch from 24655a1 to 3a60638 Compare June 23, 2026 21:14
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.

2 participants