[pve-devel] [PATCH stable-4 kernel 1/3] fix CVE-2017-2636: local privilege escalation in n_hdlc
Fabian Grünbichler
f.gruenbichler at proxmox.com
Thu Mar 9 12:43:34 CET 2017
Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
---
Makefile | 2 +
...001-TTY-n_hdlc-fix-lockdep-false-positive.patch | 111 +++++++
...02-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch | 319 +++++++++++++++++++++
3 files changed, 432 insertions(+)
create mode 100644 CVE-2017-2636-0001-TTY-n_hdlc-fix-lockdep-false-positive.patch
create mode 100644 CVE-2017-2636-0002-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch
diff --git a/Makefile b/Makefile
index 0f172f0..db86d47 100644
--- a/Makefile
+++ b/Makefile
@@ -269,6 +269,8 @@ ${KERNEL_SRC}/README ${KERNEL_CFG_ORG}: ${KERNELSRCTAR}
cd ${KERNEL_SRC}; patch -p1 < ../CVE-2017-2596-kvm-page-reference-leakage-in-handle_vmon.patch
cd ${KERNEL_SRC}; patch -p1 < ../CVE-2017-6074-dccp-fix-freeing-skb-too-early-for-IPV6_RECVPKTINFO.patch
cd ${KERNEL_SRC}; patch -p1 < ../Revert-intel_idle-Add-CPU-model-54-Atom-N2000-series.patch
+ cd ${KERNEL_SRC}; patch -p1 < ../CVE-2017-2636-0001-TTY-n_hdlc-fix-lockdep-false-positive.patch
+ cd ${KERNEL_SRC}; patch -p1 < ../CVE-2017-2636-0002-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch
sed -i ${KERNEL_SRC}/Makefile -e 's/^EXTRAVERSION.*$$/EXTRAVERSION=${EXTRAVERSION}/'
touch $@
diff --git a/CVE-2017-2636-0001-TTY-n_hdlc-fix-lockdep-false-positive.patch b/CVE-2017-2636-0001-TTY-n_hdlc-fix-lockdep-false-positive.patch
new file mode 100644
index 0000000..6a64b82
--- /dev/null
+++ b/CVE-2017-2636-0001-TTY-n_hdlc-fix-lockdep-false-positive.patch
@@ -0,0 +1,111 @@
+From c1c6bc86390390ba07618328909a9eb881ef6ace Mon Sep 17 00:00:00 2001
+From: Jiri Slaby <jslaby at suse.cz>
+Date: Thu, 26 Nov 2015 19:28:26 +0100
+Subject: [PATCH 1/2] TTY: n_hdlc, fix lockdep false positive
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The class of 4 n_hdls buf locks is the same because a single function
+n_hdlc_buf_list_init is used to init all the locks. But since
+flush_tx_queue takes n_hdlc->tx_buf_list.spinlock and then calls
+n_hdlc_buf_put which takes n_hdlc->tx_free_buf_list.spinlock, lockdep
+emits a warning:
+=============================================
+[ INFO: possible recursive locking detected ]
+4.3.0-25.g91e30a7-default #1 Not tainted
+---------------------------------------------
+a.out/1248 is trying to acquire lock:
+ (&(&list->spinlock)->rlock){......}, at: [<ffffffffa01fd020>] n_hdlc_buf_put+0x20/0x60 [n_hdlc]
+
+but task is already holding lock:
+ (&(&list->spinlock)->rlock){......}, at: [<ffffffffa01fdc07>] n_hdlc_tty_ioctl+0x127/0x1d0 [n_hdlc]
+
+other info that might help us debug this:
+ Possible unsafe locking scenario:
+
+ CPU0
+ ----
+ lock(&(&list->spinlock)->rlock);
+ lock(&(&list->spinlock)->rlock);
+
+ *** DEADLOCK ***
+
+ May be due to missing lock nesting notation
+
+2 locks held by a.out/1248:
+ #0: (&tty->ldisc_sem){++++++}, at: [<ffffffff814c9eb0>] tty_ldisc_ref_wait+0x20/0x50
+ #1: (&(&list->spinlock)->rlock){......}, at: [<ffffffffa01fdc07>] n_hdlc_tty_ioctl+0x127/0x1d0 [n_hdlc]
+...
+Call Trace:
+...
+ [<ffffffff81738fd0>] _raw_spin_lock_irqsave+0x50/0x70
+ [<ffffffffa01fd020>] n_hdlc_buf_put+0x20/0x60 [n_hdlc]
+ [<ffffffffa01fdc24>] n_hdlc_tty_ioctl+0x144/0x1d0 [n_hdlc]
+ [<ffffffff814c25c1>] tty_ioctl+0x3f1/0xe40
+...
+
+Fix it by initializing the spin_locks separately. This removes also
+reduntand memset of a freshly kzallocated space.
+
+Signed-off-by: Jiri Slaby <jslaby at suse.cz>
+Reported-by: Dmitry Vyukov <dvyukov at google.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh at linuxfoundation.org>
+
+CVE-2017-2636
+
+(cherry-picked from e9b736d88af1a143530565929390cadf036dc799 upstream)
+Signed-off-by: Stefan Bader <stefan.bader at canonical.com>
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ drivers/tty/n_hdlc.c | 19 ++++---------------
+ 1 file changed, 4 insertions(+), 15 deletions(-)
+
+diff --git a/drivers/tty/n_hdlc.c b/drivers/tty/n_hdlc.c
+index 644ddb8..a7fa016 100644
+--- a/drivers/tty/n_hdlc.c
++++ b/drivers/tty/n_hdlc.c
+@@ -159,7 +159,6 @@ struct n_hdlc {
+ /*
+ * HDLC buffer list manipulation functions
+ */
+-static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list);
+ 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);
+@@ -853,10 +852,10 @@ static struct n_hdlc *n_hdlc_alloc(void)
+ if (!n_hdlc)
+ return NULL;
+
+- n_hdlc_buf_list_init(&n_hdlc->rx_free_buf_list);
+- n_hdlc_buf_list_init(&n_hdlc->tx_free_buf_list);
+- n_hdlc_buf_list_init(&n_hdlc->rx_buf_list);
+- n_hdlc_buf_list_init(&n_hdlc->tx_buf_list);
++ spin_lock_init(&n_hdlc->rx_free_buf_list.spinlock);
++ 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);
+
+ /* allocate free rx buffer list */
+ for(i=0;i<DEFAULT_RX_BUF_COUNT;i++) {
+@@ -885,16 +884,6 @@ static struct n_hdlc *n_hdlc_alloc(void)
+ } /* end of n_hdlc_alloc() */
+
+ /**
+- * n_hdlc_buf_list_init - initialize specified HDLC buffer list
+- * @list - pointer to buffer list
+- */
+-static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list)
+-{
+- memset(list, 0, sizeof(*list));
+- spin_lock_init(&list->spinlock);
+-} /* end of n_hdlc_buf_list_init() */
+-
+-/**
+ * n_hdlc_buf_put - add specified HDLC buffer to tail of specified list
+ * @list - pointer to buffer list
+ * @buf - pointer to buffer
+--
+2.1.4
+
diff --git a/CVE-2017-2636-0002-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch b/CVE-2017-2636-0002-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch
new file mode 100644
index 0000000..5c14529
--- /dev/null
+++ b/CVE-2017-2636-0002-tty-n_hdlc-get-rid-of-racy-n_hdlc.tbuf.patch
@@ -0,0 +1,319 @@
+From 579abe8dfc6c4e87577fcd43f7afdf79f2ce6102 Mon Sep 17 00:00:00 2001
+From: Alexander Popov <alex.popov at linux.com>
+Date: Wed, 1 Mar 2017 13:31:58 -0800
+Subject: [PATCH 2/2] tty: n_hdlc: get rid of racy n_hdlc.tbuf
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+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 at linux.com>
+
+CVE-2017-2636
+
+Signed-off-by: Stefan Bader <stefan.bader at canonical.com>
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.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 a7fa016..6d1e2f7 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.1.4
+
--
2.1.4
More information about the pve-devel
mailing list