soo_close() vs. filt_soread()
Sebastian Huber
sebastian.huber at embedded-brains.de
Tue Oct 29 10:44:16 UTC 2013
Hello,
I port currently the FreeBSD network stack to a real-time operating system.
The problem described below probably does not happen in a real FreeBSD kernel.
I have the following test case:
static void
test_kqueue_close(test_context *ctx)
{
/* The cfd is some socket with a connected TCP stream */
int cfd = ctx->cfd;
int kq;
struct kevent change;
struct kevent event;
const struct timespec *timeout = NULL;
int rv;
ssize_t n;
puts("test kqueue close");
assert(ctx->cfd >= 0);
kq = kqueue();
assert(kq >= 0);
EV_SET(&change, cfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0,
TEST_UDATA);
rv = kevent(kq, &change, 1, NULL, 0, timeout);
assert(rv == 0);
set_non_blocking(cfd, 1);
do {
errno = 0;
n = read(cfd, &ctx->buf[0], sizeof(ctx->buf));
if (n == -1) {
assert(errno = EAGAIN);
}
} while (n > 0);
/* This tells some background entity that we want to close cfd once kevent
blocks */
send_events(ctx, EVENT_CLOSE);
assert(ctx->cfd >= 0);
rv = kevent(kq, NULL, 0, &event, 1, timeout);
assert(rv == 1);
assert(event.ident == cfd);
assert(event.filter == EVFILT_READ);
assert(event.flags == 0);
assert(event.fflags == 0);
assert(event.data == 0);
assert(event.udata == TEST_UDATA);
assert(ctx->cfd == -1);
rv = close(kq);
assert(rv == 0);
}
This test registers a read event and then blocks for this event. Once the
current thread blocked, someone will delete the corresponding socket. I have
now a NULL pointer access. The socket close looks like this:
/*
* API socket close on file pointer. We call soclose() to close the socket
* (including initiating closing protocols). soclose() will sorele() the
* file reference but the actual socket will not go away until the socket's
* ref count hits 0.
*/
/* ARGSUSED */
int
soo_close(struct file *fp, struct thread *td)
{
int error = 0;
struct socket *so;
so = fp->f_data;
fp->f_ops = &badfileops;
fp->f_data = NULL;
if (so)
error = soclose(so);
return (error);
}
Please note that fp->f_data is set to NULL.
The close operation will end up in:
/*ARGSUSED*/
static int
filt_soread(struct knote *kn, long hint)
{
struct socket *so;
so = kn->kn_fp->f_data;
SOCKBUF_LOCK_ASSERT(&so->so_rcv);
kn->kn_data = so->so_rcv.sb_cc - so->so_rcv.sb_ctl;
if (so->so_rcv.sb_state & SBS_CANTRCVMORE) {
kn->kn_flags |= EV_EOF;
kn->kn_fflags = so->so_error;
return (1);
} else if (so->so_error) /* temporary udp error */
return (1);
else if (kn->kn_sfflags & NOTE_LOWAT)
return (kn->kn_data >= kn->kn_sdata);
else
return (so->so_rcv.sb_cc >= so->so_rcv.sb_lowat);
}
Here so == NULL, due to soo_close().
Breakpoint 11, filt_soread (kn=0x46ff74, hint=0) at
freebsd/sys/kern/uipc_socket.c:3147
3147 so = kn->kn_fp->f_data;
(gdb) p so
$4 = (struct socket *) 0x0
(gdb) p kn->kn_ptr.p_fp
$5 = (struct file *) 0x32cec8
(gdb) where
#0 filt_soread (kn=0x46ff74, hint=0) at freebsd/sys/kern/uipc_socket.c:3147
#1 0x0010506e in knote (list=0x3f1190, hint=0, lockflags=1) at
freebsd/sys/kern/kern_event.c:1957
#2 0x00191a60 in sowakeup (so=0x3f1134, sb=0x3f1188) at
freebsd/sys/kern/uipc_sockbuf.c:191
#3 0x00127afe in soisdisconnecting (so=0x3f1134) at
freebsd/sys/kern/uipc_socket.c:3341
#4 0x00153cca in tcp_disconnect (tp=0x3f75ac) at
freebsd/sys/netinet/tcp_usrreq.c:1508
#5 0x00152b30 in tcp_usr_disconnect (so=0x3f1134) at
freebsd/sys/netinet/tcp_usrreq.c:556
#6 0x00124754 in sodisconnect (so=0x3f1134) at freebsd/sys/kern/uipc_socket.c:816
#7 0x00124384 in soclose (so=0x3f1134) at freebsd/sys/kern/uipc_socket.c:664
#8 0x00128886 in soo_close (fp=0x32cec8, td=0x0) at
freebsd/sys/kern/sys_socket.c:452
Is this an illegal kevent() use case? Are there some means that prevent this
sequence in a real FreeBSD kernel?
--
Sebastian Huber, embedded brains GmbH
Address : Dornierstr. 4, D-82178 Puchheim, Germany
Phone : +49 89 189 47 41-16
Fax : +49 89 189 47 41-09
E-Mail : sebastian.huber at embedded-brains.de
PGP : Public key available on request.
Diese Nachricht ist keine geschäftliche Mitteilung im Sinne des EHUG.
More information about the freebsd-hackers
mailing list