kernel: timers that fire, raw ping, and the sockets X needs
alarm() and setitimer() stored a countdown nothing counted down, so no
timer has ever fired; setitimer and getitimer were also registered with
no Linux number. alarmd now posts SIGALRM on the tick, while any process
has a timer armed. busybox's ping, /bin/ping on the Alpine root, sends
every request after the first from its SIGALRM handler.
It also opens SOCK_RAW, which was EPROTOTYPE. A raw ICMP socket is now a
ping socket whose reads start at the IP header. And a blocked socket
call gives up for a signal with a handler, not only for a kill: the
alarm woke recvfrom, and the wait put it straight back to sleep.
For Xorg: AF_UNIX abstract names, which read as the empty path and so as
EADDRINUSE ("server already running"); and getsockname and getpeername,
which did not exist, so libxcb sent no cookie and the server refused
every client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
d0941780f5
commit
a4ed556543
9 changed files with 428 additions and 28 deletions
|
|
@ -66,6 +66,28 @@ fetch_sun_path(addr_t uaddr, int ulen, char *out)
|
|||
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.
|
||||
//
|
||||
|
|
@ -144,7 +166,8 @@ SYSCALL_DEFINE(socket)
|
|||
type &= 0xFF;
|
||||
if (domain == AF_UNIX && type != SOCK_STREAM)
|
||||
return -EPROTOTYPE;
|
||||
if (domain == AF_INET && type != SOCK_STREAM && type != SOCK_DGRAM)
|
||||
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
|
||||
|
|
@ -168,10 +191,17 @@ SYSCALL_DEFINE(socket)
|
|||
(domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP);
|
||||
if (redundant)
|
||||
protocol = 0;
|
||||
else if (!(domain == AF_INET && type == SOCK_DGRAM &&
|
||||
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
|
||||
|
|
@ -240,6 +270,10 @@ SYSCALL_DEFINE(bind)
|
|||
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;
|
||||
|
||||
|
|
@ -315,6 +349,10 @@ SYSCALL_DEFINE(connect)
|
|||
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;
|
||||
|
||||
|
|
@ -550,6 +588,74 @@ SYSCALL_DEFINE(recvfrom)
|
|||
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). 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.
|
||||
|
|
|
|||
|
|
@ -10,14 +10,36 @@
|
|||
#include "core/uaccess.h"
|
||||
#include "uapi/errno.h"
|
||||
|
||||
// Timer values in ticks, rounding a nonzero time up: POSIX says a timer set
|
||||
// to 1 us goes off, not that it is disarmed.
|
||||
static uint
|
||||
tv_ticks(struct timeval *tv)
|
||||
{
|
||||
if (tv->tv_sec < 0 || tv->tv_usec < 0)
|
||||
return 0;
|
||||
uint64 t = (uint64)tv->tv_sec * HZ +
|
||||
((uint64)tv->tv_usec + USEC_PER_TICK - 1) / USEC_PER_TICK;
|
||||
return t > 0x7fffffff ? 0x7fffffff : (uint)t;
|
||||
}
|
||||
|
||||
static void
|
||||
ticks_tv(uint t, struct timeval *tv)
|
||||
{
|
||||
tv->tv_sec = t / HZ;
|
||||
tv->tv_usec = (t % HZ) * USEC_PER_TICK;
|
||||
}
|
||||
|
||||
// The seconds left on the previous alarm, rounded up, as POSIX asks.
|
||||
addr_t
|
||||
sys_alarm(void)
|
||||
{
|
||||
int seconds;
|
||||
if (argint(0, &seconds) < 0)
|
||||
return -1;
|
||||
myproc()->alarm_ticks = seconds * HZ;
|
||||
return 0;
|
||||
struct proc *p = myproc();
|
||||
uint left = alarm_left(p);
|
||||
alarm_arm(p, seconds > 0 ? (uint)seconds * HZ : 0, 0);
|
||||
return (left + HZ - 1) / HZ;
|
||||
}
|
||||
|
||||
addr_t
|
||||
|
|
@ -77,21 +99,52 @@ sys_clock_gettime(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
addr_t sys_getitimer(void) { return 0; } // stub
|
||||
// ITIMER_REAL only. ITIMER_VIRTUAL and ITIMER_PROF count CPU time, which
|
||||
// nothing here measures per tick, and are refused rather than accepted and
|
||||
// never fired -- which is what all three used to be.
|
||||
static int
|
||||
itimer_get(struct proc *p, struct itimerval *iv)
|
||||
{
|
||||
ticks_tv(p->alarm_every, &iv->it_interval);
|
||||
ticks_tv(alarm_left(p), &iv->it_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_getitimer(void)
|
||||
{
|
||||
int which;
|
||||
addr_t curp;
|
||||
argint(0, &which); argaddr(1, &curp);
|
||||
if (which != 0)
|
||||
return -EINVAL;
|
||||
struct itimerval iv;
|
||||
itimer_get(myproc(), &iv);
|
||||
if (copy_to_user(curp, &iv, sizeof(iv)) != 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t
|
||||
sys_setitimer(void)
|
||||
{
|
||||
int which;
|
||||
addr_t newp;
|
||||
argint(0, &which); argaddr(1, &newp);
|
||||
// We don't implement interval timers; just track the alarm if it's ITIMER_REAL.
|
||||
if (which == 0 && newp) {
|
||||
struct itimerval iv;
|
||||
if (copy_from_user(&iv, newp, sizeof(iv)) == 0)
|
||||
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * HZ +
|
||||
iv.it_value.tv_usec / USEC_PER_TICK);
|
||||
addr_t newp, oldp;
|
||||
argint(0, &which); argaddr(1, &newp); argaddr(2, &oldp);
|
||||
if (which != 0)
|
||||
return -EINVAL;
|
||||
struct proc *p = myproc();
|
||||
struct itimerval iv;
|
||||
if (oldp) {
|
||||
itimer_get(p, &iv);
|
||||
if (copy_to_user(oldp, &iv, sizeof(iv)) != 0)
|
||||
return -EFAULT;
|
||||
}
|
||||
if (newp == 0)
|
||||
return 0;
|
||||
if (copy_from_user(&iv, newp, sizeof(iv)) != 0)
|
||||
return -EFAULT;
|
||||
alarm_arm(p, tv_ticks(&iv.it_value), tv_ticks(&iv.it_interval));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ SYSCALL(102, 157, prctl)
|
|||
SYSCALL(103, 234, tgkill)
|
||||
SYSCALL(104, 200, tkill)
|
||||
SYSCALL(105, 34, pause)
|
||||
SYSCALL(106, NO_LINUX, getitimer)
|
||||
SYSCALL(107, NO_LINUX, setitimer)
|
||||
SYSCALL(106, 36, getitimer)
|
||||
SYSCALL(107, 38, setitimer)
|
||||
SYSCALL(108, 98, getrusage)
|
||||
SYSCALL(109, 100, times)
|
||||
SYSCALL(110, 99, sysinfo)
|
||||
|
|
@ -296,6 +296,8 @@ SYSCALL(203, 281, epoll_pwait)
|
|||
SYSCALL(204, 282, signalfd)
|
||||
SYSCALL(205, 284, eventfd)
|
||||
SYSCALL(206, 73, flock)
|
||||
SYSCALL(207, 51, getsockname)
|
||||
SYSCALL(208, 52, getpeername)
|
||||
|
||||
// --- mounts ---
|
||||
SYSCALL(185, 165, mount)
|
||||
|
|
|
|||
Loading…
Reference in a new issue