Print this page
7656 unlinking directory on tmpfs can cause kernel panic

Split Close
Expand all
Collapse all
          --- old/usr/src/uts/common/fs/tmpfs/tmp_dir.c
          +++ new/usr/src/uts/common/fs/tmpfs/tmp_dir.c
↓ open down ↓ 13 lines elided ↑ open up ↑
  14   14   * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15   15   * If applicable, add the following below this CDDL HEADER, with the
  16   16   * fields enclosed by brackets "[]" replaced with your own identifying
  17   17   * information: Portions Copyright [yyyy] [name of copyright owner]
  18   18   *
  19   19   * CDDL HEADER END
  20   20   */
  21   21  /*
  22   22   * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  23   23   * Use is subject to license terms.
       24 + *
       25 + * Copyright 2016 RackTop Systems.
  24   26   */
  25   27  
  26      -#pragma ident   "%Z%%M% %I%     %E% SMI"
  27      -
  28   28  #include <sys/types.h>
  29   29  #include <sys/param.h>
  30   30  #include <sys/sysmacros.h>
  31   31  #include <sys/systm.h>
  32   32  #include <sys/time.h>
  33   33  #include <sys/vfs.h>
  34   34  #include <sys/vnode.h>
  35   35  #include <sys/errno.h>
  36   36  #include <sys/cmn_err.h>
  37   37  #include <sys/cred.h>
↓ open down ↓ 480 lines elided ↑ open up ↑
 518  518  
 519  519          tmp_memfree(tpdp, sizeof (struct tdirent) + namelen);
 520  520          dir->tn_size -= (sizeof (struct tdirent) + namelen);
 521  521          dir->tn_dirents--;
 522  522  
 523  523          gethrestime(&now);
 524  524          dir->tn_mtime = now;
 525  525          dir->tn_ctime = now;
 526  526          tp->tn_ctime = now;
 527  527  
      528 +        /*
      529 +         * If this is a _REMOVE (unlink) operation there may
      530 +         * be other links to the directory entry.
      531 +         */
 528  532          ASSERT(tp->tn_nlink > 0);
 529  533          DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
 530      -        if (op == DR_RMDIR && tp->tn_type == VDIR) {
 531      -                tdirtrunc(tp);
 532      -                ASSERT(tp->tn_nlink == 0);
      534 +        if (op == DR_RMDIR || (op == DR_REMOVE && tp->tn_type == VDIR)) {
      535 +                if (tp->tn_nlink > 1) {
      536 +                        ASSERT(op == DR_REMOVE);
      537 +                } else {
      538 +                        tdirtrunc(tp);
      539 +                        ASSERT(tp->tn_nlink == 0);
      540 +                }
 533  541          }
 534  542          return (0);
 535  543  }
 536  544  
 537  545  /*
 538  546   * tdirinit is used internally to initialize a directory (dir)
 539  547   * with '.' and '..' entries without checking permissions and locking
 540  548   */
 541  549  void
 542  550  tdirinit(
↓ open down ↓ 65 lines elided ↑ open up ↑
 608  616   * tdirtrunc is called to remove all directory entries under this directory.
 609  617   */
 610  618  void
 611  619  tdirtrunc(struct tmpnode *dir)
 612  620  {
 613  621          struct tdirent *tdp;
 614  622          struct tmpnode *tp;
 615  623          size_t namelen;
 616  624          timestruc_t now;
 617  625          int isvattrdir, isdotdot, skip_decr;
      626 +        int lock_held;
 618  627  
 619  628          ASSERT(RW_WRITE_HELD(&dir->tn_rwlock));
 620  629          ASSERT(dir->tn_type == VDIR);
 621  630  
 622  631          isvattrdir = (dir->tn_vnode->v_flag & V_XATTRDIR) ? 1 : 0;
 623  632          for (tdp = dir->tn_dir; tdp; tdp = dir->tn_dir) {
 624  633                  ASSERT(tdp->td_next != tdp);
 625  634                  ASSERT(tdp->td_prev != tdp);
 626  635                  ASSERT(tdp->td_tmpnode);
 627  636  
↓ open down ↓ 18 lines elided ↑ open up ↑
 646  655                  if (!skip_decr) {
 647  656                          ASSERT(tp->tn_nlink > 0);
 648  657                          DECR_COUNT(&tp->tn_nlink, &tp->tn_tlock);
 649  658                  }
 650  659  
 651  660                  tmpfs_hash_out(tdp);
 652  661  
 653  662                  tmp_memfree(tdp, sizeof (struct tdirent) + namelen);
 654  663                  dir->tn_size -= (sizeof (struct tdirent) + namelen);
 655  664                  dir->tn_dirents--;
      665 +
      666 +                /*
      667 +                 * This directory entry may itself be a directory with
      668 +                 * entries and removing it may have created orphans.
      669 +                 * On a normal filesystem like UFS this wouldn't be
      670 +                 * a huge problem because fcsk can reclaim them.  For
      671 +                 * TMPFS which resides in RAM however, it means we
      672 +                 * end up leaking memory.
      673 +                 *
      674 +                 * To avoid this we also truncate child directories,
      675 +                 * but only if they have no other links to them.
      676 +                 */
      677 +                if (!isdotdot && tp->tn_type == VDIR && tp != dir) {
      678 +                        if (tp->tn_nlink > 1)
      679 +                                continue;
      680 +                        lock_held = RW_WRITE_HELD(&tp->tn_rwlock);
      681 +                        if (!lock_held)
      682 +                                rw_enter(&tp->tn_rwlock, RW_WRITER);
      683 +                        tdirtrunc(tp);
      684 +                        if (!lock_held)
      685 +                                rw_exit(&tp->tn_rwlock);
      686 +                        ASSERT(tp->tn_nlink == 0);
      687 +                }
 656  688          }
 657  689  
 658  690          gethrestime(&now);
 659  691          dir->tn_mtime = now;
 660  692          dir->tn_ctime = now;
 661  693  
 662  694          ASSERT(dir->tn_dir == NULL);
 663  695          ASSERT(dir->tn_size == 0);
 664  696          ASSERT(dir->tn_dirents == 0);
 665  697  }
↓ open down ↓ 430 lines elided ↑ open up ↑
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX