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

@ -509,6 +509,12 @@ sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
if(s->domain == AF_INET)
return inet_write(s->inet, addr, n);
if(myproc()){
s->tx_pid = myproc()->pid;
s->tx_uid = myproc()->euid;
s->tx_gid = myproc()->egid;
}
// A netlink request is answered immediately, into this socket's own buffer
// rather than a peer's -- there is no peer. Doing the work here is what lets
// the read that follows never block, which matters because musl reads with
@ -922,6 +928,43 @@ sock_shutdown(struct socket *s, int how)
// Readiness, for poll/select
// ---------------------------------------------------------------------------
// Netlink, for recvmsg's MSG_PEEK and MSG_TRUNC: how much is waiting, a copy
// of it from `off` on without taking it, and taking it. Everything waiting
// counts as one datagram -- it is the answer to the requests sent so far, and
// a reader walks the nlmsghdrs inside it whatever their number.
uint
socknl_pending(struct socket *s)
{
acquire(&stab.lock);
uint have = s->nwrite - s->nread;
release(&stab.lock);
return have;
}
int
socknl_copy(struct socket *s, uint off, char *addr, int n)
{
acquire(&stab.lock);
uint have = s->nwrite - s->nread;
if(off >= have)
n = 0;
else if((uint)n > have - off)
n = (int)(have - off);
memmove(addr, s->data + s->nread + off, (uint)n);
release(&stab.lock);
return n;
}
void
socknl_take(struct socket *s, uint n)
{
acquire(&stab.lock);
if(n > s->nwrite - s->nread)
n = s->nwrite - s->nread;
s->nread += n;
release(&stab.lock);
}
int
sockreadable(struct socket *s)
{
@ -933,11 +976,17 @@ sockreadable(struct socket *s)
}
// The peer is gone and everything it sent has been read.
//
// A netlink socket's far end is the kernel, which never hangs up. It has no
// `peer` either, and once drained it used to read as hung up here -- so every
// poll() on GLib's network-monitor socket returned at once, forever.
int
sock_hup(struct socket *s)
{
if(s->domain == AF_INET)
return inet_hup(s->inet);
if(s->domain == AF_NETLINK)
return 0;
if(s->state == SS_LISTENING)
return 0;
@ -1033,6 +1082,27 @@ sock_passcred(struct socket *s)
return s->domain != AF_INET && s->o_passcred;
}
// SCM_CREDENTIALS for SO_PASSCRED: whoever last wrote at the far end, or the
// far end's SO_PEERCRED if nobody has. Per socket, not per message -- a
// receiver sees the writer of the latest bytes, which for the one-writer
// sockets that ask (WebKit's pid handshake, D-Bus) is the writer.
int
sock_sendercred(struct socket *s, int *pid, uint *uid, uint *gid)
{
int r = -1;
acquire(&stab.lock);
if(s->domain != AF_INET && s->peer){
struct socket *p = s->peer;
*pid = p->tx_pid ? p->tx_pid : p->cred_pid;
*uid = p->tx_pid ? p->tx_uid : p->cred_uid;
*gid = p->tx_pid ? p->tx_gid : p->cred_gid;
r = 0;
}
release(&stab.lock);
return r;
}
// SO_PEERCRED: the credentials recorded for the far end of a connected Unix
// socket. -1 if there is no far end.
int