diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 06a18700a845..aa33669b9fda 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -111,6 +111,11 @@ static u64 attr_timeout(struct fuse_attr_out *o)
 	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
 }
 
+static u64 statx_timeout(struct fuse_statx_out *o)
+{
+	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
+}
+
 u64 entry_attr_timeout(struct fuse_entry_out *o)
 {
 	return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
@@ -1022,8 +1027,116 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr,
 	stat->blksize = 1 << blkbits;
 }
 
-static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
-			   struct file *file)
+static void fuse_fillattr2(struct inode *inode, struct fuse_attr *attr,
+			   struct fuse_statx *stx, struct kstat *stat)
+{
+	fuse_fillattr(inode, attr, stat);
+	stat->result_mask = stx->mask;
+	stat->attributes = stx->attributes;
+	stat->attributes_mask = stx->attributes_mask;
+	if (stx->mask & STATX_BTIME) {
+		stat->btime.tv_sec = stx->btime.sec;
+		stat->btime.tv_nsec = stx->btime.nsec;
+	}
+}
+
+static void fuse_statx_to_attr(struct fuse_statx *stx, struct fuse_attr *attr)
+{
+	attr->ino = stx->ino;
+	attr->size = stx->size;
+	attr->blocks = stx->blocks;
+	attr->atime = stx->atime.sec;
+	attr->mtime = stx->mtime.sec;
+	attr->ctime = stx->ctime.sec;
+	attr->atimensec = stx->atime.nsec;
+	attr->mtimensec = stx->mtime.nsec;
+	attr->ctimensec = stx->ctime.nsec;
+	attr->mode = stx->mode;
+	attr->nlink = stx->nlink;
+	attr->uid = stx->uid;
+	attr->gid = stx->gid;
+	attr->rdev = MKDEV(stx->rdev_major, stx->rdev_minor);
+	attr->blksize = stx->blksize;
+	attr->flags = stx->flags;
+}
+
+static int fuse_require_valid_attr(struct inode *inode, struct fuse_attr *attr)
+{
+	int ret = 0;
+
+	if (fuse_invalid_attr(attr) || (inode->i_mode ^ attr->mode) & S_IFMT) {
+		fuse_make_bad(inode);
+		ret = -EIO;
+	}
+	return ret;
+}
+
+static void fuse_getattr_inargs(struct inode *inode, struct file *file,
+				u32 *getattr_flags, u64 *fh)
+{
+	/* Directories have separate file-handle space */
+	if (file && S_ISREG(inode->i_mode)) {
+		struct fuse_file *ff = file->private_data;
+
+		*getattr_flags |= FUSE_GETATTR_FH;
+		*fh = ff->fh;
+	}
+}
+
+static int __fuse_do_statx(struct inode *inode, struct kstat *stat,
+			   struct file *file, u32 request_mask)
+{
+	int err;
+	struct fuse_attr attr;
+	struct fuse_statx_in inarg;
+	struct fuse_statx_out outarg;
+	struct fuse_mount *fm = get_fuse_mount(inode);
+	FUSE_ARGS(args);
+	u64 attr_version;
+
+	attr_version = fuse_get_attr_version(fm->fc);
+
+	memset(&inarg, 0, sizeof(inarg));
+	memset(&outarg, 0, sizeof(outarg));
+	fuse_getattr_inargs(inode, file, &inarg.getattr_flags, &inarg.fh);
+	inarg.mask = request_mask | STATX_BASIC_STATS;
+	args.opcode = FUSE_STATX;
+	args.nodeid = get_node_id(inode);
+	args.in_numargs = 1;
+	args.in_args[0].size = sizeof(inarg);
+	args.in_args[0].value = &inarg;
+	args.out_numargs = 1;
+	args.out_args[0].size = sizeof(outarg);
+	args.out_args[0].value = &outarg;
+	err = fuse_simple_request(fm, &args);
+	if (err == -ENOSYS)
+		goto no_statx;
+
+	if (err)
+		return err;
+
+	if ((outarg.attr.mask & STATX_BASIC_STATS) != STATX_BASIC_STATS)
+		goto no_statx;
+
+	fuse_statx_to_attr(&outarg.attr, &attr);
+	err = fuse_require_valid_attr(inode, &attr);
+	if (err)
+		return err;
+
+	fuse_change_attributes(inode, &attr,
+				statx_timeout(&outarg), attr_version);
+	if (stat)
+		fuse_fillattr2(inode, &attr, &outarg.attr, stat);
+
+	return err;
+
+no_statx:
+	fm->fc->no_statx = 1;
+	return -EINVAL;
+}
+
+static int __fuse_do_getattr(struct inode *inode, struct kstat *stat,
+			     struct file *file)
 {
 	int err;
 	struct fuse_getattr_in inarg;
@@ -1036,13 +1149,7 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
 
 	memset(&inarg, 0, sizeof(inarg));
 	memset(&outarg, 0, sizeof(outarg));
-	/* Directories have separate file-handle space */
-	if (file && S_ISREG(inode->i_mode)) {
-		struct fuse_file *ff = file->private_data;
-
-		inarg.getattr_flags |= FUSE_GETATTR_FH;
-		inarg.fh = ff->fh;
-	}
+	fuse_getattr_inargs(inode, file, &inarg.getattr_flags, &inarg.fh);
 	args.opcode = FUSE_GETATTR;
 	args.nodeid = get_node_id(inode);
 	args.in_numargs = 1;
@@ -1053,11 +1160,8 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
 	args.out_args[0].value = &outarg;
 	err = fuse_simple_request(fm, &args);
 	if (!err) {
-		if (fuse_invalid_attr(&outarg.attr) ||
-		    (inode->i_mode ^ outarg.attr.mode) & S_IFMT) {
-			fuse_make_bad(inode);
-			err = -EIO;
-		} else {
+		err = fuse_require_valid_attr(inode, &outarg.attr);
+		if (!err) {
 			fuse_change_attributes(inode, &outarg.attr,
 					       attr_timeout(&outarg),
 					       attr_version);
@@ -1068,11 +1172,31 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
 	return err;
 }
 
+static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
+			   struct file *file, u32 request_mask)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	if (fc->no_statx || fc->minor < 34 || !(request_mask & STATX_BTIME))
+		goto getattr;
+
+	err = __fuse_do_statx(inode, stat, file, request_mask);
+	if (err && fc->no_statx)
+		goto getattr;
+
+	return err;
+
+getattr:
+	return __fuse_do_getattr(inode, stat, file);
+}
+
 static int fuse_update_get_attr(struct inode *inode, struct file *file,
 				struct kstat *stat, u32 request_mask,
 				unsigned int flags)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_conn *fc = get_fuse_conn(inode);
 	int err = 0;
 	bool sync;
 
@@ -1082,12 +1206,14 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
 		sync = false;
 	else if (request_mask & READ_ONCE(fi->inval_mask))
 		sync = true;
+	else if (!fc->no_statx && (request_mask & STATX_BTIME))
+		sync = true;
 	else
 		sync = time_before64(fi->i_time, get_jiffies_64());
 
 	if (sync) {
 		forget_all_cached_acls(inode);
-		err = fuse_do_getattr(inode, stat, file);
+		err = fuse_do_getattr(inode, stat, file, request_mask);
 	} else if (stat) {
 		generic_fillattr(&init_user_ns, inode, stat);
 		stat->mode = fi->orig_i_mode;
@@ -1235,7 +1361,7 @@ static int fuse_perm_getattr(struct inode *inode, int mask)
 		return -ECHILD;
 
 	forget_all_cached_acls(inode);
-	return fuse_do_getattr(inode, NULL, NULL);
+	return fuse_do_getattr(inode, NULL, NULL, STATX_BASIC_STATS);
 }
 
 /*
@@ -1789,7 +1915,8 @@ static int fuse_setattr(struct user_namespace *mnt_userns, struct dentry *entry,
 			 * ia_mode calculation may have used stale i_mode.
 			 * Refresh and recalculate.
 			 */
-			ret = fuse_do_getattr(inode, NULL, file);
+			ret = fuse_do_getattr(inode, NULL, file,
+					      STATX_BASIC_STATS);
 			if (ret)
 				return ret;
 
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 63d97a15ffde..15cd1968aac9 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -755,6 +755,9 @@ struct fuse_conn {
 	/* Auto-mount submounts announced by the server */
 	unsigned int auto_submounts:1;
 
+	/** Is statx not implemented by fs? */
+	unsigned int no_statx:1;
+
 	/** The number of requests waiting for completion */
 	atomic_t num_waiting;
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 54442612c48b..36ac3c40b922 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -179,6 +179,9 @@
  *  7.33
  *  - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
  *  - add FUSE_OPEN_KILL_SUIDGID
+ *
+ *  7.34
+ *  - add FUSE_STATX
  */
 
 #ifndef _LINUX_FUSE_H
@@ -214,7 +217,7 @@
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 33
+#define FUSE_KERNEL_MINOR_VERSION 34
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -499,6 +502,7 @@ enum fuse_opcode {
 	FUSE_COPY_FILE_RANGE	= 47,
 	FUSE_SETUPMAPPING	= 48,
 	FUSE_REMOVEMAPPING	= 49,
+	FUSE_STATX		= 50,
 
 	/* CUSE specific operations */
 	CUSE_INIT		= 4096,
@@ -957,4 +961,46 @@ struct fuse_removemapping_one {
 #define FUSE_REMOVEMAPPING_MAX_ENTRY   \
 		(PAGE_SIZE / sizeof(struct fuse_removemapping_one))
 
+struct fuse_statx_timestamp {
+	int64_t		sec;
+	uint32_t	nsec;
+	int32_t		reserved;
+};
+
+struct fuse_statx {
+	uint32_t	mask;
+	uint32_t	blksize;
+	uint64_t	attributes;
+	uint32_t	nlink;
+	uint32_t	uid;
+	uint32_t	gid;
+	uint32_t	mode;
+	uint64_t	ino;
+	uint64_t	size;
+	uint64_t	blocks;
+	uint64_t	attributes_mask;
+	struct fuse_statx_timestamp atime;
+	struct fuse_statx_timestamp btime;
+	struct fuse_statx_timestamp ctime;
+	struct fuse_statx_timestamp mtime;
+	uint32_t	rdev_major;
+	uint32_t	rdev_minor;
+	uint32_t	flags;
+	uint32_t	padding;
+	uint64_t	spare[12];
+};
+
+struct fuse_statx_in {
+	uint32_t	getattr_flags;
+	uint32_t	mask;
+	uint64_t	fh;
+};
+
+struct fuse_statx_out {
+	uint64_t	attr_valid;
+	uint32_t	attr_valid_nsec;
+	uint32_t	dummy;
+	struct fuse_statx attr;
+};
+
 #endif /* _LINUX_FUSE_H */
