SchOS/kernel/sys/sys_net.c
Nexoniarz b411836478 libinput, seatd and libseat; sockets from accept() can pass descriptors
Alpine's libinput, libseat, seatd, xf86-input-libinput and usbutils join the
image, and seatd runs from schinit. libinput lists the keyboard and pointer as
an ordinary user, Xorg now drives both through libinput, and a program linked
to libseat running as schos opens seat0 through seatd, is handed
/dev/input/event0 and reads its name through the descriptor.

Two kernel bugs stood in the way, and both would have stopped a Wayland
compositor too. The socket connect() queues for a listener was allocated with
its domain at zero, so sendmsg refused SCM_RIGHTS on the accepted end: a server
could receive descriptors and never send one. And sendmsg treated a control
buffer longer than its messages as an error, where Linux takes a following
header too short to be one as the end; seatd passes a zeroed CMSG_SPACE array
with one descriptor at the front.

Found with /proc/strace and a temporary kinfo in sendmsg's EINVAL sites.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
2026-10-26 16:51:39 +01:00

1361 lines
45 KiB
C

// Socket syscalls.
//
// The kernel side is in kernel/net/unix.c; this file is only the translation
// between a syscall's user-supplied `struct sockaddr` and the path string the
// socket layer actually wants, plus the file-descriptor bookkeeping.
//
// Two families now. AF_UNIX is answered by kernel/net/unix.c, AF_INET by
// kernel/net/inet.c on top of lwIP; the socket layer dispatches on the domain
// the socket was created with, so this file's only extra job is to read the
// right shape of sockaddr out of user memory. AF_INET6 is still refused with
// EAFNOSUPPORT rather than EINVAL, because a program that probes for IPv6 and
// falls back to IPv4 needs to tell "not here" from "you passed nonsense".
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/mmu.h"
#include "core/proc.h"
#include "fs/fs.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "uapi/stat.h"
#include "fs/file.h"
#include "uapi/posix.h"
#include "core/syscall.h"
#include "core/uaccess.h"
#include "core/kmem.h"
#include "uapi/errno.h"
#include "net/socket.h"
// Pull the sun_path out of a user `struct sockaddr_un`, NUL-terminated.
//
// The length the caller gives is checked rather than trusted: musl passes
// `offsetof(sockaddr_un, sun_path) + strlen(path) + 1`, which is shorter than
// the full struct, so reading sizeof(struct sockaddr_un) from user memory
// would fault on a path that sits near the end of a page.
static int
fetch_sun_path(addr_t uaddr, int ulen, char *out)
{
struct sockaddr_un sa;
int plen;
if (ulen < (int)sizeof(sa.sun_family) || ulen > (int)sizeof(sa))
return -EINVAL;
memset(&sa, 0, sizeof(sa));
if (copy_from_user(&sa, uaddr, (uint64)ulen) < 0)
return -EFAULT;
if (sa.sun_family == AF_INET)
return -EAFNOSUPPORT;
if (sa.sun_family != AF_UNIX)
return -EAFNOSUPPORT;
plen = ulen - (int)sizeof(sa.sun_family);
if (plen <= 0)
return -EINVAL;
if (plen > UNIX_PATH_MAX - 1)
plen = UNIX_PATH_MAX - 1;
memmove(out, sa.sun_path, (uint)plen);
out[plen] = '\0';
// A NUL inside the copied region is fine -- it just ends the path early,
// which is what a caller passing the full struct length does.
out[UNIX_PATH_MAX - 1] = '\0';
return 0;
}
// For an abstract address -- sun_path[0] == 0 -- the name as a key, and 1.
// The name is every byte after the NUL up to the length given, NULs included,
// as Linux has it; FNV-1a, which is plenty for a table of NSOCK.
static int
sun_abstract(addr_t uaddr, int ulen, uint64 *key)
{
struct sockaddr_un sa;
int plen = ulen - (int)sizeof(sa.sun_family);
if (plen < 2 || ulen > (int)sizeof(sa))
return 0;
if (copy_from_user(&sa, uaddr, (uint64)ulen) < 0 || sa.sun_path[0] != 0)
return 0;
uint64 h = 1469598103934665603ULL;
for (int i = 1; i < plen; i++) {
h ^= (uchar)sa.sun_path[i];
h *= 1099511628211ULL;
}
*key = h ? h : 1;
return 1;
}
// Wrap an established socket in a file and a descriptor. Consumes the
// socket's reference on success; on failure the caller still owns it.
//
// `sflags` carries SOCK_CLOEXEC and SOCK_NONBLOCK, which socket(), socketpair()
// and accept4() all take and all used to discard.
//
// Discarding them is not harmless in either direction. A dropped SOCK_CLOEXEC
// leaks the descriptor across every exec the process makes -- so a server that
// forks a child holds its listening socket open in it, and the port stays bound
// after the server dies. A dropped SOCK_NONBLOCK is worse: the caller believes
// it has a non-blocking socket, does not select() first, and blocks forever in
// what it thinks is a poll loop.
//
// Both are one line each, and they were masked off with a comment saying they
// were not honoured -- which is exactly the class of quiet lie the rest of this
// file has just been cleaned of.
static int
sock_to_fd(struct socket *s, int sflags)
{
struct file *f;
int fd;
if ((f = filealloc()) == 0)
return -ENFILE;
if ((fd = fdalloc(f)) < 0) {
f->ref = 0;
return -EMFILE;
}
f->type = FD_SOCKET;
f->sock = s;
f->readable = 1;
f->writable = 1;
f->off = 0;
f->nonblock = (sflags & SOCK_NONBLOCK) != 0;
if (sflags & SOCK_CLOEXEC)
myproc()->files->flags[fd] |= FD_CLOEXEC;
return fd;
}
// Resolve a descriptor to the socket behind it.
static int
argsock(int n, struct socket **ps)
{
int fd;
struct file *f;
if (argint(n, &fd) < 0)
return -EBADF;
if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd]))
return -EBADF;
if (f->type != FD_SOCKET)
return -ENOTSOCK;
*ps = f->sock;
return 0;
}
SYSCALL_DEFINE(socket)
{
int domain, type, protocol;
struct socket *s;
int fd;
argint(0, &domain);
argint(1, &type);
argint(2, &protocol);
if (domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
return -EAFNOSUPPORT;
// SOCK_CLOEXEC / SOCK_NONBLOCK ride in the high bits of `type`. Split them
// off rather than discarding them: the type check below wants the low byte,
// and sock_to_fd wants the flags.
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
if (type & ~(0xFF | SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= 0xFF;
if (domain == AF_UNIX && type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
return -EPROTOTYPE;
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM &&
type != SOCK_RAW)
return -EPROTOTYPE;
// AF_NETLINK takes SOCK_RAW or SOCK_DGRAM and its own protocol numbering;
// socknew checks both, because which protocols exist is the socket layer's
// business rather than this one's.
if (domain == AF_NETLINK && type != SOCK_RAW && type != SOCK_DGRAM)
return -EPROTOTYPE;
// Protocol 0 means "the default for this type". The one non-default we
// support is a ping socket -- SOCK_DGRAM with IPPROTO_ICMP, Linux's way of
// letting an unprivileged process do the one thing raw sockets were used
// for. Anything else is still refused rather than quietly given a socket
// that speaks a different protocol than the caller asked for.
//
// IPPROTO_TCP with SOCK_STREAM and IPPROTO_UDP with SOCK_DGRAM name the
// default rather than departing from it, and programs do write them out:
// toybox's `nc` passes IPPROTO_TCP, and refusing it meant "Protocol not
// supported" for TCP on a system whose whole network is TCP.
if (protocol != 0 && domain != AF_NETLINK) {
int redundant =
(domain == AF_INET && type == SOCK_STREAM && protocol == IPPROTO_TCP) ||
(domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP);
if (redundant)
protocol = 0;
else if (!(domain == AF_INET &&
(type == SOCK_DGRAM || type == SOCK_RAW) &&
protocol == IPPROTO_ICMP))
return -EPROTONOSUPPORT;
}
// A raw socket is ICMP or nothing: it exists because busybox's ping, which
// is /bin/ping on the Alpine root, opens SOCK_RAW and stops at
// EPROTOTYPE. Raw TCP or UDP would be the stack handing over packets it is
// also processing, and nothing has asked for that.
if (domain == AF_INET && type == SOCK_RAW && protocol != IPPROTO_ICMP)
return -EPROTONOSUPPORT;
// ENETDOWN rather than ENFILE when the stack itself is not running: the
// old blanket ENFILE said "file table overflow" for a machine with no
// network, which is a long way from the truth and a longer way from the
// fix.
if (domain == AF_INET && !net_is_ready())
return -ENETDOWN;
if ((s = socknew(domain, type, protocol)) == 0)
return -ENFILE;
if ((fd = sock_to_fd(s, sflags)) < 0) {
sockclose(s);
return fd;
}
return fd;
}
// Pull an address and port out of a user `struct sockaddr_in`.
//
// Both come back in network byte order and stay that way: lwIP wants them that
// way, so converting here and back again would be two swaps that cancel.
static int
fetch_sin(addr_t uaddr, int ulen, uint *addr, ushort *port)
{
struct sockaddr_in sa;
if (ulen < (int)sizeof(sa))
return -EINVAL;
if (copy_from_user(&sa, uaddr, sizeof(sa)) < 0)
return -EFAULT;
if (sa.sin_family != AF_INET)
return -EAFNOSUPPORT;
*addr = sa.sin_addr;
*port = sa.sin_port;
return 0;
}
// Network byte order to host, for a port. The kernel is little-endian and the
// wire is big-endian; lwIP's own ntohs is not reachable from here without
// dragging its headers into the syscall layer.
static ushort
port_from_net(ushort p)
{
return (ushort)(((p & 0xFF) << 8) | ((p >> 8) & 0xFF));
}
SYSCALL_DEFINE(bind)
{
struct socket *s;
addr_t uaddr;
int ulen, r;
char path[UNIX_PATH_MAX];
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argint(2, &ulen);
if (sock_domain(s) == AF_INET) {
uint ip;
ushort port;
if ((r = fetch_sin(uaddr, ulen, &ip, &port)) < 0)
return r;
return sockbind_in(s, ip, port_from_net(port));
}
// sockaddr_nl: family, pad, port id, groups. Only the groups matter here.
if (sock_domain(s) == AF_NETLINK) {
uint pid = 0, groups = 0;
if (ulen < 12)
return -EINVAL;
if (copy_from_user(&pid, uaddr + 4, 4) < 0 ||
copy_from_user(&groups, uaddr + 8, 4) < 0)
return -EFAULT;
return sock_netlink_bind(s, pid, groups);
}
uint64 key;
if (sun_abstract(uaddr, ulen, &key))
return sockbind_abstract(s, key);
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
// The node lives in the filesystem now, and it is made here rather than in
// sockbind() because this is the layer that has both -- kernel/net/unix.c
// knows about sockets and has no business calling create().
//
// EADDRINUSE comes from the directory, not from a scan of the socket
// table: the name being taken is a property of the filesystem, and a
// socket file left behind by a crashed server has to refuse a rebind for
// the same reason a running one does. Otherwise two servers both believe
// they own it.
{
struct inode *ip;
uint dev;
uint64 ino;
begin_op();
if ((ip = namei(path)) != 0) {
iput(ip);
end_op();
return -EADDRINUSE;
}
if ((ip = create_sock(path)) == 0) {
end_op();
return -ENOENT; // the directory does not exist
}
dev = ip->dev;
ino = ip->inum;
iunlockput(ip);
end_op();
r = sockbind(s, path, dev, ino);
if (r < 0) {
// Take the node back out, so a failed bind leaves nothing behind
// for the next one to trip over.
begin_op();
unlink_path(path);
end_op();
}
return r;
}
}
SYSCALL_DEFINE(listen)
{
struct socket *s;
int backlog, r;
if ((r = argsock(0, &s)) < 0)
return r;
argint(1, &backlog);
return socklisten(s, backlog);
}
SYSCALL_DEFINE(connect)
{
struct socket *s;
addr_t uaddr;
int ulen, r;
char path[UNIX_PATH_MAX];
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argint(2, &ulen);
if (sock_domain(s) == AF_INET) {
uint ip;
ushort port;
if ((r = fetch_sin(uaddr, ulen, &ip, &port)) < 0)
return r;
return sockconnect_in(s, ip, port_from_net(port));
}
uint64 key;
if (sun_abstract(uaddr, ulen, &key))
return sockconnect(s, SOCK_ABSTRACT_DEV, key);
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
// Resolve the path here, for the same reason bind() creates here. A path
// that names nothing, or names something that is not a socket, is
// ECONNREFUSED -- which is what Linux says and what a client can act on.
{
struct inode *ip;
uint dev;
uint64 ino;
short type;
if ((ip = namei(path)) == 0)
return -ECONNREFUSED;
ilock(ip);
dev = ip->dev;
ino = ip->inum;
type = ip->type;
iunlockput(ip);
if (type != T_SOCK)
return -ECONNREFUSED;
return sockconnect(s, dev, ino);
}
}
// accept(2) and accept4(2) share everything but the flags word.
//
// They are two table entries rather than one with an alias, and the reason is
// the argument count: accept takes three, so a handler that read a fourth
// register would be reading whatever the caller happened to leave there and
// would invent a SOCK_NONBLOCK out of stack litter. That is precisely the bug
// the old `SYSCALL_ALIAS(288, accept)` would have grown the moment the flags
// started being honoured.
static addr_t
do_accept(int sflags)
{
struct socket *s, *c;
addr_t uaddr, ulenp;
int r, fd;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argaddr(2, &ulenp);
if ((c = sockaccept(s)) == 0)
return -EINVAL;
if ((fd = sock_to_fd(c, sflags)) < 0) {
sockclose(c);
return fd;
}
// The peer address of a Unix socket that never bound a name is the empty
// one: family AF_UNIX and a zero-length path. Reporting it is optional
// (both pointers may be null) but a caller that asks must not be left
// with an uninitialised struct it then prints.
if (uaddr && ulenp) {
struct sockaddr_un sa;
int len = (int)sizeof(sa.sun_family);
int want = 0;
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
safestrcpy(sa.sun_path, sockname(c), UNIX_PATH_MAX);
len += (int)strlen(sa.sun_path) + 1;
if (get_user(want, ulenp) < 0)
return fd; // the fd is already live; do not fail it
if (want > len)
want = len;
if (want > 0)
copy_to_user(uaddr, &sa, (uint64)want);
put_user(len, ulenp);
}
return fd;
}
SYSCALL_DEFINE(accept) { return do_accept(0); }
SYSCALL_DEFINE(accept4)
{
int flags = 0;
argint(3, &flags);
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
return do_accept(flags);
}
SYSCALL_DEFINE(socketpair)
{
int domain, type, protocol;
addr_t usv;
struct socket *a, *b;
int fd0, fd1, r;
argint(0, &domain);
argint(1, &type);
argint(2, &protocol);
argaddr(3, &usv);
if (domain != AF_UNIX)
return -EAFNOSUPPORT;
int sflags = type & (SOCK_CLOEXEC | SOCK_NONBLOCK);
type &= 0xFF;
// Datagram and sequenced-packet pairs keep message boundaries (kernel/net/unix.c).
if (type != SOCK_STREAM && type != SOCK_DGRAM && type != SOCK_SEQPACKET)
return -EPROTOTYPE;
if ((r = sockpair_type(&a, &b, type)) < 0)
return r;
if ((fd0 = sock_to_fd(a, sflags)) < 0) {
sockclose(a);
sockclose(b);
return fd0;
}
if ((fd1 = sock_to_fd(b, sflags)) < 0) {
myproc()->files->ofile[fd0] = 0;
sockclose(a);
sockclose(b);
return fd1;
}
int sv[2] = { fd0, fd1 };
if (copy_to_user(usv, sv, sizeof(sv)) < 0) {
myproc()->files->ofile[fd0] = 0;
myproc()->files->ofile[fd1] = 0;
sockclose(a);
sockclose(b);
return -EFAULT;
}
return 0;
}
// Host byte order to network, for a port on its way back to userland. The
// inverse of port_from_net above, and the same two lines: a sockaddr always
// carries the port big-endian, whichever direction it is travelling.
static ushort
port_to_net(ushort p)
{
return (ushort)(((p & 0xFF) << 8) | ((p >> 8) & 0xFF));
}
// send/recv with no flags are read/write, and sendto/recvfrom are those plus an
// address. Implementing them here rather than leaving them ENOSYS saves every
// ported program from being rewritten to use read() and write().
SYSCALL_DEFINE(sendto)
{
struct socket *s;
addr_t ubuf, uaddr;
int len, flags, ulen, r;
uint ip = 0;
ushort port = 0;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &ubuf);
argint(2, &len);
argint(3, &flags);
argaddr(4, &uaddr);
argint(5, &ulen);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
// A null address is send(), not an error: the socket already knows where
// it is pointed, which for TCP is the only possibility anyway.
if (uaddr) {
if ((r = fetch_sin(uaddr, ulen, &ip, &port)) < 0)
return r;
port = port_from_net(port);
}
return socksendto(s, p, len, ip, port);
}
static int argsockfile(int n, struct socket **sp, struct file **fp);
SYSCALL_DEFINE(recvfrom)
{
struct socket *s;
struct file *file;
addr_t ubuf, uaddr, ulenp;
int len, flags, r;
uint ip = 0;
ushort port = 0;
if ((r = argsockfile(0, &s, &file)) < 0)
return r;
argaddr(1, &ubuf);
argint(2, &len);
argint(3, &flags);
argaddr(4, &uaddr);
argaddr(5, &ulenp);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
// As in recvmsg. This one blocked regardless, and musl's resolver -- which
// sends the A and AAAA queries together and reads its non-blocking socket
// until EAGAIN -- waited for ever after the first answer: NetSurf sat on
// "Loading" with the reply in hand.
if ((file->nonblock || (flags & MSG_DONTWAIT)) && !sockreadable(s))
return -EAGAIN;
r = sockrecvfrom(s, p, len, &ip, &port);
if (r < 0 || uaddr == 0)
return r;
// The address is reported only for the family that has one to report. A
// Unix socket leaves ip at zero, and the honest answer there is a length of
// zero rather than a sockaddr full of nothing that a caller would read as
// 0.0.0.0:0 and believe.
int alen = 0;
if (sock_domain(s) == AF_INET) {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr = ip;
sa.sin_port = port_to_net(port);
alen = (int)sizeof(sa);
// Truncated rather than refused if the caller's buffer is smaller,
// which is what accept() and getsockname() do everywhere: the length
// written back tells them what they got.
int cap = alen;
if (ulenp && get_user(cap, ulenp) < 0)
return -EFAULT;
if (cap > alen)
cap = alen;
if (cap > 0 && copy_to_user(uaddr, &sa, (uint64)cap) < 0)
return -EFAULT;
} else if (sock_domain(s) == AF_NETLINK) {
// Everything on a netlink socket here comes from the kernel: port id
// 0, no groups. GLib's network monitor checks exactly that before it
// believes a message, and with no address at all it hit an assertion
// and every GNOME program that watches the network aborted.
uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 };
uint g = sock_nl_groups(s), from = sock_nl_from(s);
nl[2] = (uint16)from; nl[3] = (uint16)(from >> 16);
nl[4] = (uint16)g; nl[5] = (uint16)(g >> 16);
alen = (int)sizeof(nl);
int cap = alen;
if (ulenp && get_user(cap, ulenp) < 0)
return -EFAULT;
if (cap > alen)
cap = alen;
if (cap > 0 && copy_to_user(uaddr, nl, (uint64)cap) < 0)
return -EFAULT;
}
if (ulenp && copy_to_user(ulenp, &alen, sizeof(alen)) < 0)
return -EFAULT;
return r;
}
// getsockname(2) and getpeername(2).
//
// Neither existed, and the first program to mind was every X client: libxcb
// picks the cookie to send by the family of the connection's address, asked
// with getpeername and then getsockname, and with both failing it sent none.
// Xorg answered "Authorization required, but no authorization protocol
// specified" and startx gave up.
static int
sock_name(int peer)
{
struct socket *s;
addr_t uaddr, ulenp;
int r, cap;
if ((r = argsock(0, &s)) < 0)
return r;
argaddr(1, &uaddr);
argaddr(2, &ulenp);
if (get_user(cap, ulenp) < 0)
return -EFAULT;
if (cap < 0)
return -EINVAL;
union {
struct sockaddr_in in;
struct sockaddr_un un;
} sa;
int alen;
memset(&sa, 0, sizeof(sa));
if (sock_domain(s) == AF_INET) {
uint ip;
ushort port;
if ((r = sockname_inet(s, peer, &ip, &port)) < 0)
return r;
sa.in.sin_family = AF_INET;
sa.in.sin_addr = ip;
sa.in.sin_port = port_to_net(port);
alen = (int)sizeof(sa.in);
} else if (sock_domain(s) == AF_NETLINK) {
// A sockaddr_nl of zeros: family, padding, port id 0 (the kernel),
// no groups.
sa.un.sun_family = AF_NETLINK;
{
// Port id and bound groups, at their offsets in sockaddr_nl.
uint pid = sock_nl_pid(s), grp = sock_nl_bound_groups(s);
memmove((char *)&sa + 4, &pid, 4);
memmove((char *)&sa + 8, &grp, 4);
}
alen = 12;
} else {
if ((r = sockname_unix(s, peer, sa.un.sun_path)) < 0)
return r;
sa.un.sun_family = (uint16)sock_domain(s);
// The family, the path and its NUL; an unnamed socket is the family
// alone, as on Linux.
alen = (int)sizeof(sa.un.sun_family);
if (sa.un.sun_path[0])
alen += (int)strlen(sa.un.sun_path) + 1;
}
// Truncated to the caller's buffer, with the full length written back.
if (cap > alen)
cap = alen;
if (cap > 0 && copy_to_user(uaddr, &sa, (uint64)cap) < 0)
return -EFAULT;
if (copy_to_user(ulenp, &alen, sizeof(alen)) < 0)
return -EFAULT;
return 0;
}
SYSCALL_DEFINE(getsockname) { return sock_name(0); }
SYSCALL_DEFINE(getpeername) { return sock_name(1); }
// shutdown(2). SHUT_WR is a half-close: the peer reads EOF and can still
// write back (see sock_shutdown).
SYSCALL_DEFINE(sockshutdown)
{
struct socket *s;
int how, r;
if ((r = argsock(0, &s)) < 0)
return r;
argint(1, &how);
return sock_shutdown(s, how);
}
// setsockopt / getsockopt.
//
// These both used to succeed unconditionally -- setsockopt was `return 0;` and
// getsockopt wrote a zero for every option asked about. The comment said it was
// so that a server setting SO_REUSEADDR and treating failure as fatal would not
// fail.
//
// That trade is the wrong way round, and it is worth being precise about why,
// because "accept and ignore" looks like the kind thing to do. A caller that
// gets ENOPROTOOPT knows the option did not take effect and can decide what to
// do. A caller that gets success *changes its behaviour on the strength of it*:
// it sets SO_RCVTIMEO and then blocks forever in a read it believes will time
// out; it sets TCP_NODELAY and reports latency numbers that measure Nagle. The
// failure is silent, remote from the call, and indistinguishable from a bug in
// the protocol stack.
//
// So the rule here is the one the rest of this kernel already follows: do it,
// or say you did not. The four options below are genuinely applied to lwIP's
// pcb; the readable ones report the truth; everything else is ENOPROTOOPT.
//
// SO_REUSEADDR is honoured rather than refused precisely because of the
// original comment's concern -- it is the one option a server really does set
// and really does check.
// Fetch the int a setsockopt call is passing. Every option handled here is an
// int; anything wanting a struct (SO_RCVTIMEO and friends) is refused above.
static int
opt_in(addr_t uval, int ulen, int *out)
{
if (!uval || ulen < (int)sizeof(int))
return -EINVAL;
return get_user(*out, uval) < 0 ? -EFAULT : 0;
}
// Hand one int back, and report how many bytes that was.
static int
opt_out(addr_t uval, addr_t ulenp, int val)
{
int want = 0;
if (!uval || !ulenp)
return -EFAULT;
if (get_user(want, ulenp) < 0)
return -EFAULT;
if (want < (int)sizeof(int))
return -EINVAL;
if (put_user(val, uval) < 0)
return -EFAULT;
want = sizeof(int);
return put_user(want, ulenp) < 0 ? -EFAULT : 0;
}
SYSCALL_DEFINE(setsockopt)
{
struct socket *s;
addr_t uval;
int level, name, ulen, val, r;
if ((r = argsock(0, &s)) < 0)
return r;
argint(1, &level);
argint(2, &name);
argaddr(3, &uval);
argint(4, &ulen);
if ((r = opt_in(uval, ulen, &val)) < 0)
return r;
return sockopt_set(s, level, name, val);
}
SYSCALL_DEFINE(getsockopt)
{
struct socket *s;
addr_t uval, ulenp;
int level, name, val = 0, r;
if ((r = argsock(0, &s)) < 0)
return r;
argint(1, &level);
argint(2, &name);
argaddr(3, &uval);
argaddr(4, &ulenp);
// SO_PEERCRED: who is on the other end of a Unix socket. tmux asks its
// client this and, told nothing, refuses it as a stranger.
if (level == 1 && name == 17) {
struct { int pid; uint uid, gid; } cr;
int want;
if (sock_peercred(s, &cr.pid, &cr.uid, &cr.gid) < 0)
return -ENOTCONN;
if (!uval || !ulenp || get_user(want, ulenp) < 0)
return -EFAULT;
if (want < (int)sizeof(cr))
return -EINVAL;
if (copy_to_user(uval, &cr, sizeof(cr)) < 0)
return -EFAULT;
want = sizeof(cr);
return put_user(want, ulenp) < 0 ? -EFAULT : 0;
}
if ((r = sockopt_get(s, level, name, &val)) < 0)
return r;
return opt_out(uval, ulenp, val);
}
// ---------------------------------------------------------------------------
// sendmsg / recvmsg
//
// These exist for SCM_RIGHTS. Everything else they do -- scatter/gather over an
// iovec, an address on the side -- sendto and recvfrom already did, and the
// reason to build the general call rather than a `sendfd()` is that the
// programs that need it are not ours: libwayland and toybox's ping both call
// recvmsg with Linux's structures and would not notice a friendlier door.
//
// docs/architecture/kernel.md item 1, and docs/architecture/devices.md mechanism 1.
// ---------------------------------------------------------------------------
// Linux caps an iovec at UIO_MAXIOV = 1024, and so does this. It was 32, and
// tmux met it on its first message: imsg hands sendmsg every queued buffer at
// once, one iovec each, and a client announcing its environment has dozens.
// Nothing is sized by the count -- each entry is fetched and copied on its own.
#define SCH_IOV_MAX 1024
// The control buffer is copied onto the kernel stack, and that stack is 4 KB
// (param.h, KSTACKSIZE). 256 bytes holds CMSG_SPACE(SCM_MAXFD * 4) six times
// over; anything larger on the *send* side is a caller with a plan we do not
// need to support.
#define SCH_CMSG_MAX 256
// Resolve a descriptor to both the socket and the open file description --
// recvmsg needs the second because O_NONBLOCK lives there, not on the socket.
static int
argsockfile(int n, struct socket **ps, struct file **pf)
{
int fd;
struct file *f;
if (argint(n, &fd) < 0)
return -EBADF;
if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd]))
return -EBADF;
if (f->type != FD_SOCKET)
return -ENOTSOCK;
*ps = f->sock;
*pf = f;
return 0;
}
static int
fetch_msghdr(addr_t umsg, struct msghdr *m)
{
if (umsg == 0 || copy_from_user(m, umsg, sizeof(*m)) < 0)
return -EFAULT;
if (m->msg_iovlen > SCH_IOV_MAX)
return -EMSGSIZE;
return 0;
}
// One iovec, validated. Returns the byte count, 0 for an empty entry that the
// caller should skip, or negative on a bad pointer.
static int
fetch_iov(struct msghdr *m, uint64 i, char **pp)
{
struct iovec iv;
if (copy_from_user(&iv, (addr_t)&m->msg_iov[i], sizeof(iv)) < 0)
return -EFAULT;
if (iv.iov_len == 0)
return 0;
if (iv.iov_len > 0x7fffffff)
return -EINVAL;
if (!(*pp = (char *)user_ptr((addr_t)iv.iov_base, iv.iov_len)))
return -EFAULT;
return (int)iv.iov_len;
}
// Pull the descriptors out of a control buffer and take a kernel reference on
// each. On any failure the ones already taken are released -- a half-gathered
// message must not leave references behind for a call that returns an error.
static int
scm_gather(struct msghdr *m, struct file **fs, int *nfp)
{
char buf[SCH_CMSG_MAX];
uint64 off, payload, i;
struct cmsghdr *c;
struct file *f;
int ufd, r;
*nfp = 0;
if (m->msg_control == 0 || m->msg_controllen < sizeof(struct cmsghdr))
return 0;
if (m->msg_controllen > SCH_CMSG_MAX)
return -EINVAL;
if (copy_from_user(buf, (addr_t)m->msg_control, m->msg_controllen) < 0)
return -EFAULT;
for (off = 0; off + sizeof(struct cmsghdr) <= m->msg_controllen; ) {
c = (struct cmsghdr *)(buf + off);
// The control buffer may be longer than its messages: seatd passes
// the whole of a zeroed CMSG_SPACE(MAX_FDS) array with one descriptor
// in it. Linux's CMSG_NXTHDR treats a following header too short to
// be one as the end, not as an error, and so does this. (A first
// header that short is still wrong.)
if (off > 0 && c->cmsg_len < sizeof(struct cmsghdr))
break;
if (c->cmsg_len < CMSG_LEN(0) || c->cmsg_len > m->msg_controllen - off) {
r = -EINVAL;
goto fail;
}
if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS) {
payload = c->cmsg_len - CMSG_LEN(0);
for (i = 0; i + sizeof(int) <= payload; i += sizeof(int)) {
memmove(&ufd, CMSG_DATA(c) + i, sizeof(int));
if (*nfp >= SCM_MAXFD) {
r = -EINVAL;
goto fail;
}
if (ufd < 0 || ufd >= NOFILE || !(f = myproc()->files->ofile[ufd])) {
r = -EBADF;
goto fail;
}
fs[(*nfp)++] = filedup(f);
}
} else if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == 2 /* SCM_CREDENTIALS */) {
// Credentials, which GDBus sends with the first byte of every
// D-Bus connection. Checked as Linux checks them -- they must be
// the sender's own, or the sender root -- and then not delivered:
// nothing here sets SO_PASSCRED to receive them, and the D-Bus
// daemon asks SO_PEERCRED instead, which answers the same question
// from the kernel's side. Refusing them made every GNOME program
// fail its D-Bus handshake ("Error sending credentials").
struct { int pid; uint uid, gid; } cr;
struct proc *me = myproc();
if (c->cmsg_len < CMSG_LEN(sizeof(cr))) {
r = -EINVAL;
goto fail;
}
memmove(&cr, CMSG_DATA(c), sizeof(cr));
if (me->euid != 0 &&
(cr.pid != me->tgid ||
(cr.uid != me->uid && cr.uid != me->euid) ||
(cr.gid != me->gid && cr.gid != me->egid))) {
r = -EPERM;
goto fail;
}
} else if (c->cmsg_level == SOL_SOCKET) {
// Anything else. Refused rather than ignored: a caller that asked
// for something and was told yes would believe it happened.
r = -EINVAL;
goto fail;
}
off += CMSG_ALIGN(c->cmsg_len);
}
return 0;
fail:
while (*nfp > 0)
fileclose(fs[--*nfp]);
return r;
}
SYSCALL_DEFINE(sendmsg)
{
struct socket *s;
struct file *file;
struct msghdr m;
struct file *fs[SCM_MAXFD];
addr_t umsg;
uint64 i;
int flags, nf = 0, r;
long sent = 0;
uint ip = 0;
ushort port = 0;
int addressed = 0;
int anydata = 0;
if ((r = argsockfile(0, &s, &file)) < 0)
return r;
argaddr(1, &umsg);
argint(2, &flags);
if ((r = fetch_msghdr(umsg, &m)) < 0)
return r;
if ((r = scm_gather(&m, fs, &nf)) < 0)
return r;
// SCM_RIGHTS is an AF_UNIX facility and there is nothing to generalise: a
// descriptor has no meaning on the far side of a wire.
if (nf > 0 && sock_domain(s) != AF_UNIX) {
r = -EINVAL;
goto fail;
}
if (m.msg_name && sock_domain(s) == AF_INET) {
if ((r = fetch_sin((addr_t)m.msg_name, (int)m.msg_namelen, &ip, &port)) < 0)
goto fail;
port = port_from_net(port);
addressed = 1;
}
// A datagram socket sends the whole message as one datagram, however many
// iovecs it is spread over -- udev's header and properties are two.
if (sock_is_packet(s)) {
char *buf = kalloc(); // a page: kmalloc has no 4 KB class
int total = 0;
if (!buf) {
r = -ENOMEM;
goto fail;
}
for (i = 0; i < m.msg_iovlen; i++) {
char *p;
int len;
if ((len = fetch_iov(&m, i, &p)) < 0) {
kfree(buf);
r = len;
goto fail;
}
if (total + len > 4096 - 8) {
kfree(buf);
r = -EMSGSIZE;
goto fail;
}
memmove(buf + total, p, (uint)len);
total += len;
}
if (sock_domain(s) == AF_NETLINK) {
// sockaddr_nl: where it goes, by port id and/or multicast group.
uint dpid = 0, dgrp = 0;
if (m.msg_name && m.msg_namelen >= 12) {
if (copy_from_user(&dpid, (addr_t)m.msg_name + 4, 4) < 0 ||
copy_from_user(&dgrp, (addr_t)m.msg_name + 8, 4) < 0) {
kfree(buf);
r = -EFAULT;
goto fail;
}
} else {
dgrp = UEVENT_GROUP_UDEV;
}
r = sock_uevent_send(s, buf, total, dpid, dgrp);
} else if (nf > 0) {
r = sockwrite_fds(s, buf, total, fs, nf);
if (r < 0) {
kfree(buf);
goto fail; // not queued: the descriptors are still ours
}
nf = 0;
} else {
r = sockwrite(s, buf, total);
}
kfree(buf);
if (r < 0)
goto fail;
return total;
}
for (i = 0; i < m.msg_iovlen; i++) {
char *p;
int len, w;
if ((len = fetch_iov(&m, i, &p)) < 0) {
r = len;
goto fail;
}
if (len == 0)
continue;
anydata = 1;
if (addressed)
w = socksendto(s, p, len, ip, port);
else if (nf > 0)
w = sockwrite_fds(s, p, len, fs, nf);
else
w = sockwrite(s, p, len);
if (w < 0) {
if (sent > 0)
break; // a short write is still a write
r = w;
goto fail;
}
// Any positive count means the descriptors are queued and no longer
// ours; see the contract above sockwrite_fds.
if (w > 0)
nf = 0;
sent += w;
if (w < len)
break;
}
// Descriptors with no bytes to ride on. The delivery rule pins them to the
// position of a first byte that in this case never arrives, so there is no
// position at which a receiver could ever be handed them.
if (nf > 0) {
r = -EINVAL;
goto fail;
}
// A datagram with nothing in it is still a datagram: udev's workers say
// "done" with exactly that, and the credentials it carries are the message.
if (!anydata && sock_is_packet(s)) {
if ((r = sockwrite(s, "", 0)) < 0)
return r;
}
return (int)sent;
fail:
while (nf > 0)
fileclose(fs[--nf]);
return r;
}
SYSCALL_DEFINE(recvmsg)
{
int didread = 0;
struct socket *s;
struct file *file;
struct msghdr m;
struct file *fs[SCM_MAXFD];
char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int)) + CMSG_SPACE(12)];
int ufd[SCM_MAXFD];
addr_t umsg;
uint64 i, clen = 0;
int flags, nf = 0, mflags = 0, r;
long got = 0;
uint ip = 0;
ushort port = 0;
if ((r = argsockfile(0, &s, &file)) < 0)
return r;
argaddr(1, &umsg);
argint(2, &flags);
if ((r = fetch_msghdr(umsg, &m)) < 0)
return r;
// O_NONBLOCK on the description and MSG_DONTWAIT on the call mean the same
// thing and are both honoured, because a program that polls and then reads
// uses whichever one its library happened to pick.
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 && !sock_is_packet(s)) {
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;
if ((len = fetch_iov(&m, i, &p)) < 0)
return len;
if (len == 0)
continue;
// Only the first read may block, and it stops for good once a message
// carrying descriptors has been taken: the next iovec would be filled
// from the message *after* it, and the caller has no way to say where
// one ended and the next began.
if (got > 0 && (nf > 0 || !sockreadable(s)))
break;
didread = 1;
if (sock_domain(s) == AF_UNIX)
rd = sockread_fds(s, p, len, fs, &nf);
else if (m.msg_name && sock_domain(s) == AF_INET)
rd = sockrecvfrom(s, p, len, &ip, &port);
else
rd = sockread(s, p, len);
if (rd < 0) {
if (got > 0)
break;
return rd;
}
got += rd;
if (rd < len || sock_is_packet(s))
break; // one message per call
}
// A receive buffer of no bytes still takes a datagram off the queue (it is
// truncated to nothing). udev's workers report in with empty datagrams and
// read them this way; skipping the read left the message queued, the
// socket readable and the manager looping on it.
if (!didread && sock_is_packet(s)) {
char z;
int rd = sock_domain(s) == AF_UNIX
? sockread_fds(s, &z, 0, fs, &nf)
: sockread(s, &z, 0);
if (rd < 0)
return rd;
}
if (nf > 0) {
int room = 0, installed = 0, j;
uint64 writelen;
if (m.msg_control && m.msg_controllen >= CMSG_LEN(sizeof(int)))
room = (int)((m.msg_controllen - CMSG_LEN(0)) / sizeof(int));
if (room > nf)
room = nf;
// How much will actually be written. CMSG_SPACE includes the padding
// to where a *next* header would start, and there is no next header --
// so when the caller's buffer is not big enough for that padding the
// message still fits without it. Writing CMSG_SPACE unconditionally
// would overrun a buffer sized with CMSG_LEN, which is legal.
writelen = CMSG_SPACE(room * sizeof(int));
if (writelen > m.msg_controllen)
writelen = CMSG_LEN(room * sizeof(int));
// Check the control buffer is writable *before* installing anything.
// A descriptor that has been put in the table and then reported to a
// caller who never learned its number is unclosable.
if (room > 0 && !user_ptr((addr_t)m.msg_control, writelen))
room = 0;
for (j = 0; j < room; j++) {
int d = fdalloc(fs[j]);
if (d < 0)
break;
if (flags & MSG_CMSG_CLOEXEC)
myproc()->files->flags[d] |= FD_CLOEXEC;
ufd[installed++] = d;
}
// Whatever did not fit is closed here and nowhere else: the kernel
// holds the last reference, and a receiver with no room is still a
// delivery. MSG_CTRUNC is how the caller finds out.
for (j = installed; j < nf; j++)
fileclose(fs[j]);
if (installed < nf)
mflags |= MSG_CTRUNC;
if (installed > 0) {
struct cmsghdr *c = (struct cmsghdr *)cbuf;
memset(cbuf, 0, sizeof(cbuf)); // pad bytes, not kernel stack
c->cmsg_len = CMSG_LEN(installed * sizeof(int));
c->cmsg_level = SOL_SOCKET;
c->cmsg_type = SCM_RIGHTS;
memmove(CMSG_DATA(c), ufd, installed * sizeof(int));
clen = CMSG_SPACE(installed * sizeof(int));
if (clen > m.msg_controllen)
clen = CMSG_LEN(installed * sizeof(int));
if (copy_to_user((addr_t)m.msg_control, cbuf, clen) < 0)
return -EFAULT; // ruled out above, but not assumed
}
}
// SO_PASSCRED: who sent it, after any descriptors. On a connected Unix
// socket that is always the peer, so its recorded credentials are the
// answer. WebKit sets the option on its IPC sockets and aborts if it is
// refused.
if (sock_passcred(s) && (got > 0 || sock_is_packet(s))) {
struct { int pid; uint uid, gid; } cr;
uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 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))];
memset(tmp, 0, sizeof(tmp));
c.cmsg_len = CMSG_LEN(sizeof(cr));
c.cmsg_level = SOL_SOCKET;
c.cmsg_type = 2; // SCM_CREDENTIALS
memmove(tmp, &c, sizeof(c));
memmove(CMSG_DATA((struct cmsghdr *)tmp), &cr, sizeof(cr));
uint64 n2 = CMSG_SPACE(sizeof(cr));
if (off + n2 > m.msg_controllen)
n2 = CMSG_LEN(sizeof(cr));
if (copy_to_user((addr_t)m.msg_control + off, tmp, n2) < 0)
return -EFAULT;
clen = off + n2;
} else {
mflags |= MSG_CTRUNC;
}
}
}
// The address, for the family that has one. Same rule as recvfrom: a Unix
// socket reports a length of zero rather than a sockaddr full of nothing.
if (m.msg_name) {
uint32 alen = 0;
if (sock_domain(s) == AF_INET) {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr = ip;
sa.sin_port = port_to_net(port);
alen = (uint32)sizeof(sa);
if (alen > m.msg_namelen)
alen = m.msg_namelen;
if (alen && copy_to_user((addr_t)m.msg_name, &sa, alen) < 0)
return -EFAULT;
} else if (sock_domain(s) == AF_NETLINK) {
// The kernel, as in recvfrom above.
uint16 nl[6] = { AF_NETLINK, 0, 0, 0, 0, 0 };
uint g = sock_nl_groups(s), from = sock_nl_from(s);
nl[2] = (uint16)from; nl[3] = (uint16)(from >> 16);
nl[4] = (uint16)g; nl[5] = (uint16)(g >> 16);
alen = (uint32)sizeof(nl);
if (alen > m.msg_namelen)
alen = m.msg_namelen;
if (alen && copy_to_user((addr_t)m.msg_name, nl, alen) < 0)
return -EFAULT;
alen = (uint32)sizeof(nl);
}
if (copy_to_user(umsg + __builtin_offsetof(struct msghdr, msg_namelen),
&alen, sizeof(alen)) < 0)
return -EFAULT;
}
if (copy_to_user(umsg + __builtin_offsetof(struct msghdr, msg_controllen),
&clen, sizeof(clen)) < 0)
return -EFAULT;
if (copy_to_user(umsg + __builtin_offsetof(struct msghdr, msg_flags),
&mflags, sizeof(mflags)) < 0)
return -EFAULT;
return nlret >= 0 ? nlret : (int)got;
}