Преглед изворни кода

Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs-2.6

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs-2.6:
  [PATCH] sanitize locate_fd()
  [PATCH] sanitize unshare_files/reset_files_struct
  [PATCH] sanitize handling of shared descriptor tables in failing execve()
  [PATCH] close race in unshare_files()
  [PATCH] restore sane ->umount_begin() API
  cifs: timeout dfs automounts +little fix.
Linus Torvalds пре 17 година
родитељ
комит
bc84e0a160
16 измењених фајлова са 95 додато и 172 уклоњено
  1. 3 4
      fs/9p/vfs_super.c
  2. 1 22
      fs/binfmt_elf.c
  3. 1 17
      fs/binfmt_misc.c
  4. 0 10
      fs/binfmt_som.c
  5. 21 8
      fs/cifs/cifs_dfs_ref.c
  6. 3 7
      fs/cifs/cifsfs.c
  7. 1 7
      fs/cifs/cifsproto.h
  8. 12 16
      fs/exec.c
  9. 14 26
      fs/fcntl.c
  10. 2 3
      fs/fuse/inode.c
  11. 5 4
      fs/namespace.c
  12. 3 5
      fs/nfs/super.c
  13. 2 1
      include/linux/file.h
  14. 1 4
      include/linux/fs.h
  15. 2 4
      kernel/exit.c
  16. 24 34
      kernel/fork.c

+ 3 - 4
fs/9p/vfs_super.c

@@ -224,12 +224,11 @@ static int v9fs_show_options(struct seq_file *m, struct vfsmount *mnt)
 }
 
 static void
-v9fs_umount_begin(struct vfsmount *vfsmnt, int flags)
+v9fs_umount_begin(struct super_block *sb)
 {
-	struct v9fs_session_info *v9ses = vfsmnt->mnt_sb->s_fs_info;
+	struct v9fs_session_info *v9ses = sb->s_fs_info;
 
-	if (flags & MNT_FORCE)
-		v9fs_session_cancel(v9ses);
+	v9fs_session_cancel(v9ses);
 }
 
 static const struct super_operations v9fs_super_ops = {

+ 1 - 22
fs/binfmt_elf.c

@@ -543,7 +543,6 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 	unsigned long interp_load_addr = 0;
 	unsigned long start_code, end_code, start_data, end_data;
 	unsigned long reloc_func_desc = 0;
-	struct files_struct *files;
 	int executable_stack = EXSTACK_DEFAULT;
 	unsigned long def_flags = 0;
 	struct {
@@ -593,20 +592,9 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 		goto out_free_ph;
 	}
 
-	files = current->files;	/* Refcounted so ok */
-	retval = unshare_files();
-	if (retval < 0)
-		goto out_free_ph;
-	if (files == current->files) {
-		put_files_struct(files);
-		files = NULL;
-	}
-
-	/* exec will make our files private anyway, but for the a.out
-	   loader stuff we need to do it earlier */
 	retval = get_unused_fd();
 	if (retval < 0)
-		goto out_free_fh;
+		goto out_free_ph;
 	get_file(bprm->file);
 	fd_install(elf_exec_fileno = retval, bprm->file);
 
@@ -728,12 +716,6 @@ static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 	if (retval)
 		goto out_free_dentry;
 
-	/* Discard our unneeded old files struct */
-	if (files) {
-		put_files_struct(files);
-		files = NULL;
-	}
-
 	/* OK, This is the point of no return */
 	current->flags &= ~PF_FORKNOEXEC;
 	current->mm->def_flags = def_flags;
@@ -1016,9 +998,6 @@ out_free_interp:
 	kfree(elf_interpreter);
 out_free_file:
 	sys_close(elf_exec_fileno);
-out_free_fh:
-	if (files)
-		reset_files_struct(current, files);
 out_free_ph:
 	kfree(elf_phdata);
 	goto out;

+ 1 - 17
fs/binfmt_misc.c

@@ -110,7 +110,6 @@ static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 	char *iname_addr = iname;
 	int retval;
 	int fd_binary = -1;
-	struct files_struct *files = NULL;
 
 	retval = -ENOEXEC;
 	if (!enabled)
@@ -133,21 +132,13 @@ static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 
 	if (fmt->flags & MISC_FMT_OPEN_BINARY) {
 
-		files = current->files;
-		retval = unshare_files();
-		if (retval < 0)
-			goto _ret;
-		if (files == current->files) {
-			put_files_struct(files);
-			files = NULL;
-		}
 		/* if the binary should be opened on behalf of the
 		 * interpreter than keep it open and assign descriptor
 		 * to it */
  		fd_binary = get_unused_fd();
  		if (fd_binary < 0) {
  			retval = fd_binary;
- 			goto _unshare;
+ 			goto _ret;
  		}
  		fd_install(fd_binary, bprm->file);
 
@@ -205,10 +196,6 @@ static int load_misc_binary(struct linux_binprm *bprm, struct pt_regs *regs)
 	if (retval < 0)
 		goto _error;
 
-	if (files) {
-		put_files_struct(files);
-		files = NULL;
-	}
 _ret:
 	return retval;
 _error:
@@ -216,9 +203,6 @@ _error:
 		sys_close(fd_binary);
 	bprm->interp_flags = 0;
 	bprm->interp_data = 0;
-_unshare:
-	if (files)
-		reset_files_struct(current, files);
 	goto _ret;
 }
 

+ 0 - 10
fs/binfmt_som.c

@@ -194,7 +194,6 @@ load_som_binary(struct linux_binprm * bprm, struct pt_regs * regs)
 	unsigned long som_entry;
 	struct som_hdr *som_ex;
 	struct som_exec_auxhdr *hpuxhdr;
-	struct files_struct *files;
 
 	/* Get the exec-header */
 	som_ex = (struct som_hdr *) bprm->buf;
@@ -221,15 +220,6 @@ load_som_binary(struct linux_binprm * bprm, struct pt_regs * regs)
 		goto out_free;
 	}
 
