linux/tools/testing/selftests/landlock/ptrace_test.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Landlock tests - Ptrace
   4 *
   5 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
   6 * Copyright © 2019-2020 ANSSI
   7 */
   8
   9#define _GNU_SOURCE
  10#include <errno.h>
  11#include <fcntl.h>
  12#include <linux/landlock.h>
  13#include <signal.h>
  14#include <sys/prctl.h>
  15#include <sys/ptrace.h>
  16#include <sys/types.h>
  17#include <sys/wait.h>
  18#include <unistd.h>
  19
  20#include "common.h"
  21
  22static void create_domain(struct __test_metadata *const _metadata)
  23{
  24        int ruleset_fd;
  25        struct landlock_ruleset_attr ruleset_attr = {
  26                .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK,
  27        };
  28
  29        ruleset_fd = landlock_create_ruleset(&ruleset_attr,
  30                        sizeof(ruleset_attr), 0);
  31        EXPECT_LE(0, ruleset_fd) {
  32                TH_LOG("Failed to create a ruleset: %s", strerror(errno));
  33        }
  34        EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
  35        EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
  36        EXPECT_EQ(0, close(ruleset_fd));
  37}
  38
  39static int test_ptrace_read(const pid_t pid)
  40{
  41        static const char path_template[] = "/proc/%d/environ";
  42        char procenv_path[sizeof(path_template) + 10];
  43        int procenv_path_size, fd;
  44
  45        procenv_path_size = snprintf(procenv_path, sizeof(procenv_path),
  46                        path_template, pid);
  47        if (procenv_path_size >= sizeof(procenv_path))
  48                return E2BIG;
  49
  50        fd = open(procenv_path, O_RDONLY | O_CLOEXEC);
  51        if (fd < 0)
  52                return errno;
  53        /*
  54         * Mixing error codes from close(2) and open(2) should not lead to any
  55         * (access type) confusion for this test.
  56         */
  57        if (close(fd) != 0)
  58                return errno;
  59        return 0;
  60}
  61
  62FIXTURE(hierarchy) { };
  63
  64FIXTURE_VARIANT(hierarchy) {
  65        const bool domain_both;
  66        const bool domain_parent;
  67        const bool domain_child;
  68};
  69
  70/*
  71 * Test multiple tracing combinations between a parent process P1 and a child
  72 * process P2.
  73 *
  74 * Yama's scoped ptrace is presumed disabled.  If enabled, this optional
  75 * restriction is enforced in addition to any Landlock check, which means that
  76 * all P2 requests to trace P1 would be denied.
  77 */
  78
  79/*
  80 *        No domain
  81 *
  82 *   P1-.               P1 -> P2 : allow
  83 *       \              P2 -> P1 : allow
  84 *        'P2
  85 */
  86FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) {
  87        .domain_both = false,
  88        .domain_parent = false,
  89        .domain_child = false,
  90};
  91
  92/*
  93 *        Child domain
  94 *
  95 *   P1--.              P1 -> P2 : allow
  96 *        \             P2 -> P1 : deny
  97 *        .'-----.
  98 *        |  P2  |
  99 *        '------'
 100 */
 101FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) {
 102        .domain_both = false,
 103        .domain_parent = false,
 104        .domain_child = true,
 105};
 106
 107/*
 108 *        Parent domain
 109 * .------.
 110 * |  P1  --.           P1 -> P2 : deny
 111 * '------'  \          P2 -> P1 : allow
 112 *            '
 113 *            P2
 114 */
 115FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) {
 116        .domain_both = false,
 117        .domain_parent = true,
 118        .domain_child = false,
 119};
 120
 121/*
 122 *        Parent + child domain (siblings)
 123 * .------.
 124 * |  P1  ---.          P1 -> P2 : deny
 125 * '------'   \         P2 -> P1 : deny
 126 *         .---'--.
 127 *         |  P2  |
 128 *         '------'
 129 */
 130FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) {
 131        .domain_both = false,
 132        .domain_parent = true,
 133        .domain_child = true,
 134};
 135
 136/*
 137 *         Same domain (inherited)
 138 * .-------------.
 139 * | P1----.     |      P1 -> P2 : allow
 140 * |        \    |      P2 -> P1 : allow
 141 * |         '   |
 142 * |         P2  |
 143 * '-------------'
 144 */
 145FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) {
 146        .domain_both = true,
 147        .domain_parent = false,
 148        .domain_child = false,
 149};
 150
 151/*
 152 *         Inherited + child domain
 153 * .-----------------.
 154 * |  P1----.        |  P1 -> P2 : allow
 155 * |         \       |  P2 -> P1 : deny
 156 * |        .-'----. |
 157 * |        |  P2  | |
 158 * |        '------' |
 159 * '-----------------'
 160 */
 161FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) {
 162        .domain_both = true,
 163        .domain_parent = false,
 164        .domain_child = true,
 165};
 166
 167/*
 168 *         Inherited + parent domain
 169 * .-----------------.
 170 * |.------.         |  P1 -> P2 : deny
 171 * ||  P1  ----.     |  P2 -> P1 : allow
 172 * |'------'    \    |
 173 * |             '   |
 174 * |             P2  |
 175 * '-----------------'
 176 */
 177FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) {
 178        .domain_both = true,
 179        .domain_parent = true,
 180        .domain_child = false,
 181};
 182
 183/*
 184 *         Inherited + parent and child domain (siblings)
 185 * .-----------------.
 186 * | .------.        |  P1 -> P2 : deny
 187 * | |  P1  .        |  P2 -> P1 : deny
 188 * | '------'\       |
 189 * |          \      |
 190 * |        .--'---. |
 191 * |        |  P2  | |
 192 * |        '------' |
 193 * '-----------------'
 194 */
 195FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
 196        .domain_both = true,
 197        .domain_parent = true,
 198        .domain_child = true,
 199};
 200
 201FIXTURE_SETUP(hierarchy)
 202{ }
 203
 204FIXTURE_TEARDOWN(hierarchy)
 205{ }
 206
 207/* Test PTRACE_TRACEME and PTRACE_ATTACH for parent and child. */
 208TEST_F(hierarchy, trace)
 209{
 210        pid_t child, parent;
 211        int status, err_proc_read;
 212        int pipe_child[2], pipe_parent[2];
 213        char buf_parent;
 214        long ret;
 215
 216        /*
 217         * Removes all effective and permitted capabilities to not interfere
 218         * with cap_ptrace_access_check() in case of PTRACE_MODE_FSCREDS.
 219         */
 220        drop_caps(_metadata);
 221
 222        parent = getpid();
 223        ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
 224        ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
 225        if (variant->domain_both) {
 226                create_domain(_metadata);
 227                if (!_metadata->passed)
 228                        /* Aborts before forking. */
 229                        return;
 230        }
 231
 232        child = fork();
 233        ASSERT_LE(0, child);
 234        if (child == 0) {
 235                char buf_child;
 236
 237                ASSERT_EQ(0, close(pipe_parent[1]));
 238                ASSERT_EQ(0, close(pipe_child[0]));
 239                if (variant->domain_child)
 240                        create_domain(_metadata);
 241
 242                /* Waits for the parent to be in a domain, if any. */
 243                ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
 244
 245                /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the parent. */
 246                err_proc_read = test_ptrace_read(parent);
 247                ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
 248                if (variant->domain_child) {
 249                        EXPECT_EQ(-1, ret);
 250                        EXPECT_EQ(EPERM, errno);
 251                        EXPECT_EQ(EACCES, err_proc_read);
 252                } else {
 253                        EXPECT_EQ(0, ret);
 254                        EXPECT_EQ(0, err_proc_read);
 255                }
 256                if (ret == 0) {
 257                        ASSERT_EQ(parent, waitpid(parent, &status, 0));
 258                        ASSERT_EQ(1, WIFSTOPPED(status));
 259                        ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
 260                }
 261
 262                /* Tests child PTRACE_TRACEME. */
 263                ret = ptrace(PTRACE_TRACEME);
 264                if (variant->domain_parent) {
 265                        EXPECT_EQ(-1, ret);
 266                        EXPECT_EQ(EPERM, errno);
 267                } else {
 268                        EXPECT_EQ(0, ret);
 269                }
 270
 271                /*
 272                 * Signals that the PTRACE_ATTACH test is done and the
 273                 * PTRACE_TRACEME test is ongoing.
 274                 */
 275                ASSERT_EQ(1, write(pipe_child[1], ".", 1));
 276
 277                if (!variant->domain_parent) {
 278                        ASSERT_EQ(0, raise(SIGSTOP));
 279                }
 280
 281                /* Waits for the parent PTRACE_ATTACH test. */
 282                ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
 283                _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
 284                return;
 285        }
 286
 287        ASSERT_EQ(0, close(pipe_child[1]));
 288        ASSERT_EQ(0, close(pipe_parent[0]));
 289        if (variant->domain_parent)
 290                create_domain(_metadata);
 291
 292        /* Signals that the parent is in a domain, if any. */
 293        ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 294
 295        /*
 296         * Waits for the child to test PTRACE_ATTACH on the parent and start
 297         * testing PTRACE_TRACEME.
 298         */
 299        ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
 300
 301        /* Tests child PTRACE_TRACEME. */
 302        if (!variant->domain_parent) {
 303                ASSERT_EQ(child, waitpid(child, &status, 0));
 304                ASSERT_EQ(1, WIFSTOPPED(status));
 305                ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
 306        } else {
 307                /* The child should not be traced by the parent. */
 308                EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0));
 309                EXPECT_EQ(ESRCH, errno);
 310        }
 311
 312        /* Tests PTRACE_ATTACH and PTRACE_MODE_READ on the child. */
 313        err_proc_read = test_ptrace_read(child);
 314        ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
 315        if (variant->domain_parent) {
 316                EXPECT_EQ(-1, ret);
 317                EXPECT_EQ(EPERM, errno);
 318                EXPECT_EQ(EACCES, err_proc_read);
 319        } else {
 320                EXPECT_EQ(0, ret);
 321                EXPECT_EQ(0, err_proc_read);
 322        }
 323        if (ret == 0) {
 324                ASSERT_EQ(child, waitpid(child, &status, 0));
 325                ASSERT_EQ(1, WIFSTOPPED(status));
 326                ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
 327        }
 328
 329        /* Signals that the parent PTRACE_ATTACH test is done. */
 330        ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
 331        ASSERT_EQ(child, waitpid(child, &status, 0));
 332        if (WIFSIGNALED(status) || !WIFEXITED(status) ||
 333                        WEXITSTATUS(status) != EXIT_SUCCESS)
 334                _metadata->passed = 0;
 335}
 336
 337TEST_HARNESS_MAIN
 338