-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCXdfjBwAKCRCRxhvAZXjc
 onCBAP47WZ/ie7yjoDWhOI1QB7II3NGSzToakxpgJaWoB+NjTwEA7PGrSYVEbPrf
 pUhiEaEJ29t+cWUxX3+yDO+k7SA6BAY=
 =Ra58
 -----END PGP SIGNATURE-----

Merge tag 'threads-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux

Pull thread management updates from Christian Brauner:

 - A pidfd's fdinfo file currently contains the field "Pid:\t<pid>"
   where <pid> is the pid of the process in the pid namespace of the
   procfs instance the fdinfo file for the pidfd was opened in.

   The fdinfo file has now gained a new "NSpid:\t<ns-pid1>[\t<ns-pid2>[...]]"
   field which lists the pids of the process in all child pid namespaces
   provided the pid namespace of the procfs instance it is looked up
   under has an ancestoral relationship with the pid namespace of the
   process. If it does not 0 will be shown and no further pid namespaces
   will be listed. Tests included. (Christian Kellner)

 - If the process the pidfd references has already exited, print -1 for
   the Pid and NSpid fields in the pidfd's fdinfo file. Tests included.
   (me)

 - Add CLONE_CLEAR_SIGHAND. This lets callers clear all signal handler
   that are not SIG_DFL or SIG_IGN at process creation time. This
   originated as a feature request from glibc to improve performance and
   elimate races in their posix_spawn() implementation. Tests included.
   (me)

 - Add support for choosing a specific pid for a process with clone3().
   This is the feature which was part of the thread update for v5.4 but
   after a discussion at LPC in Lisbon we decided to delay it for one
   more cycle in order to make the interface more generic. This has now
   done. It is now possible to choose a specific pid in a whole pid
   namespaces (sub)hierarchy instead of just one pid namespace. In order
   to choose a specific pid the caller must have CAP_SYS_ADMIN in all
   owning user namespaces of the target pid namespaces. Tests included.
   (Adrian Reber)

 - Test improvements and extensions. (Andrei Vagin, me)

* tag 'threads-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux:
  selftests/clone3: skip if clone3() is ENOSYS
  selftests/clone3: check that all pids are released on error paths
  selftests/clone3: report a correct number of fails
  selftests/clone3: flush stdout and stderr before clone3() and _exit()
  selftests: add tests for clone3() with *set_tid
  fork: extend clone3() to support setting a PID
  selftests: add tests for clone3()
  tests: test CLONE_CLEAR_SIGHAND
  clone3: add CLONE_CLEAR_SIGHAND
  pid: use pid_has_task() in pidfd_open()
  exit: use pid_has_task() in do_wait()
  pid: use pid_has_task() in __change_pid()
  test: verify fdinfo for pidfd of reaped process
  pidfd: check pid has attached task in fdinfo
  pidfd: add tests for NSpid info in fdinfo
  pidfd: add NSpid entries to fdinfo
This commit is contained in:
Linus Torvalds 2019-11-25 18:36:49 -08:00
Родитель 9c91e6a5be 11fde161ab
Коммит 0acefef584
18 изменённых файлов: 1307 добавлений и 57 удалений

Просмотреть файл

@ -12861,6 +12861,7 @@ S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/brauner/linux.git
F: samples/pidfd/ F: samples/pidfd/
F: tools/testing/selftests/pidfd/ F: tools/testing/selftests/pidfd/
F: tools/testing/selftests/clone3/
K: (?i)pidfd K: (?i)pidfd
K: (?i)clone3 K: (?i)clone3
K: \b(clone_args|kernel_clone_args)\b K: \b(clone_args|kernel_clone_args)\b

Просмотреть файл

@ -85,6 +85,10 @@ static inline struct pid *get_pid(struct pid *pid)
extern void put_pid(struct pid *pid); extern void put_pid(struct pid *pid);
extern struct task_struct *pid_task(struct pid *pid, enum pid_type); extern struct task_struct *pid_task(struct pid *pid, enum pid_type);
static inline bool pid_has_task(struct pid *pid, enum pid_type type)
{
return !hlist_empty(&pid->tasks[type]);
}
extern struct task_struct *get_pid_task(struct pid *pid, enum pid_type); extern struct task_struct *get_pid_task(struct pid *pid, enum pid_type);
extern struct pid *get_task_pid(struct task_struct *task, enum pid_type type); extern struct pid *get_task_pid(struct task_struct *task, enum pid_type type);
@ -120,7 +124,8 @@ extern struct pid *find_vpid(int nr);
extern struct pid *find_get_pid(int nr); extern struct pid *find_get_pid(int nr);
extern struct pid *find_ge_pid(int nr, struct pid_namespace *); extern struct pid *find_ge_pid(int nr, struct pid_namespace *);
extern struct pid *alloc_pid(struct pid_namespace *ns); extern struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
size_t set_tid_size);
extern void free_pid(struct pid *pid); extern void free_pid(struct pid *pid);
extern void disable_pid_allocation(struct pid_namespace *ns); extern void disable_pid_allocation(struct pid_namespace *ns);

Просмотреть файл

@ -12,6 +12,8 @@
#include <linux/ns_common.h> #include <linux/ns_common.h>
#include <linux/idr.h> #include <linux/idr.h>
/* MAX_PID_NS_LEVEL is needed for limiting size of 'struct pid' */
#define MAX_PID_NS_LEVEL 32
struct fs_pin; struct fs_pin;

Просмотреть файл

