Re: [PATCH v2] evm: fix TOCTOU heap OOB in protected xattr reads
From: Roberto Sassu
Date: Mon Nov 09 2026 - 04:20:15 EST
On 11/8/2026 12:05 AM, Jérémy Jean wrote:
ima_eventinodexattrs_init_common() queries the total size of protected
xattrs, allocates that amount, and calls evm_read_protected_xattrs()
again to fill the buffer. This creates a TOCTOU window: if the
protected xattr changes between the two calls to
evm_read_protected_xattrs(), names and lengths are written without
checking whether the data still fits in the target buffer. This can
yield an OOB heap write that KASAN reports as:
BUG: KASAN: slab-out-of-bounds in evm_read_protected_xattrs+0x15e/0x280
Write of size 20 at addr ffff888002c59bcd by task init/62
Reject fields that no longer fit in the target buffer. For xattr
values, also check the length returned by the copy before accounting it.
This also handles the case where a size of 0 is treated as a length
query.
Fixes: 8314b6732ae4e ("ima: Define new template fields xattrnames, xattrlengths and xattrvalues")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
---
Changes since v1 (after review from Sashiko):
- Handle the 0 case.
Hi Jeremy
I already sent a patch to the mailing list to fix the same problem (it is similar to yours):
https://lore.kernel.org/linux-integrity/20261029142707.2602008-1-roberto.sassu@xxxxxxxxxxxxxxx/
Thanks
Roberto
v1: https://lore.kernel.org/all/20261107194909.1061154-2-Jeremy.Jean@xxxxxxxxxxxxxxxxx/
Sashiko: https://sashiko.dev/#/message/20261107194909.1061154-2-Jeremy.Jean%40oss.cyber.gouv.fr
security/integrity/evm/evm_main.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index ac8328812e6ef..2ed0b67b9109b 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -389,6 +389,10 @@ int evm_read_protected_xattrs(struct dentry *dentry, u8 *buffer,
case 'n':
size = strlen(xattr->name) + 1;
if (buffer) {
+ if (total_size > buffer_size ||
+ size > buffer_size - total_size)
+ return -ERANGE;
+
if (total_size)
*(buffer + total_size - 1) = '|';
@@ -398,6 +402,10 @@ int evm_read_protected_xattrs(struct dentry *dentry, u8 *buffer,
case 'l':
size = sizeof(u32);
if (buffer) {
+ if (total_size > buffer_size ||
+ size > buffer_size - total_size)
+ return -ERANGE;
+
if (canonical_fmt)
rc = (__force int)cpu_to_le32(rc);
@@ -407,12 +415,20 @@ int evm_read_protected_xattrs(struct dentry *dentry, u8 *buffer,
case 'v':
size = rc;
if (buffer) {
+ if (total_size > buffer_size ||
+ size > buffer_size - total_size)
+ return -ERANGE;
+
rc = __vfs_getxattr(dentry,
d_backing_inode(dentry), xattr->name,
buffer + total_size,
buffer_size - total_size);
if (rc < 0)
return rc;
+ /* A size of 0 may be treated as a query. */
+ if (rc > buffer_size - total_size)
+ return -ERANGE;
+ size = rc;
}
break;
default: