FROMGIT: kfence: show access type in report
Show the access type in KFENCE reports by plumbing through read/write information from the page fault handler. Update the documentation and test accordingly. Link: https://lkml.kernel.org/r/20210111091544.3287013-2-elver@google.com Signed-off-by: Marco Elver <elver@google.com> Suggested-by: Jörn Engel <joern@purestorage.com> Reviewed-by: Jörn Engel <joern@purestorage.com> Cc: Alexander Potapenko <glider@google.com> Cc: Marco Elver <elver@google.com> Cc: Dmitry Vyukov <dvyukov@google.com> Cc: Jann Horn <jannh@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Bug: 177201466 (cherry picked from commit e29117c1fbf30d27d5afe41cf34263e1fd8e4f04 https://github.com/hnaz/linux-mm v5.11-rc4-mmots-2021-01-21-20-10) Test: CONFIG_KFENCE_KUNIT_TEST=y passes on Cuttlefish Signed-off-by: Alexander Potapenko <glider@google.com> Change-Id: I2e9bb224292cf92ac828232c51cd57024ac56d7d
This commit is contained in:
committed by
Alistair Delva
parent
be3b5ae235
commit
2da503f43b
@@ -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
|
||||
|
||||
@@ -385,7 +385,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";
|
||||
|
||||
@@ -727,7 +727,8 @@ no_context(struct pt_regs *regs, unsigned long error_code,
|
||||
efi_recover_from_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
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -71,8 +71,14 @@ struct expect_report {
|
||||
enum kfence_error_type type; /* The type or error. */
|
||||
void *fn; /* Function pointer to expected function where access occurred. */
|
||||
char *addr; /* Address at which the bad access occurred. */
|
||||
bool is_write; /* Is access a write. */
|
||||
};
|
||||
|
||||
static const char *get_access_type(const struct expect_report *r)
|
||||
{
|
||||
return r->is_write ? "write" : "read";
|
||||
}
|
||||
|
||||
/* Check observed report matches information in @r. */
|
||||
static bool report_matches(const struct expect_report *r)
|
||||
{
|
||||
@@ -93,16 +99,19 @@ static bool report_matches(const struct expect_report *r)
|
||||
end = &expect[0][sizeof(expect[0]) - 1];
|
||||
switch (r->type) {
|
||||
case KFENCE_ERROR_OOB:
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds");
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: out-of-bounds %s",
|
||||
get_access_type(r));
|
||||
break;
|
||||
case KFENCE_ERROR_UAF:
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free");
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: use-after-free %s",
|
||||
get_access_type(r));
|
||||
break;
|
||||
case KFENCE_ERROR_CORRUPTION:
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: memory corruption");
|
||||
break;
|
||||
case KFENCE_ERROR_INVALID:
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid access");
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid %s",
|
||||
get_access_type(r));
|
||||
break;
|
||||
case KFENCE_ERROR_INVALID_FREE:
|
||||
cur += scnprintf(cur, end - cur, "BUG: KFENCE: invalid free");
|
||||
@@ -121,16 +130,16 @@ static bool report_matches(const struct expect_report *r)
|
||||
|
||||
switch (r->type) {
|
||||
case KFENCE_ERROR_OOB:
|
||||
cur += scnprintf(cur, end - cur, "Out-of-bounds access at");
|
||||
cur += scnprintf(cur, end - cur, "Out-of-bounds %s at", get_access_type(r));
|
||||
break;
|
||||
case KFENCE_ERROR_UAF:
|
||||
cur += scnprintf(cur, end - cur, "Use-after-free access at");
|
||||
cur += scnprintf(cur, end - cur, "Use-after-free %s at", get_access_type(r));
|
||||
break;
|
||||
case KFENCE_ERROR_CORRUPTION:
|
||||
cur += scnprintf(cur, end - cur, "Corrupted memory at");
|
||||
break;
|
||||
case KFENCE_ERROR_INVALID:
|
||||
cur += scnprintf(cur, end - cur, "Invalid access at");
|
||||
cur += scnprintf(cur, end - cur, "Invalid %s at", get_access_type(r));
|
||||
break;
|
||||
case KFENCE_ERROR_INVALID_FREE:
|
||||
cur += scnprintf(cur, end - cur, "Invalid free of");
|
||||
@@ -294,6 +303,7 @@ static void test_out_of_bounds_read(struct kunit *test)
|
||||
struct expect_report expect = {
|
||||
.type = KFENCE_ERROR_OOB,
|
||||
.fn = test_out_of_bounds_read,
|
||||
.is_write = false,
|
||||
};
|
||||
char *buf;
|
||||
|
||||
@@ -321,12 +331,31 @@ static void test_out_of_bounds_read(struct kunit *test)
|
||||
test_free(buf);
|
||||
}
|
||||
|
||||
static void test_out_of_bounds_write(struct kunit *test)
|
||||
{
|
||||
size_t size = 32;
|
||||
struct expect_report expect = {
|
||||
.type = KFENCE_ERROR_OOB,
|
||||
.fn = test_out_of_bounds_write,
|
||||
.is_write = true,
|
||||
};
|
||||
char *buf;
|
||||
|
||||
setup_test_cache(test, size, 0, NULL);
|
||||
buf = test_alloc(test, size, GFP_KERNEL, ALLOCATE_LEFT);
|
||||
expect.addr = buf - 1;
|
||||
WRITE_ONCE(*expect.addr, 42);
|
||||
KUNIT_EXPECT_TRUE(test, report_matches(&expect));
|
||||
test_free(buf);
|
||||
}
|
||||
|
||||
static void test_use_after_free_read(struct kunit *test)
|
||||
{
|
||||
const size_t size = 32;
|
||||
struct expect_report expect = {
|
||||
.type = KFENCE_ERROR_UAF,
|
||||
.fn = test_use_after_free_read,
|
||||
.is_write = false,
|
||||
};
|
||||
|
||||
setup_test_cache(test, size, 0, NULL);
|
||||
@@ -411,6 +440,7 @@ static void test_kmalloc_aligned_oob_read(struct kunit *test)
|
||||
struct expect_report expect = {
|
||||
.type = KFENCE_ERROR_OOB,
|
||||
.fn = test_kmalloc_aligned_oob_read,
|
||||
.is_write = false,
|
||||
};
|
||||
char *buf;
|
||||
|
||||
@@ -509,6 +539,7 @@ static void test_init_on_free(struct kunit *test)
|
||||
struct expect_report expect = {
|
||||
.type = KFENCE_ERROR_UAF,
|
||||
.fn = test_init_on_free,
|
||||
.is_write = false,
|
||||
};
|
||||
int i;
|
||||
|
||||
@@ -598,6 +629,7 @@ static void test_invalid_access(struct kunit *test)
|
||||
.type = KFENCE_ERROR_INVALID,
|
||||
.fn = test_invalid_access,
|
||||
.addr = &__kfence_pool[10],
|
||||
.is_write = false,
|
||||
};
|
||||
|
||||
READ_ONCE(__kfence_pool[10]);
|
||||
@@ -611,6 +643,7 @@ static void test_memcache_typesafe_by_rcu(struct kunit *test)
|
||||
struct expect_report expect = {
|
||||
.type = KFENCE_ERROR_UAF,
|
||||
.fn = test_memcache_typesafe_by_rcu,
|
||||
.is_write = false,
|
||||
};
|
||||
|
||||
setup_test_cache(test, size, SLAB_TYPESAFE_BY_RCU, NULL);
|
||||
@@ -647,6 +680,7 @@ static void test_krealloc(struct kunit *test)
|
||||
.type = KFENCE_ERROR_UAF,
|
||||
.fn = test_krealloc,
|
||||
.addr = test_alloc(test, size, GFP_KERNEL, ALLOCATE_ANY),
|
||||
.is_write = false,
|
||||
};
|
||||
char *buf = expect.addr;
|
||||
int i;
|
||||
@@ -728,6 +762,7 @@ static void test_memcache_alloc_bulk(struct kunit *test)
|
||||
|
||||
static struct kunit_case kfence_test_cases[] = {
|
||||
KFENCE_KUNIT_CASE(test_out_of_bounds_read),
|
||||
KFENCE_KUNIT_CASE(test_out_of_bounds_write),
|
||||
KFENCE_KUNIT_CASE(test_use_after_free_read),
|
||||
KFENCE_KUNIT_CASE(test_double_free),
|
||||
KFENCE_KUNIT_CASE(test_invalid_addr_free),
|
||||
|
||||
@@ -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