UBSAN: run-time undefined behavior sanity checker
authorAndrey Ryabinin <aryabinin@virtuozzo.com>
Wed, 20 Jan 2016 23:00:55 +0000 (15:00 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 21 Jan 2016 01:09:18 +0000 (17:09 -0800)
UBSAN uses compile-time instrumentation to catch undefined behavior
(UB).  Compiler inserts code that perform certain kinds of checks before
operations that could cause UB.  If check fails (i.e.  UB detected)
__ubsan_handle_* function called to print error message.

So the most of the work is done by compiler.  This patch just implements
ubsan handlers printing errors.

GCC has this capability since 4.9.x [1] (see -fsanitize=undefined
option and its suboptions).
However GCC 5.x has more checkers implemented [2].
Article [3] has a bit more details about UBSAN in the GCC.

[1] - https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Debugging-Options.html
[2] - https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html
[3] - http://developerblog.redhat.com/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/

Issues which UBSAN has found thus far are:

Found bugs:

 * out-of-bounds access - 97840cb67ff5 ("netfilter: nfnetlink: fix
   insufficient validation in nfnetlink_bind")

undefined shifts:

 * d48458d4a768 ("jbd2: use a better hash function for the revoke
   table")

 * 10632008b9e1 ("clockevents: Prevent shift out of bounds")

 * 'x << -1' shift in ext4 -
   http://lkml.kernel.org/r/<5444EF21.8020501@samsung.com>

 * undefined rol32(0) -
   http://lkml.kernel.org/r/<1449198241-20654-1-git-send-email-sasha.levin@oracle.com>

 * undefined dirty_ratelimit calculation -
   http://lkml.kernel.org/r/<566594E2.3050306@odin.com>

 * undefined roundown_pow_of_two(0) -
   http://lkml.kernel.org/r/<1449156616-11474-1-git-send-email-sasha.levin@oracle.com>

 * [WONTFIX] undefined shift in __bpf_prog_run -
   http://lkml.kernel.org/r/<CACT4Y+ZxoR3UjLgcNdUm4fECLMx2VdtfrENMtRRCdgHB2n0bJA@mail.gmail.com>

   WONTFIX here because it should be fixed in bpf program, not in kernel.

signed overflows:

 * 32a8df4e0b33f ("sched: Fix odd values in effective_load()
   calculations")

 * mul overflow in ntp -
   http://lkml.kernel.org/r/<1449175608-1146-1-git-send-email-sasha.levin@oracle.com>

 * incorrect conversion into rtc_time in rtc_time64_to_tm() -
   http://lkml.kernel.org/r/<1449187944-11730-1-git-send-email-sasha.levin@oracle.com>

 * unvalidated timespec in io_getevents() -
   http://lkml.kernel.org/r/<CACT4Y+bBxVYLQ6LtOKrKtnLthqLHcw-BMp3aqP3mjdAvr9FULQ@mail.gmail.com>

 * [NOTABUG] signed overflow in ktime_add_safe() -
   http://lkml.kernel.org/r/<CACT4Y+aJ4muRnWxsUe1CMnA6P8nooO33kwG-c8YZg=0Xc8rJqw@mail.gmail.com>

[akpm@linux-foundation.org: fix unused local warning]
[akpm@linux-foundation.org: fix __int128 build woes]
Signed-off-by: Andrey Ryabinin <aryabinin@virtuozzo.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Sasha Levin <sasha.levin@oracle.com>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Michal Marek <mmarek@suse.cz>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Yury Gribov <y.gribov@samsung.com>
Cc: Dmitry Vyukov <dvyukov@google.com>
Cc: Konstantin Khlebnikov <koct9i@gmail.com>
Cc: Kostya Serebryany <kcc@google.com>
Cc: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
17 files changed:
Documentation/ubsan.txt [new file with mode: 0644]
Makefile
arch/x86/Kconfig
arch/x86/boot/Makefile
arch/x86/boot/compressed/Makefile
arch/x86/entry/vdso/Makefile
arch/x86/realmode/rm/Makefile
drivers/firmware/efi/libstub/Makefile
include/linux/sched.h
lib/Kconfig.debug
lib/Kconfig.ubsan [new file with mode: 0644]
lib/Makefile
lib/ubsan.c [new file with mode: 0644]
lib/ubsan.h [new file with mode: 0644]
mm/kasan/Makefile
scripts/Makefile.lib
scripts/Makefile.ubsan [new file with mode: 0644]

diff --git a/Documentation/ubsan.txt b/Documentation/ubsan.txt
new file mode 100644 (file)
index 0000000..f58215e
--- /dev/null
@@ -0,0 +1,84 @@
+Undefined Behavior Sanitizer - UBSAN
+
+Overview
+--------
+
+UBSAN is a runtime undefined behaviour checker.
+
+UBSAN uses compile-time instrumentation to catch undefined behavior (UB).
+Compiler inserts code that perform certain kinds of checks before operations
+that may cause UB. If check fails (i.e. UB detected) __ubsan_handle_*
+function called to print error message.
+
+GCC has that feature since 4.9.x [1] (see -fsanitize=undefined option and
+its suboptions). GCC 5.x has more checkers implemented [2].
+
+Report example
+---------------
+
+        ================================================================================
+        UBSAN: Undefined behaviour in ../include/linux/bitops.h:110:33
+        shift exponent 32 is to large for 32-bit type 'unsigned int'
+        CPU: 0 PID: 0 Comm: swapper Not tainted 4.4.0-rc1+ #26
+         0000000000000000 ffffffff82403cc8 ffffffff815e6cd6 0000000000000001
+         ffffffff82403cf8 ffffffff82403ce0 ffffffff8163a5ed 0000000000000020
+         ffffffff82403d78 ffffffff8163ac2b ffffffff815f0001 0000000000000002
+        Call Trace:
+         [<ffffffff815e6cd6>] dump_stack+0x45/0x5f
+         [<ffffffff8163a5ed>] ubsan_epilogue+0xd/0x40
+         [<ffffffff8163ac2b>] __ubsan_handle_shift_out_of_bounds+0xeb/0x130
+         [<ffffffff815f0001>] ? radix_tree_gang_lookup_slot+0x51/0x150
+         [<ffffffff8173c586>] _mix_pool_bytes+0x1e6/0x480
+         [<ffffffff83105653>] ? dmi_walk_early+0x48/0x5c
+         [<ffffffff8173c881>] add_device_randomness+0x61/0x130
+         [<ffffffff83105b35>] ? dmi_save_one_device+0xaa/0xaa
+         [<ffffffff83105653>] dmi_walk_early+0x48/0x5c
+         [<ffffffff831066ae>] dmi_scan_machine+0x278/0x4b4
+         [<ffffffff8111d58a>] ? vprintk_default+0x1a/0x20
+         [<ffffffff830ad120>] ? early_idt_handler_array+0x120/0x120
+         [<ffffffff830b2240>] setup_arch+0x405/0xc2c
+         [<ffffffff830ad120>] ? early_idt_handler_array+0x120/0x120
+         [<ffffffff830ae053>] start_kernel+0x83/0x49a
+         [<ffffffff830ad120>] ? early_idt_handler_array+0x120/0x120
+         [<ffffffff830ad386>] x86_64_start_reservations+0x2a/0x2c
+         [<ffffffff830ad4f3>] x86_64_start_kernel+0x16b/0x17a
+        ================================================================================
+
+Usage
+-----
+
+To enable UBSAN configure kernel with:
+
+       CONFIG_UBSAN=y
+
+and to check the entire kernel:
+
+        CONFIG_UBSAN_SANITIZE_ALL=y
+
+To enable instrumentation for specific files or directories, add a line
+similar to the following to the respective kernel Makefile:
+
+        For a single file (e.g. main.o):
+                UBSAN_SANITIZE_main.o := y
+
+        For all files in one directory:
+                UBSAN_SANITIZE := y
+
+To exclude files from being instrumented even if
+CONFIG_UBSAN_SANITIZE_ALL=y, use:
+
+                UBSAN_SANITIZE_main.o := n
+        and:
+                UBSAN_SANITIZE := n
+
+Detection of unaligned accesses controlled through the separate option -
+CONFIG_UBSAN_ALIGNMENT. It's off by default on architectures that support
+unaligned accesses (CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y). One could
+still enable it in config, just note that it will produce a lot of UBSAN
+reports.
+
+References
+----------
+
+[1] - https://gcc.gnu.org/onlinedocs/gcc-4.9.0/gcc/Debugging-Options.html
+[2] - https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html
index 7f4ac1ee4a2b1359276b7f49b87008a5b4b6b413..abfb3e8eb0b14e680290f3800bd331e870cbb8c6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -411,7 +411,7 @@ export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
 export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
 
 export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
-export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
+export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN CFLAGS_UBSAN
 export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
 export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
 export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
@@ -784,6 +784,7 @@ endif
 
 include scripts/Makefile.kasan
 include scripts/Makefile.extrawarn
+include scripts/Makefile.ubsan
 
 # Add any arch overrides and user supplied CPPFLAGS, AFLAGS and CFLAGS as the
 # last assignments
index 4a10ba9e95daac1842b4fdd93ae814d07b444eeb..92b2a73162ee7650a33e96d3a93878d79ff3420f 100644 (file)
@@ -31,6 +31,7 @@ config X86
        select ARCH_HAS_PMEM_API                if X86_64
        select ARCH_HAS_MMIO_FLUSH
        select ARCH_HAS_SG_CHAIN
+       select ARCH_HAS_UBSAN_SANITIZE_ALL
        select ARCH_HAVE_NMI_SAFE_CMPXCHG
        select ARCH_MIGHT_HAVE_ACPI_PDC         if ACPI
        select ARCH_MIGHT_HAVE_PC_PARPORT
index 2ee62dba0373b059664de8d554caa6143ba8d989..bbe1a62efc021aadb29f27a1c7a68257b8478cef 100644 (file)
@@ -60,6 +60,7 @@ clean-files += cpustr.h
 KBUILD_CFLAGS  := $(USERINCLUDE) $(REALMODE_CFLAGS) -D_SETUP
 KBUILD_AFLAGS  := $(KBUILD_CFLAGS) -D__ASSEMBLY__
 GCOV_PROFILE := n
+UBSAN_SANITIZE := n
 
 $(obj)/bzImage: asflags-y  := $(SVGA_MODE)
 
index 0a291cdfaf77100117baf53b2e3af75a43a8af4c..f9ce75d80101cc7a620bed1169a1128056b9caa5 100644 (file)
@@ -33,6 +33,7 @@ KBUILD_CFLAGS += $(call cc-option,-fno-stack-protector)
 
 KBUILD_AFLAGS  := $(KBUILD_CFLAGS) -D__ASSEMBLY__
 GCOV_PROFILE := n
+UBSAN_SANITIZE :=n
 
 LDFLAGS := -m elf_$(UTS_MACHINE)
 LDFLAGS_vmlinux := -T
index 265c0ed6811800e3b03550cfded0c0cc743794f0..c854541d93ff66d71fa8b38d0d4f7f1ed9e16c15 100644 (file)
@@ -4,6 +4,7 @@
 
 KBUILD_CFLAGS += $(DISABLE_LTO)
 KASAN_SANITIZE := n
+UBSAN_SANITIZE := n
 
 VDSO64-$(CONFIG_X86_64)                := y
 VDSOX32-$(CONFIG_X86_X32_ABI)  := y
index 2730d775ef9a44e4709c99f29a333f07c4691ef0..3e75fcf6b8362e7c43f24340a3836386a4d5ac48 100644 (file)
@@ -70,3 +70,4 @@ KBUILD_CFLAGS := $(LINUXINCLUDE) $(REALMODE_CFLAGS) -D_SETUP -D_WAKEUP \
                   -I$(srctree)/arch/x86/boot
 KBUILD_AFLAGS  := $(KBUILD_CFLAGS) -D__ASSEMBLY__
 GCOV_PROFILE := n
+UBSAN_SANITIZE := n
index 9c12e18031d57b29b1a18547447b17207b3054de..aaf9c0bab42e8fc29e093da64f609d99064046fd 100644 (file)
@@ -22,6 +22,7 @@ KBUILD_CFLAGS                 := $(cflags-y) -DDISABLE_BRANCH_PROFILING \
 
 GCOV_PROFILE                   := n
 KASAN_SANITIZE                 := n
+UBSAN_SANITIZE                 := n
 
 lib-y                          := efi-stub-helper.o
 
index 61aa9bbea871fc6017f612bdbb06f2004782ee97..02dabf281b2fe7f1d0f08b6c6ff2664c533da8b8 100644 (file)
@@ -1643,6 +1643,9 @@ struct task_struct {
        struct held_lock held_locks[MAX_LOCK_DEPTH];
        gfp_t lockdep_reclaim_gfp;
 #endif
+#ifdef CONFIG_UBSAN
+       unsigned int in_ubsan;
+#endif
 
 /* journalling filesystem info */
        void *journal_info;
index f75a33f29f6e9b55c1e4b5ab9e1b355565fbaec6..157220b9ff05043e6b7e975633e2c3d0258d3cff 100644 (file)
@@ -1893,6 +1893,8 @@ source "samples/Kconfig"
 
 source "lib/Kconfig.kgdb"
 
+source "lib/Kconfig.ubsan"
+
 config ARCH_HAS_DEVMEM_IS_ALLOWED
        bool
 
diff --git a/lib/Kconfig.ubsan b/lib/Kconfig.ubsan
new file mode 100644 (file)
index 0000000..49518fb
--- /dev/null
@@ -0,0 +1,29 @@
+config ARCH_HAS_UBSAN_SANITIZE_ALL
+       bool
+
+config UBSAN
+       bool "Undefined behaviour sanity checker"
+       help
+         This option enables undefined behaviour sanity checker
+         Compile-time instrumentation is used to detect various undefined
+         behaviours in runtime. Various types of checks may be enabled
+         via boot parameter ubsan_handle (see: Documentation/ubsan.txt).
+
+config UBSAN_SANITIZE_ALL
+       bool "Enable instrumentation for the entire kernel"
+       depends on UBSAN
+       depends on ARCH_HAS_UBSAN_SANITIZE_ALL
+       default y
+       help
+         This option activates instrumentation for the entire kernel.
+         If you don't enable this option, you have to explicitly specify
+         UBSAN_SANITIZE := y for the files/directories you want to check for UB.
+
+config UBSAN_ALIGNMENT
+       bool "Enable checking of pointers alignment"
+       depends on UBSAN
+       default y if !HAVE_EFFICIENT_UNALIGNED_ACCESS
+       help
+         This option enables detection of unaligned memory accesses.
+         Enabling this option on architectures that support unalligned
+         accesses may produce a lot of false positives.
index b2a82e60098760db8d37e999d7b2296595bb3a41..2d4bc33d09b42097085480d7ce93ec3a8882faba 100644 (file)
@@ -209,3 +209,6 @@ quiet_cmd_build_OID_registry = GEN     $@
 clean-files    += oid_registry_data.c
 
 obj-$(CONFIG_UCS2_STRING) += ucs2_string.o
+obj-$(CONFIG_UBSAN) += ubsan.o
+
+UBSAN_SANITIZE_ubsan.o := n
diff --git a/lib/ubsan.c b/lib/ubsan.c
new file mode 100644 (file)
index 0000000..8799ae5
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ * UBSAN error reporting functions
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Author: Andrey Ryabinin <ryabinin.a.a@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/ctype.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+
+#include "ubsan.h"
+
+const char *type_check_kinds[] = {
+       "load of",
+       "store to",
+       "reference binding to",
+       "member access within",
+       "member call on",
+       "constructor call on",
+       "downcast of",
+       "downcast of"
+};
+
+#define REPORTED_BIT 31
+
+#if (BITS_PER_LONG == 64) && defined(__BIG_ENDIAN)
+#define COLUMN_MASK (~(1U << REPORTED_BIT))
+#define LINE_MASK   (~0U)
+#else
+#define COLUMN_MASK   (~0U)
+#define LINE_MASK (~(1U << REPORTED_BIT))
+#endif
+
+#define VALUE_LENGTH 40
+
+static bool was_reported(struct source_location *location)
+{
+       return test_and_set_bit(REPORTED_BIT, &location->reported);
+}
+
+static void print_source_location(const char *prefix,
+                               struct source_location *loc)
+{
+       pr_err("%s %s:%d:%d\n", prefix, loc->file_name,
+               loc->line & LINE_MASK, loc->column & COLUMN_MASK);
+}
+
+static bool suppress_report(struct source_location *loc)
+{
+       return current->in_ubsan || was_reported(loc);
+}
+
+static bool type_is_int(struct type_descriptor *type)
+{
+       return type->type_kind == type_kind_int;
+}
+
+static bool type_is_signed(struct type_descriptor *type)
+{
+       WARN_ON(!type_is_int(type));
+       return  type->type_info & 1;
+}
+
+static unsigned type_bit_width(struct type_descriptor *type)
+{
+       return 1 << (type->type_info >> 1);
+}
+
+static bool is_inline_int(struct type_descriptor *type)
+{
+       unsigned inline_bits = sizeof(unsigned long)*8;
+       unsigned bits = type_bit_width(type);
+
+       WARN_ON(!type_is_int(type));
+
+       return bits <= inline_bits;
+}
+
+static s_max get_signed_val(struct type_descriptor *type, unsigned long val)
+{
+       if (is_inline_int(type)) {
+               unsigned extra_bits = sizeof(s_max)*8 - type_bit_width(type);
+               return ((s_max)val) << extra_bits >> extra_bits;
+       }
+
+       if (type_bit_width(type) == 64)
+               return *(s64 *)val;
+
+       return *(s_max *)val;
+}
+
+static bool val_is_negative(struct type_descriptor *type, unsigned long val)
+{
+       return type_is_signed(type) && get_signed_val(type, val) < 0;
+}
+
+static u_max get_unsigned_val(struct type_descriptor *type, unsigned long val)
+{
+       if (is_inline_int(type))
+               return val;
+
+       if (type_bit_width(type) == 64)
+               return *(u64 *)val;
+
+       return *(u_max *)val;
+}
+
+static void val_to_string(char *str, size_t size, struct type_descriptor *type,
+       unsigned long value)
+{
+       if (type_is_int(type)) {
+               if (type_bit_width(type) == 128) {
+#if defined(CONFIG_ARCH_SUPPORTS_INT128) && defined(__SIZEOF_INT128__)
+                       u_max val = get_unsigned_val(type, value);
+
+                       scnprintf(str, size, "0x%08x%08x%08x%08x",
+                               (u32)(val >> 96),
+                               (u32)(val >> 64),
+                               (u32)(val >> 32),
+                               (u32)(val));
+#else
+                       WARN_ON(1);
+#endif
+               } else if (type_is_signed(type)) {
+                       scnprintf(str, size, "%lld",
+                               (s64)get_signed_val(type, value));
+               } else {
+                       scnprintf(str, size, "%llu",
+                               (u64)get_unsigned_val(type, value));
+               }
+       }
+}
+
+static bool location_is_valid(struct source_location *loc)
+{
+       return loc->file_name != NULL;
+}
+
+static DEFINE_SPINLOCK(report_lock);
+
+static void ubsan_prologue(struct source_location *location,
+                       unsigned long *flags)
+{
+       current->in_ubsan++;
+       spin_lock_irqsave(&report_lock, *flags);
+
+       pr_err("========================================"
+               "========================================\n");
+       print_source_location("UBSAN: Undefined behaviour in", location);
+}
+
+static void ubsan_epilogue(unsigned long *flags)
+{
+       dump_stack();
+       pr_err("========================================"
+               "========================================\n");
+       spin_unlock_irqrestore(&report_lock, *flags);
+       current->in_ubsan--;
+}
+
+static void handle_overflow(struct overflow_data *data, unsigned long lhs,
+                       unsigned long rhs, char op)
+{
+
+       struct type_descriptor *type = data->type;
+       unsigned long flags;
+       char lhs_val_str[VALUE_LENGTH];
+       char rhs_val_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(lhs_val_str, sizeof(lhs_val_str), type, lhs);
+       val_to_string(rhs_val_str, sizeof(rhs_val_str), type, rhs);
+       pr_err("%s integer overflow:\n",
+               type_is_signed(type) ? "signed" : "unsigned");
+       pr_err("%s %c %s cannot be represented in type %s\n",
+               lhs_val_str,
+               op,
+               rhs_val_str,
+               type->type_name);
+
+       ubsan_epilogue(&flags);
+}
+
+void __ubsan_handle_add_overflow(struct overflow_data *data,
+                               unsigned long lhs,
+                               unsigned long rhs)
+{
+
+       handle_overflow(data, lhs, rhs, '+');
+}
+EXPORT_SYMBOL(__ubsan_handle_add_overflow);
+
+void __ubsan_handle_sub_overflow(struct overflow_data *data,
+                               unsigned long lhs,
+                               unsigned long rhs)
+{
+       handle_overflow(data, lhs, rhs, '-');
+}
+EXPORT_SYMBOL(__ubsan_handle_sub_overflow);
+
+void __ubsan_handle_mul_overflow(struct overflow_data *data,
+                               unsigned long lhs,
+                               unsigned long rhs)
+{
+       handle_overflow(data, lhs, rhs, '*');
+}
+EXPORT_SYMBOL(__ubsan_handle_mul_overflow);
+
+void __ubsan_handle_negate_overflow(struct overflow_data *data,
+                               unsigned long old_val)
+{
+       unsigned long flags;
+       char old_val_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(old_val_str, sizeof(old_val_str), data->type, old_val);
+
+       pr_err("negation of %s cannot be represented in type %s:\n",
+               old_val_str, data->type->type_name);
+
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_negate_overflow);
+
+
+void __ubsan_handle_divrem_overflow(struct overflow_data *data,
+                               unsigned long lhs,
+                               unsigned long rhs)
+{
+       unsigned long flags;
+       char rhs_val_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(rhs_val_str, sizeof(rhs_val_str), data->type, rhs);
+
+       if (type_is_signed(data->type) && get_signed_val(data->type, rhs) == -1)
+               pr_err("division of %s by -1 cannot be represented in type %s\n",
+                       rhs_val_str, data->type->type_name);
+       else
+               pr_err("division by zero\n");
+
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_divrem_overflow);
+
+static void handle_null_ptr_deref(struct type_mismatch_data *data)
+{
+       unsigned long flags;
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       pr_err("%s null pointer of type %s\n",
+               type_check_kinds[data->type_check_kind],
+               data->type->type_name);
+
+       ubsan_epilogue(&flags);
+}
+
+static void handle_missaligned_access(struct type_mismatch_data *data,
+                               unsigned long ptr)
+{
+       unsigned long flags;
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       pr_err("%s misaligned address %p for type %s\n",
+               type_check_kinds[data->type_check_kind],
+               (void *)ptr, data->type->type_name);
+       pr_err("which requires %ld byte alignment\n", data->alignment);
+
+       ubsan_epilogue(&flags);
+}
+
+static void handle_object_size_mismatch(struct type_mismatch_data *data,
+                                       unsigned long ptr)
+{
+       unsigned long flags;
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+       pr_err("%s address %pk with insufficient space\n",
+               type_check_kinds[data->type_check_kind],
+               (void *) ptr);
+       pr_err("for an object of type %s\n", data->type->type_name);
+       ubsan_epilogue(&flags);
+}
+
+void __ubsan_handle_type_mismatch(struct type_mismatch_data *data,
+                               unsigned long ptr)
+{
+
+       if (!ptr)
+               handle_null_ptr_deref(data);
+       else if (data->alignment && !IS_ALIGNED(ptr, data->alignment))
+               handle_missaligned_access(data, ptr);
+       else
+               handle_object_size_mismatch(data, ptr);
+}
+EXPORT_SYMBOL(__ubsan_handle_type_mismatch);
+
+void __ubsan_handle_nonnull_return(struct nonnull_return_data *data)
+{
+       unsigned long flags;
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       pr_err("null pointer returned from function declared to never return null\n");
+
+       if (location_is_valid(&data->attr_location))
+               print_source_location("returns_nonnull attribute specified in",
+                               &data->attr_location);
+
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_nonnull_return);
+
+void __ubsan_handle_vla_bound_not_positive(struct vla_bound_data *data,
+                                       unsigned long bound)
+{
+       unsigned long flags;
+       char bound_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(bound_str, sizeof(bound_str), data->type, bound);
+       pr_err("variable length array bound value %s <= 0\n", bound_str);
+
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_vla_bound_not_positive);
+
+void __ubsan_handle_out_of_bounds(struct out_of_bounds_data *data,
+                               unsigned long index)
+{
+       unsigned long flags;
+       char index_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(index_str, sizeof(index_str), data->index_type, index);
+       pr_err("index %s is out of range for type %s\n", index_str,
+               data->array_type->type_name);
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_out_of_bounds);
+
+void __ubsan_handle_shift_out_of_bounds(struct shift_out_of_bounds_data *data,
+                                       unsigned long lhs, unsigned long rhs)
+{
+       unsigned long flags;
+       struct type_descriptor *rhs_type = data->rhs_type;
+       struct type_descriptor *lhs_type = data->lhs_type;
+       char rhs_str[VALUE_LENGTH];
+       char lhs_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(rhs_str, sizeof(rhs_str), rhs_type, rhs);
+       val_to_string(lhs_str, sizeof(lhs_str), lhs_type, lhs);
+
+       if (val_is_negative(rhs_type, rhs))
+               pr_err("shift exponent %s is negative\n", rhs_str);
+
+       else if (get_unsigned_val(rhs_type, rhs) >=
+               type_bit_width(lhs_type))
+               pr_err("shift exponent %s is too large for %u-bit type %s\n",
+                       rhs_str,
+                       type_bit_width(lhs_type),
+                       lhs_type->type_name);
+       else if (val_is_negative(lhs_type, lhs))
+               pr_err("left shift of negative value %s\n",
+                       lhs_str);
+       else
+               pr_err("left shift of %s by %s places cannot be"
+                       " represented in type %s\n",
+                       lhs_str, rhs_str,
+                       lhs_type->type_name);
+
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_shift_out_of_bounds);
+
+
+void __noreturn
+__ubsan_handle_builtin_unreachable(struct unreachable_data *data)
+{
+       unsigned long flags;
+
+       ubsan_prologue(&data->location, &flags);
+       pr_err("calling __builtin_unreachable()\n");
+       ubsan_epilogue(&flags);
+       panic("can't return from __builtin_unreachable()");
+}
+EXPORT_SYMBOL(__ubsan_handle_builtin_unreachable);
+
+void __ubsan_handle_load_invalid_value(struct invalid_value_data *data,
+                               unsigned long val)
+{
+       unsigned long flags;
+       char val_str[VALUE_LENGTH];
+
+       if (suppress_report(&data->location))
+               return;
+
+       ubsan_prologue(&data->location, &flags);
+
+       val_to_string(val_str, sizeof(val_str), data->type, val);
+
+       pr_err("load of value %s is not a valid value for type %s\n",
+               val_str, data->type->type_name);
+
+       ubsan_epilogue(&flags);
+}
+EXPORT_SYMBOL(__ubsan_handle_load_invalid_value);
diff --git a/lib/ubsan.h b/lib/ubsan.h
new file mode 100644 (file)
index 0000000..b2d18d4
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef _LIB_UBSAN_H
+#define _LIB_UBSAN_H
+
+enum {
+       type_kind_int = 0,
+       type_kind_float = 1,
+       type_unknown = 0xffff
+};
+
+struct type_descriptor {
+       u16 type_kind;
+       u16 type_info;
+       char type_name[1];
+};
+
+struct source_location {
+       const char *file_name;
+       union {
+               unsigned long reported;
+               struct {
+                       u32 line;
+                       u32 column;
+               };
+       };
+};
+
+struct overflow_data {
+       struct source_location location;
+       struct type_descriptor *type;
+};
+
+struct type_mismatch_data {
+       struct source_location location;
+       struct type_descriptor *type;
+       unsigned long alignment;
+       unsigned char type_check_kind;
+};
+
+struct nonnull_arg_data {
+       struct source_location location;
+       struct source_location attr_location;
+       int arg_index;
+};
+
+struct nonnull_return_data {
+       struct source_location location;
+       struct source_location attr_location;
+};
+
+struct vla_bound_data {
+       struct source_location location;
+       struct type_descriptor *type;
+};
+
+struct out_of_bounds_data {
+       struct source_location location;
+       struct type_descriptor *array_type;
+       struct type_descriptor *index_type;
+};
+
+struct shift_out_of_bounds_data {
+       struct source_location location;
+       struct type_descriptor *lhs_type;
+       struct type_descriptor *rhs_type;
+};
+
+struct unreachable_data {
+       struct source_location location;
+};
+
+struct invalid_value_data {
+       struct source_location location;
+       struct type_descriptor *type;
+};
+
+#if defined(CONFIG_ARCH_SUPPORTS_INT128) && defined(__SIZEOF_INT128__)
+typedef __int128 s_max;
+typedef unsigned __int128 u_max;
+#else
+typedef s64 s_max;
+typedef u64 u_max;
+#endif
+
+#endif
index 64710148941ed33d7467192797711c5c98e9e4a3..a61460d9f5b0e76a8ce5214de597b285dbca74eb 100644 (file)
@@ -1,4 +1,5 @@
 KASAN_SANITIZE := n
+UBSAN_SANITIZE_kasan.o := n
 
 CFLAGS_REMOVE_kasan.o = -pg
 # Function splitter causes unnecessary splits in __asan_load1/__asan_store1
index 39d6bb18ce7669fc8b0ea79978cddee1b44b8fc5..2edbcadb3d7f6daa047c68f78c32e6acb5508bfa 100644 (file)
@@ -130,6 +130,12 @@ _c_flags += $(if $(patsubst n%,, \
                $(CFLAGS_KASAN))
 endif
 
+ifeq ($(CONFIG_UBSAN),y)
+_c_flags += $(if $(patsubst n%,, \
+               $(UBSAN_SANITIZE_$(basetarget).o)$(UBSAN_SANITIZE)$(CONFIG_UBSAN_SANITIZE_ALL)), \
+               $(CFLAGS_UBSAN))
+endif
+
 # If building the kernel in a separate objtree expand all occurrences
 # of -Idir to -I$(srctree)/dir except for absolute paths (starting with '/').
 
diff --git a/scripts/Makefile.ubsan b/scripts/Makefile.ubsan
new file mode 100644 (file)
index 0000000..8ab6867
--- /dev/null
@@ -0,0 +1,17 @@
+ifdef CONFIG_UBSAN
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=shift)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=integer-divide-by-zero)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=unreachable)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=vla-bound)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=null)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=signed-integer-overflow)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=bounds)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=object-size)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=returns-nonnull-attribute)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=bool)
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=enum)
+
+ifdef CONFIG_UBSAN_ALIGNMENT
+      CFLAGS_UBSAN += $(call cc-option, -fsanitize=alignment)
+endif
+endif