@@ -3,24 +3,47 @@
* pidfd-backed process spawn builders
*/
+#include <linux/cred.h>
#include <linux/file.h>
#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/namei.h>
#include <linux/pid.h>
+#include <linux/pid_namespace.h>
#include <linux/pidfd_spawn.h>
#include <linux/pidfs.h>
#include <linux/refcount.h>
+#include <linux/sched/signal.h>
#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/uaccess.h>
#include <uapi/linux/pidfd.h>
+#define PIDFD_SPAWN_MAX_CONFIG_KEY_SIZE 256
+
struct pidfd_spawn_state {
+ /* Serializes configuration and payload teardown. */
+ struct mutex lock;
refcount_t count;
struct pid *pid;
+ struct mm_struct *creator_mm;
+ const struct cred *creator_cred;
+ struct pid_namespace *creator_pid_ns;
+ char *staged_path;
};
static void pidfd_spawn_state_put(struct pidfd_spawn_state *state);
static void pidfd_spawn_free_state(struct pidfd_spawn_state *state)
{
+ kfree(state->staged_path);
+ if (state->creator_mm)
+ mmdrop(state->creator_mm);
+ if (state->creator_cred)
+ put_cred(state->creator_cred);
+ if (state->creator_pid_ns)
+ put_pid_ns(state->creator_pid_ns);
put_pid(state->pid);
kfree(state);
}@@ -31,6 +54,15 @@ static void pidfd_spawn_state_put(struct pidfd_spawn_state *state)
pidfd_spawn_free_state(state);
}
+static void pidfd_spawn_state_put_cleanup(struct pidfd_spawn_state *state)
+{
+ if (!IS_ERR_OR_NULL(state))
+ pidfd_spawn_state_put(state);
+}
+
+DEFINE_FREE(pidfd_spawn_state, struct pidfd_spawn_state *,
+ pidfd_spawn_state_put_cleanup(_T))
+
static void pidfd_spawn_state_release(void *data)
{
pidfd_spawn_state_put(data);@@ -50,17 +82,140 @@ static const struct pidfs_future_file_ops pidfd_spawn_future_ops = {
.release = pidfd_spawn_state_release,
};
+static struct pidfd_spawn_state *
+pidfd_spawn_state_get_file(struct file *file)
+{
+ struct pidfd_spawn_state *state;
+
+ state = pidfs_future_file_data(file, &pidfd_spawn_future_ops);
+ if (!state || !refcount_inc_not_zero(&state->count))
+ return ERR_PTR(state ? -ESRCH : -EINVAL);
+ return state;
+}
+
+static bool pidfd_spawn_same_creator(struct pidfd_spawn_state *state)
+{
+ return current->mm == state->creator_mm &&
+ current_cred() == state->creator_cred &&
+ current->nsproxy->pid_ns_for_children == state->creator_pid_ns;
+}
+
+static struct filename *pidfd_spawn_get_path(const char __user *value)
+{
+ CLASS(filename, name)(value);
+
+ if (IS_ERR(name))
+ return ERR_CAST(name);
+
+ return no_free_ptr(name);
+}
+
+static char *pidfd_spawn_copy_path(const char __user *value)
+{
+ struct filename *name __free(putname) = NULL;
+ char *path;
+
+ name = pidfd_spawn_get_path(value);
+ if (IS_ERR(name))
+ return ERR_CAST(name);
+ path = kstrdup(name->name, GFP_KERNEL_ACCOUNT);
+ if (!path)
+ return ERR_PTR(-ENOMEM);
+
+ return path;
+}
+
+static int pidfd_spawn_state_set_path(struct pidfd_spawn_state *state,
+ const char __user *value)
+{
+ char *path __free(kfree) = NULL;
+ char *old __free(kfree) = NULL;
+
+ path = pidfd_spawn_copy_path(value);
+ if (IS_ERR(path))
+ return PTR_ERR(path);
+
+ scoped_cond_guard(mutex_intr, return -EINTR, &state->lock) {
+ if (!pidfd_spawn_same_creator(state))
+ return -EPERM;
+
+ old = state->staged_path;
+ state->staged_path = no_free_ptr(path);
+ }
+ return 0;
+}
+
+static int pidfd_spawn_state_set_string(struct pidfd_spawn_state *state,
+ const char *key,
+ const void __user *value)
+{
+ if (!strcmp(key, PIDFD_CONFIG_KEY_PATH))
+ return pidfd_spawn_state_set_path(state, value);
+
+ return -EOPNOTSUPP;
+}
+
+SYSCALL_DEFINE5(pidfd_config, int, fd, unsigned int, cmd,
+ const char __user *, ukey, const void __user *, uvalue,
+ int, aux)
+{
+ char *key __free(kfree) = NULL;
+ struct pidfd_spawn_state *state __free(pidfd_spawn_state) = NULL;
+ int ret;
+
+ switch (cmd) {
+ case PIDFD_CONFIG_SET_STRING:
+ if (!ukey || !uvalue || aux)
+ return -EINVAL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ CLASS(fd, f)(fd);
+ if (fd_empty(f))
+ return -EBADF;
+
+ state = pidfd_spawn_state_get_file(fd_file(f));
+ if (IS_ERR(state))
+ return PTR_ERR(state);
+
+ key = strndup_user(ukey, PIDFD_SPAWN_MAX_CONFIG_KEY_SIZE);
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+
+ switch (cmd) {
+ case PIDFD_CONFIG_SET_STRING:
+ ret = pidfd_spawn_state_set_string(state, key, uvalue);
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ return ret;
+}
+
int pidfd_empty_open(unsigned int flags)
{
struct pidfd_spawn_state *state;
struct file *pidfile;
int pidfd;
+ if (!current->mm)
+ return -EINVAL;
+
state = kzalloc_obj(*state, GFP_KERNEL_ACCOUNT);
if (!state)
return -ENOMEM;
+ mutex_init(&state->lock);
refcount_set(&state->count, 1);
+ mmgrab(current->mm);
+ state->creator_mm = current->mm;
+ state->creator_cred = get_current_cred();
+ state->creator_pid_ns =
+ get_pid_ns(current->nsproxy->pid_ns_for_children);
pidfile = pidfs_alloc_future_file("[pidfd_spawn]", state,
&pidfd_spawn_future_ops,