aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Gardner <rob.gardner@oracle.com>2015-12-22 21:16:07 -0700
committerDavid S. Miller <davem@davemloft.net>2015-12-24 12:12:46 -0500
commit833526941f5945cf0b22a595bb8f3525b512f654 (patch)
tree58bafcc903afdaffb84c2b89c7868a2aa49a9e33
parent3f74306ac84cf7f2da2fdc87014fc455f5e67bad (diff)
downloadlinux-stericsson-833526941f5945cf0b22a595bb8f3525b512f654.tar.gz
sparc64: Perf should save/restore fault info
There have been several reports of random processes being killed with a bus error or segfault during userspace stack walking in perf. One of the root causes of this problem is an asynchronous modification to thread_info fault_address and fault_code, which stems from a perf counter interrupt arriving during kernel processing of a "benign" fault, such as a TSB miss. Since perf_callchain_user() invokes copy_from_user() to read user stacks, a fault is not only possible, but probable. Validity checks on the stack address merely cover up the problem and reduce its frequency. The solution here is to save and restore fault_address and fault_code in perf_callchain_user() so that the benign fault handler is not disturbed by a perf interrupt. Signed-off-by: Rob Gardner <rob.gardner@oracle.com> Signed-off-by: Dave Aldridge <david.j.aldridge@oracle.com> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--arch/sparc/kernel/perf_event.c4
1 files changed, 4 insertions, 0 deletions
diff --git a/arch/sparc/kernel/perf_event.c b/arch/sparc/kernel/perf_event.c
index b1144d6acffe..6596f66ce112 100644
--- a/arch/sparc/kernel/perf_event.c
+++ b/arch/sparc/kernel/perf_event.c
@@ -1828,6 +1828,8 @@ static void perf_callchain_user_32(struct perf_callchain_entry *entry,
void
perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs)
{
+ u64 saved_fault_address = current_thread_info()->fault_address;
+ u8 saved_fault_code = get_thread_fault_code();
mm_segment_t old_fs;
perf_callchain_store(entry, regs->tpc);
@@ -1850,4 +1852,6 @@ perf_callchain_user(struct perf_callchain_entry *entry, struct pt_regs *regs)
pagefault_enable();
set_fs(old_fs);
+ set_thread_fault_code(saved_fault_code);
+ current_thread_info()->fault_address = saved_fault_address;
}