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>