landlock: Consolidate access-right and scope names in a shared header

Audit formats denial records with per-right name strings.  A following
commit adds trace events that print the same access and scope masks with
__print_flags() and need the same names, but a trace event header cannot
include Landlock-internal headers, so the names cannot be shared from
the logging unit.

Define the filesystem, network, and scope names once, as the
_LANDLOCK_ACCESS_FS_NAMES, _LANDLOCK_ACCESS_NET_NAMES, and
_LANDLOCK_SCOPE_NAMES lists in the public Landlock header.  Each entry
is a _LANDLOCK_NAME_ENTRY() the consumer expands: audit maps it to a
"[bit] = name" array slot for an O(1) lookup, the trace events map it to
a __print_flags() { mask, name } pair.  The bit value comes only from
the LANDLOCK_* UAPI constant each entry references, so every bit-to-name
mapping has a single source and does not depend on entry order.

The shared names are unprefixed; blocker_prefix() prepends the
fs./net./scope. category for audit records, so the scope names move from
inline literals to the shared table too.  Audit records are unchanged.

No functional change.

Cc: Günther Noack <gnoack@google.com>
Cc: Tingmao Wang <m@maowtm.org>
Link: https://patch.msgid.link/20260811094338.288094-7-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
Mickaël Salaün
2026-08-11 11:43:20 +02:00
parent 69ca5782f8
commit bc62ec6034
3 changed files with 109 additions and 31 deletions

View File

@@ -14599,6 +14599,7 @@ F: Documentation/admin-guide/LSM/landlock.rst
F: Documentation/security/landlock.rst
F: Documentation/userspace-api/landlock.rst
F: fs/ioctl.c
F: include/linux/landlock.h
F: include/uapi/linux/landlock.h
F: samples/landlock/
F: security/landlock/

55
include/linux/landlock.h Normal file
View File

@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Landlock - Public types and definitions
*
* Copyright © 2016-2026 Mickaël Salaün <mic@digikod.net>
* Copyright © 2026 Cloudflare, Inc.
*/
#ifndef _LINUX_LANDLOCK_H
#define _LINUX_LANDLOCK_H
#include <uapi/linux/landlock.h>
/*
* Access-right and scope names, shared between the audit records (get_blocker()
* in security/landlock/audit.c) and the trace events
* (include/trace/events/landlock.h). A consumer defines
* _LANDLOCK_NAME_ENTRY(mask, name) before expanding a list and undefines it
* afterwards: audit maps each entry to a "[bit] = name" slot for O(1) lookup,
* the trace events map it to a __print_flags() { mask, name } pair. The bit
* value lives only in the LANDLOCK_* UAPI constant each entry references.
* Names are unprefixed; audit prepends the "fs."/"net."/"scope." category.
*/
#define _LANDLOCK_ACCESS_FS_NAMES \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_EXECUTE, "execute"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_WRITE_FILE, "write_file"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_READ_FILE, "read_file"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_READ_DIR, "read_dir"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REMOVE_DIR, "remove_dir"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REMOVE_FILE, "remove_file"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_CHAR, "make_char"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_DIR, "make_dir"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_REG, "make_reg"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_SOCK, "make_sock"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_FIFO, "make_fifo"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_BLOCK, "make_block"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_MAKE_SYM, "make_sym"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_REFER, "refer"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_TRUNCATE, "truncate"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_IOCTL_DEV, "ioctl_dev"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_FS_RESOLVE_UNIX, "resolve_unix")
#define _LANDLOCK_ACCESS_NET_NAMES \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_TCP, "bind_tcp"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_TCP, "connect_tcp"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_BIND_UDP, "bind_udp"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP, \
"connect_send_udp")
#define _LANDLOCK_SCOPE_NAMES \
_LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, \
"abstract_unix_socket"), \
_LANDLOCK_NAME_ENTRY(LANDLOCK_SCOPE_SIGNAL, "signal")
#endif /* _LINUX_LANDLOCK_H */

View File

@@ -7,6 +7,7 @@
#include <linux/audit.h>
#include <linux/bitops.h>
#include <linux/landlock.h>
#include <linux/lsm_audit.h>
#include <linux/pid.h>
#include <uapi/linux/landlock.h>
@@ -19,38 +20,28 @@
#include "limits.h"
#include "log.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",
[BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix",
};
/*
* Access-right and scope names are built from the lists shared with the trace
* events (see <linux/landlock.h>). The designated initializer places each name
* at its bit index, so the lookup stays O(1) and does not depend on the entry
* order. log_blockers() adds the "fs."/"net."/"scope." category prefix.
*/
#define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name
static const char *const fs_access_strings[] = { _LANDLOCK_ACCESS_FS_NAMES };
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",
[BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp",
[BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] =
"net.connect_send_udp",
};
static const char *const net_access_strings[] = { _LANDLOCK_ACCESS_NET_NAMES };
static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET);
static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES };
static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE);
#undef _LANDLOCK_NAME_ENTRY
static __attribute_const__ const char *
get_blocker(const enum landlock_request_type type,
const unsigned long access_bit)
@@ -62,7 +53,7 @@ get_blocker(const enum landlock_request_type type,
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
WARN_ON_ONCE(access_bit != -1);
return "fs.change_topology";
return "change_topology";
case LANDLOCK_REQUEST_FS_ACCESS:
if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings)))
@@ -76,32 +67,63 @@ get_blocker(const enum landlock_request_type type,
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
WARN_ON_ONCE(access_bit != -1);
return "scope.abstract_unix_socket";
return scope_strings[BIT_INDEX(
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)];
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
WARN_ON_ONCE(access_bit != -1);
return "scope.signal";
return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)];
}
WARN_ON_ONCE(1);
return "unknown";
}
/*
* Returns the audit category prefix prepended to the unprefixed blocker name
* returned by get_blocker() (filesystem and network access rights,
* change_topology, and scopes). The ptrace blocker is standalone and carries
* its full name in get_blocker(), so it uses no prefix.
*/
static __attribute_const__ const char *
blocker_prefix(const enum landlock_request_type type)
{
switch (type) {
case LANDLOCK_REQUEST_PTRACE:
return "";
case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
case LANDLOCK_REQUEST_FS_ACCESS:
return "fs.";
case LANDLOCK_REQUEST_NET_ACCESS:
return "net.";
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
return "scope.";
}
WARN_ON_ONCE(1);
return "";
}
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;
const char *const prefix = blocker_prefix(type);
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 ? "" : ",",
audit_log_format(ab, "%s%s%s", is_first ? "" : ",", prefix,
get_blocker(type, access_bit));
is_first = false;
}
if (is_first)
audit_log_format(ab, "%s", get_blocker(type, -1));
audit_log_format(ab, "%s%s", prefix, get_blocker(type, -1));
}
static void log_domain(struct landlock_hierarchy *const hierarchy)