[PATCH 1/2] mm/memfd: add F_SEAL_PUNCH_HOLE

From: Samiullah Khawaja

Date: Mon Oct 26 2026 - 18:06:50 EST


Users of memfd_pin_folios(), such as udmabuf and iommufd
(IOMMU_IOAS_MAP_FILE), pin the folios backing a memfd range and keep
using them for DMA. A pin keeps a folio alive, but it does not keep the
folio in the file. If userspace later punches a hole with
fallocate(FALLOC_FL_PUNCH_HOLE) or madvise(MADV_REMOVE), the folio is
removed from the page cache while the pin still holds it, and the pin
holder is not told. From then on the device and the file no longer
share memory: data written by the device is not visible through the
file, and data written through the file never reaches the device.

udmabuf already requires F_SEAL_SHRINK so that truncation cannot remove
pages, but there is no seal that prevents punching holes without also
preventing writes. F_SEAL_WRITE and F_SEAL_FUTURE_WRITE do block hole
punching, but they cannot be used for memory that has to stay writable,
and udmabuf rejects memfds that carry them.

Add F_SEAL_PUNCH_HOLE, which makes fallocate(FALLOC_FL_PUNCH_HOLE) fail
with -EPERM on shmem and hugetlbfs memfds. MADV_REMOVE is covered as
well, because it is implemented with vfs_fallocate() and
FALLOC_FL_PUNCH_HOLE.

Signed-off-by: Samiullah Khawaja <skhawaja@xxxxxxxxxx>
---
fs/hugetlbfs/inode.c | 3 ++-
include/uapi/linux/fcntl.h | 1 +
mm/memfd.c | 4 +++-
mm/shmem.c | 3 ++-
4 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index e8a05d3de327..65034b72b98e 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -660,7 +660,8 @@ static long hugetlbfs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
inode_lock(inode);

/* protected by i_rwsem */
- if (info->seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE)) {
+ if (info->seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE |
+ F_SEAL_PUNCH_HOLE)) {
inode_unlock(inode);
return -EPERM;
}
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index e43e3de3e9ee..f9dd929adaf4 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -50,6 +50,7 @@
#define F_SEAL_WRITE 0x0008 /* prevent writes */
#define F_SEAL_FUTURE_WRITE 0x0010 /* prevent future writes while mapped */
#define F_SEAL_EXEC 0x0020 /* prevent chmod modifying exec bits */
+#define F_SEAL_PUNCH_HOLE 0x0040 /* prevent punching holes */
/* (1U << 31) is reserved for signed error codes */

/*
diff --git a/mm/memfd.c b/mm/memfd.c
index 0f6fff004f5e..3f0ef72fb301 100644
--- a/mm/memfd.c
+++ b/mm/memfd.c
@@ -227,7 +227,8 @@ static unsigned int *memfd_file_seals_ptr(struct file *file)
F_SEAL_SHRINK | \
F_SEAL_GROW | \
F_SEAL_WRITE | \
- F_SEAL_FUTURE_WRITE)
+ F_SEAL_FUTURE_WRITE | \
+ F_SEAL_PUNCH_HOLE)

int memfd_add_seals(struct file *file, unsigned int seals)
{
@@ -253,6 +254,7 @@ int memfd_add_seals(struct file *file, unsigned int seals)
* SEAL_GROW: Prevent the file from growing
* SEAL_WRITE: Prevent write access to the file
* SEAL_EXEC: Prevent modification of the exec bits in the file mode
+ * SEAL_PUNCH_HOLE: Prevent punching holes into the file
*
* As we don't require any trust relationship between two parties, we
* must prevent seals from being removed. Therefore, sealing a file
diff --git a/mm/shmem.c b/mm/shmem.c
index fd0953d55fd6..e79a298d6292 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -3853,7 +3853,8 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset,
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(shmem_falloc_waitq);

/* protected by i_rwsem */
- if (info->seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE)) {
+ if (info->seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE |
+ F_SEAL_PUNCH_HOLE)) {
error = -EPERM;
goto out;
}
--
2.56.0.307.g5fe7bd680c-goog