README: rewritten around what runs (Linux software, Xorg, apk, Linux's drivers), the honest gaps, measured numbers. plan/: 1.0.md is the finish line, where each clause of the acceptance test stands, and the register (one failing clause: a root that survives a reboot); built plans (foundations, hybrid, alpine, xorg) and the decided-against one (wayland) are gone, their content lives in architecture/; roadmap.md is only what is still open. guides: building/layout/testing/adding-a-driver (Linux code may be used). history: four entries (ls -lR, TLB shootdowns, MDIO 0x02460246, netlink MSG_TRUNC). Website: cards, stack, download page, docs index. Code comments that pointed at deleted plan docs point at the architecture docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
1256 lines
41 KiB
C
1256 lines
41 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 "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)
|
|
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) {
|
|
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));
|
|
}
|
|
|
|
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 are carried as a stream. What they
|
|
// are used for between two processes is passing a descriptor or a status
|
|
// word -- Rust's process spawning asks for SOCK_SEQPACKET to hear back
|
|
// from its child -- and one message at a time is all of that. A reader
|
|
// that sends two and wants them kept apart would see them joined.
|
|
if (type != SOCK_STREAM && type != 2 /* DGRAM */ && type != 5 /* SEQPACKET */)
|
|
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()->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 };
|
|
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;
|
|
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);
|
|
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;
|
|
|
|
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)) + 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) {
|
|
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;
|
|
|
|
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()->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) {
|
|
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 };
|
|
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;
|
|
}
|