Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE
@ 2026-10-26 22:05 Samiullah Khawaja
  2026-10-26 22:05 ` [PATCH 1/2] " Samiullah Khawaja
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Samiullah Khawaja @ 2026-10-26 22:05 UTC (permalink / raw)
  To: Andrew Morton, Hugh Dickins
  Cc: Baolin Wang, David Hildenbrand, Muchun Song, Oscar Salvador,
	Jeff Layton, Chuck Lever, Alexander Aring, Shuah Khan,
	Jason Gunthorpe, Gerd Hoffmann, Vivek Kasireddy, Jeff Xu,
	David Matlack, Pasha Tatashin, linux-mm, linux-api, linux-fsdevel,
	linux-kselftest, linux-kernel, Samiullah Khawaja

This series introduces F_SEAL_PUNCH_HOLE, a new memfd seal that prevents
holes from being punched into a shmem or hugetlbfs memfd while leaving
the file fully writable.

Background
==========

Drivers such as udmabuf and iommufd (via IOMMU_IOAS_MAP_FILE) use
memfd_pin_folios() to pin the folios backing a memfd and hand them to a
device for DMA. Pinning keeps those folios allocated, but it does not
keep them in the file. If userspace later calls
fallocate(FALLOC_FL_PUNCH_HOLE) or madvise(MADV_REMOVE) on the range,
the folios are dropped from the page cache while the device continues
to use them. The pin holder is never notified, and from that point on
the device and the file silently diverge.

The existing seals only partially address this. F_SEAL_SHRINK prevents
pages from being dropped by truncation, which is why udmabuf requires
it. Hole punching, however, is only blocked by F_SEAL_WRITE and
F_SEAL_FUTURE_WRITE, and both of those also forbid writes. That makes
them unusable for memory that must stay writable, such as guest RAM or
DMA buffers, and udmabuf explicitly rejects memfds that carry them.

Proposal
========

F_SEAL_PUNCH_HOLE fills this gap. Once it is set,
fallocate(FALLOC_FL_PUNCH_HOLE) and madvise(MADV_REMOVE) fail with
-EPERM, while reads, writes and resizing are unaffected. Combined with
F_SEAL_SHRINK, it guarantees that userspace cannot remove pages from
the memfd while the memfd remains writable.

Motivation
==========

The immediate user is IOMMU live update. Before iommufd preserves a
memfd-backed mapping across kexec, it must confirm that every pinned
page still belongs to the file. Today this requires walking each mapped
page at preserve time, which is costly for large mappings. With
F_SEAL_PUNCH_HOLE and F_SEAL_SHRINK in place, iommufd can verify the
seals once at IOMMU_IOAS_MAP_FILE time and skip the walk entirely.
udmabuf can benefit in the same way. Neither user is changed in this
series.

The iommufd change that would use the seal is discussed in the IOMMU
live update series:
https://lore.kernel.org/all/arVYa_90j0oN1hnu@google.com/

Testing
=======

Patch 2 extends the memfd selftests to cover the new seal on both shmem
and hugetlbfs. Both "memfd_test" and "memfd_test hugetlbfs" pass in
QEMU.

The hugetlbfs run depends on a separately posted fix. Without it, the
run aborts in the existing SEAL-EXEC test before it reaches the new
tests:
https://lore.kernel.org/all/20261026-fixes_shmem-v1-5-703206d5fa0c@kernel.org/

Samiullah Khawaja (2):
  mm/memfd: add F_SEAL_PUNCH_HOLE
  selftests/memfd: add F_SEAL_PUNCH_HOLE tests

 fs/hugetlbfs/inode.c                       |   3 +-
 include/uapi/linux/fcntl.h                 |   1 +
 mm/memfd.c                                 |   4 +-
 mm/shmem.c                                 |   3 +-
 tools/testing/selftests/memfd/memfd_test.c | 111 ++++++++++++++++++++-
 5 files changed, 118 insertions(+), 4 deletions(-)


base-commit: 7bf01f16a4d05028fbeff4d90bab40df25142a24
-- 
2.56.0.307.g5fe7bd680c-goog


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] mm/memfd: add F_SEAL_PUNCH_HOLE
  2026-10-26 22:05 [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Samiullah Khawaja
@ 2026-10-26 22:05 ` Samiullah Khawaja
  2026-10-26 22:05 ` [PATCH 2/2] selftests/memfd: add F_SEAL_PUNCH_HOLE tests Samiullah Khawaja
  2026-10-26 22:46 ` [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Andy Lutomirski
  2 siblings, 0 replies; 6+ messages in thread
From: Samiullah Khawaja @ 2026-10-26 22:05 UTC (permalink / raw)
  To: Andrew Morton, Hugh Dickins
  Cc: Baolin Wang, David Hildenbrand, Muchun Song, Oscar Salvador,
	Jeff Layton, Chuck Lever, Alexander Aring, Shuah Khan,
	Jason Gunthorpe, Gerd Hoffmann, Vivek Kasireddy, Jeff Xu,
	David Matlack, Pasha Tatashin, linux-mm, linux-api, linux-fsdevel,
	linux-kselftest, linux-kernel, Samiullah Khawaja

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@google.com>
---
 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


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/2] selftests/memfd: add F_SEAL_PUNCH_HOLE tests
  2026-10-26 22:05 [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Samiullah Khawaja
  2026-10-26 22:05 ` [PATCH 1/2] " Samiullah Khawaja
@ 2026-10-26 22:05 ` Samiullah Khawaja
  2026-10-26 22:46 ` [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Andy Lutomirski
  2 siblings, 0 replies; 6+ messages in thread
From: Samiullah Khawaja @ 2026-10-26 22:05 UTC (permalink / raw)
  To: Andrew Morton, Hugh Dickins
  Cc: Baolin Wang, David Hildenbrand, Muchun Song, Oscar Salvador,
	Jeff Layton, Chuck Lever, Alexander Aring, Shuah Khan,
	Jason Gunthorpe, Gerd Hoffmann, Vivek Kasireddy, Jeff Xu,
	David Matlack, Pasha Tatashin, linux-mm, linux-api, linux-fsdevel,
	linux-kselftest, linux-kernel, Samiullah Khawaja

Add tests for the new F_SEAL_PUNCH_HOLE seal. Like the rest of
memfd_test, they run on shmem memfds by default and on hugetlbfs memfds
when memfd_test is invoked with the "hugetlbfs" argument.

mfd_assert_write() checks that the file can be written, and then also
checks that a hole can be punched into it. The second check no longer
holds once F_SEAL_PUNCH_HOLE is set. Move the write checks into
__mfd_assert_write() and the punch check into mfd_assert_punch_hole(),
and have mfd_assert_write() call both, so the existing tests keep doing
exactly what they did before.

Add mfd_fail_punch_hole(), which checks that both
fallocate(FALLOC_FL_PUNCH_HOLE) and madvise(MADV_REMOVE) fail with
EPERM, and use it in two new tests. SEAL-PUNCH-HOLE sets only the new
seal and checks that punching a hole fails, while writing, shrinking and
growing the file still work. SEAL-PUNCH-HOLE-SHRINK adds F_SEAL_SHRINK
as well and checks that the file can neither have holes punched into it
nor be shrunk, while it can still grow.

Signed-off-by: Samiullah Khawaja <skhawaja@google.com>
---
 tools/testing/selftests/memfd/memfd_test.c | 111 ++++++++++++++++++++-
 1 file changed, 110 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/memfd/memfd_test.c b/tools/testing/selftests/memfd/memfd_test.c
index cdab3a837624..095c5eb255af 100644
--- a/tools/testing/selftests/memfd/memfd_test.c
+++ b/tools/testing/selftests/memfd/memfd_test.c
@@ -35,6 +35,10 @@
 
 #define F_SEAL_EXEC	0x0020
 
+#ifndef F_SEAL_PUNCH_HOLE
+#define F_SEAL_PUNCH_HOLE	0x0040
+#endif
+
 #define F_WX_SEALS (F_SEAL_SHRINK | \
 		    F_SEAL_GROW | \
 		    F_SEAL_WRITE | \
@@ -455,7 +459,7 @@ static void mfd_assert_fork_private_write(int fd)
 	munmap(p, mfd_def_size);
 }
 
-static void mfd_assert_write(int fd)
+static void __mfd_assert_write(int fd)
 {
 	ssize_t l;
 	void *p;
@@ -523,6 +527,11 @@ static void mfd_assert_write(int fd)
 
 	*(char *)p = 0;
 	munmap(p, mfd_def_size);
+}
+
+static void mfd_assert_punch_hole(int fd)
+{
+	int r;
 
 	/* verify PUNCH_HOLE works */
 	r = fallocate(fd,
@@ -535,6 +544,49 @@ static void mfd_assert_write(int fd)
 	}
 }
 
+static void mfd_fail_punch_hole(int fd)
+{
+	void *p;
+	int r;
+
+	/* verify PUNCH_HOLE fails */
+	r = fallocate(fd,
+		      FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+		      0,
+		      mfd_def_size);
+	if (r >= 0 || errno != EPERM) {
+		printf("expected EPERM on fallocate(PUNCH_HOLE), but got %d: %m\n",
+		       r);
+		abort();
+	}
+
+	/* verify MADV_REMOVE fails, it punches a hole as well */
+	p = mmap(NULL,
+		 mfd_def_size,
+		 PROT_READ | PROT_WRITE,
+		 MAP_SHARED,
+		 fd,
+		 0);
+	if (p == MAP_FAILED) {
+		printf("mmap() failed: %m\n");
+		abort();
+	}
+
+	r = madvise(p, mfd_def_size, MADV_REMOVE);
+	if (r >= 0 || errno != EPERM) {
+		printf("expected EPERM on madvise(MADV_REMOVE), but got %d: %m\n",
+		       r);
+		abort();
+	}
+	munmap(p, mfd_def_size);
+}
+
+static void mfd_assert_write(int fd)
+{
+	__mfd_assert_write(fd);
+	mfd_assert_punch_hole(fd);
+}
+
 static void mfd_fail_write(int fd)
 {
 	ssize_t l;
@@ -1117,6 +1169,61 @@ static void test_seal_resize(void)
 	close(fd);
 }
 
+/*
+ * Test SEAL_PUNCH_HOLE
+ * Test whether SEAL_PUNCH_HOLE actually prevents punching holes, but still
+ * allows writing and resizing
+ */
+static void test_seal_punch_hole(void)
+{
+	int fd;
+
+	printf("%s SEAL-PUNCH-HOLE\n", memfd_str);
+
+	fd = mfd_assert_new("kern_memfd_seal_punch_hole",
+			    mfd_def_size,
+			    MFD_CLOEXEC | MFD_ALLOW_SEALING);
+	mfd_assert_has_seals(fd, 0);
+	mfd_assert_add_seals(fd, F_SEAL_PUNCH_HOLE);
+	mfd_assert_has_seals(fd, F_SEAL_PUNCH_HOLE);
+
+	mfd_assert_read(fd);
+	__mfd_assert_write(fd);
+	mfd_fail_punch_hole(fd);
+	mfd_assert_shrink(fd);
+	mfd_assert_grow(fd);
+	mfd_assert_grow_write(fd);
+
+	close(fd);
+}
+
+/*
+ * Test SEAL_PUNCH_HOLE | SEAL_SHRINK
+ * Test whether SEAL_PUNCH_HOLE | SEAL_SHRINK prevents removing pages from
+ * the file
+ */
+static void test_seal_punch_hole_shrink(void)
+{
+	int fd;
+
+	printf("%s SEAL-PUNCH-HOLE-SHRINK\n", memfd_str);
+
+	fd = mfd_assert_new("kern_memfd_seal_punch_hole_shrink",
+			    mfd_def_size,
+			    MFD_CLOEXEC | MFD_ALLOW_SEALING);
+	mfd_assert_has_seals(fd, 0);
+	mfd_assert_add_seals(fd, F_SEAL_PUNCH_HOLE | F_SEAL_SHRINK);
+	mfd_assert_has_seals(fd, F_SEAL_PUNCH_HOLE | F_SEAL_SHRINK);
+
+	mfd_assert_read(fd);
+	__mfd_assert_write(fd);
+	mfd_fail_punch_hole(fd);
+	mfd_fail_shrink(fd);
+	mfd_assert_grow(fd);
+
+	close(fd);
+}
+
 /*
  * Test SEAL_EXEC
  * Test fd is created with exec and allow sealing.
@@ -1736,6 +1843,8 @@ int main(int argc, char **argv)
 	test_seal_shrink();
 	test_seal_grow();
 	test_seal_resize();
+	test_seal_punch_hole();
+	test_seal_punch_hole_shrink();
 
 	if (pid_ns_supported()) {
 		test_sysctl_simple();
-- 
2.56.0.307.g5fe7bd680c-goog


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE
  2026-10-26 22:05 [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Samiullah Khawaja
  2026-10-26 22:05 ` [PATCH 1/2] " Samiullah Khawaja
  2026-10-26 22:05 ` [PATCH 2/2] selftests/memfd: add F_SEAL_PUNCH_HOLE tests Samiullah Khawaja
@ 2026-10-26 22:46 ` Andy Lutomirski
  2026-10-27 22:55   ` Samiullah Khawaja
  2 siblings, 1 reply; 6+ messages in thread
From: Andy Lutomirski @ 2026-10-26 22:46 UTC (permalink / raw)
  To: Samiullah Khawaja
  Cc: Andrew Morton, Hugh Dickins, Baolin Wang, David Hildenbrand,
	Muchun Song, Oscar Salvador, Jeff Layton, Chuck Lever,
	Alexander Aring, Shuah Khan, Jason Gunthorpe, Gerd Hoffmann,
	Vivek Kasireddy, Jeff Xu, David Matlack, Pasha Tatashin, linux-mm,
	linux-api, linux-fsdevel, linux-kselftest, linux-kernel

On Mon, Oct 26, 2026 at 3:06 PM Samiullah Khawaja <skhawaja@google.com> wrote:
>
> This series introduces F_SEAL_PUNCH_HOLE, a new memfd seal that prevents
> holes from being punched into a shmem or hugetlbfs memfd while leaving
> the file fully writable.
>
> Background
> ==========
>
> Drivers such as udmabuf and iommufd (via IOMMU_IOAS_MAP_FILE) use
> memfd_pin_folios() to pin the folios backing a memfd and hand them to a
> device for DMA. Pinning keeps those folios allocated, but it does not
> keep them in the file. If userspace later calls
> fallocate(FALLOC_FL_PUNCH_HOLE) or madvise(MADV_REMOVE) on the range,
> the folios are dropped from the page cache while the device continues
> to use them. The pin holder is never notified, and from that point on
> the device and the file silently diverge.
>
> The existing seals only partially address this. F_SEAL_SHRINK prevents
> pages from being dropped by truncation, which is why udmabuf requires
> it. Hole punching, however, is only blocked by F_SEAL_WRITE and
> F_SEAL_FUTURE_WRITE, and both of those also forbid writes. That makes
> them unusable for memory that must stay writable, such as guest RAM or
> DMA buffers, and udmabuf explicitly rejects memfds that carry them.
>
> Proposal
> ========
>
> F_SEAL_PUNCH_HOLE fills this gap. Once it is set,
> fallocate(FALLOC_FL_PUNCH_HOLE) and madvise(MADV_REMOVE) fail with
> -EPERM, while reads, writes and resizing are unaffected. Combined with
> F_SEAL_SHRINK, it guarantees that userspace cannot remove pages from
> the memfd while the memfd remains writable.

How does this solve the problem?  F_SEAL is about a userspace
contract, and if there are existing users of the APIs in question, it
makes very little sense to start making those users fail because they
haven't taken the appropriate seal.

Would it make more sense for the drivers in question to set some flag
(refcount?) on the file to block hole punching?  Or perhaps to have
the hole punch code notice that it's trying to punch out a folio that
is pinned or otherwise referenced and to refuse to do it?

To some extent, the issue you're describing is maybe broader than just
the APIs you're contemplating.  If I open a file and then do something
in thread A that GUP's the file and punch a hole in the file in thread
B, I'm doing something weird, and I can imagine plenty of ways it
could go wrong.  (Especially if it's GUP for write.  I hope we already
get that case right.)

-- 
Andy Lutomirski
AMA Capital Management, LLC

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE
  2026-10-26 22:46 ` [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Andy Lutomirski
@ 2026-10-27 22:55   ` Samiullah Khawaja
  2026-10-28  3:57     ` Andy Lutomirski
  0 siblings, 1 reply; 6+ messages in thread
From: Samiullah Khawaja @ 2026-10-27 22:55 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Andrew Morton, Hugh Dickins, Baolin Wang, David Hildenbrand,
	Muchun Song, Oscar Salvador, Jeff Layton, Chuck Lever,
	Alexander Aring, Shuah Khan, Jason Gunthorpe, Gerd Hoffmann,
	Vivek Kasireddy, Jeff Xu, David Matlack, Pasha Tatashin, linux-mm,
	linux-api, linux-fsdevel, linux-kselftest, linux-kernel

On Mon, Oct 26, 2026 at 03:46:34PM -0700, Andy Lutomirski wrote:
>On Mon, Oct 26, 2026 at 3:06 PM Samiullah Khawaja <skhawaja@google.com> wrote:
>>
>> This series introduces F_SEAL_PUNCH_HOLE, a new memfd seal that prevents
>> holes from being punched into a shmem or hugetlbfs memfd while leaving
>> the file fully writable.
>>
>> Background
>> ==========
>>
>> Drivers such as udmabuf and iommufd (via IOMMU_IOAS_MAP_FILE) use
>> memfd_pin_folios() to pin the folios backing a memfd and hand them to a
>> device for DMA. Pinning keeps those folios allocated, but it does not
>> keep them in the file. If userspace later calls
>> fallocate(FALLOC_FL_PUNCH_HOLE) or madvise(MADV_REMOVE) on the range,
>> the folios are dropped from the page cache while the device continues
>> to use them. The pin holder is never notified, and from that point on
>> the device and the file silently diverge.
>>
>> The existing seals only partially address this. F_SEAL_SHRINK prevents
>> pages from being dropped by truncation, which is why udmabuf requires
>> it. Hole punching, however, is only blocked by F_SEAL_WRITE and
>> F_SEAL_FUTURE_WRITE, and both of those also forbid writes. That makes
>> them unusable for memory that must stay writable, such as guest RAM or
>> DMA buffers, and udmabuf explicitly rejects memfds that carry them.
>>
>> Proposal
>> ========
>>
>> F_SEAL_PUNCH_HOLE fills this gap. Once it is set,
>> fallocate(FALLOC_FL_PUNCH_HOLE) and madvise(MADV_REMOVE) fail with
>> -EPERM, while reads, writes and resizing are unaffected. Combined with
>> F_SEAL_SHRINK, it guarantees that userspace cannot remove pages from
>> the memfd while the memfd remains writable.
>
>How does this solve the problem?  F_SEAL is about a userspace
>contract, and if there are existing users of the APIs in question, it
>makes very little sense to start making those users fail because they
>haven't taken the appropriate seal.

It doesn't change any existing behaviour, nothing starts failing for
users that don't set the seal, and drivers wouldn't require it. It's
opt-in. Userspace that wants the guarantee seals the memfd, and a kernel
user that pins it can check for the seal and rely on the pages staying
in the file. For example, the iommufd live update path could skip
verifying the pinned pages at preserve time when the seal is present,
and do the full check otherwise.

https://lore.kernel.org/all/arVYa_90j0oN1hnu@google.com/
>
>Would it make more sense for the drivers in question to set some flag
>(refcount?) on the file to block hole punching?  Or perhaps to have
>the hole punch code notice that it's trying to punch out a folio that
>is pinned or otherwise referenced and to refuse to do it?

The pins already keep the memory alive while it's used for DMA; the
problem is only that the page silently stops being part of the file.
Refusing to punch pinned folios on every file would change behaviour for
existing users: fallocate() would start failing whenever a page is
pinned, even briefly by O_DIRECT. A flag set on the file by long-term
pinners would avoid the short-term case, but it would still make punches
start failing for existing users of those drivers, which is why I went
for an opt-in seal.

Another option, closer to what you mentioned, but opt-in, is instead of
sealing the whole file against hole punching, a seal that only protects
pinned pages. Punching, shrinking and truncating keep working on a
sealed memfd, except where they would remove a page that is currently
pinned; those would fail with -EBUSY. Files without the seal behave
exactly as today, so existing users are unaffected, and pinned memory is
guaranteed to stay part of the file.
>
>To some extent, the issue you're describing is maybe broader than just
>the APIs you're contemplating.  If I open a file and then do something
>in thread A that GUP's the file and punch a hole in the file in thread
>B, I'm doing something weird, and I can imagine plenty of ways it
>could go wrong.  (Especially if it's GUP for write.  I hope we already
>get that case right.)
>
>-- 
>Andy Lutomirski
>AMA Capital Management, LLC

Thanks,
Sami

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE
  2026-10-27 22:55   ` Samiullah Khawaja
@ 2026-10-28  3:57     ` Andy Lutomirski
  0 siblings, 0 replies; 6+ messages in thread
From: Andy Lutomirski @ 2026-10-28  3:57 UTC (permalink / raw)
  To: Samiullah Khawaja
  Cc: Andrew Morton, Hugh Dickins, Baolin Wang, David Hildenbrand,
	Muchun Song, Oscar Salvador, Jeff Layton, Chuck Lever,
	Alexander Aring, Shuah Khan, Jason Gunthorpe, Gerd Hoffmann,
	Vivek Kasireddy, Jeff Xu, David Matlack, Pasha Tatashin, linux-mm,
	linux-api, linux-fsdevel, linux-kselftest, linux-kernel

On Tue, Oct 27, 2026 at 3:55 PM Samiullah Khawaja <skhawaja@google.com> wrote:
>
> On Mon, Oct 26, 2026 at 03:46:34PM -0700, Andy Lutomirski wrote:
> >On Mon, Oct 26, 2026 at 3:06 PM Samiullah Khawaja <skhawaja@google.com> wrote:
> >>
> >> This series introduces F_SEAL_PUNCH_HOLE, a new memfd seal that prevents
> >> holes from being punched into a shmem or hugetlbfs memfd while leaving
> >> the file fully writable.
> >>
> >> Background
> >> ==========
> >>
> >> Drivers such as udmabuf and iommufd (via IOMMU_IOAS_MAP_FILE) use
> >> memfd_pin_folios() to pin the folios backing a memfd and hand them to a
> >> device for DMA. Pinning keeps those folios allocated, but it does not
> >> keep them in the file. If userspace later calls
> >> fallocate(FALLOC_FL_PUNCH_HOLE) or madvise(MADV_REMOVE) on the range,
> >> the folios are dropped from the page cache while the device continues
> >> to use them. The pin holder is never notified, and from that point on
> >> the device and the file silently diverge.
> >>
> >> The existing seals only partially address this. F_SEAL_SHRINK prevents
> >> pages from being dropped by truncation, which is why udmabuf requires
> >> it. Hole punching, however, is only blocked by F_SEAL_WRITE and
> >> F_SEAL_FUTURE_WRITE, and both of those also forbid writes. That makes
> >> them unusable for memory that must stay writable, such as guest RAM or
> >> DMA buffers, and udmabuf explicitly rejects memfds that carry them.
> >>
> >> Proposal
> >> ========
> >>
> >> F_SEAL_PUNCH_HOLE fills this gap. Once it is set,
> >> fallocate(FALLOC_FL_PUNCH_HOLE) and madvise(MADV_REMOVE) fail with
> >> -EPERM, while reads, writes and resizing are unaffected. Combined with
> >> F_SEAL_SHRINK, it guarantees that userspace cannot remove pages from
> >> the memfd while the memfd remains writable.
> >
> >How does this solve the problem?  F_SEAL is about a userspace
> >contract, and if there are existing users of the APIs in question, it
> >makes very little sense to start making those users fail because they
> >haven't taken the appropriate seal.
>
> It doesn't change any existing behaviour, nothing starts failing for
> users that don't set the seal, and drivers wouldn't require it. It's
> opt-in. Userspace that wants the guarantee seals the memfd, and a kernel
> user that pins it can check for the seal and rely on the pages staying
> in the file. For example, the iommufd live update path could skip
> verifying the pinned pages at preserve time when the seal is present,
> and do the full check otherwise.

Can you clarify the API you're proposing and the issue?  It seems to
me like the user is expected to preserve HWPT state by creating a
memfd, then using some new API to tell the driver to "persist" its
state into the memfd (possibly by binding its existing state as folios
into the memfd)?  And userspace is supposed to seal the memfd against
growing and shrinking but not against writes?

What if the user writes to the file?  What if the user populates
pages/folios into the file before using it to preserve HWPT?  What if
the user mmaps the file?

Have I understood this correctly at all?  Why does memfd make sense?
If the goal is to attach a bunch of pre-existing pages to a file so
that the page preservation machinery can preserve them, wouldn't it
make more sense to have a handoverfd mechanism where the kernel can
make a handoverfd that stores its purpose and logically represents
some pages but does *not* allow read or write or fallocate or any
other user operation that actually acts on the data or its memory
layout?

>
> https://lore.kernel.org/all/arVYa_90j0oN1hnu@google.com/
> >
> >Would it make more sense for the drivers in question to set some flag
> >(refcount?) on the file to block hole punching?  Or perhaps to have
> >the hole punch code notice that it's trying to punch out a folio that
> >is pinned or otherwise referenced and to refuse to do it?
>
> The pins already keep the memory alive while it's used for DMA; the
> problem is only that the page silently stops being part of the file.
> Refusing to punch pinned folios on every file would change behaviour for
> existing users: fallocate() would start failing whenever a page is
> pinned, even briefly by O_DIRECT. A flag set on the file by long-term
> pinners would avoid the short-term case, but it would still make punches
> start failing for existing users of those drivers, which is why I went
> for an opt-in seal.

Wait a moment here.  Given my comments above, I'm not sure this is
relevant to this patchset, but...

As far as I know, right now, a program can open a file, register some
of its pages with io_uring or RDMA, and then use io_uring or RDMA to
write to those pages later.

And another program can open that same file and use fallocate to punch a hole.

And then the first program thinks it's reading into the file, and it
ends up not reading into the file.  Which makes the hole punch
operation rather different from the write-zeros operation that it's
supposed to be a fancy version of.

Now this doesn't sound like a security problem per se, since the
offending hole-puncher needed write access (admittedly that write
access is only needed at hole punch time, not in the indefinite future
when weird behavior happens).  But this whole sequence seems highly
suspect to me.

Are you *sure* it wouldn't make sense for a GUP'd or similar reference
(whether pinned or not) to *actually preserve that folio's status as
part of the file*?  Or maybe for there to be an extra pin bit to do
this?  Because the current behavior is pretty strange.  And I'm not at
all convinced that anyone would really object if the kernel prevents
hole-punching a range with an active GUP, especially if this behavior
is restricted to writable GUPs.

I admit that truncate seems slightly different in the sense that it
seems more likely that someone would be annoyed by an inability to
truncate a file.  But still, truncate does have the same issue.  Maybe
extending a file could notice that there is still a folio covering the
newly extended range and stick that back into the file?  Seems messy.


-- 
Andy Lutomirski
AMA Capital Management, LLC

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-28  3:57 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26 22:05 [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Samiullah Khawaja
2026-10-26 22:05 ` [PATCH 1/2] " Samiullah Khawaja
2026-10-26 22:05 ` [PATCH 2/2] selftests/memfd: add F_SEAL_PUNCH_HOLE tests Samiullah Khawaja
2026-10-26 22:46 ` [PATCH 0/2] mm/memfd: add F_SEAL_PUNCH_HOLE Andy Lutomirski
2026-10-27 22:55   ` Samiullah Khawaja
2026-10-28  3:57     ` Andy Lutomirski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox