svn commit: r323349 - in head/sys: compat/linuxkpi/common/include/linux compat/linuxkpi/common/src sys
Hans Petter Selasky
hselasky at FreeBSD.org
Sat Sep 9 06:29:30 UTC 2017
Author: hselasky
Date: Sat Sep 9 06:29:29 2017
New Revision: 323349
URL: https://svnweb.freebsd.org/changeset/base/323349
Log:
Properly implement poll_wait() in the LinuxKPI. This prevents direct
use of the linux_poll_wakeup() function from unsafe contexts, which
can lead to use-after-free issues.
Instead of calling linux_poll_wakeup() directly use the wake_up()
family of functions in the LinuxKPI to do this.
Bump the FreeBSD version to force recompilation of external kernel modules.
MFC after: 1 week
Sponsored by: Mellanox Technologies
Modified:
head/sys/compat/linuxkpi/common/include/linux/fs.h
head/sys/compat/linuxkpi/common/include/linux/poll.h
head/sys/compat/linuxkpi/common/src/linux_compat.c
head/sys/sys/param.h
Modified: head/sys/compat/linuxkpi/common/include/linux/fs.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/linux/fs.h Sat Sep 9 06:24:21 2017 (r323348)
+++ head/sys/compat/linuxkpi/common/include/linux/fs.h Sat Sep 9 06:29:29 2017 (r323349)
@@ -72,6 +72,17 @@ struct dentry {
struct file_operations;
+struct linux_file_wait_queue {
+ struct wait_queue wq;
+ struct wait_queue_head *wqh;
+ atomic_t state;
+#define LINUX_FWQ_STATE_INIT 0
+#define LINUX_FWQ_STATE_NOT_READY 1
+#define LINUX_FWQ_STATE_QUEUED 2
+#define LINUX_FWQ_STATE_READY 3
+#define LINUX_FWQ_STATE_MAX 4
+};
+
struct linux_file {
struct file *_file;
const struct file_operations *f_op;
@@ -97,6 +108,7 @@ struct linux_file {
#define LINUX_KQ_FLAG_NEED_WRITE (1 << 3)
/* protects f_selinfo.si_note */
spinlock_t f_kqlock;
+ struct linux_file_wait_queue f_wait_queue;
};
#define file linux_file
Modified: head/sys/compat/linuxkpi/common/include/linux/poll.h
==============================================================================
--- head/sys/compat/linuxkpi/common/include/linux/poll.h Sat Sep 9 06:24:21 2017 (r323348)
+++ head/sys/compat/linuxkpi/common/include/linux/poll.h Sat Sep 9 06:29:29 2017 (r323349)
@@ -40,11 +40,8 @@
typedef struct poll_table_struct {
} poll_table;
-static inline void
-poll_wait(struct linux_file *filp, wait_queue_head_t *wait_address, poll_table *p)
-{
- /* NOP */
-}
+extern void linux_poll_wait(struct linux_file *, wait_queue_head_t *, poll_table *);
+#define poll_wait(...) linux_poll_wait(__VA_ARGS__)
extern void linux_poll_wakeup(struct linux_file *);
Modified: head/sys/compat/linuxkpi/common/src/linux_compat.c
==============================================================================
--- head/sys/compat/linuxkpi/common/src/linux_compat.c Sat Sep 9 06:24:21 2017 (r323348)
+++ head/sys/compat/linuxkpi/common/src/linux_compat.c Sat Sep 9 06:29:29 2017 (r323349)
@@ -1023,10 +1023,9 @@ linux_dev_poll(struct cdev *dev, int events, struct th
file = td->td_fpop;
filp->f_flags = file->f_flag;
linux_set_current(td);
- if (filp->f_op->poll != NULL) {
- selrecord(td, &filp->f_selinfo);
+ if (filp->f_op->poll != NULL)
revents = filp->f_op->poll(filp, NULL) & events;
- } else
+ else
revents = 0;
return (revents);
@@ -1034,7 +1033,93 @@ error:
return (events & (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
}
+/*
+ * This function atomically updates the poll wakeup state and returns
+ * the previous state at the time of update.
+ */
+static uint8_t
+linux_poll_wakeup_state(atomic_t *v, const uint8_t *pstate)
+{
+ int c, old;
+
+ c = v->counter;
+
+ while ((old = atomic_cmpxchg(v, c, pstate[c])) != c)
+ c = old;
+
+ return (c);
+}
+
+
+static int
+linux_poll_wakeup_callback(wait_queue_t *wq, unsigned int wq_state, int flags, void *key)
+{
+ static const uint8_t state[LINUX_FWQ_STATE_MAX] = {
+ [LINUX_FWQ_STATE_INIT] = LINUX_FWQ_STATE_INIT, /* NOP */
+ [LINUX_FWQ_STATE_NOT_READY] = LINUX_FWQ_STATE_NOT_READY, /* NOP */
+ [LINUX_FWQ_STATE_QUEUED] = LINUX_FWQ_STATE_READY,
+ [LINUX_FWQ_STATE_READY] = LINUX_FWQ_STATE_READY, /* NOP */
+ };
+ struct linux_file *filp = container_of(wq, struct linux_file, f_wait_queue.wq);
+
+ switch (linux_poll_wakeup_state(&filp->f_wait_queue.state, state)) {
+ case LINUX_FWQ_STATE_QUEUED:
+ linux_poll_wakeup(filp);
+ return (1);
+ default:
+ return (0);
+ }
+}
+
void
+linux_poll_wait(struct linux_file *filp, wait_queue_head_t *wqh, poll_table *p)
+{
+ static const uint8_t state[LINUX_FWQ_STATE_MAX] = {
+ [LINUX_FWQ_STATE_INIT] = LINUX_FWQ_STATE_NOT_READY,
+ [LINUX_FWQ_STATE_NOT_READY] = LINUX_FWQ_STATE_NOT_READY, /* NOP */
+ [LINUX_FWQ_STATE_QUEUED] = LINUX_FWQ_STATE_QUEUED, /* NOP */
+ [LINUX_FWQ_STATE_READY] = LINUX_FWQ_STATE_QUEUED,
+ };
+
+ selrecord(curthread, &filp->f_selinfo);
+
+ switch (linux_poll_wakeup_state(&filp->f_wait_queue.state, state)) {
+ case LINUX_FWQ_STATE_INIT:
+ /* NOTE: file handles can only belong to one wait-queue */
+ filp->f_wait_queue.wqh = wqh;
+ filp->f_wait_queue.wq.func = &linux_poll_wakeup_callback;
+ add_wait_queue(wqh, &filp->f_wait_queue.wq);
+ atomic_set(&filp->f_wait_queue.state, LINUX_FWQ_STATE_QUEUED);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+linux_poll_wait_dequeue(struct linux_file *filp)
+{
+ static const uint8_t state[LINUX_FWQ_STATE_MAX] = {
+ [LINUX_FWQ_STATE_INIT] = LINUX_FWQ_STATE_INIT, /* NOP */
+ [LINUX_FWQ_STATE_NOT_READY] = LINUX_FWQ_STATE_INIT,
+ [LINUX_FWQ_STATE_QUEUED] = LINUX_FWQ_STATE_INIT,
+ [LINUX_FWQ_STATE_READY] = LINUX_FWQ_STATE_INIT,
+ };
+
+ seldrain(&filp->f_selinfo);
+
+ switch (linux_poll_wakeup_state(&filp->f_wait_queue.state, state)) {
+ case LINUX_FWQ_STATE_NOT_READY:
+ case LINUX_FWQ_STATE_QUEUED:
+ case LINUX_FWQ_STATE_READY:
+ remove_wait_queue(filp->f_wait_queue.wqh, &filp->f_wait_queue.wq);
+ break;
+ default:
+ break;
+ }
+}
+
+void
linux_poll_wakeup(struct linux_file *filp)
{
/* this function should be NULL-safe */
@@ -1358,6 +1443,7 @@ linux_file_close(struct file *file, struct thread *td)
filp = (struct linux_file *)file->f_data;
filp->f_flags = file->f_flag;
linux_set_current(td);
+ linux_poll_wait_dequeue(filp);
error = -filp->f_op->release(NULL, filp);
funsetown(&filp->f_sigio);
kfree(filp);
Modified: head/sys/sys/param.h
==============================================================================
--- head/sys/sys/param.h Sat Sep 9 06:24:21 2017 (r323348)
+++ head/sys/sys/param.h Sat Sep 9 06:29:29 2017 (r323349)
@@ -58,7 +58,7 @@
* in the range 5 to 9.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 1200043 /* Master, propagated to newvers */
+#define __FreeBSD_version 1200044 /* Master, propagated to newvers */
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,
More information about the svn-src-head
mailing list