diff --git a/minix/include/minix/callnr.h b/minix/include/minix/callnr.h index 759350af6..a88dc1ac6 100644 --- a/minix/include/minix/callnr.h +++ b/minix/include/minix/callnr.h @@ -116,7 +116,7 @@ #define VFS_GCOV_FLUSH (VFS_BASE + 44) #define VFS_MAPDRIVER (VFS_BASE + 45) #define VFS_COPYFD (VFS_BASE + 46) -#define VFS_CHECKPERMS (VFS_BASE + 47) +#define VFS_SOCKETPATH (VFS_BASE + 47) #define VFS_GETSYSINFO (VFS_BASE + 48) #define VFS_SOCKET (VFS_BASE + 49) #define VFS_SOCKETPAIR (VFS_BASE + 50) diff --git a/minix/include/minix/ipc.h b/minix/include/minix/ipc.h index 5bfac14e7..990bb3489 100644 --- a/minix/include/minix/ipc.h +++ b/minix/include/minix/ipc.h @@ -1451,15 +1451,6 @@ typedef struct { } mess_lsys_tty_fkey_ctl; _ASSERT_MSG_SIZE(mess_lsys_tty_fkey_ctl); -typedef struct { - endpoint_t endpt; - cp_grant_id_t grant; - size_t count; - - uint8_t padding[44]; -} mess_lsys_vfs_checkperms; -_ASSERT_MSG_SIZE(mess_lsys_vfs_checkperms); - typedef struct { endpoint_t endpt; int fd; @@ -1480,6 +1471,16 @@ typedef struct { } mess_lsys_vfs_mapdriver; _ASSERT_MSG_SIZE(mess_lsys_vfs_mapdriver); +typedef struct { + endpoint_t endpt; + cp_grant_id_t grant; + size_t count; + int what; + + uint8_t padding[40]; +} mess_lsys_vfs_socketpath; +_ASSERT_MSG_SIZE(mess_lsys_vfs_socketpath); + typedef struct { endpoint_t endpt; void *addr; @@ -2266,6 +2267,14 @@ typedef struct { } mess_vfs_lsys_gcov; _ASSERT_MSG_SIZE(mess_vfs_lsys_gcov); +typedef struct { + dev_t device; + ino_t inode; + + uint8_t padding[40]; +} mess_vfs_lsys_socketpath; +_ASSERT_MSG_SIZE(mess_vfs_lsys_socketpath); + typedef struct { time_t atime; time_t mtime; @@ -2481,9 +2490,9 @@ typedef struct noxfer_message { mess_lsys_sched_scheduling_start m_lsys_sched_scheduling_start; mess_lsys_sched_scheduling_stop m_lsys_sched_scheduling_stop; mess_lsys_tty_fkey_ctl m_lsys_tty_fkey_ctl; - mess_lsys_vfs_checkperms m_lsys_vfs_checkperms; mess_lsys_vfs_copyfd m_lsys_vfs_copyfd; mess_lsys_vfs_mapdriver m_lsys_vfs_mapdriver; + mess_lsys_vfs_socketpath m_lsys_vfs_socketpath; mess_lsys_vm_getref m_lsys_vm_getref; mess_lsys_vm_info m_lsys_vm_info; mess_lsys_vm_map_phys m_lsys_vm_map_phys; @@ -2567,6 +2576,7 @@ typedef struct noxfer_message { mess_vfs_lsockdriver_simple m_vfs_lsockdriver_simple; mess_vfs_lsockdriver_socket m_vfs_lsockdriver_socket; mess_vfs_lsys_gcov m_vfs_lsys_gcov; + mess_vfs_lsys_socketpath m_vfs_lsys_socketpath; mess_vfs_utimens m_vfs_utimens; mess_vm_vfs_mmap m_vm_vfs_mmap; mess_vmmcp m_vmmcp; diff --git a/minix/include/minix/syslib.h b/minix/include/minix/syslib.h index 1d0611f70..905472cb8 100644 --- a/minix/include/minix/syslib.h +++ b/minix/include/minix/syslib.h @@ -269,7 +269,11 @@ int mapdriver(const char *label, devmajor_t major, const int *domains, pid_t getnpid(endpoint_t proc_ep); uid_t getnuid(endpoint_t proc_ep); gid_t getngid(endpoint_t proc_ep); -int checkperms(endpoint_t endpt, char *path, size_t size); +int socketpath(endpoint_t endpt, char *path, size_t size, int what, dev_t *dev, + ino_t *ino); +#define SPATH_CHECK 0 /* check user permissions on socket path */ +#define SPATH_CREATE 1 /* create socket file at given path */ +#define SPATH_CANONIZE 0x8000 /* copy back canonized path (legacy support) */ int copyfd(endpoint_t endpt, int fd, int what); #define COPYFD_FROM 0 /* copy file descriptor from remote process */ #define COPYFD_TO 1 /* copy file descriptor to remote process */ diff --git a/minix/lib/libsys/Makefile b/minix/lib/libsys/Makefile index 84a5b3aa0..e8702051c 100644 --- a/minix/lib/libsys/Makefile +++ b/minix/lib/libsys/Makefile @@ -13,7 +13,6 @@ SRCS+= \ alloc_util.c \ assert.c \ asynsend.c \ - checkperms.c \ clock_time.c \ closenb.c \ copyfd.c \ @@ -49,6 +48,7 @@ SRCS+= \ sef_ping.c \ sef_signal.c \ sef_st.c \ + socketpath.c \ sqrt_approx.c \ srv_fork.c \ srv_kill.c \ diff --git a/minix/lib/libsys/checkperms.c b/minix/lib/libsys/checkperms.c deleted file mode 100644 index 0998e64d8..000000000 --- a/minix/lib/libsys/checkperms.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "syslib.h" - -#include -#include -#include - -int -checkperms(endpoint_t endpt, char *path, size_t size) -{ - cp_grant_id_t grant; - message m; - int r; - - if ((grant = cpf_grant_direct(VFS_PROC_NR, (vir_bytes) path, size, - CPF_READ | CPF_WRITE)) == GRANT_INVALID) - return ENOMEM; - - memset(&m, 0, sizeof(m)); - m.m_lsys_vfs_checkperms.endpt = endpt; - m.m_lsys_vfs_checkperms.grant = grant; - m.m_lsys_vfs_checkperms.count = size; - - r = _taskcall(VFS_PROC_NR, VFS_CHECKPERMS, &m); - - cpf_revoke(grant); - - return r; -} diff --git a/minix/lib/libsys/socketpath.c b/minix/lib/libsys/socketpath.c new file mode 100644 index 000000000..2473a7815 --- /dev/null +++ b/minix/lib/libsys/socketpath.c @@ -0,0 +1,35 @@ +#include "syslib.h" + +#include +#include +#include + +int +socketpath(endpoint_t endpt, char * path, size_t size, int what, dev_t * dev, + ino_t * ino) +{ + cp_grant_id_t grant; + message m; + int r; + + if ((grant = cpf_grant_direct(VFS_PROC_NR, (vir_bytes)path, size, + CPF_READ | CPF_WRITE)) == GRANT_INVALID) + return ENOMEM; + + memset(&m, 0, sizeof(m)); + m.m_lsys_vfs_socketpath.endpt = endpt; + m.m_lsys_vfs_socketpath.grant = grant; + m.m_lsys_vfs_socketpath.count = size; + m.m_lsys_vfs_socketpath.what = what | SPATH_CANONIZE; + + r = _taskcall(VFS_PROC_NR, VFS_SOCKETPATH, &m); + + cpf_revoke(grant); + + if (r == OK) { + *dev = m.m_vfs_lsys_socketpath.device; + *ino = m.m_vfs_lsys_socketpath.inode; + } + + return r; +} diff --git a/minix/net/uds/ioc_uds.c b/minix/net/uds/ioc_uds.c index 3ac44084d..23d8563df 100644 --- a/minix/net/uds/ioc_uds.c +++ b/minix/net/uds/ioc_uds.c @@ -67,10 +67,11 @@ do_accept(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) /* Locate the server socket. */ for (i = 0; i < NR_FDS; i++) { - if (uds_fd_table[i].addr.sun_family == AF_UNIX && + if (uds_fd_table[i].stale == FALSE && + uds_fd_table[i].listening == TRUE && + uds_fd_table[i].addr.sun_family == AF_UNIX && !strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path, - sizeof(uds_fd_table[i].addr.sun_path)) && - uds_fd_table[i].listening == 1) + sizeof(uds_fd_table[i].addr.sun_path))) break; } @@ -146,6 +147,8 @@ do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) int child, peer; struct sockaddr_un addr; int rc, i, j; + dev_t dev; + ino_t ino; dprintf(("UDS: do_connect(%d)\n", minor)); @@ -167,8 +170,8 @@ do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) sizeof(struct sockaddr_un))) != OK) return rc; - if ((rc = checkperms(uds_fd_table[minor].owner, addr.sun_path, - sizeof(addr.sun_path))) != OK) + if ((rc = socketpath(uds_fd_table[minor].owner, addr.sun_path, + sizeof(addr.sun_path), SPATH_CHECK, &dev, &ino)) != OK) return rc; /* @@ -178,7 +181,9 @@ do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) for (i = 0; i < NR_FDS; i++) { if (uds_fd_table[minor].type != uds_fd_table[i].type) continue; - if (!uds_fd_table[i].listening) + if (uds_fd_table[i].listening == FALSE) + continue; + if (uds_fd_table[i].stale == TRUE) continue; if (uds_fd_table[i].addr.sun_family != AF_UNIX) continue; @@ -276,7 +281,7 @@ do_listen(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) sizeof(backlog_size))) != OK) return rc; - if (uds_fd_table[minor].listening == 0) { + if (uds_fd_table[minor].listening == FALSE) { /* Set the backlog size to a reasonable value. */ if (backlog_size <= 0 || backlog_size > UDS_SOMAXCONN) backlog_size = UDS_SOMAXCONN; @@ -295,7 +300,7 @@ do_listen(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) } /* This socket is now listening. */ - uds_fd_table[minor].listening = 1; + uds_fd_table[minor].listening = TRUE; return OK; } @@ -334,6 +339,8 @@ do_bind(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) { struct sockaddr_un addr; int rc, i; + dev_t dev; + ino_t ino; dprintf(("UDS: do_bind(%d)\n", minor)); @@ -356,21 +363,41 @@ do_bind(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) if (addr.sun_path[0] == '\0') return ENOENT; - if ((rc = checkperms(uds_fd_table[minor].owner, addr.sun_path, - sizeof(addr.sun_path))) != OK) + /* Attempt to create the socket file. */ + if ((rc = socketpath(uds_fd_table[minor].owner, addr.sun_path, +#if NOT_YET + sizeof(addr.sun_path), SPATH_CREATE, &dev, &ino)) != OK) +#else + sizeof(addr.sun_path), SPATH_CHECK, &dev, &ino)) != OK) +#endif return rc; - /* Make sure the address isn't already in use by another socket. */ + /* + * It is possible that the socket path name was already in use as + * address by another socket. This means that the socket file was + * prematurely unlinked. In that case, mark the old socket as stale, + * so that its path name will not be matched and only the newly bound + * socket will be found in address-based searches. For now, we leave + * the old socket marked as stale for as long as it is bound to the + * same address. A more advanced implementation could establish an + * order between the sockets so that the most recently bound socket is + * found at any time, but it is doubtful whether that would be useful. + */ for (i = 0; i < NR_FDS; i++) { - if (uds_fd_table[i].addr.sun_family == AF_UNIX && + if (uds_fd_table[i].stale == FALSE && + uds_fd_table[i].addr.sun_family == AF_UNIX && !strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path, sizeof(uds_fd_table[i].addr.sun_path))) { - /* Another socket is bound to this sun_path. */ +#if NOT_YET + uds_fd_table[i].stale = TRUE; +#else return EADDRINUSE; +#endif } } /* Looks good, perform the bind(). */ + uds_fd_table[minor].stale = FALSE; memcpy(&uds_fd_table[minor].addr, &addr, sizeof(struct sockaddr_un)); return OK; @@ -597,6 +624,8 @@ do_sendto(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) { int rc; struct sockaddr_un addr; + dev_t dev; + ino_t ino; dprintf(("UDS: do_sendto(%d)\n", minor)); @@ -612,8 +641,8 @@ do_sendto(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) if (addr.sun_family != AF_UNIX || addr.sun_path[0] == '\0') return EINVAL; - if ((rc = checkperms(uds_fd_table[minor].owner, addr.sun_path, - sizeof(addr.sun_path))) != OK) + if ((rc = socketpath(uds_fd_table[minor].owner, addr.sun_path, + sizeof(addr.sun_path), SPATH_CHECK, &dev, &ino)) != OK) return rc; memcpy(&uds_fd_table[minor].target, &addr, sizeof(struct sockaddr_un)); @@ -825,6 +854,7 @@ do_sendmsg(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) * target address. */ if (uds_fd_table[i].type == SOCK_DGRAM && + uds_fd_table[i].stale == FALSE && uds_fd_table[i].addr.sun_family == AF_UNIX && !strncmp(uds_fd_table[minor].target.sun_path, uds_fd_table[i].addr.sun_path, diff --git a/minix/net/uds/uds.c b/minix/net/uds/uds.c index 49929548a..baca3c1ed 100644 --- a/minix/net/uds/uds.c +++ b/minix/net/uds/uds.c @@ -94,7 +94,8 @@ uds_open(devminor_t UNUSED(orig_minor), int access, for (i = 0; i < OPEN_MAX; i++) uds_fd_table[minor].ancillary_data.fds[i] = -1; - uds_fd_table[minor].listening = 0; + uds_fd_table[minor].stale = FALSE; + uds_fd_table[minor].listening = FALSE; uds_fd_table[minor].peer = -1; uds_fd_table[minor].child = -1; @@ -203,7 +204,7 @@ uds_select(devminor_t minor, unsigned int ops, endpoint_t endpt) bytes = uds_perform_read(minor, NONE, GRANT_INVALID, 1, 1); if (bytes > 0) { ready_ops |= CDEV_OP_RD; /* data available */ - } else if (uds_fd_table[minor].listening == 1) { + } else if (uds_fd_table[minor].listening == TRUE) { /* Check for pending connections. */ for (i = 0; i < uds_fd_table[minor].backlog_size; i++) { @@ -389,6 +390,7 @@ uds_perform_write(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant, * the target address. */ if (uds_fd_table[i].type == SOCK_DGRAM && + uds_fd_table[i].stale == FALSE && uds_fd_table[i].addr.sun_family == AF_UNIX && !strncmp(uds_fd_table[minor].target.sun_path, uds_fd_table[i].addr.sun_path, diff --git a/minix/net/uds/uds.h b/minix/net/uds/uds.h index db9a87940..cfcafdf99 100644 --- a/minix/net/uds/uds.h +++ b/minix/net/uds/uds.h @@ -121,8 +121,14 @@ struct uds_fd { */ struct sockaddr_un source; - /* Flag (1 or 0) - listening for incoming connections. - * Default to 0. Set to 1 by do_listen() + /* Flag (TRUE or FALSE) - address overridden by newer socket. + * Default to FALSE. Set to TRUE by do_bind() on another socket with + * the same path but its on-disk socket file removed in the meantime. + */ + int stale; + + /* Flag (TRUE or FALSE) - listening for incoming connections. + * Default to FALSE. Set to TRUE by do_listen(). */ int listening; diff --git a/minix/servers/vfs/path.c b/minix/servers/vfs/path.c index 21fab6f1a..5f0191a4b 100644 --- a/minix/servers/vfs/path.c +++ b/minix/servers/vfs/path.c @@ -33,8 +33,6 @@ static int lookup(struct vnode *dirp, struct lookup *resolve, node_details_t *node, struct fproc *rfp); -static int check_perms(endpoint_t ep, cp_grant_id_t io_gr, size_t - pathlen); /*===========================================================================* * advance * @@ -801,64 +799,145 @@ canonical_path(char orig_path[PATH_MAX], struct fproc *rfp) } /*===========================================================================* - * check_perms * + * do_socketpath * *===========================================================================*/ -static int check_perms(ep, io_gr, pathlen) -endpoint_t ep; -cp_grant_id_t io_gr; -size_t pathlen; +int do_socketpath(void) { - int r, slot; - struct vnode *vp; - struct vmnt *vmp; +/* + * Perform a path action on an on-disk socket file. This call may be performed + * by the UDS service only. The action is always on behalf of a user process + * that is currently making a socket call to the UDS service, and thus, VFS may + * rely on the fact that the user process is blocked. TODO: there should be + * checks in place to prevent (even accidental) abuse of this function, though. + */ + int r, what, slot; + endpoint_t ep; + cp_grant_id_t io_gr; + size_t pathlen; + struct vnode *dirp, *vp; + struct vmnt *vmp, *vmp2; struct fproc *rfp; - char canon_path[PATH_MAX]; - struct lookup resolve; + char path[PATH_MAX]; + struct lookup resolve, resolve2; struct sockaddr_un sun; + mode_t bits; + + /* This should be replaced by an ACL check. */ + if (!super_user) return EPERM; + + ep = job_m_in.m_lsys_vfs_socketpath.endpt; + io_gr = job_m_in.m_lsys_vfs_socketpath.grant; + pathlen = job_m_in.m_lsys_vfs_socketpath.count; + what = job_m_in.m_lsys_vfs_socketpath.what; if (isokendpt(ep, &slot) != OK) return(EINVAL); if (pathlen < sizeof(sun.sun_path) || pathlen >= PATH_MAX) return(EINVAL); rfp = &(fproc[slot]); - r = sys_safecopyfrom(who_e, io_gr, (vir_bytes) 0, (vir_bytes) canon_path, - pathlen); + r = sys_safecopyfrom(who_e, io_gr, (vir_bytes)0, (vir_bytes)path, pathlen); if (r != OK) return(r); - canon_path[pathlen] = '\0'; + path[pathlen] = '\0'; - /* Turn path into canonical path to the socket file */ - if ((r = canonical_path(canon_path, rfp)) != OK) return(r); - if (strlen(canon_path) >= pathlen) return(ENAMETOOLONG); + /* If requested, turn path into canonical path to the socket file */ + if (what & SPATH_CANONIZE) { + if ((r = canonical_path(path, rfp)) != OK) return(r); + if (strlen(path) >= pathlen) return(ENAMETOOLONG); - /* copy canon_path back to the caller */ - r = sys_safecopyto(who_e, (cp_grant_id_t) io_gr, (vir_bytes) 0, - (vir_bytes) canon_path, pathlen); - if (r != OK) return(r); + /* copy path back to the caller */ + r = sys_safecopyto(who_e, (cp_grant_id_t)io_gr, (vir_bytes)0, + (vir_bytes)path, pathlen); + if (r != OK) return(r); + } - /* Now do permissions checking */ - lookup_init(&resolve, canon_path, PATH_NOFLAGS, &vmp, &vp); - resolve.l_vmnt_lock = VMNT_READ; - resolve.l_vnode_lock = VNODE_READ; - if ((vp = eat_path(&resolve, rfp)) == NULL) return(err_code); + /* Now perform the requested action. For the SPATH_CHECK action, a socket + * file is expected to exist already, and we should check whether the given + * user process has access to it. For the SPATH_CREATE action, no file is + * expected to exist yet, and a socket file should be created on behalf of + * the user process. In both cases, on success, return the socket file's + * device and inode numbers to the caller. + * + * Since the above canonicalization releases all locks once done, we need to + * recheck absolutely everything now. TODO: do not release locks in between. + */ + switch (what & ~SPATH_CANONIZE) { + case SPATH_CHECK: + lookup_init(&resolve, path, PATH_NOFLAGS, &vmp, &vp); + resolve.l_vmnt_lock = VMNT_READ; + resolve.l_vnode_lock = VNODE_READ; + if ((vp = eat_path(&resolve, rfp)) == NULL) return(err_code); - /* check permissions */ - r = forbidden(rfp, vp, (R_BIT | W_BIT)); + /* Check file type and permissions. */ + if (!S_ISSOCK(vp->v_mode)) + r = ENOTSOCK; /* not in POSIX spec; this is what NetBSD does */ + else + r = forbidden(rfp, vp, R_BIT | W_BIT); - unlock_vnode(vp); - unlock_vmnt(vmp); + if (r == OK) { + job_m_out.m_vfs_lsys_socketpath.device = vp->v_dev; + job_m_out.m_vfs_lsys_socketpath.inode = vp->v_inode_nr; + } + + unlock_vnode(vp); + unlock_vmnt(vmp); + put_vnode(vp); + break; + + case SPATH_CREATE: + /* This is effectively simulating a mknod(2) call by the user process, + * including the application of its umask to the file permissions. + */ + lookup_init(&resolve, path, PATH_RET_SYMLINK, &vmp, &dirp); + resolve.l_vmnt_lock = VMNT_WRITE; + resolve.l_vnode_lock = VNODE_WRITE; + + if ((dirp = last_dir(&resolve, rfp)) == NULL) return(err_code); + + bits = S_IFSOCK | (ACCESSPERMS & rfp->fp_umask); + + if (!S_ISDIR(dirp->v_mode)) + r = ENOTDIR; + else if ((r = forbidden(rfp, dirp, W_BIT | X_BIT)) == OK) { + r = req_mknod(dirp->v_fs_e, dirp->v_inode_nr, path, + rfp->fp_effuid, rfp->fp_effgid, bits, NO_DEV); + if (r == OK) { + /* Now we need to find out the device and inode number + * of the socket file we just created. The vmnt lock + * should prevent any trouble here. + */ + lookup_init(&resolve2, resolve.l_path, + PATH_RET_SYMLINK, &vmp2, &vp); + resolve2.l_vmnt_lock = VMNT_READ; + resolve2.l_vnode_lock = VNODE_READ; + vp = advance(dirp, &resolve2, rfp); + assert(vmp2 == NULL); + if (vp != NULL) { + job_m_out.m_vfs_lsys_socketpath.device = + vp->v_dev; + job_m_out.m_vfs_lsys_socketpath.inode = + vp->v_inode_nr; + unlock_vnode(vp); + put_vnode(vp); + } else { + /* Huh. This should never happen. If it does, + * we assume the socket file has somehow been + * lost, so we do not try to unlink it. + */ + printf("VFS: socketpath did not find created " + "node at %s (%d)\n", path, err_code); + r = err_code; + } + } else if (r == EEXIST) + r = EADDRINUSE; + } + + unlock_vnode(dirp); + unlock_vmnt(vmp); + put_vnode(dirp); + break; + + default: + r = ENOSYS; + } - put_vnode(vp); return(r); } - -/*===========================================================================* - * do_checkperms * - *===========================================================================*/ -int do_checkperms(void) -{ - /* This should be replaced by an ACL check. */ - if (!super_user) return EPERM; - - return check_perms(job_m_in.m_lsys_vfs_checkperms.endpt, - job_m_in.m_lsys_vfs_checkperms.grant, - job_m_in.m_lsys_vfs_checkperms.count); -} diff --git a/minix/servers/vfs/proto.h b/minix/servers/vfs/proto.h index d9a3b2068..17f593c1a 100644 --- a/minix/servers/vfs/proto.h +++ b/minix/servers/vfs/proto.h @@ -170,7 +170,7 @@ void lookup_init(struct lookup *resolve, char *path, int flags, struct vmnt **vmp, struct vnode **vp); int get_name(struct vnode *dirp, struct vnode *entry, char *_name); int canonical_path(char *orig_path, struct fproc *rfp); -int do_checkperms(void); +int do_socketpath(void); /* pipe.c */ int do_pipe2(void); diff --git a/minix/servers/vfs/table.c b/minix/servers/vfs/table.c index f3b24ec96..dde3eb5c4 100644 --- a/minix/servers/vfs/table.c +++ b/minix/servers/vfs/table.c @@ -62,7 +62,7 @@ int (* const call_vec[NR_VFS_CALLS])(void) = { CALL(VFS_GCOV_FLUSH) = do_gcov_flush, /* gcov_flush(2) */ CALL(VFS_MAPDRIVER) = do_mapdriver, /* mapdriver(2) */ CALL(VFS_COPYFD) = do_copyfd, /* copyfd(2) */ - CALL(VFS_CHECKPERMS) = do_checkperms, /* checkperms(2) */ + CALL(VFS_SOCKETPATH) = do_socketpath, /* socketpath(2) */ CALL(VFS_GETSYSINFO) = do_getsysinfo, /* getsysinfo(2) */ CALL(VFS_SOCKET) = do_socket, /* socket(2) */ CALL(VFS_SOCKETPAIR) = do_socketpair, /* socketpair(2) */ diff --git a/minix/tests/test56.c b/minix/tests/test56.c index 9a408911b..4ce66e5f5 100644 --- a/minix/tests/test56.c +++ b/minix/tests/test56.c @@ -1390,14 +1390,197 @@ static void test_file(void) { struct sockaddr_un addr; - int sd; +#if NOT_YET + struct sockaddr_un saddr, saddr2; + char buf[1]; + socklen_t len; + struct stat st; + mode_t omask; + int, csd, fd; +#endif + int sd, sd2; + /* + * If the provided socket path exists on the file system, the bind(2) + * call must fail, regardless of whether there is a socket that is + * bound to that address. + */ UNLINK(TEST_SUN_PATH); memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strlcpy(addr.sun_path, TEST_SUN_PATH, sizeof(addr.sun_path)); + if ((sd = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) + test_fail("Can't open socket"); + if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + test_fail("Can't bind socket"); + + if ((sd2 = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) + test_fail("Can't open socket"); + if (bind(sd2, (struct sockaddr *)&addr, sizeof(addr)) != -1) + test_fail("Binding socket unexpectedly succeeded"); + if (errno != EADDRINUSE) + test_fail("Binding socket failed with wrong error"); + + CLOSE(sd); + +#if NOT_YET + if (bind(sd2, (struct sockaddr *)&addr, sizeof(addr)) != -1) + test_fail("Binding socket unexpectedly succeeded"); + if (errno != EADDRINUSE) + test_fail("Binding socket failed with wrong error"); + + CLOSE(sd2); + + UNLINK(TEST_SUN_PATH); + + /* + * If the socket is removed from the file system while there is still a + * socket bound to it, it should be possible to bind a new socket to + * the address. The old socket should then become unreachable in terms + * of connections and data directed to the address, even though it + * should still appear to be bound to the same address. + */ + if ((sd = socket(PF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) + test_fail("Can't open socket"); + if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + test_fail("Can't bind socket"); + memset(&saddr, 0, sizeof(saddr)); + len = sizeof(saddr); + if (getsockname(sd, (struct sockaddr *)&saddr, &len) != 0) + test_fail("Can't get socket address"); + + if ((csd = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) + test_fail("Can't open client socket"); + + memset(buf, 'X', sizeof(buf)); + if (sendto(csd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, + sizeof(addr)) != sizeof(buf)) + test_fail("Can't send to socket"); + if (recvfrom(sd, buf, sizeof(buf), 0, NULL, 0) != sizeof(buf)) + test_fail("Can't receive from socket"); + if (buf[0] != 'X') + test_fail("Transmission failure"); + + if (unlink(TEST_SUN_PATH) != 0) + test_fail("Can't unlink socket"); + + if ((sd2 = socket(PF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0)) == -1) + test_fail("Can't open socket"); + if (bind(sd2, (struct sockaddr *)&addr, sizeof(addr)) != 0) + test_fail("Can't bind socket"); + memset(&saddr2, 0, sizeof(saddr2)); + len = sizeof(saddr2); + if (getsockname(sd2, (struct sockaddr *)&saddr2, &len) != 0) + test_fail("Can't get socket address"); + if (memcmp(&saddr, &saddr2, sizeof(saddr))) + test_fail("Unexpected socket address"); + + memset(buf, 'Y', sizeof(buf)); + if (sendto(csd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, + sizeof(addr)) != sizeof(buf)) + test_fail("Can't send to socket"); + if (recvfrom(sd, buf, sizeof(buf), 0, NULL, 0) != -1) + test_fail("Unexpectedly received from old socket"); + if (errno != EWOULDBLOCK) + test_fail("Wrong receive failure from old socket"); + if (recvfrom(sd2, buf, sizeof(buf), 0, NULL, 0) != sizeof(buf)) + test_fail("Can't receive from new socket"); + if (buf[0] != 'Y') + test_fail("Transmission failure"); + + len = sizeof(saddr2); + if (getsockname(sd, (struct sockaddr *)&saddr2, &len) != 0) + test_fail("Can't get old socket address"); + if (memcmp(&saddr, &saddr2, sizeof(saddr))) + test_fail("Unexpected old socket address"); + + /* + * Currently, our implementation "hides" the old socket even if the new + * socket is closed, but since this is not standard behavior and may be + * changed later, we do not test for it. However, in any case, + * rebinding the hidden socket should make it "visible" again. + */ + strlcpy(saddr2.sun_path, TEST_SUN_PATHB, sizeof(saddr2.sun_path)); + if (bind(sd, (struct sockaddr *)&saddr2, sizeof(saddr2)) != 0) + test_fail("Can't rebind socket"); + + memset(buf, 'Z', sizeof(buf)); + if (sendto(csd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr2, + sizeof(saddr2)) != sizeof(buf)) + test_fail("Can't send to socket"); + if (recvfrom(sd, buf, sizeof(buf), 0, NULL, 0) != sizeof(buf)) + test_fail("Can't receive from socket"); + if (buf[0] != 'Z') + test_fail("Transmission failure"); + + if (unlink(TEST_SUN_PATH) != 0) + test_fail("Can't unlink socket"); + if (unlink(TEST_SUN_PATHB) != 0) + test_fail("Can't unlink other socket"); + + CLOSE(sd); + CLOSE(sd2); + CLOSE(csd); + + /* + * If the socket path identifies a file that is not a socket, bind(2) + * should still fail with EADDRINUSE, but connect(2) and sendto(2) + * should fail with ENOTSOCK (the latter is not specified by POSIX, so + * we follow NetBSD here). + */ + if ((fd = open(TEST_SUN_PATH, O_WRONLY | O_CREAT | O_EXCL, 0700)) < 0) + test_fail("Can't create regular file"); + CLOSE(fd); + + if ((sd = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) + test_fail("Can't open socket"); + if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != -1) + test_fail("Binding socket unexpectedly succeeded"); + if (errno != EADDRINUSE) + test_fail("Binding socket failed with wrong error"); + if (sendto(sd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, + sizeof(addr)) != -1) + test_fail("Sending to socket unexpectedly succeeded"); + if (errno != ENOTSOCK) + test_fail("Sending to socket failed with wrong error"); + CLOSE(sd); + + if ((sd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) + test_fail("Can't open socket"); + if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) != -1) + test_fail("Connecting socket unexpectedly succeeded"); + if (errno != ENOTSOCK) + test_fail("Connecting socket failed with wrong error"); + CLOSE(sd); + + UNLINK(TEST_SUN_PATH); + + /* + * The created socket file should have an access mode of 0777 corrected + * with the calling process's file mode creation mask. + */ + omask = umask(045123); + + if ((sd = socket(PF_UNIX, SOCK_DGRAM, 0)) == -1) + test_fail("Can't open socket"); + if (bind(sd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + test_fail("Can't bind socket"); + + if (stat(TEST_SUN_PATH, &st) != 0) + test_fail("Can't stat socket"); + if (!S_ISSOCK(st.st_mode)) + test_fail("File is not a socket"); + if ((st.st_mode & ALLPERMS) != (ACCESSPERMS & ~0123)) + test_fail("Unexpected file permission mask"); + + CLOSE(sd); + UNLINK(TEST_SUN_PATH); + + umask(omask); +#endif + /* * Only socket(2), socketpair(2), and accept(2) may be used to obtain * new file descriptors to sockets (or "sockets"); open(2) on a socket