diff --git a/fs/attr.c b/fs/attr.c
index d22e8187477f..c6b1c1132c8f 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -85,6 +85,28 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr)
 	if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid))
 		return -EPERM;
 
+	/*
+	 * Project Quota ID state is only allowed to change from within the init
+	 * namespace. Enforce that restriction only if we are trying to change
+	 * the quota ID state. Everything else is allowed in user namespaces.
+	 */
+	if ((ia_valid & ATTR_PROJID) && current_user_ns() != &init_user_ns) {
+		kprojid_t projid;
+		int rc;
+
+		/*
+		 * Filesystem like xfs does't have ->get_projid hook
+		 * should check permission by themselves.
+		 */
+		if (inode->i_sb->dq_op->get_projid) {
+			rc = inode->i_sb->dq_op->get_projid(inode, &projid);
+			if (rc)
+				return rc;
+			if (!projid_eq(projid, attr->ia_projid))
+				return -EPERM;
+		}
+	}
+
 	/* Make sure a caller can chmod. */
 	if (ia_valid & ATTR_MODE) {
 		if (!inode_owner_or_capable(inode))
@@ -232,8 +254,8 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
 	unsigned int ia_valid = attr->ia_valid;
 
 	WARN_ON_ONCE(!inode_is_locked(inode));
-
-	if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_TIMES_SET)) {
+	if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_PROJID |
+			ATTR_TIMES_SET)) {
 		if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 			return -EPERM;
 	}
diff --git a/fs/open.c b/fs/open.c
index 0285ce7dbd51..4e58c6ee23b3 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -597,7 +597,8 @@ SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode)
 	return do_fchmodat(AT_FDCWD, filename, mode);
 }
 
-static int chown_common(const struct path *path, uid_t user, gid_t group)
+static int chown_common(const struct path *path, uid_t user, gid_t group,
+			bool set_project)
 {
 	struct inode *inode = path->dentry->d_inode;
 	struct inode *delegated_inode = NULL;
@@ -605,9 +606,11 @@ static int chown_common(const struct path *path, uid_t user, gid_t group)
 	struct iattr newattrs;
 	kuid_t uid;
 	kgid_t gid;
+	kprojid_t projid;
 
 	uid = make_kuid(current_user_ns(), user);
 	gid = make_kgid(current_user_ns(), group);
+	projid = make_kprojid(current_user_ns(), (projid_t)group);
 
 retry_deleg:
 	newattrs.ia_valid =  ATTR_CTIME;
@@ -620,13 +623,22 @@ static int chown_common(const struct path *path, uid_t user, gid_t group)
 	if (group != (gid_t) -1) {
 		if (!gid_valid(gid))
 			return -EINVAL;
-		newattrs.ia_valid |= ATTR_GID;
-		newattrs.ia_gid = gid;
+		if (!set_project) {
+			newattrs.ia_valid |= ATTR_GID;
+			newattrs.ia_gid = gid;
+		} else {
+			newattrs.ia_valid |= ATTR_PROJID;
+			newattrs.ia_projid = projid;
+		}
+	} else if (set_project) {
+		return -EINVAL;
 	}
 	if (!S_ISDIR(inode->i_mode))
 		newattrs.ia_valid |=
 			ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;
 	inode_lock(inode);
+	if (set_project)
+		gid = make_kgid(current_user_ns(), (gid_t) -1);
 	error = security_path_chown(path, uid, gid);
 	if (!error)
 		error = notify_change(path->dentry, &newattrs, &delegated_inode);
@@ -645,10 +657,15 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
 	struct path path;
 	int error = -EINVAL;
 	int lookup_flags;
+	bool set_project = false;
 
-	if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
+	if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH |
+		      AT_FCHOWN_PROJID)) != 0)
 		goto out;
 
+	if (flag & AT_FCHOWN_PROJID)
+		set_project = true;
+
 	lookup_flags = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
 	if (flag & AT_EMPTY_PATH)
 		lookup_flags |= LOOKUP_EMPTY;
@@ -659,7 +676,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
 	error = mnt_want_write(path.mnt);
 	if (error)
 		goto out_release;
-	error = chown_common(&path, user, group);
+	error = chown_common(&path, user, group, set_project);
 	mnt_drop_write(path.mnt);
 out_release:
 	path_put(&path);
@@ -700,7 +717,7 @@ int ksys_fchown(unsigned int fd, uid_t user, gid_t group)
 	if (error)
 		goto out_fput;
 	audit_file(f.file);
