summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorghelmer <ghelmer@FreeBSD.org>2012-12-10 16:14:44 +0000
committerghelmer <ghelmer@FreeBSD.org>2012-12-10 16:14:44 +0000
commit726beb3d43000997e7410621a0c31a65b05ebb31 (patch)
tree05f79a3e8fd1361998dd0dc02f1088df035c8960
parent2160ff58527c3253bc5fe11f85c2e3d0fa680688 (diff)
downloadFreeBSD-src-726beb3d43000997e7410621a0c31a65b05ebb31.zip
FreeBSD-src-726beb3d43000997e7410621a0c31a65b05ebb31.tar.gz
Changes to resolve races in bpfread() and catchpacket() that, at worst,
cause kernel panics. Add a flag to the bpf descriptor to indicate whether the hold buffer is in use. In bpfread(), set the "hold buffer in use" flag before dropping the descriptor lock during the call to bpf_uiomove(). Everywhere else the hold buffer is used or changed, wait while the hold buffer is in use by bpfread(). Add a KASSERT in bpfread() after re-acquiring the descriptor lock to assist uncovering any additional hold buffer races.
-rw-r--r--sys/net/bpf.c41
-rw-r--r--sys/net/bpf.h3
-rw-r--r--sys/net/bpf_buffer.c5
-rw-r--r--sys/net/bpfdesc.h1
4 files changed, 38 insertions, 12 deletions
diff --git a/sys/net/bpf.c b/sys/net/bpf.c
index 6d9a917..9ee1964 100644
--- a/sys/net/bpf.c
+++ b/sys/net/bpf.c
@@ -819,6 +819,7 @@ bpfopen(struct cdev *dev, int flags, int fmt, struct thread *td)
* particular buffer method.
*/
bpf_buffer_init(d);
+ d->bd_hbuf_in_use = 0;
d->bd_bufmode = BPF_BUFMODE_BUFFER;
d->bd_sig = SIGIO;
d->bd_direction = BPF_D_INOUT;
@@ -872,6 +873,9 @@ bpfread(struct cdev *dev, struct uio *uio, int ioflag)
callout_stop(&d->bd_callout);
timed_out = (d->bd_state == BPF_TIMED_OUT);
d->bd_state = BPF_IDLE;
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock,
+ PRINET|PCATCH, "bd_hbuf", 0);
/*
* If the hold buffer is empty, then do a timed sleep, which
* ends when the timeout expires or when enough packets
@@ -940,27 +944,27 @@ bpfread(struct cdev *dev, struct uio *uio, int ioflag)
/*
* At this point, we know we have something in the hold slot.
*/
+ d->bd_hbuf_in_use = 1;
BPFD_UNLOCK(d);
/*
* Move data from hold buffer into user space.
* We know the entire buffer is transferred since
* we checked above that the read buffer is bpf_bufsize bytes.
- *
- * XXXRW: More synchronization needed here: what if a second thread
- * issues a read on the same fd at the same time? Don't want this
- * getting invalidated.
+ *
+ * We do not have to worry about simultaneous reads because
+ * we waited for sole access to the hold buffer above.
*/
error = bpf_uiomove(d, d->bd_hbuf, d->bd_hlen, uio);
BPFD_LOCK(d);
- if (d->bd_hbuf != NULL) {
- /* Free the hold buffer only if it is still valid. */
- d->bd_fbuf = d->bd_hbuf;
- d->bd_hbuf = NULL;
- d->bd_hlen = 0;
- bpf_buf_reclaimed(d);
- }
+ KASSERT(d->bd_hbuf != NULL, ("bpfread: lost bd_hbuf"));
+ d->bd_fbuf = d->bd_hbuf;
+ d->bd_hbuf = NULL;
+ d->bd_hlen = 0;
+ bpf_buf_reclaimed(d);
+ d->bd_hbuf_in_use = 0;
+ wakeup(&d->bd_hbuf_in_use);
BPFD_UNLOCK(d);
return (error);
@@ -1114,6 +1118,9 @@ reset_d(struct bpf_d *d)
BPFD_LOCK_ASSERT(d);
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock, PRINET,
+ "bd_hbuf", 0);
if ((d->bd_hbuf != NULL) &&
(d->bd_bufmode != BPF_BUFMODE_ZBUF || bpf_canfreebuf(d))) {
/* Free the hold buffer. */
@@ -1254,6 +1261,9 @@ bpfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags,
BPFD_LOCK(d);
n = d->bd_slen;
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock,
+ PRINET, "bd_hbuf", 0);
if (d->bd_hbuf)
n += d->bd_hlen;
BPFD_UNLOCK(d);
@@ -1967,6 +1977,9 @@ filt_bpfread(struct knote *kn, long hint)
ready = bpf_ready(d);
if (ready) {
kn->kn_data = d->bd_slen;
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock,
+ PRINET, "bd_hbuf", 0);
if (d->bd_hbuf)
kn->kn_data += d->bd_hlen;
} else if (d->bd_rtout > 0 && d->bd_state == BPF_IDLE) {
@@ -2299,6 +2312,9 @@ catchpacket(struct bpf_d *d, u_char *pkt, u_int pktlen, u_int snaplen,
* spot to do it.
*/
if (d->bd_fbuf == NULL && bpf_canfreebuf(d)) {
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock,
+ PRINET, "bd_hbuf", 0);
d->bd_fbuf = d->bd_hbuf;
d->bd_hbuf = NULL;
d->bd_hlen = 0;
@@ -2341,6 +2357,9 @@ catchpacket(struct bpf_d *d, u_char *pkt, u_int pktlen, u_int snaplen,
++d->bd_dcount;
return;
}
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock,
+ PRINET, "bd_hbuf", 0);
ROTATE_BUFFERS(d);
do_wakeup = 1;
curlen = 0;
diff --git a/sys/net/bpf.h b/sys/net/bpf.h
index a778ce6..e362f16 100644
--- a/sys/net/bpf.h
+++ b/sys/net/bpf.h
@@ -1235,7 +1235,8 @@ SYSCTL_DECL(_net_bpf);
/*
* Rotate the packet buffers in descriptor d. Move the store buffer into the
* hold slot, and the free buffer ino the store slot. Zero the length of the
- * new store buffer. Descriptor lock should be held.
+ * new store buffer. Descriptor lock should be held. Hold buffer must
+ * not be marked "in use".
*/
#define ROTATE_BUFFERS(d) do { \
(d)->bd_hbuf = (d)->bd_sbuf; \
diff --git a/sys/net/bpf_buffer.c b/sys/net/bpf_buffer.c
index 64bb982..74e1ae4 100644
--- a/sys/net/bpf_buffer.c
+++ b/sys/net/bpf_buffer.c
@@ -79,6 +79,8 @@ __FBSDID("$FreeBSD$");
#include <net/bpf_buffer.h>
#include <net/bpfdesc.h>
+#define PRINET 26 /* interruptible */
+
/*
* Implement historical kernel memory buffering model for BPF: two malloc(9)
* kernel buffers are hung off of the descriptor. The size is fixed prior to
@@ -189,6 +191,9 @@ bpf_buffer_ioctl_sblen(struct bpf_d *d, u_int *i)
return (EINVAL);
}
+ while (d->bd_hbuf_in_use)
+ mtx_sleep(&d->bd_hbuf_in_use, &d->bd_lock,
+ PRINET, "bd_hbuf", 0);
/* Free old buffers if set */
if (d->bd_fbuf != NULL)
free(d->bd_fbuf, M_BPF);
diff --git a/sys/net/bpfdesc.h b/sys/net/bpfdesc.h
index 6d58cc3..496f0b3 100644
--- a/sys/net/bpfdesc.h
+++ b/sys/net/bpfdesc.h
@@ -63,6 +63,7 @@ struct bpf_d {
caddr_t bd_sbuf; /* store slot */
caddr_t bd_hbuf; /* hold slot */
caddr_t bd_fbuf; /* free slot */
+ int bd_hbuf_in_use; /* don't rotate buffers */
int bd_slen; /* current length of store buffer */
int bd_hlen; /* current length of hold buffer */
OpenPOWER on IntegriCloud