linux-symlink-handling-avoid-crash-20070418
authorChristopher Allen Wing <wingc@engin.umich.edu>
Wed, 18 Apr 2007 19:18:35 +0000 (19:18 +0000)
committerDerrick Brashear <shadow@dementia.org>
Wed, 18 Apr 2007 19:18:35 +0000 (19:18 +0000)
Background: OpenAFS is vulnerable to crashing in the linux kernel symlink
code when running on kernel versions between 2.6.10 to 2.6.12.  This also
includes all RHEL4 kernels, because RHEL4 includes the code from 2.6.10. The
problem is that the symlink text caching API, page_follow_link() et al, is
unsuitable for network filesystems where the page cache may be invalidated
in parallel with a path lookup.

This crash can be triggered easily by doing a bunch of path lookups
involving symlinks (e.g., stat() on various files pointed to through links),
while simultaneously running 'fs flushvol' on the volume containing the
symlinks.

The simplest way to fix this problem is to disable the use of symlink text
caching when the kernel does not provide a usable symlink API.

acinclude.m4
src/afs/LINUX/osi_vnodeops.c
src/cf/linux-test4.m4

index 549529f..17799dd 100644 (file)
@@ -606,6 +606,7 @@ case $AFS_SYSNAME in *_linux* | *_umlinux*)
                 LINUX_IOP_I_CREATE_TAKES_NAMEIDATA
                 LINUX_IOP_I_LOOKUP_TAKES_NAMEIDATA
                 LINUX_IOP_I_PERMISSION_TAKES_NAMEIDATA
+                LINUX_IOP_I_PUT_LINK_TAKES_COOKIE
                 LINUX_DOP_D_REVALIDATE_TAKES_NAMEIDATA
                 LINUX_AOP_WRITEBACK_CONTROL
                 LINUX_FS_STRUCT_FOP_HAS_FLOCK
@@ -848,6 +849,11 @@ case $AFS_SYSNAME in *_linux* | *_umlinux*)
                 if test "x$ac_cv_linux_exports_tasklist_lock" = "xyes" ; then
                  AC_DEFINE(EXPORTED_TASKLIST_LOCK, 1, [define if tasklist_lock exported])
                 fi
+                if test "x$ac_cv_linux_kernel_page_follow_link" = "xyes" -o "x$ac_cv_linux_func_i_put_link_takes_cookie" = "xyes"; then
+                 AC_DEFINE(USABLE_KERNEL_PAGE_SYMLINK_CACHE, 1, [define if your kernel has a usable symlink cache API])
+                else
+                 AC_MSG_WARN([your kernel does not have a usable symlink cache API])
+                fi
                 :
                fi
 esac
index 3da1aa6..e115ae4 100644 (file)
@@ -1317,7 +1317,7 @@ afs_linux_ireadlink(struct inode *ip, char *target, int maxlen, uio_seg_t seg)
        return -code;
 }
 
-#if !defined(AFS_LINUX24_ENV)
+#if !defined(USABLE_KERNEL_PAGE_SYMLINK_CACHE)
 /* afs_linux_readlink 
  * Fill target (which is in user space) with contents of symlink.
  */
@@ -1337,6 +1337,36 @@ afs_linux_readlink(struct dentry *dp, char *target, int maxlen)
 /* afs_linux_follow_link
  * a file system dependent link following routine.
  */
+#if defined(AFS_LINUX24_ENV)
+static int afs_linux_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+    int code;
+    char *name;
+
+    name = osi_Alloc(PATH_MAX);
+    if (!name) {
+       return -EIO;
+    }
+
+    AFS_GLOCK();
+    code = afs_linux_ireadlink(dentry->d_inode, name, PATH_MAX - 1, AFS_UIOSYS);
+    AFS_GUNLOCK();
+
+    if (code < 0) {
+       goto out;
+    }
+
+    name[code] = '\0';
+    code = vfs_follow_link(nd, name);
+
+out:
+    osi_Free(name, PATH_MAX);
+
+    return code;
+}
+
+#else /* !defined(AFS_LINUX24_ENV) */
+
 static struct dentry *
 afs_linux_follow_link(struct dentry *dp, struct dentry *basep,
                      unsigned int follow)
