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 --- 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