diff --git a/fs/ioctl.c b/fs/ioctl.c
index 1d5abfdf0f22..3e3aacb6ea6e 100644
--- a/fs/ioctl.c
+++ b/fs/ioctl.c
@@ -22,6 +22,7 @@
 #include <linux/mount.h>
 #include <linux/fscrypt.h>
 #include <linux/fileattr.h>
+#include <linux/namei.h>
 
 #include "internal.h"
 
@@ -647,6 +648,19 @@ static int fileattr_set_prepare(struct inode *inode,
 	if (fa->fsx_cowextsize == 0)
 		fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE;
 
+	/*
+	 * The only use case for special files is to set project ID, forbid any
+	 * other attributes
+	 */
+	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) {
+		if (fa->fsx_xflags & ~FS_XFLAG_PROJINHERIT)
+			return -EINVAL;
+		if (!S_ISLNK(inode->i_mode) && fa->fsx_nextents)
+			return -EINVAL;
+		if (fa->fsx_extsize || fa->fsx_cowextsize)
+			return -EINVAL;
+	}
+
 	return 0;
 }
 
@@ -763,6 +777,79 @@ static int ioctl_fssetxattr(struct file *file, void __user *argp)
 	return err;
 }
 
+static int ioctl_fsgetxattrat(struct file *file, void __user *argp)
+{
+	struct path filepath;
+	struct fsxattrat fsxat;
+	struct fileattr fa;
+	int error;
+
+	if (!S_ISDIR(file_inode(file)->i_mode))
+		return -EBADF;
+
+	if (copy_from_user(&fsxat, argp, sizeof(struct fsxattrat)))
+		return -EFAULT;
+
+	error = user_path_at(fsxat.dfd, fsxat.path, 0, &filepath);
+	if (error)
+		return error;
+
+	error = vfs_fileattr_get(filepath.dentry, &fa);
+	if (error) {
+		path_put(&filepath);
+		return error;
+	}
+
+	fsxat.fsx.fsx_xflags = fa.fsx_xflags;
+	fsxat.fsx.fsx_extsize = fa.fsx_extsize;
+	fsxat.fsx.fsx_nextents = fa.fsx_nextents;
+	fsxat.fsx.fsx_projid = fa.fsx_projid;
+	fsxat.fsx.fsx_cowextsize = fa.fsx_cowextsize;
+
+	if (copy_to_user(argp, &fsxat, sizeof(struct fsxattrat)))
+		error = -EFAULT;
+
+	path_put(&filepath);
+	return error;
+}
+
+static int ioctl_fssetxattrat(struct file *file, void __user *argp)
+{
+	struct mnt_idmap *idmap = file_mnt_idmap(file);
+	struct fsxattrat fsxat;
+	struct path filepath;
+	struct fileattr fa;
+	int error;
+
+	if (!S_ISDIR(file_inode(file)->i_mode))
+		return -EBADF;
+
+	if (copy_from_user(&fsxat, argp, sizeof(struct fsxattrat)))
+		return -EFAULT;
+
+	error = user_path_at(fsxat.dfd, fsxat.path, 0, &filepath);
+	if (error)
+		return error;
+
+	error = mnt_want_write(filepath.mnt);
+	if (error) {
+		path_put(&filepath);
+		return error;
+	}
+
+	fileattr_fill_xflags(&fa, fsxat.fsx.fsx_xflags);
+	fa.fsx_extsize = fsxat.fsx.fsx_extsize;
+	fa.fsx_nextents = fsxat.fsx.fsx_nextents;
+	fa.fsx_projid = fsxat.fsx.fsx_projid;
+	fa.fsx_cowextsize = fsxat.fsx.fsx_cowextsize;
+	fa.fsx_valid = true;
+
+	error = vfs_fileattr_set(idmap, filepath.dentry, &fa);
+	mnt_drop_write(filepath.mnt);
+	path_put(&filepath);
+	return error;
+}
+
 static int ioctl_getfsuuid(struct file *file, void __user *argp)
 {
 	struct super_block *sb = file_inode(file)->i_sb;
@@ -872,6 +959,12 @@ static int do_vfs_ioctl(struct file *filp, unsigned int fd,
 	case FS_IOC_FSSETXATTR:
 		return ioctl_fssetxattr(filp, argp);
 
+	case FS_IOC_FSGETXATTRAT:
+		return ioctl_fsgetxattrat(filp, argp);
+
+	case FS_IOC_FSSETXATTRAT:
+		return ioctl_fssetxattrat(filp, argp);
+
 	case FS_IOC_GETFSUUID:
 		return ioctl_getfsuuid(filp, argp);
 
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 45e4e64fd664..f8cd8d7bf35d 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -139,6 +139,15 @@ struct fsxattr {
 	unsigned char	fsx_pad[8];
 };
 
+/*
+ * Structure passed to FS_IOC_FSGETXATTRAT/FS_IOC_FSSETXATTRAT
+ */
+struct fsxattrat {
+	struct fsxattr	fsx;		/* XATTR to get/set */
+	__u32		dfd;		/* parent dir */
+	const char	__user *path;
+};
+
 /*
  * Flags for the fsx_xflags field
  */
@@ -231,6 +240,8 @@ struct fsxattr {
 #define FS_IOC32_SETVERSION		_IOW('v', 2, int)
 #define FS_IOC_FSGETXATTR		_IOR('X', 31, struct fsxattr)
 #define FS_IOC_FSSETXATTR		_IOW('X', 32, struct fsxattr)
+#define FS_IOC_FSGETXATTRAT		_IOR('X', 33, struct fsxattrat)
+#define FS_IOC_FSSETXATTRAT		_IOW('X', 34, struct fsxattrat)
 #define FS_IOC_GETFSLABEL		_IOR(0x94, 49, char[FSLABEL_MAX])
 #define FS_IOC_SETFSLABEL		_IOW(0x94, 50, char[FSLABEL_MAX])
 /* Returns the external filesystem UUID, the same one blkid returns */