-	error = chown_common(&f.file->f_path, user, group);
+	error = chown_common(&f.file->f_path, user, group, false);
 	mnt_drop_write_file(f.file);
 out_fput:
 	fdput(f);
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index fc20e06c56ba..46f39ee87312 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -2095,6 +2095,29 @@ int dquot_transfer(struct inode *inode, struct iattr *iattr)
 		}
 		transfer_to[GRPQUOTA] = dquot;
 	}
+
+	if (iattr->ia_valid & ATTR_PROJID) {
+		kprojid_t projid;
+
+		if (!inode->i_sb->dq_op->get_projid)
+			return -ENOTSUPP;
+
+		ret = inode->i_sb->dq_op->get_projid(inode, &projid);
+		if (ret)
+			return ret;
+		if (!projid_eq(iattr->ia_projid, projid)) {
+			dquot = dqget(sb, make_kqid_projid(iattr->ia_projid));
+			if (IS_ERR(dquot)) {
+				if (PTR_ERR(dquot) != -ESRCH) {
+					ret = PTR_ERR(dquot);
+					goto out_put;
+				}
+				dquot = NULL;
+			}
+			transfer_to[PRJQUOTA] = dquot;
+		}
+	}
+
 	ret = __dquot_transfer(inode, transfer_to);
 out_put:
 	dqput_all(transfer_to);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 29d8e2cfed0e..2a878a2b90e3 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -37,6 +37,7 @@
 #include <linux/uuid.h>
 #include <linux/errseq.h>
 #include <linux/ioprio.h>
+#include <linux/projid.h>
 
 #include <asm/byteorder.h>
 #include <uapi/linux/fs.h>
@@ -191,6 +192,7 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
 #define ATTR_OPEN	(1 << 15) /* Truncating from open(O_TRUNC) */
 #define ATTR_TIMES_SET	(1 << 16)
 #define ATTR_TOUCH	(1 << 17)
+#define ATTR_PROJID	(1 << 18)
 
 /*
  * Whiteout is represented by a char device.  The following constants define the
@@ -213,6 +215,7 @@ struct iattr {
 	umode_t		ia_mode;
 	kuid_t		ia_uid;
 	kgid_t		ia_gid;
+	kprojid_t	ia_projid;
 	loff_t		ia_size;
 	struct timespec64 ia_atime;
 	struct timespec64 ia_mtime;
diff --git a/include/linux/quotaops.h b/include/linux/quotaops.h
index dc905a4ff8d7..84d3aeb43e2c 100644
--- a/include/linux/quotaops.h
+++ b/include/linux/quotaops.h
@@ -22,6 +22,15 @@ static inline struct quota_info *sb_dqopt(struct super_block *sb)
 /* i_mutex must being held */
 static inline bool is_quota_modification(struct inode *inode, struct iattr *ia)
 {
+	if (ia->ia_valid & ATTR_PROJID && inode->i_sb->dq_op->get_projid) {
+		kprojid_t projid;
+		int rc;
+
+		rc = inode->i_sb->dq_op->get_projid(inode, &projid);
+		if (!rc && !projid_eq(projid, ia->ia_projid))
+			return true;
+	}
+
 	return (ia->ia_valid & ATTR_SIZE && ia->ia_size != inode->i_size) ||
 		(ia->ia_valid & ATTR_UID && !uid_eq(ia->ia_uid, inode->i_uid)) ||
 		(ia->ia_valid & ATTR_GID && !gid_eq(ia->ia_gid, inode->i_gid));
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 6448cdd9a350..712c60d7f727 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -90,5 +90,6 @@
 #define AT_STATX_FORCE_SYNC	0x2000	/* - Force the attributes to be sync'd with the server */
 #define AT_STATX_DONT_SYNC	0x4000	/* - Don't sync attributes with the server */
 
+#define AT_FCHOWN_PROJID	0x40000000 /* Change project ID instead of group id */
 
 #endif /* _UAPI_LINUX_FCNTL_H */
diff --git a/tools/include/uapi/linux/fcntl.h b/tools/include/uapi/linux/fcntl.h
index 6448cdd9a350..712c60d7f727 100644
--- a/tools/include/uapi/linux/fcntl.h
+++ b/tools/include/uapi/linux/fcntl.h
@@ -90,5 +90,6 @@
 #define AT_STATX_FORCE_SYNC	0x2000	/* - Force the attributes to be sync'd with the server */
 #define AT_STATX_DONT_SYNC	0x4000	/* - Don't sync attributes with the server */
 
+#define AT_FCHOWN_PROJID	0x40000000 /* Change project ID instead of group id */
 
 #endif /* _UAPI_LINUX_FCNTL_H */
