linux/security/landlock/syscalls.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Landlock LSM - System call implementations and user space interfaces
   4 *
   5 * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
   6 * Copyright © 2018-2020 ANSSI
   7 */
   8
   9#include <asm/current.h>
  10#include <linux/anon_inodes.h>
  11#include <linux/build_bug.h>
  12#include <linux/capability.h>
  13#include <linux/compiler_types.h>
  14#include <linux/dcache.h>
  15#include <linux/err.h>
  16#include <linux/errno.h>
  17#include <linux/fs.h>
  18#include <linux/limits.h>
  19#include <linux/mount.h>
  20#include <linux/path.h>
  21#include <linux/sched.h>
  22#include <linux/security.h>
  23#include <linux/stddef.h>
  24#include <linux/syscalls.h>
  25#include <linux/types.h>
  26#include <linux/uaccess.h>
  27#include <uapi/linux/landlock.h>
  28
  29#include "cred.h"
  30#include "fs.h"
  31#include "limits.h"
  32#include "ruleset.h"
  33#include "setup.h"
  34
  35/**
  36 * copy_min_struct_from_user - Safe future-proof argument copying
  37 *
  38 * Extend copy_struct_from_user() to check for consistent user buffer.
  39 *
  40 * @dst: Kernel space pointer or NULL.
  41 * @ksize: Actual size of the data pointed to by @dst.
  42 * @ksize_min: Minimal required size to be copied.
  43 * @src: User space pointer or NULL.
  44 * @usize: (Alleged) size of the data pointed to by @src.
  45 */
  46static __always_inline int copy_min_struct_from_user(void *const dst,
  47                const size_t ksize, const size_t ksize_min,
  48                const void __user *const src, const size_t usize)
  49{
  50        /* Checks buffer inconsistencies. */
  51        BUILD_BUG_ON(!dst);
  52        if (!src)
  53                return -EFAULT;
  54
  55        /* Checks size ranges. */
  56        BUILD_BUG_ON(ksize <= 0);
  57        BUILD_BUG_ON(ksize < ksize_min);
  58        if (usize < ksize_min)
  59                return -EINVAL;
  60        if (usize > PAGE_SIZE)
  61                return -E2BIG;
  62
  63        /* Copies user buffer and fills with zeros. */
  64        return copy_struct_from_user(dst, ksize, src, usize);
  65}
  66
  67/*
  68 * This function only contains arithmetic operations with constants, leading to
  69 * BUILD_BUG_ON().  The related code is evaluated and checked at build time,
  70 * but it is then ignored thanks to compiler optimizations.
  71 */
  72static void build_check_abi(void)
  73{
  74        struct landlock_ruleset_attr ruleset_attr;
  75        struct landlock_path_beneath_attr path_beneath_attr;
  76        size_t ruleset_size, path_beneath_size;
  77
  78        /*
  79         * For each user space ABI structures, first checks that there is no
  80         * hole in them, then checks that all architectures have the same
  81         * struct size.
  82         */
  83        ruleset_size = sizeof(ruleset_attr.handled_access_fs);
  84        BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
  85        BUILD_BUG_ON(sizeof(ruleset_attr) != 8);
  86
  87        path_beneath_size = sizeof(path_beneath_attr.allowed_access);
  88        path_beneath_size += sizeof(path_beneath_attr.parent_fd);
  89        BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
  90        BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
  91}
  92
  93/* Ruleset handling */
  94
  95static int fop_ruleset_release(struct inode *const inode,
  96                struct file *const filp)
  97{
  98        struct landlock_ruleset *ruleset = filp->private_data;
  99
 100        landlock_put_ruleset(ruleset);
 101        return 0;
 102}
 103
 104static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf,
 105                const size_t size, loff_t *const ppos)
 106{
 107        /* Dummy handler to enable FMODE_CAN_READ. */
 108        return -EINVAL;
 109}
 110
 111static ssize_t fop_dummy_write(struct file *const filp,
 112                const char __user *const buf, const size_t size,
 113                loff_t *const ppos)
 114{
 115        /* Dummy handler to enable FMODE_CAN_WRITE. */
 116        return -EINVAL;
 117}
 118
 119/*
 120 * A ruleset file descriptor enables to build a ruleset by adding (i.e.
 121 * writing) rule after rule, without relying on the task's context.  This
 122 * reentrant design is also used in a read way to enforce the ruleset on the
 123 * current task.
 124 */
 125static const struct file_operations ruleset_fops = {
 126        .release = fop_ruleset_release,
 127        .read = fop_dummy_read,
 128        .write = fop_dummy_write,
 129};
 130
 131#define LANDLOCK_ABI_VERSION    1
 132
 133/**
 134 * sys_landlock_create_ruleset - Create a new ruleset
 135 *
 136 * @attr: Pointer to a &struct landlock_ruleset_attr identifying the scope of
 137 *        the new ruleset.
 138 * @size: Size of the pointed &struct landlock_ruleset_attr (needed for
 139 *        backward and forward compatibility).
 140 * @flags: Supported value: %LANDLOCK_CREATE_RULESET_VERSION.
 141 *
 142 * This system call enables to create a new Landlock ruleset, and returns the
 143 * related file descriptor on success.
 144 *
 145 * If @flags is %LANDLOCK_CREATE_RULESET_VERSION and @attr is NULL and @size is
 146 * 0, then the returned value is the highest supported Landlock ABI version
 147 * (starting at 1).
 148 *
 149 * Possible returned errors are:
 150 *
 151 * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
 152 * - EINVAL: unknown @flags, or unknown access, or too small @size;
 153 * - E2BIG or EFAULT: @attr or @size inconsistencies;
 154 * - ENOMSG: empty &landlock_ruleset_attr.handled_access_fs.
 155 */
 156SYSCALL_DEFINE3(landlock_create_ruleset,
 157                const struct landlock_ruleset_attr __user *const, attr,
 158                const size_t, size, const __u32, flags)
 159{
 160        struct landlock_ruleset_attr ruleset_attr;
 161        struct landlock_ruleset *ruleset;
 162        int err, ruleset_fd;
 163
 164        /* Build-time checks. */
 165        build_check_abi();
 166
 167        if (!landlock_initialized)
 168                return -EOPNOTSUPP;
 169
 170        if (flags) {
 171                if ((flags == LANDLOCK_CREATE_RULESET_VERSION)
 172                                && !attr && !size)
 173                        return LANDLOCK_ABI_VERSION;
 174                return -EINVAL;
 175        }
 176
 177        /* Copies raw user space buffer. */
 178        err = copy_min_struct_from_user(&ruleset_attr, sizeof(ruleset_attr),
 179                        offsetofend(typeof(ruleset_attr), handled_access_fs),
 180                        attr, size);
 181        if (err)
 182                return err;
 183
 184        /* Checks content (and 32-bits cast). */
 185        if ((ruleset_attr.handled_access_fs | LANDLOCK_MASK_ACCESS_FS) !=
 186                        LANDLOCK_MASK_ACCESS_FS)
 187                return -EINVAL;
 188
 189        /* Checks arguments and transforms to kernel struct. */
 190        ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs);
 191        if (IS_ERR(ruleset))
 192                return PTR_ERR(ruleset);
 193
 194        /* Creates anonymous FD referring to the ruleset. */
 195        ruleset_fd = anon_inode_getfd("landlock-ruleset", &ruleset_fops,
 196                        ruleset, O_RDWR | O_CLOEXEC);
 197        if (ruleset_fd < 0)
 198                landlock_put_ruleset(ruleset);
 199        return ruleset_fd;
 200}
 201
 202/*
 203 * Returns an owned ruleset from a FD. It is thus needed to call
 204 * landlock_put_ruleset() on the return value.
 205 */
 206static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
 207                const fmode_t mode)
 208{
 209        struct fd ruleset_f;
 210        struct landlock_ruleset *ruleset;
 211
 212        ruleset_f = fdget(fd);
 213        if (!ruleset_f.file)
 214                return ERR_PTR(-EBADF);
 215
 216        /* Checks FD type and access right. */
 217        if (ruleset_f.file->f_op != &ruleset_fops) {
 218                ruleset = ERR_PTR(-EBADFD);
 219                goto out_fdput;
 220        }
 221        if (!(ruleset_f.file->f_mode & mode)) {
 222                ruleset = ERR_PTR(-EPERM);
 223                goto out_fdput;
 224        }
 225        ruleset = ruleset_f.file->private_data;
 226        if (WARN_ON_ONCE(ruleset->num_layers != 1)) {
 227                ruleset = ERR_PTR(-EINVAL);
 228                goto out_fdput;
 229        }
 230        landlock_get_ruleset(ruleset);
 231
 232out_fdput:
 233        fdput(ruleset_f);
 234        return ruleset;
 235}
 236
 237/* Path handling */
 238
 239/*
 240 * @path: Must call put_path(@path) after the call if it succeeded.
 241 */
 242static int get_path_from_fd(const s32 fd, struct path *const path)
 243{
 244        struct fd f;
 245        int err = 0;
 246
 247        BUILD_BUG_ON(!__same_type(fd,
 248                ((struct landlock_path_beneath_attr *)NULL)->parent_fd));
 249
 250        /* Handles O_PATH. */
 251        f = fdget_raw(fd);
 252        if (!f.file)
 253                return -EBADF;
 254        /*
 255         * Forbids ruleset FDs, internal filesystems (e.g. nsfs), including
 256         * pseudo filesystems that will never be mountable (e.g. sockfs,
 257         * pipefs).
 258         */
 259        if ((f.file->f_op == &ruleset_fops) ||
 260                        (f.file->f_path.mnt->mnt_flags & MNT_INTERNAL) ||
 261                        (f.file->f_path.dentry->d_sb->s_flags & SB_NOUSER) ||
 262                        d_is_negative(f.file->f_path.dentry) ||
 263                        IS_PRIVATE(d_backing_inode(f.file->f_path.dentry))) {
 264                err = -EBADFD;
 265                goto out_fdput;
 266        }
 267        *path = f.file->f_path;
 268        path_get(path);
 269
 270out_fdput:
 271        fdput(f);
 272        return err;
 273}
 274
 275/**
 276 * sys_landlock_add_rule - Add a new rule to a ruleset
 277 *
 278 * @ruleset_fd: File descriptor tied to the ruleset that should be extended
 279 *              with the new rule.
 280 * @rule_type: Identify the structure type pointed to by @rule_attr (only
 281 *             LANDLOCK_RULE_PATH_BENEATH for now).
 282 * @rule_attr: Pointer to a rule (only of type &struct
 283 *             landlock_path_beneath_attr for now).
 284 * @flags: Must be 0.
 285 *
 286 * This system call enables to define a new rule and add it to an existing
 287 * ruleset.
 288 *
 289 * Possible returned errors are:
 290 *
 291 * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
 292 * - EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
 293 *   &landlock_path_beneath_attr.allowed_access is not a subset of the rule's
 294 *   accesses);
 295 * - ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
 296 * - EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
 297 *   member of @rule_attr is not a file descriptor as expected;
 298 * - EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of
 299 *   @rule_attr is not the expected file descriptor type (e.g. file open
 300 *   without O_PATH);
 301 * - EPERM: @ruleset_fd has no write access to the underlying ruleset;
 302 * - EFAULT: @rule_attr inconsistency.
 303 */
 304SYSCALL_DEFINE4(landlock_add_rule,
 305                const int, ruleset_fd, const enum landlock_rule_type, rule_type,
 306                const void __user *const, rule_attr, const __u32, flags)
 307{
 308        struct landlock_path_beneath_attr path_beneath_attr;
 309        struct path path;
 310        struct landlock_ruleset *ruleset;
 311        int res, err;
 312
 313        if (!landlock_initialized)
 314                return -EOPNOTSUPP;
 315
 316        /* No flag for now. */
 317        if (flags)
 318                return -EINVAL;
 319
 320        if (rule_type != LANDLOCK_RULE_PATH_BENEATH)
 321                return -EINVAL;
 322
 323        /* Copies raw user space buffer, only one type for now. */
 324        res = copy_from_user(&path_beneath_attr, rule_attr,
 325                        sizeof(path_beneath_attr));
 326        if (res)
 327                return -EFAULT;
 328
 329        /* Gets and checks the ruleset. */
 330        ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
 331        if (IS_ERR(ruleset))
 332                return PTR_ERR(ruleset);
 333
 334        /*
 335         * Informs about useless rule: empty allowed_access (i.e. deny rules)
 336         * are ignored in path walks.
 337         */
 338        if (!path_beneath_attr.allowed_access) {
 339                err = -ENOMSG;
 340                goto out_put_ruleset;
 341        }
 342        /*
 343         * Checks that allowed_access matches the @ruleset constraints
 344         * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits).
 345         */
 346        if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) !=
 347                        ruleset->fs_access_masks[0]) {
 348                err = -EINVAL;
 349                goto out_put_ruleset;
 350        }
 351
 352        /* Gets and checks the new rule. */
 353        err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
 354        if (err)
 355                goto out_put_ruleset;
 356
 357        /* Imports the new rule. */
 358        err = landlock_append_fs_rule(ruleset, &path,
 359                        path_beneath_attr.allowed_access);
 360        path_put(&path);
 361
 362out_put_ruleset:
 363        landlock_put_ruleset(ruleset);
 364        return err;
 365}
 366
 367/* Enforcement */
 368
 369/**
 370 * sys_landlock_restrict_self - Enforce a ruleset on the calling thread
 371 *
 372 * @ruleset_fd: File descriptor tied to the ruleset to merge with the target.
 373 * @flags: Must be 0.
 374 *
 375 * This system call enables to enforce a Landlock ruleset on the current
 376 * thread.  Enforcing a ruleset requires that the task has CAP_SYS_ADMIN in its
 377 * namespace or is running with no_new_privs.  This avoids scenarios where
 378 * unprivileged tasks can affect the behavior of privileged children.
 379 *
 380 * Possible returned errors are:
 381 *
 382 * - EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
 383 * - EINVAL: @flags is not 0.
 384 * - EBADF: @ruleset_fd is not a file descriptor for the current thread;
 385 * - EBADFD: @ruleset_fd is not a ruleset file descriptor;
 386 * - EPERM: @ruleset_fd has no read access to the underlying ruleset, or the
 387 *   current thread is not running with no_new_privs, or it doesn't have
 388 *   CAP_SYS_ADMIN in its namespace.
 389 * - E2BIG: The maximum number of stacked rulesets is reached for the current
 390 *   thread.
 391 */
 392SYSCALL_DEFINE2(landlock_restrict_self,
 393                const int, ruleset_fd, const __u32, flags)
 394{
 395        struct landlock_ruleset *new_dom, *ruleset;
 396        struct cred *new_cred;
 397        struct landlock_cred_security *new_llcred;
 398        int err;
 399
 400        if (!landlock_initialized)
 401                return -EOPNOTSUPP;
 402
 403        /* No flag for now. */
 404        if (flags)
 405                return -EINVAL;
 406
 407        /*
 408         * Similar checks as for seccomp(2), except that an -EPERM may be
 409         * returned.
 410         */
 411        if (!task_no_new_privs(current) &&
 412                        !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
 413                return -EPERM;
 414
 415        /* Gets and checks the ruleset. */
 416        ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
 417        if (IS_ERR(ruleset))
 418                return PTR_ERR(ruleset);
 419
 420        /* Prepares new credentials. */
 421        new_cred = prepare_creds();
 422        if (!new_cred) {
 423                err = -ENOMEM;
 424                goto out_put_ruleset;
 425        }
 426        new_llcred = landlock_cred(new_cred);
 427
 428        /*
 429         * There is no possible race condition while copying and manipulating
 430         * the current credentials because they are dedicated per thread.
 431         */
 432        new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset);
 433        if (IS_ERR(new_dom)) {
 434                err = PTR_ERR(new_dom);
 435                goto out_put_creds;
 436        }
 437
 438        /* Replaces the old (prepared) domain. */
 439        landlock_put_ruleset(new_llcred->domain);
 440        new_llcred->domain = new_dom;
 441
 442        landlock_put_ruleset(ruleset);
 443        return commit_creds(new_cred);
 444
 445out_put_creds:
 446        abort_creds(new_cred);
 447
 448out_put_ruleset:
 449        landlock_put_ruleset(ruleset);
 450        return err;
 451}
 452
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.