Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolai Stange <nstange@suse.de>2018-07-13 14:20:14 +0200
committerMiroslav Benes <mbenes@suse.cz>2018-07-19 10:53:43 +0200
commit75352c0134940bcbf6d4f7b5dffbed4e21f1a5ab (patch)
tree54852a461b9cc13ff13f970f110e9a818f72edd7
parent58fddd5116b88802afb88ae66a6cd03998c79595 (diff)
Fix for CVE-2017-11600 ("net/xfrm/xfrm_policy.c does not ensure that the dir value of xfrm_userpolicy_id is XFRM_POLICY_MAX or less")
Live patch for CVE-2017-11600. Upstream commit 7bab09631c2a ("xfrm: policy: check policy direction value"). KLP: CVE-2017-11600 References: bsc#1096564 CVE-2017-11600 Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Miroslav Benes <mbenes@suse.cz>
-rw-r--r--bsc1096564/kgr_patch_bsc1096564.c433
-rw-r--r--bsc1096564/kgr_patch_bsc1096564.h20
2 files changed, 453 insertions, 0 deletions
diff --git a/bsc1096564/kgr_patch_bsc1096564.c b/bsc1096564/kgr_patch_bsc1096564.c
new file mode 100644
index 0000000..53a0935
--- /dev/null
+++ b/bsc1096564/kgr_patch_bsc1096564.c
@@ -0,0 +1,433 @@
+/*
+ * kgraft_patch_bsc1096564
+ *
+ * Fix for CVE-2017-11600, bsc#1096564
+ *
+ * Upstream commit:
+ * 7bab09631c2a ("xfrm: policy: check policy direction value")
+ *
+ * SLE12(-SP1) commit:
+ * 39c1e4a9db9e7efb66fd5d934c03bd2eb21b6577
+ *
+ * SLE12-SP{2,3} commit:
+ * dd78184946acb11118fc8965146b3570c289d152 (stable 4.4.87)
+ *
+ * SLE15 commit:
+ * b96a777439326328ee1dde4df72e737704dd4973 (stable 4.12.11)
+ *
+ * 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/kallsyms.h>
+#include <net/xfrm.h>
+#include <linux/jhash.h>
+#include "kgr_patch_bsc1096564.h"
+
+#if !IS_ENABLED(CONFIG_XFRM)
+#error "Live patch supports only CONFIG_XFRM=y."
+#endif
+
+#if !IS_ENABLED(CONFIG_XFRM_MIGRATE)
+#error "Live patch supports only CONFIG_XFRM_MIGRATE=y."
+#endif
+
+
+static bool
+(*kgr_xfrm_migrate_selector_match)(const struct xfrm_selector *sel_cmp,
+ const struct xfrm_selector *sel_tgt);
+
+static struct {
+ char *name;
+ char **addr;
+} kgr_funcs[] = {
+ { "xfrm_migrate_selector_match",
+ (void *)&kgr_xfrm_migrate_selector_match },
+};
+
+
+/* from net/xfrm/xfrm_hash.h */
+/* inlined */
+static inline u32 kgr__bits2mask32(__u8 bits)
+{
+ u32 mask32 = 0xffffffff;
+
+ if (bits == 0)
+ mask32 = 0;
+ else if (bits < 32)
+ mask32 <<= (32 - bits);
+
+ return mask32;
+}
+
+/* inlined */
+static inline unsigned int
+kgr__xfrm4_dpref_spref_hash(const xfrm_address_t *daddr,
+ const xfrm_address_t *saddr,
+ __u8 dbits,
+ __u8 sbits)
+{
+ return jhash_2words(ntohl(daddr->a4) & kgr__bits2mask32(dbits),
+ ntohl(saddr->a4) & kgr__bits2mask32(sbits),
+ 0);
+}
+
+/* inlined */
+static inline unsigned int kgr__xfrm6_pref_hash(const xfrm_address_t *addr,
+ __u8 prefixlen)
+{
+ int pdw;
+ int pbi;
+ u32 initval = 0;
+
+ pdw = prefixlen >> 5; /* num of whole u32 in prefix */
+ pbi = prefixlen & 0x1f; /* num of bits in incomplete u32 in prefix */
+
+ if (pbi) {
+ __be32 mask;
+
+ mask = htonl((0xffffffff) << (32 - pbi));
+
+ initval = (__force u32)(addr->a6[pdw] & mask);
+ }
+
+ return jhash2((__force u32 *)addr->a6, pdw, initval);
+}
+
+/* inlined */
+static inline unsigned int
+kgr__xfrm6_dpref_spref_hash(const xfrm_address_t *daddr,
+ const xfrm_address_t *saddr,
+ __u8 dbits,
+ __u8 sbits)
+{
+ return kgr__xfrm6_pref_hash(daddr, dbits) ^
+ kgr__xfrm6_pref_hash(saddr, sbits);
+}
+
+/* inlined */
+static inline unsigned int kgr__addr_hash(const xfrm_address_t *daddr,
+ const xfrm_address_t *saddr,
+ unsigned short family,
+ unsigned int hmask,
+ u8 dbits, u8 sbits)
+{
+ unsigned int h = 0;
+
+ switch (family) {
+ case AF_INET:
+ h = kgr__xfrm4_dpref_spref_hash(daddr, saddr, dbits, sbits);
+ break;
+
+ case AF_INET6:
+ h = kgr__xfrm6_dpref_spref_hash(daddr, saddr, dbits, sbits);
+ break;
+ }
+ h ^= (h >> 16);
+ return h & hmask;
+}
+
+
+/* from net/xfrm/xfrm_policy.c */
+/* inlined */
+static void kgr__get_hash_thresh(struct net *net,
+ unsigned short family, int dir,
+ u8 *dbits, u8 *sbits)
+{
+ switch (family) {
+ case AF_INET:
+ *dbits = net->xfrm.policy_bydst[dir].dbits4;
+ *sbits = net->xfrm.policy_bydst[dir].sbits4;
+ break;
+
+ case AF_INET6:
+ *dbits = net->xfrm.policy_bydst[dir].dbits6;
+ *sbits = net->xfrm.policy_bydst[dir].sbits6;
+ break;
+
+ default:
+ *dbits = 0;
+ *sbits = 0;
+ }
+}
+
+/* inlined */
+static struct hlist_head *kgr_policy_hash_direct(struct net *net,
+ const xfrm_address_t *daddr,
+ const xfrm_address_t *saddr,
+ unsigned short family, int dir)
+{
+ unsigned int hmask = net->xfrm.policy_bydst[dir].hmask;
+ unsigned int hash;
+ u8 dbits;
+ u8 sbits;
+
+ kgr__get_hash_thresh(net, family, dir, &dbits, &sbits);
+ hash = kgr__addr_hash(daddr, saddr, family, hmask, dbits, sbits);
+
+ return net->xfrm.policy_bydst[dir].table + hash;
+}
+
+/* inlined */
+static struct xfrm_policy *
+kgr_xfrm_migrate_policy_find(const struct xfrm_selector *sel,
+ u8 dir, u8 type, struct net *net)
+{
+ struct xfrm_policy *pol, *ret = NULL;
+ struct hlist_head *chain;
+ u32 priority = ~0U;
+
+ read_lock_bh(&net->xfrm.xfrm_policy_lock); /*FIXME*/
+ chain = kgr_policy_hash_direct(net, &sel->daddr, &sel->saddr, sel->family, dir);
+ hlist_for_each_entry(pol, chain, bydst) {
+ if (kgr_xfrm_migrate_selector_match(sel, &pol->selector) &&
+ pol->type == type) {
+ ret = pol;
+ priority = ret->priority;
+ break;
+ }
+ }
+ chain = &net->xfrm.policy_inexact[dir];
+ hlist_for_each_entry(pol, chain, bydst) {
+ if ((pol->priority >= priority) && ret)
+ break;
+
+ if (kgr_xfrm_migrate_selector_match(sel, &pol->selector) &&
+ pol->type == type) {
+ ret = pol;
+ break;
+ }
+ }
+
+ xfrm_pol_hold(ret);
+
+ read_unlock_bh(&net->xfrm.xfrm_policy_lock);
+
+ return ret;
+}
+
+/* inlined */
+static int kgr_migrate_tmpl_match(const struct xfrm_migrate *m,
+ const struct xfrm_tmpl *t)
+{
+ int match = 0;
+
+ if (t->mode == m->mode && t->id.proto == m->proto &&
+ (m->reqid == 0 || t->reqid == m->reqid)) {
+ switch (t->mode) {
+ case XFRM_MODE_TUNNEL:
+ case XFRM_MODE_BEET:
+ if (xfrm_addr_equal(&t->id.daddr, &m->old_daddr,
+ m->old_family) &&
+ xfrm_addr_equal(&t->saddr, &m->old_saddr,
+ m->old_family)) {
+ match = 1;
+ }
+ break;
+ case XFRM_MODE_TRANSPORT:
+ /* in case of transport mode, template does not store
+ any IP addresses, hence we just compare mode and
+ protocol */
+ match = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ return match;
+}
+
+/* inlined */
+static int kgr_xfrm_policy_migrate(struct xfrm_policy *pol,
+ struct xfrm_migrate *m, int num_migrate)
+{
+ struct xfrm_migrate *mp;
+ int i, j, n = 0;
+
+ write_lock_bh(&pol->lock);
+ if (unlikely(pol->walk.dead)) {
+ /* target policy has been deleted */
+ write_unlock_bh(&pol->lock);
+ return -ENOENT;
+ }
+
+ for (i = 0; i < pol->xfrm_nr; i++) {
+ for (j = 0, mp = m; j < num_migrate; j++, mp++) {
+ if (!kgr_migrate_tmpl_match(mp, &pol->xfrm_vec[i]))
+ continue;
+ n++;
+ if (pol->xfrm_vec[i].mode != XFRM_MODE_TUNNEL &&
+ pol->xfrm_vec[i].mode != XFRM_MODE_BEET)
+ continue;
+ /* update endpoints */
+ memcpy(&pol->xfrm_vec[i].id.daddr, &mp->new_daddr,
+ sizeof(pol->xfrm_vec[i].id.daddr));
+ memcpy(&pol->xfrm_vec[i].saddr, &mp->new_saddr,
+ sizeof(pol->xfrm_vec[i].saddr));
+ pol->xfrm_vec[i].encap_family = mp->new_family;
+ /* flush bundles */
+ atomic_inc(&pol->genid);
+ }
+ }
+
+ write_unlock_bh(&pol->lock);
+
+ if (!n)
+ return -ENODATA;
+
+ return 0;
+}
+
+/* inlined */
+static int kgr_xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate)
+{
+ int i, j;
+
+ if (num_migrate < 1 || num_migrate > XFRM_MAX_DEPTH)
+ return -EINVAL;
+
+ for (i = 0; i < num_migrate; i++) {
+ if (xfrm_addr_equal(&m[i].old_daddr, &m[i].new_daddr,
+ m[i].old_family) &&
+ xfrm_addr_equal(&m[i].old_saddr, &m[i].new_saddr,
+ m[i].old_family))
+ return -EINVAL;
+ if (xfrm_addr_any(&m[i].new_daddr, m[i].new_family) ||
+ xfrm_addr_any(&m[i].new_saddr, m[i].new_family))
+ return -EINVAL;
+
+ /* check if there is any duplicated entry */
+ for (j = i + 1; j < num_migrate; j++) {
+ if (!memcmp(&m[i].old_daddr, &m[j].old_daddr,
+ sizeof(m[i].old_daddr)) &&
+ !memcmp(&m[i].old_saddr, &m[j].old_saddr,
+ sizeof(m[i].old_saddr)) &&
+ m[i].proto == m[j].proto &&
+ m[i].mode == m[j].mode &&
+ m[i].reqid == m[j].reqid &&
+ m[i].old_family == m[j].old_family)
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+
+
+/* patched */
+int kgr_xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
+ struct xfrm_migrate *m, int num_migrate,
+ struct xfrm_kmaddress *k, struct net *net)
+{
+ int i, err, nx_cur = 0, nx_new = 0;
+ struct xfrm_policy *pol = NULL;
+ struct xfrm_state *x, *xc;
+ struct xfrm_state *x_cur[XFRM_MAX_DEPTH];
+ struct xfrm_state *x_new[XFRM_MAX_DEPTH];
+ struct xfrm_migrate *mp;
+
+ if ((err = kgr_xfrm_migrate_check(m, num_migrate)) < 0)
+ goto out;
+
+ /*
+ * Fix CVE-2017-11600
+ * +5 lines
+ */
+ if (dir >= XFRM_POLICY_MAX) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* Stage 1 - find policy */
+ if ((pol = kgr_xfrm_migrate_policy_find(sel, dir, type, net)) == NULL) {
+ err = -ENOENT;
+ goto out;
+ }
+
+ /* Stage 2 - find and update state(s) */
+ for (i = 0, mp = m; i < num_migrate; i++, mp++) {
+ if ((x = xfrm_migrate_state_find(mp, net))) {
+ x_cur[nx_cur] = x;
+ nx_cur++;
+ if ((xc = xfrm_state_migrate(x, mp))) {
+ x_new[nx_new] = xc;
+ nx_new++;
+ } else {
+ err = -ENODATA;
+ goto restore_state;
+ }
+ }
+ }
+
+ /* Stage 3 - update policy */
+ if ((err = kgr_xfrm_policy_migrate(pol, m, num_migrate)) < 0)
+ goto restore_state;
+
+ /* Stage 4 - delete old state(s) */
+ if (nx_cur) {
+ xfrm_states_put(x_cur, nx_cur);
+ xfrm_states_delete(x_cur, nx_cur);
+ }
+
+ /* Stage 5 - announce */
+ km_migrate(sel, dir, type, m, num_migrate, k);
+
+ xfrm_pol_put(pol);
+
+ return 0;
+out:
+ return err;
+
+restore_state:
+ if (pol)
+ xfrm_pol_put(pol);
+ if (nx_cur)
+ xfrm_states_put(x_cur, nx_cur);
+ if (nx_new)
+ xfrm_states_delete(x_new, nx_new);
+
+ return err;
+}
+
+
+
+static int kgr_patch_bsc1096564_kallsyms(void)
+{
+ unsigned long addr;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(kgr_funcs); i++) {
+ addr = kallsyms_lookup_name(kgr_funcs[i].name);
+ if (!addr) {
+ pr_err("kgraft-patch: symbol %s not resolved\n",
+ kgr_funcs[i].name);
+ return -ENOENT;
+ }
+
+ *(kgr_funcs[i].addr) = (void *)addr;
+ }
+
+ return 0;
+}
+
+int kgr_patch_bsc1096564_init(void)
+{
+ return kgr_patch_bsc1096564_kallsyms();
+}
diff --git a/bsc1096564/kgr_patch_bsc1096564.h b/bsc1096564/kgr_patch_bsc1096564.h
new file mode 100644
index 0000000..ca15151
--- /dev/null
+++ b/bsc1096564/kgr_patch_bsc1096564.h
@@ -0,0 +1,20 @@
+#ifndef _KGR_PATCH_BSC1096564_H
+#define _KGR_PATCH_BSC1096564_H
+
+int kgr_patch_bsc1096564_init(void);
+static inline void kgr_patch_bsc1096564_cleanup(void) {}
+
+
+struct xfrm_selector;
+struct xfrm_migrate;
+struct xfrm_kmaddress;
+struct net;
+
+int kgr_xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type,
+ struct xfrm_migrate *m, int num_migrate,
+ struct xfrm_kmaddress *k, struct net *net);
+
+#define KGR_PATCH_BSC1096564_FUNCS \
+ KGR_PATCH(xfrm_migrate, kgr_xfrm_migrate), \
+
+#endif