@ -26,6 +26,9 @@ struct kernel_clone_args {
unsigned long stack; unsigned long stack;
unsigned long stack_size; unsigned long stack_size;
unsigned long tls; unsigned long tls;
pid_t *set_tid;
/* Number of elements in *set_tid */
size_t set_tid_size;
}; };
/* /*

Просмотреть файл

@ -33,31 +33,48 @@
#define CLONE_NEWNET 0x40000000 /* New network namespace */ #define CLONE_NEWNET 0x40000000 /* New network namespace */
#define CLONE_IO 0x80000000 /* Clone io context */ #define CLONE_IO 0x80000000 /* Clone io context */
/* Flags for the clone3() syscall. */
#define CLONE_CLEAR_SIGHAND 0x100000000ULL /* Clear any signal handler and reset to SIG_DFL. */
#ifndef __ASSEMBLY__ #ifndef __ASSEMBLY__
/** /**
* struct clone_args - arguments for the clone3 syscall * struct clone_args - arguments for the clone3 syscall
* @flags: Flags for the new process as listed above. * @flags: Flags for the new process as listed above.
* All flags are valid except for CSIGNAL and * All flags are valid except for CSIGNAL and
* CLONE_DETACHED. * CLONE_DETACHED.
* @pidfd: If CLONE_PIDFD is set, a pidfd will be * @pidfd: If CLONE_PIDFD is set, a pidfd will be
* returned in this argument. * returned in this argument.
* @child_tid: If CLONE_CHILD_SETTID is set, the TID of the * @child_tid: If CLONE_CHILD_SETTID is set, the TID of the
* child process will be returned in the child's * child process will be returned in the child's
* memory. * memory.
* @parent_tid: If CLONE_PARENT_SETTID is set, the TID of * @parent_tid: If CLONE_PARENT_SETTID is set, the TID of
* the child process will be returned in the * the child process will be returned in the
* parent's memory. * parent's memory.
* @exit_signal: The exit_signal the parent process will be * @exit_signal: The exit_signal the parent process will be
* sent when the child exits. * sent when the child exits.
* @stack: Specify the location of the stack for the * @stack: Specify the location of the stack for the
* child process. * child process.
* Note, @stack is expected to point to the * Note, @stack is expected to point to the
* lowest address. The stack direction will be * lowest address. The stack direction will be
* determined by the kernel and set up * determined by the kernel and set up
* appropriately based on @stack_size. * appropriately based on @stack_size.
* @stack_size: The size of the stack for the child process. * @stack_size: The size of the stack for the child process.
* @tls: If CLONE_SETTLS is set, the tls descriptor * @tls: If CLONE_SETTLS is set, the tls descriptor
* is set to tls. * is set to tls.
* @set_tid: Pointer to an array of type *pid_t. The size
* of the array is defined using @set_tid_size.
* This array is used to select PIDs/TIDs for
* newly created processes. The first element in
* this defines the PID in the most nested PID
* namespace. Each additional element in the array
* defines the PID in the parent PID namespace of
* the original PID namespace. If the array has
* less entries than the number of currently
* nested PID namespaces only the PIDs in the
* corresponding namespaces are set.
* @set_tid_size: This defines the size of the array referenced
* in @set_tid. This cannot be larger than the
* kernel's limit of nested PID namespaces.
* *
* The structure is versioned by size and thus extensible. * The structure is versioned by size and thus extensible.
* New struct members must go at the end of the struct and * New struct members must go at the end of the struct and
@ -72,10 +89,13 @@ struct clone_args {
__aligned_u64 stack; __aligned_u64 stack;
__aligned_u64 stack_size; __aligned_u64 stack_size;
__aligned_u64 tls; __aligned_u64 tls;
__aligned_u64 set_tid;
__aligned_u64 set_tid_size;
}; };
#endif #endif
#define CLONE_ARGS_SIZE_VER0 64 /* sizeof first published struct */ #define CLONE_ARGS_SIZE_VER0 64 /* sizeof first published struct */
#define CLONE_ARGS_SIZE_VER1 80 /* sizeof second published struct */
/* /*
* Scheduling policies * Scheduling policies

Просмотреть файл

@ -1457,7 +1457,7 @@ repeat:
*/ */
wo->notask_error = -ECHILD; wo->notask_error = -ECHILD;
if ((wo->wo_type < PIDTYPE_MAX) && if ((wo->wo_type < PIDTYPE_MAX) &&
(!wo->wo_pid || hlist_empty(&wo->wo_pid->tasks[wo->wo_type]))) (!wo->wo_pid || !pid_has_task(wo->wo_pid, wo->wo_type)))
goto notask; goto notask;
set_current_state(TASK_INTERRUPTIBLE); set_current_state(TASK_INTERRUPTIBLE);

Просмотреть файл

@ -1517,6 +1517,11 @@ static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
spin_lock_irq(&current->sighand->siglock); spin_lock_irq(&current->sighand->siglock);
memcpy(sig->action, current->sighand->action, sizeof(sig->action)); memcpy(sig->action, current->sighand->action, sizeof(sig->action));
spin_unlock_irq(&current->sighand->siglock); spin_unlock_irq(&current->sighand->siglock);
/* Reset all signal handler not set to SIG_IGN to SIG_DFL. */
if (clone_flags & CLONE_CLEAR_SIGHAND)
flush_signal_handlers(tsk, 0);
return 0; return 0;
} }
@ -1695,12 +1700,68 @@ static int pidfd_release(struct inode *inode, struct file *file)
} }
#ifdef CONFIG_PROC_FS #ifdef CONFIG_PROC_FS
/**
* pidfd_show_fdinfo - print information about a pidfd
* @m: proc fdinfo file
* @f: file referencing a pidfd
*
* Pid:
* This function will print the pid that a given pidfd refers to in the
* pid namespace of the procfs instance.
* If the pid namespace of the process is not a descendant of the pid
* namespace of the procfs instance 0 will be shown as its pid. This is
* similar to calling getppid() on a process whose parent is outside of
* its pid namespace.
*
* NSpid:
* If pid namespaces are supported then this function will also print
* the pid of a given pidfd refers to for all descendant pid namespaces
* starting from the current pid namespace of the instance, i.e. the
* Pid field and the first entry in the NSpid field will be identical.
* If the pid namespace of the process is not a descendant of the pid
* namespace of the procfs instance 0 will be shown as its first NSpid
* entry and no others will be shown.
* Note that this differs from the Pid and NSpid fields in
* /proc/<pid>/status where Pid and NSpid are always shown relative to
* the pid namespace of the procfs instance. The difference becomes
* obvious when sending around a pidfd between pid namespaces from a
* different branch of the tree, i.e. where no ancestoral relation is
* present between the pid namespaces:
* - create two new pid namespaces ns1 and ns2 in the initial pid
* namespace (also take care to create new mount namespaces in the
* new pid namespace and mount procfs)
* - create a process with a pidfd in ns1
* - send pidfd from ns1 to ns2
* - read /proc/self/fdinfo/<pidfd> and observe that both Pid and NSpid
* have exactly one entry, which is 0
*/
static void pidfd_show_fdinfo(struct seq_file *m, struct file *f) static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
{ {
struct pid_namespace *ns = proc_pid_ns(file_inode(m->file));
struct pid *pid = f->private_data; struct pid *pid = f->private_data;
struct pid_namespace *ns;
pid_t nr = -1;
seq_put_decimal_ull(m, "Pid:\t", pid_nr_ns(pid, ns)); if (likely(pid_has_task(pid, PIDTYPE_PID))) {
ns = proc_pid_ns(file_inode(m->file));
nr = pid_nr_ns(pid, ns);
}
seq_put_decimal_ll(m, "Pid:\t", nr);
#ifdef CONFIG_PID_NS
seq_put_decimal_ll(m, "\nNSpid:\t", nr);
if (nr > 0) {
int i;
/* If nr is non-zero it means that 'pid' is valid and that
* ns, i.e. the pid namespace associated with the procfs
* instance, is in the pid namespace hierarchy of pid.
* Start at one below the already printed level.
*/
for (i = ns->level + 1; i <= pid->level; i++)
seq_put_decimal_ll(m, "\t", pid->numbers[i].nr);
}
#endif
seq_putc(m, '\n'); seq_putc(m, '\n');
} }
#endif #endif
@ -2026,7 +2087,8 @@ static __latent_entropy struct task_struct *copy_process(
stackleak_task_init(p); stackleak_task_init(p);
if (pid != &init_struct_pid) { if (pid != &init_struct_pid) {
pid = alloc_pid(p->nsproxy->pid_ns_for_children); pid = alloc_pid(p->nsproxy->pid_ns_for_children, args->set_tid,
args->set_tid_size);
if (IS_ERR(pid)) { if (IS_ERR(pid)) {
retval = PTR_ERR(pid); retval = PTR_ERR(pid);
goto bad_fork_cleanup_thread; goto bad_fork_cleanup_thread;
@ -2529,6 +2591,7 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
{ {
int err; int err;
struct clone_args args; struct clone_args args;
pid_t *kset_tid = kargs->set_tid;
if (unlikely(usize > PAGE_SIZE)) if (unlikely(usize > PAGE_SIZE))
return -E2BIG; return -E2BIG;
@ -2539,6 +2602,15 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
if (err) if (err)
return err; return err;
if (unlikely(args.set_tid_size > MAX_PID_NS_LEVEL))
return -EINVAL;
if (unlikely(!args.set_tid && args.set_tid_size > 0))
return -EINVAL;
if (unlikely(args.set_tid && args.set_tid_size == 0))
return -EINVAL;
/* /*
* Verify that higher 32bits of exit_signal are unset and that * Verify that higher 32bits of exit_signal are unset and that
* it is a valid signal * it is a valid signal
@ -2556,8 +2628,16 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
.stack = args.stack, .stack = args.stack,
.stack_size = args.stack_size, .stack_size = args.stack_size,
.tls = args.tls, .tls = args.tls,
.set_tid_size = args.set_tid_size,
}; };
if (args.set_tid &&
copy_from_user(kset_tid, u64_to_user_ptr(args.set_tid),
(kargs->set_tid_size * sizeof(pid_t))))
return -EFAULT;
kargs->set_tid = kset_tid;
return 0; return 0;
} }
@ -2591,11 +2671,8 @@ static inline bool clone3_stack_valid(struct kernel_clone_args *kargs)
static bool clone3_args_valid(struct kernel_clone_args *kargs) static bool clone3_args_valid(struct kernel_clone_args *kargs)
{ {
/* /* Verify that no unknown flags are passed along. */
* All lower bits of the flag word are taken. if (kargs->flags & ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND))
* Verify that no other unknown flags are passed along.
*/
if (kargs->flags & ~CLONE_LEGACY_FLAGS)
return false; return false;
/* /*
@ -2605,6 +2682,10 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs)
if (kargs->flags & (CLONE_DETACHED | CSIGNAL)) if (kargs->flags & (CLONE_DETACHED | CSIGNAL))
return false; return false;
if ((kargs->flags & (CLONE_SIGHAND | CLONE_CLEAR_SIGHAND)) ==
(CLONE_SIGHAND | CLONE_CLEAR_SIGHAND))
return false;
if ((kargs->flags & (CLONE_THREAD | CLONE_PARENT)) && if ((kargs->flags & (CLONE_THREAD | CLONE_PARENT)) &&
kargs->exit_signal) kargs->exit_signal)
return false; return false;
@ -2631,6 +2712,9 @@ SYSCALL_DEFINE2(clone3, struct clone_args __user *, uargs, size_t, size)
int err; int err;
struct kernel_clone_args kargs; struct kernel_clone_args kargs;
pid_t set_tid[MAX_PID_NS_LEVEL];
kargs.set_tid = set_tid;
err = copy_clone_args_from_user(&kargs, uargs, size); err = copy_clone_args_from_user(&kargs, uargs, size);
if (err) if (err)

Просмотреть файл

@ -157,7 +157,8 @@ void free_pid(struct pid *pid)
call_rcu(&pid->rcu, delayed_put_pid); call_rcu(&pid->rcu, delayed_put_pid);
} }
struct pid *alloc_pid(struct pid_namespace *ns) struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
size_t set_tid_size)
{ {
struct pid *pid; struct pid *pid;
enum pid_type type; enum pid_type type;
@ -166,6 +167,17 @@ struct pid *alloc_pid(struct pid_namespace *ns)
struct upid *upid; struct upid *upid;
int retval = -ENOMEM; int retval = -ENOMEM;
/*
* set_tid_size contains the size of the set_tid array. Starting at
* the most nested currently active PID namespace it tells alloc_pid()
* which PID to set for a process in that most nested PID namespace
* up to set_tid_size PID namespaces. It does not have to set the PID
* for a process in all nested PID namespaces but set_tid_size must
* never be greater than the current ns->level + 1.
*/
if (set_tid_size > ns->level + 1)
return ERR_PTR(-EINVAL);
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid) if (!pid)
return ERR_PTR(retval); return ERR_PTR(retval);
@ -174,24 +186,54 @@ struct pid *alloc_pid(struct pid_namespace *ns)
pid->level = ns->level; pid->level = ns->level;
for (i = ns->level; i >= 0; i--) { for (i = ns->level; i >= 0; i--) {
int pid_min = 1; int tid = 0;
if (set_tid_size) {
tid = set_tid[ns->level - i];
retval = -EINVAL;
if (tid < 1 || tid >= pid_max)
goto out_free;
/*
* Also fail if a PID != 1 is requested and
* no PID 1 exists.
*/
if (tid != 1 && !tmp->child_reaper)
goto out_free;
retval = -EPERM;
if (!ns_capable(tmp->user_ns, CAP_SYS_ADMIN))
goto out_free;
set_tid_size--;
}
idr_preload(GFP_KERNEL); idr_preload(GFP_KERNEL);
spin_lock_irq(&pidmap_lock); spin_lock_irq(&pidmap_lock);
/* if (tid) {
* init really needs pid 1, but after reaching the maximum nr = idr_alloc(&tmp->idr, NULL, tid,
* wrap back to RESERVED_PIDS tid + 1, GFP_ATOMIC);
*/ /*
if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS) * If ENOSPC is returned it means that the PID is
pid_min = RESERVED_PIDS; * alreay in use. Return EEXIST in that case.
*/
if (nr == -ENOSPC)
nr = -EEXIST;
} else {
int pid_min = 1;
/*
* init really needs pid 1, but after reaching the
* maximum wrap back to RESERVED_PIDS
*/
if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
pid_min = RESERVED_PIDS;
/* /*
* Store a null pointer so find_pid_ns does not find * Store a null pointer so find_pid_ns does not find
* a partially initialized PID (see below). * a partially initialized PID (see below).
*/ */
nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min, nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
pid_max, GFP_ATOMIC); pid_max, GFP_ATOMIC);
}
spin_unlock_irq(&pidmap_lock); spin_unlock_irq(&pidmap_lock);
idr_preload_end(); idr_preload_end();
@ -299,7 +341,7 @@ static void __change_pid(struct task_struct *task, enum pid_type type,
*pid_ptr = new; *pid_ptr = new;
for (tmp = PIDTYPE_MAX; --tmp >= 0; ) for (tmp = PIDTYPE_MAX; --tmp >= 0; )
if (!hlist_empty(&pid->tasks[tmp])) if (pid_has_task(pid, tmp))
return; return;
free_pid(pid); free_pid(pid);
@ -497,7 +539,7 @@ static int pidfd_create(struct pid *pid)
*/ */
SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags) SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags)
{ {
int fd, ret; int fd;
struct pid *p; struct pid *p;
if (flags) if (flags)
@ -510,13 +552,11 @@ SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags)
if (!p) if (!p)
return -ESRCH; return -ESRCH;
ret = 0; if (pid_has_task(p, PIDTYPE_TGID))
rcu_read_lock(); fd = pidfd_create(p);
if (!pid_task(p, PIDTYPE_TGID)) else
ret = -EINVAL; fd = -EINVAL;
rcu_read_unlock();
fd = ret ?: pidfd_create(p);
put_pid(p); put_pid(p);
return fd; return fd;
} }

Просмотреть файл

@ -26,8 +26,6 @@
static DEFINE_MUTEX(pid_caches_mutex); static DEFINE_MUTEX(pid_caches_mutex);
static struct kmem_cache *pid_ns_cachep; static struct kmem_cache *pid_ns_cachep;
/* MAX_PID_NS_LEVEL is needed for limiting size of 'struct pid' */
#define MAX_PID_NS_LEVEL 32
/* Write once array, filled from the beginning. */ /* Write once array, filled from the beginning. */
static struct kmem_cache *pid_cache[MAX_PID_NS_LEVEL]; static struct kmem_cache *pid_cache[MAX_PID_NS_LEVEL];

Просмотреть файл

@ -5,6 +5,7 @@ TARGETS += bpf
TARGETS += breakpoints TARGETS += breakpoints
TARGETS += capabilities TARGETS += capabilities
TARGETS += cgroup TARGETS += cgroup
TARGETS += clone3
TARGETS += cpufreq TARGETS += cpufreq
TARGETS += cpu-hotplug TARGETS += cpu-hotplug
TARGETS += drivers/dma-buf TARGETS += drivers/dma-buf

3
tools/testing/selftests/clone3/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
clone3
clone3_clear_sighand
clone3_set_tid

Просмотреть файл

@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -g -I../../../../usr/include/
TEST_GEN_PROGS := clone3 clone3_clear_sighand clone3_set_tid
include ../lib.mk

Просмотреть файл

@ -0,0 +1,202 @@
// SPDX-License-Identifier: GPL-2.0
/* Based on Christian Brauner's clone3() example */
#define _GNU_SOURCE
#include <errno.h>
#include <inttypes.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sched.h>
#include "../kselftest.h"
#include "clone3_selftests.h"
/*
* Different sizes of struct clone_args
*/
#ifndef CLONE3_ARGS_SIZE_V0
#define CLONE3_ARGS_SIZE_V0 64
#endif
enum test_mode {
CLONE3_ARGS_NO_TEST,
CLONE3_ARGS_ALL_0,
CLONE3_ARGS_INVAL_EXIT_SIGNAL_BIG,
CLONE3_ARGS_INVAL_EXIT_SIGNAL_NEG,
CLONE3_ARGS_INVAL_EXIT_SIGNAL_CSIG,
CLONE3_ARGS_INVAL_EXIT_SIGNAL_NSIG,
};
static int call_clone3(uint64_t flags, size_t size, enum test_mode test_mode)
{
struct clone_args args = {
.flags = flags,
.exit_signal = SIGCHLD,
};
struct clone_args_extended {
struct clone_args args;
__aligned_u64 excess_space[2];
} args_ext;
pid_t pid = -1;
int status;
memset(&args_ext, 0, sizeof(args_ext));
if (size > sizeof(struct clone_args))
args_ext.excess_space[1] = 1;
if (size == 0)
size = sizeof(struct clone_args);
switch (test_mode) {
case CLONE3_ARGS_ALL_0:
args.flags = 0;
args.exit_signal = 0;
break;
case CLONE3_ARGS_INVAL_EXIT_SIGNAL_BIG:
args.exit_signal = 0xbadc0ded00000000ULL;
break;
case CLONE3_ARGS_INVAL_EXIT_SIGNAL_NEG:
args.exit_signal = 0x0000000080000000ULL;
break;
case CLONE3_ARGS_INVAL_EXIT_SIGNAL_CSIG:
args.exit_signal = 0x0000000000000100ULL;
break;
case CLONE3_ARGS_INVAL_EXIT_SIGNAL_NSIG:
args.exit_signal = 0x00000000000000f0ULL;
break;
}
memcpy(&args_ext.args, &args, sizeof(struct clone_args));
pid = sys_clone3((struct clone_args *)&args_ext, size);
if (pid < 0) {
ksft_print_msg("%s - Failed to create new process\n",
strerror(errno));
return -errno;
}
if (pid == 0) {
ksft_print_msg("I am the child, my PID is %d\n", getpid());
_exit(EXIT_SUCCESS);
}
ksft_print_msg("I am the parent (%d). My child's pid is %d\n",
getpid(), pid);
if (waitpid(-1, &status, __WALL) < 0) {
ksft_print_msg("Child returned %s\n", strerror(errno));
return -errno;
}
if (WEXITSTATUS(status))
return WEXITSTATUS(status);
return 0;
}
static void test_clone3(uint64_t flags, size_t size, int expected,
enum test_mode test_mode)
{
int ret;
ksft_print_msg(
"[%d] Trying clone3() with flags %#" PRIx64 " (size %zu)\n",
getpid(), flags, size);
ret = call_clone3(flags, size, test_mode);
ksft_print_msg("[%d] clone3() with flags says: %d expected %d\n",
getpid(), ret, expected);
if (ret != expected)
ksft_test_result_fail(
"[%d] Result (%d) is different than expected (%d)\n",
getpid(), ret, expected);
else
ksft_test_result_pass(
"[%d] Result (%d) matches expectation (%d)\n",
getpid(), ret, expected);
}
int main(int argc, char *argv[])
{
pid_t pid;
uid_t uid = getuid();
test_clone3_supported();
ksft_print_header();
ksft_set_plan(17);
/* Just a simple clone3() should return 0.*/
test_clone3(0, 0, 0, CLONE3_ARGS_NO_TEST);
/* Do a clone3() in a new PID NS.*/
if (uid == 0)
test_clone3(CLONE_NEWPID, 0, 0, CLONE3_ARGS_NO_TEST);
else
ksft_test_result_skip("Skipping clone3() with CLONE_NEWPID\n");
/* Do a clone3() with CLONE3_ARGS_SIZE_V0. */
test_clone3(0, CLONE3_ARGS_SIZE_V0, 0, CLONE3_ARGS_NO_TEST);
/* Do a clone3() with CLONE3_ARGS_SIZE_V0 - 8 */
test_clone3(0, CLONE3_ARGS_SIZE_V0 - 8, -EINVAL, CLONE3_ARGS_NO_TEST);
/* Do a clone3() with sizeof(struct clone_args) + 8 */
test_clone3(0, sizeof(struct clone_args) + 8, 0, CLONE3_ARGS_NO_TEST);
/* Do a clone3() with exit_signal having highest 32 bits non-zero */
test_clone3(0, 0, -EINVAL, CLONE3_ARGS_INVAL_EXIT_SIGNAL_BIG);
/* Do a clone3() with negative 32-bit exit_signal */
test_clone3(0, 0, -EINVAL, CLONE3_ARGS_INVAL_EXIT_SIGNAL_NEG);
/* Do a clone3() with exit_signal not fitting into CSIGNAL mask */
test_clone3(0, 0, -EINVAL, CLONE3_ARGS_INVAL_EXIT_SIGNAL_CSIG);
/* Do a clone3() with NSIG < exit_signal < CSIG */
test_clone3(0, 0, -EINVAL, CLONE3_ARGS_INVAL_EXIT_SIGNAL_NSIG);
test_clone3(0, sizeof(struct clone_args) + 8, 0, CLONE3_ARGS_ALL_0);
test_clone3(0, sizeof(struct clone_args) + 16, -E2BIG,
CLONE3_ARGS_ALL_0);
test_clone3(0, sizeof(struct clone_args) * 2, -E2BIG,
CLONE3_ARGS_ALL_0);
/* Do a clone3() with > page size */
test_clone3(0, getpagesize() + 8, -E2BIG, CLONE3_ARGS_NO_TEST);
/* Do a clone3() with CLONE3_ARGS_SIZE_V0 in a new PID NS. */
if (uid == 0)
test_clone3(CLONE_NEWPID, CLONE3_ARGS_SIZE_V0, 0,
CLONE3_ARGS_NO_TEST);
else
ksft_test_result_skip("Skipping clone3() with CLONE_NEWPID\n");
/* Do a clone3() with CLONE3_ARGS_SIZE_V0 - 8 in a new PID NS */
test_clone3(CLONE_NEWPID, CLONE3_ARGS_SIZE_V0 - 8, -EINVAL,
CLONE3_ARGS_NO_TEST);
/* Do a clone3() with sizeof(struct clone_args) + 8 in a new PID NS */
if (uid == 0)
test_clone3(CLONE_NEWPID, sizeof(struct clone_args) + 8, 0,
CLONE3_ARGS_NO_TEST);
else
ksft_test_result_skip("Skipping clone3() with CLONE_NEWPID\n");
/* Do a clone3() with > page size in a new PID NS */
test_clone3(CLONE_NEWPID, getpagesize() + 8, -E2BIG,
CLONE3_ARGS_NO_TEST);
return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail();
}

Просмотреть файл

@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include "../kselftest.h"
#include "clone3_selftests.h"
#ifndef CLONE_CLEAR_SIGHAND
#define CLONE_CLEAR_SIGHAND 0x100000000ULL
#endif
static void nop_handler(int signo)
{
}
static int wait_for_pid(pid_t pid)
{
int status, ret;
again:
ret = waitpid(pid, &status, 0);
if (ret == -1) {
if (errno == EINTR)
goto again;
return -1;
}
if (!WIFEXITED(status))
return -1;
return WEXITSTATUS(status);
}
static void test_clone3_clear_sighand(void)
{
int ret;
pid_t pid;
struct clone_args args = {};
struct sigaction act;
/*
* Check that CLONE_CLEAR_SIGHAND and CLONE_SIGHAND are mutually
* exclusive.
*/
args.flags |= CLONE_CLEAR_SIGHAND | CLONE_SIGHAND;
args.exit_signal = SIGCHLD;
pid = sys_clone3(&args, sizeof(args));
if (pid > 0)
ksft_exit_fail_msg(
"clone3(CLONE_CLEAR_SIGHAND | CLONE_SIGHAND) succeeded\n");
act.sa_handler = nop_handler;
ret = sigemptyset(&act.sa_mask);
if (ret < 0)
ksft_exit_fail_msg("%s - sigemptyset() failed\n",
strerror(errno));
act.sa_flags = 0;
/* Register signal handler for SIGUSR1 */
ret = sigaction(SIGUSR1, &act, NULL);
if (ret < 0)
ksft_exit_fail_msg(
"%s - sigaction(SIGUSR1, &act, NULL) failed\n",
strerror(errno));
/* Register signal handler for SIGUSR2 */
ret = sigaction(SIGUSR2, &act, NULL);
if (ret < 0)
ksft_exit_fail_msg(
"%s - sigaction(SIGUSR2, &act, NULL) failed\n",
strerror(errno));
/* Check that CLONE_CLEAR_SIGHAND works. */
args.flags = CLONE_CLEAR_SIGHAND;
pid = sys_clone3(&args, sizeof(args));
if (pid < 0)
ksft_exit_fail_msg("%s - clone3(CLONE_CLEAR_SIGHAND) failed\n",
strerror(errno));
if (pid == 0) {
ret = sigaction(SIGUSR1, NULL, &act);
if (ret < 0)
exit(EXIT_FAILURE);
if (act.sa_handler != SIG_DFL)
exit(EXIT_FAILURE);
ret = sigaction(SIGUSR2, NULL, &act);
if (ret < 0)
exit(EXIT_FAILURE);
if (act.sa_handler != SIG_DFL)
exit(EXIT_FAILURE);
exit(EXIT_SUCCESS);
}
ret = wait_for_pid(pid);
if (ret)
ksft_exit_fail_msg(
"Failed to clear signal handler for child process\n");
ksft_test_result_pass("Cleared signal handlers for child process\n");
}
int main(int argc, char **argv)
{
ksft_print_header();
test_clone3_supported();
ksft_set_plan(1);
test_clone3_clear_sighand();
return ksft_exit_pass();
}

Просмотреть файл

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _CLONE3_SELFTESTS_H
#define _CLONE3_SELFTESTS_H
#define _GNU_SOURCE
#include <sched.h>
#include <stdint.h>
#include <syscall.h>
#include <linux/types.h>
#define ptr_to_u64(ptr) ((__u64)((uintptr_t)(ptr)))
#ifndef __NR_clone3
#define __NR_clone3 -1
struct clone_args {
__aligned_u64 flags;
__aligned_u64 pidfd;
__aligned_u64 child_tid;
__aligned_u64 parent_tid;
__aligned_u64 exit_signal;
__aligned_u64 stack;
__aligned_u64 stack_size;
__aligned_u64 tls;
__aligned_u64 set_tid;
__aligned_u64 set_tid_size;
};
#endif
static pid_t sys_clone3(struct clone_args *args, size_t size)
{
fflush(stdout);
fflush(stderr);
return syscall(__NR_clone3, args, size);
}
static inline void test_clone3_supported(void)
{
pid_t pid;
struct clone_args args = {};
if (__NR_clone3 < 0)
ksft_exit_skip("clone3() syscall is not supported\n");
/* Set to something that will always cause EINVAL. */
args.exit_signal = -1;
pid = sys_clone3(&args, sizeof(args));
if (!pid)
exit(EXIT_SUCCESS);
if (pid > 0) {
wait(NULL);
ksft_exit_fail_msg(
"Managed to create child process with invalid exit_signal\n");
}
if (errno == ENOSYS)
ksft_exit_skip("clone3() syscall is not supported\n");
ksft_print_msg("clone3() syscall supported\n");
}
#endif /* _CLONE3_SELFTESTS_H */

Просмотреть файл

@ -0,0 +1,397 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Based on Christian Brauner's clone3() example.
* These tests are assuming to be running in the host's
* PID namespace.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sched.h>
#include "../kselftest.h"
#include "clone3_selftests.h"
#ifndef MAX_PID_NS_LEVEL
#define MAX_PID_NS_LEVEL 32
#endif
static int pipe_1[2];
static int pipe_2[2];
static void child_exit(int ret)
{
fflush(stdout);
fflush(stderr);
_exit(ret);
}
static int call_clone3_set_tid(pid_t *set_tid,
size_t set_tid_size,
int flags,
int expected_pid,
bool wait_for_it)
{
int status;
pid_t pid = -1;
struct clone_args args = {
.flags = flags,
.exit_signal = SIGCHLD,
.set_tid = ptr_to_u64(set_tid),
.set_tid_size = set_tid_size,
};
pid = sys_clone3(&args, sizeof(struct clone_args));
if (pid < 0) {
ksft_print_msg("%s - Failed to create new process\n",
strerror(errno));
return -errno;
}
if (pid == 0) {
int ret;
char tmp = 0;
int exit_code = EXIT_SUCCESS;
ksft_print_msg("I am the child, my PID is %d (expected %d)\n",
getpid(), set_tid[0]);
if (wait_for_it) {
ksft_print_msg("[%d] Child is ready and waiting\n",
getpid());
/* Signal the parent that the child is ready */
close(pipe_1[0]);
ret = write(pipe_1[1], &tmp, 1);
if (ret != 1) {
ksft_print_msg(
"Writing to pipe returned %d", ret);
exit_code = EXIT_FAILURE;
}
close(pipe_1[1]);
close(pipe_2[1]);
ret = read(pipe_2[0], &tmp, 1);
if (ret != 1) {
ksft_print_msg(
"Reading from pipe returned %d", ret);
exit_code = EXIT_FAILURE;
}
close(pipe_2[0]);
}
if (set_tid[0] != getpid())
child_exit(EXIT_FAILURE);
child_exit(exit_code);
}
if (expected_pid == 0 || expected_pid == pid) {
ksft_print_msg("I am the parent (%d). My child's pid is %d\n",
getpid(), pid);
} else {
ksft_print_msg(
"Expected child pid %d does not match actual pid %d\n",
expected_pid, pid);
return -1;
}
if (waitpid(pid, &status, 0) < 0) {
ksft_print_msg("Child returned %s\n", strerror(errno));
return -errno;
}
if (!WIFEXITED(status))
return -1;
return WEXITSTATUS(status);
}
static void test_clone3_set_tid(pid_t *set_tid,
size_t set_tid_size,
int flags,
int expected,
int expected_pid,
bool wait_for_it)
{
int ret;
ksft_print_msg(
"[%d] Trying clone3() with CLONE_SET_TID to %d and 0x%x\n",
getpid(), set_tid[0], flags);
ret = call_clone3_set_tid(set_tid, set_tid_size, flags, expected_pid,
wait_for_it);
ksft_print_msg(
"[%d] clone3() with CLONE_SET_TID %d says :%d - expected %d\n",
getpid(), set_tid[0], ret, expected);
if (ret != expected)
ksft_test_result_fail(
"[%d] Result (%d) is different than expected (%d)\n",
getpid(), ret, expected);
else
ksft_test_result_pass(
"[%d] Result (%d) matches expectation (%d)\n",
getpid(), ret, expected);
}
int main(int argc, char *argv[])
{
FILE *f;
char buf;
char *line;
int status;
int ret = -1;
size_t len = 0;
int pid_max = 0;
uid_t uid = getuid();
char proc_path[100] = {0};
pid_t pid, ns1, ns2, ns3, ns_pid;
pid_t set_tid[MAX_PID_NS_LEVEL * 2];
ksft_print_header();
test_clone3_supported();
ksft_set_plan(29);
if (pipe(pipe_1) < 0 || pipe(pipe_2) < 0)
ksft_exit_fail_msg("pipe() failed\n");
f = fopen("/proc/sys/kernel/pid_max", "r");
if (f == NULL)
ksft_exit_fail_msg(
"%s - Could not open /proc/sys/kernel/pid_max\n",
strerror(errno));
fscanf(f, "%d", &pid_max);
fclose(f);
ksft_print_msg("/proc/sys/kernel/pid_max %d\n", pid_max);
/* Try invalid settings */
memset(&set_tid, 0, sizeof(set_tid));
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL + 1, 0, -EINVAL, 0, 0);
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL * 2, 0, -EINVAL, 0, 0);
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL * 2 + 1, 0,
-EINVAL, 0, 0);
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL * 42, 0, -EINVAL, 0, 0);
/*
* This can actually work if this test running in a MAX_PID_NS_LEVEL - 1
* nested PID namespace.
*/
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL - 1, 0, -EINVAL, 0, 0);
memset(&set_tid, 0xff, sizeof(set_tid));
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL + 1, 0, -EINVAL, 0, 0);
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL * 2, 0, -EINVAL, 0, 0);
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL * 2 + 1, 0,
-EINVAL, 0, 0);
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL * 42, 0, -EINVAL, 0, 0);
/*
* This can actually work if this test running in a MAX_PID_NS_LEVEL - 1
* nested PID namespace.
*/
test_clone3_set_tid(set_tid, MAX_PID_NS_LEVEL - 1, 0, -EINVAL, 0, 0);
memset(&set_tid, 0, sizeof(set_tid));
/* Try with an invalid PID */
set_tid[0] = 0;
test_clone3_set_tid(set_tid, 1, 0, -EINVAL, 0, 0);
set_tid[0] = -1;
test_clone3_set_tid(set_tid, 1, 0, -EINVAL, 0, 0);
/* Claim that the set_tid array actually contains 2 elements. */
test_clone3_set_tid(set_tid, 2, 0, -EINVAL, 0, 0);
/* Try it in a new PID namespace */
if (uid == 0)
test_clone3_set_tid(set_tid, 1, CLONE_NEWPID, -EINVAL, 0, 0);
else
ksft_test_result_skip("Clone3() with set_tid requires root\n");
/* Try with a valid PID (1) this should return -EEXIST. */
set_tid[0] = 1;
if (uid == 0)
test_clone3_set_tid(set_tid, 1, 0, -EEXIST, 0, 0);
else
ksft_test_result_skip("Clone3() with set_tid requires root\n");
/* Try it in a new PID namespace */
if (uid == 0)
test_clone3_set_tid(set_tid, 1, CLONE_NEWPID, 0, 0, 0);
else
ksft_test_result_skip("Clone3() with set_tid requires root\n");
/* pid_max should fail everywhere */
set_tid[0] = pid_max;
test_clone3_set_tid(set_tid, 1, 0, -EINVAL, 0, 0);
if (uid == 0)
test_clone3_set_tid(set_tid, 1, CLONE_NEWPID, -EINVAL, 0, 0);
else
ksft_test_result_skip("Clone3() with set_tid requires root\n");
if (uid != 0) {
/*
* All remaining tests require root. Tell the framework
* that all those tests are skipped as non-root.
*/
ksft_cnt.ksft_xskip += ksft_plan - ksft_test_num();
goto out;
}
/* Find the current active PID */
pid = fork();
if (pid == 0) {
ksft_print_msg("Child has PID %d\n", getpid());
child_exit(EXIT_SUCCESS);
}
if (waitpid(pid, &status, 0) < 0)
ksft_exit_fail_msg("Waiting for child %d failed", pid);
/* After the child has finished, its PID should be free. */
set_tid[0] = pid;
test_clone3_set_tid(set_tid, 1, 0, 0, 0, 0);
/* This should fail as there is no PID 1 in that namespace */
test_clone3_set_tid(set_tid, 1, CLONE_NEWPID, -EINVAL, 0, 0);
/*
* Creating a process with PID 1 in the newly created most nested
* PID namespace and PID 'pid' in the parent PID namespace. This
* needs to work.
*/
set_tid[0] = 1;
set_tid[1] = pid;
test_clone3_set_tid(set_tid, 2, CLONE_NEWPID, 0, pid, 0);
ksft_print_msg("unshare PID namespace\n");
if (unshare(CLONE_NEWPID) == -1)
ksft_exit_fail_msg("unshare(CLONE_NEWPID) failed: %s\n",
strerror(errno));
set_tid[0] = pid;
/* This should fail as there is no PID 1 in that namespace */
test_clone3_set_tid(set_tid, 1, 0, -EINVAL, 0, 0);
/* Let's create a PID 1 */
ns_pid = fork();
if (ns_pid == 0) {
/*
* This and the next test cases check that all pid-s are
* released on error paths.
*/
set_tid[0] = 43;
set_tid[1] = -1;
test_clone3_set_tid(set_tid, 2, 0, -EINVAL, 0, 0);
set_tid[0] = 43;
set_tid[1] = pid;
test_clone3_set_tid(set_tid, 2, 0, 0, 43, 0);
ksft_print_msg("Child in PID namespace has PID %d\n", getpid());
set_tid[0] = 2;
test_clone3_set_tid(set_tid, 1, 0, 0, 2, 0);
set_tid[0] = 1;
set_tid[1] = -1;
set_tid[2] = pid;
/* This should fail as there is invalid PID at level '1'. */
test_clone3_set_tid(set_tid, 3, CLONE_NEWPID, -EINVAL, 0, 0);
set_tid[0] = 1;
set_tid[1] = 42;
set_tid[2] = pid;
/*
* This should fail as there are not enough active PID
* namespaces. Again assuming this is running in the host's
* PID namespace. Not yet nested.
*/
test_clone3_set_tid(set_tid, 4, CLONE_NEWPID, -EINVAL, 0, 0);
/*
* This should work and from the parent we should see
* something like 'NSpid: pid 42 1'.
*/
test_clone3_set_tid(set_tid, 3, CLONE_NEWPID, 0, 42, true);
child_exit(ksft_cnt.ksft_fail);
}
close(pipe_1[1]);
close(pipe_2[0]);
while (read(pipe_1[0], &buf, 1) > 0) {
ksft_print_msg("[%d] Child is ready and waiting\n", getpid());
break;
}
snprintf(proc_path, sizeof(proc_path), "/proc/%d/status", pid);
f = fopen(proc_path, "r");
if (f == NULL)
ksft_exit_fail_msg(
"%s - Could not open %s\n",
strerror(errno), proc_path);
while (getline(&line, &len, f) != -1) {
if (strstr(line, "NSpid")) {
int i;
/* Verify that all generated PIDs are as expected. */
i = sscanf(line, "NSpid:\t%d\t%d\t%d",
&ns3, &ns2, &ns1);
if (i != 3) {
ksft_print_msg(
"Unexpected 'NSPid:' entry: %s",
line);
ns1 = ns2 = ns3 = 0;
}
break;
}
}
fclose(f);
free(line);
close(pipe_2[0]);
/* Tell the clone3()'d child to finish. */
write(pipe_2[1], &buf, 1);
close(pipe_2[1]);
if (waitpid(ns_pid, &status, 0) < 0) {
ksft_print_msg("Child returned %s\n", strerror(errno));
ret = -errno;
goto out;
}
if (!WIFEXITED(status))
ksft_test_result_fail("Child error\n");
ksft_cnt.ksft_pass += 6 - (ksft_cnt.ksft_fail - WEXITSTATUS(status));
ksft_cnt.ksft_fail = WEXITSTATUS(status);
if (ns3 == pid && ns2 == 42 && ns1 == 1)
ksft_test_result_pass(
"PIDs in all namespaces as expected (%d,%d,%d)\n",
ns3, ns2, ns1);
else
ksft_test_result_fail(
"PIDs in all namespaces not as expected (%d,%d,%d)\n",
ns3, ns2, ns1);
out:
ret = 0;
return !ret ? ksft_exit_pass() : ksft_exit_fail();
}

