summaryrefslogtreecommitdiff
path: root/kernel/fork.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/fork.c')
-rw-r--r--kernel/fork.c104
1 files changed, 39 insertions, 65 deletions
diff --git a/kernel/fork.c b/kernel/fork.c
index 5ef413368912..2142c6f38119 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1083,9 +1083,7 @@ static void mmap_init_lock(struct mm_struct *mm)
{
init_rwsem(&mm->mmap_lock);
mm_lock_seqcount_init(mm);
-#ifdef CONFIG_PER_VMA_LOCK
rcuwait_init(&mm->vma_writer_wait);
-#endif
}
static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p)
@@ -1599,6 +1597,7 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk)
tsk->mm = mm;
tsk->active_mm = mm;
+ sched_cache_fork(tsk);
return 0;
}
@@ -1618,7 +1617,13 @@ static int copy_exec_state(u64 clone_flags, struct task_struct *tsk)
return task_exec_state_copy(tsk);
}
-static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh)
+enum copy_fs_flags_t {
+ COPY_FS_UMH = (1 << 0),
+ COPY_FS_IDLE = (1 << 1),
+};
+
+static int copy_fs(u64 clone_flags, struct task_struct *tsk,
+ enum copy_fs_flags_t flags)
{
struct fs_struct *fs;
@@ -1628,19 +1633,20 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh)
* filesystem state, or be started from a non-initial mount
* namespace.
*/
- if (umh) {
+ if (flags & COPY_FS_UMH) {
if (clone_flags & (CLONE_NEWNS | CLONE_FS))
return -EINVAL;
if (current->nsproxy->mnt_ns != &init_mnt_ns)
return -EINVAL;
fs = userspace_init_fs;
+ } else if (flags & COPY_FS_IDLE) {
+ fs = &init_fs;
} else {
fs = current->fs;
VFS_WARN_ON_ONCE(current->fs != current->real_fs);
}
if (clone_flags & CLONE_FS) {
- /* tsk->fs is already what we want */
read_seqlock_excl(&fs->seq);
/* "users" and "in_exec" locked for check_unsafe_exec() */
if (fs->in_exec) {
@@ -1649,6 +1655,7 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh)
}
fs->users++;
read_sequnlock_excl(&fs->seq);
+ tsk->real_fs = tsk->fs = fs;
return 0;
}
tsk->real_fs = tsk->fs = copy_fs_struct(fs);
@@ -1676,6 +1683,7 @@ static int copy_files(u64 clone_flags, struct task_struct *tsk,
if (clone_flags & CLONE_FILES) {
atomic_inc(&oldf->count);
+ tsk->files = oldf;
return 0;
}
@@ -2145,12 +2153,9 @@ __latent_entropy struct task_struct *copy_process(
if (args->kthread)
p->flags |= PF_KTHREAD;
if (args->user_worker) {
- /*
- * Mark us a user worker, and block any signal that isn't
- * fatal or STOP
- */
+ /* A user worker takes only the signals nobody can block. */
p->flags |= PF_USER_WORKER;
- siginitsetinv(&p->blocked, sigmask(SIGKILL)|sigmask(SIGSTOP));
+ siginitsetinv(&p->blocked, SIG_KERNEL_ONLY_MASK);
}
if (args->io_thread)
p->flags |= PF_IO_WORKER;
@@ -2199,6 +2204,8 @@ __latent_entropy struct task_struct *copy_process(
INIT_LIST_HEAD(&p->sibling);
rcu_copy_process(p);
p->vfork_done = NULL;
+ /* Set by copy_files(), exit_files() on the error path skips NULL. */
+ p->files = NULL;
spin_lock_init(&p->alloc_lock);
init_sigpending(&p->pending);
@@ -2298,9 +2305,11 @@ __latent_entropy struct task_struct *copy_process(
retval = copy_files(clone_flags, p, args->no_files);
if (retval)
goto bad_fork_cleanup_semundo;
- retval = copy_fs(clone_flags, p, args->umh);
+ retval = copy_fs(clone_flags, p,
+ (args->umh ? COPY_FS_UMH : 0) |
+ (args->idle ? COPY_FS_IDLE : 0));
if (retval)
- goto bad_fork_cleanup_files;
+ goto bad_fork_cleanup_semundo;
retval = copy_sighand(clone_flags, p);
if (retval)
goto bad_fork_cleanup_fs;
@@ -2602,6 +2611,7 @@ bad_fork_cleanup_io:
bad_fork_cleanup_namespaces:
exit_nsproxy_namespaces(p);
bad_fork_cleanup_mm:
+ sched_cache_fork_cleanup(p);
if (p->mm) {
mm_clear_owner(p->mm, p);
mmput(p->mm);
@@ -2613,8 +2623,6 @@ bad_fork_cleanup_sighand:
__cleanup_sighand(p->sighand);
bad_fork_cleanup_fs:
exit_fs(p); /* blocking */
-bad_fork_cleanup_files:
- exit_files(p); /* blocking */
bad_fork_cleanup_semundo:
exit_sem(p);
bad_fork_cleanup_security:
@@ -2625,6 +2633,8 @@ bad_fork_cleanup_perf:
perf_event_free_task(p);
bad_fork_sched_cancel_fork:
sched_cancel_fork(p);
+ /* ->release() of a file may need scx_fork_rwsem for write. */
+ exit_files(p); /* blocking */
bad_fork_cleanup_policy:
lockdep_free_task(p);
#ifdef CONFIG_NUMA
@@ -2669,7 +2679,7 @@ struct task_struct * __init fork_idle(int cpu)
{
struct task_struct *task;
struct kernel_clone_args args = {
- .flags = CLONE_VM,
+ .flags = CLONE_VM | CLONE_FS,
.fn = &idle_dummy,
.fn_arg = NULL,
.kthread = 1,
@@ -2703,6 +2713,10 @@ struct task_struct *create_io_thread(int (*fn)(void *), void *arg, int node)
.user_worker = 1,
};
+ /* A creator past its fatal signal or its coredump point gets no thread. */
+ if (current->flags & (PF_SIGNALED | PF_POSTCOREDUMP))
+ return ERR_PTR(-EINTR);
+
return copy_process(NULL, 0, node, &args);
}
@@ -3211,24 +3225,6 @@ static int unshare_fs(unsigned long unshare_flags, struct fs_struct **new_fsp)
}
/*
- * Unshare file descriptor table if it is being shared
- */
-static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp)
-{
- struct files_struct *fd = current->files;
-
- if ((unshare_flags & CLONE_FILES) &&
- (fd && atomic_read(&fd->count) > 1)) {
- fd = dup_fd(fd, NULL);
- if (IS_ERR(fd))
- return PTR_ERR(fd);
- *new_fdp = fd;
- }
-
- return 0;
-}
-
-/*
* unshare allows a process to 'unshare' part of the process
* context which was originally shared using clone. copy_*
* functions used by kernel_clone() cannot be used here directly
@@ -3315,18 +3311,19 @@ int ksys_unshare(unsigned long unshare_flags)
shm_init_task(current);
}
+ if (new_fs) {
+ new_fs = switch_fs_struct(new_fs);
+ if (new_fs)
+ free_fs_struct(no_free_ptr(new_fs));
+ }
+
if (new_nsproxy) {
switch_task_namespaces(current, new_nsproxy);
new_nsproxy = NULL;
}
- if (new_fs)
- new_fs = switch_fs_struct(new_fs);
-
- if (new_fd) {
- guard(task_lock)(current);
- swap(current->files, new_fd);
- }
+ if (new_fd)
+ switch_files_struct(current, no_free_ptr(new_fd));
if (new_cred) {
/* Install the new user namespace */
@@ -3338,8 +3335,9 @@ int ksys_unshare(unsigned long unshare_flags)
perf_event_namespaces(current);
bad_unshare_cleanup_nsproxy:
+ /* never installed, so no active references to drop */
if (new_nsproxy)
- put_nsproxy(new_nsproxy);
+ nsproxy_free(new_nsproxy);
bad_unshare_cleanup_cred:
if (new_cred)
put_cred(new_cred);
@@ -3359,30 +3357,6 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)
return ksys_unshare(unshare_flags);
}
-/*
- * 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 task_struct *task = current;
- struct files_struct *old, *copy = NULL;
- int error;
-
- error = unshare_fd(CLONE_FILES, &copy);
- if (error || !copy)
- return error;
-
- old = task->files;
- task_lock(task);
- task->files = copy;
- task_unlock(task);
- put_files_struct(old);
- return 0;
-}
-
static int sysctl_max_threads(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{