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/[email protected]
Signed-off-by: Marco Elver <[email protected]>
Suggested-by: Jörn Engel <[email protected]>
Reviewed-by: Jörn Engel <[email protected]>
Cc: Alexander Potapenko <[email protected]>
Cc: Marco Elver <[email protected]>
Cc: Dmitry Vyukov <[email protected]>
Cc: Jann Horn <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>

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 <[email protected]>
Change-Id: I2e9bb224292cf92ac828232c51cd57024ac56d7d
This commit is contained in:
Marco Elver 2021-01-22 09:18:43 +00:00 • committed by Michael Bestas
commit 7540bb92fb
No known key found for this signature in database
GPG key ID: CC95044519BE6669
8 changed files with 82 additions and 31 deletions

View file

@ -223,7 +223,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;
}
@ -361,7 +361,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. */

View file

@ -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);

View file

@ -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");
@ -286,6 +295,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;
@ -313,12 +323,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);
@ -403,6 +432,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;
@ -501,6 +531,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;
@ -590,6 +621,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]);
@ -603,6 +635,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);
@ -639,6 +672,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;
@ -720,6 +754,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),

View file

@ -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]);