Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolai Stange <nstange@suse.de>2018-12-11 11:43:18 +0100
committerMiroslav Benes <mbenes@suse.cz>2018-12-11 13:01:33 +0100
commitd028cf922be57ca53bf87a7e5b4b1785c9176086 (patch)
treefd3ac5d350f861ebe9843dc43d6ad1e71ea6310e
parent76fa1a42d05db3cb21e3fbc0b5ccece8dee12f7e (diff)
Fix for CVE-2018-9568 ("Memory corruption due to incorrect socket cloning")
Live patch for CVE-2018-9568. Upstream commit 9d538fa60bad ("net: Set sk_prot_creator when cloning sockets to the right proto"). KLP: CVE-2018-9568 References: bsc#1118320 CVE-2018-9568 Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Miroslav Benes <mbenes@suse.cz>
-rw-r--r--bsc1118320/livepatch_bsc1118320.c299
-rw-r--r--bsc1118320/livepatch_bsc1118320.h12
-rw-r--r--bsc1118320/patched_funcs.csv1
3 files changed, 312 insertions, 0 deletions
diff --git a/bsc1118320/livepatch_bsc1118320.c b/bsc1118320/livepatch_bsc1118320.c
new file mode 100644
index 0000000..987fad3
--- /dev/null
+++ b/bsc1118320/livepatch_bsc1118320.c
@@ -0,0 +1,299 @@
+/*
+ * livepatch_bsc1118320
+ *
+ * Fix for CVE-2018-9568, bsc#1118320
+ *
+ * Upstream commit:
+ * 9d538fa60bad ("net: Set sk_prot_creator when cloning sockets to the right
+ * proto")
+ *
+ * SLE12(-SP1) commit:
+ * none yet
+ *
+ * SLE12-SP2 commit:
+ * 3d8a2d3174f8750efa28a14a3c88f6fa0b7e5284 ("Linux 4.4.94")
+ *
+ * SLE12-SP3 commit:
+ * 775f001a8be5c0326957b16a11606abe5bfa6e88 ("Linux 4.4.94")
+ *
+ * SLE15 commit:
+ * 2132a94048a0ec77a12b9423e4537a205e97a53c
+ *
+ *
+ * Copyright (c) 2018 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <net/sock.h>
+#include <net/net_namespace.h>
+#include <linux/security.h>
+#include "livepatch_bsc1118320.h"
+#include "kallsyms_relocs.h"
+
+#if IS_ENABLED(CONFIG_LOCKDEP)
+#error "Live patch supports only CONFIG_LOCKDEP=n."
+#endif
+
+/*
+ * These are declared, but not looked up: for CONFIG_LOCKDEP=n, they
+ * aren't actually read, but have to be usable in array dereference
+ * resp. pointer arithmetic expressions. In order to improve
+ * compile-time checking, declare these to be arrays of a dummy type
+ * incompatible with everything. Also, the arrays themselves are kept
+ * incomplete, such that they can't get allocated.
+ */
+struct klp_dummy_type {};
+static const struct klp_dummy_type klp_af_family_keys[];
+static const struct klp_dummy_type klp_af_family_kern_keys[];
+static const struct klp_dummy_type klp_af_family_slock_keys[];
+static const struct klp_dummy_type klp_af_family_kern_slock_keys[];
+static const struct klp_dummy_type klp_af_family_key_strings[];
+static const struct klp_dummy_type klp_af_family_slock_key_strings[];
+static const struct klp_dummy_type klp_af_family_clock_key_strings[];
+static const struct klp_dummy_type klp_af_family_kern_key_strings[];
+static const struct klp_dummy_type klp_af_family_kern_slock_key_strings[];
+static const struct klp_dummy_type klp_af_family_rlock_key_strings[];
+static const struct klp_dummy_type klp_af_family_wlock_key_strings[];
+static const struct klp_dummy_type klp_af_family_elock_key_strings[];
+static const struct klp_dummy_type klp_af_callback_keys[];
+static const struct klp_dummy_type klp_af_rlock_keys[];
+static const struct klp_dummy_type klp_af_wlock_keys[];
+static const struct klp_dummy_type klp_af_elock_keys[];
+
+static struct sock *(*klp_sk_prot_alloc)(struct proto *prot, gfp_t priority,
+ int family);
+static void (*klp_mem_cgroup_sk_alloc)(struct sock *sk);
+static void (*klp_cgroup_sk_alloc)(struct sock_cgroup_data *skcd);
+static bool (*klp_sk_filter_charge)(struct sock *sk, struct sk_filter *fp);
+static int (*klp__xfrm_sk_clone_policy)(struct sock *sk,
+ const struct sock *osk);
+
+static struct klp_kallsyms_reloc klp_funcs[] = {
+ { "sk_prot_alloc", (void *)&klp_sk_prot_alloc },
+ { "mem_cgroup_sk_alloc", (void *)&klp_mem_cgroup_sk_alloc },
+ { "cgroup_sk_alloc", (void *)&klp_cgroup_sk_alloc },
+ { "sk_filter_charge", (void *)&klp_sk_filter_charge },
+ { "__xfrm_sk_clone_policy", (void *)&klp__xfrm_sk_clone_policy },
+};
+
+
+
+/* from include/net/xfrm.h */
+/* resolve reference to __xfrm_sk_clone_policy */
+static inline int klp_xfrm_sk_clone_policy(struct sock *sk,
+ const struct sock *osk)
+{
+ sk->sk_policy[0] = NULL;
+ sk->sk_policy[1] = NULL;
+ if (unlikely(osk->sk_policy[0] || osk->sk_policy[1]))
+ return klp__xfrm_sk_clone_policy(sk, osk);
+ return 0;
+}
+
+
+/* from net/core/sock.c */
+/* inlined */
+static bool klp_sock_needs_netstamp(const struct sock *sk)
+{
+ switch (sk->sk_family) {
+ case AF_UNSPEC:
+ case AF_UNIX:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/* inlined */
+static inline void klp_sock_lock_init(struct sock *sk)
+{
+ if (sk->sk_kern_sock)
+ sock_lock_init_class_and_name(
+ sk,
+ klp_af_family_kern_slock_key_strings[sk->sk_family],
+ klp_af_family_kern_slock_keys + sk->sk_family,
+ klp_af_family_kern_key_strings[sk->sk_family],
+ klp_af_family_kern_keys + sk->sk_family);
+ else
+ sock_lock_init_class_and_name(
+ sk,
+ klp_af_family_slock_key_strings[sk->sk_family],
+ klp_af_family_slock_keys + sk->sk_family,
+ klp_af_family_key_strings[sk->sk_family],
+ klp_af_family_keys + sk->sk_family);
+}
+
+/* inlined */
+static void klp_sock_copy(struct sock *nsk, const struct sock *osk)
+{
+#ifdef CONFIG_SECURITY_NETWORK
+ void *sptr = nsk->sk_security;
+#endif
+ memcpy(nsk, osk, offsetof(struct sock, sk_dontcopy_begin));
+
+ memcpy(&nsk->sk_dontcopy_end, &osk->sk_dontcopy_end,
+ osk->sk_prot->obj_size - offsetof(struct sock, sk_dontcopy_end));
+
+#ifdef CONFIG_SECURITY_NETWORK
+ nsk->sk_security = sptr;
+ security_sk_clone(osk, nsk);
+#endif
+}
+
+/* inlined */
+static void klp_sk_init_common(struct sock *sk)
+{
+ skb_queue_head_init(&sk->sk_receive_queue);
+ skb_queue_head_init(&sk->sk_write_queue);
+ skb_queue_head_init(&sk->sk_error_queue);
+
+ rwlock_init(&sk->sk_callback_lock);
+ lockdep_set_class_and_name(&sk->sk_receive_queue.lock,
+ klp_af_rlock_keys + sk->sk_family,
+ klp_af_family_rlock_key_strings[sk->sk_family]);
+ lockdep_set_class_and_name(&sk->sk_write_queue.lock,
+ klp_af_wlock_keys + sk->sk_family,
+ klp_af_family_wlock_key_strings[sk->sk_family]);
+ lockdep_set_class_and_name(&sk->sk_error_queue.lock,
+ klp_af_elock_keys + sk->sk_family,
+ klp_af_family_elock_key_strings[sk->sk_family]);
+ lockdep_set_class_and_name(&sk->sk_callback_lock,
+ klp_af_callback_keys + sk->sk_family,
+ klp_af_family_clock_key_strings[sk->sk_family]);
+}
+
+
+
+/* patched */
+struct sock *klp_sk_clone_lock(const struct sock *sk, const gfp_t priority)
+{
+ struct sock *newsk;
+ bool is_charged = true;
+
+ newsk = klp_sk_prot_alloc(sk->sk_prot, priority, sk->sk_family);
+ if (newsk != NULL) {
+ struct sk_filter *filter;
+
+ klp_sock_copy(newsk, sk);
+
+ /*
+ * Fix CVE-2018-9568
+ * +2 lines
+ */
+ newsk->sk_prot_creator = sk->sk_prot;
+
+ /* SANITY */
+ if (likely(newsk->sk_net_refcnt))
+ get_net(sock_net(newsk));
+ sk_node_init(&newsk->sk_node);
+ klp_sock_lock_init(newsk);
+ bh_lock_sock(newsk);
+ newsk->sk_backlog.head = newsk->sk_backlog.tail = NULL;
+ newsk->sk_backlog.len = 0;
+
+ atomic_set(&newsk->sk_rmem_alloc, 0);
+ /*
+ * sk_wmem_alloc set to one (see sk_free() and sock_wfree())
+ */
+ atomic_set(&newsk->sk_wmem_alloc, 1);
+ atomic_set(&newsk->sk_omem_alloc, 0);
+ klp_sk_init_common(newsk);
+
+ newsk->sk_dst_cache = NULL;
+ newsk->sk_dst_pending_confirm = 0;
+ newsk->sk_wmem_queued = 0;
+ newsk->sk_forward_alloc = 0;
+ atomic_set(&newsk->sk_drops, 0);
+ newsk->sk_send_head = NULL;
+ newsk->sk_userlocks = sk->sk_userlocks & ~SOCK_BINDPORT_LOCK;
+
+ sock_reset_flag(newsk, SOCK_DONE);
+ klp_mem_cgroup_sk_alloc(newsk);
+ klp_cgroup_sk_alloc(&newsk->sk_cgrp_data);
+
+ rcu_read_lock();
+ filter = rcu_dereference(sk->sk_filter);
+ if (filter != NULL)
+ /* though it's an empty new sock, the charging may fail
+ * if sysctl_optmem_max was changed between creation of
+ * original socket and cloning
+ */
+ is_charged = klp_sk_filter_charge(newsk, filter);
+ RCU_INIT_POINTER(newsk->sk_filter, filter);
+ rcu_read_unlock();
+
+ if (unlikely(!is_charged || klp_xfrm_sk_clone_policy(newsk, sk))) {
+ /* We need to make sure that we don't uncharge the new
+ * socket if we couldn't charge it in the first place
+ * as otherwise we uncharge the parent's filter.
+ */
+ if (!is_charged)
+ RCU_INIT_POINTER(newsk->sk_filter, NULL);
+ sk_free_unlock_clone(newsk);
+ newsk = NULL;
+ goto out;
+ }
+ RCU_INIT_POINTER(newsk->sk_reuseport_cb, NULL);
+
+ newsk->sk_err = 0;
+ newsk->sk_err_soft = 0;
+ newsk->sk_priority = 0;
+ newsk->sk_incoming_cpu = raw_smp_processor_id();
+ atomic64_set(&newsk->sk_cookie, 0);
+
+ /*
+ * Before updating sk_refcnt, we must commit prior changes to memory
+ * (Documentation/RCU/rculist_nulls.txt for details)
+ */
+ smp_wmb();
+ atomic_set(&newsk->sk_refcnt, 2);
+
+ /*
+ * Increment the counter in the same struct proto as the master
+ * sock (sk_refcnt_debug_inc uses newsk->sk_prot->socks, that
+ * is the same as sk->sk_prot->socks, as this field was copied
+ * with memcpy).
+ *
+ * This _changes_ the previous behaviour, where
+ * tcp_create_openreq_child always was incrementing the
+ * equivalent to tcp_prot->socks (inet_sock_nr), so this have
+ * to be taken into account in all callers. -acme
+ */
+ sk_refcnt_debug_inc(newsk);
+ sk_set_socket(newsk, NULL);
+ newsk->sk_wq = NULL;
+
+ if (newsk->sk_prot->sockets_allocated)
+ sk_sockets_allocated_inc(newsk);
+
+ if (klp_sock_needs_netstamp(sk) &&
+ newsk->sk_flags & SK_FLAGS_TIMESTAMP)
+ net_enable_timestamp();
+ }
+out:
+ return newsk;
+}
+
+
+
+int livepatch_bsc1118320_init(void)
+{
+ return __klp_resolve_kallsyms_relocs(klp_funcs, ARRAY_SIZE(klp_funcs));
+}
diff --git a/bsc1118320/livepatch_bsc1118320.h b/bsc1118320/livepatch_bsc1118320.h
new file mode 100644
index 0000000..46cf7a4
--- /dev/null
+++ b/bsc1118320/livepatch_bsc1118320.h
@@ -0,0 +1,12 @@
+#ifndef _LIVEPATCH_BSC1118320_H
+#define _LIVEPATCH_BSC1118320_H
+
+int livepatch_bsc1118320_init(void);
+static inline void livepatch_bsc1118320_cleanup(void) {}
+
+
+struct sock;
+
+struct sock *klp_sk_clone_lock(const struct sock *sk, const gfp_t priority);
+
+#endif /* _LIVEPATCH_BSC1118320_H */
diff --git a/bsc1118320/patched_funcs.csv b/bsc1118320/patched_funcs.csv
new file mode 100644
index 0000000..9b9f428
--- /dev/null
+++ b/bsc1118320/patched_funcs.csv
@@ -0,0 +1 @@
+vmlinux sk_clone_lock klp_sk_clone_lock