kern/122710: panic occured by shutdown(2) against an one-to-one SCTP socket

Masahiro Kozuka ma-kun at kozuka.jp
Sun Apr 13 09:00:09 UTC 2008


>Number:         122710
>Category:       kern
>Synopsis:       panic occured by shutdown(2) against an one-to-one SCTP socket
>Confidential:   no
>Severity:       critical
>Priority:       high
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sun Apr 13 09:00:08 UTC 2008
>Closed-Date:
>Last-Modified:
>Originator:     Masahiro Kozuka
>Release:        FreeBSD 7.0-RELEASE
>Organization:
Kyoto University
>Environment:
FreeBSD  7.0-RELEASE FreeBSD 7.0-RELEASE #3: Sun Apr 13 17:22:27 JST 2008     root@:/usr/src/sys/i386/compile/SHARP  i386
>Description:
On FreeBSD 7.0R, when shutdown(2) called with SHUT_RD or SHUT_RDWR against 
an one-to-one SCTP socket, sbdrop_internel() called.
However, sbdrop_internel() assumes that the receiver socket buffer is one for not SCTP.
In the case of SCTP, sb_mb is always null (not used).
So, sbdrop_internel() will panic at sys/kern/uipc_sockbuf.c[849].

>How-To-Repeat:
The below very simple codes will trigger panic ....

==
% cat > client.c
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

#include <stdio.h>
#include <string.h>
#include <err.h>

int main(int argc, char **argv)
{

	int sfd;
	struct sockaddr_in sin;

	sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
	if (sfd < 0) {
		err(1, "socket");
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_len = sizeof(sin);
	sin.sin_addr.s_addr = inet_addr("127.0.0.1");
	sin.sin_port = htons(10001);

	if (connect(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		err(1, "connect");
	}

	write(sfd, "foo", sizeof("foo"));

	close(sfd);
	return 0;
}

% gcc -o client client.c
% cat > server.c
#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

#include <stdio.h>
#include <string.h>
#include <err.h>

int main(int argc, char **argv)
{

	int sfd, sfd1;
	struct sockaddr_in sin, addr;
	socklen_t addrlen;
	char buf[4096];
	int len;

	sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
	if (sfd < 0) {
		err(1, "socket");
	}

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_len = sizeof(sin);
	sin.sin_addr.s_addr = inet_addr("127.0.0.1");
	sin.sin_port = htons(10001);

	if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
		err(1, "bind");
	}

	if (listen(sfd, 1) < 0) {
		err(1, "listen");
	}

	addrlen = sizeof(addr);
	sfd1 = accept(sfd, (struct sockaddr *)&addr, &addrlen);
	if (sfd1 < 0) {
		err(1, "accept");
	}

	sleep(1);
	if (shutdown(sfd1, SHUT_RD) < 0) {
		err(1, "shutdown");
	}

	len = read(sfd1, buf, sizeof(buf));
	if (len < 0) {
		err(1, "read");
	}
	printf("len=%d\n", len);

	close(sfd1);
	close(sfd);
	return 0;
}
% gcc -o server server.c
% ./server&
% ./client
==
>Fix:
The below patch is for FreeBSD 7.0-RELEASE.

diff -u -r sys.orig/kern/uipc_domain.c sys/kern/uipc_domain.c
--- sys.orig/kern/uipc_domain.c	2007-08-06 23:26:00.000000000 +0900
+++ sys/kern/uipc_domain.c	2008-04-13 17:38:06.000000000 +0900
@@ -98,6 +98,7 @@
 	.pru_sosend =		pru_sosend_notsupp,
 	.pru_soreceive =	pru_soreceive_notsupp,
 	.pru_sopoll =		pru_sopoll_notsupp,
+	.pru_soshutdown =	pru_soshutdown_notsupp,
 };
 
 static void
@@ -122,6 +123,7 @@
 	DEFAULT(pu->pru_sosend, sosend_generic);
 	DEFAULT(pu->pru_soreceive, soreceive_generic);
 	DEFAULT(pu->pru_sopoll, sopoll_generic);
+	DEFAULT(pu->pru_soshutdown, soshutdown_generic);
 #undef DEFAULT
 	if (pr->pr_init)
 		(*pr->pr_init)();
diff -u -r sys.orig/kern/uipc_socket.c sys/kern/uipc_socket.c
--- sys.orig/kern/uipc_socket.c	2008-02-02 21:44:13.000000000 +0900
+++ sys/kern/uipc_socket.c	2008-04-13 17:38:06.000000000 +0900
@@ -1857,6 +1857,17 @@
 int
 soshutdown(struct socket *so, int how)
 {
+
+	/* XXXRW: Temporary debugging. */
+	KASSERT(so->so_proto->pr_usrreqs->pru_soshutdown != soshutdown,
+	    ("soshutdown: protocol calls soshutdown"));
+
+	return (so->so_proto->pr_usrreqs->pru_soshutdown(so, how));
+}
+
+int
+soshutdown_generic(struct socket *so, int how)
+{
 	struct protosw *pr = so->so_proto;
 
 	if (!(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR))
@@ -2678,6 +2689,13 @@
 	return EOPNOTSUPP;
 }
 
