mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +02:00
The pstore ftrace frontend works by purely collecting the instruction address, saving it on the persistent area through the backend and when the log is read, on next boot for example, the address is then resolved by using the regular printk symbol lookup (%pS for example). Problem: if we are running a relocatable kernel with KASLR enabled, this is a recipe for failure in the symbol resolution on next boots, since the addresses are offset'ed by the KASLR address. So, naturally the way to go is factor the KASLR address out of instruction address collection, and adding the fresh offset when resolving the symbol on future boots. Problem #2: modules also have varying addresses that float based on module base address and potentially the module ordering in memory, meaning factoring KASLR offset for them is useless. So, let's hereby only take KASLR offset into account for core kernel addresses, leaving module ones as is. And we have yet a 3rd complexity: not necessarily the check range for core kernel addresses holds true on future boots, since the module base address will vary. With that, the choice was to mark the addresses as being core vs module based on its MSB. And with that... ...we have the 4th challenge here: for some "simple" architectures, the CPU number is saved bit-encoded on the instruction pointer, to allow bigger timestamps - this is set through the PSTORE_CPU_IN_IP define for such architectures. Hence, the approach here is to skip such architectures (at least in a first moment). Finished? No. On top of all previous complexities, we have one extra pain point: kaslr_offset() is inlined and fully "resolved" at boot-time, after kernel decompression, through ELF relocation mechanism. Once the offset is known, it's patched to the kernel text area, wherever it is used. The mechanism, and its users, are only built-in - incompatible with module usage. Though there are possibly some hacks (as computing the offset using some kallsym lookup), the choice here is to restrict this optimization to the (hopefully common) case of CONFIG_PSTORE=y. TL;DR: let's factor KASLR offsets on pstore/ftrace for core kernel addresses, only when PSTORE is built-in and leaving module addresses out, as well as architectures that define PSTORE_CPU_IN_IP. Signed-off-by: Guilherme G. Piccoli <gpiccoli@igalia.com> Link: https://patch.msgid.link/20260410205848.2607169-1-gpiccoli@igalia.com Signed-off-by: Kees Cook <kees@kernel.org>
231 lines
5.3 KiB
C
231 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2012 Google, Inc.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/compiler.h>
|
|
#include <linux/irqflags.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/err.h>
|
|
#include <linux/cache.h>
|
|
#include <linux/slab.h>
|
|
#include <asm/barrier.h>
|
|
#include <asm/setup.h>
|
|
#include "internal.h"
|
|
|
|
/* This doesn't need to be atomic: speed is chosen over correctness here. */
|
|
static u64 pstore_ftrace_stamp;
|
|
|
|
static inline unsigned long adjust_ip(unsigned long ip)
|
|
{
|
|
#if defined(CONFIG_RANDOMIZE_BASE) && !defined(PSTORE_CPU_IN_IP) && IS_BUILTIN(CONFIG_PSTORE)
|
|
if (core_kernel_text(ip))
|
|
return ip - kaslr_offset();
|
|
|
|
__clear_bit(BITS_PER_LONG - 1, &ip);
|
|
#endif
|
|
return ip;
|
|
}
|
|
|
|
inline unsigned long decode_ip(unsigned long ip)
|
|
{
|
|
#if defined(CONFIG_RANDOMIZE_BASE) && !defined(PSTORE_CPU_IN_IP) && IS_BUILTIN(CONFIG_PSTORE)
|
|
if (test_bit(BITS_PER_LONG - 1, &ip))
|
|
return ip + kaslr_offset();
|
|
|
|
__set_bit(BITS_PER_LONG - 1, &ip);
|
|
|
|
#endif
|
|
return ip;
|
|
}
|
|
|
|
static void notrace pstore_ftrace_call(unsigned long ip,
|
|
unsigned long parent_ip,
|
|
struct ftrace_ops *op,
|
|
struct ftrace_regs *fregs)
|
|
{
|
|
int bit;
|
|
unsigned long flags;
|
|
struct pstore_ftrace_record rec = {};
|
|
struct pstore_record record = {
|
|
.type = PSTORE_TYPE_FTRACE,
|
|
.buf = (char *)&rec,
|
|
.size = sizeof(rec),
|
|
.psi = psinfo,
|
|
};
|
|
|
|
if (unlikely(oops_in_progress))
|
|
return;
|
|
|
|
bit = ftrace_test_recursion_trylock(ip, parent_ip);
|
|
if (bit < 0)
|
|
return;
|
|
|
|
local_irq_save(flags);
|
|
|
|
rec.ip = adjust_ip(ip);
|
|
rec.parent_ip = adjust_ip(parent_ip);
|
|
pstore_ftrace_write_timestamp(&rec, pstore_ftrace_stamp++);
|
|
pstore_ftrace_encode_cpu(&rec, raw_smp_processor_id());
|
|
psinfo->write(&record);
|
|
|
|
local_irq_restore(flags);
|
|
ftrace_test_recursion_unlock(bit);
|
|
}
|
|
|
|
static struct ftrace_ops pstore_ftrace_ops __read_mostly = {
|
|
.func = pstore_ftrace_call,
|
|
};
|
|
|
|
static DEFINE_MUTEX(pstore_ftrace_lock);
|
|
static bool record_ftrace;
|
|
module_param(record_ftrace, bool, 0400);
|
|
MODULE_PARM_DESC(record_ftrace,
|
|
"enable ftrace recording immediately (default: off)");
|
|
|
|
static int pstore_set_ftrace_enabled(bool on)
|
|
{
|
|
ssize_t ret;
|
|
|
|
if (on == record_ftrace)
|
|
return 0;
|
|
|
|
if (on) {
|
|
ftrace_ops_set_global_filter(&pstore_ftrace_ops);
|
|
ret = register_ftrace_function(&pstore_ftrace_ops);
|
|
} else {
|
|
ret = unregister_ftrace_function(&pstore_ftrace_ops);
|
|
}
|
|
|
|
if (ret) {
|
|
pr_err("%s: unable to %sregister ftrace ops: %zd\n",
|
|
__func__, on ? "" : "un", ret);
|
|
} else {
|
|
record_ftrace = on;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t pstore_ftrace_knob_write(struct file *f, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
u8 on;
|
|
ssize_t ret;
|
|
|
|
ret = kstrtou8_from_user(buf, count, 2, &on);
|
|
if (ret)
|
|
return ret;
|
|
|
|
mutex_lock(&pstore_ftrace_lock);
|
|
ret = pstore_set_ftrace_enabled(on);
|
|
mutex_unlock(&pstore_ftrace_lock);
|
|
|
|
if (ret == 0)
|
|
ret = count;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t pstore_ftrace_knob_read(struct file *f, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
char val[] = { '0' + record_ftrace, '\n' };
|
|
|
|
return simple_read_from_buffer(buf, count, ppos, val, sizeof(val));
|
|
}
|
|
|
|
static const struct file_operations pstore_knob_fops = {
|
|
.open = simple_open,
|
|
.read = pstore_ftrace_knob_read,
|
|
.write = pstore_ftrace_knob_write,
|
|
};
|
|
|
|
static struct dentry *pstore_ftrace_dir;
|
|
|
|
void pstore_register_ftrace(void)
|
|
{
|
|
if (!psinfo->write)
|
|
return;
|
|
|
|
pstore_ftrace_dir = debugfs_create_dir("pstore", NULL);
|
|
|
|
pstore_set_ftrace_enabled(record_ftrace);
|
|
|
|
debugfs_create_file("record_ftrace", 0600, pstore_ftrace_dir, NULL,
|
|
&pstore_knob_fops);
|
|
}
|
|
|
|
void pstore_unregister_ftrace(void)
|
|
{
|
|
mutex_lock(&pstore_ftrace_lock);
|
|
if (record_ftrace) {
|
|
unregister_ftrace_function(&pstore_ftrace_ops);
|
|
record_ftrace = false;
|
|
}
|
|
mutex_unlock(&pstore_ftrace_lock);
|
|
|
|
debugfs_remove_recursive(pstore_ftrace_dir);
|
|
}
|
|
|
|
ssize_t pstore_ftrace_combine_log(char **dest_log, size_t *dest_log_size,
|
|
const char *src_log, size_t src_log_size)
|
|
{
|
|
size_t dest_size, src_size, total, dest_off, src_off;
|
|
size_t dest_idx = 0, src_idx = 0, merged_idx = 0;
|
|
void *merged_buf;
|
|
struct pstore_ftrace_record *drec, *srec, *mrec;
|
|
size_t record_size = sizeof(struct pstore_ftrace_record);
|
|
|
|
dest_off = *dest_log_size % record_size;
|
|
dest_size = *dest_log_size - dest_off;
|
|
|
|
src_off = src_log_size % record_size;
|
|
src_size = src_log_size - src_off;
|
|
|
|
total = dest_size + src_size;
|
|
merged_buf = kmalloc(total, GFP_KERNEL);
|
|
if (!merged_buf)
|
|
return -ENOMEM;
|
|
|
|
drec = (struct pstore_ftrace_record *)(*dest_log + dest_off);
|
|
srec = (struct pstore_ftrace_record *)(src_log + src_off);
|
|
mrec = (struct pstore_ftrace_record *)(merged_buf);
|
|
|
|
while (dest_size > 0 && src_size > 0) {
|
|
if (pstore_ftrace_read_timestamp(&drec[dest_idx]) <
|
|
pstore_ftrace_read_timestamp(&srec[src_idx])) {
|
|
mrec[merged_idx++] = drec[dest_idx++];
|
|
dest_size -= record_size;
|
|
} else {
|
|
mrec[merged_idx++] = srec[src_idx++];
|
|
src_size -= record_size;
|
|
}
|
|
}
|
|
|
|
while (dest_size > 0) {
|
|
mrec[merged_idx++] = drec[dest_idx++];
|
|
dest_size -= record_size;
|
|
}
|
|
|
|
while (src_size > 0) {
|
|
mrec[merged_idx++] = srec[src_idx++];
|
|
src_size -= record_size;
|
|
}
|
|
|
|
kfree(*dest_log);
|
|
*dest_log = merged_buf;
|
|
*dest_log_size = total;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pstore_ftrace_combine_log);
|