schdbgd bridges TCP to the debugger, which is the one way in that does not
need you to be sitting at the machine. It is a bridge and nothing else: a line
in becomes a line written to /dev/dbgro and the answer comes back, so `nc` is a
complete client and there is no clever client to be broken on the day you need
this.
# schdbgd &
schdbgd: debugger on port 4444, loopback only
$ nc 127.0.0.1 4444
dma
arena 0xffff80003da41000-0xffff80003ea41000 16384 KB total, ...
A kernel debugger plus a TCP socket is a remote root hole, so three things were
decided rather than discovered:
- It does not start by itself. schdbgd.job ships Disabled. SchOS has no
kernel command line, so the boot flag the plan asked for is the job
manifest, which is the same thing in this system's own idiom.
- It binds loopback; -a names an address and says so in a paragraph.
- It cannot reach a view that changes state, enforced in the kernel.
That last one is /dev/dbgro: /dev/dbg on minor 1. The minor is the one piece of
per-open state a character device already sees, so which node you opened is the
permission -- nothing to latch, nothing to remember between the write and the
read, no way across. Which views are refused comes from dbg_register_rw() on
the registration itself, because a central list of dangerous views goes stale
in the direction where a new view is quietly treated as safe. Today that is
`watch <addr>`; `watch` with no argument is still the listing and still
answered.
Also: socket() now accepts IPPROTO_TCP with SOCK_STREAM and IPPROTO_UDP with
SOCK_DGRAM. They name the default rather than departing from it, and toybox's
nc writes them out -- so TCP was "Protocol not supported" on a system whose
whole network is TCP.
docs/plan/debugger.md retires into docs/architecture/debugging.md; 16 files
repointed. Every plan in docs/plan/ that was open is now closed, so the next
thing is Gate 0 itself: the survey, with the instrument built.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
954 lines
29 KiB
C
954 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.
|
|
//
|
|
// 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 &&
|
|
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;
|
|
}
|