162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/kernel.h>
362306a36Sopenharmony_ci#include <linux/string.h>
462306a36Sopenharmony_ci#include <linux/mm.h>
562306a36Sopenharmony_ci#include <linux/mmdebug.h>
662306a36Sopenharmony_ci#include <linux/highmem.h>
762306a36Sopenharmony_ci#include <linux/poison.h>
862306a36Sopenharmony_ci#include <linux/ratelimit.h>
962306a36Sopenharmony_ci#include <linux/kasan.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_cibool _page_poisoning_enabled_early;
1262306a36Sopenharmony_ciEXPORT_SYMBOL(_page_poisoning_enabled_early);
1362306a36Sopenharmony_ciDEFINE_STATIC_KEY_FALSE(_page_poisoning_enabled);
1462306a36Sopenharmony_ciEXPORT_SYMBOL(_page_poisoning_enabled);
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic int __init early_page_poison_param(char *buf)
1762306a36Sopenharmony_ci{
1862306a36Sopenharmony_ci	return kstrtobool(buf, &_page_poisoning_enabled_early);
1962306a36Sopenharmony_ci}
2062306a36Sopenharmony_ciearly_param("page_poison", early_page_poison_param);
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic void poison_page(struct page *page)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	void *addr = kmap_atomic(page);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	/* KASAN still think the page is in-use, so skip it. */
2762306a36Sopenharmony_ci	kasan_disable_current();
2862306a36Sopenharmony_ci	memset(kasan_reset_tag(addr), PAGE_POISON, PAGE_SIZE);
2962306a36Sopenharmony_ci	kasan_enable_current();
3062306a36Sopenharmony_ci	kunmap_atomic(addr);
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_civoid __kernel_poison_pages(struct page *page, int n)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	int i;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	for (i = 0; i < n; i++)
3862306a36Sopenharmony_ci		poison_page(page + i);
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic bool single_bit_flip(unsigned char a, unsigned char b)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	unsigned char error = a ^ b;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	return error && !(error & (error - 1));
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic void check_poison_mem(struct page *page, unsigned char *mem, size_t bytes)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 10);
5162306a36Sopenharmony_ci	unsigned char *start;
5262306a36Sopenharmony_ci	unsigned char *end;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	start = memchr_inv(mem, PAGE_POISON, bytes);
5562306a36Sopenharmony_ci	if (!start)
5662306a36Sopenharmony_ci		return;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	for (end = mem + bytes - 1; end > start; end--) {
5962306a36Sopenharmony_ci		if (*end != PAGE_POISON)
6062306a36Sopenharmony_ci			break;
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (!__ratelimit(&ratelimit))
6462306a36Sopenharmony_ci		return;
6562306a36Sopenharmony_ci	else if (start == end && single_bit_flip(*start, PAGE_POISON))
6662306a36Sopenharmony_ci		pr_err("pagealloc: single bit error\n");
6762306a36Sopenharmony_ci	else
6862306a36Sopenharmony_ci		pr_err("pagealloc: memory corruption\n");
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, start,
7162306a36Sopenharmony_ci			end - start + 1, 1);
7262306a36Sopenharmony_ci	dump_stack();
7362306a36Sopenharmony_ci	dump_page(page, "pagealloc: corrupted page details");
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic void unpoison_page(struct page *page)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	void *addr;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	addr = kmap_atomic(page);
8162306a36Sopenharmony_ci	kasan_disable_current();
8262306a36Sopenharmony_ci	/*
8362306a36Sopenharmony_ci	 * Page poisoning when enabled poisons each and every page
8462306a36Sopenharmony_ci	 * that is freed to buddy. Thus no extra check is done to
8562306a36Sopenharmony_ci	 * see if a page was poisoned.
8662306a36Sopenharmony_ci	 */
8762306a36Sopenharmony_ci	check_poison_mem(page, kasan_reset_tag(addr), PAGE_SIZE);
8862306a36Sopenharmony_ci	kasan_enable_current();
8962306a36Sopenharmony_ci	kunmap_atomic(addr);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_civoid __kernel_unpoison_pages(struct page *page, int n)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	int i;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	for (i = 0; i < n; i++)
9762306a36Sopenharmony_ci		unpoison_page(page + i);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci#ifndef CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC
10162306a36Sopenharmony_civoid __kernel_map_pages(struct page *page, int numpages, int enable)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	/* This function does nothing, all work is done via poison pages */
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci#endif
106