| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * inode.c - part of tracefs, a pseudo file system for activating tracing |
|---|
| 3 | 4 | * |
|---|
| .. | .. |
|---|
| 5 | 6 | * |
|---|
| 6 | 7 | * Copyright (C) 2014 Red Hat Inc, author: Steven Rostedt <srostedt@redhat.com> |
|---|
| 7 | 8 | * |
|---|
| 8 | | - * This program is free software; you can redistribute it and/or |
|---|
| 9 | | - * modify it under the terms of the GNU General Public License version |
|---|
| 10 | | - * 2 as published by the Free Software Foundation. |
|---|
| 11 | | - * |
|---|
| 12 | 9 | * tracefs is the file system that is used by the tracing infrastructure. |
|---|
| 13 | | - * |
|---|
| 14 | 10 | */ |
|---|
| 15 | 11 | |
|---|
| 16 | 12 | #include <linux/module.h> |
|---|
| .. | .. |
|---|
| 20 | 16 | #include <linux/namei.h> |
|---|
| 21 | 17 | #include <linux/tracefs.h> |
|---|
| 22 | 18 | #include <linux/fsnotify.h> |
|---|
| 19 | +#include <linux/security.h> |
|---|
| 23 | 20 | #include <linux/seq_file.h> |
|---|
| 24 | 21 | #include <linux/parser.h> |
|---|
| 25 | 22 | #include <linux/magic.h> |
|---|
| .. | .. |
|---|
| 142 | 139 | kuid_t uid; |
|---|
| 143 | 140 | kgid_t gid; |
|---|
| 144 | 141 | umode_t mode; |
|---|
| 142 | + /* Opt_* bitfield. */ |
|---|
| 143 | + unsigned int opts; |
|---|
| 145 | 144 | }; |
|---|
| 146 | 145 | |
|---|
| 147 | 146 | enum { |
|---|
| .. | .. |
|---|
| 199 | 198 | |
|---|
| 200 | 199 | if (!list_empty(&dentry->d_subdirs)) { |
|---|
| 201 | 200 | spin_unlock(&this_parent->d_lock); |
|---|
| 202 | | - spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_); |
|---|
| 201 | + spin_release(&dentry->d_lock.dep_map, _RET_IP_); |
|---|
| 203 | 202 | this_parent = dentry; |
|---|
| 204 | 203 | spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_); |
|---|
| 205 | 204 | goto repeat; |
|---|
| .. | .. |
|---|
| 242 | 241 | kgid_t gid; |
|---|
| 243 | 242 | char *p; |
|---|
| 244 | 243 | |
|---|
| 244 | + opts->opts = 0; |
|---|
| 245 | 245 | opts->mode = TRACEFS_DEFAULT_MODE; |
|---|
| 246 | 246 | |
|---|
| 247 | 247 | while ((p = strsep(&data, ",")) != NULL) { |
|---|
| .. | .. |
|---|
| 276 | 276 | * but traditionally tracefs has ignored all mount options |
|---|
| 277 | 277 | */ |
|---|
| 278 | 278 | } |
|---|
| 279 | + |
|---|
| 280 | + opts->opts |= BIT(token); |
|---|
| 279 | 281 | } |
|---|
| 280 | 282 | |
|---|
| 281 | 283 | return 0; |
|---|
| 282 | 284 | } |
|---|
| 283 | 285 | |
|---|
| 284 | | -static int tracefs_apply_options(struct super_block *sb) |
|---|
| 286 | +static int tracefs_apply_options(struct super_block *sb, bool remount) |
|---|
| 285 | 287 | { |
|---|
| 286 | 288 | struct tracefs_fs_info *fsi = sb->s_fs_info; |
|---|
| 287 | 289 | struct inode *inode = sb->s_root->d_inode; |
|---|
| 288 | 290 | struct tracefs_mount_opts *opts = &fsi->mount_opts; |
|---|
| 289 | 291 | |
|---|
| 290 | | - inode->i_mode &= ~S_IALLUGO; |
|---|
| 291 | | - inode->i_mode |= opts->mode; |
|---|
| 292 | + /* |
|---|
| 293 | + * On remount, only reset mode/uid/gid if they were provided as mount |
|---|
| 294 | + * options. |
|---|
| 295 | + */ |
|---|
| 292 | 296 | |
|---|
| 293 | | - inode->i_uid = opts->uid; |
|---|
| 297 | + if (!remount || opts->opts & BIT(Opt_mode)) { |
|---|
| 298 | + inode->i_mode &= ~S_IALLUGO; |
|---|
| 299 | + inode->i_mode |= opts->mode; |
|---|
| 300 | + } |
|---|
| 294 | 301 | |
|---|
| 295 | | - /* Set all the group ids to the mount option */ |
|---|
| 296 | | - set_gid(sb->s_root, opts->gid); |
|---|
| 302 | + if (!remount || opts->opts & BIT(Opt_uid)) |
|---|
| 303 | + inode->i_uid = opts->uid; |
|---|
| 304 | + |
|---|
| 305 | + if (!remount || opts->opts & BIT(Opt_gid)) { |
|---|
| 306 | + /* Set all the group ids to the mount option */ |
|---|
| 307 | + set_gid(sb->s_root, opts->gid); |
|---|
| 308 | + } |
|---|
| 297 | 309 | |
|---|
| 298 | 310 | return 0; |
|---|
| 299 | 311 | } |
|---|
| .. | .. |
|---|
| 308 | 320 | if (err) |
|---|
| 309 | 321 | goto fail; |
|---|
| 310 | 322 | |
|---|
| 311 | | - tracefs_apply_options(sb); |
|---|
| 323 | + tracefs_apply_options(sb, true); |
|---|
| 312 | 324 | |
|---|
| 313 | 325 | fail: |
|---|
| 314 | 326 | return err; |
|---|
| .. | .. |
|---|
| 360 | 372 | |
|---|
| 361 | 373 | sb->s_op = &tracefs_super_operations; |
|---|
| 362 | 374 | |
|---|
| 363 | | - tracefs_apply_options(sb); |
|---|
| 375 | + tracefs_apply_options(sb, false); |
|---|
| 364 | 376 | |
|---|
| 365 | 377 | return 0; |
|---|
| 366 | 378 | |
|---|
| .. | .. |
|---|
| 406 | 418 | parent = tracefs_mount->mnt_root; |
|---|
| 407 | 419 | |
|---|
| 408 | 420 | inode_lock(parent->d_inode); |
|---|
| 409 | | - dentry = lookup_one_len(name, parent, strlen(name)); |
|---|
| 421 | + if (unlikely(IS_DEADDIR(parent->d_inode))) |
|---|
| 422 | + dentry = ERR_PTR(-ENOENT); |
|---|
| 423 | + else |
|---|
| 424 | + dentry = lookup_one_len(name, parent, strlen(name)); |
|---|
| 410 | 425 | if (!IS_ERR(dentry) && dentry->d_inode) { |
|---|
| 411 | 426 | dput(dentry); |
|---|
| 412 | 427 | dentry = ERR_PTR(-EEXIST); |
|---|
| .. | .. |
|---|
| 466 | 481 | { |
|---|
| 467 | 482 | struct dentry *dentry; |
|---|
| 468 | 483 | struct inode *inode; |
|---|
| 484 | + |
|---|
| 485 | + if (security_locked_down(LOCKDOWN_TRACEFS)) |
|---|
| 486 | + return NULL; |
|---|
| 469 | 487 | |
|---|
| 470 | 488 | if (!(mode & S_IFMT)) |
|---|
| 471 | 489 | mode |= S_IFREG; |
|---|
| .. | .. |
|---|
| 535 | 553 | */ |
|---|
| 536 | 554 | struct dentry *tracefs_create_dir(const char *name, struct dentry *parent) |
|---|
| 537 | 555 | { |
|---|
| 556 | + if (security_locked_down(LOCKDOWN_TRACEFS)) |
|---|
| 557 | + return NULL; |
|---|
| 558 | + |
|---|
| 538 | 559 | return __create_dir(name, parent, &simple_dir_inode_operations); |
|---|
| 539 | 560 | } |
|---|
| 540 | 561 | |
|---|
| .. | .. |
|---|
| 576 | 597 | return dentry; |
|---|
| 577 | 598 | } |
|---|
| 578 | 599 | |
|---|
| 579 | | -static int __tracefs_remove(struct dentry *dentry, struct dentry *parent) |
|---|
| 600 | +static void remove_one(struct dentry *victim) |
|---|
| 580 | 601 | { |
|---|
| 581 | | - int ret = 0; |
|---|
| 582 | | - |
|---|
| 583 | | - if (simple_positive(dentry)) { |
|---|
| 584 | | - if (dentry->d_inode) { |
|---|
| 585 | | - dget(dentry); |
|---|
| 586 | | - switch (dentry->d_inode->i_mode & S_IFMT) { |
|---|
| 587 | | - case S_IFDIR: |
|---|
| 588 | | - ret = simple_rmdir(parent->d_inode, dentry); |
|---|
| 589 | | - break; |
|---|
| 590 | | - default: |
|---|
| 591 | | - simple_unlink(parent->d_inode, dentry); |
|---|
| 592 | | - break; |
|---|
| 593 | | - } |
|---|
| 594 | | - if (!ret) |
|---|
| 595 | | - d_delete(dentry); |
|---|
| 596 | | - dput(dentry); |
|---|
| 597 | | - } |
|---|
| 598 | | - } |
|---|
| 599 | | - return ret; |
|---|
| 602 | + simple_release_fs(&tracefs_mount, &tracefs_mount_count); |
|---|
| 600 | 603 | } |
|---|
| 601 | 604 | |
|---|
| 602 | 605 | /** |
|---|
| 603 | | - * tracefs_remove - removes a file or directory from the tracefs filesystem |
|---|
| 604 | | - * @dentry: a pointer to a the dentry of the file or directory to be |
|---|
| 605 | | - * removed. |
|---|
| 606 | | - * |
|---|
| 607 | | - * This function removes a file or directory in tracefs that was previously |
|---|
| 608 | | - * created with a call to another tracefs function (like |
|---|
| 609 | | - * tracefs_create_file() or variants thereof.) |
|---|
| 610 | | - */ |
|---|
| 611 | | -void tracefs_remove(struct dentry *dentry) |
|---|
| 612 | | -{ |
|---|
| 613 | | - struct dentry *parent; |
|---|
| 614 | | - int ret; |
|---|
| 615 | | - |
|---|
| 616 | | - if (IS_ERR_OR_NULL(dentry)) |
|---|
| 617 | | - return; |
|---|
| 618 | | - |
|---|
| 619 | | - parent = dentry->d_parent; |
|---|
| 620 | | - inode_lock(parent->d_inode); |
|---|
| 621 | | - ret = __tracefs_remove(dentry, parent); |
|---|
| 622 | | - inode_unlock(parent->d_inode); |
|---|
| 623 | | - if (!ret) |
|---|
| 624 | | - simple_release_fs(&tracefs_mount, &tracefs_mount_count); |
|---|
| 625 | | -} |
|---|
| 626 | | - |
|---|
| 627 | | -/** |
|---|
| 628 | | - * tracefs_remove_recursive - recursively removes a directory |
|---|
| 606 | + * tracefs_remove - recursively removes a directory |
|---|
| 629 | 607 | * @dentry: a pointer to a the dentry of the directory to be removed. |
|---|
| 630 | 608 | * |
|---|
| 631 | 609 | * This function recursively removes a directory tree in tracefs that |
|---|
| 632 | 610 | * was previously created with a call to another tracefs function |
|---|
| 633 | 611 | * (like tracefs_create_file() or variants thereof.) |
|---|
| 634 | 612 | */ |
|---|
| 635 | | -void tracefs_remove_recursive(struct dentry *dentry) |
|---|
| 613 | +void tracefs_remove(struct dentry *dentry) |
|---|
| 636 | 614 | { |
|---|
| 637 | | - struct dentry *child, *parent; |
|---|
| 638 | | - |
|---|
| 639 | 615 | if (IS_ERR_OR_NULL(dentry)) |
|---|
| 640 | 616 | return; |
|---|
| 641 | 617 | |
|---|
| 642 | | - parent = dentry; |
|---|
| 643 | | - down: |
|---|
| 644 | | - inode_lock(parent->d_inode); |
|---|
| 645 | | - loop: |
|---|
| 646 | | - /* |
|---|
| 647 | | - * The parent->d_subdirs is protected by the d_lock. Outside that |
|---|
| 648 | | - * lock, the child can be unlinked and set to be freed which can |
|---|
| 649 | | - * use the d_u.d_child as the rcu head and corrupt this list. |
|---|
| 650 | | - */ |
|---|
| 651 | | - spin_lock(&parent->d_lock); |
|---|
| 652 | | - list_for_each_entry(child, &parent->d_subdirs, d_child) { |
|---|
| 653 | | - if (!simple_positive(child)) |
|---|
| 654 | | - continue; |
|---|
| 655 | | - |
|---|
| 656 | | - /* perhaps simple_empty(child) makes more sense */ |
|---|
| 657 | | - if (!list_empty(&child->d_subdirs)) { |
|---|
| 658 | | - spin_unlock(&parent->d_lock); |
|---|
| 659 | | - inode_unlock(parent->d_inode); |
|---|
| 660 | | - parent = child; |
|---|
| 661 | | - goto down; |
|---|
| 662 | | - } |
|---|
| 663 | | - |
|---|
| 664 | | - spin_unlock(&parent->d_lock); |
|---|
| 665 | | - |
|---|
| 666 | | - if (!__tracefs_remove(child, parent)) |
|---|
| 667 | | - simple_release_fs(&tracefs_mount, &tracefs_mount_count); |
|---|
| 668 | | - |
|---|
| 669 | | - /* |
|---|
| 670 | | - * The parent->d_lock protects agaist child from unlinking |
|---|
| 671 | | - * from d_subdirs. When releasing the parent->d_lock we can |
|---|
| 672 | | - * no longer trust that the next pointer is valid. |
|---|
| 673 | | - * Restart the loop. We'll skip this one with the |
|---|
| 674 | | - * simple_positive() check. |
|---|
| 675 | | - */ |
|---|
| 676 | | - goto loop; |
|---|
| 677 | | - } |
|---|
| 678 | | - spin_unlock(&parent->d_lock); |
|---|
| 679 | | - |
|---|
| 680 | | - inode_unlock(parent->d_inode); |
|---|
| 681 | | - child = parent; |
|---|
| 682 | | - parent = parent->d_parent; |
|---|
| 683 | | - inode_lock(parent->d_inode); |
|---|
| 684 | | - |
|---|
| 685 | | - if (child != dentry) |
|---|
| 686 | | - /* go up */ |
|---|
| 687 | | - goto loop; |
|---|
| 688 | | - |
|---|
| 689 | | - if (!__tracefs_remove(child, parent)) |
|---|
| 690 | | - simple_release_fs(&tracefs_mount, &tracefs_mount_count); |
|---|
| 691 | | - inode_unlock(parent->d_inode); |
|---|
| 618 | + simple_pin_fs(&trace_fs_type, &tracefs_mount, &tracefs_mount_count); |
|---|
| 619 | + simple_recursive_removal(dentry, remove_one); |
|---|
| 620 | + simple_release_fs(&tracefs_mount, &tracefs_mount_count); |
|---|
| 692 | 621 | } |
|---|
| 693 | 622 | |
|---|
| 694 | 623 | /** |
|---|