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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue