Security
Headlines
HeadlinesLatestCVEs

Headline

GHSA-2gqc-6j2q-83qp: RustCrypto Utilities cmov: `thumbv6m-none-eabi` compiler emits non-constant time assembly when using `cmovnz`

Summary

thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). I did not found any other target with the same behaviour but I did not go through all targets supported by Rust.

Details

It seems that, during mask computation, an LLVM optimisation pass is detecting that bitnz is returning 0 or 1, that can be interpreted as a boolean. This intermediate value is not masked by a call to black_box and thus the subsequent .wrapping_sub(1) can be interpreted as a conditional bitwise conditional not.

PoC

This is an attempt at having a minimal faulty code. In a library crate with an up-to-date cmov as only dependency, the content of src/lib.rs is:

#![no_std]
use cmov::Cmov;

#[inline(never)]
pub fn test_ct_cmov(a: &mut u8, b: u8, c: u8) {
    a.cmovnz(&b, c);
}

The resulting assembly emitted (shown using cargo asm --release --target thumbv6m-none-eabi that uses cargo-show-asm):

<details> <summary>Collapsed assembly</summary>

.section .text.not_ct::test_ct_cmov,"ax",%progbits
    .globl  not_ct::test_ct_cmov
    .p2align    1
    .type   not_ct::test_ct_cmov,%function
    .code   16
    .thumb_func
not_ct::test_ct_cmov:
    .fnstart
    .cfi_sections .debug_frame
    .cfi_startproc
    .save   {r7, lr}
    push {r7, lr}
    .cfi_def_cfa_offset 8
    .cfi_offset lr, -4
    .cfi_offset r7, -8
    .setfp  r7, sp
    add r7, sp, #0
    .cfi_def_cfa_register r7
    .pad    #8
    sub sp, #8
    movs r3, #0
    lsls r2, r2, #24
    bne .LBB0_2
    mvns r3, r3
.LBB0_2:
    ldrb r2, [r0]
    str r3, [sp, #4]
    str r3, [sp]
    mov r3, sp
    @APP
    @NO_APP
    ldr r3, [sp]
    bics r1, r3
    ands r2, r3
    adds r1, r2, r1
    strb r1, [r0]
    add sp, #8
    pop {r7, pc}

</details>

The non-constant time assembly is:

    bne  .LBB0_2
    mvns r3, r3
.LBB0_2:

Impact

The exact impact is unclear, especially since cmov clearly warns users that the portable version is best-effort.

ghsa
#git

Summary

thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). I did not found any other target with the same behaviour but I did not go through all targets supported by Rust.

Details

It seems that, during mask computation, an LLVM optimisation pass is detecting that bitnz is returning 0 or 1, that can be interpreted as a boolean. This intermediate value is not masked by a call to black_box and thus the subsequent .wrapping_sub(1) can be interpreted as a conditional bitwise conditional not.

PoC

This is an attempt at having a minimal faulty code. In a library crate with an up-to-date cmov as only dependency, the content of src/lib.rs is:

#![no_std] use cmov::Cmov;

#[inline(never)] pub fn test_ct_cmov(a: &mut u8, b: u8, c: u8) { a.cmovnz(&b, c); }

The resulting assembly emitted (shown using cargo asm --release --target thumbv6m-none-eabi that uses cargo-show-asm):

Collapsed assembly

.section .text.not_ct::test_ct_cmov,"ax",%progbits .globl not_ct::test_ct_cmov .p2align 1 .type not_ct::test_ct_cmov,%function .code 16 .thumb_func not_ct::test_ct_cmov: .fnstart .cfi_sections .debug_frame .cfi_startproc .save {r7, lr} push {r7, lr} .cfi_def_cfa_offset 8 .cfi_offset lr, -4 .cfi_offset r7, -8 .setfp r7, sp add r7, sp, #0 .cfi_def_cfa_register r7 .pad #8 sub sp, #8 movs r3, #0 lsls r2, r2, #24 bne .LBB0_2 mvns r3, r3 .LBB0_2: ldrb r2, [r0] str r3, [sp, #4] str r3, [sp] mov r3, sp @APP @NO_APP ldr r3, [sp] bics r1, r3 ands r2, r3 adds r1, r2, r1 strb r1, [r0] add sp, #8 pop {r7, pc}

The non-constant time assembly is:

bne  .LBB0\_2
mvns r3, r3

.LBB0_2:

Impact

The exact impact is unclear, especially since cmov clearly warns users that the portable version is best-effort.

References

  • GHSA-2gqc-6j2q-83qp
  • RustCrypto/utils@5597725

ghsa: Latest News

GHSA-2gqc-6j2q-83qp: RustCrypto Utilities cmov: `thumbv6m-none-eabi` compiler emits non-constant time assembly when using `cmovnz`