kernel - Fix kernel panic caused by rename race
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 1 Feb 2013 21:47:37 +0000 (13:47 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 1 Feb 2013 21:47:37 +0000 (13:47 -0800)
* kern_rename() must temporarily unlock the source ncp when resolving the
  target ncp, and will sometimes have to temporarily unlock the target ncp
  when relocking both.  This can race against a topological removal of one
  or both ncp's, resulting in incorrect operation or a panic due to a
  NULL vp.

* Detect the case and return EAGAIN if the ncp's have been marked NCF_DESTROYED
  or if fromncp loses its vp resolution.  Callers detect the EAGAIN and retry
  the operation.

Reported-by: ftigeot
sys/emulation/linux/linux_file.c
sys/kern/vfs_syscalls.c

index c3652ec..1449015 100644 (file)
@@ -803,14 +803,16 @@ sys_linux_rename(struct linux_rename_args *args)
                kprintf(ARGS(rename, "%s, %s"), from, to);
 #endif
        get_mplock();
-       error = nlookup_init(&fromnd, from, UIO_SYSSPACE, 0);
-       if (error == 0) {
-               error = nlookup_init(&tond, to, UIO_SYSSPACE, 0);
-               if (error == 0)
-                       error = kern_rename(&fromnd, &tond);
-               nlookup_done(&tond);
-       }
-       nlookup_done(&fromnd);
+       do {
+               error = nlookup_init(&fromnd, from, UIO_SYSSPACE, 0);
+               if (error == 0) {
+                       error = nlookup_init(&tond, to, UIO_SYSSPACE, 0);
+                       if (error == 0)
+                               error = kern_rename(&fromnd, &tond);
+                       nlookup_done(&tond);
+               }
+               nlookup_done(&fromnd);
+       } while (error == EAGAIN);
        rel_mplock();
        linux_free_path(&from);
        linux_free_path(&to);
index dc144d2..53cf1bf 100644 (file)
@@ -3694,6 +3694,22 @@ kern_rename(struct nlookupdata *fromnd, struct nlookupdata *tond)
        fromnd->nl_flags |= NLC_NCPISLOCKED;
 
        /*
+        * If either fromnd or tond are marked destroyed a ripout occured
+        * out from under us and we must retry.
+        */
+       if ((fromnd->nl_nch.ncp->nc_flag & (NCF_DESTROYED | NCF_UNRESOLVED)) ||
+           fromnd->nl_nch.ncp->nc_vp == NULL ||
+           (tond->nl_nch.ncp->nc_flag & NCF_DESTROYED)) {
+               kprintf("kern_rename: retry due to ripout on: "
+                       "\"%s\" -> \"%s\"\n",
+                       fromnd->nl_nch.ncp->nc_name,
+                       tond->nl_nch.ncp->nc_name);
+               cache_drop(&fnchd);
+               cache_drop(&tnchd);
+               return (EAGAIN);
+       }
+
+       /*
         * make sure the parent directories linkages are the same
         */
        if (fnchd.ncp != fromnd->nl_nch.ncp->nc_parent ||
@@ -3798,14 +3814,16 @@ sys_rename(struct rename_args *uap)
        struct nlookupdata fromnd, tond;
        int error;
 
-       error = nlookup_init(&fromnd, uap->from, UIO_USERSPACE, 0);
-       if (error == 0) {
-               error = nlookup_init(&tond, uap->to, UIO_USERSPACE, 0);
-               if (error == 0)
-                       error = kern_rename(&fromnd, &tond);
-               nlookup_done(&tond);
-       }
-       nlookup_done(&fromnd);
+       do {
+               error = nlookup_init(&fromnd, uap->from, UIO_USERSPACE, 0);
+               if (error == 0) {
+                       error = nlookup_init(&tond, uap->to, UIO_USERSPACE, 0);
+                       if (error == 0)
+                               error = kern_rename(&fromnd, &tond);
+                       nlookup_done(&tond);
+               }
+               nlookup_done(&fromnd);
+       } while (error == EAGAIN);
        return (error);
 }
 
@@ -3823,16 +3841,20 @@ sys_renameat(struct renameat_args *uap)
        struct file *oldfp, *newfp;
        int error;
 
-       error = nlookup_init_at(&oldnd, &oldfp, uap->oldfd, uap->old,
-           UIO_USERSPACE, 0);
-       if (error == 0) {
-               error = nlookup_init_at(&newnd, &newfp, uap->newfd, uap->new,
-                   UIO_USERSPACE, 0);
-               if (error == 0)
-                       error = kern_rename(&oldnd, &newnd);
-               nlookup_done_at(&newnd, newfp);
-       }
-       nlookup_done_at(&oldnd, oldfp);
+       do {
+               error = nlookup_init_at(&oldnd, &oldfp,
+                                       uap->oldfd, uap->old,
+                                       UIO_USERSPACE, 0);
+               if (error == 0) {
+                       error = nlookup_init_at(&newnd, &newfp,
+                                               uap->newfd, uap->new,
+                                               UIO_USERSPACE, 0);
+                       if (error == 0)
+                               error = kern_rename(&oldnd, &newnd);
+                       nlookup_done_at(&newnd, newfp);
+               }
+               nlookup_done_at(&oldnd, oldfp);
+       } while (error == EAGAIN);
        return (error);
 }