Patched on disk, old library still running: how to find stale Linux processes
Package updates can replace vulnerable shared libraries on disk while long-running Linux processes keep the old deleted objects mapped in memory. Here is how to inspect stale library mappings after patching.
Most Linux patch workflows are comfortable with package state.
dnf update ran. The fixed package version is installed. The scanner may calm down after its next scan. The spreadsheet or ticket gets a note that the host was patched.
That is useful evidence, but it is not always the end of the work.
On Linux, a running process can keep using a file after that file has been replaced or removed on disk. That behavior is normal. It is also why a service can keep old vulnerable code mapped in memory after the package that provided that code has already been updated.
The practical question after patching is not only:
Is the fixed package installed?
It is:
Is any running process still using the old library?
That is the gap this check is meant to close.
Why this happens
When a process starts, it may map shared libraries into memory. If the package manager later updates one of those libraries, the file on disk changes, but the already-running process does not magically reload the new object.
The old file can become unlinked from the filesystem while the process still has it open or mapped.
You may see that as:
/usr/lib64/libssl.so.3 (deleted)
That does not mean the file is sitting in the directory with the name (deleted). It means the running process still has a reference to an object that no longer has the same live filesystem entry.
For patch verification, that distinction matters.
The package can be fixed on disk while the service is still running code loaded before the update.
Start with the simple process-map check
The fastest first pass is to look for deleted mapped shared libraries under /proc.
sudo grep -H '(deleted)' /proc/[0-9]*/maps 2>/dev/null | \
grep -E '/(usr/)?lib(64)?/.*\.so'
This is intentionally narrow. It looks for process memory maps that reference deleted shared objects under common library paths.
Example shape:
/proc/1834/maps:7f2c2a000000-7f2c2a1e0000 r-xp ... /usr/lib64/libssl.so.3 (deleted)
From that line you can learn a few things:
- the process ID is
1834 - the mapped object is
libssl.so.3 - the object came from
/usr/lib64 - the live process still has the old object mapped
That is not yet a full vulnerability conclusion. It is a signal that the process deserves a closer look.
Get the process behind the mapping
Once you have the PID, identify the process.
ps -fp 1834
If the process is managed by systemd, this is often useful:
systemctl status 1834
You can also ask systemd which unit owns the main PID if you already know the service, but in a real investigation I usually start with ps and then follow the process tree.
pstree -asp 1834
The goal is to turn:
PID 1834 maps old libssl
into something an operator can act on:
nginx worker maps old libssl
restart nginx during approved service window
That is the difference between interesting evidence and useful evidence.
lsof +L1 gives another view
lsof can find open files with a link count less than one:
sudo lsof +L1
This catches deleted files that are still held open by processes. It can be helpful after patching, especially when you are trying to explain why disk state and runtime state disagree.
But it is also noisy.
You may see deleted temporary files, log files, sockets, runtime artifacts, or application files that have nothing to do with vulnerability remediation. That is why I treat lsof +L1 as a supporting view, not the whole answer.
For shared-library restart debt, /proc/[pid]/maps is often the more direct place to look.
needs-restarting helps, but still needs context
On RHEL-family systems, needs-restarting is one of the most practical tools for this problem when it is available.
For service/process restart checks:
sudo dnf needs-restarting
For reboot checks:
sudo dnf needs-restarting -r
echo $?
The exact packaging and output can vary by distro version and installed plugins, but the operator intent is clear: find processes or system state that still need a restart after package updates.
I like needs-restarting, but I still want the evidence to say what action is needed.
package fixed on disk
nginx still maps old libssl
action: restart nginx
That is more useful than a vague:
needs restart
Map the library back to a package
A deleted mapped object is not automatically a vulnerability finding.
You still need to connect it to package and advisory context.
For a live library path, package ownership is straightforward:
rpm -qf /usr/lib64/libssl.so.3
With a deleted mapping, you may need to strip the (deleted) suffix and check the current file path on disk. If the replacement file still exists at that path, that tells you which installed package owns it now.
rpm -qf /usr/lib64/libssl.so.3
rpm -q openssl-libs
Then you still need vendor metadata or your vulnerability data source to answer:
Which CVE maps to this package?
Which EVR contains the vendor fix?
Is the installed package at or above that fixed EVR?
Does this running process still matter for the finding?
The stale mapping is runtime evidence. Package/advisory matching is vulnerability evidence. You need both before calling the result.
Not every deleted mapping has the same urgency
This is where a lot of manual reviews go sideways.
A deleted .so mapping is worth investigating, but it is not always equally urgent.
Useful questions:
- Which package owned the library?
- Is the installed package below, at, or above the vendor-fixed EVR?
- Which process maps it?
- Is that process part of a listening service?
- Is the service exposed to the network?
- Is the CVE high severity, in KEV, or otherwise likely to matter?
- Is this a long-running daemon or a short-lived process that will exit naturally?
If nginx, sshd, postgres, a JVM, or an internet-facing application process maps an old security-sensitive library, that is a very different operational signal than an internal one-off helper process holding a deleted temporary file.
The evidence should preserve that distinction.
A practical classification model
For stale shared-library findings, I like these buckets.
Package update required
The installed package is below the vendor-fixed EVR.
status: package update required
action: update package
There is no point arguing about restart state until the fixed package is installed.
Restart required
The fixed package is installed, but a running process still maps the old deleted library.
status: restart required
action: restart affected service
This is the classic “patched on disk, old code still running” state.
Verified runtime clean
The fixed package is installed and runtime evidence does not show stale vulnerable library mappings for the relevant service.
status: verified runtime clean
checked_at: timestamp
This is the evidence you want before closing the loop.
Review required
The data is incomplete, noisy, containerized, or unclear.
status: review required
action: inspect package, process, container, and exposure context
This is better than pretending that every deleted mapping is either harmless or critical.
What the evidence should look like
For a useful stale-library check, I want a compact record:
Host:
Distro:
Checked at:
CVE / advisory:
Package:
Installed EVR:
Vendor-fixed EVR:
PID:
Process:
Service / unit:
Mapped library:
Package-state result:
Runtime-state result:
Recommended action:
Example:
Host: app-01
Distro: Rocky Linux 9.4
Checked at: 2026-08-02T15:22:11Z
CVE / advisory: vendor advisory for openssl-libs
Package: openssl-libs
Installed EVR: installed package is at or above vendor-fixed EVR
Vendor-fixed EVR: from vendor security metadata
PID: 1834
Process: nginx worker
Service / unit: nginx.service
Mapped library: /usr/lib64/libssl.so.3 (deleted)
Package-state result: fixed package installed on disk
Runtime-state result: process still maps old deleted library
Recommended action: restart nginx, then verify process maps again
That tells the Linux team what to do next. It tells security why the package result alone was not enough. It gives compliance something clearer than a screenshot pile.
Be careful with false certainty
There are a few traps worth avoiding.
Do not call a host remediated just because the fixed package is installed.
Do not call every deleted mapping vulnerable without package and advisory context.
Do not assume lsof +L1 output is all security-relevant.
Do not ignore stale mappings for long-running network services.
Do not treat missing runtime evidence as proof that no old code is running.
The safer language is more precise:
Fixed package installed.
Restart required.
Runtime evidence not collected.
No relevant stale mapping observed.
Review required.
Verified at this timestamp.
That is not as tidy as a green checkmark, but it is much closer to how Linux actually behaves.
The real goal is a better handoff
The scanner found a vulnerable package.
The package manager installed the fixed build.
The operator still needs to know whether any running service is carrying old code forward.
That last step is where a lot of patch evidence gets vague.
If the answer is “restart nginx,” say that.
If the answer is “reboot the host,” say that.
If the answer is “the package is still below the vendor-fixed EVR,” say that.
If the answer is “we did not collect runtime evidence,” say that too.
The point is not to make patching sound more complicated. The point is to stop flattening different states into the same word: patched.
If you want to see this evidence model in report form, the sample Patch Truth report shows how package state, runtime state, and next action fit together. For one supported RHEL-family host, the one-host snapshot is a quick way to test the flow on a real system.