-	files = current->files; /* Refcounted so ok */
-	retval = unshare_files();
-	if (retval < 0)
-		goto out_free;
-	if (files == current->files) {
-		put_files_struct(files);
-		files = NULL;
-	}
-
 	retval = get_unused_fd();
 	if (retval < 0)
 		goto out_free;

+ 21 - 8
fs/cifs/cifs_dfs_ref.c

@@ -25,14 +25,26 @@
 
 static LIST_HEAD(cifs_dfs_automount_list);
 
-/*
- * DFS functions
-*/
+static void cifs_dfs_expire_automounts(struct work_struct *work);
+static DECLARE_DELAYED_WORK(cifs_dfs_automount_task,
+			    cifs_dfs_expire_automounts);
+static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ;
+
+static void cifs_dfs_expire_automounts(struct work_struct *work)
+{
+	struct list_head *list = &cifs_dfs_automount_list;
+
+	mark_mounts_for_expiry(list);
+	if (!list_empty(list))
+		schedule_delayed_work(&cifs_dfs_automount_task,
+				      cifs_dfs_mountpoint_expiry_timeout);
+}
 
-void dfs_shrink_umount_helper(struct vfsmount *vfsmnt)
+void cifs_dfs_release_automount_timer(void)
 {
-	mark_mounts_for_expiry(&cifs_dfs_automount_list);
-	mark_mounts_for_expiry(&cifs_dfs_automount_list);
+	BUG_ON(!list_empty(&cifs_dfs_automount_list));
+	cancel_delayed_work(&cifs_dfs_automount_task);
+	flush_scheduled_work();
 }
 
 /**
@@ -261,10 +273,11 @@ static int add_mount_helper(struct vfsmount *newmnt, struct nameidata *nd,
 	err = do_add_mount(newmnt, nd, nd->path.mnt->mnt_flags, mntlist);
 	switch (err) {
 	case 0:
-		dput(nd->path.dentry);
-		mntput(nd->path.mnt);
+		path_put(&nd->path);
 		nd->path.mnt = newmnt;
 		nd->path.dentry = dget(newmnt->mnt_root);
+		schedule_delayed_work(&cifs_dfs_automount_task,
+				      cifs_dfs_mountpoint_expiry_timeout);
 		break;
 	case -EBUSY:
 		/* someone else made a mount here whilst we were busy */

+ 3 - 7
fs/cifs/cifsfs.c

@@ -466,16 +466,11 @@ static struct quotactl_ops cifs_quotactl_ops = {
 };
 #endif
 
