[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