@@ -1370,7 +1400,8 @@ afs_linux_follow_link(struct dentry *dp, struct dentry *basep,
     AFS_GUNLOCK();
     return res;
 }
-#endif
+#endif /* AFS_LINUX24_ENV */
+#endif /* USABLE_KERNEL_PAGE_SYMLINK_CACHE */
 
 /* afs_linux_readpage
  * all reads come through here. A strategy-like read call.
@@ -1735,7 +1766,7 @@ static struct inode_operations afs_dir_iops = {
 /* We really need a separate symlink set of ops, since do_follow_link()
  * determines if it _is_ a link by checking if the follow_link op is set.
  */
-#if defined(AFS_LINUX24_ENV)
+#if defined(USABLE_KERNEL_PAGE_SYMLINK_CACHE)
 static int
 afs_symlink_filler(struct file *file, struct page *page)
 {
@@ -1770,10 +1801,10 @@ afs_symlink_filler(struct file *file, struct page *page)
 static struct address_space_operations afs_symlink_aops = {
   .readpage =  afs_symlink_filler
 };
-#endif
+#endif /* USABLE_KERNEL_PAGE_SYMLINK_CACHE */
 
 static struct inode_operations afs_symlink_iops = {
-#if defined(AFS_LINUX24_ENV)
+#if defined(USABLE_KERNEL_PAGE_SYMLINK_CACHE)
   .readlink =          page_readlink,
 #if defined(HAVE_KERNEL_PAGE_FOLLOW_LINK)
   .follow_link =       page_follow_link,
@@ -1781,13 +1812,17 @@ static struct inode_operations afs_symlink_iops = {
   .follow_link =       page_follow_link_light,
   .put_link =           page_put_link,
 #endif
-  .setattr =           afs_notify_change,
-#else
+#else /* !defined(USABLE_KERNEL_PAGE_SYMLINK_CACHE) */
   .readlink =          afs_linux_readlink,
   .follow_link =       afs_linux_follow_link,
+#if !defined(AFS_LINUX24_ENV)
   .permission =                afs_linux_permission,
   .revalidate =                afs_linux_revalidate,
 #endif
+#endif /* USABLE_KERNEL_PAGE_SYMLINK_CACHE */
+#if defined(AFS_LINUX24_ENV)
+  .setattr =           afs_notify_change,
+#endif
 };
 
 void
@@ -1813,7 +1848,7 @@ afs_fill_inode(struct inode *ip, struct vattr *vattr)
 
     } else if (S_ISLNK(ip->i_mode)) {
        ip->i_op = &afs_symlink_iops;
-#if defined(AFS_LINUX24_ENV)
+#if defined(USABLE_KERNEL_PAGE_SYMLINK_CACHE)
        ip->i_data.a_ops = &afs_symlink_aops;
        ip->i_mapping = &ip->i_data;
 #endif
index 653464f..c8659fb 100644 (file)
@@ -644,6 +644,22 @@ struct nameidata _nameidata;
   AC_MSG_RESULT($ac_cv_linux_func_i_permission_takes_nameidata)])
 
 
+AC_DEFUN([LINUX_IOP_I_PUT_LINK_TAKES_COOKIE], [
+  AC_MSG_CHECKING([whether inode_operations.put_link takes an opaque cookie])
+  AC_CACHE_VAL([ac_cv_linux_func_i_put_link_takes_cookie], [
+    AC_TRY_KBUILD(
+[#include <linux/fs.h>
+#include <linux/namei.h>],
+[struct inode _inode;
+struct dentry _dentry;
+struct nameidata _nameidata;
+void *cookie;
+(void)_inode.i_op->put_link(&_dentry, &_nameidata, cookie);],
+      ac_cv_linux_func_i_put_link_takes_cookie=yes,
+      ac_cv_linux_func_i_put_link_takes_cookie=no)])
+  AC_MSG_RESULT($ac_cv_linux_func_i_put_link_takes_cookie)])
+
+
 AC_DEFUN([LINUX_DOP_D_REVALIDATE_TAKES_NAMEIDATA], [
   AC_MSG_CHECKING([whether dentry_operations.d_revalidate takes a nameidata])
   AC_CACHE_VAL([ac_cv_linux_func_d_revalidate_takes_nameidata], [