mirror of
https://github.com/torvalds/linux
synced 2024-11-05 18:23:50 +00:00
6ec62961e6
Pull objtool updates from Ingo Molnar: "This is a series from Peter Zijlstra that adds x86 build-time uaccess validation of SMAP to objtool, which will detect and warn about the following uaccess API usage bugs and weirdnesses: - call to %s() with UACCESS enabled - return with UACCESS enabled - return with UACCESS disabled from a UACCESS-safe function - recursive UACCESS enable - redundant UACCESS disable - UACCESS-safe disables UACCESS As it turns out not leaking uaccess permissions outside the intended uaccess functionality is hard when the interfaces are complex and when such bugs are mostly dormant. As a bonus we now also check the DF flag. We had at least one high-profile bug in that area in the early days of Linux, and the checking is fairly simple. The checks performed and warnings emitted are: - call to %s() with DF set - return with DF set - return with modified stack frame - recursive STD - redundant CLD It's all x86-only for now, but later on this can also be used for PAN on ARM and objtool is fairly cross-platform in principle. While all warnings emitted by this new checking facility that got reported to us were fixed, there might be GCC version dependent warnings that were not reported yet - which we'll address, should they trigger. The warnings are non-fatal build warnings" * 'core-objtool-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (27 commits) mm/uaccess: Use 'unsigned long' to placate UBSAN warnings on older GCC versions x86/uaccess: Dont leak the AC flag into __put_user() argument evaluation sched/x86_64: Don't save flags on context switch objtool: Add Direction Flag validation objtool: Add UACCESS validation objtool: Fix sibling call detection objtool: Rewrite alt->skip_orig objtool: Add --backtrace support objtool: Rewrite add_ignores() objtool: Handle function aliases objtool: Set insn->func for alternatives x86/uaccess, kcov: Disable stack protector x86/uaccess, ftrace: Fix ftrace_likely_update() vs. SMAP x86/uaccess, ubsan: Fix UBSAN vs. SMAP x86/uaccess, kasan: Fix KASAN vs SMAP x86/smap: Ditch __stringify() x86/uaccess: Introduce user_access_{save,restore}() x86/uaccess, signal: Fix AC=1 bloat x86/uaccess: Always inline user_access_begin() x86/uaccess, xen: Suppress SMAP warnings ...
442 lines
11 KiB
C
442 lines
11 KiB
C
/*
|
|
* 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 <linux/uaccess.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, void *val)
|
|
{
|
|
if (is_inline_int(type)) {
|
|
unsigned extra_bits = sizeof(s_max)*8 - type_bit_width(type);
|
|
unsigned long ulong_val = (unsigned long)val;
|
|
|
|
return ((s_max)ulong_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, void *val)
|
|
{
|
|
return type_is_signed(type) && get_signed_val(type, val) < 0;
|
|
}
|
|
|
|
static u_max get_unsigned_val(struct type_descriptor *type, void *val)
|
|
{
|
|
if (is_inline_int(type))
|
|
return (unsigned long)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,
|
|
void *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 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, void *lhs,
|
|
void *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,
|
|
void *lhs, void *rhs)
|
|
{
|
|
|
|
handle_overflow(data, lhs, rhs, '+');
|
|
}
|
|
EXPORT_SYMBOL(__ubsan_handle_add_overflow);
|
|
|
|
void __ubsan_handle_sub_overflow(struct overflow_data *data,
|
|
void *lhs, void *rhs)
|
|
{
|
|
handle_overflow(data, lhs, rhs, '-');
|
|
}
|
|
EXPORT_SYMBOL(__ubsan_handle_sub_overflow);
|
|
|
|
void __ubsan_handle_mul_overflow(struct overflow_data *data,
|
|
void *lhs, void *rhs)
|
|
{
|
|
handle_overflow(data, lhs, rhs, '*');
|
|
}
|
|
EXPORT_SYMBOL(__ubsan_handle_mul_overflow);
|
|
|
|
void __ubsan_handle_negate_overflow(struct overflow_data *data,
|
|
void *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,
|
|
void *lhs, void *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_common *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_misaligned_access(struct type_mismatch_data_common *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_common *data,
|
|
unsigned long ptr)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (suppress_report(data->location))
|
|
return;
|
|
|
|
ubsan_prologue(data->location, &flags);
|
|
pr_err("%s address %p 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);
|
|
}
|
|
|
|
static void ubsan_type_mismatch_common(struct type_mismatch_data_common *data,
|
|
unsigned long ptr)
|
|
{
|
|
unsigned long flags = user_access_save();
|
|
|
|
if (!ptr)
|
|
handle_null_ptr_deref(data);
|
|
else if (data->alignment && !IS_ALIGNED(ptr, data->alignment))
|
|
handle_misaligned_access(data, ptr);
|
|
else
|
|
handle_object_size_mismatch(data, ptr);
|
|
|
|
user_access_restore(flags);
|
|
}
|
|
|
|
void __ubsan_handle_type_mismatch(struct type_mismatch_data *data,
|
|
void *ptr)
|
|
{
|
|
struct type_mismatch_data_common common_data = {
|
|
.location = &data->location,
|
|
.type = data->type,
|
|
.alignment = data->alignment,
|
|
.type_check_kind = data->type_check_kind
|
|
};
|
|
|
|
ubsan_type_mismatch_common(&common_data, (unsigned long)ptr);
|
|
}
|
|
EXPORT_SYMBOL(__ubsan_handle_type_mismatch);
|
|
|
|
void __ubsan_handle_type_mismatch_v1(struct type_mismatch_data_v1 *data,
|
|
void *ptr)
|
|
{
|
|
|
|
struct type_mismatch_data_common common_data = {
|
|
.location = &data->location,
|
|
.type = data->type,
|
|
.alignment = 1UL << data->log_alignment,
|
|
.type_check_kind = data->type_check_kind
|
|
};
|
|
|
|
ubsan_type_mismatch_common(&common_data, (unsigned long)ptr);
|
|
}
|
|
EXPORT_SYMBOL(__ubsan_handle_type_mismatch_v1);
|
|
|
|
void __ubsan_handle_out_of_bounds(struct out_of_bounds_data *data, void *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,
|
|
void *lhs, void *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 __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,
|
|
void *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);
|