mirror of
https://github.com/torvalds/linux.git
synced 2026-09-27 11:02:03 +02:00
landlock: Add landlock_add_rule_fs and landlock_add_rule_net tracepoints
Add tracepoints for Landlock rule addition, landlock_add_rule_fs for filesystem rules and landlock_add_rule_net for network rules, so trace consumers can correlate filesystem objects and network ports with their rulesets. Both are emitted under the ruleset lock (asserted in TP_fast_assign) so an eBPF program reads the ruleset, including the rule just inserted, in a consistent snapshot. Add a version field to struct landlock_ruleset, gated on CONFIG_TRACEPOINTS like the id field and incremented under the ruleset lock on each successful landlock_add_rule(2), including when it only extends an existing rule's access rights. It fills the existing 4-byte hole after usage, so the struct does not grow. Pairing the ruleset ID with the version lets a later restrict_self event record the exact ruleset revision merged into a domain. Resolve the filesystem rule's absolute path with d_absolute_path() rather than the d_path() audit uses: d_absolute_path() produces namespace-independent paths that do not depend on the tracer's chroot state, making trace output deterministic regardless of mount namespace configuration. Distinguish the error cases as "<too_long>" (-ENAMETOOLONG) and "<unreachable>" (anonymous files or detached mounts). Also add __trace_print_untrusted_str(), a static inline helper in the header guarded by CREATE_TRACE_POINTS: it escapes separators, quotes, backslashes, and non-printable bytes via string_escape_mem() so an untrusted string (the path here, process names in later denial events) cannot inject field separators or control characters into the ftrace text output. Cc: Christian Brauner <brauner@kernel.org> Cc: Günther Noack <gnoack@google.com> Cc: Justin Suess <utilityemal77@gmail.com> Cc: Masami Hiramatsu <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-9-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
parent
b4540a72be
commit
63747c9477
|
|
@ -11,9 +11,57 @@
|
||||||
#define _TRACE_LANDLOCK_H
|
#define _TRACE_LANDLOCK_H
|
||||||
|
|
||||||
#include <linux/landlock.h>
|
#include <linux/landlock.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/string_helpers.h>
|
||||||
#include <linux/tracepoint.h>
|
#include <linux/tracepoint.h>
|
||||||
|
#include <linux/trace_seq.h>
|
||||||
|
|
||||||
struct landlock_ruleset;
|
struct landlock_ruleset;
|
||||||
|
struct path;
|
||||||
|
|
||||||
|
#ifdef CREATE_TRACE_POINTS
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Escapes @len bytes of an untrusted string into the trace sequence @p so it
|
||||||
|
* cannot inject field separators or control characters into the ftrace text
|
||||||
|
* output, and can be unambiguously recovered. Called from the TP_printk() of
|
||||||
|
* the tracepoints that expose paths and process names. @len is passed by the
|
||||||
|
* caller (rather than derived with strlen()) so a name that is not
|
||||||
|
* NUL-terminated or carries embedded NUL bytes (an abstract socket name) is
|
||||||
|
* escaped in full instead of being truncated at the first NUL.
|
||||||
|
*
|
||||||
|
* Return: a pointer into @p's buffer, or NULL if @src is NULL or the buffer is
|
||||||
|
* exhausted (normal when the trace buffer is full).
|
||||||
|
*/
|
||||||
|
static inline const char *
|
||||||
|
__trace_print_untrusted_str(struct trace_seq *p, const char *src, size_t len)
|
||||||
|
{
|
||||||
|
int escaped_size;
|
||||||
|
char *buf;
|
||||||
|
size_t buf_size = seq_buf_get_buf(&p->seq, &buf);
|
||||||
|
const char *ret = trace_seq_buffer_ptr(p);
|
||||||
|
|
||||||
|
/* Buffer exhaustion is normal when the trace buffer is full. */
|
||||||
|
if (!src || buf_size == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
escaped_size =
|
||||||
|
string_escape_mem(src, len, buf, buf_size,
|
||||||
|
ESCAPE_SPACE | ESCAPE_SPECIAL | ESCAPE_NAP |
|
||||||
|
ESCAPE_APPEND | ESCAPE_OCTAL,
|
||||||
|
" ='\"\\");
|
||||||
|
if (unlikely(escaped_size >= buf_size)) {
|
||||||
|
/* We need some room for the final '\0'. */
|
||||||
|
seq_buf_set_overflow(&p->seq);
|
||||||
|
p->full = 1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
seq_buf_commit(&p->seq, escaped_size);
|
||||||
|
trace_seq_putc(p, 0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CREATE_TRACE_POINTS */
|
||||||
|
|
||||||
/* clang-format off */
|
/* clang-format off */
|
||||||
|
|
||||||
|
|
@ -65,6 +113,14 @@ struct landlock_ruleset;
|
||||||
* lockless snapshot instead: a task's comm, and the deny_access_net struct
|
* lockless snapshot instead: a task's comm, and the deny_access_net struct
|
||||||
* sock (whose network hook holds no socket lock), matching how the sched
|
* sock (whose network hook holds no socket lock), matching how the sched
|
||||||
* and signal trace events sample comm.
|
* and signal trace events sample comm.
|
||||||
|
*
|
||||||
|
* Field encoding
|
||||||
|
* ~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* Fields that mirror the Landlock UAPI use the same C types and endianness
|
||||||
|
* (e.g. network ports are __u64 in host endianness, like
|
||||||
|
* landlock_net_port_attr.port). Per-event details, such as where a value
|
||||||
|
* is byte-swapped, live in the field's own kdoc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,6 +142,7 @@ TRACE_EVENT(landlock_create_ruleset,
|
||||||
|
|
||||||
TP_STRUCT__entry(
|
TP_STRUCT__entry(
|
||||||
__field( __u64, ruleset_id )
|
__field( __u64, ruleset_id )
|
||||||
|
__field( __u32, ruleset_version )
|
||||||
__field( access_mask_t, handled_fs )
|
__field( access_mask_t, handled_fs )
|
||||||
__field( access_mask_t, handled_net )
|
__field( access_mask_t, handled_net )
|
||||||
__field( access_mask_t, scoped )
|
__field( access_mask_t, scoped )
|
||||||
|
|
@ -93,13 +150,14 @@ TRACE_EVENT(landlock_create_ruleset,
|
||||||
|
|
||||||
TP_fast_assign(
|
TP_fast_assign(
|
||||||
__entry->ruleset_id = ruleset->id;
|
__entry->ruleset_id = ruleset->id;
|
||||||
|
__entry->ruleset_version = ruleset->version;
|
||||||
__entry->handled_fs = ruleset->handled_masks.fs;
|
__entry->handled_fs = ruleset->handled_masks.fs;
|
||||||
__entry->handled_net = ruleset->handled_masks.net;
|
__entry->handled_net = ruleset->handled_masks.net;
|
||||||
__entry->scoped = ruleset->handled_masks.scope;
|
__entry->scoped = ruleset->handled_masks.scope;
|
||||||
),
|
),
|
||||||
|
|
||||||
TP_printk("ruleset=%llx handled_fs=%s handled_net=%s scoped=%s",
|
TP_printk("ruleset=%llx.%u handled_fs=%s handled_net=%s scoped=%s",
|
||||||
__entry->ruleset_id,
|
__entry->ruleset_id, __entry->ruleset_version,
|
||||||
__print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES),
|
__print_flags(__entry->handled_fs, "|", _LANDLOCK_ACCESS_FS_NAMES),
|
||||||
__print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES),
|
__print_flags(__entry->handled_net, "|", _LANDLOCK_ACCESS_NET_NAMES),
|
||||||
__print_flags(__entry->scoped, "|", _LANDLOCK_SCOPE_NAMES))
|
__print_flags(__entry->scoped, "|", _LANDLOCK_SCOPE_NAMES))
|
||||||
|
|
@ -124,13 +182,112 @@ TRACE_EVENT(landlock_free_ruleset,
|
||||||
|
|
||||||
TP_STRUCT__entry(
|
TP_STRUCT__entry(
|
||||||
__field( __u64, ruleset_id )
|
__field( __u64, ruleset_id )
|
||||||
|
__field( __u32, ruleset_version )
|
||||||
),
|
),
|
||||||
|
|
||||||
TP_fast_assign(
|
TP_fast_assign(
|
||||||
__entry->ruleset_id = ruleset->id;
|
__entry->ruleset_id = ruleset->id;
|
||||||
|
__entry->ruleset_version = ruleset->version;
|
||||||
),
|
),
|
||||||
|
|
||||||
TP_printk("ruleset=%llx", __entry->ruleset_id)
|
TP_printk("ruleset=%llx.%u",
|
||||||
|
__entry->ruleset_id, __entry->ruleset_version)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landlock_add_rule_fs - Filesystem rule added to a ruleset
|
||||||
|
*
|
||||||
|
* @ruleset: Source ruleset (never NULL).
|
||||||
|
* @access_rights: Effective access mask stored in the rule, not the raw
|
||||||
|
* sys_landlock_add_rule() argument (unhandled rights
|
||||||
|
* added).
|
||||||
|
* @path: Filesystem path for the rule (never NULL).
|
||||||
|
* @pathname: Resolved absolute path string (never NULL; error placeholder
|
||||||
|
* on resolution failure).
|
||||||
|
*
|
||||||
|
* Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so
|
||||||
|
* the reported ruleset is a stable snapshot that no concurrent writer can
|
||||||
|
* change.
|
||||||
|
*/
|
||||||
|
TRACE_EVENT(landlock_add_rule_fs,
|
||||||
|
|
||||||
|
TP_PROTO(const struct landlock_ruleset *ruleset,
|
||||||
|
access_mask_t access_rights, const struct path *path,
|
||||||
|
const char *pathname),
|
||||||
|
|
||||||
|
TP_ARGS(ruleset, access_rights, path, pathname),
|
||||||
|
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field( __u64, ruleset_id )
|
||||||
|
__field( __u32, ruleset_version )
|
||||||
|
__field( access_mask_t, access_rights )
|
||||||
|
__field( dev_t, dev )
|
||||||
|
__field( ino_t, ino )
|
||||||
|
__string( pathname, pathname )
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_fast_assign(
|
||||||
|
lockdep_assert_held(&ruleset->lock);
|
||||||
|
__entry->ruleset_id = ruleset->id;
|
||||||
|
__entry->ruleset_version = ruleset->version;
|
||||||
|
__entry->access_rights = access_rights;
|
||||||
|
__entry->dev = path->dentry->d_sb->s_dev;
|
||||||
|
/*
|
||||||
|
* The inode number may not be the user-visible one,
|
||||||
|
* but it will be the same used by audit.
|
||||||
|
*/
|
||||||
|
__entry->ino = d_backing_inode(path->dentry)->i_ino;
|
||||||
|
__assign_str(pathname);
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_printk("ruleset=%llx.%u access_rights=%s dev=%u:%u ino=%lu path=%s",
|
||||||
|
__entry->ruleset_id, __entry->ruleset_version,
|
||||||
|
__print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_FS_NAMES),
|
||||||
|
MAJOR(__entry->dev), MINOR(__entry->dev), __entry->ino,
|
||||||
|
__trace_print_untrusted_str(p, __get_str(pathname),
|
||||||
|
__get_dynamic_array_len(pathname) - 1))
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* landlock_add_rule_net - Network port rule added to a ruleset
|
||||||
|
*
|
||||||
|
* @ruleset: Source ruleset (never NULL).
|
||||||
|
* @access_rights: Effective access mask stored in the rule, not the raw
|
||||||
|
* sys_landlock_add_rule() argument (unhandled rights
|
||||||
|
* added).
|
||||||
|
* @port: Network port, the landlock_net_port_attr.port UAPI value
|
||||||
|
* forwarded directly.
|
||||||
|
*
|
||||||
|
* Emitted by sys_landlock_add_rule() under the modified ruleset's lock, so
|
||||||
|
* the reported ruleset is a stable snapshot that no concurrent writer can
|
||||||
|
* change.
|
||||||
|
*/
|
||||||
|
TRACE_EVENT(landlock_add_rule_net,
|
||||||
|
|
||||||
|
TP_PROTO(const struct landlock_ruleset *ruleset,
|
||||||
|
access_mask_t access_rights, __u64 port),
|
||||||
|
|
||||||
|
TP_ARGS(ruleset, access_rights, port),
|
||||||
|
|
||||||
|
TP_STRUCT__entry(
|
||||||
|
__field( __u64, ruleset_id )
|
||||||
|
__field( __u32, ruleset_version )
|
||||||
|
__field( access_mask_t, access_rights )
|
||||||
|
__field( __u64, port )
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_fast_assign(
|
||||||
|
lockdep_assert_held(&ruleset->lock);
|
||||||
|
__entry->ruleset_id = ruleset->id;
|
||||||
|
__entry->ruleset_version = ruleset->version;
|
||||||
|
__entry->access_rights = access_rights;
|
||||||
|
__entry->port = port;
|
||||||
|
),
|
||||||
|
|
||||||
|
TP_printk("ruleset=%llx.%u access_rights=%s port=%llu",
|
||||||
|
__entry->ruleset_id, __entry->ruleset_version,
|
||||||
|
__print_flags(__entry->access_rights, "|", _LANDLOCK_ACCESS_NET_NAMES),
|
||||||
|
__entry->port)
|
||||||
);
|
);
|
||||||
|
|
||||||
#undef _LANDLOCK_NAME_ENTRY
|
#undef _LANDLOCK_NAME_ENTRY
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@
|
||||||
#include "ruleset.h"
|
#include "ruleset.h"
|
||||||
#include "setup.h"
|
#include "setup.h"
|
||||||
|
|
||||||
|
#include <trace/events/landlock.h>
|
||||||
|
|
||||||
/* Underlying object management */
|
/* Underlying object management */
|
||||||
|
|
||||||
static void release_inode(struct landlock_object *const object)
|
static void release_inode(struct landlock_object *const object)
|
||||||
|
|
@ -347,7 +349,24 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
|
||||||
return PTR_ERR(id.key.object);
|
return PTR_ERR(id.key.object);
|
||||||
mutex_lock(&ruleset->lock);
|
mutex_lock(&ruleset->lock);
|
||||||
err = landlock_insert_rule(ruleset, id, access_rights, flags);
|
err = landlock_insert_rule(ruleset, id, access_rights, flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Emit after the rule insertion succeeds, so every event corresponds to
|
||||||
|
* a rule that is actually in the ruleset. The ruleset lock is still
|
||||||
|
* held for BTF consistency (enforced by lockdep_assert_held in
|
||||||
|
* TP_fast_assign).
|
||||||
|
*/
|
||||||
|
if (!err && trace_landlock_add_rule_fs_enabled()) {
|
||||||
|
char *buffer __free(__putname) = __getname();
|
||||||
|
const char *pathname =
|
||||||
|
buffer ? resolve_path_for_trace(path, buffer) :
|
||||||
|
"<no_mem>";
|
||||||
|
|
||||||
|
trace_landlock_add_rule_fs(ruleset, access_rights, path,
|
||||||
|
pathname);
|
||||||
|
}
|
||||||
mutex_unlock(&ruleset->lock);
|
mutex_unlock(&ruleset->lock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* No need to check for an error because landlock_insert_rule()
|
* No need to check for an error because landlock_insert_rule()
|
||||||
* increments the refcount for the new object if needed.
|
* increments the refcount for the new object if needed.
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#define _SECURITY_LANDLOCK_FS_H
|
#define _SECURITY_LANDLOCK_FS_H
|
||||||
|
|
||||||
#include <linux/build_bug.h>
|
#include <linux/build_bug.h>
|
||||||
|
#include <linux/cleanup.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/rcupdate.h>
|
#include <linux/rcupdate.h>
|
||||||
|
|
@ -20,6 +21,8 @@
|
||||||
#include "ruleset.h"
|
#include "ruleset.h"
|
||||||
#include "setup.h"
|
#include "setup.h"
|
||||||
|
|
||||||
|
DEFINE_FREE(__putname, char *, if (_T) __putname(_T))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct landlock_inode_security - Inode security blob
|
* struct landlock_inode_security - Inode security blob
|
||||||
*
|
*
|
||||||
|
|
@ -153,4 +156,33 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
|
||||||
const struct path *const path,
|
const struct path *const path,
|
||||||
access_mask_t access_hierarchy, const u32 flags);
|
access_mask_t access_hierarchy, const u32 flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resolve_path_for_trace - Resolve a path for tracepoint display
|
||||||
|
*
|
||||||
|
* @path: The path to resolve.
|
||||||
|
* @buf: A buffer of at least PATH_MAX bytes for the resolved path.
|
||||||
|
*
|
||||||
|
* Uses d_absolute_path() to produce a namespace-independent absolute path,
|
||||||
|
* unlike d_path() which resolves relative to the process's chroot. This
|
||||||
|
* ensures trace output is deterministic regardless of the tracer's mount
|
||||||
|
* namespace.
|
||||||
|
*
|
||||||
|
* Return: A pointer into @buf with the resolved path, or an error string
|
||||||
|
* ("<too_long>", "<unreachable>").
|
||||||
|
*/
|
||||||
|
static inline const char *resolve_path_for_trace(const struct path *path,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
p = d_absolute_path(path, buf, PATH_MAX);
|
||||||
|
if (!IS_ERR_OR_NULL(p))
|
||||||
|
return p;
|
||||||
|
|
||||||
|
if (PTR_ERR(p) == -ENAMETOOLONG)
|
||||||
|
return "<too_long>";
|
||||||
|
|
||||||
|
return "<unreachable>";
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* _SECURITY_LANDLOCK_FS_H */
|
#endif /* _SECURITY_LANDLOCK_FS_H */
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "ruleset.h"
|
#include "ruleset.h"
|
||||||
|
|
||||||
|
#include <trace/events/landlock.h>
|
||||||
|
|
||||||
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
||||||
const u16 port, access_mask_t access_rights,
|
const u16 port, access_mask_t access_rights,
|
||||||
const u32 flags)
|
const u32 flags)
|
||||||
|
|
@ -37,6 +39,15 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
|
||||||
|
|
||||||
mutex_lock(&ruleset->lock);
|
mutex_lock(&ruleset->lock);
|
||||||
err = landlock_insert_rule(ruleset, id, access_rights, flags);
|
err = landlock_insert_rule(ruleset, id, access_rights, flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Emit after the rule insertion succeeds, so every event corresponds to
|
||||||
|
* a rule that is actually in the ruleset. The ruleset lock is still
|
||||||
|
* held for BTF consistency (enforced by lockdep_assert_held in
|
||||||
|
* TP_fast_assign).
|
||||||
|
*/
|
||||||
|
if (!err)
|
||||||
|
trace_landlock_add_rule_net(ruleset, access_rights, port);
|
||||||
mutex_unlock(&ruleset->lock);
|
mutex_unlock(&ruleset->lock);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
*
|
*
|
||||||
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
|
* Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
|
||||||
* Copyright © 2018-2020 ANSSI
|
* Copyright © 2018-2020 ANSSI
|
||||||
|
* Copyright © 2026 Cloudflare, Inc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/bits.h>
|
#include <linux/bits.h>
|
||||||
|
|
@ -306,11 +307,19 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
|
||||||
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
|
.quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET),
|
||||||
},
|
},
|
||||||
} };
|
} };
|
||||||
|
int err;
|
||||||
|
|
||||||
build_check_layer();
|
build_check_layer();
|
||||||
lockdep_assert_held(&ruleset->lock);
|
lockdep_assert_held(&ruleset->lock);
|
||||||
return landlock_store_rule(&ruleset->rules, id, &layers,
|
err = landlock_store_rule(&ruleset->rules, id, &layers,
|
||||||
ARRAY_SIZE(layers));
|
ARRAY_SIZE(layers));
|
||||||
|
|
||||||
|
#ifdef CONFIG_TRACEPOINTS
|
||||||
|
if (!err)
|
||||||
|
ruleset->version++;
|
||||||
|
#endif /* CONFIG_TRACEPOINTS */
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
void landlock_free_rules(struct landlock_rules *const rules)
|
void landlock_free_rules(struct landlock_rules *const rules)
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,13 @@ struct landlock_ruleset {
|
||||||
refcount_t usage;
|
refcount_t usage;
|
||||||
|
|
||||||
#ifdef CONFIG_TRACEPOINTS
|
#ifdef CONFIG_TRACEPOINTS
|
||||||
|
/**
|
||||||
|
* @version: Counter incremented on each successful
|
||||||
|
* landlock_add_rule(2), including when it only extends an existing
|
||||||
|
* rule's access rights. Used by tracepoints to correlate a domain with
|
||||||
|
* the exact ruleset state it was created from. Protected by @lock.
|
||||||
|
*/
|
||||||
|
u32 version;
|
||||||
/**
|
/**
|
||||||
* @id: Unique identifier for this ruleset, used for tracing.
|
* @id: Unique identifier for this ruleset, used for tracing.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user