root/security/landlock/audit.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Landlock - Audit helpers
 *
 * Copyright © 2023-2025 Microsoft Corporation
 */

#include <kunit/test.h>
#include <linux/audit.h>
#include <linux/bitops.h>
#include <linux/lsm_audit.h>
#include <linux/pid.h>
#include <uapi/linux/landlock.h>

#include "access.h"
#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "limits.h"
#include "ruleset.h"

static const char *const fs_access_strings[] = {
        [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate",
        [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev",
};

static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS);

static const char *const net_access_strings[] = {
        [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp",
        [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp",
};

static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);

static __attribute_const__ const char *
get_blocker(const enum landlock_request_type type,
            const unsigned long access_bit)
{
        switch (type) {
        case LANDLOCK_REQUEST_PTRACE:
                WARN_ON_ONCE(access_bit != -1);
                return "ptrace";

        case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
                WARN_ON_ONCE(access_bit != -1);
                return "fs.change_topology";

        case LANDLOCK_REQUEST_FS_ACCESS:
                if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
                        return "unknown";
                return fs_access_strings[access_bit];

        case LANDLOCK_REQUEST_NET_ACCESS:
                if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings)))
                        return "unknown";
                return net_access_strings[access_bit];

        case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
                WARN_ON_ONCE(access_bit != -1);
                return "scope.abstract_unix_socket";

        case LANDLOCK_REQUEST_SCOPE_SIGNAL:
                WARN_ON_ONCE(access_bit != -1);
                return "scope.signal";
        }

        WARN_ON_ONCE(1);
        return "unknown";
}

static void log_blockers(struct audit_buffer *const ab,
                         const enum landlock_request_type type,
                         const access_mask_t access)
{
        const unsigned long access_mask = access;
        unsigned long access_bit;
        bool is_first = true;

        for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) {
                audit_log_format(ab, "%s%s", is_first ? "" : ",",
                                 get_blocker(type, access_bit));
                is_first = false;
        }
        if (is_first)
                audit_log_format(ab, "%s", get_blocker(type, -1));
}

static void log_domain(struct landlock_hierarchy *const hierarchy)
{
        struct audit_buffer *ab;

        /* Ignores already logged domains.  */
        if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED)
                return;

        /* Uses consistent allocation flags wrt common_lsm_audit(). */
        ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
                             AUDIT_LANDLOCK_DOMAIN);
        if (!ab)
                return;

        WARN_ON_ONCE(hierarchy->id == 0);
        audit_log_format(
                ab,
                "domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=",
                hierarchy->id, pid_nr(hierarchy->details->pid),
                hierarchy->details->uid);
        audit_log_untrustedstring(ab, hierarchy->details->exe_path);
        audit_log_format(ab, " comm=");
        audit_log_untrustedstring(ab, hierarchy->details->comm);
        audit_log_end(ab);

        /*
         * There may be race condition leading to logging of the same domain
         * several times but that is OK.
         */
        WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
}

static struct landlock_hierarchy *
get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
{
        struct landlock_hierarchy *hierarchy = domain->hierarchy;
        ssize_t i;

        if (WARN_ON_ONCE(layer >= domain->num_layers))
                return hierarchy;

        for (i = domain->num_layers - 1; i > layer; i--) {
                if (WARN_ON_ONCE(!hierarchy->parent))
                        break;

                hierarchy = hierarchy->parent;
        }

        return hierarchy;
}

#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST

static void test_get_hierarchy(struct kunit *const test)
{
        struct landlock_hierarchy dom0_hierarchy = {
                .id = 10,
        };
        struct landlock_hierarchy dom1_hierarchy = {
                .parent = &dom0_hierarchy,
                .id = 20,
        };
        struct landlock_hierarchy dom2_hierarchy = {
                .parent = &dom1_hierarchy,
                .id = 30,
        };
        struct landlock_ruleset dom2 = {
                .hierarchy = &dom2_hierarchy,
                .num_layers = 3,
        };

        KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
        KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
        KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
        /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */
}

#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */

/* Get the youngest layer that denied the access_request. */
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
                               access_mask_t *const access_request,
                               const struct layer_access_masks *masks)
{
        for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
                if (masks->access[i] & *access_request) {
                        *access_request &= masks->access[i];
                        return i;
                }
        }

        /* Not found - fall back to default values */
        *access_request = 0;
        return domain->num_layers - 1;
}

#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST

static void test_get_denied_layer(struct kunit *const test)
{
        const struct landlock_ruleset dom = {
                .num_layers = 5,
        };
        const struct layer_access_masks masks = {
                .access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
                             LANDLOCK_ACCESS_FS_READ_DIR,
                .access[1] = LANDLOCK_ACCESS_FS_READ_FILE |
                             LANDLOCK_ACCESS_FS_READ_DIR,
                .access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR,
        };
        access_mask_t access;

        access = LANDLOCK_ACCESS_FS_EXECUTE;
        KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);

        access = LANDLOCK_ACCESS_FS_READ_FILE;
        KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);

        access = LANDLOCK_ACCESS_FS_READ_DIR;
        KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);

        access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
        KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
        KUNIT_EXPECT_EQ(test, access,
                        LANDLOCK_ACCESS_FS_READ_FILE |
                                LANDLOCK_ACCESS_FS_READ_DIR);

        access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
        KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);

        access = LANDLOCK_ACCESS_FS_WRITE_FILE;
        KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks));
        KUNIT_EXPECT_EQ(test, access, 0);
}

#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */

static size_t
get_layer_from_deny_masks(access_mask_t *const access_request,
                          const access_mask_t all_existing_optional_access,
                          const deny_masks_t deny_masks)
{
        const unsigned long access_opt = all_existing_optional_access;
        const unsigned long access_req = *access_request;
        access_mask_t missing = 0;
        size_t youngest_layer = 0;
        size_t access_index = 0;
        unsigned long access_bit;

        /* This will require change with new object types. */
        WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);

        for_each_set_bit(access_bit, &access_opt,
                         BITS_PER_TYPE(access_mask_t)) {
                if (access_req & BIT(access_bit)) {
                        const size_t layer =
                                (deny_masks >> (access_index * 4)) &
                                (LANDLOCK_MAX_NUM_LAYERS - 1);

                        if (layer > youngest_layer) {
                                youngest_layer = layer;
                                missing = BIT(access_bit);
                        } else if (layer == youngest_layer) {
                                missing |= BIT(access_bit);
                        }
                }
                access_index++;
        }

        *access_request = missing;
        return youngest_layer;
}

#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST

static void test_get_layer_from_deny_masks(struct kunit *const test)
{
        deny_masks_t deny_mask;
        access_mask_t access;

        /* truncate:0 ioctl_dev:2 */
        deny_mask = 0x20;

        access = LANDLOCK_ACCESS_FS_TRUNCATE;
        KUNIT_EXPECT_EQ(test, 0,
                        get_layer_from_deny_masks(&access,
                                                  _LANDLOCK_ACCESS_FS_OPTIONAL,
                                                  deny_mask));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);

        access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
        KUNIT_EXPECT_EQ(test, 2,
                        get_layer_from_deny_masks(&access,
                                                  _LANDLOCK_ACCESS_FS_OPTIONAL,
                                                  deny_mask));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);

        /* truncate:15 ioctl_dev:15 */
        deny_mask = 0xff;

        access = LANDLOCK_ACCESS_FS_TRUNCATE;
        KUNIT_EXPECT_EQ(test, 15,
                        get_layer_from_deny_masks(&access,
                                                  _LANDLOCK_ACCESS_FS_OPTIONAL,
                                                  deny_mask));
        KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);

        access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
        KUNIT_EXPECT_EQ(test, 15,
                        get_layer_from_deny_masks(&access,
                                                  _LANDLOCK_ACCESS_FS_OPTIONAL,
                                                  deny_mask));
        KUNIT_EXPECT_EQ(test, access,
                        LANDLOCK_ACCESS_FS_TRUNCATE |
                                LANDLOCK_ACCESS_FS_IOCTL_DEV);
}

#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */

static bool is_valid_request(const struct landlock_request *const request)
{
        if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
                return false;

        if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
                return false;

        if (request->access) {
                if (WARN_ON_ONCE(!(!!request->layer_masks ^
                                   !!request->all_existing_optional_access)))
                        return false;
        } else {
                if (WARN_ON_ONCE(request->layer_masks ||
                                 request->all_existing_optional_access))
                        return false;
        }

        if (request->deny_masks) {
                if (WARN_ON_ONCE(!request->all_existing_optional_access))
                        return false;
        }

        return true;
}

/**
 * landlock_log_denial - Create audit records related to a denial
 *
 * @subject: The Landlock subject's credential denying an action.
 * @request: Detail of the user space request.
 */
void landlock_log_denial(const struct landlock_cred_security *const subject,
                         const struct landlock_request *const request)
{
        struct audit_buffer *ab;
        struct landlock_hierarchy *youngest_denied;
        size_t youngest_layer;
        access_mask_t missing;

        if (WARN_ON_ONCE(!subject || !subject->domain ||
                         !subject->domain->hierarchy || !request))
                return;

        if (!is_valid_request(request))
                return;

        missing = request->access;
        if (missing) {
                /* Gets the nearest domain that denies the request. */
                if (request->layer_masks) {
                        youngest_layer = get_denied_layer(subject->domain,
                                                          &missing,
                                                          request->layer_masks);
                } else {
                        youngest_layer = get_layer_from_deny_masks(
                                &missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
                                request->deny_masks);
                }
                youngest_denied =
                        get_hierarchy(subject->domain, youngest_layer);
        } else {
                youngest_layer = request->layer_plus_one - 1;
                youngest_denied =
                        get_hierarchy(subject->domain, youngest_layer);
        }

        if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
                return;

        /*
         * Consistently keeps track of the number of denied access requests
         * even if audit is currently disabled, or if audit rules currently
         * exclude this record type, or if landlock_restrict_self(2)'s flags
         * quiet logs.
         */
        atomic64_inc(&youngest_denied->num_denials);

        if (!audit_enabled)
                return;

        /* Checks if the current exec was restricting itself. */
        if (subject->domain_exec & BIT(youngest_layer)) {
                /* Ignores denials for the same execution. */
                if (!youngest_denied->log_same_exec)
                        return;
        } else {
                /* Ignores denials after a new execution. */
                if (!youngest_denied->log_new_exec)
                        return;
        }

        /* Uses consistent allocation flags wrt common_lsm_audit(). */
        ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
                             AUDIT_LANDLOCK_ACCESS);
        if (!ab)
                return;

        audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
        log_blockers(ab, request->type, missing);
        audit_log_lsm_data(ab, &request->audit);
        audit_log_end(ab);

        /* Logs this domain the first time it shows in log. */
        log_domain(youngest_denied);
}

/**
 * landlock_log_drop_domain - Create an audit record on domain deallocation
 *
 * @hierarchy: The domain's hierarchy being deallocated.
 *
 * Only domains which previously appeared in the audit logs are logged again.
 * This is useful to know when a domain will never show again in the audit log.
 *
 * Called in a work queue scheduled by landlock_put_ruleset_deferred() called
 * by hook_cred_free().
 */
void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
{
        struct audit_buffer *ab;

        if (WARN_ON_ONCE(!hierarchy))
                return;

        if (!audit_enabled)
                return;

        /* Ignores domains that were not logged.  */
        if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED)
                return;

        /*
         * If logging of domain allocation succeeded, warns about failure to log
         * domain deallocation to highlight unbalanced domain lifetime logs.
         */
        ab = audit_log_start(audit_context(), GFP_KERNEL,
                             AUDIT_LANDLOCK_DOMAIN);
        if (!ab)
                return;

        audit_log_format(ab, "domain=%llx status=deallocated denials=%llu",
                         hierarchy->id, atomic64_read(&hierarchy->num_denials));
        audit_log_end(ab);
}

#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST

static struct kunit_case test_cases[] = {
        /* clang-format off */
        KUNIT_CASE(test_get_hierarchy),
        KUNIT_CASE(test_get_denied_layer),
        KUNIT_CASE(test_get_layer_from_deny_masks),
        {}
        /* clang-format on */
};

static struct kunit_suite test_suite = {
        .name = "landlock_audit",
        .test_cases = test_cases,
};

kunit_test_suite(test_suite);

#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */