Date: Tue, 22 Mar 2005 16:54:06 GMT From: James Juran <James.Juran@baesystems.com> To: freebsd-gnats-submit@FreeBSD.org Subject: kern/79138: close while sending on connected UNIX-domain socket can return ENOTCONN, should return EPIPE or 0 Message-ID: <200503221654.j2MGs67V014609@www.freebsd.org> Resent-Message-ID: <200503221700.j2MH06K8058261@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>Number: 79138 >Category: kern >Synopsis: close while sending on connected UNIX-domain socket can return ENOTCONN, should return EPIPE or 0 >Confidential: no >Severity: non-critical >Priority: low >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Tue Mar 22 17:00:06 GMT 2005 >Closed-Date: >Last-Modified: >Originator: James Juran >Release: 5.3-RELEASE >Organization: BAE Systems Information Technology, LLC >Environment: FreeBSD fbsd 5.3-RELEASE FreeBSD 5.3-RELEASE #1: Mon Mar 22 09:15:42 EST 2005 root@fbsd:/usr/obj/usr/src/sys/PREEMPT i386 (built with options PREEMPTION FULL_PREEMPTION) >Description: The test program shown below, when run on a uniproc system compiled with PREEMPTION and FULL_PREEMPTION, shows that calling send() for more than 2048 bytes on a UNIX-domain connection-oriented socket can return ENOTCONN, even when the socket is connected. This happens when the other side of the connection does a partial recv() and then closes the connection with data still in the send buffer. The send() should return either EPIPE, if the close happens before the send completes, or 0 if the send completes before the close takes effect. What happens is that sosend() breaks the send into 2048-byte chunks and calls uipc_send() for each one. uipc_send() wakes up a process waiting in recv(). In the GENERIC kernel on uniproc, this wakeup doesn't actually take effect until sosend() tries to get the SOCKBUF_LOCK at the bottom of the main loop. It then checks SBS_CANTSENDMORE on the next loop iteration, and returns EPIPE as it should. However, with option FULL_PREEMPTION, the process doing the recv() gets run right away, and performs its close before the second call to uipc_send(). uipc_send() then fails the check for SS_ISCONNECTED and returns ENOTCONN. Even though this failure requires FULL_PREEMPTION to reproduce on uniproc, I would think that on SMP there would be a chance of failure even on a standard kernel. >How-To-Repeat: Script started on Tue Mar 22 10:47:37 2005 bash-3.00$ cat sock-exec.c #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> int start_server(const char *name) { pid_t pid; pid = fork(); if (pid == -1) perror("fork failed"); else if (pid == 0) { /* Child process */ execl("sock-lpc", NULL); perror("execl returned?"); } return 1; } int run_client(const char *pathname) { int s; struct sockaddr_un un; int ret; char buf[8193]; s = socket(PF_LOCAL, SOCK_STREAM, 0); if (s == -1) { printf("socket failed: %s\n", strerror(errno)); exit(1); } memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, pathname); ret = connect(s, (struct sockaddr *)&un, sizeof(un.sun_family) + strlen(un.sun_path) + 1); if (ret == -1) { printf("connect failed: %s\n", strerror(errno)); exit(1); } memset(buf, 'A', sizeof(buf)); ret = send(s, buf, sizeof(buf), 0); if (ret == -1) { printf("send failed: %s\n", strerror(errno)); exit(1); } printf("send returned %d\n", ret); return 1; } int main(void) { if (!start_server("/tmp/AAAA")) return 1; /* Let the child process get the socket established */ sleep(2); if (!run_client("/tmp/AAAA")) return 1; puts("Parent process finished."); return 0; } bash-3.00$ cat sock-lpc.c #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> int main(void) { int s; const char name[] = "/tmp/AAAA"; struct sockaddr_un un; int conn; char buf[8193]; FILE *fp; int ret; fp = fopen("/tmp/child-log.txt", "w"); if (!fp) exit(1); s = socket(PF_UNIX, SOCK_STREAM, 0); if (s == -1) { fprintf(fp, "socket failed: %s", strerror(errno)); exit(1); } fprintf(fp, "socket for %s is %d\n", name, s); unlink(name); memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; strcpy(un.sun_path, name); ret = bind(s, (struct sockaddr *)&un, sizeof(un.sun_family) + strlen(un.sun_path) + 1); if (ret == -1) { fprintf(fp, "bind failed: %s\n", strerror(errno)); exit(1); } fprintf(fp, "Returned %d from bind to %s\n", ret, name); fprintf(fp, "Child process %d about to call listen\n", getpid()); ret = listen(s, 2); if (ret == -1) { fprintf(fp, "listen failed: %s\n", strerror(errno)); exit(1); } fprintf(fp, "Returned %d from listen\n", ret); conn = accept(s, NULL, NULL); fprintf(fp, "accept returned %d\n", conn); if (conn == -1) { fprintf(fp, "No connection: %s\n", strerror(errno)); exit(1); } ret = recv(conn, buf, sizeof(buf), 0); if (ret == -1) { fprintf(fp, "recv failed: %s\n", strerror(errno)); exit(1); } fprintf(fp, "recv returned %d\n", ret); close(conn); close(s); fclose(fp); return 0; } bash-3.00$ gcc -Wall -Werror -o sock-exec sock-exec.c bash-3.00$ gcc -Wall -Werror -o sock-lpc sock-lpc.c bash-3.00$ ./sock-exec send failed: Socket is not connected bash-3.00$ cat /tmp/child-log.txt socket for /tmp/AAAA is 4 Returned 0 from bind to /tmp/AAAA Child process 550 about to call listen Returned 0 from listen accept returned 5 recv returned 2048 bash-3.00$ ktrace ./sock-exec send failed: Socket is not connected bash-3.00$ kdump [...] 552 sock-exec CALL socket(0x1,0x1,0) 552 sock-exec RET socket 3 552 sock-exec CALL connect(0x3,0xbfbfea40,0xb) 552 sock-exec NAMI "/tmp/AAAA" 552 sock-exec RET connect 0 552 sock-exec CALL sendto(0x3,0xbfbfca20,0x2001,0,0,0) 552 sock-exec RET sendto -1 errno 57 Socket is not connected >Fix: Reversing the checks for SBS_CANTSENDMORE and SS_ISCONNECTED in uipc_send(), as in this patch, should avoid this problem. However, I don't have an SMP system to test on and I'm not sure this is the correct fix. Index: uipc_usrreq.c =================================================================== RCS file: /home/ncvs/src/sys/kern/uipc_usrreq.c,v retrieving revision 1.151 diff -u -p -f -u -p -r1.151 uipc_usrreq.c --- uipc_usrreq.c 21 Feb 2005 21:58:16 -0000 1.151 +++ uipc_usrreq.c 22 Mar 2005 16:45:33 -0000 @@ -436,6 +436,13 @@ uipc_send(struct socket *so, int flags, } case SOCK_STREAM: + SOCKBUF_LOCK(&so->so_snd); + if (so->so_snd.sb_state & SBS_CANTSENDMORE) { + SOCKBUF_UNLOCK(&so->so_snd); + error = EPIPE; + break; + } + /* Connect if not connected yet. */ /* * Note: A better implementation would complain @@ -443,21 +450,18 @@ uipc_send(struct socket *so, int flags, */ if ((so->so_state & SS_ISCONNECTED) == 0) { if (nam != NULL) { + SOCKBUF_UNLOCK(&so->so_snd); error = unp_connect(so, nam, td); if (error) break; /* XXX */ + SOCKBUF_LOCK(&so->so_snd); } else { + SOCKBUF_UNLOCK(&so->so_snd); error = ENOTCONN; break; } } - SOCKBUF_LOCK(&so->so_snd); - if (so->so_snd.sb_state & SBS_CANTSENDMORE) { - SOCKBUF_UNLOCK(&so->so_snd); - error = EPIPE; - break; - } if (unp->unp_conn == NULL) panic("uipc_send connected but no connection?"); so2 = unp->unp_conn->unp_socket; >Release-Note: >Audit-Trail: >Unformatted:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200503221654.j2MGs67V014609>