.. | .. |
---|
| 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 | /** |
---|