| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (C) 2008 Red Hat, Inc., Eric Paris <eparis@redhat.com> |
|---|
| 3 | | - * |
|---|
| 4 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 5 | | - * it under the terms of the GNU General Public License as published by |
|---|
| 6 | | - * the Free Software Foundation; either version 2, or (at your option) |
|---|
| 7 | | - * any later version. |
|---|
| 8 | | - * |
|---|
| 9 | | - * This program is distributed in the hope that it will be useful, |
|---|
| 10 | | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 11 | | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 12 | | - * GNU General Public License for more details. |
|---|
| 13 | | - * |
|---|
| 14 | | - * You should have received a copy of the GNU General Public License |
|---|
| 15 | | - * along with this program; see the file COPYING. If not, write to |
|---|
| 16 | | - * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
|---|
| 17 | 4 | */ |
|---|
| 18 | 5 | |
|---|
| 19 | 6 | /* |
|---|
| .. | .. |
|---|
| 82 | 69 | #include <linux/slab.h> |
|---|
| 83 | 70 | #include <linux/spinlock.h> |
|---|
| 84 | 71 | #include <linux/srcu.h> |
|---|
| 72 | +#include <linux/ratelimit.h> |
|---|
| 85 | 73 | |
|---|
| 86 | 74 | #include <linux/atomic.h> |
|---|
| 87 | 75 | |
|---|
| .. | .. |
|---|
| 115 | 103 | return &fsnotify_conn_inode(conn)->i_fsnotify_mask; |
|---|
| 116 | 104 | else if (conn->type == FSNOTIFY_OBJ_TYPE_VFSMOUNT) |
|---|
| 117 | 105 | return &fsnotify_conn_mount(conn)->mnt_fsnotify_mask; |
|---|
| 106 | + else if (conn->type == FSNOTIFY_OBJ_TYPE_SB) |
|---|
| 107 | + return &fsnotify_conn_sb(conn)->s_fsnotify_mask; |
|---|
| 118 | 108 | return NULL; |
|---|
| 119 | 109 | } |
|---|
| 120 | 110 | |
|---|
| .. | .. |
|---|
| 195 | 185 | atomic_long_inc(&inode->i_sb->s_fsnotify_inode_refs); |
|---|
| 196 | 186 | } else if (conn->type == FSNOTIFY_OBJ_TYPE_VFSMOUNT) { |
|---|
| 197 | 187 | fsnotify_conn_mount(conn)->mnt_fsnotify_mask = 0; |
|---|
| 188 | + } else if (conn->type == FSNOTIFY_OBJ_TYPE_SB) { |
|---|
| 189 | + fsnotify_conn_sb(conn)->s_fsnotify_mask = 0; |
|---|
| 198 | 190 | } |
|---|
| 199 | 191 | |
|---|
| 200 | 192 | rcu_assign_pointer(*(conn->obj), NULL); |
|---|
| .. | .. |
|---|
| 234 | 226 | |
|---|
| 235 | 227 | void fsnotify_put_mark(struct fsnotify_mark *mark) |
|---|
| 236 | 228 | { |
|---|
| 237 | | - struct fsnotify_mark_connector *conn; |
|---|
| 229 | + struct fsnotify_mark_connector *conn = READ_ONCE(mark->connector); |
|---|
| 238 | 230 | void *objp = NULL; |
|---|
| 239 | 231 | unsigned int type = FSNOTIFY_OBJ_TYPE_DETACHED; |
|---|
| 240 | 232 | bool free_conn = false; |
|---|
| 241 | 233 | |
|---|
| 242 | 234 | /* Catch marks that were actually never attached to object */ |
|---|
| 243 | | - if (!mark->connector) { |
|---|
| 235 | + if (!conn) { |
|---|
| 244 | 236 | if (refcount_dec_and_test(&mark->refcnt)) |
|---|
| 245 | 237 | fsnotify_final_mark_destroy(mark); |
|---|
| 246 | 238 | return; |
|---|
| .. | .. |
|---|
| 250 | 242 | * We have to be careful so that traversals of obj_list under lock can |
|---|
| 251 | 243 | * safely grab mark reference. |
|---|
| 252 | 244 | */ |
|---|
| 253 | | - if (!refcount_dec_and_lock(&mark->refcnt, &mark->connector->lock)) |
|---|
| 245 | + if (!refcount_dec_and_lock(&mark->refcnt, &conn->lock)) |
|---|
| 254 | 246 | return; |
|---|
| 255 | 247 | |
|---|
| 256 | | - conn = mark->connector; |
|---|
| 257 | 248 | hlist_del_init_rcu(&mark->obj_list); |
|---|
| 258 | 249 | if (hlist_empty(&conn->list)) { |
|---|
| 259 | 250 | objp = fsnotify_detach_connector_from_object(conn, &type); |
|---|
| .. | .. |
|---|
| 261 | 252 | } else { |
|---|
| 262 | 253 | __fsnotify_recalc_mask(conn); |
|---|
| 263 | 254 | } |
|---|
| 264 | | - mark->connector = NULL; |
|---|
| 255 | + WRITE_ONCE(mark->connector, NULL); |
|---|
| 265 | 256 | spin_unlock(&conn->lock); |
|---|
| 266 | 257 | |
|---|
| 267 | 258 | fsnotify_drop_object(type, objp); |
|---|
| .. | .. |
|---|
| 285 | 276 | queue_delayed_work(system_unbound_wq, &reaper_work, |
|---|
| 286 | 277 | FSNOTIFY_REAPER_DELAY); |
|---|
| 287 | 278 | } |
|---|
| 279 | +EXPORT_SYMBOL_GPL(fsnotify_put_mark); |
|---|
| 288 | 280 | |
|---|
| 289 | 281 | /* |
|---|
| 290 | 282 | * Get mark reference when we found the mark via lockless traversal of object |
|---|
| .. | .. |
|---|
| 333 | 325 | } |
|---|
| 334 | 326 | |
|---|
| 335 | 327 | bool fsnotify_prepare_user_wait(struct fsnotify_iter_info *iter_info) |
|---|
| 328 | + __releases(&fsnotify_mark_srcu) |
|---|
| 336 | 329 | { |
|---|
| 337 | 330 | int type; |
|---|
| 338 | 331 | |
|---|
| 339 | 332 | fsnotify_foreach_obj_type(type) { |
|---|
| 340 | 333 | /* This can fail if mark is being removed */ |
|---|
| 341 | | - if (!fsnotify_get_mark_safe(iter_info->marks[type])) |
|---|
| 334 | + if (!fsnotify_get_mark_safe(iter_info->marks[type])) { |
|---|
| 335 | + __release(&fsnotify_mark_srcu); |
|---|
| 342 | 336 | goto fail; |
|---|
| 337 | + } |
|---|
| 343 | 338 | } |
|---|
| 344 | 339 | |
|---|
| 345 | 340 | /* |
|---|
| .. | .. |
|---|
| 358 | 353 | } |
|---|
| 359 | 354 | |
|---|
| 360 | 355 | void fsnotify_finish_user_wait(struct fsnotify_iter_info *iter_info) |
|---|
| 356 | + __acquires(&fsnotify_mark_srcu) |
|---|
| 361 | 357 | { |
|---|
| 362 | 358 | int type; |
|---|
| 363 | 359 | |
|---|
| .. | .. |
|---|
| 434 | 430 | void fsnotify_destroy_mark(struct fsnotify_mark *mark, |
|---|
| 435 | 431 | struct fsnotify_group *group) |
|---|
| 436 | 432 | { |
|---|
| 437 | | - mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); |
|---|
| 433 | + mutex_lock(&group->mark_mutex); |
|---|
| 438 | 434 | fsnotify_detach_mark(mark); |
|---|
| 439 | 435 | mutex_unlock(&group->mark_mutex); |
|---|
| 440 | 436 | fsnotify_free_mark(mark); |
|---|
| 441 | 437 | } |
|---|
| 438 | +EXPORT_SYMBOL_GPL(fsnotify_destroy_mark); |
|---|
| 442 | 439 | |
|---|
| 443 | 440 | /* |
|---|
| 444 | 441 | * Sorting function for lists of fsnotify marks. |
|---|
| .. | .. |
|---|
| 477 | 474 | } |
|---|
| 478 | 475 | |
|---|
| 479 | 476 | static int fsnotify_attach_connector_to_object(fsnotify_connp_t *connp, |
|---|
| 480 | | - unsigned int type) |
|---|
| 477 | + unsigned int type, |
|---|
| 478 | + __kernel_fsid_t *fsid) |
|---|
| 481 | 479 | { |
|---|
| 482 | 480 | struct inode *inode = NULL; |
|---|
| 483 | 481 | struct fsnotify_mark_connector *conn; |
|---|
| .. | .. |
|---|
| 489 | 487 | INIT_HLIST_HEAD(&conn->list); |
|---|
| 490 | 488 | conn->type = type; |
|---|
| 491 | 489 | conn->obj = connp; |
|---|
| 490 | + /* Cache fsid of filesystem containing the object */ |
|---|
| 491 | + if (fsid) { |
|---|
| 492 | + conn->fsid = *fsid; |
|---|
| 493 | + conn->flags = FSNOTIFY_CONN_FLAG_HAS_FSID; |
|---|
| 494 | + } else { |
|---|
| 495 | + conn->fsid.val[0] = conn->fsid.val[1] = 0; |
|---|
| 496 | + conn->flags = 0; |
|---|
| 497 | + } |
|---|
| 492 | 498 | if (conn->type == FSNOTIFY_OBJ_TYPE_INODE) |
|---|
| 493 | 499 | inode = igrab(fsnotify_conn_inode(conn)); |
|---|
| 494 | 500 | /* |
|---|
| .. | .. |
|---|
| 540 | 546 | */ |
|---|
| 541 | 547 | static int fsnotify_add_mark_list(struct fsnotify_mark *mark, |
|---|
| 542 | 548 | fsnotify_connp_t *connp, unsigned int type, |
|---|
| 543 | | - int allow_dups) |
|---|
| 549 | + int allow_dups, __kernel_fsid_t *fsid) |
|---|
| 544 | 550 | { |
|---|
| 545 | 551 | struct fsnotify_mark *lmark, *last = NULL; |
|---|
| 546 | 552 | struct fsnotify_mark_connector *conn; |
|---|
| .. | .. |
|---|
| 549 | 555 | |
|---|
| 550 | 556 | if (WARN_ON(!fsnotify_valid_obj_type(type))) |
|---|
| 551 | 557 | return -EINVAL; |
|---|
| 558 | + |
|---|
| 559 | + /* Backend is expected to check for zero fsid (e.g. tmpfs) */ |
|---|
| 560 | + if (fsid && WARN_ON_ONCE(!fsid->val[0] && !fsid->val[1])) |
|---|
| 561 | + return -ENODEV; |
|---|
| 562 | + |
|---|
| 552 | 563 | restart: |
|---|
| 553 | 564 | spin_lock(&mark->lock); |
|---|
| 554 | 565 | conn = fsnotify_grab_connector(connp); |
|---|
| 555 | 566 | if (!conn) { |
|---|
| 556 | 567 | spin_unlock(&mark->lock); |
|---|
| 557 | | - err = fsnotify_attach_connector_to_object(connp, type); |
|---|
| 568 | + err = fsnotify_attach_connector_to_object(connp, type, fsid); |
|---|
| 558 | 569 | if (err) |
|---|
| 559 | 570 | return err; |
|---|
| 560 | 571 | goto restart; |
|---|
| 572 | + } else if (fsid && !(conn->flags & FSNOTIFY_CONN_FLAG_HAS_FSID)) { |
|---|
| 573 | + conn->fsid = *fsid; |
|---|
| 574 | + /* Pairs with smp_rmb() in fanotify_get_fsid() */ |
|---|
| 575 | + smp_wmb(); |
|---|
| 576 | + conn->flags |= FSNOTIFY_CONN_FLAG_HAS_FSID; |
|---|
| 577 | + } else if (fsid && (conn->flags & FSNOTIFY_CONN_FLAG_HAS_FSID) && |
|---|
| 578 | + (fsid->val[0] != conn->fsid.val[0] || |
|---|
| 579 | + fsid->val[1] != conn->fsid.val[1])) { |
|---|
| 580 | + /* |
|---|
| 581 | + * Backend is expected to check for non uniform fsid |
|---|
| 582 | + * (e.g. btrfs), but maybe we missed something? |
|---|
| 583 | + * Only allow setting conn->fsid once to non zero fsid. |
|---|
| 584 | + * inotify and non-fid fanotify groups do not set nor test |
|---|
| 585 | + * conn->fsid. |
|---|
| 586 | + */ |
|---|
| 587 | + pr_warn_ratelimited("%s: fsid mismatch on object of type %u: " |
|---|
| 588 | + "%x.%x != %x.%x\n", __func__, conn->type, |
|---|
| 589 | + fsid->val[0], fsid->val[1], |
|---|
| 590 | + conn->fsid.val[0], conn->fsid.val[1]); |
|---|
| 591 | + err = -EXDEV; |
|---|
| 592 | + goto out_err; |
|---|
| 561 | 593 | } |
|---|
| 562 | 594 | |
|---|
| 563 | 595 | /* is mark the first mark? */ |
|---|
| .. | .. |
|---|
| 588 | 620 | /* mark should be the last entry. last is the current last entry */ |
|---|
| 589 | 621 | hlist_add_behind_rcu(&mark->obj_list, &last->obj_list); |
|---|
| 590 | 622 | added: |
|---|
| 591 | | - mark->connector = conn; |
|---|
| 623 | + /* |
|---|
| 624 | + * Since connector is attached to object using cmpxchg() we are |
|---|
| 625 | + * guaranteed that connector initialization is fully visible by anyone |
|---|
| 626 | + * seeing mark->connector set. |
|---|
| 627 | + */ |
|---|
| 628 | + WRITE_ONCE(mark->connector, conn); |
|---|
| 592 | 629 | out_err: |
|---|
| 593 | 630 | spin_unlock(&conn->lock); |
|---|
| 594 | 631 | spin_unlock(&mark->lock); |
|---|
| .. | .. |
|---|
| 602 | 639 | */ |
|---|
| 603 | 640 | int fsnotify_add_mark_locked(struct fsnotify_mark *mark, |
|---|
| 604 | 641 | fsnotify_connp_t *connp, unsigned int type, |
|---|
| 605 | | - int allow_dups) |
|---|
| 642 | + int allow_dups, __kernel_fsid_t *fsid) |
|---|
| 606 | 643 | { |
|---|
| 607 | 644 | struct fsnotify_group *group = mark->group; |
|---|
| 608 | 645 | int ret = 0; |
|---|
| .. | .. |
|---|
| 623 | 660 | fsnotify_get_mark(mark); /* for g_list */ |
|---|
| 624 | 661 | spin_unlock(&mark->lock); |
|---|
| 625 | 662 | |
|---|
| 626 | | - ret = fsnotify_add_mark_list(mark, connp, type, allow_dups); |
|---|
| 663 | + ret = fsnotify_add_mark_list(mark, connp, type, allow_dups, fsid); |
|---|
| 627 | 664 | if (ret) |
|---|
| 628 | 665 | goto err; |
|---|
| 629 | 666 | |
|---|
| .. | .. |
|---|
| 644 | 681 | } |
|---|
| 645 | 682 | |
|---|
| 646 | 683 | int fsnotify_add_mark(struct fsnotify_mark *mark, fsnotify_connp_t *connp, |
|---|
| 647 | | - unsigned int type, int allow_dups) |
|---|
| 684 | + unsigned int type, int allow_dups, __kernel_fsid_t *fsid) |
|---|
| 648 | 685 | { |
|---|
| 649 | 686 | int ret; |
|---|
| 650 | 687 | struct fsnotify_group *group = mark->group; |
|---|
| 651 | 688 | |
|---|
| 652 | 689 | mutex_lock(&group->mark_mutex); |
|---|
| 653 | | - ret = fsnotify_add_mark_locked(mark, connp, type, allow_dups); |
|---|
| 690 | + ret = fsnotify_add_mark_locked(mark, connp, type, allow_dups, fsid); |
|---|
| 654 | 691 | mutex_unlock(&group->mark_mutex); |
|---|
| 655 | 692 | return ret; |
|---|
| 656 | 693 | } |
|---|
| 694 | +EXPORT_SYMBOL_GPL(fsnotify_add_mark); |
|---|
| 657 | 695 | |
|---|
| 658 | 696 | /* |
|---|
| 659 | 697 | * Given a list of marks, find the mark associated with given group. If found |
|---|
| .. | .. |
|---|
| 680 | 718 | spin_unlock(&conn->lock); |
|---|
| 681 | 719 | return NULL; |
|---|
| 682 | 720 | } |
|---|
| 721 | +EXPORT_SYMBOL_GPL(fsnotify_find_mark); |
|---|
| 683 | 722 | |
|---|
| 684 | 723 | /* Clear any marks in a group with given type mask */ |
|---|
| 685 | 724 | void fsnotify_clear_marks_by_group(struct fsnotify_group *group, |
|---|
| .. | .. |
|---|
| 703 | 742 | * move marks to free to to_free list in one go and then free marks in |
|---|
| 704 | 743 | * to_free list one by one. |
|---|
| 705 | 744 | */ |
|---|
| 706 | | - mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); |
|---|
| 745 | + mutex_lock(&group->mark_mutex); |
|---|
| 707 | 746 | list_for_each_entry_safe(mark, lmark, &group->marks_list, g_list) { |
|---|
| 708 | 747 | if ((1U << mark->connector->type) & type_mask) |
|---|
| 709 | 748 | list_move(&mark->g_list, &to_free); |
|---|
| .. | .. |
|---|
| 712 | 751 | |
|---|
| 713 | 752 | clear: |
|---|
| 714 | 753 | while (1) { |
|---|
| 715 | | - mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING); |
|---|
| 754 | + mutex_lock(&group->mark_mutex); |
|---|
| 716 | 755 | if (list_empty(head)) { |
|---|
| 717 | 756 | mutex_unlock(&group->mark_mutex); |
|---|
| 718 | 757 | break; |
|---|
| .. | .. |
|---|
| 776 | 815 | refcount_set(&mark->refcnt, 1); |
|---|
| 777 | 816 | fsnotify_get_group(group); |
|---|
| 778 | 817 | mark->group = group; |
|---|
| 818 | + WRITE_ONCE(mark->connector, NULL); |
|---|
| 779 | 819 | } |
|---|
| 820 | +EXPORT_SYMBOL_GPL(fsnotify_init_mark); |
|---|
| 780 | 821 | |
|---|
| 781 | 822 | /* |
|---|
| 782 | 823 | * Destroy all marks in destroy_list, waits for SRCU period to finish before |
|---|
| .. | .. |
|---|
| 805 | 846 | { |
|---|
| 806 | 847 | flush_delayed_work(&reaper_work); |
|---|
| 807 | 848 | } |
|---|
| 849 | +EXPORT_SYMBOL_GPL(fsnotify_wait_marks_destroyed); |
|---|