Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 9 Sep 2017 06:29:29 +0000 (UTC)
From:      Hans Petter Selasky <hselasky@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r323349 - in head/sys: compat/linuxkpi/common/include/linux compat/linuxkpi/common/src sys
Message-ID:  <201709090629.v896TTdl060092@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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,



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201709090629.v896TTdl060092>