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