vfs: lseek(fd, 0, SEEK_CUR) race condition
Alain Knaff [Tue, 11 Nov 2008 01:08:08 +0000 (17:08 -0800)]
This patch fixes a race condition in lseek. While it is expected that
unpredictable behaviour may result while repositioning the offset of a
file descriptor concurrently with reading/writing to the same file
descriptor, this should not happen when merely *reading* the file
descriptor's offset.

Unfortunately, the only portable way in Unix to read a file
descriptor's offset is lseek(fd, 0, SEEK_CUR); however executing this
concurrently with read/write may mess up the position.

[with fixes from akpm]

Signed-off-by: Alain Knaff <alain@knaff.lu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>

fs/read_write.c

index 969a6d9..5cc6924 100644 (file)
@@ -50,6 +50,14 @@ generic_file_llseek_unlocked(struct file *file, loff_t offset, int origin)
                offset += inode->i_size;
                break;
        case SEEK_CUR:
+               /*
+                * Here we special-case the lseek(fd, 0, SEEK_CUR)
+                * position-querying operation.  Avoid rewriting the "same"
+                * f_pos value back to the file because a concurrent read(),
+                * write() or lseek() might have altered it
+                */
+               if (offset == 0)
+                       return file->f_pos;
                offset += file->f_pos;
                break;
        }
@@ -105,6 +113,10 @@ loff_t default_llseek(struct file *file, loff_t offset, int origin)
                        offset += i_size_read(file->f_path.dentry->d_inode);
                        break;
                case SEEK_CUR:
+                       if (offset == 0) {
+                               retval = file->f_pos;
+                               goto out;
+                       }
                        offset += file->f_pos;
        }
        retval = -EINVAL;
@@ -115,6 +127,7 @@ loff_t default_llseek(struct file *file, loff_t offset, int origin)
                }
                retval = offset;
        }
+out:
        unlock_kernel();
        return retval;
 }