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 user_namespace *mnt_userns, 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(-ECHILD); 25 26 if (fuse_is_bad(inode)) 27 return ERR_PTR(-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(-EOPNOTSUPP); 38 39 value = kmalloc(PAGE_SIZE, GFP_KERNEL); 40 if (!value) 41 return ERR_PTR(-ENOMEM); 42 size = fuse_getxattr(inode, name, value, PAGE_SIZE); 43 if (size > 0) 44 acl = posix_acl_from_xattr(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(-E2BIG); 50 else 51 acl = ERR_PTR(size); 52 53 kfree(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 user_namespace *mnt_userns, 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(-EOPNOTSUPP); 76 77 return __fuse_get_acl(fc, mnt_userns, inode, type, 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, &init_user_ns, inode, type, rcu); 95 } 96 97 int fuse_set_acl(struct user_namespace *mnt_userns, 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 extra_flags = 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(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(fc->user_ns, acl, value, size); 138 if (ret < 0) { 139 kfree(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(i_gid_into_vfsgid(&init_user_ns, inode)) && 150 !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) 151 extra_flags |= FUSE_SETXATTR_ACL_KILL_SGID; 152 153 ret = fuse_setxattr(inode, name, value, size, 0, extra_flags); 154 kfree(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