Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-03-01 Thread Madhavan T. Venkataraman



On 2/25/21 5:58 AM, Mark Rutland wrote:
> On Wed, Feb 24, 2021 at 01:34:09PM -0600, Madhavan T. Venkataraman wrote:
>> On 2/24/21 6:17 AM, Mark Rutland wrote:
>>> On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com 
>>> wrote:
 From: "Madhavan T. Venkataraman" 
Termination
===

Currently, the unwinder terminates when both the FP (frame pointer)
and the PC (return address) of a frame are 0. But a frame could get
corrupted and zeroed. There needs to be a better check.

The following special terminating frame and function have been
defined for this purpose:

const u64arm64_last_frame[2] __attribute__ ((aligned (16)));

void arm64_last_func(void)
{
}

So, set the FP to arm64_last_frame and the PC to arm64_last_func in
the bottom most frame.
>>>
>>> My expectation was that we'd do this per-task, creating an empty frame
>>> record (i.e. with fp=NULL and lr=NULL) on the task's stack at the
>>> instant it was created, and chaining this into x29. That way the address
>>> is known (since it can be derived from the task), and the frame will
>>> also implicitly check that the callchain terminates on the task stack
>>> without loops. That also means that we can use it to detect the entry
>>> code going wrong (e.g. if the SP gets corrupted), since in that case the
>>> entry code would place the record at a different location.
>>
>> That is exactly what this is doing. arm64_last_frame[] is a marker frame
>> that contains fp=0 and pc=0.
> 
> Almost! What I meant was that rather that each task should have its own
> final/marker frame record on its task task rather than sharing a common
> global variable.
> 
> That way a check for that frame record implicitly checks that a task
> started at the expected location *on that stack*, which catches
> additional stack corruption cases (e.g. if we left data on the stack
> prior to an EL0 entry).
> 

Ok. I will think about this.

