Skip to content

Jv rox 33034 track xattr changes test all events - #864

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

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

Conversation

@JoukoVirtanen

@JoukoVirtanenJoukoVirtanen 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 ownerJune 22, 2026 17:17
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Description check⚠️ WarningThe 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⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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.

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

@coderabbitaicoderabbitaiBot 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 threadtests/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.

@coderabbitaicoderabbitaiBot 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 threadtests/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,
)
ifresult.returncode!=0:
returnNone
exe_path=None
forlineinresult.stdout.splitlines():
parts=line.split(maxsplit=1)
iflen(parts) !=2:
continue
ifparts[0] !=str(pid):
continue
cmdline=parts[1].strip()
ifnotcmdline:
continue
exe_path=cmdline.split()[0]
break
ifexe_pathisNone:
returnNone
🤖 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-changesJune 23, 2026 06:17
@JoukoVirtanenJoukoVirtanen mentioned this pull request Jun 23, 2026
6 tasks
@JoukoVirtanen
JoukoVirtanenforce-pushed the jv-ROX-33034-track-xattr-changes branch from 4770b44 to 5291250CompareJune 23, 2026 18:16
@JoukoVirtanen
JoukoVirtanenforce-pushed the jv-ROX-33034-track-xattr-changes-test-all-events branch from 24655a1 to 3a60638CompareJune 23, 2026 21:14
@JoukoVirtanen
JoukoVirtanenforce-pushed the jv-ROX-33034-track-xattr-changes branch 2 times, most recently from a4a89b3 to 24c4c6aCompareJune 25, 2026 16:46
Base automatically changed from jv-ROX-33034-track-xattr-changes to mainJune 26, 2026 15:36
Sign up for freeto 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

@JoukoVirtanen@codecov-commenter