-static void cifs_umount_begin(struct vfsmount *vfsmnt, int flags)
+static void cifs_umount_begin(struct super_block *sb)
 {
-	struct cifs_sb_info *cifs_sb;
+	struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
 	struct cifsTconInfo *tcon;
 
-	dfs_shrink_umount_helper(vfsmnt);
-
-	if (!(flags & MNT_FORCE))
-		return;
-	cifs_sb = CIFS_SB(vfsmnt->mnt_sb);
 	if (cifs_sb == NULL)
 		return;
 
@@ -1100,6 +1095,7 @@ exit_cifs(void)
 	cFYI(DBG2, ("exit_cifs"));
 	cifs_proc_clean();
 #ifdef CONFIG_CIFS_DFS_UPCALL
+	cifs_dfs_release_automount_timer();
 	unregister_key_type(&key_type_dns_resolver);
 #endif
 #ifdef CONFIG_CIFS_UPCALL

+ 1 - 7
fs/cifs/cifsproto.h

@@ -104,13 +104,7 @@ extern int mode_to_acl(struct inode *inode, const char *path, __u64);
 extern int cifs_mount(struct super_block *, struct cifs_sb_info *, char *,
 			const char *);
 extern int cifs_umount(struct super_block *, struct cifs_sb_info *);
-#ifdef CONFIG_CIFS_DFS_UPCALL
-extern void dfs_shrink_umount_helper(struct vfsmount *vfsmnt);
-#else
-static inline void dfs_shrink_umount_helper(struct vfsmount *vfsmnt)
-{
-}
-#endif /* DFS_UPCALL */
+extern void cifs_dfs_release_automount_timer(void);
 void cifs_proc_init(void);
 void cifs_proc_clean(void);
 

+ 12 - 16
fs/exec.c

@@ -953,7 +953,6 @@ int flush_old_exec(struct linux_binprm * bprm)
 {
 	char * name;
 	int i, ch, retval;
-	struct files_struct *files;
 	char tcomm[sizeof(current->comm)];
 
 	/*
@@ -964,27 +963,16 @@ int flush_old_exec(struct linux_binprm * bprm)
 	if (retval)
 		goto out;
 
-	/*
-	 * Make sure we have private file handles. Ask the
-	 * fork helper to do the work for us and the exit
-	 * helper to do the cleanup of the old one.
-	 */
-	files = current->files;		/* refcounted so safe to hold */
-	retval = unshare_files();
-	if (retval)
-		goto out;
 	/*
 	 * Release all of the old mmap stuff
 	 */
 	retval = exec_mmap(bprm->mm);
 	if (retval)
-		goto mmap_failed;
+		goto out;
 
 	bprm->mm = NULL;		/* We're using it now */
 
 	/* This is the point of no return */
-	put_files_struct(files);
-
 	current->sas_ss_sp = current->sas_ss_size = 0;
 
 	if (current->euid == current->uid && current->egid == current->gid)
@@ -1034,8 +1022,6 @@ int flush_old_exec(struct linux_binprm * bprm)
 
 	return 0;
 
-mmap_failed:
-	reset_files_struct(current, files);
 out:
 	return retval;
 }
@@ -1283,12 +1269,17 @@ int do_execve(char * filename,
 	struct linux_binprm *bprm;
 	struct file *file;
 	unsigned long env_p;
+	struct files_struct *displaced;
 	int retval;
 
+	retval = unshare_files(&displaced);
+	if (retval)
+		goto out_ret;
+
 	retval = -ENOMEM;
 	bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
 	if (!bprm)
-		goto out_ret;
+		goto out_files;
 
 	file = open_exec(filename);
 	retval = PTR_ERR(file);
@@ -1343,6 +1334,8 @@ int do_execve(char * filename,
 		security_bprm_free(bprm);
 		acct_update_integrals(current);
 		kfree(bprm);
+		if (displaced)
+			put_files_struct(displaced);
 		return retval;
 	}
 
@@ -1363,6 +1356,9 @@ out_file:
 out_kfree:
 	kfree(bprm);
 
+out_files:
+	if (displaced)
+		reset_files_struct(displaced);
 out_ret:
 	return retval;
 }

+ 14 - 26
fs/fcntl.c

@@ -55,14 +55,16 @@ static int get_close_on_exec(unsigned int fd)
  * file_lock held for write.
  */
 
-static int locate_fd(struct files_struct *files, 
-			    struct file *file, unsigned int orig_start)
+static int locate_fd(unsigned int orig_start, int cloexec)
 {
+	struct files_struct *files = current->files;
 	unsigned int newfd;
 	unsigned int start;
 	int error;
 	struct fdtable *fdt;
 
+	spin_lock(&files->file_lock);
+
 	error = -EINVAL;
 	if (orig_start >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
 		goto out;
@@ -97,42 +99,28 @@ repeat:
 	if (error)
 		goto repeat;
 
-	/*
-	 * We reacquired files_lock, so we are safe as long as
-	 * we reacquire the fdtable pointer and use it while holding
-	 * the lock, no one can free it during that time.
-	 */
 	if (start <= files->next_fd)
 		files->next_fd = newfd + 1;
 
+	FD_SET(newfd, fdt->open_fds);
+	if (cloexec)
+		FD_SET(newfd, fdt->close_on_exec);
+	else
+		FD_CLR(newfd, fdt->close_on_exec);
 	error = newfd;
-	
+
 out:
+	spin_unlock(&files->file_lock);
 	return error;
 }
 
 static int dupfd(struct file *file, unsigned int start, int cloexec)
 {
-	struct files_struct * files = current->files;
-	struct fdtable *fdt;
-	int fd;
-
-	spin_lock(&files->file_lock);
-	fd = locate_fd(files, file, start);
-	if (fd >= 0) {
-		/* locate_fd() may have expanded fdtable, load the ptr */
-		fdt = files_fdtable(files);
-		FD_SET(fd, fdt->open_fds);
-		if (cloexec)
-			FD_SET(fd, fdt->close_on_exec);
-		else
-			FD_CLR(fd, fdt->close_on_exec);
-		spin_unlock(&files->file_lock);
+	int fd = locate_fd(start, cloexec);
+	if (fd >= 0)
 		fd_install(fd, file);
-	} else {
-		spin_unlock(&files->file_lock);
+	else
 		fput(file);
-	}
 
 	return fd;
 }

+ 2 - 3
fs/fuse/inode.c

@@ -242,10 +242,9 @@ struct inode *fuse_iget(struct super_block *sb, unsigned long nodeid,
 	return inode;
 }
 
-static void fuse_umount_begin(struct vfsmount *vfsmnt, int flags)
+static void fuse_umount_begin(struct super_block *sb)
 {
-	if (flags & MNT_FORCE)
-		fuse_abort_conn(get_fuse_conn_super(vfsmnt->mnt_sb));
+	fuse_abort_conn(get_fuse_conn_super(sb));
 }
 
 static void fuse_send_destroy(struct fuse_conn *fc)

+ 5 - 4
fs/namespace.c

@@ -1061,10 +1061,11 @@ static int do_umount(struct vfsmount *mnt, int flags)
 	 * about for the moment.
 	 */
 
-	lock_kernel();
-	if (sb->s_op->umount_begin)
-		sb->s_op->umount_begin(mnt, flags);
-	unlock_kernel();
+	if (flags & MNT_FORCE && sb->s_op->umount_begin) {
+		lock_kernel();
+		sb->s_op->umount_begin(sb);
+		unlock_kernel();
+	}
 
 	/*
 	 * No sense to grab the lock for this test, but test itself looks

+ 3 - 5
fs/nfs/super.c

@@ -198,7 +198,7 @@ static match_table_t nfs_secflavor_tokens = {
 };
 
 
-static void nfs_umount_begin(struct vfsmount *, int);
+static void nfs_umount_begin(struct super_block *);
 static int  nfs_statfs(struct dentry *, struct kstatfs *);
 static int  nfs_show_options(struct seq_file *, struct vfsmount *);
 static int  nfs_show_stats(struct seq_file *, struct vfsmount *);
@@ -647,13 +647,11 @@ static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt)
  * Begin unmount by attempting to remove all automounted mountpoints we added
  * in response to xdev traversals and referrals
  */
-static void nfs_umount_begin(struct vfsmount *vfsmnt, int flags)
+static void nfs_umount_begin(struct super_block *sb)
 {
-	struct nfs_server *server = NFS_SB(vfsmnt->mnt_sb);
+	struct nfs_server *server = NFS_SB(sb);
 	struct rpc_clnt *rpc;
 
-	if (!(flags & MNT_FORCE))
-		return;
 	/* -EIO all pending I/O */
 	rpc = server->client_acl;
 	if (!IS_ERR(rpc))

+ 2 - 1
include/linux/file.h

@@ -117,7 +117,8 @@ struct task_struct;
 
 struct files_struct *get_files_struct(struct task_struct *);
 void put_files_struct(struct files_struct *fs);
-void reset_files_struct(struct task_struct *, struct files_struct *);
+void reset_files_struct(struct files_struct *);
+int unshare_files(struct files_struct **);
 
 extern struct kmem_cache *files_cachep;
 

+ 1 - 4
include/linux/fs.h

@@ -1309,7 +1309,7 @@ struct super_operations {
 	int (*statfs) (struct dentry *, struct kstatfs *);
 	int (*remount_fs) (struct super_block *, int *, char *);
 	void (*clear_inode) (struct inode *);
-	void (*umount_begin) (struct vfsmount *, int);
+	void (*umount_begin) (struct super_block *);
 
 	int (*show_options)(struct seq_file *, struct vfsmount *);
 	int (*show_stats)(struct seq_file *, struct vfsmount *);
@@ -2034,9 +2034,6 @@ static inline ino_t parent_ino(struct dentry *dentry)
 	return res;
 }
 
-/* kernel/fork.c */
-extern int unshare_files(void);
-
 /* Transaction based IO helpers */
 
 /*

+ 2 - 4
kernel/exit.c

@@ -507,10 +507,9 @@ void put_files_struct(struct files_struct *files)
 	}
 }
 
-EXPORT_SYMBOL(put_files_struct);
-
-void reset_files_struct(struct task_struct *tsk, struct files_struct *files)
+void reset_files_struct(struct files_struct *files)
 {
+	struct task_struct *tsk = current;
 	struct files_struct *old;
 
 	old = tsk->files;
@@ -519,7 +518,6 @@ void reset_files_struct(struct task_struct *tsk, struct files_struct *files)
 	task_unlock(tsk);
 	put_files_struct(old);
 }
-EXPORT_SYMBOL(reset_files_struct);
 
 void exit_files(struct task_struct *tsk)
 {

+ 24 - 34
kernel/fork.c

@@ -805,12 +805,6 @@ static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
 		goto out;
 	}
 
-	/*
-	 * Note: we may be using current for both targets (See exec.c)
-	 * This works because we cache current->files (old) as oldf. Don't
-	 * break this.
-	 */
-	tsk->files = NULL;
 	newf = dup_fd(oldf, &error);
 	if (!newf)
 		goto out;
@@ -846,34 +840,6 @@ static int copy_io(unsigned long clone_flags, struct task_struct *tsk)
 	return 0;
 }
 
-/*
- *	Helper to unshare the files of the current task.
- *	We don't want to expose copy_files internals to
- *	the exec layer of the kernel.
- */
-
-int unshare_files(void)
-{
-	struct files_struct *files  = current->files;
-	int rc;
-
-	BUG_ON(!files);
-
-	/* This can race but the race causes us to copy when we don't
-	   need to and drop the copy */
-	if(atomic_read(&files->count) == 1)
-	{
-		atomic_inc(&files->count);
-		return 0;
-	}
-	rc = copy_files(0, current);
-	if(rc)
-		current->files = files;
-	return rc;
-}
-
-EXPORT_SYMBOL(unshare_files);
-
 static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
 {
 	struct sighand_struct *sig;
@@ -1811,3 +1777,27 @@ bad_unshare_cleanup_thread:
 bad_unshare_out:
 	return err;
 }
+
+/*
+ *	Helper to unshare the files of the current task.
+ *	We don't want to expose copy_files internals to
+ *	the exec layer of the kernel.
+ */
+
+int unshare_files(struct files_struct **displaced)
+{
+	struct task_struct *task = current;
+	struct files_struct *copy;
+	int error;
+
+	error = unshare_fd(CLONE_FILES, &copy);
+	if (error || !copy) {
+		*displaced = NULL;
+		return error;
+	}
+	*displaced = task->files;
+	task_lock(task);
+	task->files = copy;
+	task_unlock(task);
+	return 0;
+}