@@ -1812,8 +1812,10 @@ static int do_execveat_common(int fd, struct filename *filename,
free_bprm(bprm);
kfree(pathbuf);
putname(filename);
- if (displaced)
+ if (displaced) {
+ change_lock_owners(current->files, displaced);
put_files_struct(displaced);
+ }
return retval;
out:
@@ -365,6 +365,50 @@ struct files_struct *dup_fd(struct files_struct *oldf, int *errorp)
return NULL;
}
+/**
+ * change_lock_owners - change lock ownership from old files struct to new
+ * @files: new files struct to own locks
+ * @old: old files struct that previously held locks
+ *
+ * On execve, a process may end up with a new files_struct. In that case, we
+ * must change all of the locks that were owned by the previous files_struct
+ * to the new one.
+ */
+void change_lock_owners(struct files_struct *new, struct files_struct *old)
+{
+ struct fdtable *fdt = rcu_dereference_raw(new->fdt);
+ unsigned int i, j = 0;
+
+ /*
+ * It is safe to dereference the fd table without RCU or ->file_lock
+ * because this is the only reference to the files structure. If that's
+ * ever not the case, just warn and don't change anything.
+ */
+ if (unlikely(atomic_read(&new->count) != 1)) {
+ WARN_ON_ONCE(1);
+ return;
+ }
+
+ for (;;) {
+ unsigned long set;
+ i = j * BITS_PER_LONG;
+ if (i >= fdt->max_fds)
+ break;
+ set = fdt->open_fds[j++];
+ while (set) {
+ if (set & 1) {
+ struct file * file = fdt->fd[i];
+ if (file) {
+ posix_chown_locks(file, old, new);
+ cond_resched();
+ }
+ }
+ i++;
+ set >>= 1;
+ }
+ }
+}
+
static struct fdtable *close_files(struct files_struct * files)
{
/*
@@ -993,6 +993,49 @@ static int flock_lock_inode(struct inode *inode, struct file_lock *request)
return error;
}
+/**
+ * posix_chown_locks - change all locks on inode owned by old fl_owner to new
+ * @file: struct file on which to change the locks
+ * @old: old fl_owner value to change from
+ * @new: new fl_owner value to change to
+ *
+ * This function changes all of the file locks in an inode owned by an old
+ * fl_owner to be owned by a new fl_owner.
+ */
+void posix_chown_locks(struct file *file, fl_owner_t old, fl_owner_t new)
+{
+ struct inode *inode = file_inode(file);
+ struct file_lock_context *ctx;
+ struct file_lock *fl, *tmp;
+
+ /* Don't want to allocate a context in this case */
+ ctx = locks_get_lock_context(inode, F_UNLCK);
+ if (!ctx)
+ return;
+
+ percpu_down_read_preempt_disable(&file_rwsem);
+ spin_lock(&ctx->flc_lock);
+ /* Find the first old lock with the same owner as the new lock */
+ list_for_each_entry(fl, &ctx->flc_posix, fl_list) {
+ if (fl->fl_owner == old)
+ break;
+ }
+
+ list_for_each_entry_safe_from(fl, tmp, &ctx->flc_posix, fl_list) {
+ if (fl->fl_owner != old)
+ break;
+
+ /* This should only be used for normal userland lockmanager */
+ if (fl->fl_lmops) {
+ WARN_ON_ONCE(1);
+ continue;
+ }
+ fl->fl_owner = new;
+ }
+ spin_unlock(&ctx->flc_lock);
+ percpu_up_read_preempt_enable(&file_rwsem);
+}
+
static int posix_lock_inode(struct inode *inode, struct file_lock *request,
struct file_lock *conflock)
{
@@ -114,6 +114,7 @@ void do_close_on_exec(struct files_struct *);
int iterate_fd(struct files_struct *, unsigned,
int (*)(const void *, struct file *, unsigned),
const void *);
+void change_lock_owners(struct files_struct *, struct files_struct *);
extern int __alloc_fd(struct files_struct *files,
unsigned start, unsigned end, unsigned flags);
@@ -1084,6 +1084,7 @@ extern void locks_remove_posix(struct file *, fl_owner_t);
extern void locks_remove_file(struct file *);
extern void locks_release_private(struct file_lock *);
extern void posix_test_lock(struct file *, struct file_lock *);
+extern void posix_chown_locks(struct file *, fl_owner_t old, fl_owner_t new);
extern int posix_lock_file(struct file *, struct file_lock *, struct file_lock *);
extern int posix_unblock_lock(struct file_lock *);
extern int vfs_test_lock(struct file *, struct file_lock *);