|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +#include <linux/fs.h> |
| 3 | +#include <linux/security.h> |
| 4 | +#include <linux/fscrypt.h> |
| 5 | +#include <linux/fileattr.h> |
| 6 | +#include <linux/export.h> |
| 7 | + |
| 8 | +/** |
| 9 | + * fileattr_fill_xflags - initialize fileattr with xflags |
| 10 | + * @fa: fileattr pointer |
| 11 | + * @xflags: FS_XFLAG_* flags |
| 12 | + * |
| 13 | + * Set ->fsx_xflags, ->fsx_valid and ->flags (translated xflags). All |
| 14 | + * other fields are zeroed. |
| 15 | + */ |
| 16 | +void fileattr_fill_xflags(struct fileattr *fa, u32 xflags) |
| 17 | +{ |
| 18 | + memset(fa, 0, sizeof(*fa)); |
| 19 | + fa->fsx_valid = true; |
| 20 | + fa->fsx_xflags = xflags; |
| 21 | + if (fa->fsx_xflags & FS_XFLAG_IMMUTABLE) |
| 22 | + fa->flags |= FS_IMMUTABLE_FL; |
| 23 | + if (fa->fsx_xflags & FS_XFLAG_APPEND) |
| 24 | + fa->flags |= FS_APPEND_FL; |
| 25 | + if (fa->fsx_xflags & FS_XFLAG_SYNC) |
| 26 | + fa->flags |= FS_SYNC_FL; |
| 27 | + if (fa->fsx_xflags & FS_XFLAG_NOATIME) |
| 28 | + fa->flags |= FS_NOATIME_FL; |
| 29 | + if (fa->fsx_xflags & FS_XFLAG_NODUMP) |
| 30 | + fa->flags |= FS_NODUMP_FL; |
| 31 | + if (fa->fsx_xflags & FS_XFLAG_DAX) |
| 32 | + fa->flags |= FS_DAX_FL; |
| 33 | + if (fa->fsx_xflags & FS_XFLAG_PROJINHERIT) |
| 34 | + fa->flags |= FS_PROJINHERIT_FL; |
| 35 | +} |
| 36 | +EXPORT_SYMBOL(fileattr_fill_xflags); |
| 37 | + |
| 38 | +/** |
| 39 | + * fileattr_fill_flags - initialize fileattr with flags |
| 40 | + * @fa: fileattr pointer |
| 41 | + * @flags: FS_*_FL flags |
| 42 | + * |
| 43 | + * Set ->flags, ->flags_valid and ->fsx_xflags (translated flags). |
| 44 | + * All other fields are zeroed. |
| 45 | + */ |
| 46 | +void fileattr_fill_flags(struct fileattr *fa, u32 flags) |
| 47 | +{ |
| 48 | + memset(fa, 0, sizeof(*fa)); |
| 49 | + fa->flags_valid = true; |
| 50 | + fa->flags = flags; |
| 51 | + if (fa->flags & FS_SYNC_FL) |
| 52 | + fa->fsx_xflags |= FS_XFLAG_SYNC; |
| 53 | + if (fa->flags & FS_IMMUTABLE_FL) |
| 54 | + fa->fsx_xflags |= FS_XFLAG_IMMUTABLE; |
| 55 | + if (fa->flags & FS_APPEND_FL) |
| 56 | + fa->fsx_xflags |= FS_XFLAG_APPEND; |
| 57 | + if (fa->flags & FS_NODUMP_FL) |
| 58 | + fa->fsx_xflags |= FS_XFLAG_NODUMP; |
| 59 | + if (fa->flags & FS_NOATIME_FL) |
| 60 | + fa->fsx_xflags |= FS_XFLAG_NOATIME; |
| 61 | + if (fa->flags & FS_DAX_FL) |
| 62 | + fa->fsx_xflags |= FS_XFLAG_DAX; |
| 63 | + if (fa->flags & FS_PROJINHERIT_FL) |
| 64 | + fa->fsx_xflags |= FS_XFLAG_PROJINHERIT; |
| 65 | +} |
| 66 | +EXPORT_SYMBOL(fileattr_fill_flags); |
| 67 | + |
| 68 | +/** |
| 69 | + * vfs_fileattr_get - retrieve miscellaneous file attributes |
| 70 | + * @dentry: the object to retrieve from |
| 71 | + * @fa: fileattr pointer |
| 72 | + * |
| 73 | + * Call i_op->fileattr_get() callback, if exists. |
| 74 | + * |
| 75 | + * Return: 0 on success, or a negative error on failure. |
| 76 | + */ |
| 77 | +int vfs_fileattr_get(struct dentry *dentry, struct fileattr *fa) |
| 78 | +{ |
| 79 | + struct inode *inode = d_inode(dentry); |
| 80 | + |
| 81 | + if (!inode->i_op->fileattr_get) |
| 82 | + return -ENOIOCTLCMD; |
| 83 | + |
| 84 | + return inode->i_op->fileattr_get(dentry, fa); |
| 85 | +} |
| 86 | +EXPORT_SYMBOL(vfs_fileattr_get); |
| 87 | + |
| 88 | +/** |
| 89 | + * copy_fsxattr_to_user - copy fsxattr to userspace. |
| 90 | + * @fa: fileattr pointer |
| 91 | + * @ufa: fsxattr user pointer |
| 92 | + * |
| 93 | + * Return: 0 on success, or -EFAULT on failure. |
| 94 | + */ |
| 95 | +int copy_fsxattr_to_user(const struct fileattr *fa, struct fsxattr __user *ufa) |
| 96 | +{ |
| 97 | + struct fsxattr xfa; |
| 98 | + |
| 99 | + memset(&xfa, 0, sizeof(xfa)); |
| 100 | + xfa.fsx_xflags = fa->fsx_xflags; |
| 101 | + xfa.fsx_extsize = fa->fsx_extsize; |
| 102 | + xfa.fsx_nextents = fa->fsx_nextents; |
| 103 | + xfa.fsx_projid = fa->fsx_projid; |
| 104 | + xfa.fsx_cowextsize = fa->fsx_cowextsize; |
| 105 | + |
| 106 | + if (copy_to_user(ufa, &xfa, sizeof(xfa))) |
| 107 | + return -EFAULT; |
| 108 | + |
| 109 | + return 0; |
| 110 | +} |
| 111 | +EXPORT_SYMBOL(copy_fsxattr_to_user); |
| 112 | + |
| 113 | +static int copy_fsxattr_from_user(struct fileattr *fa, |
| 114 | + struct fsxattr __user *ufa) |
| 115 | +{ |
| 116 | + struct fsxattr xfa; |
| 117 | + |
| 118 | + if (copy_from_user(&xfa, ufa, sizeof(xfa))) |
| 119 | + return -EFAULT; |
| 120 | + |
| 121 | + fileattr_fill_xflags(fa, xfa.fsx_xflags); |
| 122 | + fa->fsx_extsize = xfa.fsx_extsize; |
| 123 | + fa->fsx_nextents = xfa.fsx_nextents; |
| 124 | + fa->fsx_projid = xfa.fsx_projid; |
| 125 | + fa->fsx_cowextsize = xfa.fsx_cowextsize; |
| 126 | + |
| 127 | + return 0; |
| 128 | +} |
| 129 | + |
| 130 | +/* |
| 131 | + * Generic function to check FS_IOC_FSSETXATTR/FS_IOC_SETFLAGS values and reject |
| 132 | + * any invalid configurations. |
| 133 | + * |
| 134 | + * Note: must be called with inode lock held. |
| 135 | + */ |
| 136 | +static int fileattr_set_prepare(struct inode *inode, |
| 137 | + const struct fileattr *old_ma, |
| 138 | + struct fileattr *fa) |
| 139 | +{ |
| 140 | + int err; |
| 141 | + |
| 142 | + /* |
| 143 | + * The IMMUTABLE and APPEND_ONLY flags can only be changed by |
| 144 | + * the relevant capability. |
| 145 | + */ |
| 146 | + if ((fa->flags ^ old_ma->flags) & (FS_APPEND_FL | FS_IMMUTABLE_FL) && |
| 147 | + !capable(CAP_LINUX_IMMUTABLE)) |
| 148 | + return -EPERM; |
| 149 | + |
| 150 | + err = fscrypt_prepare_setflags(inode, old_ma->flags, fa->flags); |
| 151 | + if (err) |
| 152 | + return err; |
| 153 | + |
| 154 | + /* |
| 155 | + * Project Quota ID state is only allowed to change from within the init |
| 156 | + * namespace. Enforce that restriction only if we are trying to change |
| 157 | + * the quota ID state. Everything else is allowed in user namespaces. |
| 158 | + */ |
| 159 | + if (current_user_ns() != &init_user_ns) { |
| 160 | + if (old_ma->fsx_projid != fa->fsx_projid) |
| 161 | + return -EINVAL; |
| 162 | + if ((old_ma->fsx_xflags ^ fa->fsx_xflags) & |
| 163 | + FS_XFLAG_PROJINHERIT) |
| 164 | + return -EINVAL; |
| 165 | + } else { |
| 166 | + /* |
| 167 | + * Caller is allowed to change the project ID. If it is being |
| 168 | + * changed, make sure that the new value is valid. |
| 169 | + */ |
| 170 | + if (old_ma->fsx_projid != fa->fsx_projid && |
| 171 | + !projid_valid(make_kprojid(&init_user_ns, fa->fsx_projid))) |
| 172 | + return -EINVAL; |
| 173 | + } |
| 174 | + |
| 175 | + /* Check extent size hints. */ |
| 176 | + if ((fa->fsx_xflags & FS_XFLAG_EXTSIZE) && !S_ISREG(inode->i_mode)) |
| 177 | + return -EINVAL; |
| 178 | + |
| 179 | + if ((fa->fsx_xflags & FS_XFLAG_EXTSZINHERIT) && |
| 180 | + !S_ISDIR(inode->i_mode)) |
| 181 | + return -EINVAL; |
| 182 | + |
| 183 | + if ((fa->fsx_xflags & FS_XFLAG_COWEXTSIZE) && |
| 184 | + !S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) |
| 185 | + return -EINVAL; |
| 186 | + |
| 187 | + /* |
| 188 | + * It is only valid to set the DAX flag on regular files and |
| 189 | + * directories on filesystems. |
| 190 | + */ |
| 191 | + if ((fa->fsx_xflags & FS_XFLAG_DAX) && |
| 192 | + !(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) |
| 193 | + return -EINVAL; |
| 194 | + |
| 195 | + /* Extent size hints of zero turn off the flags. */ |
| 196 | + if (fa->fsx_extsize == 0) |
| 197 | + fa->fsx_xflags &= ~(FS_XFLAG_EXTSIZE | FS_XFLAG_EXTSZINHERIT); |
| 198 | + if (fa->fsx_cowextsize == 0) |
| 199 | + fa->fsx_xflags &= ~FS_XFLAG_COWEXTSIZE; |
| 200 | + |
| 201 | + return 0; |
| 202 | +} |
| 203 | + |
| 204 | +/** |
| 205 | + * vfs_fileattr_set - change miscellaneous file attributes |
| 206 | + * @idmap: idmap of the mount |
| 207 | + * @dentry: the object to change |
| 208 | + * @fa: fileattr pointer |
| 209 | + * |
| 210 | + * After verifying permissions, call i_op->fileattr_set() callback, if |
| 211 | + * exists. |
| 212 | + * |
| 213 | + * Verifying attributes involves retrieving current attributes with |
| 214 | + * i_op->fileattr_get(), this also allows initializing attributes that have |
| 215 | + * not been set by the caller to current values. Inode lock is held |
| 216 | + * thoughout to prevent racing with another instance. |
| 217 | + * |
| 218 | + * Return: 0 on success, or a negative error on failure. |
| 219 | + */ |
| 220 | +int vfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, |
| 221 | + struct fileattr *fa) |
| 222 | +{ |
| 223 | + struct inode *inode = d_inode(dentry); |
| 224 | + struct fileattr old_ma = {}; |
| 225 | + int err; |
| 226 | + |
| 227 | + if (!inode->i_op->fileattr_set) |
| 228 | + return -ENOIOCTLCMD; |
| 229 | + |
| 230 | + if (!inode_owner_or_capable(idmap, inode)) |
| 231 | + return -EPERM; |
| 232 | + |
| 233 | + inode_lock(inode); |
| 234 | + err = vfs_fileattr_get(dentry, &old_ma); |
| 235 | + if (!err) { |
| 236 | + /* initialize missing bits from old_ma */ |
| 237 | + if (fa->flags_valid) { |
| 238 | + fa->fsx_xflags |= old_ma.fsx_xflags & ~FS_XFLAG_COMMON; |
| 239 | + fa->fsx_extsize = old_ma.fsx_extsize; |
| 240 | + fa->fsx_nextents = old_ma.fsx_nextents; |
| 241 | + fa->fsx_projid = old_ma.fsx_projid; |
| 242 | + fa->fsx_cowextsize = old_ma.fsx_cowextsize; |
| 243 | + } else { |
| 244 | + fa->flags |= old_ma.flags & ~FS_COMMON_FL; |
| 245 | + } |
| 246 | + err = fileattr_set_prepare(inode, &old_ma, fa); |
| 247 | + if (!err) |
| 248 | + err = inode->i_op->fileattr_set(idmap, dentry, fa); |
| 249 | + } |
| 250 | + inode_unlock(inode); |
| 251 | + |
| 252 | + return err; |
| 253 | +} |
| 254 | +EXPORT_SYMBOL(vfs_fileattr_set); |
| 255 | + |
| 256 | +int ioctl_getflags(struct file *file, unsigned int __user *argp) |
| 257 | +{ |
| 258 | + struct fileattr fa = { .flags_valid = true }; /* hint only */ |
| 259 | + int err; |
| 260 | + |
| 261 | + err = vfs_fileattr_get(file->f_path.dentry, &fa); |
| 262 | + if (!err) |
| 263 | + err = put_user(fa.flags, argp); |
| 264 | + return err; |
| 265 | +} |
| 266 | +EXPORT_SYMBOL(ioctl_getflags); |
| 267 | + |
| 268 | +int ioctl_setflags(struct file *file, unsigned int __user *argp) |
| 269 | +{ |
| 270 | + struct mnt_idmap *idmap = file_mnt_idmap(file); |
| 271 | + struct dentry *dentry = file->f_path.dentry; |
| 272 | + struct fileattr fa; |
| 273 | + unsigned int flags; |
| 274 | + int err; |
| 275 | + |
| 276 | + err = get_user(flags, argp); |
| 277 | + if (!err) { |
| 278 | + err = mnt_want_write_file(file); |
| 279 | + if (!err) { |
| 280 | + fileattr_fill_flags(&fa, flags); |
| 281 | + err = vfs_fileattr_set(idmap, dentry, &fa); |
| 282 | + mnt_drop_write_file(file); |
| 283 | + } |
| 284 | + } |
| 285 | + return err; |
| 286 | +} |
| 287 | +EXPORT_SYMBOL(ioctl_setflags); |
| 288 | + |
| 289 | +int ioctl_fsgetxattr(struct file *file, void __user *argp) |
| 290 | +{ |
| 291 | + struct fileattr fa = { .fsx_valid = true }; /* hint only */ |
| 292 | + int err; |
| 293 | + |
| 294 | + err = vfs_fileattr_get(file->f_path.dentry, &fa); |
| 295 | + if (!err) |
| 296 | + err = copy_fsxattr_to_user(&fa, argp); |
| 297 | + |
| 298 | + return err; |
| 299 | +} |
| 300 | +EXPORT_SYMBOL(ioctl_fsgetxattr); |
| 301 | + |
| 302 | +int ioctl_fssetxattr(struct file *file, void __user *argp) |
| 303 | +{ |
| 304 | + struct mnt_idmap *idmap = file_mnt_idmap(file); |
| 305 | + struct dentry *dentry = file->f_path.dentry; |
| 306 | + struct fileattr fa; |
| 307 | + int err; |
| 308 | + |
| 309 | + err = copy_fsxattr_from_user(&fa, argp); |
| 310 | + if (!err) { |
| 311 | + err = mnt_want_write_file(file); |
| 312 | + if (!err) { |
| 313 | + err = vfs_fileattr_set(idmap, dentry, &fa); |
| 314 | + mnt_drop_write_file(file); |
| 315 | + } |
| 316 | + } |
| 317 | + return err; |
| 318 | +} |
| 319 | +EXPORT_SYMBOL(ioctl_fssetxattr); |
0 commit comments