]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - fs/ceph/dir.c
ceph: avoid dereferencing invalid pointer during cached readdir
[mirror_ubuntu-bionic-kernel.git] / fs / ceph / dir.c
index cd52a0b109fb04f470dbbdb85923f2960c1accc0..60ee531eece400887fc52a52a2596821d5b1382d 100644 (file)
@@ -231,11 +231,17 @@ static int __dcache_readdir(struct file *file,  struct dir_context *ctx,
                        goto out;
                }
 
-               di = ceph_dentry(dentry);
                spin_lock(&dentry->d_lock);
-               if (di->lease_shared_gen == shared_gen &&
-                   d_really_is_positive(dentry) &&
-                   fpos_cmp(ctx->pos, di->offset) <= 0) {
+               di = ceph_dentry(dentry);
+               if (d_unhashed(dentry) ||
+                   d_really_is_negative(dentry) ||
+                   di->lease_shared_gen != shared_gen) {
+                       spin_unlock(&dentry->d_lock);
+                       dput(dentry);
+                       err = -EAGAIN;
+                       goto out;
+               }
+               if (fpos_cmp(ctx->pos, di->offset) <= 0) {
                        emit_dentry = true;
                }
                spin_unlock(&dentry->d_lock);
@@ -1332,24 +1338,37 @@ static void ceph_d_release(struct dentry *dentry)
  */
 static void ceph_d_prune(struct dentry *dentry)
 {
-       dout("ceph_d_prune %p\n", dentry);
+       struct ceph_inode_info *dir_ci;
+       struct ceph_dentry_info *di;
+
+       dout("ceph_d_prune %pd %p\n", dentry, dentry);
 
        /* do we have a valid parent? */
        if (IS_ROOT(dentry))
                return;
 
-       /* if we are not hashed, we don't affect dir's completeness */
-       if (d_unhashed(dentry))
+       /* we hold d_lock, so d_parent is stable */
+       dir_ci = ceph_inode(d_inode(dentry->d_parent));
+       if (dir_ci->i_vino.snap == CEPH_SNAPDIR)
                return;
 
-       if (ceph_snap(d_inode(dentry->d_parent)) == CEPH_SNAPDIR)
+       /* who calls d_delete() should also disable dcache readdir */
+       if (d_really_is_negative(dentry))
                return;
 
-       /*
-        * we hold d_lock, so d_parent is stable, and d_fsdata is never
-        * cleared until d_release
-        */
-       ceph_dir_clear_complete(d_inode(dentry->d_parent));
+       /* d_fsdata does not get cleared until d_release */
+       if (!d_unhashed(dentry)) {
+               __ceph_dir_clear_complete(dir_ci);
+               return;
+       }
+
+       /* Disable dcache readdir just in case that someone called d_drop()
+        * or d_invalidate(), but MDS didn't revoke CEPH_CAP_FILE_SHARED
+        * properly (dcache readdir is still enabled) */
+       di = ceph_dentry(dentry);
+       if (di->offset > 0 &&
+           di->lease_shared_gen == atomic_read(&dir_ci->i_shared_gen))
+               __ceph_dir_clear_ordered(dir_ci);
 }
 
 /*