diff mbox series

ARM:unwind:fix unwind abort for uleb128 case

Message ID 20230407033341.5139-1-haibo.li@mediatek.com (mailing list archive)
State New, archived
Headers show
Series ARM:unwind:fix unwind abort for uleb128 case | expand

Commit Message

Haibo Li April 7, 2023, 3:33 a.m. UTC
When unwind instruction is 0xb2,the subsequent instructions
are uleb128 bytes.
For now,it uses only the first uleb128 byte in code.

For vsp increments of 0x204~0x400,use one uleb128 byte like below:
0xc06a00e4 <unwind_test_work>: 0x80b27fac
  Compact model index: 0
  0xb2 0x7f vsp = vsp + 1024
  0xac      pop {r4, r5, r6, r7, r8, r14}

For vsp increments larger than 0x400,use two uleb128 bytes like below:
0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
  Compact model index: 1
  0xb2 0x81 0x01 vsp = vsp + 1032
  0xac      pop {r4, r5, r6, r7, r8, r14}
The unwind works well since the decoded uleb128 byte is also 0x81.

For vsp increments larger than 0x600,use two uleb128 bytes like below:
0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
  Compact model index: 1
  0xb2 0x81 0x02 vsp = vsp + 1544
  0xac      pop {r4, r5, r6, r7, r8, r14}
In this case,the decoded uleb128 result is 0x101(vsp=0x204+(0x101<<2)).
While the uleb128 used in code is 0x81(vsp=0x204+(0x81<<2)).
The unwind aborts at this frame since it gets incorrect vsp.

To fix this,add uleb128 decode to cover all the above case.

Signed-off-by: Haibo Li <haibo.li@mediatek.com>
---
 arch/arm/kernel/unwind.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

Comments

Linus Walleij April 11, 2023, 9:31 p.m. UTC | #1
On Fri, Apr 7, 2023 at 5:33 AM Haibo Li <haibo.li@mediatek.com> wrote:

> When unwind instruction is 0xb2,the subsequent instructions
> are uleb128 bytes.
> For now,it uses only the first uleb128 byte in code.
>
> For vsp increments of 0x204~0x400,use one uleb128 byte like below:
> 0xc06a00e4 <unwind_test_work>: 0x80b27fac
>   Compact model index: 0
>   0xb2 0x7f vsp = vsp + 1024
>   0xac      pop {r4, r5, r6, r7, r8, r14}
>
> For vsp increments larger than 0x400,use two uleb128 bytes like below:
> 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
>   Compact model index: 1
>   0xb2 0x81 0x01 vsp = vsp + 1032
>   0xac      pop {r4, r5, r6, r7, r8, r14}
> The unwind works well since the decoded uleb128 byte is also 0x81.
>
> For vsp increments larger than 0x600,use two uleb128 bytes like below:
> 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
>   Compact model index: 1
>   0xb2 0x81 0x02 vsp = vsp + 1544
>   0xac      pop {r4, r5, r6, r7, r8, r14}
> In this case,the decoded uleb128 result is 0x101(vsp=0x204+(0x101<<2)).
> While the uleb128 used in code is 0x81(vsp=0x204+(0x81<<2)).
> The unwind aborts at this frame since it gets incorrect vsp.
>
> To fix this,add uleb128 decode to cover all the above case.
>
> Signed-off-by: Haibo Li <haibo.li@mediatek.com>

[Added people such as Catalin, Ard and Anurag who wrote the lion's
share of actual algorithms in this file]

I would just link the wikipedia in the patch commit log actually:

Link: https://en.wikipedia.org/wiki/LEB128

for poor souls like me who need a primer on this encoding.

It's great if you also have a reference to the spec where you
found this, but I take your word for that this appears in code.
Did compilers always emit this? Then we should have a Cc stable
to this patch. Unfortunately the link in the top of the file is dead.

> +static unsigned long unwind_decode_uleb128(struct unwind_ctrl_block *ctrl)

So this decodes max an unsigned long? Are we sure that will always
suffice?

