SchOS/kernel/sys/sys_net.c
Nexoniarz 9e6d8dd4e3 sockets: a descriptor can cross between processes
sendmsg/recvmsg with SCM_RIGHTS. Item 1 of docs/plan/foundations.md and
mechanism 1 of docs/plan/hybrid.md, which are the same primitive seen from two
directions: Wayland's wl_shm_create_pool passes a file descriptor with no
integer-shaped alternative, and handing a driver process the device it is
allowed to touch is the same act. Both plans stopped here.

Linux's structures byte for byte -- msghdr, cmsghdr, the CMSG_ macros -- because
the programs that need this are not ours. libwayland and toybox both call
recvmsg with Linux's headers and would not notice a friendlier door.

The delivery rule is the whole design. A batch of descriptors is pinned to the
stream position of the first byte of the message that carried it, and that has
two halves: a reader arriving at that position is handed them, and a reader that
has not reached it is stopped SHORT of it. Leave out the second half and a
program asking for 4096 bytes swallows three messages and is handed one
message's descriptors, with nothing in the result saying which. socktest checks
exactly that: two one-byte messages, an eight-byte read, one byte back.

The kernel holds a reference to every descriptor in flight, so a sender may
close its own copy the moment sendmsg returns. Giving that reference back is the
part that leaks if it is not written down, and there are three ways a message
ends:

  - received with room            installed in the receiver's table
  - received without room, or by
    plain read(2)                 closed; MSG_CTRUNC for the first case
  - never received, socket closed closed by sockclose

The middle row looks like a choice and is not: holding kernel references for a
receiver that has proved it is not looking is a leak with extra steps.

No garbage collector. A socket descriptor sent over that same socket and never
received holds a reference to the socket that holds the reference to it, and
neither close reaches zero. Linux carries several hundred lines for this.
Nothing on either plan passes a socket over a socket -- Wayland passes memory,
the driver work passes devices -- so it is a bounded leak that is documented
rather than a silent one.

Fourteen checks in cmd/socktest, and every one moves data through the descriptor
rather than inspecting it. The descriptor passed is always a pipe end, because a
pipe announces its own lifetime: a read of a pipe whose writers are all gone
returns 0, which turns "was the kernel's reference released" -- otherwise
invisible from userspace -- into a value a test can compare.

Two bugs fell out, neither in the new code:

  - sockpair() never set the socket's domain, so every socketpair() socket
    claimed domain 0. Invisible while the data paths asked only whether a socket
    was AF_INET or AF_NETLINK, since zero answers "neither" correctly.
    SCM_RIGHTS asks "is this AF_UNIX", and zero is wrong for that one.

  - pipe_poll() reports POLLHUP but not POLLIN at end of file, so a non-blocking
    read of a pipe whose writers had all gone returned EAGAIN forever instead of
    0 -- a program that polls and reads spins waiting for an end that already
    happened. sockreadable() always had the equivalent condition; the pipe path
    did not. Found because the test helper for "is this descriptor closed" is a
    non-blocking read of a pipe.

projects/schcom/README.md said SCM_RIGHTS was done for a long time while nothing
in the kernel implemented it, and an entire GUI plan was drawn on that line.
It is true now, which is not the same as it having been right, and the README
says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 19:49:37 +02:00

944 lines
29 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 "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "fs.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "file.h"
#include "posix.h"
#include "syscall.h"
#include "uaccess.h"
#include "errno.h"
#include "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;
}
// 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()->ofile_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()->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)
return -EPROTOTYPE;
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM)
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.
if (protocol != 0) {
if (!(domain == AF_INET && type == SOCK_DGRAM &&
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));
}
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
return sockbind(s, path);
}
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));
}
if ((r = fetch_sun_path(uaddr, ulen, path)) < 0)
return r;
return sockconnect(s, path);
}
// 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;
if (type != SOCK_STREAM)
return -EPROTOTYPE;
if ((r = sockpair(&a, &b)) < 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()->ofile[fd0] = 0;
sockclose(a);
sockclose(b);
return fd1;
}
int sv[2] = { fd0, fd1 };
if (copy_to_user(usv, sv, sizeof(sv)) < 0) {
myproc()->ofile[fd0] = 0;
myproc()->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);
}
SYSCALL_DEFINE(recvfrom)
{
struct socket *s;
addr_t ubuf, uaddr, ulenp;
int len, flags, 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);
argaddr(5, &ulenp);
if (len < 0)
return -EINVAL;
char *p = (char *)user_ptr(ubuf, (uint64)len);
if (len && !p)
return -EFAULT;
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;
}
if (ulenp && copy_to_user(ulenp, &alen, sizeof(alen)) < 0)
return -EFAULT;
return r;
}
// shutdown(2). Half-close is not modelled -- there is one buffer per
// direction and no per-direction flag -- so SHUT_WR and SHUT_RDWR both mean
// "the peer sees end-of-file", which is the behaviour programs depend on.
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);
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/plan/foundations.md item 1, and docs/plan/hybrid.md mechanism 1.
// ---------------------------------------------------------------------------
// Linux caps an iovec at UIO_MAXIOV = 1024. Thirty-two is the cap here for one
// reason: each entry is validated and copied individually, and a caller that
// needs more than thirty-two separate buffers in one message is describing a
// problem this kernel has not met. EMSGSIZE is Linux's answer for too many.
#define SCH_IOV_MAX 32
// 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()->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);
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()->ofile[ufd])) {
r = -EBADF;
goto fail;
}
fs[(*nfp)++] = filedup(f);
}
} else if (c->cmsg_level == SOL_SOCKET) {
// SCM_CREDENTIALS and friends. Refused rather than ignored: a
// caller that asked to pass credentials and was told yes would
// believe the receiver got them.
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;
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;
}
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;
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;
}
return (int)sent;
fail:
while (nf > 0)
fileclose(fs[--nf]);
return r;
}
SYSCALL_DEFINE(recvmsg)
{
struct socket *s;
struct file *file;
struct msghdr m;
struct file *fs[SCM_MAXFD];
char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int))];
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;
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;
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)
break;
}
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()->ofile_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
}
}
// 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;
}
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 (int)got;
}