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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue