mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
kfence: add test suite
Add KFENCE test suite, testing various error detection scenarios. Makes use of KUnit for test organization. Since KFENCE's interface to obtain error reports is via the console, the test verifies that KFENCE outputs expected reports to the console. [elver@google.com: fix typo in test] Link: https://lkml.kernel.org/r/X9lHQExmHGvETxY4@elver.google.com [elver@google.com: show access type in report] Link: https://lkml.kernel.org/r/20210111091544.3287013-2-elver@google.com Link: https://lkml.kernel.org/r/20201103175841.3495947-9-elver@google.com Signed-off-by: Alexander Potapenko <glider@google.com> Signed-off-by: Marco Elver <elver@google.com> Reviewed-by: Dmitry Vyukov <dvyukov@google.com> Co-developed-by: Alexander Potapenko <glider@google.com> Reviewed-by: Jann Horn <jannh@google.com> Cc: Andrey Konovalov <andreyknvl@google.com> Cc: Andrey Ryabinin <aryabinin@virtuozzo.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Borislav Petkov <bp@alien8.de> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Christopher Lameter <cl@linux.com> Cc: Dave Hansen <dave.hansen@linux.intel.com> Cc: David Rientjes <rientjes@google.com> Cc: Eric Dumazet <edumazet@google.com> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Cc: Hillf Danton <hdanton@sina.com> Cc: "H. Peter Anvin" <hpa@zytor.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Joern Engel <joern@purestorage.com> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Cc: Kees Cook <keescook@chromium.org> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Paul E. McKenney <paulmck@kernel.org> Cc: Pekka Enberg <penberg@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: SeongJae Park <sjpark@amazon.de> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Vlastimil Babka <vbabka@suse.cz> Cc: Will Deacon <will@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
committed by
Linus Torvalds
parent
10efe55f88
commit
bc8fbc5f30
@@ -65,9 +65,9 @@ Error reports
|
||||
A typical out-of-bounds access looks like this::
|
||||
|
||||
==================================================================
|
||||
BUG: KFENCE: out-of-bounds in test_out_of_bounds_read+0xa3/0x22b
|
||||
BUG: KFENCE: out-of-bounds read in test_out_of_bounds_read+0xa3/0x22b
|
||||
|
||||
Out-of-bounds access at 0xffffffffb672efff (1B left of kfence-#17):
|
||||
Out-of-bounds read at 0xffffffffb672efff (1B left of kfence-#17):
|
||||
test_out_of_bounds_read+0xa3/0x22b
|
||||
kunit_try_run_case+0x51/0x85
|
||||
kunit_generic_run_threadfn_adapter+0x16/0x30
|
||||
@@ -94,9 +94,9 @@ its origin. Note that, real kernel addresses are only shown for
|
||||
Use-after-free accesses are reported as::
|
||||
|
||||
==================================================================
|
||||
BUG: KFENCE: use-after-free in test_use_after_free_read+0xb3/0x143
|
||||
BUG: KFENCE: use-after-free read in test_use_after_free_read+0xb3/0x143
|
||||
|
||||
Use-after-free access at 0xffffffffb673dfe0 (in kfence-#24):
|
||||
Use-after-free read at 0xffffffffb673dfe0 (in kfence-#24):
|
||||
test_use_after_free_read+0xb3/0x143
|
||||
kunit_try_run_case+0x51/0x85
|
||||
kunit_generic_run_threadfn_adapter+0x16/0x30
|
||||
@@ -193,9 +193,9 @@ where it was not possible to determine an associated object, e.g. if adjacent
|
||||
object pages had not yet been allocated::
|
||||
|
||||
==================================================================
|
||||
BUG: KFENCE: invalid access in test_invalid_access+0x26/0xe0
|
||||
BUG: KFENCE: invalid read in test_invalid_access+0x26/0xe0
|
||||
|
||||
Invalid access at 0xffffffffb670b00a:
|
||||
Invalid read at 0xffffffffb670b00a:
|
||||
test_invalid_access+0x26/0xe0
|
||||
kunit_try_run_case+0x51/0x85
|
||||
kunit_generic_run_threadfn_adapter+0x16/0x30
|
||||
|
||||
@@ -390,7 +390,7 @@ static void __do_kernel_fault(unsigned long addr, unsigned int esr,
|
||||
} else if (addr < PAGE_SIZE) {
|
||||
msg = "NULL pointer dereference";
|
||||
} else {
|
||||
if (kfence_handle_page_fault(addr, regs))
|
||||
if (kfence_handle_page_fault(addr, esr & ESR_ELx_WNR, regs))
|
||||
return;
|
||||
|
||||
msg = "paging request";
|
||||
|
||||
+2
-1
@@ -682,7 +682,8 @@ page_fault_oops(struct pt_regs *regs, unsigned long error_code,
|
||||
efi_crash_gracefully_on_page_fault(address);
|
||||
|
||||
/* Only not-present faults should be handled by KFENCE. */
|
||||
if (!(error_code & X86_PF_PROT) && kfence_handle_page_fault(address, regs))
|
||||
if (!(error_code & X86_PF_PROT) &&
|
||||
kfence_handle_page_fault(address, error_code & X86_PF_WRITE, regs))
|
||||
return;
|
||||
|
||||
oops:
|
||||
|
||||
@@ -186,6 +186,7 @@ static __always_inline __must_check bool kfence_free(void *addr)
|
||||
/**
|
||||
* kfence_handle_page_fault() - perform page fault handling for KFENCE pages
|
||||
* @addr: faulting address
|
||||
* @is_write: is access a write
|
||||
* @regs: current struct pt_regs (can be NULL, but shows full stack trace)
|
||||
*
|
||||
* Return:
|
||||
@@ -197,7 +198,7 @@ static __always_inline __must_check bool kfence_free(void *addr)
|
||||
* cases KFENCE prints an error message and marks the offending page as
|
||||
* present, so that the kernel can proceed.
|
||||
*/
|
||||
bool __must_check kfence_handle_page_fault(unsigned long addr, struct pt_regs *regs);
|
||||
bool __must_check kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs *regs);
|
||||
|
||||
#else /* CONFIG_KFENCE */
|
||||
|
||||
@@ -210,7 +211,11 @@ static inline size_t kfence_ksize(const void *addr) { return 0; }
|
||||
static inline void *kfence_object_start(const void *addr) { return NULL; }
|
||||
static inline void __kfence_free(void *addr) { }
|
||||
static inline bool __must_check kfence_free(void *addr) { return false; }
|
||||
static inline bool __must_check kfence_handle_page_fault(unsigned long addr, struct pt_regs *regs) { return false; }
|
||||
static inline bool __must_check kfence_handle_page_fault(unsigned long addr, bool is_write,
|
||||
struct pt_regs *regs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -66,4 +66,17 @@ config KFENCE_STRESS_TEST_FAULTS
|
||||
|
||||
Only for KFENCE testing; set to 0 if you are not a KFENCE developer.
|
||||
|
||||
config KFENCE_KUNIT_TEST
|
||||
tristate "KFENCE integration test suite" if !KUNIT_ALL_TESTS
|
||||
default KUNIT_ALL_TESTS
|
||||
depends on TRACEPOINTS && KUNIT
|
||||
help
|
||||
Test suite for KFENCE, testing various error detection scenarios with
|
||||
various allocation types, and checking that reports are correctly
|
||||
output to console.
|
||||
|
||||
Say Y here if you want the test to be built into the kernel and run
|
||||
during boot; say M if you want the test to build as a module; say N
|
||||
if you are unsure.
|
||||
|
||||
endif # KFENCE
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_KFENCE) := core.o report.o
|
||||
|
||||
CFLAGS_kfence_test.o := -g -fno-omit-frame-pointer -fno-optimize-sibling-calls
|
||||
obj-$(CONFIG_KFENCE_KUNIT_TEST) += kfence_test.o
|
||||
|
||||
+6
-5
@@ -216,7 +216,7 @@ static inline bool check_canary_byte(u8 *addr)
|
||||
return true;
|
||||
|
||||
atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]);
|
||||
kfence_report_error((unsigned long)addr, NULL, addr_to_metadata((unsigned long)addr),
|
||||
kfence_report_error((unsigned long)addr, false, NULL, addr_to_metadata((unsigned long)addr),
|
||||
KFENCE_ERROR_CORRUPTION);
|
||||
return false;
|
||||
}
|
||||
@@ -355,7 +355,8 @@ static void kfence_guarded_free(void *addr, struct kfence_metadata *meta, bool z
|
||||
if (meta->state != KFENCE_OBJECT_ALLOCATED || meta->addr != (unsigned long)addr) {
|
||||
/* Invalid or double-free, bail out. */
|
||||
atomic_long_inc(&counters[KFENCE_COUNTER_BUGS]);
|
||||
kfence_report_error((unsigned long)addr, NULL, meta, KFENCE_ERROR_INVALID_FREE);
|
||||
kfence_report_error((unsigned long)addr, false, NULL, meta,
|
||||
KFENCE_ERROR_INVALID_FREE);
|
||||
raw_spin_unlock_irqrestore(&meta->lock, flags);
|
||||
return;
|
||||
}
|
||||
@@ -770,7 +771,7 @@ void __kfence_free(void *addr)
|
||||
kfence_guarded_free(addr, meta, false);
|
||||
}
|
||||
|
||||
bool kfence_handle_page_fault(unsigned long addr, struct pt_regs *regs)
|
||||
bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs *regs)
|
||||
{
|
||||
const int page_index = (addr - (unsigned long)__kfence_pool) / PAGE_SIZE;
|
||||
struct kfence_metadata *to_report = NULL;
|
||||
@@ -833,11 +834,11 @@ bool kfence_handle_page_fault(unsigned long addr, struct pt_regs *regs)
|
||||
|
||||
out:
|
||||
if (to_report) {
|
||||
kfence_report_error(addr, regs, to_report, error_type);
|
||||
kfence_report_error(addr, is_write, regs, to_report, error_type);
|
||||
raw_spin_unlock_irqrestore(&to_report->lock, flags);
|
||||
} else {
|
||||
/* This may be a UAF or OOB access, but we can't be sure. */
|
||||
kfence_report_error(addr, regs, NULL, KFENCE_ERROR_INVALID);
|
||||
kfence_report_error(addr, is_write, regs, NULL, KFENCE_ERROR_INVALID);
|
||||
}
|
||||
|
||||
return kfence_unprotect(addr); /* Unprotect and let access proceed. */
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ enum kfence_error_type {
|
||||
KFENCE_ERROR_INVALID_FREE, /* Invalid free. */
|
||||
};
|
||||
|
||||
void kfence_report_error(unsigned long address, struct pt_regs *regs,
|
||||
void kfence_report_error(unsigned long address, bool is_write, struct pt_regs *regs,
|
||||
const struct kfence_metadata *meta, enum kfence_error_type type);
|
||||
|
||||
void kfence_print_object(struct seq_file *seq, const struct kfence_metadata *meta);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+18
-9
@@ -156,7 +156,12 @@ static void print_diff_canary(unsigned long address, size_t bytes_to_show,
|
||||
pr_cont(" ]");
|
||||
}
|
||||
|
||||
void kfence_report_error(unsigned long address, struct pt_regs *regs,
|
||||
static const char *get_access_type(bool is_write)
|
||||
{
|
||||
return is_write ? "write" : "read";
|
||||
}
|
||||
|
||||
void kfence_report_error(unsigned long address, bool is_write, struct pt_regs *regs,
|
||||
const struct kfence_metadata *meta, enum kfence_error_type type)
|
||||
{
|
||||
unsigned long stack_entries[KFENCE_STACK_DEPTH] = { 0 };
|
||||
@@ -194,17 +199,19 @@ void kfence_report_error(unsigned long address, struct pt_regs *regs,
|
||||
case KFENCE_ERROR_OOB: {
|
||||
const bool left_of_object = address < meta->addr;
|
||||
|
||||
pr_err("BUG: KFENCE: out-of-bounds in %pS\n\n", (void *)stack_entries[skipnr]);
|
||||
pr_err("Out-of-bounds access at 0x" PTR_FMT " (%luB %s of kfence-#%zd):\n",
|
||||
(void *)address,
|
||||
pr_err("BUG: KFENCE: out-of-bounds %s in %pS\n\n", get_access_type(is_write),
|
||||
(void *)stack_entries[skipnr]);
|
||||
pr_err("Out-of-bounds %s at 0x" PTR_FMT " (%luB %s of kfence-#%zd):\n",
|
||||
get_access_type(is_write), (void *)address,
|
||||
left_of_object ? meta->addr - address : address - meta->addr,
|
||||
left_of_object ? "left" : "right", object_index);
|
||||
break;
|
||||
}
|
||||
case KFENCE_ERROR_UAF:
|
||||
pr_err("BUG: KFENCE: use-after-free in %pS\n\n", (void *)stack_entries[skipnr]);
|
||||
pr_err("Use-after-free access at 0x" PTR_FMT " (in kfence-#%zd):\n",
|
||||
(void *)address, object_index);
|
||||
pr_err("BUG: KFENCE: use-after-free %s in %pS\n\n", get_access_type(is_write),
|
||||
(void *)stack_entries[skipnr]);
|
||||
pr_err("Use-after-free %s at 0x" PTR_FMT " (in kfence-#%zd):\n",
|
||||
get_access_type(is_write), (void *)address, object_index);
|
||||
break;
|
||||
case KFENCE_ERROR_CORRUPTION:
|
||||
pr_err("BUG: KFENCE: memory corruption in %pS\n\n", (void *)stack_entries[skipnr]);
|
||||
@@ -213,8 +220,10 @@ void kfence_report_error(unsigned long address, struct pt_regs *regs,
|
||||
pr_cont(" (in kfence-#%zd):\n", object_index);
|
||||
break;
|
||||
case KFENCE_ERROR_INVALID:
|
||||
pr_err("BUG: KFENCE: invalid access in %pS\n\n", (void *)stack_entries[skipnr]);
|
||||
pr_err("Invalid access at 0x" PTR_FMT ":\n", (void *)address);
|
||||
pr_err("BUG: KFENCE: invalid %s in %pS\n\n", get_access_type(is_write),
|
||||
(void *)stack_entries[skipnr]);
|
||||
pr_err("Invalid %s at 0x" PTR_FMT ":\n", get_access_type(is_write),
|
||||
(void *)address);
|
||||
break;
|
||||
case KFENCE_ERROR_INVALID_FREE:
|
||||
pr_err("BUG: KFENCE: invalid free in %pS\n\n", (void *)stack_entries[skipnr]);
|
||||
|
||||
Reference in New Issue
Block a user