[PATCH v2 2/2] hostfs: store permissions in extended attributes

Marko Petrović petrovicmarko2006 at gmail.com
Thu Apr 13 19:33:57 PDT 2023


Fix GID assignment error in uml_chown() and add support for correct
behavior when parent directory has SETGID bit.

Signed-off-by: Marko Petrović <petrovicmarko2006 at gmail.com>
---
 fs/hostfs/hostfs.h      |   5 +-
 fs/hostfs/hostfs_kern.c |  23 +++++--
 fs/hostfs/hostfs_user.c | 142 +++++++++++++++++++++++++++++++++++++---
 3 files changed, 156 insertions(+), 14 deletions(-)

diff --git a/fs/hostfs/hostfs.h b/fs/hostfs/hostfs.h
index 69cb796f6270..9756303fc089 100644
--- a/fs/hostfs/hostfs.h
+++ b/fs/hostfs/hostfs.h
@@ -37,6 +37,7 @@
  * is on, and remove the appropriate bits from attr->ia_mode (attr is a
  * "struct iattr *"). -BlaisorBlade
  */
+extern int use_xattr;
 struct hostfs_timespec {
 	long long tv_sec;
 	long long tv_nsec;
@@ -83,11 +84,11 @@ extern int write_file(int fd, unsigned long long *offset, const char *buf,
 		      int len);
 extern int lseek_file(int fd, long long offset, int whence);
 extern int fsync_file(int fd, int datasync);
-extern int file_create(char *name, int mode);
+extern int file_create(char *name, int mode, uid_t uid, gid_t gid);
 extern int set_attr(const char *file, struct hostfs_iattr *attrs, int fd);
 extern int make_symlink(const char *from, const char *to);
 extern int unlink_file(const char *file);
-extern int do_mkdir(const char *file, int mode);
+extern int do_mkdir(const char *file, int mode, uid_t uid, gid_t gid);
 extern int hostfs_do_rmdir(const char *file);
 extern int do_mknod(const char *file, int mode, unsigned int major,
 		    unsigned int minor);
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index 28b4f15c19eb..920d211d4e19 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -17,6 +17,7 @@
 #include <linux/writeback.h>
 #include <linux/mount.h>
 #include <linux/namei.h>
+#include <linux/uidgid.h>
 #include "hostfs.h"
 #include <init.h>
 #include <kern.h>
@@ -40,6 +41,7 @@ static struct kmem_cache *hostfs_inode_cache;
 /* Changed in hostfs_args before the kernel starts running */
 static char *root_ino = "";
 static int append = 0;
+int use_xattr;
 
 static const struct inode_operations hostfs_iops;
 static const struct inode_operations hostfs_dir_iops;
@@ -50,6 +52,7 @@ static int __init hostfs_args(char *options, int *add)
 {
 	char *ptr;
 
+	use_xattr = 0;
 	ptr = strchr(options, ',');
 	if (ptr != NULL)
 		*ptr++ = '\0';
@@ -64,6 +67,8 @@ static int __init hostfs_args(char *options, int *add)
 		if (*options != '\0') {
 			if (!strcmp(options, "append"))
 				append = 1;
+			else if (!strcmp(options, "xattrperm"))
+				use_xattr = 1;
 			else printf("hostfs_args - unsupported option - %s\n",
 				    options);
 		}
@@ -79,8 +84,10 @@ __uml_setup("hostfs=", hostfs_args,
 "    tree on the host.  If this isn't specified, then a user inside UML can\n"
 "    mount anything on the host that's accessible to the user that's running\n"
 "    it.\n"
-"    The only flag currently supported is 'append', which specifies that all\n"
-"    files opened by hostfs will be opened in append mode.\n\n"
+"    The only flags currently supported are 'append', which specifies that\n"
+"    all files opened by hostfs will be opened in append mode and 'xattrperm'\n"
+"    which specifies that permissions of files will be stored in extended\n"
+"    attributes.\n\n"
 );
 #endif
 
@@ -566,6 +573,8 @@ static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir,
 	struct inode *inode;
 	char *name;
 	int error, fd;
+	unsigned int currentuid;
+	unsigned int currentgid;
 
 	inode = hostfs_iget(dir->i_sb);
 	if (IS_ERR(inode)) {
@@ -578,7 +587,9 @@ static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir,
 	if (name == NULL)
 		goto out_put;
 
-	fd = file_create(name, mode & 0777);
+	currentuid = from_kuid(current->cred->user_ns, current->cred->euid);
+	currentgid = from_kgid(current->cred->user_ns, current->cred->egid);
+	fd = file_create(name, mode & 0777, currentuid, currentgid);
 	if (fd < 0)
 		error = fd;
 	else
@@ -677,10 +688,14 @@ static int hostfs_mkdir(struct mnt_idmap *idmap, struct inode *ino,
 {
 	char *file;
 	int err;
+	unsigned int currentuid;
+	unsigned int currentgid;
 
 	if ((file = dentry_name(dentry)) == NULL)
 		return -ENOMEM;
-	err = do_mkdir(file, mode);
+	currentuid = from_kuid(current->cred->user_ns, current->cred->euid);
+	currentgid = from_kgid(current->cred->user_ns, current->cred->egid);
+	err = do_mkdir(file, mode, currentuid, currentgid);
 	__putname(file);
 	return err;
 }
diff --git a/fs/hostfs/hostfs_user.c b/fs/hostfs/hostfs_user.c
index 5ecc4706172b..f2cc667ab0dd 100644
--- a/fs/hostfs/hostfs_user.c
+++ b/fs/hostfs/hostfs_user.c
@@ -15,6 +15,8 @@
 #include <sys/types.h>
 #include <sys/vfs.h>
 #include <sys/syscall.h>
+#include <sys/xattr.h>
+#include <sys/mman.h>
 #include "hostfs.h"
 #include <utime.h>
 
@@ -38,6 +40,118 @@ static void stat64_to_hostfs(const struct stat64 *buf, struct hostfs_stat *p)
 	p->min = os_minor(buf->st_rdev);
 }
 
+static int uml_chown(const char *pathname, unsigned int owner, unsigned int group)
+{
+	int status;
+
+	if (use_xattr) {
+		if (owner != -1) {
+			status = setxattr(pathname, "user.umluid", &owner,
+							sizeof(unsigned int), 0);
+			if (status < 0)
+				return status;
+		}
+		if (group != -1) {
+			status = setxattr(pathname, "user.umlgid", &group,
+							sizeof(unsigned int), 0);
+			if (status < 0)
+				return status;
+		}
+		return 0;
+	} else {
+		return chown(pathname, owner, group);
+	}
+}
+
+static int uml_fchown(int fd, unsigned int owner, unsigned int group)
+{
+	int status;
+
+	if (use_xattr) {
+		if (owner != -1) {
+			status = fsetxattr(fd, "user.umluid", &owner,
+						sizeof(unsigned int), 0);
+			if (status < 0)
+				return status;
+		}
+		if (group != -1) {
+			status = fsetxattr(fd, "user.umlgid", &group,
+						sizeof(unsigned int), 0);
+			if (status < 0)
+				return status;
+		}
+		return 0;
+	} else {
+		return fchown(fd, owner, group);
+	}
+}
+
+static int uml_chmod(const char *pathname, unsigned int mode)
+{
+	if (use_xattr)
+		return setxattr(pathname, "user.umlmode", &mode,
+						sizeof(unsigned int), 0);
+	return chmod(pathname, mode);
+}
+
+static int uml_fchmod(int fd, unsigned int mode)
+{
+	if (use_xattr)
+		return fsetxattr(fd, "user.umlmode", &mode,
+						sizeof(unsigned int), 0);
+	return fchmod(fd, mode);
+}
+
+static void read_permissions(const char *path, struct stat64 *p)
+{
+	unsigned int mode, uid, gid;
+
+	if (!use_xattr)
+		return;
+	if (getxattr(path, "user.umlmode", &mode, sizeof(unsigned int)) != -1)
+		p->st_mode = mode;
+	if (getxattr(path, "user.umluid", &uid, sizeof(unsigned int)) != -1)
+		p->st_uid = uid;
+	if (getxattr(path, "user.umlgid", &gid, sizeof(unsigned int)) != -1)
+		p->st_gid = gid;
+}
+
+// Remove double slash from the path
+static void fix_path(char *path)
+{
+	int i = 0;
+	int skip = 0;
+
+	while (path[i] != '\0') {
+		if (path[i] == '/' && path[i+1] == '/')
+			skip = 1;
+		path[i] = path[i+skip];
+		i++;
+	}
+}
+
+// path is in the form "rootfs//var/abc"
+static long is_set_gid(const char *path)
+{
+	int i = strlen(path) + 1;
+	char *parent = mmap(NULL, i, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
+	struct stat64 buf = { 0 };
+
+	strcpy(parent, path);
+	i = i - 3;
+	while (parent[i] != '/')
+		i--;
+	parent[i] = '\0';
+	fix_path(parent);
+
+	stat64(parent, &buf);
+	read_permissions(parent, &buf);
+	munmap(parent, strlen(path) + 1);
+	if (buf.st_mode & S_ISGID)
+		return buf.st_gid;
+	return -1;
+}
+
 int stat_file(const char *path, struct hostfs_stat *p, int fd)
 {
 	struct stat64 buf;
@@ -48,6 +162,7 @@ int stat_file(const char *path, struct hostfs_stat *p, int fd)
 	} else if (lstat64(path, &buf) < 0) {
 		return -errno;
 	}
+	read_permissions(path, &buf);
 	stat64_to_hostfs(&buf, p);
 	return 0;
 }
@@ -181,13 +296,19 @@ void close_dir(void *stream)
 	closedir(stream);
 }
 
-int file_create(char *name, int mode)
+int file_create(char *name, int mode, unsigned int uid, unsigned int gid)
 {
 	int fd;
+	long ret;
 
 	fd = open64(name, O_CREAT | O_RDWR, mode);
 	if (fd < 0)
 		return -errno;
+
+	ret = is_set_gid(name);
+	if (ret != -1)
+		gid = ret;
+	uml_chown(name, uid, gid);
 	return fd;
 }
 
@@ -199,25 +320,25 @@ int set_attr(const char *file, struct hostfs_iattr *attrs, int fd)
 
 	if (attrs->ia_valid & HOSTFS_ATTR_MODE) {
 		if (fd >= 0) {
-			if (fchmod(fd, attrs->ia_mode) != 0)
+			if (uml_fchmod(fd, attrs->ia_mode) != 0)
 				return -errno;
-		} else if (chmod(file, attrs->ia_mode) != 0) {
+		} else if (uml_chmod(file, attrs->ia_mode) != 0) {
 			return -errno;
 		}
 	}
 	if (attrs->ia_valid & HOSTFS_ATTR_UID) {
 		if (fd >= 0) {
-			if (fchown(fd, attrs->ia_uid, -1))
+			if (uml_fchown(fd, attrs->ia_uid, -1))
 				return -errno;
-		} else if (chown(file, attrs->ia_uid, -1)) {
+		} else if (uml_chown(file, attrs->ia_uid, -1)) {
 			return -errno;
 		}
 	}
 	if (attrs->ia_valid & HOSTFS_ATTR_GID) {
 		if (fd >= 0) {
-			if (fchown(fd, -1, attrs->ia_gid))
+			if (uml_fchown(fd, -1, attrs->ia_gid))
 				return -errno;
-		} else if (chown(file, -1, attrs->ia_gid)) {
+		} else if (uml_chown(file, -1, attrs->ia_gid)) {
 			return -errno;
 		}
 	}
@@ -294,13 +415,18 @@ int unlink_file(const char *file)
 	return 0;
 }
 
-int do_mkdir(const char *file, int mode)
+int do_mkdir(const char *file, int mode, unsigned int uid, unsigned int gid)
 {
 	int err;
+	long ret;
 
 	err = mkdir(file, mode);
 	if (err)
 		return -errno;
+	ret = is_set_gid(file);
+	if (ret != -1)
+		gid = ret;
+	uml_chown(file, uid, gid);
 	return 0;
 }
 
-- 
2.39.2




More information about the linux-um mailing list