+int
+pru_soshutdown_notsupp(struct socket *so, int how)
+{
+
+	return EOPNOTSUPP;
+}
+
 static void
 filt_sordetach(struct knote *kn)
 {
diff -u -r sys.orig/netinet/sctp_usrreq.c sys/netinet/sctp_usrreq.c
--- sys.orig/netinet/sctp_usrreq.c	2007-12-10 05:23:47.000000000 +0900
+++ sys/netinet/sctp_usrreq.c	2008-04-13 17:38:21.000000000 +0900
@@ -4440,5 +4440,6 @@
 	.pru_shutdown = sctp_shutdown,
 	.pru_sockaddr = sctp_ingetaddr,
 	.pru_sosend = sctp_sosend,
-	.pru_soreceive = sctp_soreceive
+	.pru_soreceive = sctp_soreceive,
+	.pru_soshutdown = sctp_soshutdown
 };
diff -u -r sys.orig/netinet/sctputil.c sys/netinet/sctputil.c
--- sys.orig/netinet/sctputil.c	2008-02-02 21:44:13.000000000 +0900
+++ sys/netinet/sctputil.c	2008-04-13 17:38:21.000000000 +0900
@@ -6065,6 +6065,48 @@
 }
 
 
+int
+sctp_soshutdown(struct socket *so, int how)
+{
+	struct protosw *pr = so->so_proto;
+	struct sctp_inpcb *inp;
+	struct sctp_queued_to_read *sq;
+
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == NULL) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTPUTIL, EINVAL);
+		return (EINVAL);
+	}
+
+	if (!(how == SHUT_RD || how == SHUT_WR || how == SHUT_RDWR))
+		return (EINVAL);
+
+	if (how != SHUT_WR) {
+		socantrcvmore(so);
+
+		SCTP_INP_READ_LOCK(inp);
+		while ((sq = TAILQ_FIRST(&inp->read_queue)) != NULL) {
+			if (sq->length)
+				SCTP_STAT_INCR(sctps_left_abandon);
+
+			TAILQ_REMOVE(&inp->read_queue, sq, next);
+			sctp_free_remote_addr(sq->whoFrom);
+			if (sq->data) {
+				sctp_sbfree(sq, sq->stcb, &so->so_rcv, sq->data);
+				sctp_m_freem(sq->data);
+				sq->data = NULL;
+			}
+			SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_readq, sq);
+			SCTP_DECR_READQ_COUNT();
+		}
+		SCTP_INP_READ_UNLOCK(inp);
+	}
+	if (how != SHUT_RD)
+		return ((*pr->pr_usrreqs->pru_shutdown)(so));
+	return (0);
+}
+
+
 
 
 
diff -u -r sys.orig/netinet/sctputil.h sys/netinet/sctputil.h
--- sys.orig/netinet/sctputil.h	2007-11-06 11:48:04.000000000 +0900
+++ sys/netinet/sctputil.h	2008-04-13 17:38:21.000000000 +0900
@@ -334,6 +334,10 @@
     int *flag);
 
 
+int
+sctp_soshutdown(struct socket *so, int how);
+
+
 void
      sctp_misc_ints(uint8_t from, uint32_t a, uint32_t b, uint32_t c, uint32_t d);
 
diff -u -r sys.orig/sys/protosw.h sys/sys/protosw.h
--- sys.orig/sys/protosw.h	2006-07-25 00:20:08.000000000 +0900
+++ sys/sys/protosw.h	2008-04-13 17:38:41.000000000 +0900
@@ -236,6 +236,7 @@
 		    int *flagsp);
 	int	(*pru_sopoll)(struct socket *so, int events,
 		    struct ucred *cred, struct thread *td);
+	int	(*pru_soshutdown)(struct socket *so, int how);
 	void	(*pru_sosetlabel)(struct socket *so);
 	void	(*pru_close)(struct socket *so);
 };
@@ -270,6 +271,7 @@
 	    int *flagsp);
 int	pru_sopoll_notsupp(struct socket *so, int events, struct ucred *cred,
 	    struct thread *td);
+int	pru_soshutdown_notsupp(struct socket *so, int how);
 
 #endif /* _KERNEL */
 
diff -u -r sys.orig/sys/socketvar.h sys/sys/socketvar.h
--- sys.orig/sys/socketvar.h	2008-02-02 21:44:14.000000000 +0900
+++ sys/sys/socketvar.h	2008-04-13 17:38:41.000000000 +0900
@@ -551,6 +551,7 @@
 	    int flags, struct thread *td);
 int	sosetopt(struct socket *so, struct sockopt *sopt);
 int	soshutdown(struct socket *so, int how);
+int	soshutdown_generic(struct socket *so, int how);
 void	sotoxsocket(struct socket *so, struct xsocket *xso);
 void	sowakeup(struct socket *so, struct sockbuf *sb);
 


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list