Fix remount races with symlink handling in affs
authorAl Viro <viro@zeniv.linux.org.uk>
Sun, 24 Jan 2010 05:04:07 +0000 (00:04 -0500)
committerAl Viro <viro@zeniv.linux.org.uk>
Wed, 27 Jan 2010 03:22:24 +0000 (22:22 -0500)
A couple of fields in affs_sb_info is used in follow_link() and
symlink() for handling AFFS "absolute" symlinks.  Need locking
against affs_remount() updates.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/affs/affs.h
fs/affs/namei.c
fs/affs/super.c
fs/affs/symlink.c

index e511dc6..0e40caa 100644 (file)
@@ -106,8 +106,8 @@ struct affs_sb_info {
        u32 s_last_bmap;
        struct buffer_head *s_bmap_bh;
        char *s_prefix;                 /* Prefix for volumes and assigns. */
-       int s_prefix_len;               /* Length of prefix. */
        char s_volume[32];              /* Volume prefix for absolute symlinks. */
+       spinlock_t symlink_lock;        /* protects the previous two */
 };
 
 #define SF_INTL                0x0001          /* International filesystem. */
index 960d336..d70bbba 100644 (file)
@@ -341,10 +341,13 @@ affs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
        p  = (char *)AFFS_HEAD(bh)->table;
        lc = '/';
        if (*symname == '/') {
+               struct affs_sb_info *sbi = AFFS_SB(sb);
                while (*symname == '/')
                        symname++;
-               while (AFFS_SB(sb)->s_volume[i])        /* Cannot overflow */
-                       *p++ = AFFS_SB(sb)->s_volume[i++];
+               spin_lock(&sbi->symlink_lock);
+               while (sbi->s_volume[i])        /* Cannot overflow */
+                       *p++ = sbi->s_volume[i++];
+               spin_unlock(&sbi->symlink_lock);
        }
        while (i < maxlen && (c = *symname++)) {
                if (c == '.' && lc == '/' && *symname == '.' && symname[1] == '/') {
index b2a5958..be6a6e8 100644 (file)
@@ -221,8 +221,6 @@ parse_options(char *options, uid_t *uid, gid_t *gid, int *mode, int *reserved, s
                        *mount_opts |= SF_MUFS;
                        break;
                case Opt_prefix:
-                       /* Free any previous prefix */
-                       kfree(*prefix);
                        *prefix = match_strdup(&args[0]);
                        if (!*prefix)
                                return 0;
@@ -311,6 +309,7 @@ static int affs_fill_super(struct super_block *sb, void *data, int silent)
                return -ENOMEM;
        sb->s_fs_info = sbi;
        mutex_init(&sbi->s_bmlock);
+       spin_lock_init(&sbi->symlink_lock);
 
        if (!parse_options(data,&uid,&gid,&i,&reserved,&root_block,
                                &blocksize,&sbi->s_prefix,
@@ -518,14 +517,18 @@ affs_remount(struct super_block *sb, int *flags, char *data)
        unsigned long            mount_flags;
        int                      res = 0;
        char                    *new_opts = kstrdup(data, GFP_KERNEL);
+       char                     volume[32];
+       char                    *prefix = NULL;
 
        pr_debug("AFFS: remount(flags=0x%x,opts=\"%s\")\n",*flags,data);
 
        *flags |= MS_NODIRATIME;
 
+       memcpy(volume, sbi->s_volume, 32);
        if (!parse_options(data, &uid, &gid, &mode, &reserved, &root_block,
-                          &blocksize, &sbi->s_prefix, sbi->s_volume,
+                          &blocksize, &prefix, volume,
                           &mount_flags)) {
+               kfree(prefix);
                kfree(new_opts);
                return -EINVAL;
        }
@@ -536,6 +539,14 @@ affs_remount(struct super_block *sb, int *flags, char *data)
        sbi->s_mode  = mode;
        sbi->s_uid   = uid;
        sbi->s_gid   = gid;
+       /* protect against readers */
+       spin_lock(&sbi->symlink_lock);
+       if (prefix) {
+               kfree(sbi->s_prefix);
+               sbi->s_prefix = prefix;
+       }
+       memcpy(sbi->s_volume, volume, 32);
+       spin_unlock(&sbi->symlink_lock);
 
        if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY)) {
                unlock_kernel();
index 4178253..ee00f08 100644 (file)
@@ -20,7 +20,6 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
        int                      i, j;
        char                     c;
        char                     lc;
-       char                    *pf;
 
        pr_debug("AFFS: follow_link(ino=%lu)\n",inode->i_ino);
 
@@ -32,11 +31,15 @@ static int affs_symlink_readpage(struct file *file, struct page *page)
        j  = 0;
        lf = (struct slink_front *)bh->b_data;
        lc = 0;
-       pf = AFFS_SB(inode->i_sb)->s_prefix ? AFFS_SB(inode->i_sb)->s_prefix : "/";
 
        if (strchr(lf->symname,':')) {  /* Handle assign or volume name */
+               struct affs_sb_info *sbi = AFFS_SB(inode->i_sb);
+               char *pf;
+               spin_lock(&sbi->symlink_lock);
+               pf = sbi->s_prefix ? sbi->s_prefix : "/";
                while (i < 1023 && (c = pf[i]))
                        link[i++] = c;
+               spin_unlock(&sbi->symlink_lock);
                while (i < 1023 && lf->symname[j] != ':')
                        link[i++] = lf->symname[j++];
                if (i < 1023)