[LTP] [PATCH v5] Add EPERM test for clone and clone3

Li Wang liwang@redhat.com
Tue Nov 18 05:48:42 CET 2025


Hi Stephen,

Mostly looks good, but a few places need refining.

On Tue, Nov 18, 2025 at 9:18 AM Stephen Bertram via ltp <ltp@lists.linux.it>
wrote:

> Confirming EPERM is returned when CAP_SYS_ADMIN is
> removed from clone and clone3.
> And for clone3 the set_tid_size is greater than 0.
>
> Signed-off-by: Stephen Bertram <sbertram@redhat.com>
> ---
>  runtest/syscalls                            |  2 +
>  testcases/kernel/syscalls/clone/.gitignore  |  1 +
>  testcases/kernel/syscalls/clone/clone11.c   | 74 +++++++++++++++++
>  testcases/kernel/syscalls/clone3/.gitignore |  1 +
>  testcases/kernel/syscalls/clone3/clone304.c | 88 +++++++++++++++++++++
>  5 files changed, 166 insertions(+)
>  create mode 100644 testcases/kernel/syscalls/clone/clone11.c
>  create mode 100644 testcases/kernel/syscalls/clone3/clone304.c
>
> diff --git a/runtest/syscalls b/runtest/syscalls
> index 54d94c0ca..a1ef7548b 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -124,10 +124,12 @@ clone07 clone07
>  clone08 clone08
>  clone09 clone09
>  clone10 clone10
> +clone11 clone11
>
>  clone301 clone301
>  clone302 clone302
>  clone303 clone303
> +clone304 clone304
>
>  close01 close01
>  close02 close02
> diff --git a/testcases/kernel/syscalls/clone/.gitignore
> b/testcases/kernel/syscalls/clone/.gitignore
> index adfb8257d..0edcfef5d 100644
> --- a/testcases/kernel/syscalls/clone/.gitignore
> +++ b/testcases/kernel/syscalls/clone/.gitignore
> @@ -8,3 +8,4 @@
>  /clone08
>  /clone09
>  /clone10
> +/clone11
> diff --git a/testcases/kernel/syscalls/clone/clone11.c
> b/testcases/kernel/syscalls/clone/clone11.c
> new file mode 100644
> index 000000000..6fb9e5346
> --- /dev/null
> +++ b/testcases/kernel/syscalls/clone/clone11.c
> @@ -0,0 +1,74 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2025 Stephen Bertram <sbertram@redhat.com>
> + */
> +
> +/*\
> + * This test verifies that :man2:`clone` fails with EPERM when
> CAP_SYS_ADMIN
> + * has been dropped.
> + */
> +
> +#define _GNU_SOURCE
> +#define DESC(x) .flags = x, .sflags = #x
> +
> +#include "tst_test.h"
> +#include "clone_platform.h"
> +#include "lapi/sched.h"
> +
> +static void *child_stack;
> +static int *child_pid;
>

static pid_t *child_pid;



> +
> +static struct tcase {
> +       uint64_t flags;
> +       char *sflags;
>

const char *sflags;

+} tcases[] = {
> +       { DESC(CLONE_NEWPID) },
> +       { DESC(CLONE_NEWCGROUP) },
> +       { DESC(CLONE_NEWIPC) },
> +       { DESC(CLONE_NEWNET) },
> +       { DESC(CLONE_NEWNS) },
> +       { DESC(CLONE_NEWUTS) },
> +};
> +
> +static int child_fn(void *arg LTP_ATTRIBUTE_UNUSED)
> +{
> +       *child_pid = getpid();
> +       exit(0);
>

Used _exit() in child_fn() to avoid flushing stdio twice.

+}
> +
> +static void run(unsigned int n)
> +{
> +       struct tcase *tc = &tcases[n];
> +
> +       TST_EXP_FAIL(ltp_clone(tc->flags, child_fn, NULL, CHILD_STACK_SIZE,
> +                       child_stack), EPERM, "clone(%s) should fail with
> EPERM"
> +                       , tc->sflags);
>

Code with newlines should be refined.

       TST_EXP_FAIL(ltp_clone(tc->flags, child_fn, NULL, CHILD_STACK_SIZE,
child_stack),
                    EPERM, "clone(%s) should fail with EPERM",
                    tc->sflags);

+}
> +
> +static void setup(void)
> +{
> +       child_pid = SAFE_MMAP(NULL, sizeof(*child_pid), PROT_READ |
> PROT_WRITE,
> +               MAP_SHARED | MAP_ANONYMOUS, -1, 0);
> +}
> +
> +static void cleanup(void)
> +{
> +       if (child_pid)
> +               SAFE_MUNMAP(child_pid, sizeof(*child_pid));
> +}
> +
> +static struct tst_test test = {
> +       .tcnt = ARRAY_SIZE(tcases),
> +       .setup = setup,
> +       .test = run,
> +       .cleanup = cleanup,
> +       .needs_root = 1,
> +       .caps = (struct tst_cap []) {
> +                       TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
> +                       {},
> +       },
> +       .bufs = (struct tst_buffers []) {
> +                       {&child_stack, .size = CHILD_STACK_SIZE},
> +                       {},
> +       },
>

Code indent issue.

@@ -64,11 +64,11 @@ static struct tst_test test = {
        .cleanup = cleanup,
        .needs_root = 1,
        .caps = (struct tst_cap []) {
-                       TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
-                       {},
+               TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
+               {},
        },
        .bufs = (struct tst_buffers []) {
-                       {&child_stack, .size = CHILD_STACK_SIZE},
-                       {},
+               {&child_stack, .size = CHILD_STACK_SIZE},
+               {},
        },
 };



> +};
> diff --git a/testcases/kernel/syscalls/clone3/.gitignore
> b/testcases/kernel/syscalls/clone3/.gitignore
> index 10369954b..e9b5312f4 100644
> --- a/testcases/kernel/syscalls/clone3/.gitignore
> +++ b/testcases/kernel/syscalls/clone3/.gitignore
> @@ -1,3 +1,4 @@
>  clone301
>  clone302
>  clone303
> +clone304
> diff --git a/testcases/kernel/syscalls/clone3/clone304.c
> b/testcases/kernel/syscalls/clone3/clone304.c
> new file mode 100644
> index 000000000..75ceaa065
> --- /dev/null
> +++ b/testcases/kernel/syscalls/clone3/clone304.c
> @@ -0,0 +1,88 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2025 Stephen Bertram <sbertram@redhat.com>
> + */
> +
> +/*\
> + * This test verifies that :man2:`clone3` fails with EPERM when
> CAP_SYS_ADMIN
> + * has been dropped and ``clone_args.set_tid_size`` is greater than zero.
> + */
>

/*\
 * Verify clone3() behavior under dropped CAP_SYS_ADMIN:
 *
 *  - set_tid_size = 0, flags = CLONE_NEW* : expect EPERM
 *  - set_tid_size > 0, flags = CLONE_NEW* : expect EPERM
 *  - set_tid_size > 0, flags = 0          : expect EPERM
 *
 * No child stack or func is supplied because each invocation should fail
 * before the kernel attempts to create a task.
 */


+
> +#define _GNU_SOURCE
> +#define DESC(x) .flags = x, .sflags = #x
> +
> +#include "tst_test.h"
> +#include "lapi/sched.h"
> +
> +enum case_type {
> +       K_SET_TID,      /* flags = 0 || CLONE_NEW*, set_tid_size > 0 =>
> EPERM */
> +       K_NAMESPACE_ONLY,  /* flags = CLONE_NEW*, set_tid_size = 0 =>
> EPERM */
> +};
> +
> +static struct clone_args args = {0};
> +static pid_t tid_array[1] = {1};

+
> +static struct tcase {
> +       uint64_t flags;
> +       char *sflags;
> +       enum case_type type;
> +       const char *desc;
> +} tcases[] = {
> +       { DESC(CLONE_NEWPID), K_NAMESPACE_ONLY, "set_tid_size=0" },
> +       { DESC(CLONE_NEWCGROUP), K_NAMESPACE_ONLY, "set_tid_size=0" },
> +       { DESC(CLONE_NEWIPC), K_NAMESPACE_ONLY, "set_tid_size=0" },
> +       { DESC(CLONE_NEWNET), K_NAMESPACE_ONLY, "set_tid_size=0" },
> +       { DESC(CLONE_NEWNS), K_NAMESPACE_ONLY, "set_tid_size=0" },
> +       { DESC(CLONE_NEWUTS), K_NAMESPACE_ONLY, "set_tid_size=0" },
> +
> +       { DESC(CLONE_NEWPID), K_SET_TID, "set_tid_size>0" },
> +       { DESC(CLONE_NEWCGROUP), K_SET_TID, "set_tid_size>0" },
> +       { DESC(CLONE_NEWIPC), K_SET_TID, "set_tid_size>0" },
> +       { DESC(CLONE_NEWNET), K_SET_TID, "set_tid_size>0" },
> +       { DESC(CLONE_NEWNS), K_SET_TID, "set_tid_size>0" },
> +       { DESC(CLONE_NEWUTS), K_SET_TID, "set_tid_size>0" },
> +
> +       { DESC(0), K_SET_TID, "set_tid_size>0" },
> +};
>

