[Bug] landlock: sun_path length underflow to SIZE_MAX in landlock_deny_scope_abstract_unix_socket TP_printk -> unbounded OOB read in trace reader
Mickaël Salaün
mic at digikod.net
Fri Aug 28 19:12:47 UTC 2026
Hi Charles,
This is an impressive report but there is no bug.
There is no underflow because __string_len() reserves one extra byte for
the terminating NUL. I sent a test patch to prove that:
https://lore.kernel.org/r/20260828190038.71831-1-mic@digikod.net
Dropping the "- 1" would introduce a new bug though.
Bug reports are very welcome but please test the potential issue before.
See https://docs.kernel.org/process/coding-assistants.html#procedure-for-finding-and-fixing-bugs
Regards,
Mickaël
On Tue, Aug 25, 2026 at 09:00:05PM +0800, poppet lovelace wrote:
> Hi Mickaël,
>
> While auditing the new landlock tracepoints we found a size_t underflow in
> the TP_printk() of landlock_deny_scope_abstract_unix_socket that turns a
> zero-length abstract AF_UNIX address into an effectively unbounded OOB read
> in the context of whoever consumes the trace (typically root reading
> tracefs). The tracepoints are new in the current merge window, so this can
> still be fixed before 7.3.
>
> Root cause
> ----------
>
> The event stores the abstract name correctly:
>
> include/trace/events/landlock.h:899-932
>
> __string_len(sun_path,
> unix_sk(peer)->addr->name->sun_path + 1,
> unix_sk(peer)->addr->len -
> offsetof(struct sockaddr_un, sun_path) - 1)
>
> For an abstract address bound with addrlen == sizeof(sa_family_t) + 1 (= 3),
> which net/unix/af_unix.c:335-345 (unix_validate_addr) accepts, addr->len is
> 3, so the stored dynamic-array length is 0.
>
> The printer then subtracts one more:
>
> include/trace/events/landlock.h:954-955
>
> __trace_print_untrusted_str(p, __get_str(sun_path),
> __get_dynamic_array_len(sun_path) - 1)
>
> 0u - 1 wraps to SIZE_MAX. __trace_print_untrusted_str()
> (include/trace/events/landlock.h:36-68) forwards that length to
> string_escape_mem(), whose main loop
>
> lib/string_helpers.c:586
>
> while (isz--) {
> unsigned char c = *src++;
>
> reads every input byte regardless of output exhaustion (the escape_*
> helpers stop writing past `end`, but nothing breaks the input walk). With
> isz == SIZE_MAX the read walks forward from the ring-buffer record through
> the rest of the tracing buffer and the kernel direct map until it faults.
>
> Impact
> ------
>
> An unprivileged task inside any landlock domain that scopes abstract unix
> sockets can plant such a record deterministically. The OOB read itself
> executes when the trace buffer is consumed (e.g. cat of
> /sys/kernel/tracing/trace or trace_pipe by a privileged monitor -- exactly
> the deployment these tracepoints target):
>
> * information disclosure: escaped bytes of adjacent kernel memory
> (ring-buffer contents and beyond, i.e. direct map) are copied into the
> visible trace text;
> * availability: the walk ends in a page fault / oops in the reader's
> syscall context; with panic_on_oops this takes the whole machine down.
>
> Same-call-site audit: the other __trace_print_untrusted_str() users subtract
> 1 as well (pathname :389/:725, comm :829/:878), but their captured lengths
> cannot be 0 (dentry names are non-empty; comm is fixed-size), so only the
> abstract-socket event is currently reachable. The -1 looks like a leftover
> from string lengths that include a terminator; here the capture side already
> excluded the leading abstract-name NUL.
>
> Reproducer (hand-written, not executed against a live kernel)
> -------------------------------------------------------------
>
> int s = socket(AF_UNIX, SOCK_STREAM, 0);
> struct sockaddr_un a = { .sun_family = AF_UNIX };
> a.sun_path[0] = '\0'; /* abstract, zero length */
> bind(s, (struct sockaddr *)&a, sizeof(a.sun_family) + 1); /* ok, len 3 */
>
> /* task restricted by a domain scoping abstract unix sockets: */
> connect(cfd, (struct sockaddr *)&a, 3); /* denied -> event recorded */
>
> # echo 1 > /sys/kernel/tracing/events/landlock/\
> landlock_deny_scope_abstract_unix_socket/enable
> # cat /sys/kernel/tracing/trace /* OOB read here */
>
> Self-assessed severity
> ----------------------
>
> CVSS:3.1 AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H = 6.7 (Medium).
>
> If the cross-boundary read is judged Scope:Changed (kernel-memory bytes are
> surfaced into user-visible trace text, and the walk can traverse the whole
> direct map until it faults): AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:H = 7.3.
> Honest range 5.5-7.3 depending on scope treatment. Preconditions: the
> landlock tracepoints are enabled and consumed by a privileged reader --
> i.e. exactly the monitoring deployment they were added for.
>
> Suggested fixes (either suffices; both preferred)
> -------------------------------------------------
>
> 1. Drop the "- 1" at include/trace/events/landlock.h:955 -- the capture-side
> length already excludes the abstract prefix NUL; the subtraction is a
> double decrement. Please also re-check :389/:725/:829/:878 against their
> capture lengths.
> 2. Harden __trace_print_untrusted_str() (or string_escape_mem()) so the
> input walk cannot exceed the source object: e.g. clamp len, or break the
> loop when output is exhausted (note this changes string_escape_mem()'s
> return-size contract for existing callers).
>
> Found by Charles <dmcjisalive at gmail.com> -- happy to test a fix.
>
> Thanks,
> Charles
>
> Reported-by: Charles <dmcjisalive at gmail.com>
>
More information about the Linux-security-module-archive
mailing list