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:
parent
4ad49ed5f2
commit
3676036762
5 changed files with 120 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue