CVE-2024-31852 (C++, published 2024-04-05T15:15Z)
Description: value: LLVM before 18.1.3 generates code in which the LR register can be overwritten without data being saved to the stack, and thus there can sometimes be an exploitable error in the flow of control. This affects the ARM backend and can be demonstrated with Clang. NOTE: the vendor perspective is "we don't have strong objections for a CVE to be created ... It does seem that the likelihood of this miscompile enabling an exploit remains very low, because the miscompile resulting in this JOP gadget is such that the function is most likely to crash on most valid inputs to the function. So, if this function is covered by any testing, the miscompile is most likely to be discovered before the binary is shipped to production."
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/llvmbot/llvm-project
Files changed:
llvm/lib/Target/ARM/ARMFrameLowering.cpp
llvm/lib/Target/ARM/ARMFrameLowering.h
llvm/lib/Target/ARM/ARMLoadStoreOptimizer.cpp
llvm/test/CodeGen/ARM/ldst-opt-lr-restored.ll
Commit message: [ARM] Update IsRestored for LR based on all returns (#82745)

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.

(cherry picked from commit 749384c08e042739342c88b521c8ba5dac1b9276)


Vulnerable code:
void ARMFrameLowering::processFunctionBeforeFrameFinalized(
    MachineFunction &MF, RegScavenger *RS) const {
  TargetFrameLowering::processFunctionBeforeFrameFinalized(MF, RS);

      // We now restore LR into PC so it is not live-out of the return block
      // anymore: Clear the CSI Restored bit.
      MachineFrameInfo &MFI = MBB.getParent()->getFrameInfo();
      // CSI should be fixed after PrologEpilog Insertion
      assert(MFI.isCalleeSavedInfoValid() && "CSI should be valid");
      for (CalleeSavedInfo &Info : MFI.getCalleeSavedInfo()) {
        if (Info.getReg() == ARM::LR) {
          Info.setRestored(false);
          break;
        }
      }
  bool Modified = false;
      Modified |= MergeReturnIntoLDM(MBB);
; CHECK-NEXT:    movw r12, :lower16:val2
; CHECK-NEXT:    movw r3, :lower16:val1
; CHECK-NEXT:    movt r2, :upper16:val0
; CHECK-NEXT:    add.w lr, r1, #4
; CHECK-NEXT:    stm.w lr, {r2, r3, r12}

Fixed code:
void ARMFrameLowering::updateLRRestored(MachineFunction &MF) {
void ARMFrameLowering::processFunctionBeforeFrameFinalized(
    MachineFunction &MF, RegScavenger *RS) const {
  TargetFrameLowering::processFunctionBeforeFrameFinalized(MF, RS);
  updateLRRestored(MF);
}

  /// Update the IsRestored flag on LR if it is spilled, based on the return
  /// instructions.
  static void updateLRRestored(MachineFunction &MF);

  bool Modified = false, ModifiedLDMReturn = false;
      ModifiedLDMReturn |= MergeReturnIntoLDM(MBB);
  Modified |= ModifiedLDMReturn;

  // If we merged a BX instruction into an LDM, we need to re-calculate whether
  // LR is restored. This check needs to consider the whole function, not just
  // the instruction(s) we changed, because there may be other BX returns which
  // still need LR to be restored.
  if (ModifiedLDMReturn)
    ARMFrameLowering::updateLRRestored(Fn);
; CHECK-NEXT:    movw r12, :lower16:val2
; CHECK-NEXT:    movw r3, :lower16:val1
; CHECK-NEXT:    movt r2, :upper16:val0
; CHECK-NEXT:    str r2, [r1, #4]
; CHECK-NEXT:    str r3, [r1, #8]
; CHECK-NEXT:    str.w r12, [r1, #12]