> [...]
> 
>>> ... I reckon once we've moved the last of the exception triage out to C
>>> this will be relatively simple, since all of the exception handlers will
>>> look like:
>>>
>>> | SYM_CODE_START_LOCAL(elX_exception)
>>> |   kernel_entry X
>>> |   mov x0, sp
>>> |   bl  elX_exception_handler
>>> |   kernel_exit X
>>> | SYM_CODE_END(elX_exception)
>>>
>>> ... and so we just need to identify the set of elX_exception functions
>>> (which we'll never expect to take exceptions from directly). We could be
>>> strict and reject unwinding into arbitrary bits of the entry code (e.g.
>>> if we took an unexpected exception), and only permit unwinding to the
>>> BL.
>>>
It can do this if the FP and PC are also recorded elsewhere in the
pt_regs for comparison. Currently, the FP is also stored in
regs->regs[29]. The PC is stored in regs->pc. However, regs->pc can
be changed by lower level functions.

Create a new field, pt_regs->orig_pc, and record the return address
PC there. With this, the unwinder can validate the exception frame
and set a flag so that the caller of the unwinder can know when
an exception frame is encountered.
>>>
>>> I don't understand the case you're trying to solve here. When is
>>> regs->pc changed in a way that's problematic?
>>>
>>
>> For instance, I used a test driver in which the driver calls a function
>> pointer which is NULL. The low level fault handler sends a signal to the
>> task. Looks like it changes regs->pc for this.
> 
> I'm struggling to follow what you mean here.
> 
> If the kernel branches to NULL, the CPU should raise an instruction
> abort from the current EL, and our handling of that should terminate the
> thread via die_kernel_fault(), without returning to the faulting
> context, and without altering the PC in the faulting context.
> 
> Signals are delivered to userspace and alter the userspace PC, not a
> kernel PC, so this doesn't seem relevant. Do you mean an exception fixup
> handler rather than a signal?
> 
>> When I dump the stack from the low level handler, the comparison with
>> regs->pc does not work.  But comparison with regs->orig_pc works.
> 
> As above, I'm struggling with this; could you share a concrete example? 
> 

Actually, my bad. I needed the orig_pc because of something that my test
driver did. And, it slipped my mind entirely.

Thanks for pointing it out. I will fix this in my resend.

Thanks again for your comments.

I am currently studying probing/tracing. As soon as I have a solution for that,
I will send out the next version. I look forward to the in-depth review.

Thanks,

Madhavan


Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-25 Thread Mark Rutland
On Wed, Feb 24, 2021 at 01:34:09PM -0600, Madhavan T. Venkataraman wrote:
> On 2/24/21 6:17 AM, Mark Rutland wrote:
> > On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com 
> > wrote:
> >> From: "Madhavan T. Venkataraman" 
> >>Termination
> >>===
> >>
> >>Currently, the unwinder terminates when both the FP (frame pointer)
> >>and the PC (return address) of a frame are 0. But a frame could get
> >>corrupted and zeroed. There needs to be a better check.
> >>
> >>The following special terminating frame and function have been
> >>defined for this purpose:
> >>
> >>const u64arm64_last_frame[2] __attribute__ ((aligned (16)));
> >>
> >>void arm64_last_func(void)
> >>{
> >>}
> >>
> >>So, set the FP to arm64_last_frame and the PC to arm64_last_func in
> >>the bottom most frame.
> > 
> > My expectation was that we'd do this per-task, creating an empty frame
> > record (i.e. with fp=NULL and lr=NULL) on the task's stack at the
> > instant it was created, and chaining this into x29. That way the address
> > is known (since it can be derived from the task), and the frame will
> > also implicitly check that the callchain terminates on the task stack
> > without loops. That also means that we can use it to detect the entry
> > code going wrong (e.g. if the SP gets corrupted), since in that case the
> > entry code would place the record at a different location.
> 
> That is exactly what this is doing. arm64_last_frame[] is a marker frame
> that contains fp=0 and pc=0.

Almost! What I meant was that rather that each task should have its own
final/marker frame record on its task task rather than sharing a common
global variable.

That way a check for that frame record implicitly checks that a task
started at the expected location *on that stack*, which catches
additional stack corruption cases (e.g. if we left data on the stack
prior to an EL0 entry).

[...]

> > ... I reckon once we've moved the last of the exception triage out to C
> > this will be relatively simple, since all of the exception handlers will
> > look like:
> > 
> > | SYM_CODE_START_LOCAL(elX_exception)
> > |   kernel_entry X
> > |   mov x0, sp
> > |   bl  elX_exception_handler
> > |   kernel_exit X
> > | SYM_CODE_END(elX_exception)
> > 
> > ... and so we just need to identify the set of elX_exception functions
> > (which we'll never expect to take exceptions from directly). We could be
> > strict and reject unwinding into arbitrary bits of the entry code (e.g.
> > if we took an unexpected exception), and only permit unwinding to the
> > BL.
> > 
> >>It can do this if the FP and PC are also recorded elsewhere in the
> >>pt_regs for comparison. Currently, the FP is also stored in
> >>regs->regs[29]. The PC is stored in regs->pc. However, regs->pc can
> >>be changed by lower level functions.
> >>
> >>Create a new field, pt_regs->orig_pc, and record the return address
> >>PC there. With this, the unwinder can validate the exception frame
> >>and set a flag so that the caller of the unwinder can know when
> >>an exception frame is encountered.
> > 
> > I don't understand the case you're trying to solve here. When is
> > regs->pc changed in a way that's problematic?
> > 
> 
> For instance, I used a test driver in which the driver calls a function
> pointer which is NULL. The low level fault handler sends a signal to the
> task. Looks like it changes regs->pc for this.

I'm struggling to follow what you mean here.

If the kernel branches to NULL, the CPU should raise an instruction
abort from the current EL, and our handling of that should terminate the
thread via die_kernel_fault(), without returning to the faulting
context, and without altering the PC in the faulting context.

Signals are delivered to userspace and alter the userspace PC, not a
kernel PC, so this doesn't seem relevant. Do you mean an exception fixup
handler rather than a signal?

> When I dump the stack from the low level handler, the comparison with
> regs->pc does not work.  But comparison with regs->orig_pc works.

As above, I'm struggling with this; could you share a concrete example? 

Thanks,
Mark.


Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-24 Thread Madhavan T. Venkataraman



On 2/24/21 6:17 AM, Mark Rutland wrote:
> Hi Madhavan,
> 
> As Mark Brown says, I think this needs to be split into several
> patches. i have some comments on the general approach, but I'll save
> in-depth review until this has been split.
> 

OK.

> On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com wrote:
>> From: "Madhavan T. Venkataraman" 
>>
>> Unwinder changes
>> 
>>
>>  Termination
>>  ===
>>
>>  Currently, the unwinder terminates when both the FP (frame pointer)
>>  and the PC (return address) of a frame are 0. But a frame could get
>>  corrupted and zeroed. There needs to be a better check.
>>
>>  The following special terminating frame and function have been
>>  defined for this purpose:
>>
>>  const u64arm64_last_frame[2] __attribute__ ((aligned (16)));
>>
>>  void arm64_last_func(void)
>>  {
>>  }
>>
>>  So, set the FP to arm64_last_frame and the PC to arm64_last_func in
>>  the bottom most frame.
> 
> My expectation was that we'd do this per-task, creating an empty frame
> record (i.e. with fp=NULL and lr=NULL) on the task's stack at the
> instant it was created, and chaining this into x29. That way the address
> is known (since it can be derived from the task), and the frame will
> also implicitly check that the callchain terminates on the task stack
> without loops. That also means that we can use it to detect the entry
> code going wrong (e.g. if the SP gets corrupted), since in that case the
> entry code would place the record at a different location.
> 

That is exactly what this is doing. arm64_last_frame[] is a marker frame
that contains fp=0 and pc=0.

>>
>>  Exception/Interrupt detection
>>  =
>>
>>  An EL1 exception renders the stack trace unreliable as it can happen
>>  anywhere including the frame pointer prolog and epilog. The
>>  unwinder needs to be able to detect the exception on the stack.
>>
>>  Currently, the EL1 exception handler sets up pt_regs on the stack
>>  and chains pt_regs->stackframe with the other frames on the stack.
>>  But, the unwinder does not know where this exception frame is in
>>  the stack trace.
>>
>>  Set the LSB of the exception frame FP to allow the unwinder to
>>  detect the exception frame. When the unwinder detects the frame,
>>  it needs to make sure that it is really an exception frame and
>>  not the result of any stack corruption.
> 
> I'm not keen on messing with the encoding of the frame record as this
> will break external unwinders (e.g. using GDB on a kernel running under
> QEMU). I'd rather that we detected the exception boundary based on the
> LR, similar to what we did in commit:
> 

OK. I will take a look at the commit you mentioned.

>   7326749801396105 ("arm64: unwind: reference pt_regs via embedded stack 
> frame")
> 
> ... I reckon once we've moved the last of the exception triage out to C
> this will be relatively simple, since all of the exception handlers will
> look like:
> 
> | SYM_CODE_START_LOCAL(elX_exception)
> | kernel_entry X
> | mov x0, sp
> | bl  elX_exception_handler
> | kernel_exit X
> | SYM_CODE_END(elX_exception)
> 
> ... and so we just need to identify the set of elX_exception functions
> (which we'll never expect to take exceptions from directly). We could be
> strict and reject unwinding into arbitrary bits of the entry code (e.g.
> if we took an unexpected exception), and only permit unwinding to the
> BL.
> 
>>  It can do this if the FP and PC are also recorded elsewhere in the
>>  pt_regs for comparison. Currently, the FP is also stored in
>>  regs->regs[29]. The PC is stored in regs->pc. However, regs->pc can
>>  be changed by lower level functions.
>>
>>  Create a new field, pt_regs->orig_pc, and record the return address
>>  PC there. With this, the unwinder can validate the exception frame
>>  and set a flag so that the caller of the unwinder can know when
>>  an exception frame is encountered.
> 
> I don't understand the case you're trying to solve here. When is
> regs->pc changed in a way that's problematic?
> 

For instance, I used a test driver in which the driver calls a function
pointer which is NULL. The low level fault handler sends a signal to the
task. Looks like it changes regs->pc for this. When I dump the stack
from the low level handler, the comparison with regs->pc does not work.
But comparison with regs->orig_pc works.

>>  Unwinder return value
>>  =
>>
>>  Currently, the unwinder returns -EINVAL for stack trace termination
>>  as well as stack trace error. Return -ENOENT for stack trace
>>  termination and -EINVAL for error to disambiguate. This idea has
>>  been borrowed from Mark Brown.
> 
> IIRC Mark Brown already has a patch for this (and it could be queued on
> its own if it hasn't already been).
> 

I 

Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-24 Thread Madhavan T. Venkataraman



On 2/24/21 6:33 AM, Mark Brown wrote:
> On Tue, Feb 23, 2021 at 01:20:49PM -0600, Madhavan T. Venkataraman wrote:
>> On 2/23/21 1:02 PM, Mark Brown wrote:
>>> On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com 
>>> wrote:
> 
 Reliable stack trace function
 =

 Implement arch_stack_walk_reliable(). This function walks the stack like
 the existing stack trace functions with a couple of additional checks:
> 
>>> Again, this should be at least one separate patch.  How does this ensure
>>> that we don't have any issues with any of the various probe mechanisms?
>>> If there's no need to explicitly check anything that should be called
>>> out in the changelog.
> 
>> I am trying to do this in an incremental fashion. I have to study the probe
>> mechanisms a little bit more before I can come up with a solution. But
>> if you want to see that addressed in this patch set, I could do that.
>> It will take a little bit of time. That is all.
> 
> Handling of the probes stuff seems like it's critical to reliable stack
> walk so we shouldn't claim to have support for reliable stack walk
> without it.  If it was a working implementation we could improve that'd
> be one thing but this would be buggy which is a different thing.
> 

OK. I will address the probe stuff in my resend.

 +  (void) on_accessible_stack(task, stackframe, );
> 
>>> Shouldn't we return NULL if we are not on an accessible stack?
> 
>> The prev_fp has already been checked by the unwinder in the previous
>> frame. That is why I don't check the return value. If that is acceptable,
>> I will add a comment.
> 
> TBH if you're adding the comment it seems like you may as well add the
> check, it's not like it's expensive and it means there's no possibility
> that some future change could result in this assumption being broken.
> 

OK. I will add the check.

Thanks.

Madhavan


Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-24 Thread Mark Brown
On Tue, Feb 23, 2021 at 01:20:49PM -0600, Madhavan T. Venkataraman wrote:
> On 2/23/21 1:02 PM, Mark Brown wrote:
> > On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com 
> > wrote:

> >> Reliable stack trace function
> >> =
> >>
> >> Implement arch_stack_walk_reliable(). This function walks the stack like
> >> the existing stack trace functions with a couple of additional checks:

> > Again, this should be at least one separate patch.  How does this ensure
> > that we don't have any issues with any of the various probe mechanisms?
> > If there's no need to explicitly check anything that should be called
> > out in the changelog.

> I am trying to do this in an incremental fashion. I have to study the probe
> mechanisms a little bit more before I can come up with a solution. But
> if you want to see that addressed in this patch set, I could do that.
> It will take a little bit of time. That is all.

Handling of the probes stuff seems like it's critical to reliable stack
walk so we shouldn't claim to have support for reliable stack walk
without it.  If it was a working implementation we could improve that'd
be one thing but this would be buggy which is a different thing.

> >> +  (void) on_accessible_stack(task, stackframe, );

> > Shouldn't we return NULL if we are not on an accessible stack?

> The prev_fp has already been checked by the unwinder in the previous
> frame. That is why I don't check the return value. If that is acceptable,
> I will add a comment.

TBH if you're adding the comment it seems like you may as well add the
check, it's not like it's expensive and it means there's no possibility
that some future change could result in this assumption being broken.


signature.asc
Description: PGP signature


Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-24 Thread Mark Rutland
Hi Madhavan,

As Mark Brown says, I think this needs to be split into several
patches. i have some comments on the general approach, but I'll save
in-depth review until this has been split.

On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com wrote:
> From: "Madhavan T. Venkataraman" 
> 
> Unwinder changes
> 
> 
>   Termination
>   ===
> 
>   Currently, the unwinder terminates when both the FP (frame pointer)
>   and the PC (return address) of a frame are 0. But a frame could get
>   corrupted and zeroed. There needs to be a better check.
> 
>   The following special terminating frame and function have been
>   defined for this purpose:
> 
>   const u64arm64_last_frame[2] __attribute__ ((aligned (16)));
> 
>   void arm64_last_func(void)
>   {
>   }
> 
>   So, set the FP to arm64_last_frame and the PC to arm64_last_func in
>   the bottom most frame.

My expectation was that we'd do this per-task, creating an empty frame
record (i.e. with fp=NULL and lr=NULL) on the task's stack at the
instant it was created, and chaining this into x29. That way the address
is known (since it can be derived from the task), and the frame will
also implicitly check that the callchain terminates on the task stack
without loops. That also means that we can use it to detect the entry
code going wrong (e.g. if the SP gets corrupted), since in that case the
entry code would place the record at a different location.

> 
>   Exception/Interrupt detection
>   =
> 
>   An EL1 exception renders the stack trace unreliable as it can happen
>   anywhere including the frame pointer prolog and epilog. The
>   unwinder needs to be able to detect the exception on the stack.
> 
>   Currently, the EL1 exception handler sets up pt_regs on the stack
>   and chains pt_regs->stackframe with the other frames on the stack.
>   But, the unwinder does not know where this exception frame is in
>   the stack trace.
> 
>   Set the LSB of the exception frame FP to allow the unwinder to
>   detect the exception frame. When the unwinder detects the frame,
>   it needs to make sure that it is really an exception frame and
>   not the result of any stack corruption.

I'm not keen on messing with the encoding of the frame record as this
will break external unwinders (e.g. using GDB on a kernel running under
QEMU). I'd rather that we detected the exception boundary based on the
LR, similar to what we did in commit:

  7326749801396105 ("arm64: unwind: reference pt_regs via embedded stack frame")

... I reckon once we've moved the last of the exception triage out to C
this will be relatively simple, since all of the exception handlers will
look like:

| SYM_CODE_START_LOCAL(elX_exception)
|   kernel_entry X
|   mov x0, sp
|   bl  elX_exception_handler
|   kernel_exit X
| SYM_CODE_END(elX_exception)

... and so we just need to identify the set of elX_exception functions
(which we'll never expect to take exceptions from directly). We could be
strict and reject unwinding into arbitrary bits of the entry code (e.g.
if we took an unexpected exception), and only permit unwinding to the
BL.

>   It can do this if the FP and PC are also recorded elsewhere in the
>   pt_regs for comparison. Currently, the FP is also stored in
>   regs->regs[29]. The PC is stored in regs->pc. However, regs->pc can
>   be changed by lower level functions.
> 
>   Create a new field, pt_regs->orig_pc, and record the return address
>   PC there. With this, the unwinder can validate the exception frame
>   and set a flag so that the caller of the unwinder can know when
>   an exception frame is encountered.

I don't understand the case you're trying to solve here. When is
regs->pc changed in a way that's problematic?

>   Unwinder return value
>   =
> 
>   Currently, the unwinder returns -EINVAL for stack trace termination
>   as well as stack trace error. Return -ENOENT for stack trace
>   termination and -EINVAL for error to disambiguate. This idea has
>   been borrowed from Mark Brown.

IIRC Mark Brown already has a patch for this (and it could be queued on
its own if it hasn't already been).

Thanks,
Mark.

> 
> Reliable stack trace function
> =
> 
> Implement arch_stack_walk_reliable(). This function walks the stack like
> the existing stack trace functions with a couple of additional checks:
> 
>   Return address check
>   
> 
>   For each frame, check the return address to see if it is a
>   proper kernel text address. If not, return -EINVAL.
> 
>   Exception frame check
>   -
> 
>   Check each frame to see if it is an EL1 exception frame. If it is,
>   return -EINVAL.
> 
> Signed-off-by: Madhavan T. Venkataraman 
> 

Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-23 Thread Madhavan T. Venkataraman



On 2/23/21 1:02 PM, Mark Brown wrote:
> On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com wrote:
>> From: "Madhavan T. Venkataraman" 
>>
>> Unwinder changes
>> 
> 
> This is making several different changes so should be split into a patch
> series - for example the change to terminate on a specific function
> pointer rather than NULL and the changes to the exception/interupt
> detection should be split.  Please see submitting-patches.rst for some
> discussion about how to split things up.  In general if you've got a
> changelog enumerating a number of different changes in a patch that's a
> warning sign that it might be good split things up.
> 

Will do.

> You should also copy the architecture maintainers (Catalin and Will) on
> any arch/arm64 submissions.
> 

Will do when I resubmit.

>>  Unwinder return value
>>  =
>>
>>  Currently, the unwinder returns -EINVAL for stack trace termination
>>  as well as stack trace error. Return -ENOENT for stack trace
>>  termination and -EINVAL for error to disambiguate. This idea has
>>  been borrowed from Mark Brown.
> 
> You could just include my patch for this in your series.
> 

OK.

>> Reliable stack trace function
>> =
>>
>> Implement arch_stack_walk_reliable(). This function walks the stack like
>> the existing stack trace functions with a couple of additional checks:
>>
>>  Return address check
>>  
>>
>>  For each frame, check the return address to see if it is a
>>  proper kernel text address. If not, return -EINVAL.
>>
>>  Exception frame check
>>  -
>>
>>  Check each frame to see if it is an EL1 exception frame. If it is,
>>  return -EINVAL.
> 
> Again, this should be at least one separate patch.  How does this ensure
> that we don't have any issues with any of the various probe mechanisms?
> If there's no need to explicitly check anything that should be called
> out in the changelog.
> 

I am trying to do this in an incremental fashion. I have to study the probe
mechanisms a little bit more before I can come up with a solution. But
if you want to see that addressed in this patch set, I could do that.
It will take a little bit of time. That is all.

> Since all these changes are mixed up this is a fairly superficial
> review of the actual code.
> 

Understood. I will split things up and we can take it from there.

>> +static notrace struct pt_regs *get_frame_regs(struct task_struct *task,
>> +  struct stackframe *frame)
>> +{
>> +unsigned long stackframe, regs_start, regs_end;
>> +struct stack_info info;
>> +
>> +stackframe = frame->prev_fp;
>> +if (!stackframe)
>> +return NULL;
>> +
>> +(void) on_accessible_stack(task, stackframe, );
> 
> Shouldn't we return NULL if we are not on an accessible stack?
> 

The prev_fp has already been checked by the unwinder in the previous
frame. That is why I don't check the return value. If that is acceptable,
I will add a comment.

>> +static notrace int update_frame(struct task_struct *task,
>> +struct stackframe *frame)
> 
> This function really needs some documentation, the function is just
> called update_frame() which doesn't say what sort of updates it's
> supposed to do and most of the checks aren't explained, not all of them
> are super obvious.
> 

I will add the documentation as well as try think of a better name.

>> +{
>> +unsigned long lsb = frame->fp & 0xf;
>> +unsigned long fp = frame->fp & ~lsb;
>> +unsigned long pc = frame->pc;
>> +struct pt_regs *regs;
>> +
>> +frame->exception_frame = false;
>> +
>> +if (fp == (unsigned long) arm64_last_frame &&
>> +pc == (unsigned long) arm64_last_func)
>> +return -ENOENT;
>> +
>> +if (!lsb)
>> +return 0;
>> +if (lsb != 1)
>> +return -EINVAL;
>> +
>> +/*
>> + * This looks like an EL1 exception frame.
> 
> For clarity it would be good to spell out the properties of an EL1
> exception frame.  It is not clear to me why we don't reference the frame
> type information the unwinder already records as part of these checks.
> 
> In general, especially for the bits specific to reliable stack trace, I
> think we want to err on the side of verbosity here so that it is crystal
> clear what all the checks are supposed to be doing and it's that much
> easier to tie everything through to the requirements document.

OK. I will improve the documentation.

Madhavan


Re: [RFC PATCH v1 1/1] arm64: Unwinder enhancements for reliable stack trace

2021-02-23 Thread Mark Brown
On Tue, Feb 23, 2021 at 12:12:43PM -0600, madve...@linux.microsoft.com wrote:
> From: "Madhavan T. Venkataraman" 
> 
> Unwinder changes
> 

This is making several different changes so should be split into a patch
series - for example the change to terminate on a specific function
pointer rather than NULL and the changes to the exception/interupt
detection should be split.  Please see submitting-patches.rst for some
discussion about how to split things up.  In general if you've got a
changelog enumerating a number of different changes in a patch that's a
warning sign that it might be good split things up.

You should also copy the architecture maintainers (Catalin and Will) on
any arch/arm64 submissions.

>   Unwinder return value
>   =
> 
>   Currently, the unwinder returns -EINVAL for stack trace termination
>   as well as stack trace error. Return -ENOENT for stack trace
>   termination and -EINVAL for error to disambiguate. This idea has
>   been borrowed from Mark Brown.

You could just include my patch for this in your series.

> Reliable stack trace function
> =
> 
> Implement arch_stack_walk_reliable(). This function walks the stack like
> the existing stack trace functions with a couple of additional checks:
> 
>   Return address check
>   
> 
>   For each frame, check the return address to see if it is a
>   proper kernel text address. If not, return -EINVAL.
> 
>   Exception frame check
>   -
> 
>   Check each frame to see if it is an EL1 exception frame. If it is,
>   return -EINVAL.

Again, this should be at least one separate patch.  How does this ensure
that we don't have any issues with any of the various probe mechanisms?
If there's no need to explicitly check anything that should be called
out in the changelog.

Since all these changes are mixed up this is a fairly superficial
review of the actual code.

> +static notrace struct pt_regs *get_frame_regs(struct task_struct *task,
> +   struct stackframe *frame)
> +{
> + unsigned long stackframe, regs_start, regs_end;
> + struct stack_info info;
> +
> + stackframe = frame->prev_fp;
> + if (!stackframe)
> + return NULL;
> +
> + (void) on_accessible_stack(task, stackframe, );

Shouldn't we return NULL if we are not on an accessible stack?

> +static notrace int update_frame(struct task_struct *task,
> + struct stackframe *frame)

This function really needs some documentation, the function is just
called update_frame() which doesn't say what sort of updates it's
supposed to do and most of the checks aren't explained, not all of them
are super obvious.

> +{
> + unsigned long lsb = frame->fp & 0xf;
> + unsigned long fp = frame->fp & ~lsb;
> + unsigned long pc = frame->pc;
> + struct pt_regs *regs;
> +
> + frame->exception_frame = false;
> +
> + if (fp == (unsigned long) arm64_last_frame &&
> + pc == (unsigned long) arm64_last_func)
> + return -ENOENT;
> +
> + if (!lsb)
> + return 0;
> + if (lsb != 1)
> + return -EINVAL;
> +
> + /*
> +  * This looks like an EL1 exception frame.

For clarity it would be good to spell out the properties of an EL1
exception frame.  It is not clear to me why we don't reference the frame
type information the unwinder already records as part of these checks.

In general, especially for the bits specific to reliable stack trace, I
think we want to err on the side of verbosity here so that it is crystal
clear what all the checks are supposed to be doing and it's that much
easier to tie everything through to the requirements document.


signature.asc
Description: PGP signature