Netlink datagrams can be measured, credentials name their sender

Epiphany's main thread spun at 100% on GLib's network monitor. GLib
sizes each netlink datagram with recvmsg(MSG_PEEK|MSG_TRUNC) and no
buffer; that answered 0, GLib read 0 bytes, the answer stayed queued and
poll() said readable again, forever. recvmsg on netlink now peeks
without consuming and with MSG_TRUNC returns what is waiting. A drained
netlink socket also no longer reports hangup -- it has no peer, and
sock_hup took that for a closed one.

SCM_CREDENTIALS under SO_PASSCRED named the process that created the far
end, not the one that wrote to it. For a socketpair those differ: WebKit
makes the pair, hands one end to a child, and learns the child's pid
from the credentials on its first byte. Each end now records its last
writer.

LDLS: napi_consume_skb frees the skb. r8169 frees every sent frame with
it, and as a stub it leaked one per frame until the DMA heap ran out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-25 14:58:14 +01:00
commit 3676036762
5 changed files with 120 additions and 2 deletions

View file

@ -1066,6 +1066,38 @@ SYSCALL_DEFINE(recvmsg)
if ((file->nonblock || (flags & MSG_DONTWAIT)) && !sockreadable(s))
return -EAGAIN;
// Netlink is datagrams, and its readers size them before reading:
// GLib's network monitor asks recvmsg(MSG_PEEK|MSG_TRUNC) with no buffer
// at all and expects the datagram's length back. Answered 0, it read 0,
// the answer stayed queued, poll said readable, and Epiphany's main
// thread spun on that forever.
//
// A plain read shorter than what is waiting keeps the rest for the next
// one rather than discarding it as Linux would: musl's getifaddrs reads
// in chunks, and this whole queue is one "datagram" only by convention.
int nlret = -1;
if (sock_domain(s) == AF_NETLINK) {
uint have = socknl_pending(s);
if (have == 0)
return -EAGAIN; // never blocks; see sockread
for (i = 0; i < m.msg_iovlen; i++) {
char *p;
int len, rd;
if ((len = fetch_iov(&m, i, &p)) < 0)
return len;
rd = socknl_copy(s, (uint)got, p, len);
got += rd;
if (rd < len)
break;
}
if (!(flags & MSG_PEEK))
socknl_take(s, (uint)got);
if ((uint)got < have && (flags & MSG_PEEK))
mflags |= MSG_TRUNC;
nlret = (flags & MSG_TRUNC) ? (int)have : (int)got;
m.msg_iovlen = 0; // the loop below has nothing left
}
for (i = 0; i < m.msg_iovlen; i++) {
char *p;
int len, rd;
@ -1162,7 +1194,7 @@ SYSCALL_DEFINE(recvmsg)
if (sock_passcred(s) && got > 0) {
struct { int pid; uint uid, gid; } cr;
uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 0;
if (sock_peercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) {
if (sock_sendercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) {
if (m.msg_control && off + CMSG_LEN(sizeof(cr)) <= m.msg_controllen) {
struct cmsghdr c;
char tmp[CMSG_SPACE(sizeof(cr))];
@ -1220,5 +1252,5 @@ SYSCALL_DEFINE(recvmsg)
if (copy_to_user(umsg + __builtin_offsetof(struct msghdr, msg_flags),
&mflags, sizeof(mflags)) < 0)
return -EFAULT;
return (int)got;
return nlret >= 0 ? nlret : (int)got;
}