[PATCH 2/2] dlm: require CAP_SYS_ADMIN for dlm-monitor device
From: Haofeng Li
Date: Thu Jul 16 2026 - 03:22:30 EST
monitor_device_open() in fs/dlm/user.c performs only
atomic_inc(&dlm_monitor_opened) and sets dlm_monitor_unused = 0; it
does no capability check. monitor_device_close() does
atomic_dec_and_test(&dlm_monitor_opened) and, when the count reaches
zero, calls dlm_stop_lockspaces() — which stops every lockspace on
the node. The miscdevice is also registered with no .mode field.
Attack chain (when the device node is reachable by an unprivileged
opener — see mitigation note below):
1. attacker open("/dev/dlm-monitor") with no cap check; the
global counter goes 0 -> 1
2. attacker close(fd); atomic_dec_and_test reaches zero again
and dlm_stop_lockspaces() runs -> every DLM lockspace on the
local node is stopped. Other cluster members then observe
the node losing its lockspaces (membership / recovery side
effects), so the impact is not strictly local to GFS2 /
OCFS2 / lvmlockd / cluster-md workloads on this node.
variant: attacker holds the fd open indefinitely to suppress
the intended stop when dlm_controld later closes its own fd
(inverse abuse — recovery / shutdown stalls)
Mitigation: devtmpfs creates /dev/dlm-monitor as 0600 root:root on
a stock kernel, so unprivileged open is blocked by the node mode,
not by a kernel cap check. The gap is real wherever the node is
reachable (udev MODE=0666, container bind-mount, fd via SCM_RIGHTS,
or any setup where dlm_controld shares its monitor fd).
Reproduction (kernel 7.2.0-rc3, dlm loaded, no live lockspace):
# ./exploit_h3 # as root
[*] lockspace devices present: 0
[!!!] AUTH BYPASS: opened /dev/dlm-monitor, no cap check (fd=3)
[VULNERABLE] monitor open auth bypass demonstrated
$ setpriv --reuid 65534 --regid 65534 ./exploit_h3
[OK ] open denied: Permission denied # node 0600, not cap check
The destructive close path is opt-in in the PoX
(--i-know-it-stops-lockspaces); we did not drive it here. Driving
the close path on a node with active lockspaces would stop them;
on this throw-away node there are none, but we keep the opt-in gate
so the same PoX is safe to re-run on production-like clusters.
Fix: gate monitor_device_open() on capable(CAP_SYS_ADMIN) and set
.mode = 0600 on monitor_device, matching the dlm_controld-only
intended usage.
Signed-off-by: Haofeng Li <lihaofeng@xxxxxxxxxx>
---
fs/dlm/user.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/fs/dlm/user.c b/fs/dlm/user.c
index a8ed4c8fdc5b..cd7e142ca670 100644
--- a/fs/dlm/user.c
+++ b/fs/dlm/user.c
@@ -4,6 +4,7 @@
*/
#include <linux/miscdevice.h>
+#include <linux/capability.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/file.h>
@@ -910,6 +911,10 @@ static int ctl_device_close(struct inode *inode, struct file *file)
static int monitor_device_open(struct inode *inode, struct file *file)
{
+ /* dlm_controld is the only expected opener; last close stops LS. */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
atomic_inc(&dlm_monitor_opened);
dlm_monitor_unused = 0;
return 0;
@@ -958,6 +963,7 @@ static struct miscdevice monitor_device = {
.name = "dlm-monitor",
.fops = &monitor_device_fops,
.minor = MISC_DYNAMIC_MINOR,
+ .mode = 0600,
};
int __init dlm_user_init(void)
--
2.25.1