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,
_______________________________________________
[email protected] mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "[email protected]"

Reply via email to