diff --git a/Documentation/filesystems/fsverity.rst b/Documentation/filesystems/fsverity.rst
index 1d831e3cbcb3..71186cebf15d 100644
--- a/Documentation/filesystems/fsverity.rst
+++ b/Documentation/filesystems/fsverity.rst
@@ -533,6 +533,24 @@ Currently, f2fs verity only supports a Merkle tree block size of 4096.
 Also, f2fs doesn't support enabling verity on files that currently
 have atomic or volatile writes pending.
 
+tmpfs
+-----
+
+tmpfs supports fsverity since Linux v5.17.
+
+Fsverity support for tmpfs can be enabled at build time through the kernel
+configuration option ``CONFIG_TMPFS_VERITY``. If enabled, it is also
+automatically enabled at mount time for every tmpfs filesystem
+instantiated.
+
+Like f2fs, tmpfs stores the verity metadata (Merkle tree and
+fsverity_descriptor) past the end of the file, starting at the first
+64K boundary beyond i_size. Also, like f2fs, it stores the fsverity
+descriptor location in an xattr.
+
+Currently, tmpfs verity only supports the case where the Merkle tree
+block size and page size are the same.
+
 Implementation details
 ======================
 
diff --git a/MAINTAINERS b/MAINTAINERS
index 9096c64d8d09..118cf9d58601 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19137,6 +19137,7 @@ L:	linux-mm@kvack.org
 S:	Maintained
 F:	include/linux/shmem_fs.h
 F:	mm/shmem.c
+F:	mm/shmem_verity.c
 
 TOMOYO SECURITY MODULE
 M:	Kentaro Takeda <takedakn@nttdata.co.jp>
diff --git a/fs/Kconfig b/fs/Kconfig
index a6313a969bc5..d67f7a1cdcb6 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -229,6 +229,13 @@ config TMPFS_INODE64
 
 	  If unsure, say N.
 
+config TMPFS_VERITY
+	bool "fsverity support for tmpfs (EXPERIMENTAL)"
+	depends on FS_VERITY && TMPFS && TMPFS_XATTR
+
+	help
+	  Enable fsverity protection for files in the tmpfs filesystem.
+
 config ARCH_SUPPORTS_HUGETLBFS
 	def_bool n
 
diff --git a/fs/verity/enable.c b/fs/verity/enable.c
index 60a4372aa4d7..9cd64cbe3579 100644
--- a/fs/verity/enable.c
+++ b/fs/verity/enable.c
@@ -13,6 +13,7 @@
 #include <linux/pagemap.h>
 #include <linux/sched/signal.h>
 #include <linux/uaccess.h>
+#include <linux/shmem_fs.h>
 
 /*
  * Read a file data page for Merkle tree construction.  Do aggressive readahead,
@@ -31,7 +32,10 @@ static struct page *read_file_data_page(struct file *filp, pgoff_t index,
 		else
 			page_cache_sync_readahead(filp->f_mapping, ra, filp,
 						  index, remaining_pages);
-		page = read_mapping_page(filp->f_mapping, index, NULL);
+		if (shmem_file(filp))
+			page = shmem_read_mapping_page(filp->f_mapping, index);
+		else
+			page = read_mapping_page(filp->f_mapping, index, NULL);
 		if (IS_ERR(page))
 			return page;
 	}
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index 166158b6e917..07b9a142c7d3 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -10,6 +10,9 @@
 #include <linux/xattr.h>
 #include <linux/fs_parser.h>
 
+#define SHMEM_VERITY_IN_PROGRESS 0x00000001
+#define SHMEM_XATTR_NAME_VERITY "v"
+
 /* inode in-kernel data */
 
 struct shmem_inode_info {
@@ -44,6 +47,7 @@ struct shmem_sb_info {
 	spinlock_t shrinklist_lock;   /* Protects shrinklist */
 	struct list_head shrinklist;  /* List of shinkable inodes */
 	unsigned long shrinklist_len; /* Length of shrinklist */
+	bool verity;		      /* Fsverity enabled or not */
 };
 
 static inline struct shmem_inode_info *SHMEM_I(struct inode *inode)
@@ -51,10 +55,33 @@ static inline struct shmem_inode_info *SHMEM_I(struct inode *inode)
 	return container_of(inode, struct shmem_inode_info, vfs_inode);
 }
 
