Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug][Clang][ARM] Wrong assembly code generation, "LR" corrupted. #80287

Closed
P1119r1m opened this issue Feb 1, 2024 · 16 comments · Fixed by #82745
Closed

[Bug][Clang][ARM] Wrong assembly code generation, "LR" corrupted. #80287

P1119r1m opened this issue Feb 1, 2024 · 16 comments · Fixed by #82745

Comments

@P1119r1m
Copy link

P1119r1m commented Feb 1, 2024

The Clang compiler generates code for a function that overwrites the lr register without saving its data on stack beforehand.
This bug was detected on a large software component and the problem was minimized to a PoC (see example.c).

Clang versions info:

  • llvmorg-17-init - bug wasn't detected
  • llvmorg-17.0.2 - bug detected
  • llvmorg-17.0.6 - bug detected
  • llvmorg-18-init - bug detected

Host OS: Ubuntu 20.04.3 LTS

In disassembler the bug looks like this (generated example.c.o.objdump):

00000010 <BUGGY_FUNCTION>:
;     if (!ctx) {
      10:     	cmp	r0, #0
      14:     	beq	0x48 <BUGGY_FUNCTION+0x38> @ imm = #0x2c                <-- Goto 0x48 if "(!ctx)" is True.
;     ctx->cmd_c = func_2;
      18:     	ldr	r12, [pc, #0x40]        @ 0x60 <BUGGY_FUNCTION+0x50>    <-- Continue if "(!ctx)" is False.
      1c:     	mov	r1, r0
      20:     	mov	r0, #0
;     ctx->cmd_a = func_0;
      24:     	add	lr, r1, #8              <-- ERROR. THIS LOOKS CRAZY!!! "lr" WAS NOT STORED ON THE STACK!
;     ctx->cmd_c = func_2;
      28:     	ldr	r12, [pc, r12]
... < SOME OTHER CODE > ...
      44:     	bx	lr                      <-- ERROR. BRANCH TO "lr" ADDRESS THAT WAS PREVIOUSLY CORRUPTED!!!
      48:     	push	{r11, lr}
... < SOME OTHER CODE > ...

Steps to reproduce.

  • example.c
void __attribute__((optnone)) printer(int line) {}

#define INFO() printer(__LINE__);

extern int func_0(void);
extern int func_1(void);
extern int func_2(void);

typedef int (*f_ptr_t)(void);

typedef struct operation {
    double double_0;
    f_ptr_t cmd_a;
    f_ptr_t cmd_b;
    f_ptr_t cmd_c;
    f_ptr_t cmd_d;
} operation_t;

int BUGGY_FUNCTION(operation_t *ctx)
{
    // INFO(); // UNCOMMENT TO FIX ASSEMBLY CODE GENERATION!!!
    if (!ctx) {
        INFO()
        return -1;
    }
    ctx->cmd_a = func_0;
    ctx->cmd_b = func_1;
    ctx->cmd_c = func_2;
    ctx->cmd_d = (void *)0;
    return 0;
}
  • reproduce.sh
#!/bin/bash

SDK_BIN_PATH=<PUT YOUR BIN PATH LOCATION HERE>

CC="${SDK_BIN_PATH}/armv7-none-gnueabi-clang"
OBJDUMP="${SDK_BIN_PATH}/armv7-none-gnueabi-objdump"

INPUT_FILE=example.c
OBJ_FILE=example.c.o
OBJDUMP_FILE=example.c.o.objdump

echo "Compile and objdump..." \
&& "${CC}" -ggdb -Os -fPIE -c "${INPUT_FILE}" -o "${OBJ_FILE}" \
&& "${OBJDUMP}" -dS "${OBJ_FILE}" | tee "${OBJDUMP_FILE}"
@github-actions github-actions bot added the clang Clang issues not falling into any other category label Feb 1, 2024
@jroelofs
Copy link
Contributor

jroelofs commented Feb 1, 2024

Smells like shrink-wrapping.

@llvmbot
Copy link
Collaborator

llvmbot commented Feb 1, 2024

@llvm/issue-subscribers-backend-arm

Author: Victor Signaevskyi (P1119r1m)

The Clang compiler generates code for a function that overwrites the `lr` register without saving its data on stack beforehand. This bug was detected on a large software component and the problem was minimized to a PoC (see `example.c`).

Clang versions info:

  • llvmorg-17-init - bug wasn't detected
  • llvmorg-17.0.2 - bug detected
  • llvmorg-17.0.6 - bug detected
  • llvmorg-18-init - bug detected

Host OS: Ubuntu 20.04.3 LTS

In disassembler the bug looks like this (generated example.c.o.objdump):

00000010 &lt;BUGGY_FUNCTION&gt;:
;     if (!ctx) {
      10:     	cmp	r0, #<!-- -->0
      14:     	beq	0x48 &lt;BUGGY_FUNCTION+0x38&gt; @ imm = #<!-- -->0x2c                &lt;-- Goto 0x48 if "(!ctx)" is True.
;     ctx-&gt;cmd_c = func_2;
      18:     	ldr	r12, [pc, #<!-- -->0x40]        @ 0x60 &lt;BUGGY_FUNCTION+0x50&gt;    &lt;-- Continue if "(!ctx)" is False.
      1c:     	mov	r1, r0
      20:     	mov	r0, #<!-- -->0
;     ctx-&gt;cmd_a = func_0;
      24:     	add	lr, r1, #<!-- -->8              &lt;-- ERROR. THIS LOOKS CRAZY!!! "lr" WAS NOT STORED ON THE STACK!
;     ctx-&gt;cmd_c = func_2;
      28:     	ldr	r12, [pc, r12]
... &lt; SOME OTHER CODE &gt; ...
      44:     	bx	lr                      &lt;-- ERROR. BRANCH TO "lr" ADDRESS THAT WAS PREVIOUSLY CORRUPTED!!!
      48:     	push	{r11, lr}
... &lt; SOME OTHER CODE &gt; ...

Steps to reproduce.

  • example.c
void __attribute__((optnone)) printer(int line) {}

#define INFO() printer(__LINE__);

extern int func_0(void);
extern int func_1(void);
extern int func_2(void);

typedef int (*f_ptr_t)(void);

typedef struct operation {
    double double_0;
    f_ptr_t cmd_a;
    f_ptr_t cmd_b;
    f_ptr_t cmd_c;
    f_ptr_t cmd_d;
} operation_t;

int BUGGY_FUNCTION(operation_t *ctx)
{
    // INFO(); // UNCOMMENT TO FIX ASSEMBLY CODE GENERATION!!!
    if (!ctx) {
        INFO()
        return -1;
    }
    ctx-&gt;cmd_a = func_0;
    ctx-&gt;cmd_b = func_1;
    ctx-&gt;cmd_c = func_2;
    ctx-&gt;cmd_d = (void *)0;
    return 0;
}
  • reproduce.sh
#!/bin/bash

SDK_BIN_PATH=&lt;PUT YOUR BIN PATH LOCATION HERE&gt;

CC="${SDK_BIN_PATH}/arm-secureos-gnueabi-clang"
OBJDUMP="${SDK_BIN_PATH}/arm-secureos-gnueabi-objdump"

INPUT_FILE=example.c
OBJ_FILE=example.c.o
OBJDUMP_FILE=example.c.o.objdump

echo "Compile and objdump..." \
&amp;&amp; "${CC}" -ggdb -Os -fPIE -c "${INPUT_FILE}" -o "${OBJ_FILE}" \
&amp;&amp; "${OBJDUMP}" -dS "${OBJ_FILE}" | tee "${OBJDUMP_FILE}"

@fhahn
Copy link
Contributor

fhahn commented Feb 1, 2024

I suspect this was fixed by swiftlang@b1a5ee1

Could you check with a build from current main or the upcoming 18.x release candidates?

@davemgreen
Copy link
Collaborator

I still see the same in https://godbolt.org/z/7PKxqj7vx I'm afraid.

It looks like what @jroelofs says - shrink wrapping leads to frame lowering in one of the two branches, followed by a bit of tail folding ends up with the load/store optimizing using lr that it thinks is available.

@EugeneZelenko EugeneZelenko removed the clang Clang issues not falling into any other category label Feb 1, 2024
@P1119r1m
Copy link
Author

P1119r1m commented Feb 1, 2024

BTW, there is no bug if to build it for -target armv4t-none-eabi instead of -target thumbv7a-none-eabi:

@P1119r1m
Copy link
Author

P1119r1m commented Feb 5, 2024

Dear contributors/collaborators,

are there any chance to fix this compiler bug?

There is a feeling that this bug can manifest itself in many other places in the code.
Maybe not even only in 32-bit architectures.

Also, I think this bug could be used in some hacker attacks.

@P1119r1m
Copy link
Author

@jroelofs @davemgreen @fhahn
I think we should report a CVE for this Clang issue to warn at least all ARM32 users about this Clang compiler vulnerability.

@smithp35
Copy link
Collaborator

If you feel like this is a security issue please report using the process in https://llvm.org/docs/Security.html . My instinct is that this would not be a worthy of a CVE. CVEs are used to report security vulnerabilities in programs, this looks like a code-generation bug that if it affects your program it is likely to crash. It doesn't look like something that would be systemicly reproduced across many programs.

@P1119r1m
Copy link
Author

@smithp35 Thanks for the link.
I think the bug deserves a CVE because "security experts" can intentionally look for this incorrect binary pattern in binaries and exploit it.
Theoretically, thanks to this compiler bug, one can branch to any address.

@smithp35
Copy link
Collaborator

Feel free to report it to the LLVM security group if you disagree with me.

@P1119r1m
Copy link
Author

@smithp35 Thank you. Reported locally to LLVM security group as:

@jroelofs
Copy link
Contributor

workaround: -mllvm --enable-shrink-wrap=false

https://godbolt.org/z/3Koq6YEWW

@ostannard ostannard self-assigned this Feb 22, 2024
ostannard added a commit to ostannard/llvm-project that referenced this issue Feb 23, 2024
This test shows the bug where LR is used as a general-purpose register
on a code path where it is not spilled to the stack.
ostannard added a commit to ostannard/llvm-project that referenced this issue Feb 23, 2024
PR llvm#75527 fixed ARMFrameLowering to set the IsRestored flag for LR based
on all of the return instructions in the function, not just one.
However, there is also code in ARMLoadStoreOptimizer which changes
return instructions, but it set IsRestored based on the one instruction
it changed, not the whole function.

The fix is to factor out the code added in llvm#75527, and also call it from
ARMLoadStoreOptimizer if it made a change to return instructions.

Fixes llvm#80287.
ostannard added a commit that referenced this issue Feb 26, 2024
This test shows the bug where LR is used as a general-purpose register
on a code path where it is not spilled to the stack.
ostannard added a commit to ostannard/llvm-project that referenced this issue Feb 26, 2024
PR llvm#75527 fixed ARMFrameLowering to set the IsRestored flag for LR based
on all of the return instructions in the function, not just one.
However, there is also code in ARMLoadStoreOptimizer which changes
return instructions, but it set IsRestored based on the one instruction
it changed, not the whole function.

The fix is to factor out the code added in llvm#75527, and also call it from
ARMLoadStoreOptimizer if it made a change to return instructions.

Fixes llvm#80287.
ostannard added a commit that referenced this issue Feb 26, 2024
PR #75527 fixed ARMFrameLowering to set the IsRestored flag for LR based
on all of the return instructions in the function, not just one.
However, there is also code in ARMLoadStoreOptimizer which changes
return instructions, but it set IsRestored based on the one instruction
it changed, not the whole function.

The fix is to factor out the code added in #75527, and also call it from
ARMLoadStoreOptimizer if it made a change to return instructions.

Fixes #80287.
@ostannard ostannard added this to the LLVM 18.X Release milestone Feb 27, 2024
@ostannard
Copy link
Collaborator

/cherry-pick 8779cf6 749384c

llvmbot pushed a commit to llvmbot/llvm-project that referenced this issue Feb 27, 2024
This test shows the bug where LR is used as a general-purpose register
on a code path where it is not spilled to the stack.

(cherry picked from commit 8779cf6)
llvmbot pushed a commit to llvmbot/llvm-project that referenced this issue Feb 27, 2024
PR llvm#75527 fixed ARMFrameLowering to set the IsRestored flag for LR based
on all of the return instructions in the function, not just one.
However, there is also code in ARMLoadStoreOptimizer which changes
return instructions, but it set IsRestored based on the one instruction
it changed, not the whole function.

The fix is to factor out the code added in llvm#75527, and also call it from
ARMLoadStoreOptimizer if it made a change to return instructions.

Fixes llvm#80287.

(cherry picked from commit 749384c)
@llvmbot
Copy link
Collaborator

llvmbot commented Feb 27, 2024

/pull-request #83129

llvmbot pushed a commit to llvmbot/llvm-project that referenced this issue Mar 23, 2024
This test shows the bug where LR is used as a general-purpose register
on a code path where it is not spilled to the stack.

(cherry picked from commit 8779cf6)
llvmbot pushed a commit to llvmbot/llvm-project that referenced this issue Mar 23, 2024
PR llvm#75527 fixed ARMFrameLowering to set the IsRestored flag for LR based
on all of the return instructions in the function, not just one.
However, there is also code in ARMLoadStoreOptimizer which changes
return instructions, but it set IsRestored based on the one instruction
it changed, not the whole function.

The fix is to factor out the code added in llvm#75527, and also call it from
ARMLoadStoreOptimizer if it made a change to return instructions.

Fixes llvm#80287.

(cherry picked from commit 749384c)
@tstellar
Copy link
Collaborator

Do we have a conclusion about which versions of LLVM and which targets this affects? Does it affect 64-bit ARM?

@tstellar
Copy link
Collaborator

Do we have a conclusion about which versions of LLVM and which targets this affects? Does it affect 64-bit ARM?

The fix is only for 32-bit ARM, so I'm assuming it doesn't affect 64-bit ARM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging a pull request may close this issue.

9 participants