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