+static inline bool shmem_verity_in_progress(struct inode *inode)
+{
+	struct shmem_inode_info *info = SHMEM_I(inode);
+
+	return IS_ENABLED(CONFIG_FS_VERITY) &&
+	       (info->flags & SHMEM_VERITY_IN_PROGRESS);
+}
+
+static inline void shmem_verity_set_in_progress(struct inode *inode)
+{
+	struct shmem_inode_info *info = SHMEM_I(inode);
+
+	info->flags |= SHMEM_VERITY_IN_PROGRESS;
+}
+
+static inline void shmem_verity_clear_in_progress(struct inode *inode)
+{
+	struct shmem_inode_info *info = SHMEM_I(inode);
+
+	info->flags &= ~SHMEM_VERITY_IN_PROGRESS;
+}
+
 /*
  * Functions in mm/shmem.c called directly from elsewhere:
  */
 extern const struct fs_parameter_spec shmem_fs_parameters[];
+extern const struct fsverity_operations shmem_verityops;
 extern int shmem_init(void);
 extern int shmem_init_fs_context(struct fs_context *fc);
 extern struct file *shmem_file_setup(const char *name,
diff --git a/mm/Makefile b/mm/Makefile
index d6c0042e3aa0..f15b48dbd235 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -54,6 +54,8 @@ obj-y			:= filemap.o mempool.o oom_kill.o fadvise.o \
 			   interval_tree.o list_lru.o workingset.o \
 			   debug.o gup.o mmap_lock.o $(mmu-y)
 
+obj-$(CONFIG_TMPFS_VERITY)	+= shmem_verity.o
+
 # Give 'page_alloc' its own module-parameter namespace
 page-alloc-y := page_alloc.o
 page-alloc-$(CONFIG_SHUFFLE_PAGE_ALLOCATOR) += shuffle.o
diff --git a/mm/shmem.c b/mm/shmem.c
index 427863cbf0dc..f36e1a493610 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -78,6 +78,7 @@ static struct vfsmount *shm_mnt;
 #include <linux/userfaultfd_k.h>
 #include <linux/rmap.h>
 #include <linux/uuid.h>
+#include <linux/fsverity.h>
 
 #include <linux/uaccess.h>
 
@@ -1089,6 +1090,10 @@ static int shmem_setattr(struct user_namespace *mnt_userns,
 	if (error)
 		return error;
 
+	error = fsverity_prepare_setattr(dentry, attr);
+	if (error)
+		return error;
+
 	if (S_ISREG(inode->i_mode) && (attr->ia_valid & ATTR_SIZE)) {
 		loff_t oldsize = inode->i_size;
 		loff_t newsize = attr->ia_size;
@@ -1156,6 +1161,7 @@ static void shmem_evict_inode(struct inode *inode)
 		}
 	}
 
+	fsverity_cleanup_inode(inode);
 	simple_xattrs_free(&info->xattrs);
 	WARN_ON(inode->i_blocks);
 	shmem_free_inode(inode->i_sb);
@@ -1827,7 +1833,8 @@ static int shmem_getpage_gfp(struct inode *inode, pgoff_t index,
 		return -EFBIG;
 repeat:
 	if (sgp <= SGP_CACHE &&
-	    ((loff_t)index << PAGE_SHIFT) >= i_size_read(inode)) {
+	    ((loff_t)index << PAGE_SHIFT) >= i_size_read(inode) &&
+	    !fsverity_active(inode) && !shmem_verity_in_progress(inode)) {
 		return -EINVAL;
 	}
 
@@ -2485,7 +2492,7 @@ shmem_write_end(struct file *file, struct address_space *mapping,
 {
 	struct inode *inode = mapping->host;
 
-	if (pos + copied > inode->i_size)
+	if (pos + copied > inode->i_size && !shmem_verity_in_progress(inode))
 		i_size_write(inode, pos + copied);
 
 	if (!PageUptodate(page)) {
@@ -2805,6 +2812,56 @@ static long shmem_fallocate(struct file *file, int mode, loff_t offset,
 	return error;
 }
 
+static bool shmem_sb_has_verity(struct inode *inode)
+{
+	return SHMEM_SB(inode->i_sb)->verity;
+}
+
+static int shmem_ioc_enable_verity(struct file *filp, unsigned long arg)
+{
+	if (!shmem_sb_has_verity(file_inode(filp)))
+		return -EOPNOTSUPP;
+
+	return fsverity_ioctl_enable(filp, (const void __user *)arg);
+}
+
+static int shmem_ioc_measure_verity(struct file *filp, unsigned long arg)
+{
+	if (!shmem_sb_has_verity(file_inode(filp)))
+		return -EOPNOTSUPP;
+
+	return fsverity_ioctl_measure(filp, (void __user *)arg);
+}
+
+static int shmem_ioc_read_verity_metadata(struct file *filp, unsigned long arg)
+{
+	if (!shmem_sb_has_verity(file_inode(filp)))
+		return -EOPNOTSUPP;
+
+	return fsverity_ioctl_read_metadata(filp, (const void __user *)arg);
+}
+
+static long shmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case FS_IOC_ENABLE_VERITY:
+		return shmem_ioc_enable_verity(filp, arg);
+	case FS_IOC_MEASURE_VERITY:
+		return shmem_ioc_measure_verity(filp, arg);
+	case FS_IOC_READ_VERITY_METADATA:
+		return shmem_ioc_read_verity_metadata(filp, arg);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+#ifdef CONFIG_TMPFS_VERITY
+static int shmem_file_open(struct inode *inode, struct file *filp)
+{
+	return fsverity_file_open(inode, filp);
+}
+#endif
+
 static int shmem_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
 	struct shmem_sb_info *sbinfo = SHMEM_SB(dentry->d_sb);
@@ -3673,6 +3730,10 @@ static int shmem_fill_super(struct super_block *sb, struct fs_context *fc)
 	}
 	sb->s_export_op = &shmem_export_ops;
 	sb->s_flags |= SB_NOSEC;
+#ifdef CONFIG_TMPFS_VERITY
+	sb->s_vop = &shmem_verityops;
+	sbinfo->verity = true;
+#endif
 #else
 	sb->s_flags |= SB_NOUSER;
 #endif
@@ -3825,6 +3886,10 @@ static const struct file_operations shmem_file_operations = {
 	.splice_read	= generic_file_splice_read,
 	.splice_write	= iter_file_splice_write,
 	.fallocate	= shmem_fallocate,
+	.unlocked_ioctl	= shmem_ioctl,
+#ifdef CONFIG_TMPFS_VERITY
+	.open		= shmem_file_open,
+#endif
 #endif
 };
 
diff --git a/mm/shmem_verity.c b/mm/shmem_verity.c
new file mode 100644
index 000000000000..f5f5c7394dda
--- /dev/null
+++ b/mm/shmem_verity.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 Google LLC
+ * Copyright 2021 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ */
+
+/*
+ * Implementation of fsverity_operations for tmpfs.
+ *
+ * Like ext4, tmpfs stores the verity metadata (Merkle tree and
+ * fsverity_descriptor) past the end of the file, starting at the first 64K
+ * boundary beyond i_size.
+ *
+ * Using a 64K boundary rather than a 4K one keeps things ready for
+ * architectures with 64K pages, and it doesn't necessarily waste space on-disk
+ * since there can be a hole between i_size and the start of the Merkle tree.
+ */
+
+#include <linux/xattr.h>
+#include <linux/fsverity.h>
+#include <linux/shmem_fs.h>
+#include <linux/quotaops.h>
+
+#define SHMEM_VERIFY_VER	(1)
+
+static inline loff_t shmem_verity_metadata_pos(const struct inode *inode)
+{
+	return round_up(inode->i_size, 65536);
+}
+
+/*
+ * Read some verity metadata from the inode.  __vfs_read() can't be used because
+ * we need to read beyond i_size.
+ */
+static int pagecache_read(struct inode *inode, void *buf, size_t count,
+			  loff_t pos)
+{
+	while (count) {
+		size_t n = min_t(size_t, count,
+				 PAGE_SIZE - offset_in_page(pos));
+		struct page *page;
+		void *addr;
+
+		page = shmem_read_mapping_page(inode->i_mapping,
+					       pos >> PAGE_SHIFT);
+		if (IS_ERR(page))
+			return PTR_ERR(page);
+
+		addr = kmap_atomic(page);
+		memcpy(buf, addr + offset_in_page(pos), n);
+		kunmap_atomic(addr);
+
+		put_page(page);
+
+		buf += n;
+		pos += n;
+		count -= n;
+	}
+	return 0;
+}
+
+/*
+ * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY.
+ * kernel_write() can't be used because the file descriptor is readonly.
+ */
+static int pagecache_write(struct inode *inode, const void *buf, size_t count,
+			   loff_t pos)
+{
+	if (pos + count > inode->i_sb->s_maxbytes)
+		return -EFBIG;
+
+	while (count) {
+		size_t n = min_t(size_t, count,
+				 PAGE_SIZE - offset_in_page(pos));
+		struct page *page;
+		void *fsdata;
+		void *addr;
+		int res;
+
+		res = pagecache_write_begin(NULL, inode->i_mapping, pos, n, 0,
+					    &page, &fsdata);
+		if (res)
+			return res;
+
+		addr = kmap_atomic(page);
+		memcpy(addr + offset_in_page(pos), buf, n);
+		kunmap_atomic(addr);
+
+		res = pagecache_write_end(NULL, inode->i_mapping, pos, n, n,
+					  page, fsdata);
+		if (res < 0)
+			return res;
+		if (res != n)
+			return -EIO;
+
+		buf += n;
+		pos += n;
+		count -= n;
+	}
+	return 0;
+}
+
+/*
+ * Format of tmpfs verity xattr.  This points to the location of the verity
+ * descriptor within the file data rather than containing it (the code was taken
+ * from fs/f2fs/verity.c).
+ */
+struct fsverity_descriptor_location {
+	__le32 version;
+	__le32 size;
+	__le64 pos;
+};
+
+static int shmem_begin_enable_verity(struct file *filp)
+{
+	struct inode *inode = file_inode(filp);
+	int err;
+
+	if (shmem_verity_in_progress(inode))
+		return -EBUSY;
+
+	/*
+	 * Since the file was opened readonly, we have to initialize the quotas
+	 * here and not rely on ->open() doing it.
+	 */
+	err = dquot_initialize(inode);
+	if (err)
+		return err;
+
+	shmem_verity_set_in_progress(inode);
+	return 0;
+}
+
+static int shmem_end_enable_verity(struct file *filp, const void *desc,
+				   size_t desc_size, u64 merkle_tree_size)
+{
+	struct inode *inode = file_inode(filp);
+	struct shmem_inode_info *info = SHMEM_I(inode);
+	u64 desc_pos = shmem_verity_metadata_pos(inode) + merkle_tree_size;
+	struct fsverity_descriptor_location dloc = {
+		.version = cpu_to_le32(SHMEM_VERIFY_VER),
+		.size = cpu_to_le32(desc_size),
+		.pos = cpu_to_le64(desc_pos),
+	};
+	int err = 0;
+
+	/*
+	 * If an error already occurred (which fs/verity/ signals by passing
+	 * desc == NULL), then only clean-up is needed.
+	 */
+	if (desc == NULL)
+		goto cleanup;
+
+	/* Append the verity descriptor. */
+	err = pagecache_write(inode, desc, desc_size, desc_pos);
+	if (err)
+		goto cleanup;
+
+	/*
+	 * Write all pages (both data and verity metadata).  Note that this must
+	 * happen before clearing SHMEM_VERITY_IN_PROGRESS; otherwise pages
+	 * beyond i_size won't be written properly.
+	 */
+	err = filemap_write_and_wait(inode->i_mapping);
+	if (err)
+		goto cleanup;
+
+	/* Set the verity xattr. */
+	err = simple_xattr_set(&info->xattrs, SHMEM_XATTR_NAME_VERITY, &dloc,
+			       sizeof(dloc), XATTR_CREATE, NULL);
+	if (err)
+		goto cleanup;
+
+	/* Finally, set the verity inode flag. */
+	inode_set_flags(inode, S_VERITY, inode->i_flags | S_VERITY);
+	mark_inode_dirty_sync(inode);
+
+	shmem_verity_clear_in_progress(inode);
+	return 0;
+
+cleanup:
+	/*
+	 * Verity failed to be enabled, so clean up by truncating any verity
+	 * metadata that was written beyond i_size (both from cache and from
+	 * disk) and clearing FI_VERITY_IN_PROGRESS.
+	 */
+	shmem_truncate_range(inode, 0, inode->i_size);
+	shmem_verity_clear_in_progress(inode);
+	return err;
+}
+
+static int shmem_get_verity_descriptor(struct inode *inode, void *buf,
+				       size_t buf_size)
+{
+	struct fsverity_descriptor_location dloc;
+	struct shmem_inode_info *info = SHMEM_I(inode);
+	int res;
+	u32 size;
+	u64 pos;
+
+	/* Get the descriptor location */
+	res = simple_xattr_get(&info->xattrs, SHMEM_XATTR_NAME_VERITY, &dloc,
+			       sizeof(dloc));
+	if (res < 0 && res != -ERANGE)
+		return res;
+	if (res != sizeof(dloc) ||
+	    dloc.version != cpu_to_le32(SHMEM_VERIFY_VER)) {
+		pr_err("Unknown verity xattr format inode %lu\n", inode->i_ino);
+		return -EINVAL;
+	}
+	size = le32_to_cpu(dloc.size);
+	pos = le64_to_cpu(dloc.pos);
+
+	/* Get the descriptor */
+	if (pos + size < pos || pos + size > inode->i_sb->s_maxbytes ||
+	    pos < shmem_verity_metadata_pos(inode) || size > INT_MAX) {
+		pr_err("Invalid verity xattr for inode %lu\n", inode->i_ino);
+		return -EINVAL;
+	}
+	if (buf_size) {
+		if (size > buf_size)
+			return -ERANGE;
+		res = pagecache_read(inode, buf, size, pos);
+		if (res)
+			return res;
+	}
+	return size;
+}
+
+static struct page *shmem_read_merkle_tree_page(struct inode *inode,
+						pgoff_t index,
+						unsigned long num_ra_pages)
+{
+	DEFINE_READAHEAD(ractl, NULL, NULL, inode->i_mapping, index);
+	struct page *page;
+
+	index += shmem_verity_metadata_pos(inode) >> PAGE_SHIFT;
+
+	page = find_get_page_flags(inode->i_mapping, index, FGP_ACCESSED);
+	if (!page || !PageUptodate(page)) {
+		if (page)
+			put_page(page);
+		else if (num_ra_pages > 1)
+			page_cache_ra_unbounded(&ractl, num_ra_pages, 0);
+		page = shmem_read_mapping_page(inode->i_mapping, index);
+	}
+	return page;
+}
+
+static int shmem_write_merkle_tree_block(struct inode *inode, const void *buf,
+					 u64 index, int log_blocksize)
+{
+	loff_t pos = shmem_verity_metadata_pos(inode) +
+		     (index << log_blocksize);
+
+	return pagecache_write(inode, buf, 1 << log_blocksize, pos);
+}
+
+const struct fsverity_operations shmem_verityops = {
+	.begin_enable_verity	= shmem_begin_enable_verity,
+	.end_enable_verity	= shmem_end_enable_verity,
+	.get_verity_descriptor	= shmem_get_verity_descriptor,
+	.read_merkle_tree_page	= shmem_read_merkle_tree_page,
+	.write_merkle_tree_block = shmem_write_merkle_tree_block,
+};
