diff --git a/include/core/defs.h b/include/core/defs.h index 9765fa8..63705ae 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -533,6 +533,10 @@ int sockname_inet(struct socket*, int peer, uint *addr, ushort *port #define SOCK_ABSTRACT_DEV 0xFFFFFFFFu // no filesystem has it; see unix.c int sockreadable(struct socket*); int sock_hup(struct socket*); +uint socknl_pending(struct socket*); +int sock_sendercred(struct socket*, int*, uint*, uint*); +int socknl_copy(struct socket*, uint, char*, int); +void socknl_take(struct socket*, uint); int sock_shutdown(struct socket*, int how); int sockpair(struct socket **a, struct socket **b); const char* sockname(struct socket*); diff --git a/include/net/socket.h b/include/net/socket.h index 7c8dd81..ac17a2c 100644 --- a/include/net/socket.h +++ b/include/net/socket.h @@ -362,6 +362,13 @@ struct socket { // the listener -- which is what Linux records too. int cred_pid; uint cred_uid, cred_gid; + + // Who last wrote at this end, for SCM_CREDENTIALS on the other. Not the + // same thing as cred_*: one end of a socketpair is made by the parent and + // written by the child it is handed to, and WebKit learns its children's + // pids from exactly that. 0 until the first write. + int tx_pid; + uint tx_uid, tx_gid; }; #endif diff --git a/kernel/net/unix.c b/kernel/net/unix.c index ff1b1aa..3b9c8c1 100644 --- a/kernel/net/unix.c +++ b/kernel/net/unix.c @@ -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 diff --git a/kernel/sys/sys_net.c b/kernel/sys/sys_net.c index b577308..53d5280 100644 --- a/kernel/sys/sys_net.c +++ b/kernel/sys/sys_net.c @@ -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; } diff --git a/ldls/kpi/base.c b/ldls/kpi/base.c index 2813745..d69466e 100644 --- a/ldls/kpi/base.c +++ b/ldls/kpi/base.c @@ -839,6 +839,7 @@ static void *kpi_leds_list[2] = { kpi_leds_list, kpi_leds_list }; extern int kpi_queue_delayed_work_on(int cpu, void *q, void *dwork, unsigned long delay); extern int kpi_cancel_work_sync(void *work); +extern void kpi_kfree_skb(void *skb); const struct kpi_sym kpi_base_table[] = { // printing, formatted @@ -945,6 +946,10 @@ const struct kpi_sym kpi_base_table[] = { { "crc32_le", (void *)kpi_crc32_le }, { "get_random_bytes", (void *)kpi_get_random_bytes }, { "__skb_pad", (void *)kpi_skb_pad }, + // r8169 frees every transmitted skb this way; a stub leaked one per frame + // until the DMA heap ran out. The budget argument changes only which cache + // Linux returns it to. + { "napi_consume_skb", (void *)kpi_kfree_skb }, // data { "link_mode_params", (void *)kpi_link_mode_params },