Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/security/landlock/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 14 kB image not shown  

Quelle  audit.c   Sprache: 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 */

static size_t get_denied_layer(const struct landlock_ruleset *const domain,
          access_mask_t *const access_request,
          const layer_mask_t (*const layer_masks)[],
          const size_t layer_masks_size)
{
 const unsigned long access_req = *access_request;
 unsigned long access_bit;
 access_mask_t missing = 0;
 long youngest_layer = -1;

 for_each_set_bit(access_bit, &access_req, layer_masks_size) {
  const access_mask_t mask = (*layer_masks)[access_bit];
  long layer;

  if (!mask)
   continue;

  /* __fls(1) == 0 */
  layer = __fls(mask);
  if (layer > youngest_layer) {
   youngest_layer = layer;
   missing = BIT(access_bit);
  } else if (layer == youngest_layer) {
   missing |= BIT(access_bit);
  }
 }

 *access_request = missing;
 if (youngest_layer == -1)
  return domain->num_layers - 1;

 return youngest_layer;
}

#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 layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
  [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0),
  [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1),
  [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0),
  [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2),
 };
 access_mask_t access;

 access = LANDLOCK_ACCESS_FS_EXECUTE;
 KUNIT_EXPECT_EQ(test, 0,
   get_denied_layer(&dom, &access, &layer_masks,
      sizeof(layer_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, &layer_masks,
      sizeof(layer_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, &layer_masks,
      sizeof(layer_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, &layer_masks,
      sizeof(layer_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, &layer_masks,
      sizeof(layer_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, &layer_masks,
      sizeof(layer_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 (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size))
  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,
    request->layer_masks_size);
  } else {
   youngest_layer = get_layer_from_deny_masks(
    &missing, request->all_existing_optional_access,
    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 */

Messung V0.5
C=97 H=90 G=93

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.