diff options
-rw-r--r-- | 0001-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch | 311 | ||||
-rw-r--r-- | kernel.spec | 10 | ||||
-rw-r--r-- | sources | 2 | ||||
-rw-r--r-- | ucount-Remove-the-atomicity-from-ucount-count.patch | 89 |
4 files changed, 97 insertions, 315 deletions
diff --git a/0001-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch b/0001-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch deleted file mode 100644 index b6cd8b211..000000000 --- a/0001-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch +++ /dev/null @@ -1,311 +0,0 @@ -From 1dea7a8061ad9212f4464464a80d0dcd477eceab Mon Sep 17 00:00:00 2001 -From: Alexander Popov <alex.popov@linux.com> -Date: Tue, 28 Feb 2017 19:28:54 +0300 -Subject: [PATCH 1/1] tty: n_hdlc: get rid of racy n_hdlc.tbuf - -Currently N_HDLC line discipline uses a self-made singly linked list for -data buffers and has n_hdlc.tbuf pointer for buffer retransmitting after -an error. - -The commit be10eb7589337e5defbe214dae038a53dd21add8 -("tty: n_hdlc add buffer flushing") introduced racy access to n_hdlc.tbuf. -After tx error concurrent flush_tx_queue() and n_hdlc_send_frames() can put -one data buffer to tx_free_buf_list twice. That causes double free in -n_hdlc_release(). - -Let's use standard kernel linked list and get rid of n_hdlc.tbuf: -in case of tx error put current data buffer after the head of tx_buf_list. - -Signed-off-by: Alexander Popov <alex.popov@linux.com> ---- - drivers/tty/n_hdlc.c | 132 +++++++++++++++++++++++++++------------------------ - 1 file changed, 69 insertions(+), 63 deletions(-) - -diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c -index eb27883..728c824 100644 ---- a/drivers/tty/n_hdlc.c -+++ b/drivers/tty/n_hdlc.c -@@ -114,7 +114,7 @@ - #define DEFAULT_TX_BUF_COUNT 3 - - struct n_hdlc_buf { -- struct n_hdlc_buf *link; -+ struct list_head list_item; - int count; - char buf[1]; - }; -@@ -122,8 +122,7 @@ struct n_hdlc_buf { - #define N_HDLC_BUF_SIZE (sizeof(struct n_hdlc_buf) + maxframe) - - struct n_hdlc_buf_list { -- struct n_hdlc_buf *head; -- struct n_hdlc_buf *tail; -+ struct list_head list; - int count; - spinlock_t spinlock; - }; -@@ -136,7 +135,6 @@ struct n_hdlc_buf_list { - * @backup_tty - TTY to use if tty gets closed - * @tbusy - reentrancy flag for tx wakeup code - * @woke_up - FIXME: describe this field -- * @tbuf - currently transmitting tx buffer - * @tx_buf_list - list of pending transmit frame buffers - * @rx_buf_list - list of received frame buffers - * @tx_free_buf_list - list unused transmit frame buffers -@@ -149,7 +147,6 @@ struct n_hdlc { - struct tty_struct *backup_tty; - int tbusy; - int woke_up; -- struct n_hdlc_buf *tbuf; - struct n_hdlc_buf_list tx_buf_list; - struct n_hdlc_buf_list rx_buf_list; - struct n_hdlc_buf_list tx_free_buf_list; -@@ -159,6 +156,8 @@ struct n_hdlc { - /* - * HDLC buffer list manipulation functions - */ -+static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list, -+ struct n_hdlc_buf *buf); - static void n_hdlc_buf_put(struct n_hdlc_buf_list *list, - struct n_hdlc_buf *buf); - static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *list); -@@ -208,16 +207,9 @@ static void flush_tx_queue(struct tty_struct *tty) - { - struct n_hdlc *n_hdlc = tty2n_hdlc(tty); - struct n_hdlc_buf *buf; -- unsigned long flags; - - while ((buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list))) - n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, buf); -- spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); -- if (n_hdlc->tbuf) { -- n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, n_hdlc->tbuf); -- n_hdlc->tbuf = NULL; -- } -- spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); - } - - static struct tty_ldisc_ops n_hdlc_ldisc = { -@@ -283,7 +275,6 @@ static void n_hdlc_release(struct n_hdlc *n_hdlc) - } else - break; - } -- kfree(n_hdlc->tbuf); - kfree(n_hdlc); - - } /* end of n_hdlc_release() */ -@@ -402,13 +393,7 @@ static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) - n_hdlc->woke_up = 0; - spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); - -- /* get current transmit buffer or get new transmit */ -- /* buffer from list of pending transmit buffers */ -- -- tbuf = n_hdlc->tbuf; -- if (!tbuf) -- tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); -- -+ tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); - while (tbuf) { - if (debuglevel >= DEBUG_LEVEL_INFO) - printk("%s(%d)sending frame %p, count=%d\n", -@@ -420,7 +405,7 @@ static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) - - /* rollback was possible and has been done */ - if (actual == -ERESTARTSYS) { -- n_hdlc->tbuf = tbuf; -+ n_hdlc_buf_return(&n_hdlc->tx_buf_list, tbuf); - break; - } - /* if transmit error, throw frame away by */ -@@ -435,10 +420,7 @@ static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) - - /* free current transmit buffer */ - n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, tbuf); -- -- /* this tx buffer is done */ -- n_hdlc->tbuf = NULL; -- -+ - /* wait up sleeping writers */ - wake_up_interruptible(&tty->write_wait); - -@@ -448,10 +430,12 @@ static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) - if (debuglevel >= DEBUG_LEVEL_INFO) - printk("%s(%d)frame %p pending\n", - __FILE__,__LINE__,tbuf); -- -- /* buffer not accepted by driver */ -- /* set this buffer as pending buffer */ -- n_hdlc->tbuf = tbuf; -+ -+ /* -+ * the buffer was not accepted by driver, -+ * return it back into tx queue -+ */ -+ n_hdlc_buf_return(&n_hdlc->tx_buf_list, tbuf); - break; - } - } -@@ -749,7 +733,8 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file, - int error = 0; - int count; - unsigned long flags; -- -+ struct n_hdlc_buf *buf = NULL; -+ - if (debuglevel >= DEBUG_LEVEL_INFO) - printk("%s(%d)n_hdlc_tty_ioctl() called %d\n", - __FILE__,__LINE__,cmd); -@@ -763,8 +748,10 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file, - /* report count of read data available */ - /* in next available frame (if any) */ - spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock,flags); -- if (n_hdlc->rx_buf_list.head) -- count = n_hdlc->rx_buf_list.head->count; -+ buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list, -+ struct n_hdlc_buf, list_item); -+ if (buf) -+ count = buf->count; - else - count = 0; - spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock,flags); -@@ -776,8 +763,10 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file, - count = tty_chars_in_buffer(tty); - /* add size of next output frame in queue */ - spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock,flags); -- if (n_hdlc->tx_buf_list.head) -- count += n_hdlc->tx_buf_list.head->count; -+ buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list, -+ struct n_hdlc_buf, list_item); -+ if (buf) -+ count += buf->count; - spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock,flags); - error = put_user(count, (int __user *)arg); - break; -@@ -825,14 +814,14 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp, - poll_wait(filp, &tty->write_wait, wait); - - /* set bits for operations that won't block */ -- if (n_hdlc->rx_buf_list.head) -+ if (!list_empty(&n_hdlc->rx_buf_list.list)) - mask |= POLLIN | POLLRDNORM; /* readable */ - if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) - mask |= POLLHUP; - if (tty_hung_up_p(filp)) - mask |= POLLHUP; - if (!tty_is_writelocked(tty) && -- n_hdlc->tx_free_buf_list.head) -+ !list_empty(&n_hdlc->tx_free_buf_list.list)) - mask |= POLLOUT | POLLWRNORM; /* writable */ - } - return mask; -@@ -856,7 +845,12 @@ static struct n_hdlc *n_hdlc_alloc(void) - spin_lock_init(&n_hdlc->tx_free_buf_list.spinlock); - spin_lock_init(&n_hdlc->rx_buf_list.spinlock); - spin_lock_init(&n_hdlc->tx_buf_list.spinlock); -- -+ -+ INIT_LIST_HEAD(&n_hdlc->rx_free_buf_list.list); -+ INIT_LIST_HEAD(&n_hdlc->tx_free_buf_list.list); -+ INIT_LIST_HEAD(&n_hdlc->rx_buf_list.list); -+ INIT_LIST_HEAD(&n_hdlc->tx_buf_list.list); -+ - /* allocate free rx buffer list */ - for(i=0;i<DEFAULT_RX_BUF_COUNT;i++) { - buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL); -@@ -884,53 +878,65 @@ static struct n_hdlc *n_hdlc_alloc(void) - } /* end of n_hdlc_alloc() */ - - /** -+ * n_hdlc_buf_return - put the HDLC buffer after the head of the specified list -+ * @buf_list - pointer to the buffer list -+ * @buf - pointer to the buffer -+ */ -+static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list, -+ struct n_hdlc_buf *buf) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&buf_list->spinlock, flags); -+ -+ list_add(&buf->list_item, &buf_list->list); -+ buf_list->count++; -+ -+ spin_unlock_irqrestore(&buf_list->spinlock, flags); -+} -+ -+/** - * n_hdlc_buf_put - add specified HDLC buffer to tail of specified list -- * @list - pointer to buffer list -+ * @buf_list - pointer to buffer list - * @buf - pointer to buffer - */ --static void n_hdlc_buf_put(struct n_hdlc_buf_list *list, -+static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list, - struct n_hdlc_buf *buf) - { - unsigned long flags; -- spin_lock_irqsave(&list->spinlock,flags); -- -- buf->link=NULL; -- if (list->tail) -- list->tail->link = buf; -- else -- list->head = buf; -- list->tail = buf; -- (list->count)++; -- -- spin_unlock_irqrestore(&list->spinlock,flags); -- -+ -+ spin_lock_irqsave(&buf_list->spinlock, flags); -+ -+ list_add_tail(&buf->list_item, &buf_list->list); -+ buf_list->count++; -+ -+ spin_unlock_irqrestore(&buf_list->spinlock, flags); - } /* end of n_hdlc_buf_put() */ - - /** - * n_hdlc_buf_get - remove and return an HDLC buffer from list -- * @list - pointer to HDLC buffer list -+ * @buf_list - pointer to HDLC buffer list - * - * Remove and return an HDLC buffer from the head of the specified HDLC buffer - * list. - * Returns a pointer to HDLC buffer if available, otherwise %NULL. - */ --static struct n_hdlc_buf* n_hdlc_buf_get(struct n_hdlc_buf_list *list) -+static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list) - { - unsigned long flags; - struct n_hdlc_buf *buf; -- spin_lock_irqsave(&list->spinlock,flags); -- -- buf = list->head; -+ -+ spin_lock_irqsave(&buf_list->spinlock, flags); -+ -+ buf = list_first_entry_or_null(&buf_list->list, -+ struct n_hdlc_buf, list_item); - if (buf) { -- list->head = buf->link; -- (list->count)--; -+ list_del(&buf->list_item); -+ buf_list->count--; - } -- if (!list->head) -- list->tail = NULL; -- -- spin_unlock_irqrestore(&list->spinlock,flags); -+ -+ spin_unlock_irqrestore(&buf_list->spinlock, flags); - return buf; -- - } /* end of n_hdlc_buf_get() */ - - static char hdlc_banner[] __initdata = --- -2.7.4 - diff --git a/kernel.spec b/kernel.spec index fabfeae22..3bf66240b 100644 --- a/kernel.spec +++ b/kernel.spec @@ -54,7 +54,7 @@ Summary: The Linux kernel %if 0%{?released_kernel} # Do we have a -stable update to apply? -%define stable_update 2 +%define stable_update 3 # Set rpm version accordingly %if 0%{?stable_update} %define stablerev %{stable_update} @@ -612,8 +612,8 @@ Patch854: kvm-fix-page-struct-leak-in-handle_vmon.patch #CVE-2017-6353 rhbz 1428907 1428910 Patch855: sctp-deny-peeloff-operation-on-asocs-with-threads-sl.patch -# CVE-2017-2636 rhbz 1430049 -Patch668: 0001-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch +#CVE-2017-6874 rhbz 1432429 1432430 +Patch856: ucount-Remove-the-atomicity-from-ucount-count.patch # END OF PATCH DEFINITIONS @@ -2184,6 +2184,10 @@ fi # # %changelog +* Wed Mar 15 2017 Justin M. Forbes <jforbes@fedoraproject.org> - 4.10.3-200 +- Linux v4.10.3 +- CVE-2017-6874 Fix race condition in ucount.c (rhbz 1432429 1432430) + * Mon Mar 13 2017 Justin M. Forbes <jforbes@fedoraproject.org> - 4.10.2-200 - Linux v4.10.2 @@ -1,3 +1,3 @@ SHA512 (linux-4.10.tar.xz) = c3690125a8402df638095bd98a613fcf1a257b81de7611c84711d315cd11e2634ab4636302b3742aedf1e3ba9ce0fea53fe8c7d48e37865d8ee5db3565220d90 SHA512 (perf-man-4.10.tar.gz) = 2c830e06f47211d70a8330961487af73a8bc01073019475e6b6131d3bb8c95658b77ca0ae5f1b44371accf103658bc5a3a4366b3e017a4088a8fd408dd6867e8 -SHA512 (patch-4.10.2.xz) = 9a980fa3a028bef8926c2e2b7fcb1ff918c6d883e00ada95f1b0b35f59e2811e6badd2f9b2f2cc806397c072305ef0b3b2f8f18196c4b9a6ff7a9578bbe04457 +SHA512 (patch-4.10.3.xz) = ad297c3c9d52c15444bb76b3be6785a99219527614f39e84299f2121f2657377de4145a9a0c649b3719462c48e8aae8686267a521ad9e58a0372df117edf7594 diff --git a/ucount-Remove-the-atomicity-from-ucount-count.patch b/ucount-Remove-the-atomicity-from-ucount-count.patch new file mode 100644 index 000000000..1b4e3c03f --- /dev/null +++ b/ucount-Remove-the-atomicity-from-ucount-count.patch @@ -0,0 +1,89 @@ +From 040757f738e13caaa9c5078bca79aa97e11dde88 Mon Sep 17 00:00:00 2001 +From: "Eric W. Biederman" <ebiederm@xmission.com> +Date: Sun, 5 Mar 2017 15:03:22 -0600 +Subject: [PATCH] ucount: Remove the atomicity from ucount->count + +Always increment/decrement ucount->count under the ucounts_lock. The +increments are there already and moving the decrements there means the +locking logic of the code is simpler. This simplification in the +locking logic fixes a race between put_ucounts and get_ucounts that +could result in a use-after-free because the count could go zero then +be found by get_ucounts and then be freed by put_ucounts. + +A bug presumably this one was found by a combination of syzkaller and +KASAN. JongWhan Kim reported the syzkaller failure and Dmitry Vyukov +spotted the race in the code. + +Cc: stable@vger.kernel.org +Fixes: f6b2db1a3e8d ("userns: Make the count of user namespaces per user") +Reported-by: JongHwan Kim <zzoru007@gmail.com> +Reported-by: Dmitry Vyukov <dvyukov@google.com> +Reviewed-by: Andrei Vagin <avagin@gmail.com> +Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com> +--- + include/linux/user_namespace.h | 2 +- + kernel/ucount.c | 18 +++++++++++------- + 2 files changed, 12 insertions(+), 8 deletions(-) + +diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h +index be76523..32354b4 100644 +--- a/include/linux/user_namespace.h ++++ b/include/linux/user_namespace.h +@@ -72,7 +72,7 @@ struct ucounts { + struct hlist_node node; + struct user_namespace *ns; + kuid_t uid; +- atomic_t count; ++ int count; + atomic_t ucount[UCOUNT_COUNTS]; + }; + +diff --git a/kernel/ucount.c b/kernel/ucount.c +index 62630a4..b4eeee0 100644 +--- a/kernel/ucount.c ++++ b/kernel/ucount.c +@@ -144,7 +144,7 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid) + + new->ns = ns; + new->uid = uid; +- atomic_set(&new->count, 0); ++ new->count = 0; + + spin_lock_irq(&ucounts_lock); + ucounts = find_ucounts(ns, uid, hashent); +@@ -155,8 +155,10 @@ static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid) + ucounts = new; + } + } +- if (!atomic_add_unless(&ucounts->count, 1, INT_MAX)) ++ if (ucounts->count == INT_MAX) + ucounts = NULL; ++ else ++ ucounts->count += 1; + spin_unlock_irq(&ucounts_lock); + return ucounts; + } +@@ -165,13 +167,15 @@ static void put_ucounts(struct ucounts *ucounts) + { + unsigned long flags; + +- if (atomic_dec_and_test(&ucounts->count)) { +- spin_lock_irqsave(&ucounts_lock, flags); ++ spin_lock_irqsave(&ucounts_lock, flags); ++ ucounts->count -= 1; ++ if (!ucounts->count) + hlist_del_init(&ucounts->node); +- spin_unlock_irqrestore(&ucounts_lock, flags); ++ else ++ ucounts = NULL; ++ spin_unlock_irqrestore(&ucounts_lock, flags); + +- kfree(ucounts); +- } ++ kfree(ucounts); + } + + static inline bool atomic_inc_below(atomic_t *v, int u) +-- +2.9.3 + |