// 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. // // Only AF_UNIX is answered. AF_INET is recognised and refused with // EAFNOSUPPORT rather than EINVAL, because a program that probes for IPv6 and // falls back to IPv4 needs to be able to tell "this address family does not // exist 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. static int sock_to_fd(struct socket *s) { 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; 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) return -EAFNOSUPPORT; // SOCK_CLOEXEC / SOCK_NONBLOCK ride in the high bits of `type`. Neither // is honoured yet, but masking them off means a musl program that always // sets SOCK_CLOEXEC still gets a socket instead of EINVAL. type &= 0xFF; if (type != SOCK_STREAM) return -EPROTOTYPE; if (protocol != 0) return -EPROTONOSUPPORT; if ((s = socknew(domain, type)) == 0) return -ENFILE; if ((fd = sock_to_fd(s)) < 0) { sockclose(s); return fd; } return fd; } 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 ((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 ((r = fetch_sun_path(uaddr, ulen, path)) < 0) return r; return sockconnect(s, path); } SYSCALL_DEFINE(accept) { 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)) < 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(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; type &= 0xFF; if (type != SOCK_STREAM) return -EPROTOTYPE; if ((r = sockpair(&a, &b)) < 0) return r; if ((fd0 = sock_to_fd(a)) < 0) { sockclose(a); sockclose(b); return fd0; } if ((fd1 = sock_to_fd(b)) < 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; } // send/recv with no flags are read/write. Implementing them here rather than // leaving them ENOSYS costs four lines and saves every ported program from // having to be rewritten to use write(). SYSCALL_DEFINE(sendto) { struct socket *s; addr_t ubuf; int len, flags, r; if ((r = argsock(0, &s)) < 0) return r; argaddr(1, &ubuf); argint(2, &len); argint(3, &flags); if (len < 0) return -EINVAL; char *p = (char *)user_ptr(ubuf, (uint64)len); if (len && !p) return -EFAULT; return sockwrite(s, p, len); } SYSCALL_DEFINE(recvfrom) { struct socket *s; addr_t ubuf; int len, flags, r; if ((r = argsock(0, &s)) < 0) return r; argaddr(1, &ubuf); argint(2, &len); argint(3, &flags); if (len < 0) return -EINVAL; char *p = (char *)user_ptr(ubuf, (uint64)len); if (len && !p) return -EFAULT; return sockread(s, p, len); } // 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); } // getsockopt/setsockopt: accepted and ignored. A server that sets SO_REUSEADDR // and treats failure as fatal is common enough to be worth not failing. SYSCALL_DEFINE(setsockopt) { return 0; } SYSCALL_DEFINE(getsockopt) { addr_t uval, ulen; int zero = 0, len = 0; argaddr(3, &uval); argaddr(4, &ulen); if (uval && ulen) { if (get_user(len, ulen) == 0 && len >= (int)sizeof(int)) { put_user(zero, uval); len = sizeof(int); put_user(len, ulen); } } return 0; }