Seems the *desc is redundant; we can avoid defining one field here.

static struct tcase {
        uint64_t flags;
-       char *sflags;
+       const char *sflags;
        enum case_type type;
-       const char *desc;
 } tcases[] = {
-       { DESC(CLONE_NEWPID), K_NAMESPACE_ONLY, "set_tid_size=0" },
-       { DESC(CLONE_NEWCGROUP), K_NAMESPACE_ONLY, "set_tid_size=0" },
-       { DESC(CLONE_NEWIPC), K_NAMESPACE_ONLY, "set_tid_size=0" },
-       { DESC(CLONE_NEWNET), K_NAMESPACE_ONLY, "set_tid_size=0" },
-       { DESC(CLONE_NEWNS), K_NAMESPACE_ONLY, "set_tid_size=0" },
-       { DESC(CLONE_NEWUTS), K_NAMESPACE_ONLY, "set_tid_size=0" },
-
-       { DESC(CLONE_NEWPID), K_SET_TID, "set_tid_size>0" },
-       { DESC(CLONE_NEWCGROUP), K_SET_TID, "set_tid_size>0" },
-       { DESC(CLONE_NEWIPC), K_SET_TID, "set_tid_size>0" },
-       { DESC(CLONE_NEWNET), K_SET_TID, "set_tid_size>0" },
-       { DESC(CLONE_NEWNS), K_SET_TID, "set_tid_size>0" },
-       { DESC(CLONE_NEWUTS), K_SET_TID, "set_tid_size>0" },
-
-       { DESC(0), K_SET_TID, "set_tid_size>0" },
+       {DESC(CLONE_NEWPID), K_NAMESPACE_ONLY},
+       {DESC(CLONE_NEWCGROUP), K_NAMESPACE_ONLY},
+       {DESC(CLONE_NEWIPC), K_NAMESPACE_ONLY},
+       {DESC(CLONE_NEWNET), K_NAMESPACE_ONLY},
+       {DESC(CLONE_NEWNS), K_NAMESPACE_ONLY},
+       {DESC(CLONE_NEWUTS), K_NAMESPACE_ONLY},
+
+       {DESC(CLONE_NEWPID), K_SET_TID},
+       {DESC(CLONE_NEWCGROUP), K_SET_TID},
+       {DESC(CLONE_NEWIPC), K_SET_TID},
+       {DESC(CLONE_NEWNET), K_SET_TID},
+       {DESC(CLONE_NEWNS), K_SET_TID},
+       {DESC(CLONE_NEWUTS), K_SET_TID},
+
+       {DESC(0), K_SET_TID},
 };



> +
> +static void run(unsigned int n)
> +{
> +       struct tcase *tc = &tcases[n];
> +
> +       args.flags = tc->flags;
> +
> +       if (tc->type == K_NAMESPACE_ONLY) {
> +               args.set_tid = 0;
> +               args.set_tid_size = 0;
> +       } else {

+               args.set_tid = (uint64_t)(uintptr_t)tid_array;
> +               args.set_tid_size = 1;
> +       }
>



> +       if (!tc->flags)
> +               SAFE_UNSHARE(CLONE_NEWUSER | CLONE_NEWNS);
>

We should move SAFE_UNSHARE(CLONE_NEWUSER | CLONE_NEWNS) into setup(),
to avoid "-i N" test failures for subtest iteration.

+
> +       TST_EXP_FAIL(clone3(&args, sizeof(args)), EPERM, "clone3(%s),
> should fail when %s (set_tid_size=%ld)"
> +                       , tc->sflags, tc->desc, args.set_tid_size);
>

Code indent issue.

       TST_EXP_FAIL(clone3(&args, sizeof(args)), EPERM,
                       "clone3(%s), should fail when set_tid_size=%ld",
                       tc->sflags,
                       args.set_tid_size);


> +}
> +
> +static void setup(void)
> +{
> +       clone3_supported_by_kernel();
> +
> +       memset(&args, 0, sizeof(args));
> +}
> +
> +static struct tst_test test = {
> +       .tcnt = ARRAY_SIZE(tcases),
> +       .setup = setup,
> +       .test = run,
> +       .needs_root = 1,
>


> +       .caps = (struct tst_cap []) {
> +                       TST_CAP(TST_CAP_DROP, CAP_SYS_ADMIN),
> +                       {},
> +       },
> +       .bufs = (struct tst_buffers []) {
> +                       {&args, .size = sizeof(struct clone_args)},
> +                       {},
> +       },
>

Indentation issue.

+};
> --
> 2.49.0
>
>
> --
> Mailing list info: https://lists.linux.it/listinfo/ltp
>
>

-- 
Regards,
Li Wang


More information about the ltp mailing list