Skip to content

Add ProcFdPlugin for Linux process FD analysis#1628

Open
Torres773 wants to merge 6 commits intofox-it:mainfrom
Torres773:feat/proc-fd-plugin
Open

Add ProcFdPlugin for Linux process FD analysis#1628
Torres773 wants to merge 6 commits intofox-it:mainfrom
Torres773:feat/proc-fd-plugin

Conversation

@Torres773
Copy link

Depends on #1609

Add ProcFdPlugin for Linux process FD analysis

Implement a new plugin to analyze open file descriptors by parsing
/proc/[pid]/fd and fdinfo. This provides forensic visibility into
files, sockets, and pipes used by running processes.

The plugin yields FileDescriptorRecord which includes file offsets
and access flags. This builds upon the recent FileDescriptor
iterator addition in the proc parser.

@Schamper Schamper requested a review from Horofic March 17, 2026 16:53
def test_fd(target_linux_users: Target, fs_linux_proc: VirtualFilesystem) -> None:
target_linux_users.add_plugin(ProcPlugin)
results = list(target_linux_users.fd())
assert len(results) == 4 No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

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

Missing newline at end of file.

Suggested change
assert len(results) == 4
assert len(results) == 4

"""Return information about open file descriptors for all processes.

This plugin identifies files, sockets, pipes, and other artifacts
currently in use by processes by parsing /proc/[pid]/fd and fdinfo.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
currently in use by processes by parsing /proc/[pid]/fd and fdinfo.
currently in use by processes by parsing ``/proc/[pid]/fd`` and ``/proc/[pid]/fdinfo`` entries.

Comment on lines +54 to +57
try:
ts = fd_obj.path.stat().st_mtime
except Exception:
ts = None
Copy link
Member

Choose a reason for hiding this comment

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

Please make the exception a bit more specific here.

Suggested change
try:
ts = fd_obj.path.stat().st_mtime
except Exception:
ts = None
try:
ts = fd_obj.path.stat().st_mtime
except Exception:
ts = None

pid (varint): The process id (pid) of the process.
name (string): The name associated to the pid.
fd (varint): The file descriptor number.
path (string): The resolved path or resource link.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
path (string): The resolved path or resource link.
link (string): The resolved path or file-type and inode.

name (string): The name associated to the pid.
fd (varint): The file descriptor number.
path (string): The resolved path or resource link.
pos (uint64): The current file offset from fdinfo.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pos (uint64): The current file offset from fdinfo.
pos (varint): The current file offset from fdinfo.

Comment on lines +33 to +34
@export(record=FileDescriptorRecord)
def fd(self) -> Iterator[FileDescriptorRecord]:
Copy link
Member

Choose a reason for hiding this comment

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

So you can access the plugin using filedescriptor as well. Requires the necessary imports.

Suggested change
@export(record=FileDescriptorRecord)
def fd(self) -> Iterator[FileDescriptorRecord]:
@alias("filedescriptor")
def fd(self) -> Iterator[FileDescriptorRecord]:


from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export
Copy link
Member

Choose a reason for hiding this comment

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

Import for the alias suggestion.

Suggested change
from dissect.target.plugin import Plugin, export
from dissect.target.plugin import Plugin, alias, export

Comment on lines +58 to +67
yield FileDescriptorRecord(
ts=ts,
pid=process.pid,
name=process.name,
fd=fd_obj.number,
link=fd_obj.link,
pos=int(fd_obj.info.get("pos", 0)),
flags=fd_obj.info.get("flags", "0"),
_target=self.target,
)
Copy link
Member

Choose a reason for hiding this comment

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

I think passing None is a bit more 'correct' when the values are not present. This code might need a little change when you go the FdInfo route mentioned in PR #1609

Suggested change
yield FileDescriptorRecord(
ts=ts,
pid=process.pid,
name=process.name,
fd=fd_obj.number,
link=fd_obj.link,
pos=int(fd_obj.info.get("pos", 0)),
flags=fd_obj.info.get("flags", "0"),
_target=self.target,
)
yield FileDescriptorRecord(
ts=ts,
pid=process.pid,
name=process.name,
fd=fd_obj.number,
link=fd_obj.link,
pos=fd_obj.info.get("pos", None),
flags=fd_obj.info.get("flags", None),
_target=self.target,
)

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