[PATCH 1/2] dlm: gate dlm_plock device on CAP_SYS_ADMIN

From: Haofeng Li

Date: Thu Jul 16 2026 - 03:22:21 EST


fs/dlm/plock.c registers /dev/dlm_plock via a miscdevice whose
file_operations has no .open callback and whose .mode field is unset.
The kernel therefore performs no capability check on open, and any
process that can open the node becomes an unprivileged plock daemon
with full read+write access to the pending-plock queue.

Attack chain (when the device node is reachable by an unprivileged
opener — see mitigation note below):

1. attacker open("/dev/dlm_plock") succeeds with no cap check
2. dev_read() drains pending plock requests straight to user
space, leaking dlm_plock_info fields: fsid, number (resource
id / inode), start, end, owner, pid, ex, wait
3. dev_write() matches an attacker-supplied dlm_plock_info
against a pending op on recv_list (matched on fsid+number+
owner+pid+start+end+ex+wait) and memcpy()'s it into the
in-kernel op, including a forged .rv == 0
4. when a matched op exists, the forged result is then applied:
for async ops (op->data != NULL), dlm_plock_callback() runs
posix_lock_file(); for sync ops, the requester wakes and
proceeds as if the cluster had granted the lock. Either way
the requester ends up holding a POSIX lock without a real DLM
grant, or sees attacker-chosen rv that breaks lock correctness.
With no pending op on recv_list, dev_write() still returns
sizeof(info) but does nothing beyond a pr_debug ("dlm dev_write
no op ...") — so a syntactically accepted write does not by
itself prove an applied grant.

Mitigation: on a stock kernel, devtmpfs creates /dev/dlm_plock as
0600 root:root, so steps 1-4 are only reachable where the node is
exposed to a less privileged principal — e.g. udev MODE=0666,
container bind-mount of the node, or an fd passed via SCM_RIGHTS.
The in-kernel capability gap is real regardless of node mode.

Reproduction (kernel 7.2.0-rc3, dlm loaded):

# ./exploit_h2 # as root
[*] node /dev/dlm_plock mode=0600 uid=0 gid=0
[!!!] AUTH BYPASS: opened with no capability check (fd=3)
[!!!] FORGE ACCEPTED: kernel accepted forged plock result (rv=0);
pending ops are grant-forgeable
[VULNERABLE] open + read-leak + grant-forge demonstrated

The "FORGE ACCEPTED" line means dev_write() returned sizeof(info),
i.e. the write path is reachable and the version check passed; it
does not by itself mean a grant was applied. Demonstrating an
actual forged grant requires a concurrent plock op on recv_list to
match against.

$ setpriv --reuid 65534 --regid 65534 ./exploit_h2
[OK ] open denied: Permission denied # devtmpfs 0600,
not a kernel cap check

Fix: add a .open callback that requires CAP_SYS_ADMIN, and set
.mode = 0600 on the miscdevice so the explicit expectation matches
the devtmpfs default and survives future defaults.

Signed-off-by: Haofeng Li <lihaofeng@xxxxxxxxxx>
---
fs/dlm/plock.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/fs/dlm/plock.c b/fs/dlm/plock.c
index e9598b3fe5d0..711e8bc3a46a 100644
--- a/fs/dlm/plock.c
+++ b/fs/dlm/plock.c
@@ -4,6 +4,7 @@
*/

#include <linux/fs.h>
+#include <linux/capability.h>
#include <linux/filelock.h>
#include <linux/miscdevice.h>
#include <linux/poll.h>
@@ -477,6 +478,15 @@ int dlm_posix_get(dlm_lockspace_t *lockspace, u64 number, struct file *file,
}
EXPORT_SYMBOL_GPL(dlm_posix_get);

+static int dev_open(struct inode *inode, struct file *file)
+{
+ /* Userspace plock daemon is a privileged cluster component. */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return 0;
+}
+
/* a read copies out one plock request from the send list */
static ssize_t dev_read(struct file *file, char __user *u, size_t count,
loff_t *ppos)
@@ -598,6 +608,7 @@ static __poll_t dev_poll(struct file *file, poll_table *wait)
}

static const struct file_operations dev_fops = {
+ .open = dev_open,
.read = dev_read,
.write = dev_write,
.poll = dev_poll,
@@ -608,7 +619,8 @@ static const struct file_operations dev_fops = {
static struct miscdevice plock_dev_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DLM_PLOCK_MISC_NAME,
- .fops = &dev_fops
+ .fops = &dev_fops,
+ .mode = 0600,
};

int dlm_plock_init(void)
--
2.25.1