Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiroslav Benes <mbenes@suse.cz>2018-08-09 16:07:58 +0200
committerMiroslav Benes <mbenes@suse.cz>2018-08-09 16:07:58 +0200
commit493c42402209baa2db698c873fd0f82f2c0c0375 (patch)
treeb94c0eef93111dbd4e3737f2fe693e2f3a5d947b
parentbeeecfaabec1406c96969705e8b3fa9f1329a52f (diff)
parent9b1943850690372e405c2d8b950323d8557336ff (diff)
Merge branch 'bsc#1103203_15' into SLE15_Update_0_EMBARGO
-rw-r--r--bsc1103203/livepatch_bsc1103203.c287
-rw-r--r--bsc1103203/livepatch_bsc1103203.h14
-rw-r--r--bsc1103203/patched_funcs.csv1
-rw-r--r--kallsyms_relocs.c125
-rw-r--r--kallsyms_relocs.h15
-rw-r--r--rpm/kernel-livepatch.spec4
-rwxr-xr-xscripts/register-patches.sh4
-rwxr-xr-xscripts/tar-up.sh2
8 files changed, 450 insertions, 2 deletions
diff --git a/bsc1103203/livepatch_bsc1103203.c b/bsc1103203/livepatch_bsc1103203.c
new file mode 100644
index 0000000..0d9a386
--- /dev/null
+++ b/bsc1103203/livepatch_bsc1103203.c
@@ -0,0 +1,287 @@
+/*
+ * livepatch_bsc1103203
+ *
+ * Fix for CVE-2017-18344, bsc#1103203
+ *
+ * Upstream commit:
+ * cef31d9af9082 ("posix-timer: Properly check sigevent->sigev_notify")
+ *
+ * SLE12 commit:
+ * not affected (no CONFIG_CHECKPOINT_RESTORE, and thus, no show_timer())
+ *
+ * SLE12-SP1 commit
+ * not affected (no CONFIG_CHECKPOINT_RESTORE, and thus, no show_timer())
+ *
+ * SLE12-SP2 commit:
+ * 90635b5f64786d5a7ad9121900a2fd08b94dbfad (stable 4.4.116)
+ *
+ * SLE12-SP3 commit:
+ * 8c5d5ef65b07d2a8f4288f01c3f25053a92a16f9 (stable 4.4.116)
+ *
+ * SLE15 commit:
+ * 2e3fc38c585099b2fed8cc3c1e3b658a0c643c18
+ *
+ *
+ * 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/syscalls.h>
+#include <linux/nospec.h>
+#include <linux/slab.h>
+#include <linux/posix-timers.h>
+#include <linux/hashtable.h>
+#include "livepatch_bsc1103203.h"
+#include "kallsyms_relocs.h"
+
+
+static struct k_clock *klp_clock_posix_dynamic;
+static struct k_clock *klp_clock_posix_cpu;
+static struct k_clock (*klp_posix_clocks)[MAX_CLOCKS];
+static struct kmem_cache **klp_posix_timers_cache;
+static struct hlist_head (*klp_posix_timers_hashtable)[1 << 9];
+static spinlock_t *klp_hash_lock;
+
+static struct task_struct *(*klp_find_task_by_vpid)(pid_t vnr);
+static struct sigqueue *(*klp_sigqueue_alloc)(void);
+static void (*klp_release_posix_timer)(struct k_itimer *tmr, int it_id_set);
+
+static struct klp_kallsyms_reloc klp_funcs[] = {
+ { "clock_posix_dynamic", (void *)&klp_clock_posix_dynamic },
+ { "clock_posix_cpu", (void *)&klp_clock_posix_cpu },
+ { "posix_clocks", (void *)&klp_posix_clocks },
+ { "posix_timers_cache", (void *)&klp_posix_timers_cache },
+ { "posix_timers_hashtable", (void *)&klp_posix_timers_hashtable },
+ { "hash_lock", (void *)&klp_hash_lock, .sympos = 2 },
+ { "find_task_by_vpid", (void *)&klp_find_task_by_vpid },
+ { "sigqueue_alloc", (void *)&klp_sigqueue_alloc },
+ { "release_posix_timer", (void *)&klp_release_posix_timer },
+};
+
+
+
+/* from kernel/time/posix-timers.c */
+/* inlined */
+static int klp_hash(struct signal_struct *sig, unsigned int nr)
+{
+ return hash_32(hash32_ptr(sig) ^ nr, HASH_BITS(*klp_posix_timers_hashtable));
+}
+
+/* inlined */
+static struct k_itimer *klp__posix_timers_find(struct hlist_head *head,
+ struct signal_struct *sig,
+ timer_t id)
+{
+ struct k_itimer *timer;
+
+ hlist_for_each_entry_rcu(timer, head, t_hash) {
+ if ((timer->it_signal == sig) && (timer->it_id == id))
+ return timer;
+ }
+ return NULL;
+}
+
+/* inlined */
+static int klp_posix_timer_add(struct k_itimer *timer)
+{
+ struct signal_struct *sig = current->signal;
+ int first_free_id = sig->posix_timer_id;
+ struct hlist_head *head;
+ int ret = -ENOENT;
+
+ do {
+ spin_lock(klp_hash_lock);
+ head = &(*klp_posix_timers_hashtable)[klp_hash(sig, sig->posix_timer_id)];
+ if (!klp__posix_timers_find(head, sig, sig->posix_timer_id)) {
+ hlist_add_head_rcu(&timer->t_hash, head);
+ ret = sig->posix_timer_id;
+ }
+ if (++sig->posix_timer_id < 0)
+ sig->posix_timer_id = 0;
+ if ((sig->posix_timer_id == first_free_id) && (ret == -ENOENT))
+ /* Loop over all possible ids completed */
+ ret = -EAGAIN;
+ spin_unlock(klp_hash_lock);
+ } while (ret == -ENOENT);
+ return ret;
+}
+
+/* inlined */
+static struct k_itimer * klp_alloc_posix_timer(void)
+{
+ struct k_itimer *tmr;
+ tmr = kmem_cache_zalloc(*klp_posix_timers_cache, GFP_KERNEL);
+ if (!tmr)
+ return tmr;
+ if (unlikely(!(tmr->sigq = klp_sigqueue_alloc()))) {
+ kmem_cache_free(*klp_posix_timers_cache, tmr);
+ return NULL;
+ }
+ memset(&tmr->sigq->info, 0, sizeof(siginfo_t));
+ return tmr;
+}
+
+#define KLP_IT_ID_SET 1
+#define KLP_IT_ID_NOT_SET 0
+
+/* partially inlined */
+static struct k_clock *klp_clockid_to_kclock(const clockid_t id)
+{
+ clockid_t idx = id;
+
+ if (id < 0) {
+ return (id & CLOCKFD_MASK) == CLOCKFD ?
+ klp_clock_posix_dynamic : klp_clock_posix_cpu;
+ }
+
+ if (id >= MAX_CLOCKS)
+ return NULL;
+
+ idx = array_index_nospec(idx, MAX_CLOCKS);
+
+ if (!(*klp_posix_clocks)[idx].clock_getres)
+ return NULL;
+
+ return &(*klp_posix_clocks)[idx];
+}
+
+
+
+/* patched, inlined */
+static struct pid *klp_good_sigevent(sigevent_t * event)
+{
+ struct task_struct *rtn = current->group_leader;
+
+ /*
+ * Fix CVE-2017-18344
+ * -11 lines (all), +16 lines
+ */
+ switch (event->sigev_notify) {
+ case SIGEV_SIGNAL | SIGEV_THREAD_ID:
+ rtn = klp_find_task_by_vpid(event->sigev_notify_thread_id);
+ if (!rtn || !same_thread_group(rtn, current))
+ return NULL;
+ /* FALLTHRU */
+ case SIGEV_SIGNAL:
+ case SIGEV_THREAD:
+ if (event->sigev_signo <= 0 || event->sigev_signo > SIGRTMAX)
+ return NULL;
+ /* FALLTHRU */
+ case SIGEV_NONE:
+ return task_pid(rtn);
+ default:
+ return NULL;
+ }
+}
+
+/* patched, calls good_sigevent() */
+__SYSCALL_DEFINEx(3, _klp_timer_create, const clockid_t, which_clock,
+ struct sigevent __user *, timer_event_spec,
+ timer_t __user *, created_timer_id)
+{
+ struct k_clock *kc = klp_clockid_to_kclock(which_clock);
+ struct k_itimer *new_timer;
+ int error, new_timer_id;
+ sigevent_t event;
+ int it_id_set = KLP_IT_ID_NOT_SET;
+
+ if (!kc)
+ return -EINVAL;
+ if (!kc->timer_create)
+ return -EOPNOTSUPP;
+
+ new_timer = klp_alloc_posix_timer();
+ if (unlikely(!new_timer))
+ return -EAGAIN;
+
+ spin_lock_init(&new_timer->it_lock);
+ new_timer_id = klp_posix_timer_add(new_timer);
+ if (new_timer_id < 0) {
+ error = new_timer_id;
+ goto out;
+ }
+
+ it_id_set = KLP_IT_ID_SET;
+ new_timer->it_id = (timer_t) new_timer_id;
+ new_timer->it_clock = which_clock;
+ new_timer->it_overrun = -1;
+
+ if (timer_event_spec) {
+ if (copy_from_user(&event, timer_event_spec, sizeof (event))) {
+ error = -EFAULT;
+ goto out;
+ }
+ rcu_read_lock();
+ new_timer->it_pid = get_pid(klp_good_sigevent(&event));
+ rcu_read_unlock();
+ if (!new_timer->it_pid) {
+ error = -EINVAL;
+ goto out;
+ }
+ } else {
+ memset(&event.sigev_value, 0, sizeof(event.sigev_value));
+ event.sigev_notify = SIGEV_SIGNAL;
+ event.sigev_signo = SIGALRM;
+ event.sigev_value.sival_int = new_timer->it_id;
+ new_timer->it_pid = get_pid(task_tgid(current));
+ }
+
+ new_timer->it_sigev_notify = event.sigev_notify;
+ new_timer->sigq->info.si_signo = event.sigev_signo;
+ new_timer->sigq->info.si_value = event.sigev_value;
+ new_timer->sigq->info.si_tid = new_timer->it_id;
+ new_timer->sigq->info.si_code = SI_TIMER;
+
+ if (copy_to_user(created_timer_id,
+ &new_timer_id, sizeof (new_timer_id))) {
+ error = -EFAULT;
+ goto out;
+ }
+
+ error = kc->timer_create(new_timer);
+ if (error)
+ goto out;
+
+ spin_lock_irq(&current->sighand->siglock);
+ new_timer->it_signal = current->signal;
+ list_add(&new_timer->list, &current->signal->posix_timers);
+ spin_unlock_irq(&current->sighand->siglock);
+
+ return 0;
+ /*
+ * In the case of the timer belonging to another task, after
+ * the task is unlocked, the timer is owned by the other task
+ * and may cease to exist at any time. Don't use or modify
+ * new_timer after the unlock call.
+ */
+out:
+ klp_release_posix_timer(new_timer, it_id_set);
+ return error;
+}
+/* alias for usedsymbols */
+asmlinkage long klp_sys_timer_create(long which_clock, long timer_event_spec,
+ long created_timer_id)
+ __attribute__((alias("SyS_klp_timer_create")));
+
+
+
+int livepatch_bsc1103203_init(void)
+{
+ return __klp_resolve_kallsyms_relocs(klp_funcs, ARRAY_SIZE(klp_funcs));
+}
diff --git a/bsc1103203/livepatch_bsc1103203.h b/bsc1103203/livepatch_bsc1103203.h
new file mode 100644
index 0000000..5967e61
--- /dev/null
+++ b/bsc1103203/livepatch_bsc1103203.h
@@ -0,0 +1,14 @@
+#ifndef _LIVEPATCH_BSC1103203_H
+#define _LIVEPATCH_BSC1103203_H
+
+#include <linux/types.h>
+
+int livepatch_bsc1103203_init(void);
+static inline void livepatch_bsc1103203_cleanup(void) {}
+
+
+struct sigevent;
+asmlinkage long SyS_klp_timer_create(long which_clock, long timer_event_spec,
+ long created_timer_id);
+
+#endif /* _LIVEPATCH_BSC1103203_H */
diff --git a/bsc1103203/patched_funcs.csv b/bsc1103203/patched_funcs.csv
new file mode 100644
index 0000000..85d8a80
--- /dev/null
+++ b/bsc1103203/patched_funcs.csv
@@ -0,0 +1 @@
+vmlinux SyS_timer_create SyS_klp_timer_create
diff --git a/kallsyms_relocs.c b/kallsyms_relocs.c
new file mode 100644
index 0000000..5347af0
--- /dev/null
+++ b/kallsyms_relocs.c
@@ -0,0 +1,125 @@
+/*
+ * kallsyms_relocs.c - resolve non-exported symbols
+ *
+ * Copyright (C) 2018 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * 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 <linux/kallsyms.h>
+#include "kallsyms_relocs.h"
+
+struct find_args
+{
+ struct klp_kallsyms_reloc reloc;
+ unsigned long match_count;
+};
+
+static int __find_callback(void *data, const char *name,
+ struct module *mod, unsigned long addr)
+{
+ struct find_args *args = data;
+
+ if ((mod && !args->reloc.objname) || (!mod && args->reloc.objname))
+ return 0;
+
+ if (strcmp(args->reloc.symname, name))
+ return 0;
+
+ if (args->reloc.objname && strcmp(args->reloc.objname, mod->name))
+ return 0;
+
+ args->match_count++;
+
+ /*
+ * Finish the search when the symbol is found for the desired
+ * position or the position is not defined.
+ */
+ if (!args->reloc.sympos || args->match_count == args->reloc.sympos) {
+ *args->reloc.addr = (void *)addr;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int (*klp_module_kallsyms_on_each_symbol)(int (*fn)(void *, const char *,
+ struct module *,
+ unsigned long),
+ void *data);
+
+/* Bootstrap: resolve non-exported module_kallsyms_on_each_symbol() */
+static int __kallsyms_relocs_init(void)
+{
+ const char symname[] = "module_kallsyms_on_each_symbol";
+
+ if (klp_module_kallsyms_on_each_symbol)
+ return 0;
+
+ klp_module_kallsyms_on_each_symbol =
+ (void *)kallsyms_lookup_name(symname);
+
+ if (!klp_module_kallsyms_on_each_symbol) {
+ pr_err("livepatch: symbol %s not resolved\n", symname);
+ return -ENOENT;
+ }
+
+ return 0;
+}
+
+/*
+ * Must be called with module_mutex held if any of the relocs'
+ * ->objname can be non-NULL.
+ */
+int __klp_resolve_kallsyms_relocs(struct klp_kallsyms_reloc *relocs,
+ unsigned long count)
+{
+ int ret;
+ unsigned long i;
+ struct find_args args;
+
+ ret = __kallsyms_relocs_init();
+ if (ret)
+ return ret;
+
+ for (i = 0; i < count; ++i) {
+ *relocs[i].addr = NULL;
+ args.reloc = relocs[i];
+ args.match_count = 0;
+
+ if (args.reloc.objname) {
+ klp_module_kallsyms_on_each_symbol(__find_callback,
+ &args);
+ } else {
+ kallsyms_on_each_symbol(__find_callback, &args);
+ }
+
+ if (!*relocs[i].addr) {
+ if (relocs[i].objname) {
+ pr_err("livepatch: symbol %s:%s not resolved\n",
+ relocs[i].objname, relocs[i].symname);
+ } else {
+ pr_err("livepatch: symbol %s not resolved\n",
+ relocs[i].symname);
+ }
+
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
diff --git a/kallsyms_relocs.h b/kallsyms_relocs.h
new file mode 100644
index 0000000..87ae4f5
--- /dev/null
+++ b/kallsyms_relocs.h
@@ -0,0 +1,15 @@
+#ifndef _KLP_KALLSYMS_RELOCS
+#define _KLP_KALLSYMS_RELOCS
+
+struct klp_kallsyms_reloc
+{
+ const char *symname;
+ void **addr;
+ const char *objname;
+ unsigned long sympos;
+};
+
+int __klp_resolve_kallsyms_relocs(struct klp_kallsyms_reloc *relocs,
+ unsigned long count);
+
+#endif /* _KLP_KALLSYMS_RELOCS */
diff --git a/rpm/kernel-livepatch.spec b/rpm/kernel-livepatch.spec
index 5638494..11efeb9 100644
--- a/rpm/kernel-livepatch.spec
+++ b/rpm/kernel-livepatch.spec
@@ -30,6 +30,8 @@ Source2: livepatch_main.c
Source3: config.sh
Source4: source-timestamp
Source5: shadow.h
+Source6: kallsyms_relocs.h
+Source7: kallsyms_relocs.c
@@KLP_PATCHES_SOURCES@@
BuildRequires: kernel-syms kernel-livepatch-tools-devel libelf-devel
ExclusiveArch: ppc64le x86_64
@@ -45,6 +47,8 @@ This is a live patch for SUSE Linux Enterprise Server kernel.
@@KLP_PATCHES_SETUP_SOURCES@@
cp %_sourcedir/livepatch_main.c .
cp %_sourcedir/shadow.h .
+cp %_sourcedir/kallsyms_relocs.h .
+cp %_sourcedir/kallsyms_relocs.c .
cp %_sourcedir/Makefile .
%build
diff --git a/scripts/register-patches.sh b/scripts/register-patches.sh
index 42b56d7..1a53baa 100755
--- a/scripts/register-patches.sh
+++ b/scripts/register-patches.sh
@@ -264,8 +264,8 @@ EOF
# Finish kernel-livepatch.spec:
## Enumerate the per subpatch source *.tar.bz2.
-## Note: Start with Source5
-S=6
+## Note: Start with Source8
+S=8
## First check that none of the to be occupied Source<n> slots has
## been used already.
for i in "${!livepatches[@]}"; do
diff --git a/scripts/tar-up.sh b/scripts/tar-up.sh
index e5d3762..92fe7b4 100755
--- a/scripts/tar-up.sh
+++ b/scripts/tar-up.sh
@@ -74,6 +74,8 @@ source $(dirname $0)/release-version.sh
install -m 644 livepatch_main.c $build_dir
install -m 644 shadow.h $build_dir
+install -m 644 kallsyms_relocs.h $build_dir
+install -m 644 kallsyms_relocs.c $build_dir
install -m 644 rpm/kernel-livepatch.spec $build_dir/kernel-livepatch-"$RELEASE".spec
scripts/register-patches.sh $build_dir/livepatch_main.c $build_dir/kernel-livepatch-"$RELEASE".spec
install -m 644 rpm/config.sh $build_dir/config.sh