Просмотреть файл

@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -g -I../../../../usr/include/ -pthread CFLAGS += -g -I../../../../usr/include/ -pthread
TEST_GEN_PROGS := pidfd_test pidfd_open_test pidfd_poll_test pidfd_wait TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test pidfd_poll_test pidfd_wait
include ../lib.mk include ../lib.mk

Просмотреть файл

@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/types.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/wait.h>
#include "pidfd.h"
#include "../kselftest.h"
struct error {
int code;
char msg[512];
};
static int error_set(struct error *err, int code, const char *fmt, ...)
{
va_list args;
int r;
if (code == PIDFD_PASS || !err || err->code != PIDFD_PASS)
return code;
err->code = code;
va_start(args, fmt);
r = vsnprintf(err->msg, sizeof(err->msg), fmt, args);
assert((size_t)r < sizeof(err->msg));
va_end(args);
return code;
}
static void error_report(struct error *err, const char *test_name)
{
switch (err->code) {
case PIDFD_ERROR:
ksft_exit_fail_msg("%s test: Fatal: %s\n", test_name, err->msg);
break;
case PIDFD_FAIL:
/* will be: not ok %d # error %s test: %s */
ksft_test_result_error("%s test: %s\n", test_name, err->msg);
break;
case PIDFD_SKIP:
/* will be: not ok %d # SKIP %s test: %s */
ksft_test_result_skip("%s test: %s\n", test_name, err->msg);
break;
case PIDFD_XFAIL:
ksft_test_result_pass("%s test: Expected failure: %s\n",
test_name, err->msg);
break;
case PIDFD_PASS:
ksft_test_result_pass("%s test: Passed\n");
break;
default:
ksft_exit_fail_msg("%s test: Unknown code: %d %s\n",
test_name, err->code, err->msg);
break;
}
}
static inline int error_check(struct error *err, const char *test_name)
{
/* In case of error we bail out and terminate the test program */
if (err->code == PIDFD_ERROR)
error_report(err, test_name);
return err->code;
}
struct child {
pid_t pid;
int fd;
};
static struct child clone_newns(int (*fn)(void *), void *args,
struct error *err)
{
static int flags = CLONE_PIDFD | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD;
size_t stack_size = 1024;
char *stack[1024] = { 0 };
struct child ret;
if (!(flags & CLONE_NEWUSER) && geteuid() != 0)
flags |= CLONE_NEWUSER;
#ifdef __ia64__
ret.pid = __clone2(fn, stack, stack_size, flags, args, &ret.fd);
#else
ret.pid = clone(fn, stack + stack_size, flags, args, &ret.fd);
#endif
if (ret.pid < 0) {
error_set(err, PIDFD_ERROR, "clone failed (ret %d, errno %d)",
ret.fd, errno);
return ret;
}
ksft_print_msg("New child: %d, fd: %d\n", ret.pid, ret.fd);
return ret;
}
static inline void child_close(struct child *child)
{
close(child->fd);
}
static inline int child_join(struct child *child, struct error *err)
{
int r;
r = wait_for_pid(child->pid);
if (r < 0)
error_set(err, PIDFD_ERROR, "waitpid failed (ret %d, errno %d)",
r, errno);
else if (r > 0)
error_set(err, r, "child %d reported: %d", child->pid, r);
return r;
}
static inline int child_join_close(struct child *child, struct error *err)
{
child_close(child);
return child_join(child, err);
}
static inline void trim_newline(char *str)
{
char *pos = strrchr(str, '\n');
if (pos)
*pos = '\0';
}
static int verify_fdinfo(int pidfd, struct error *err, const char *prefix,
size_t prefix_len, const char *expect, ...)
{
char buffer[512] = {0, };
char path[512] = {0, };
va_list args;
FILE *f;
char *line = NULL;
size_t n = 0;
int found = 0;
int r;
va_start(args, expect);
r = vsnprintf(buffer, sizeof(buffer), expect, args);
assert((size_t)r < sizeof(buffer));
va_end(args);
snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd);
f = fopen(path, "re");
if (!f)
return error_set(err, PIDFD_ERROR, "fdinfo open failed for %d",
pidfd);
while (getline(&line, &n, f) != -1) {
char *val;
if (strncmp(line, prefix, prefix_len))
continue;
found = 1;
val = line + prefix_len;
r = strcmp(val, buffer);
if (r != 0) {
trim_newline(line);
trim_newline(buffer);
error_set(err, PIDFD_FAIL, "%s '%s' != '%s'",
prefix, val, buffer);
}
break;
}
free(line);
fclose(f);
if (found == 0)
return error_set(err, PIDFD_FAIL, "%s not found for fd %d",
prefix, pidfd);
return PIDFD_PASS;
}
static int child_fdinfo_nspid_test(void *args)
{
struct error err;
int pidfd;
int r;
/* if we got no fd for the sibling, we are done */
if (!args)
return PIDFD_PASS;
/* verify that we can not resolve the pidfd for a process
* in a sibling pid namespace, i.e. a pid namespace it is
* not in our or a descended namespace
*/
r = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0);
if (r < 0) {
ksft_print_msg("Failed to remount / private\n");
return PIDFD_ERROR;
}
(void)umount2("/proc", MNT_DETACH);
r = mount("proc", "/proc", "proc", 0, NULL);
if (r < 0) {
ksft_print_msg("Failed to remount /proc\n");
return PIDFD_ERROR;
}
pidfd = *(int *)args;
r = verify_fdinfo(pidfd, &err, "NSpid:", 6, "\t0\n");
if (r != PIDFD_PASS)
ksft_print_msg("NSpid fdinfo check failed: %s\n", err.msg);
return r;
}
static void test_pidfd_fdinfo_nspid(void)
{
struct child a, b;
struct error err = {0, };
const char *test_name = "pidfd check for NSpid in fdinfo";
/* Create a new child in a new pid and mount namespace */
a = clone_newns(child_fdinfo_nspid_test, NULL, &err);
error_check(&err, test_name);
/* Pass the pidfd representing the first child to the
* second child, which will be in a sibling pid namespace,
* which means that the fdinfo NSpid entry for the pidfd
* should only contain '0'.
*/
b = clone_newns(child_fdinfo_nspid_test, &a.fd, &err);
error_check(&err, test_name);
/* The children will have pid 1 in the new pid namespace,
* so the line must be 'NSPid:\t<pid>\t1'.
*/
verify_fdinfo(a.fd, &err, "NSpid:", 6, "\t%d\t%d\n", a.pid, 1);
verify_fdinfo(b.fd, &err, "NSpid:", 6, "\t%d\t%d\n", b.pid, 1);
/* wait for the process, check the exit status and set
* 'err' accordingly, if it is not already set.
*/
child_join_close(&a, &err);
child_join_close(&b, &err);
error_report(&err, test_name);
}
static void test_pidfd_dead_fdinfo(void)
{
struct child a;
struct error err = {0, };
const char *test_name = "pidfd check fdinfo for dead process";
/* Create a new child in a new pid and mount namespace */
a = clone_newns(child_fdinfo_nspid_test, NULL, &err);
error_check(&err, test_name);
child_join(&a, &err);
verify_fdinfo(a.fd, &err, "Pid:", 4, "\t-1\n");
verify_fdinfo(a.fd, &err, "NSpid:", 6, "\t-1\n");
child_close(&a);
error_report(&err, test_name);
}
int main(int argc, char **argv)
{
ksft_print_header();
ksft_set_plan(2);
test_pidfd_fdinfo_nspid();
test_pidfd_dead_fdinfo();
return ksft_exit_pass();
}