> +{
> +       unsigned long result = 0;
> +       unsigned long insn;
> +       unsigned long bytes = 0;
> +
> +       do {
> +               insn = unwind_get_byte(ctrl);
> +               result |= (insn & 0x7f) << (bytes * 7);
> +               bytes++;
> +               if (bytes == sizeof(result))
> +                       break;
> +       } while (!!(insn & 0x80));

I suppose the documentation is in the commit message, but something terse
and nice that make us understand this code would be needed here as well.
Could you fold in a comment of how the do {} while-loop works and th expected
outcome? Something like:

"unwind_get_byte() will advance ctrl one instruction at a time, we loop
until we get an instruction byte where bit 7 is not set."

Is there a risk that this will loop forever or way too long if it happens
to point at some corrupted memory containing say 0xff 0xff 0xff ...?

Since we're decoding a 32 bit unsigned long maybe break the loop after max
5 bytes (35 bits)? Or are we sure this will not happen?

> @@ -361,7 +376,7 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)
>                 if (ret)
>                         goto error;
>         } else if (insn == 0xb2) {
> -               unsigned long uleb128 = unwind_get_byte(ctrl);
> +               unsigned long uleb128 = unwind_decode_uleb128(ctrl);

Is unsigned long always enough? We are sure?

Yours,
Linus Walleij
Haibo Li April 12, 2023, 2:44 a.m. UTC | #2
> On Fri, Apr 7, 2023 at 5:33 AM Haibo Li <haibo.li@mediatek.com> wrote:
> 
> > When unwind instruction is 0xb2,the subsequent instructions
> > are uleb128 bytes.
> > For now,it uses only the first uleb128 byte in code.
> >
> > For vsp increments of 0x204~0x400,use one uleb128 byte like below:
> > 0xc06a00e4 <unwind_test_work>: 0x80b27fac
> >   Compact model index: 0
> >   0xb2 0x7f vsp = vsp + 1024
> >   0xac      pop {r4, r5, r6, r7, r8, r14}
> >
> > For vsp increments larger than 0x400,use two uleb128 bytes like below:
> > 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
> >   Compact model index: 1
> >   0xb2 0x81 0x01 vsp = vsp + 1032
> >   0xac      pop {r4, r5, r6, r7, r8, r14}
> > The unwind works well since the decoded uleb128 byte is also 0x81.
> >
> > For vsp increments larger than 0x600,use two uleb128 bytes like below:
> > 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
> >   Compact model index: 1
> >   0xb2 0x81 0x02 vsp = vsp + 1544
> >   0xac      pop {r4, r5, r6, r7, r8, r14}
> > In this case,the decoded uleb128 result is 0x101(vsp=0x204+(0x101<<2)).
> > While the uleb128 used in code is 0x81(vsp=0x204+(0x81<<2)).
> > The unwind aborts at this frame since it gets incorrect vsp.
> >
> > To fix this,add uleb128 decode to cover all the above case.
> >
> > Signed-off-by: Haibo Li <haibo.li@mediatek.com>
> 
> [Added people such as Catalin, Ard and Anurag who wrote the lion's
> share of actual algorithms in this file]
> 
> I would just link the wikipedia in the patch commit log actually:
> 
> Link:https://en.wikipedia.org/wiki/LEB128
> 
> for poor souls like me who need a primer on this encoding.
> 
> It's great if you also have a reference to the spec where you
> found this, but I take your word for that this appears in code.
> Did compilers always emit this? Then we should have a Cc stable
> to this patch. Unfortunately the link in the top of the file is dead.
Yes.I also study uleb128 enc/dec format from this link.
In experiment,Both Clang and GCC produces unwind instructions using ULEB128
> 
> > +static unsigned long unwind_decode_uleb128(struct unwind_ctrl_block
> *ctrl)
> 
> So this decodes max an unsigned long? Are we sure that will always
> suffice?
For now,the maximum thread size of arm is 16KB(KASAN on).
From below experiment(worse case while impossible),two uleb128 bytes is sufficent for 16KB stack.
0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
  Compact model index: 1
  0xb2 0xff 0x1e vsp = vsp + 16384
  0xac      pop {r4, r5, r6, r7, r8, r14}
From below experiment,the code picks maximum 4 uleb128 encoded bytes,
correspoding to vsp increments of 1073742336,the unwind_decode_uleb128 returns 0xFFFFFFF.
So unsigned long is suffice.
0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
  Compact model index: 1
  0xb2 0xff 0xff 0xff 0x7f vsp = vsp + 1073742336
  0xac      pop {r4, r5, r6, r7, r8, r14}
> 
> > +{
> > +       unsigned long result = 0;
> > +       unsigned long insn;
> > +       unsigned long bytes = 0;
> > +
> > +       do {
> > +               insn = unwind_get_byte(ctrl);
> > +               result |= (insn & 0x7f) << (bytes * 7);
> > +               bytes++;
> > +               if (bytes == sizeof(result))
> > +                       break;
> > +       } while (!!(insn & 0x80));
> 
> I suppose the documentation is in the commit message, but something terse
> and nice that make us understand this code would be needed here as well.
> Could you fold in a comment of how the do {} while-loop works and th expected
> outcome? Something like:
> 
> "unwind_get_byte() will advance ctrl one instruction at a time, we loop
> until we get an instruction byte where bit 7 is not set."
> 
I will add a comment in later patch.
> Is there a risk that this will loop forever or way too long if it happens
> to point at some corrupted memory containing say 0xff 0xff 0xff ...?
> 
> Since we're decoding a 32 bit unsigned long maybe break the loop after max
> 5 bytes (35 bits)? Or are we sure this will not happen?
in case of some corrupted memory containing say 0xff 0xff 0xff ...,the loop breaks after 
max 4 bytes(decode as max 28 bits)
> 
> > @@ -361,7 +376,7 @@ static int unwind_exec_insn(struct
> unwind_ctrl_block *ctrl)
> >                 if (ret)
> >                         goto error;
> >         } else if (insn == 0xb2) {
> > -               unsigned long uleb128 = unwind_get_byte(ctrl);
> > +               unsigned long uleb128 = unwind_decode_uleb128(ctrl);
> 
> Is unsigned long always enough? We are sure?
For the patch,it can cover single frame up to 1073742336 Bytes.So it is enough.
>
> Yours,
> Linus Walleij
Linus Walleij April 12, 2023, 12:25 p.m. UTC | #3
On Wed, Apr 12, 2023 at 4:44 AM Haibo Li <haibo.li@mediatek.com> wrote:

> > Since we're decoding a 32 bit unsigned long maybe break the loop after max
> > 5 bytes (35 bits)? Or are we sure this will not happen?

> in case of some corrupted memory containing say 0xff 0xff 0xff ...,the loop breaks after
> max 4 bytes(decode as max 28 bits)

You're obviously right, I must have been too tired to understand the
==sizeof() break;

Thanks!
Linus Walleij
Alexandre Mergnat April 12, 2023, 12:43 p.m. UTC | #4
On 07/04/2023 05:33, Haibo Li wrote:
> When unwind instruction is 0xb2,the subsequent instructions
> are uleb128 bytes.
> For now,it uses only the first uleb128 byte in code.
> 
> For vsp increments of 0x204~0x400,use one uleb128 byte like below:
> 0xc06a00e4 <unwind_test_work>: 0x80b27fac
>    Compact model index: 0
>    0xb2 0x7f vsp = vsp + 1024
>    0xac      pop {r4, r5, r6, r7, r8, r14}
> 
> For vsp increments larger than 0x400,use two uleb128 bytes like below:
> 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
>    Compact model index: 1
>    0xb2 0x81 0x01 vsp = vsp + 1032
>    0xac      pop {r4, r5, r6, r7, r8, r14}
> The unwind works well since the decoded uleb128 byte is also 0x81.
> 
> For vsp increments larger than 0x600,use two uleb128 bytes like below:
> 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
>    Compact model index: 1
>    0xb2 0x81 0x02 vsp = vsp + 1544
>    0xac      pop {r4, r5, r6, r7, r8, r14}
> In this case,the decoded uleb128 result is 0x101(vsp=0x204+(0x101<<2)).
> While the uleb128 used in code is 0x81(vsp=0x204+(0x81<<2)).
> The unwind aborts at this frame since it gets incorrect vsp.
> 
> To fix this,add uleb128 decode to cover all the above case.
> 
> Signed-off-by: Haibo Li <haibo.li@mediatek.com>
> ---
>   arch/arm/kernel/unwind.c | 19 +++++++++++++++++--
>   1 file changed, 17 insertions(+), 2 deletions(-)
> 
> diff --git a/arch/arm/kernel/unwind.c b/arch/arm/kernel/unwind.c
> index 53be7ea6181b..e5796a5acba1 100644
> --- a/arch/arm/kernel/unwind.c
> +++ b/arch/arm/kernel/unwind.c
> @@ -20,7 +20,6 @@
>   #warning    Change compiler or disable ARM_UNWIND option.
>   #endif
>   #endif /* __CHECKER__ */
> -

Why delete this line ?

>   #include <linux/kernel.h>
>   #include <linux/init.h>
>   #include <linux/export.h>
> @@ -308,6 +307,22 @@ static int unwind_exec_pop_subset_r0_to_r3(struct unwind_ctrl_block *ctrl,
>   	return URC_OK;
>   }
>   
> +static unsigned long unwind_decode_uleb128(struct unwind_ctrl_block *ctrl)
> +{
> +	unsigned long result = 0;
> +	unsigned long insn;
> +	unsigned long bytes = 0;

Alphabetical order please.

> +
> +	do {
> +		insn = unwind_get_byte(ctrl);
> +		result |= (insn & 0x7f) << (bytes * 7);
> +		bytes++;
> +		if (bytes == sizeof(result))
> +			break;
> +	} while (!!(insn & 0x80));
> +
> +	return result;
> +}

Please add a blank line for readability.

>   /*
>    * Execute the current unwind instruction.
>    */
> @@ -361,7 +376,7 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)
>   		if (ret)
>   			goto error;
>   	} else if (insn == 0xb2) {
> -		unsigned long uleb128 = unwind_get_byte(ctrl);
> +		unsigned long uleb128 = unwind_decode_uleb128(ctrl);
>   
>   		ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
>   	} else {

Great job! I'm aligned with Linus Walleij's feedback about the need of 
few comments to explain the decode loop, even if your code is clear, 
light and robust.
Haibo Li April 13, 2023, 7:19 a.m. UTC | #5
> On 07/04/2023 05:33, Haibo Li wrote:
> > When unwind instruction is 0xb2,the subsequent instructions are
> > uleb128 bytes.
> > For now,it uses only the first uleb128 byte in code.
> >
> > For vsp increments of 0x204~0x400,use one uleb128 byte like below:
> > 0xc06a00e4 <unwind_test_work>: 0x80b27fac
> >    Compact model index: 0
> >    0xb2 0x7f vsp = vsp + 1024
> >    0xac      pop {r4, r5, r6, r7, r8, r14}
> >
> > For vsp increments larger than 0x400,use two uleb128 bytes like below:
> > 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
> >    Compact model index: 1
> >    0xb2 0x81 0x01 vsp = vsp + 1032
> >    0xac      pop {r4, r5, r6, r7, r8, r14}
> > The unwind works well since the decoded uleb128 byte is also 0x81.
> >
> > For vsp increments larger than 0x600,use two uleb128 bytes like below:
> > 0xc06a00e4 <unwind_test_work>: @0xc0cc9e0c
> >    Compact model index: 1
> >    0xb2 0x81 0x02 vsp = vsp + 1544
> >    0xac      pop {r4, r5, r6, r7, r8, r14}
> > In this case,the decoded uleb128 result is 0x101(vsp=0x204+(0x101<<2)).
> > While the uleb128 used in code is 0x81(vsp=0x204+(0x81<<2)).
> > The unwind aborts at this frame since it gets incorrect vsp.
> >
> > To fix this,add uleb128 decode to cover all the above case.
> >
> > Signed-off-by: Haibo Li <haibo.li@mediatek.com>
> > ---
> >   arch/arm/kernel/unwind.c | 19 +++++++++++++++++--
> >   1 file changed, 17 insertions(+), 2 deletions(-)
> >
> > diff --git a/arch/arm/kernel/unwind.c b/arch/arm/kernel/unwind.c index
> > 53be7ea6181b..e5796a5acba1 100644
> > --- a/arch/arm/kernel/unwind.c
> > +++ b/arch/arm/kernel/unwind.c
> > @@ -20,7 +20,6 @@
> >   #warning    Change compiler or disable ARM_UNWIND option.
> >   #endif
> >   #endif /* __CHECKER__ */
> > -
> 
> Why delete this line ?
It may be changed by mistake.I will restore it.
> 
> >   #include <linux/kernel.h>
> >   #include <linux/init.h>
> >   #include <linux/export.h>
> > @@ -308,6 +307,22 @@ static int
> unwind_exec_pop_subset_r0_to_r3(struct unwind_ctrl_block *ctrl,
> >       return URC_OK;
> >   }
> >
> > +static unsigned long unwind_decode_uleb128(struct unwind_ctrl_block
> > +*ctrl) {
> > +     unsigned long result = 0;
> > +     unsigned long insn;
> > +     unsigned long bytes = 0;
> 
> Alphabetical order please.
get it.
> 
> > +
> > +     do {
> > +             insn = unwind_get_byte(ctrl);
> > +             result |= (insn & 0x7f) << (bytes * 7);
> > +             bytes++;
> > +             if (bytes == sizeof(result))
> > +                     break;
> > +     } while (!!(insn & 0x80));
> > +
> > +     return result;
> > +}
> 
> Please add a blank line for readability.
OK.
> 
> >   /*
> >    * Execute the current unwind instruction.
> >    */
> > @@ -361,7 +376,7 @@ static int unwind_exec_insn(struct
> unwind_ctrl_block *ctrl)
> >               if (ret)
> >                       goto error;
> >       } else if (insn == 0xb2) {
> > -             unsigned long uleb128 = unwind_get_byte(ctrl);
> > +             unsigned long uleb128 = unwind_decode_uleb128(ctrl);
> >
> >               ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
> >       } else {
> 
> Great job! I'm aligned with Linus Walleij's feedback about the need of few
> comments to explain the decode loop, even if your code is clear, light and
> robust.
Thanks for reviewing the patch.I will add the comment in later patch.
>
diff mbox series

Patch

diff --git a/arch/arm/kernel/unwind.c b/arch/arm/kernel/unwind.c
index 53be7ea6181b..e5796a5acba1 100644
--- a/arch/arm/kernel/unwind.c
+++ b/arch/arm/kernel/unwind.c
@@ -20,7 +20,6 @@ 
 #warning    Change compiler or disable ARM_UNWIND option.
 #endif
 #endif /* __CHECKER__ */
-
 #include <linux/kernel.h>
 #include <linux/init.h>
 #include <linux/export.h>
@@ -308,6 +307,22 @@  static int unwind_exec_pop_subset_r0_to_r3(struct unwind_ctrl_block *ctrl,
 	return URC_OK;
 }
 
+static unsigned long unwind_decode_uleb128(struct unwind_ctrl_block *ctrl)
+{
+	unsigned long result = 0;
+	unsigned long insn;
+	unsigned long bytes = 0;
+
+	do {
+		insn = unwind_get_byte(ctrl);
+		result |= (insn & 0x7f) << (bytes * 7);
+		bytes++;
+		if (bytes == sizeof(result))
+			break;
+	} while (!!(insn & 0x80));
+
+	return result;
+}
 /*
  * Execute the current unwind instruction.
  */
@@ -361,7 +376,7 @@  static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)
 		if (ret)
 			goto error;
 	} else if (insn == 0xb2) {
-		unsigned long uleb128 = unwind_get_byte(ctrl);
+		unsigned long uleb128 = unwind_decode_uleb128(ctrl);
 
 		ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
 	} else {