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 <keesc...@chromium.org>
---
 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 <linux/sched/debug.h>
 #include <linux/slab.h>
 #include <linux/compat.h>
+#include <linux/random.h>
 
 #include <linux/uaccess.h>
 #include <asm/unistd.h>
@@ -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)
-- 
2.7.4


-- 
Kees Cook
Pixel Security

Reply via email to