| 1 | --- a/fs/Kconfig |
| 2 | +++ b/fs/Kconfig |
| 3 | @@ -121,9 +121,25 @@ config TMPFS |
| 4 | |
| 5 | See <file:Documentation/filesystems/tmpfs.txt> for details. |
| 6 | |
| 7 | +config TMPFS_XATTR |
| 8 | + bool "Tmpfs extended attributes" |
| 9 | + depends on TMPFS |
| 10 | + default n |
| 11 | + help |
| 12 | + Extended attributes are name:value pairs associated with inodes by |
| 13 | + the kernel or by users (see the attr(5) manual page, or visit |
| 14 | + <http://acl.bestbits.at/> for details). |
| 15 | + |
| 16 | + Currently this enables support for the trusted.* and |
| 17 | + security.* namespaces. |
| 18 | + |
| 19 | + If unsure, say N. |
| 20 | + |
| 21 | + You need this for POSIX ACL support on tmpfs. |
| 22 | + |
| 23 | config TMPFS_POSIX_ACL |
| 24 | bool "Tmpfs POSIX Access Control Lists" |
| 25 | - depends on TMPFS |
| 26 | + depends on TMPFS_XATTR |
| 27 | select GENERIC_ACL |
| 28 | help |
| 29 | POSIX Access Control Lists (ACLs) support permissions for users and |
| 30 | --- a/include/linux/shmem_fs.h |
| 31 | +++ b/include/linux/shmem_fs.h |
| 32 | @@ -9,6 +9,8 @@ |
| 33 | |
| 34 | #define SHMEM_NR_DIRECT 16 |
| 35 | |
| 36 | +#define SHMEM_SYMLINK_INLINE_LEN (SHMEM_NR_DIRECT * sizeof(swp_entry_t)) |
| 37 | + |
| 38 | struct shmem_inode_info { |
| 39 | spinlock_t lock; |
| 40 | unsigned long flags; |
| 41 | @@ -17,8 +19,12 @@ struct shmem_inode_info { |
| 42 | unsigned long next_index; /* highest alloced index + 1 */ |
| 43 | struct shared_policy policy; /* NUMA memory alloc policy */ |
| 44 | struct page *i_indirect; /* top indirect blocks page */ |
| 45 | - swp_entry_t i_direct[SHMEM_NR_DIRECT]; /* first blocks */ |
| 46 | + union { |
| 47 | + swp_entry_t i_direct[SHMEM_NR_DIRECT]; /* first blocks */ |
| 48 | + char inline_symlink[SHMEM_SYMLINK_INLINE_LEN]; |
| 49 | + }; |
| 50 | struct list_head swaplist; /* chain of maybes on swap */ |
| 51 | + struct list_head xattr_list; /* list of shmem_xattr */ |
| 52 | struct inode vfs_inode; |
| 53 | }; |
| 54 | |
| 55 | --- a/mm/shmem.c |
| 56 | +++ b/mm/shmem.c |
| 57 | @@ -99,6 +99,13 @@ static struct vfsmount *shm_mnt; |
| 58 | /* Pretend that each entry is of this size in directory's i_size */ |
| 59 | #define BOGO_DIRENT_SIZE 20 |
| 60 | |
| 61 | +struct shmem_xattr { |
| 62 | + struct list_head list; /* anchored by shmem_inode_info->xattr_list */ |
| 63 | + char *name; /* xattr name */ |
| 64 | + size_t size; |
| 65 | + char value[0]; |
| 66 | +}; |
| 67 | + |
| 68 | /* Flag allocation requirements to shmem_getpage and shmem_swp_alloc */ |
| 69 | enum sgp_type { |
| 70 | SGP_READ, /* don't exceed i_size, don't allocate page */ |
| 71 | @@ -822,6 +829,7 @@ static int shmem_notify_change(struct de |
| 72 | static void shmem_evict_inode(struct inode *inode) |
| 73 | { |
| 74 | struct shmem_inode_info *info = SHMEM_I(inode); |
| 75 | + struct shmem_xattr *xattr, *nxattr; |
| 76 | |
| 77 | if (inode->i_mapping->a_ops == &shmem_aops) { |
| 78 | truncate_inode_pages(inode->i_mapping, 0); |
| 79 | @@ -834,6 +842,11 @@ static void shmem_evict_inode(struct ino |
| 80 | mutex_unlock(&shmem_swaplist_mutex); |
| 81 | } |
| 82 | } |
| 83 | + |
| 84 | + list_for_each_entry_safe(xattr, nxattr, &info->xattr_list, list) { |
| 85 | + kfree(xattr->name); |
| 86 | + kfree(xattr); |
| 87 | + } |
| 88 | BUG_ON(inode->i_blocks); |
| 89 | shmem_free_inode(inode->i_sb); |
| 90 | end_writeback(inode); |
| 91 | @@ -1615,6 +1628,7 @@ static struct inode *shmem_get_inode(str |
| 92 | spin_lock_init(&info->lock); |
| 93 | info->flags = flags & VM_NORESERVE; |
| 94 | INIT_LIST_HEAD(&info->swaplist); |
| 95 | + INIT_LIST_HEAD(&info->xattr_list); |
| 96 | cache_no_acl(inode); |
| 97 | |
| 98 | switch (mode & S_IFMT) { |
| 99 | @@ -2014,9 +2028,9 @@ static int shmem_symlink(struct inode *d |
| 100 | |
| 101 | info = SHMEM_I(inode); |
| 102 | inode->i_size = len-1; |
| 103 | - if (len <= (char *)inode - (char *)info) { |
| 104 | + if (len <= SHMEM_SYMLINK_INLINE_LEN) { |
| 105 | /* do it inline */ |
| 106 | - memcpy(info, symname, len); |
| 107 | + memcpy(info->inline_symlink, symname, len); |
| 108 | inode->i_op = &shmem_symlink_inline_operations; |
| 109 | } else { |
| 110 | error = shmem_getpage(inode, 0, &page, SGP_WRITE, NULL); |
| 111 | @@ -2042,7 +2056,7 @@ static int shmem_symlink(struct inode *d |
| 112 | |
| 113 | static void *shmem_follow_link_inline(struct dentry *dentry, struct nameidata *nd) |
| 114 | { |
| 115 | - nd_set_link(nd, (char *)SHMEM_I(dentry->d_inode)); |
| 116 | + nd_set_link(nd, SHMEM_I(dentry->d_inode)->inline_symlink); |
| 117 | return NULL; |
| 118 | } |
| 119 | |
| 120 | @@ -2066,63 +2080,253 @@ static void shmem_put_link(struct dentry |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | -static const struct inode_operations shmem_symlink_inline_operations = { |
| 125 | - .readlink = generic_readlink, |
| 126 | - .follow_link = shmem_follow_link_inline, |
| 127 | -}; |
| 128 | - |
| 129 | -static const struct inode_operations shmem_symlink_inode_operations = { |
| 130 | - .readlink = generic_readlink, |
| 131 | - .follow_link = shmem_follow_link, |
| 132 | - .put_link = shmem_put_link, |
| 133 | -}; |
| 134 | - |
| 135 | -#ifdef CONFIG_TMPFS_POSIX_ACL |
| 136 | +#ifdef CONFIG_TMPFS_XATTR |
| 137 | /* |
| 138 | - * Superblocks without xattr inode operations will get security.* xattr |
| 139 | - * support from the VFS "for free". As soon as we have any other xattrs |
| 140 | + * Superblocks without xattr inode operations may get some security.* xattr |
| 141 | + * support from the LSM "for free". As soon as we have any other xattrs |
| 142 | * like ACLs, we also need to implement the security.* handlers at |
| 143 | * filesystem level, though. |
| 144 | */ |
| 145 | |
| 146 | -static size_t shmem_xattr_security_list(struct dentry *dentry, char *list, |
| 147 | - size_t list_len, const char *name, |
| 148 | - size_t name_len, int handler_flags) |
| 149 | +static int shmem_xattr_get(struct dentry *dentry, const char *name, |
| 150 | + void *buffer, size_t size) |
| 151 | { |
| 152 | - return security_inode_listsecurity(dentry->d_inode, list, list_len); |
| 153 | -} |
| 154 | + struct shmem_inode_info *info; |
| 155 | + struct shmem_xattr *xattr; |
| 156 | + int ret = -ENODATA; |
| 157 | |
| 158 | -static int shmem_xattr_security_get(struct dentry *dentry, const char *name, |
| 159 | - void *buffer, size_t size, int handler_flags) |
| 160 | -{ |
| 161 | - if (strcmp(name, "") == 0) |
| 162 | - return -EINVAL; |
| 163 | - return xattr_getsecurity(dentry->d_inode, name, buffer, size); |
| 164 | + info = SHMEM_I(dentry->d_inode); |
| 165 | + |
| 166 | + spin_lock(&info->lock); |
| 167 | + list_for_each_entry(xattr, &info->xattr_list, list) { |
| 168 | + if (strcmp(name, xattr->name)) |
| 169 | + continue; |
| 170 | + |
| 171 | + ret = xattr->size; |
| 172 | + if (buffer) { |
| 173 | + if (size < xattr->size) |
| 174 | + ret = -ERANGE; |
| 175 | + else |
| 176 | + memcpy(buffer, xattr->value, xattr->size); |
| 177 | + } |
| 178 | + break; |
| 179 | + } |
| 180 | + spin_unlock(&info->lock); |
| 181 | + return ret; |
| 182 | } |
| 183 | |
| 184 | -static int shmem_xattr_security_set(struct dentry *dentry, const char *name, |
| 185 | - const void *value, size_t size, int flags, int handler_flags) |
| 186 | +static int shmem_xattr_set(struct dentry *dentry, const char *name, |
| 187 | + const void *value, size_t size, int flags) |
| 188 | { |
| 189 | - if (strcmp(name, "") == 0) |
| 190 | - return -EINVAL; |
| 191 | - return security_inode_setsecurity(dentry->d_inode, name, value, |
| 192 | - size, flags); |
| 193 | + struct inode *inode = dentry->d_inode; |
| 194 | + struct shmem_inode_info *info = SHMEM_I(inode); |
| 195 | + struct shmem_xattr *xattr; |
| 196 | + struct shmem_xattr *new_xattr = NULL; |
| 197 | + size_t len; |
| 198 | + int err = 0; |
| 199 | + |
| 200 | + /* value == NULL means remove */ |
| 201 | + if (value) { |
| 202 | + /* wrap around? */ |
| 203 | + len = sizeof(*new_xattr) + size; |
| 204 | + if (len <= sizeof(*new_xattr)) |
| 205 | + return -ENOMEM; |
| 206 | + |
| 207 | + new_xattr = kmalloc(len, GFP_KERNEL); |
| 208 | + if (!new_xattr) |
| 209 | + return -ENOMEM; |
| 210 | + |
| 211 | + new_xattr->name = kstrdup(name, GFP_KERNEL); |
| 212 | + if (!new_xattr->name) { |
| 213 | + kfree(new_xattr); |
| 214 | + return -ENOMEM; |
| 215 | + } |
| 216 | + |
| 217 | + new_xattr->size = size; |
| 218 | + memcpy(new_xattr->value, value, size); |
| 219 | + } |
| 220 | + |
| 221 | + spin_lock(&info->lock); |
| 222 | + list_for_each_entry(xattr, &info->xattr_list, list) { |
| 223 | + if (!strcmp(name, xattr->name)) { |
| 224 | + if (flags & XATTR_CREATE) { |
| 225 | + xattr = new_xattr; |
| 226 | + err = -EEXIST; |
| 227 | + } else if (new_xattr) { |
| 228 | + list_replace(&xattr->list, &new_xattr->list); |
| 229 | + } else { |
| 230 | + list_del(&xattr->list); |
| 231 | + } |
| 232 | + goto out; |
| 233 | + } |
| 234 | + } |
| 235 | + if (flags & XATTR_REPLACE) { |
| 236 | + xattr = new_xattr; |
| 237 | + err = -ENODATA; |
| 238 | + } else { |
| 239 | + list_add(&new_xattr->list, &info->xattr_list); |
| 240 | + xattr = NULL; |
| 241 | + } |
| 242 | +out: |
| 243 | + spin_unlock(&info->lock); |
| 244 | + if (xattr) |
| 245 | + kfree(xattr->name); |
| 246 | + kfree(xattr); |
| 247 | + return err; |
| 248 | } |
| 249 | |
| 250 | -static const struct xattr_handler shmem_xattr_security_handler = { |
| 251 | - .prefix = XATTR_SECURITY_PREFIX, |
| 252 | - .list = shmem_xattr_security_list, |
| 253 | - .get = shmem_xattr_security_get, |
| 254 | - .set = shmem_xattr_security_set, |
| 255 | -}; |
| 256 | |
| 257 | static const struct xattr_handler *shmem_xattr_handlers[] = { |
| 258 | +#ifdef CONFIG_TMPFS_POSIX_ACL |
| 259 | &generic_acl_access_handler, |
| 260 | &generic_acl_default_handler, |
| 261 | - &shmem_xattr_security_handler, |
| 262 | +#endif |
| 263 | NULL |
| 264 | }; |
| 265 | + |
| 266 | +static int shmem_xattr_validate(const char *name) |
| 267 | +{ |
| 268 | + struct { const char *prefix; size_t len; } arr[] = { |
| 269 | + { XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN }, |
| 270 | + { XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN } |
| 271 | + }; |
| 272 | + int i; |
| 273 | + |
| 274 | + for (i = 0; i < ARRAY_SIZE(arr); i++) { |
| 275 | + size_t preflen = arr[i].len; |
| 276 | + if (strncmp(name, arr[i].prefix, preflen) == 0) { |
| 277 | + if (!name[preflen]) |
| 278 | + return -EINVAL; |
| 279 | + return 0; |
| 280 | + } |
| 281 | + } |
| 282 | + return -EOPNOTSUPP; |
| 283 | +} |
| 284 | + |
| 285 | +static ssize_t shmem_getxattr(struct dentry *dentry, const char *name, |
| 286 | + void *buffer, size_t size) |
| 287 | +{ |
| 288 | + int err; |
| 289 | + |
| 290 | + /* |
| 291 | + * If this is a request for a synthetic attribute in the system.* |
| 292 | + * namespace use the generic infrastructure to resolve a handler |
| 293 | + * for it via sb->s_xattr. |
| 294 | + */ |
| 295 | + if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN)) |
| 296 | + return generic_getxattr(dentry, name, buffer, size); |
| 297 | + |
| 298 | + err = shmem_xattr_validate(name); |
| 299 | + if (err) |
| 300 | + return err; |
| 301 | + |
| 302 | + return shmem_xattr_get(dentry, name, buffer, size); |
| 303 | +} |
| 304 | + |
| 305 | +static int shmem_setxattr(struct dentry *dentry, const char *name, |
| 306 | + const void *value, size_t size, int flags) |
| 307 | +{ |
| 308 | + int err; |
| 309 | + |
| 310 | + /* |
| 311 | + * If this is a request for a synthetic attribute in the system.* |
| 312 | + * namespace use the generic infrastructure to resolve a handler |
| 313 | + * for it via sb->s_xattr. |
| 314 | + */ |
| 315 | + if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN)) |
| 316 | + return generic_setxattr(dentry, name, value, size, flags); |
| 317 | + |
| 318 | + err = shmem_xattr_validate(name); |
| 319 | + if (err) |
| 320 | + return err; |
| 321 | + |
| 322 | + if (size == 0) |
| 323 | + value = ""; /* empty EA, do not remove */ |
| 324 | + |
| 325 | + return shmem_xattr_set(dentry, name, value, size, flags); |
| 326 | + |
| 327 | +} |
| 328 | + |
| 329 | +static int shmem_removexattr(struct dentry *dentry, const char *name) |
| 330 | +{ |
| 331 | + int err; |
| 332 | + |
| 333 | + /* |
| 334 | + * If this is a request for a synthetic attribute in the system.* |
| 335 | + * namespace use the generic infrastructure to resolve a handler |
| 336 | + * for it via sb->s_xattr. |
| 337 | + */ |
| 338 | + if (!strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN)) |
| 339 | + return generic_removexattr(dentry, name); |
| 340 | + |
| 341 | + err = shmem_xattr_validate(name); |
| 342 | + if (err) |
| 343 | + return err; |
| 344 | + |
| 345 | + return shmem_xattr_set(dentry, name, NULL, 0, XATTR_REPLACE); |
| 346 | +} |
| 347 | + |
| 348 | +static bool xattr_is_trusted(const char *name) |
| 349 | +{ |
| 350 | + return !strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN); |
| 351 | +} |
| 352 | + |
| 353 | +static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size) |
| 354 | +{ |
| 355 | + bool trusted = capable(CAP_SYS_ADMIN); |
| 356 | + struct shmem_xattr *xattr; |
| 357 | + struct shmem_inode_info *info; |
| 358 | + size_t used = 0; |
| 359 | + |
| 360 | + info = SHMEM_I(dentry->d_inode); |
| 361 | + |
| 362 | + spin_lock(&info->lock); |
| 363 | + list_for_each_entry(xattr, &info->xattr_list, list) { |
| 364 | + size_t len; |
| 365 | + |
| 366 | + /* skip "trusted." attributes for unprivileged callers */ |
| 367 | + if (!trusted && xattr_is_trusted(xattr->name)) |
| 368 | + continue; |
| 369 | + |
| 370 | + len = strlen(xattr->name) + 1; |
| 371 | + used += len; |
| 372 | + if (buffer) { |
| 373 | + if (size < used) { |
| 374 | + used = -ERANGE; |
| 375 | + break; |
| 376 | + } |
| 377 | + memcpy(buffer, xattr->name, len); |
| 378 | + buffer += len; |
| 379 | + } |
| 380 | + } |
| 381 | + spin_unlock(&info->lock); |
| 382 | + |
| 383 | + return used; |
| 384 | +} |
| 385 | +#endif /* CONFIG_TMPFS_XATTR */ |
| 386 | + |
| 387 | +static const struct inode_operations shmem_symlink_inline_operations = { |
| 388 | + .readlink = generic_readlink, |
| 389 | + .follow_link = shmem_follow_link_inline, |
| 390 | +#ifdef CONFIG_TMPFS_XATTR |
| 391 | + .setxattr = shmem_setxattr, |
| 392 | + .getxattr = shmem_getxattr, |
| 393 | + .listxattr = shmem_listxattr, |
| 394 | + .removexattr = shmem_removexattr, |
| 395 | +#endif |
| 396 | +}; |
| 397 | + |
| 398 | +static const struct inode_operations shmem_symlink_inode_operations = { |
| 399 | + .readlink = generic_readlink, |
| 400 | + .follow_link = shmem_follow_link, |
| 401 | + .put_link = shmem_put_link, |
| 402 | +#ifdef CONFIG_TMPFS_XATTR |
| 403 | + .setxattr = shmem_setxattr, |
| 404 | + .getxattr = shmem_getxattr, |
| 405 | + .listxattr = shmem_listxattr, |
| 406 | + .removexattr = shmem_removexattr, |
| 407 | #endif |
| 408 | +}; |
| 409 | |
| 410 | static struct dentry *shmem_get_parent(struct dentry *child) |
| 411 | { |
| 412 | @@ -2402,8 +2606,10 @@ int shmem_fill_super(struct super_block |
| 413 | sb->s_magic = TMPFS_MAGIC; |
| 414 | sb->s_op = &shmem_ops; |
| 415 | sb->s_time_gran = 1; |
| 416 | -#ifdef CONFIG_TMPFS_POSIX_ACL |
| 417 | +#ifdef CONFIG_TMPFS_XATTR |
| 418 | sb->s_xattr = shmem_xattr_handlers; |
| 419 | +#endif |
| 420 | +#ifdef CONFIG_TMPFS_POSIX_ACL |
| 421 | sb->s_flags |= MS_POSIXACL; |
| 422 | #endif |
| 423 | |
| 424 | @@ -2501,11 +2707,13 @@ static const struct file_operations shme |
| 425 | static const struct inode_operations shmem_inode_operations = { |
| 426 | .setattr = shmem_notify_change, |
| 427 | .truncate_range = shmem_truncate_range, |
| 428 | +#ifdef CONFIG_TMPFS_XATTR |
| 429 | + .setxattr = shmem_setxattr, |
| 430 | + .getxattr = shmem_getxattr, |
| 431 | + .listxattr = shmem_listxattr, |
| 432 | + .removexattr = shmem_removexattr, |
| 433 | +#endif |
| 434 | #ifdef CONFIG_TMPFS_POSIX_ACL |
| 435 | - .setxattr = generic_setxattr, |
| 436 | - .getxattr = generic_getxattr, |
| 437 | - .listxattr = generic_listxattr, |
| 438 | - .removexattr = generic_removexattr, |
| 439 | .check_acl = generic_check_acl, |
| 440 | #endif |
| 441 | |
| 442 | @@ -2523,23 +2731,27 @@ static const struct inode_operations shm |
| 443 | .mknod = shmem_mknod, |
| 444 | .rename = shmem_rename, |
| 445 | #endif |
| 446 | +#ifdef CONFIG_TMPFS_XATTR |
| 447 | + .setxattr = shmem_setxattr, |
| 448 | + .getxattr = shmem_getxattr, |
| 449 | + .listxattr = shmem_listxattr, |
| 450 | + .removexattr = shmem_removexattr, |
| 451 | +#endif |
| 452 | #ifdef CONFIG_TMPFS_POSIX_ACL |
| 453 | .setattr = shmem_notify_change, |
| 454 | - .setxattr = generic_setxattr, |
| 455 | - .getxattr = generic_getxattr, |
| 456 | - .listxattr = generic_listxattr, |
| 457 | - .removexattr = generic_removexattr, |
| 458 | .check_acl = generic_check_acl, |
| 459 | #endif |
| 460 | }; |
| 461 | |
| 462 | static const struct inode_operations shmem_special_inode_operations = { |
| 463 | +#ifdef CONFIG_TMPFS_XATTR |
| 464 | + .setxattr = shmem_setxattr, |
| 465 | + .getxattr = shmem_getxattr, |
| 466 | + .listxattr = shmem_listxattr, |
| 467 | + .removexattr = shmem_removexattr, |
| 468 | +#endif |
| 469 | #ifdef CONFIG_TMPFS_POSIX_ACL |
| 470 | .setattr = shmem_notify_change, |
| 471 | - .setxattr = generic_setxattr, |
| 472 | - .getxattr = generic_getxattr, |
| 473 | - .listxattr = generic_listxattr, |
| 474 | - .removexattr = generic_removexattr, |
| 475 | .check_acl = generic_check_acl, |
| 476 | #endif |
| 477 | }; |
| 478 | |