xref: /linux/fs/fuse/acl.c (revision bba2c3615bd6cfee7456d1130f2e6b01b3f4e9ba)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * FUSE: Filesystem in Userspace
4  * Copyright (C) 2016 Canonical Ltd. <seth.forshee@canonical.com>
5  */
6 
7 #include "fuse_i.h"
8 
9 #include <linux/posix_acl.h>
10 #include <linux/posix_acl_xattr.h>
11 
12 static struct posix_acl *__fuse_get_acl(struct fuse_conn *fc,
13 					struct inode *inode, int type, bool rcu)
14 {
15 	int size;
16 	const char *name;
17 	void *value = NULL;
18 	struct posix_acl *acl;
19 
20 	if (rcu)
21 		return ERR_PTR(-ECHILD);
22 
23 	if (fuse_is_bad(inode))
24 		return ERR_PTR(-EIO);
25 
26 	if (fc->no_getxattr)
27 		return NULL;
28 
29 	if (type == ACL_TYPE_ACCESS)
30 		name = XATTR_NAME_POSIX_ACL_ACCESS;
31 	else if (type == ACL_TYPE_DEFAULT)
32 		name = XATTR_NAME_POSIX_ACL_DEFAULT;
33 	else
34 		return ERR_PTR(-EOPNOTSUPP);
35 
36 	value = kmalloc(PAGE_SIZE, GFP_KERNEL);
37 	if (!value)
38 		return ERR_PTR(-ENOMEM);
39 	size = fuse_getxattr(inode, name, value, PAGE_SIZE);
40 	if (size > 0)
41 		acl = posix_acl_from_xattr(fc->user_ns, value, size);
42 	else if ((size == 0) || (size == -ENODATA) ||
43 		 (size == -EOPNOTSUPP && fc->no_getxattr))
44 		acl = NULL;
45 	else if (size == -ERANGE)
46 		acl = ERR_PTR(-E2BIG);
47 	else
48 		acl = ERR_PTR(size);
49 
50 	kfree(value);
51 	return acl;
52 }
53 
54 static inline bool fuse_no_acl(const struct fuse_conn *fc,
55 			       const struct inode *inode)
56 {
57 	/*
58 	 * Refuse interacting with POSIX ACLs for daemons that
59 	 * don't support FUSE_POSIX_ACL and are not mounted on
60 	 * the host to retain backwards compatibility.
61 	 */
62 	return !fc->posix_acl && (i_user_ns(inode) != &init_user_ns);
63 }
64 
65 struct posix_acl *fuse_get_acl(struct mnt_idmap *idmap,
66 			       struct dentry *dentry, int type)
67 {
68 	struct inode *inode = d_inode(dentry);
69 	struct fuse_conn *fc = get_fuse_conn(inode);
70 
71 	if (fuse_no_acl(fc, inode))
72 		return ERR_PTR(-EOPNOTSUPP);
73 
74 	return __fuse_get_acl(fc, inode, type, false);
75 }
76 
77 struct posix_acl *fuse_get_inode_acl(struct inode *inode, int type, bool rcu)
78 {
79 	struct fuse_conn *fc = get_fuse_conn(inode);
80 
81 	/*
82 	 * FUSE daemons before FUSE_POSIX_ACL was introduced could get and set
83 	 * POSIX ACLs without them being used for permission checking by the
84 	 * vfs. Retain that behavior for backwards compatibility as there are
85 	 * filesystems that do all permission checking for acls in the daemon
86 	 * and not in the kernel.
87 	 */
88 	if (!fc->posix_acl)
89 		return NULL;
90 	return __fuse_get_acl(fc,  inode, type, rcu);
91 }
92 
93 int fuse_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
94 		 struct posix_acl *acl, int type)
95 {
96 	struct inode *inode = d_inode(dentry);
97 	struct fuse_conn *fc = get_fuse_conn(inode);
98 	const char *name;
99 	int ret;
100 
101 	if (fuse_is_bad(inode))
102 		return -EIO;
103 
104 	if (fc->no_setxattr || fuse_no_acl(fc, inode))
105 		return -EOPNOTSUPP;
106 
107 	if (type == ACL_TYPE_ACCESS)
108 		name = XATTR_NAME_POSIX_ACL_ACCESS;
109 	else if (type == ACL_TYPE_DEFAULT)
110 		name = XATTR_NAME_POSIX_ACL_DEFAULT;
111 	else
112 		return -EINVAL;
113 
114 	if (acl) {
115 		unsigned int extra_flags = 0;
116 		/*
117 		 * Fuse userspace is responsible for updating access
118 		 * permissions in the inode, if needed. fuse_setxattr
119 		 * invalidates the inode attributes, which will force
120 		 * them to be refreshed the next time they are used,
121 		 * and it also updates i_ctime.
122 		 */
123 		size_t size;
124 		void *value;
125 
126 		value = posix_acl_to_xattr(fc->user_ns, acl, &size, GFP_KERNEL);
127 		if (!value)
128 			return -ENOMEM;
129 
130 		if (size > PAGE_SIZE) {
131 			kfree(value);
132 			return -E2BIG;
133 		}
134 
135 		/*
136 		 * Fuse daemons without FUSE_POSIX_ACL never changed the passed
137 		 * through POSIX ACLs. Such daemons don't expect setgid bits to
138 		 * be stripped.
139 		 */
140 		if (fc->posix_acl &&
141 		    !in_group_or_capable(idmap, inode,
142 					 i_gid_into_vfsgid(idmap, inode)))
143 			extra_flags |= FUSE_SETXATTR_ACL_KILL_SGID;
144 
145 		ret = fuse_setxattr(inode, name, value, size, 0, extra_flags);
146 		kfree(value);
147 	} else {
148 		ret = fuse_removexattr(inode, name);
149 	}
150 
151 	if (fc->posix_acl) {
152 		/*
153 		 * Fuse daemons without FUSE_POSIX_ACL never cached POSIX ACLs
154 		 * and didn't invalidate attributes. Retain that behavior.
155 		 */
156 		forget_all_cached_acls(inode);
157 		fuse_invalidate_attr(inode);
158 	}
159 
160 	return ret;
161 }
162