[Bug] landlock: sun_path length underflow to SIZE_MAX in landlock_deny_scope_abstract_unix_socket TP_printk -> unbounded OOB read in trace reader
poppet lovelace
dmcjisalive at gmail.com
Tue Aug 25 13:00:05 UTC 2026
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