arm64: add fault address to trapframe
It was previously possible for the fault address register to get
clobbered before it was saved. This small window occurred when an
additional exception was encountered inside the exception handler,
overwriting the previous value.
Commit f29942229d
("Read the arm64 far early in el0 exceptions")
patched this issue, but avoided changing the trapframe since this could
be considered a KBI change in FreeBSD 13.
Revert the above fix and save the fault address in the trapframe
instead. This saves the fault address even earlier in the exception
handling process, and is a more robust and simple fix.
Reviewed by: andrew, jhb, jrtc27
Sponsored by: Arm Ltd
Differential Revision: https://reviews.freebsd.org/D38984
This commit is contained in:
parent
2ecbbcc7ca
commit
f4036a9234
@ -65,9 +65,10 @@ __FBSDID("$FreeBSD$");
|
||||
mrs x10, elr_el1
|
||||
mrs x11, spsr_el1
|
||||
mrs x12, esr_el1
|
||||
mrs x13, far_el1
|
||||
stp x18, lr, [sp, #(TF_SP - TF_X)]!
|
||||
str x10, [sp, #(TF_ELR)]
|
||||
stp x11, x12, [sp, #(TF_SPSR)]
|
||||
stp x10, x11, [sp, #(TF_ELR)]
|
||||
stp x12, x13, [sp, #(TF_ESR)]
|
||||
mrs x18, tpidr_el1
|
||||
.endm
|
||||
|
||||
@ -211,25 +212,10 @@ ENTRY(handle_el1h_irq)
|
||||
END(handle_el1h_irq)
|
||||
|
||||
ENTRY(handle_el0_sync)
|
||||
/*
|
||||
* Read the fault address early. The current thread structure may
|
||||
* be transiently unmapped if it is part of a memory range being
|
||||
* promoted or demoted to/from a superpage. As this involves a
|
||||
* break-before-make sequence there is a short period of time where
|
||||
* an access will raise an exception. If this happens the fault
|
||||
* address will be changed to the kernel address so a later read of
|
||||
* far_el1 will give the wrong value.
|
||||
*
|
||||
* The earliest memory access that could trigger a fault is in a
|
||||
* function called by the save_registers macro so this is the latest
|
||||
* we can read the userspace value.
|
||||
*/
|
||||
mrs x19, far_el1
|
||||
save_registers 0
|
||||
ldr x0, [x18, #PC_CURTHREAD]
|
||||
mov x1, sp
|
||||
str x1, [x0, #TD_FRAME]
|
||||
mov x2, x19
|
||||
bl do_el0_sync
|
||||
do_ast
|
||||
restore_registers 0
|
||||
|
@ -77,4 +77,5 @@ ASSYM(TF_SIZE, sizeof(struct trapframe));
|
||||
ASSYM(TF_SP, offsetof(struct trapframe, tf_sp));
|
||||
ASSYM(TF_ELR, offsetof(struct trapframe, tf_elr));
|
||||
ASSYM(TF_SPSR, offsetof(struct trapframe, tf_spsr));
|
||||
ASSYM(TF_ESR, offsetof(struct trapframe, tf_esr));
|
||||
ASSYM(TF_X, offsetof(struct trapframe, tf_x));
|
||||
|
@ -76,7 +76,7 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
/* Called from exception.S */
|
||||
void do_el1h_sync(struct thread *, struct trapframe *);
|
||||
void do_el0_sync(struct thread *, struct trapframe *, uint64_t far);
|
||||
void do_el0_sync(struct thread *, struct trapframe *);
|
||||
void do_el0_error(struct trapframe *);
|
||||
void do_serror(struct trapframe *);
|
||||
void unhandled_exception(struct trapframe *);
|
||||
@ -465,6 +465,7 @@ do_el1h_sync(struct thread *td, struct trapframe *frame)
|
||||
uint64_t esr, far;
|
||||
int dfsc;
|
||||
|
||||
far = frame->tf_far;
|
||||
/* Read the esr register to get the exception details */
|
||||
esr = frame->tf_esr;
|
||||
exception = ESR_ELx_EXCEPTION(esr);
|
||||
@ -502,7 +503,6 @@ do_el1h_sync(struct thread *td, struct trapframe *frame)
|
||||
break;
|
||||
case EXCP_INSN_ABORT:
|
||||
case EXCP_DATA_ABORT:
|
||||
far = READ_SPECIALREG(far_el1);
|
||||
dfsc = esr & ISS_DATA_DFSC_MASK;
|
||||
if (dfsc < nitems(abort_handlers) &&
|
||||
abort_handlers[dfsc] != NULL) {
|
||||
@ -541,7 +541,7 @@ do_el1h_sync(struct thread *td, struct trapframe *frame)
|
||||
case EXCP_FPAC:
|
||||
/* We can see this if the authentication on PAC fails */
|
||||
print_registers(frame);
|
||||
printf(" far: %16lx\n", READ_SPECIALREG(far_el1));
|
||||
print_gp_register("far", far);
|
||||
panic("FPAC kernel exception");
|
||||
break;
|
||||
case EXCP_UNKNOWN:
|
||||
@ -552,18 +552,18 @@ do_el1h_sync(struct thread *td, struct trapframe *frame)
|
||||
/* FALLTHROUGH */
|
||||
default:
|
||||
print_registers(frame);
|
||||
print_gp_register("far", READ_SPECIALREG(far_el1));
|
||||
print_gp_register("far", far);
|
||||
panic("Unknown kernel exception %x esr_el1 %lx", exception,
|
||||
esr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
do_el0_sync(struct thread *td, struct trapframe *frame, uint64_t far)
|
||||
do_el0_sync(struct thread *td, struct trapframe *frame)
|
||||
{
|
||||
pcpu_bp_harden bp_harden;
|
||||
uint32_t exception;
|
||||
uint64_t esr;
|
||||
uint64_t esr, far;
|
||||
int dfsc;
|
||||
|
||||
/* Check we have a sane environment when entering from userland */
|
||||
@ -571,6 +571,7 @@ do_el0_sync(struct thread *td, struct trapframe *frame, uint64_t far)
|
||||
("Invalid pcpu address from userland: %p (tpidr %lx)",
|
||||
get_pcpu(), READ_SPECIALREG(tpidr_el1)));
|
||||
|
||||
far = frame->tf_far;
|
||||
esr = frame->tf_esr;
|
||||
exception = ESR_ELx_EXCEPTION(esr);
|
||||
if (exception == EXCP_INSN_ABORT_L && far > VM_MAXUSER_ADDRESS) {
|
||||
@ -711,7 +712,7 @@ do_serror(struct trapframe *frame)
|
||||
{
|
||||
uint64_t esr, far;
|
||||
|
||||
far = READ_SPECIALREG(far_el1);
|
||||
far = frame->tf_far;
|
||||
esr = frame->tf_esr;
|
||||
|
||||
print_registers(frame);
|
||||
@ -725,7 +726,7 @@ unhandled_exception(struct trapframe *frame)
|
||||
{
|
||||
uint64_t esr, far;
|
||||
|
||||
far = READ_SPECIALREG(far_el1);
|
||||
far = frame->tf_far;
|
||||
esr = frame->tf_esr;
|
||||
|
||||
print_registers(frame);
|
||||
|
@ -47,7 +47,7 @@ struct trapframe {
|
||||
uint64_t tf_elr;
|
||||
uint64_t tf_spsr;
|
||||
uint64_t tf_esr;
|
||||
uint64_t pad; /* struct must be 16B aligned */
|
||||
uint64_t tf_far;
|
||||
uint64_t tf_x[30];
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user