From patchwork Tue Aug 8 00:33:45 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kees Cook X-Patchwork-Id: 9886379 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 9E53860363 for ; Tue, 8 Aug 2017 00:35:03 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8FC8C28740 for ; Tue, 8 Aug 2017 00:35:03 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 84C9B28741; Tue, 8 Aug 2017 00:35:03 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.wl.linuxfoundation.org (Postfix) with SMTP id 5F72C28742 for ; Tue, 8 Aug 2017 00:35:01 +0000 (UTC) Received: (qmail 24127 invoked by uid 550); 8 Aug 2017 00:34:03 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Delivered-To: mailing list kernel-hardening@lists.openwall.com Received: (qmail 24067 invoked from network); 8 Aug 2017 00:33:59 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=date:from:to:cc:subject:message-id:mime-version:content-disposition; bh=bVCQfNUFnvXW7DS7OTMiinx3grm3vSSDpOFjYJevofs=; b=NnGs+5yVSrwCR9Ff++7YWwQURYCgtOkXHDg6aVJbHWcCrqLdNTNh3auytLA9Ga9BGr XqNNaaJiS6NsvRJAZbpYF72hakeXJU0Fv4CAu2MyLW/jvLQGLY+boioJPXblrqQHGAB7 CiVVIE0nS7BhGcXoP0QdkBCSUlPi4vXoZDKaY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:mime-version :content-disposition; bh=bVCQfNUFnvXW7DS7OTMiinx3grm3vSSDpOFjYJevofs=; b=EpcH1hkD4ffTpzlcNGozo7yP2TRXCkfU/8XIjaqIvq38isXQZAlcc8F/uqY7ehkFEC X4IqgtDQzWnVnPQEd3Ptw2Ch47tEz4kfD7V6Oy8eixID5ATT09F0AKH30YD40G+jPi6q fcnncVn2jawcEGsBiwXd3tI7hQorbMG9DTWet4fMscZIO3p6jeReMcBukLKzilleHrrv ux/OFZM73AIUriqu2VcwLEBo9OaPh3FoaXECg2tvglyh5WqyKTJ4vaG5PLFdCSUhXG0j d1CqrMPmxKGRamWDegguLkpf5v9a3reqwuM1IoVBxm4jfZNBINFmB2aHVqM3+KWuH9ZM 0PFA== X-Gm-Message-State: AHYfb5iVNdqEw5OdYIAre3MnzjB113kFR6WJBm8G3p036oDePhd76qLJ 7Yrg1BvbGvhzB2tO X-Received: by 10.98.38.68 with SMTP id m65mr2442383pfm.47.1502152427615; Mon, 07 Aug 2017 17:33:47 -0700 (PDT) Date: Mon, 7 Aug 2017 17:33:45 -0700 From: Kees Cook To: kernel-hardening@lists.openwall.com Cc: Thomas Gleixner , John Stultz , Stephen Boyd , linux-kernel@vger.kernel.org Message-ID: <20170808003345.GA107289@beast> MIME-Version: 1.0 Content-Disposition: inline Subject: [kernel-hardening] [RFC][PATCH] timer: Add function-change canary X-Virus-Scanned: ClamAV using ClamSMTP This introduces canaries to struct timer_list in an effort to protect the function callback pointer from getting rewritten during stack or heap overflow attacks. The struct timer_list has become a recent target for security flaw exploitation because it includes the "data" argument in the structure, along with the function callback. This provides attackers with a ROP-like primitive for performing limited kernel function calls without needing all the prerequisites to stage a ROP attack. Recent examples of exploits using struct timer_list attacks: http://www.openwall.com/lists/oss-security/2016/12/06/1 (https://www.exploit-db.com/exploits/40871/) https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html (https://www.exploit-db.com/exploits/41458/) Timers normally have their callback functions initialized either via the setup_timer_*() macros or manually before calls to add_timer(). The per-timer canary gets set in either case, and then checked at timer expiration time before calling the function. Signed-off-by: Kees Cook --- include/linux/timer.h | 6 ++++-- kernel/time/timer.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/include/linux/timer.h b/include/linux/timer.h index e6789b8757d5..9aac0da9d2ec 100644 --- a/include/linux/timer.h +++ b/include/linux/timer.h @@ -16,6 +16,7 @@ struct timer_list { */ struct hlist_node entry; unsigned long expires; + unsigned long canary; void (*function)(unsigned long); unsigned long data; u32 flags; @@ -91,6 +92,7 @@ struct timer_list { void init_timer_key(struct timer_list *timer, unsigned int flags, const char *name, struct lock_class_key *key); +void init_timer_func(struct timer_list *timer, void (*func)(unsigned long)); #ifdef CONFIG_DEBUG_OBJECTS_TIMERS extern void init_timer_on_stack_key(struct timer_list *timer, @@ -140,14 +142,14 @@ static inline void init_timer_on_stack_key(struct timer_list *timer, #define __setup_timer(_timer, _fn, _data, _flags) \ do { \ __init_timer((_timer), (_flags)); \ - (_timer)->function = (_fn); \ + init_timer_func((_timer), (_fn)); \ (_timer)->data = (_data); \ } while (0) #define __setup_timer_on_stack(_timer, _fn, _data, _flags) \ do { \ __init_timer_on_stack((_timer), (_flags)); \ - (_timer)->function = (_fn); \ + init_timer_func((_timer), (_fn)); \ (_timer)->data = (_data); \ } while (0) diff --git a/kernel/time/timer.c b/kernel/time/timer.c index 71ce3f4eead3..bc8ae8ef9106 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -1060,6 +1061,34 @@ int mod_timer(struct timer_list *timer, unsigned long expires) } EXPORT_SYMBOL(mod_timer); +static DEFINE_MUTEX(timer_canary_mutex); +static unsigned long timer_canary __ro_after_init; + +/** + * init_timer_func - set the function used for the timer + * @timer: the timer to be updated + * @func: the function to be called by the timer + * + * This should only be called once per timer creation to set the function. + * Normally used via the setup_timer_*() macros or add_timer(). + */ +void init_timer_func(struct timer_list *timer, void (*func)(unsigned long)) +{ + /* Initialize the global timer canary on first use. */ + if (!timer_canary) { + mutex_lock(&timer_canary_mutex); + if (!timer_canary) + timer_canary = get_random_long(); + mutex_unlock(&timer_canary_mutex); + } + + /* Record timer canary for this timer function. */ + timer->function = func; + timer->canary = (unsigned long)timer->function ^ + (unsigned long)timer ^ timer_canary; +} +EXPORT_SYMBOL(init_timer_func); + /** * add_timer - start a timer * @timer: the timer to be added @@ -1077,6 +1106,7 @@ EXPORT_SYMBOL(mod_timer); void add_timer(struct timer_list *timer) { BUG_ON(timer_pending(timer)); + init_timer_func(timer, timer->function); mod_timer(timer, timer->expires); } EXPORT_SYMBOL(add_timer); @@ -1095,6 +1125,7 @@ void add_timer_on(struct timer_list *timer, int cpu) BUG_ON(timer_pending(timer) || !timer->function); + init_timer_func(timer, timer->function); new_base = get_timer_cpu_base(timer->flags, cpu); /* @@ -1244,6 +1275,7 @@ static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long), unsigned long data) { int count = preempt_count(); + void (*badfn)(unsigned long) = NULL; #ifdef CONFIG_LOCKDEP /* @@ -1265,7 +1297,14 @@ static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long), lock_map_acquire(&lockdep_map); trace_timer_expire_entry(timer); - fn(data); + + /* Make sure the timer function hasn't changed since canary set. */ + if (((unsigned long)fn ^ (unsigned long)timer ^ timer->canary) != + timer_canary) { + badfn = fn; + } else + fn(data); + trace_timer_expire_exit(timer); lock_map_release(&lockdep_map); @@ -1281,6 +1320,10 @@ static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long), */ preempt_count_set(count); } + + WARN_RATELIMIT(badfn, + "timer: corrupt timer function (was %pF)\n", + (void *)((unsigned long)timer ^ timer->canary ^ timer_canary)); } static void expire_timers(struct timer_base *base, struct hlist_head *head)