Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/netbsd-2-0]: src/sys/nfs Pull up revision 1.135 (requested by yamt in ti...



details:   https://anonhg.NetBSD.org/src/rev/3ef9796fb052
branches:  netbsd-2-0
changeset: 562664:3ef9796fb052
user:      he <he%NetBSD.org@localhost>
date:      Sat Sep 18 19:21:45 2004 +0000

description:
Pull up revision 1.135 (requested by yamt in ticket #858):
  Fix access-after-free bugs in dircache code by reference
  counting nfsdircache.  Fixes PR#26864.

diffstat:

 sys/nfs/nfs_subs.c |  189 ++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 145 insertions(+), 44 deletions(-)

diffs (truncated from 326 to 300 lines):

diff -r 0383a7d9900a -r 3ef9796fb052 sys/nfs/nfs_subs.c
--- a/sys/nfs/nfs_subs.c        Sat Sep 18 19:21:23 2004 +0000
+++ b/sys/nfs/nfs_subs.c        Sat Sep 18 19:21:45 2004 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: nfs_subs.c,v 1.132.2.1 2004/06/21 10:15:57 tron Exp $  */
+/*     $NetBSD: nfs_subs.c,v 1.132.2.2 2004/09/18 19:21:45 he Exp $    */
 
 /*
  * Copyright (c) 1989, 1993
@@ -70,7 +70,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nfs_subs.c,v 1.132.2.1 2004/06/21 10:15:57 tron Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nfs_subs.c,v 1.132.2.2 2004/09/18 19:21:45 he Exp $");
 
 #include "fs_nfs.h"
 #include "opt_nfs.h"
@@ -1214,19 +1214,32 @@
        return sum;
 }
 
+#define        _NFSDC_MTX(np)          (&NFSTOV(np)->v_interlock)
+#define        NFSDC_LOCK(np)          simple_lock(_NFSDC_MTX(np))
+#define        NFSDC_UNLOCK(np)        simple_unlock(_NFSDC_MTX(np))
+#define        NFSDC_ASSERT_LOCKED(np) LOCK_ASSERT(simple_lock_held(_NFSDC_MTX(np)))
+
 void
 nfs_initdircache(vp)
        struct vnode *vp;
 {
        struct nfsnode *np = VTONFS(vp);
+       struct nfsdirhashhead *dircache;
 
-       KASSERT(np->n_dircache == NULL);
+       dircache = hashinit(NFS_DIRHASHSIZ, HASH_LIST, M_NFSDIROFF,
+           M_WAITOK, &nfsdirhashmask);
 
-       np->n_dircachesize = 0;
-       np->n_dblkno = 1;
-       np->n_dircache = hashinit(NFS_DIRHASHSIZ, HASH_LIST, M_NFSDIROFF,
-           M_WAITOK, &nfsdirhashmask);
-       TAILQ_INIT(&np->n_dirchain);
+       NFSDC_LOCK(np);
+       if (np->n_dircache == NULL) {
+               np->n_dircachesize = 0;
+               np->n_dblkno = 1;
+               np->n_dircache = dircache;
+               dircache = NULL;
+               TAILQ_INIT(&np->n_dirchain);
+       }
+       NFSDC_UNLOCK(np);
+       if (dircache)
+               hashdone(dircache, M_NFSDIROFF);
 }
 
 void
@@ -1234,16 +1247,83 @@
        struct vnode *vp;
 {
        struct nfsnode *np = VTONFS(vp);
+       unsigned *dirgens;
 
        KASSERT(VFSTONFS(vp->v_mount)->nm_flag & NFSMNT_XLATECOOKIE);
-       KASSERT(np->n_dirgens == NULL);
 
-       MALLOC(np->n_dirgens, unsigned *,
-           NFS_DIRHASHSIZ * sizeof (unsigned), M_NFSDIROFF, M_WAITOK);
-       memset((caddr_t)np->n_dirgens, 0, NFS_DIRHASHSIZ * sizeof (unsigned));
+       dirgens = malloc(NFS_DIRHASHSIZ * sizeof (unsigned), M_NFSDIROFF,
+           M_WAITOK|M_ZERO);
+       NFSDC_LOCK(np);
+       if (np->n_dirgens == NULL) {
+               np->n_dirgens = dirgens;
+               dirgens = NULL;
+       }
+       NFSDC_UNLOCK(np);
+       if (dirgens)
+               free(dirgens, M_NFSDIROFF);
 }
 
-static struct nfsdircache dzero = {0, 0, {0, 0}, {0, 0}, 0, 0, 0};
+static const struct nfsdircache dzero;
+
+static void nfs_unlinkdircache __P((struct nfsnode *np, struct nfsdircache *));
+static void nfs_putdircache_unlocked __P((struct nfsnode *,
+    struct nfsdircache *));
+
+static void
+nfs_unlinkdircache(np, ndp)
+       struct nfsnode *np;
+       struct nfsdircache *ndp;
+{
+
+       NFSDC_ASSERT_LOCKED(np);
+       KASSERT(ndp != &dzero);
+
+       if (LIST_NEXT(ndp, dc_hash) == (void *)-1)
+               return;
+
+       TAILQ_REMOVE(&np->n_dirchain, ndp, dc_chain);
+       LIST_REMOVE(ndp, dc_hash);
+       LIST_NEXT(ndp, dc_hash) = (void *)-1; /* mark as unlinked */
+
+       nfs_putdircache_unlocked(np, ndp);
+}
+
+void
+nfs_putdircache(np, ndp)
+       struct nfsnode *np;
+       struct nfsdircache *ndp;
+{
+       int ref;
+
+       if (ndp == &dzero)
+               return;
+
+       KASSERT(ndp->dc_refcnt > 0);
+       NFSDC_LOCK(np);
+       ref = --ndp->dc_refcnt;
+       NFSDC_UNLOCK(np);
+
+       if (ref == 0)
+               free(ndp, M_NFSDIROFF);
+}
+
+static void
+nfs_putdircache_unlocked(np, ndp)
+       struct nfsnode *np;
+       struct nfsdircache *ndp;
+{
+       int ref;
+
+       NFSDC_ASSERT_LOCKED(np);
+
+       if (ndp == &dzero)
+               return;
+
+       KASSERT(ndp->dc_refcnt > 0);
+       ref = --ndp->dc_refcnt;
+       if (ref == 0)
+               free(ndp, M_NFSDIROFF);
+}
 
 struct nfsdircache *
 nfs_searchdircache(vp, off, do32, hashent)
@@ -1261,7 +1341,8 @@
         * Zero is always a valid cookie.
         */
        if (off == 0)
-               return &dzero;
+               /* LINTED const cast away */
+               return (struct nfsdircache *)&dzero;
 
        if (!np->n_dircache)
                return NULL;
@@ -1281,6 +1362,8 @@
 
        if (hashent)
                *hashent = (int)(ndhp - np->n_dircache);
+
+       NFSDC_LOCK(np);
        if (do32) {
                LIST_FOREACH(ndp, ndhp, dc_hash) {
                        if (ndp->dc_cookie32 == (u_int32_t)off) {
@@ -1289,10 +1372,11 @@
                                 * start of a new block fetched from
                                 * the server.
                                 */
-                               if (ndp->dc_blkno == -1) {
+                               if (ndp->dc_flags & NFSDC_INVALID) {
                                        ndp->dc_blkcookie = ndp->dc_cookie;
                                        ndp->dc_blkno = np->n_dblkno++;
                                        ndp->dc_entry = 0;
+                                       ndp->dc_flags &= ~NFSDC_INVALID;
                                }
                                break;
                        }
@@ -1303,6 +1387,9 @@
                                break;
                }
        }
+       if (ndp != NULL)
+               ndp->dc_refcnt++;
+       NFSDC_UNLOCK(np);
        return ndp;
 }
 
@@ -1316,10 +1403,20 @@
 {
        struct nfsnode *np = VTONFS(vp);
        struct nfsdirhashhead *ndhp;
-       struct nfsdircache *ndp = NULL, *first;
+       struct nfsdircache *ndp = NULL;
+       struct nfsdircache *newndp = NULL;
        struct nfsmount *nmp = VFSTONFS(vp->v_mount);
        int hashent, gen, overwrite;
 
+       /*
+        * XXX refuse entries for offset 0. amd(8) erroneously sets
+        * cookie 0 for the '.' entry, making this necessary. This
+        * isn't so bad, as 0 is a special case anyway.
+        */
+       if (off == 0)
+               /* LINTED const cast away */
+               return (struct nfsdircache *)&dzero;
+
        if (!np->n_dircache)
                /*
                 * XXX would like to do this in nfs_nget but vtype
@@ -1330,34 +1427,34 @@
        if ((nmp->nm_flag & NFSMNT_XLATECOOKIE) && !np->n_dirgens)
                nfs_initdirxlatecookie(vp);
 
-       /*
-        * XXX refuse entries for offset 0. amd(8) erroneously sets
-        * cookie 0 for the '.' entry, making this necessary. This
-        * isn't so bad, as 0 is a special case anyway.
-        */
-       if (off == 0)
-               return &dzero;
-
+retry:
        ndp = nfs_searchdircache(vp, off, 0, &hashent);
 
-       if (ndp && ndp->dc_blkno != -1) {
+       NFSDC_LOCK(np);
+       if (ndp && (ndp->dc_flags & NFSDC_INVALID) == 0) {
                /*
                 * Overwriting an old entry. Check if it's the same.
                 * If so, just return. If not, remove the old entry.
                 */
                if (ndp->dc_blkcookie == blkoff && ndp->dc_entry == en)
-                       return ndp;
-               TAILQ_REMOVE(&np->n_dirchain, ndp, dc_chain);
-               LIST_REMOVE(ndp, dc_hash);
-               FREE(ndp, M_NFSDIROFF);
-               ndp = 0;
+                       goto done;
+               nfs_unlinkdircache(np, ndp);
+               nfs_putdircache_unlocked(np, ndp);
+               ndp = NULL;
        }
 
        ndhp = &np->n_dircache[hashent];
 
        if (!ndp) {
-               MALLOC(ndp, struct nfsdircache *, sizeof (*ndp), M_NFSDIROFF,
-                   M_WAITOK);
+               if (newndp == NULL) {
+                       NFSDC_UNLOCK(np);
+                       newndp = malloc(sizeof(*ndp), M_NFSDIROFF, M_WAITOK);
+                       newndp->dc_refcnt = 1;
+                       LIST_NEXT(newndp, dc_hash) = (void *)-1;
+                       goto retry;
+               }
+               ndp = newndp;
+               newndp = NULL;
                overwrite = 0;
                if (nmp->nm_flag & NFSMNT_XLATECOOKIE) {
                        /*
@@ -1388,7 +1485,7 @@
        ndp->dc_entry = en;
 
        if (overwrite)
-               return ndp;
+               goto done;
 
        /*
         * If the maximum directory cookie cache size has been reached
@@ -1398,15 +1495,19 @@
         * loss.
         */
        if (np->n_dircachesize == NFS_MAXDIRCACHE) {
-               first = TAILQ_FIRST(&np->n_dirchain);
-               TAILQ_REMOVE(&np->n_dirchain, first, dc_chain);
-               LIST_REMOVE(first, dc_hash);
-               FREE(first, M_NFSDIROFF);
+               nfs_unlinkdircache(np, TAILQ_FIRST(&np->n_dirchain));
        } else
                np->n_dircachesize++;
                
+       KASSERT(ndp->dc_refcnt == 1);
        LIST_INSERT_HEAD(ndhp, ndp, dc_hash);
        TAILQ_INSERT_TAIL(&np->n_dirchain, ndp, dc_chain);
+       ndp->dc_refcnt++;
+done:
+       KASSERT(ndp->dc_refcnt > 0);
+       NFSDC_UNLOCK(np);
+       if (newndp)
+               nfs_putdircache(np, newndp);
        return ndp;
 }
 
@@ -1427,11 +1528,11 @@
        if (!np->n_dircache)
                return;
 
+       NFSDC_LOCK(np);
        if (!(nmp->nm_flag & NFSMNT_XLATECOOKIE) || forcefree) {



Home | Main Index | Thread Index | Old Index