llvm / llvm/llvm-project

Miscompilation during memcpy-to-load conversion which reads uninitialized bytes as poison

Open
#176,685 2 comments 0 reactions 0 assignees View on GitHub
generated by fuzzer llvm:optimizations miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

**Fuzzer Generated Test**
**Reproducer**
https://alive2.llvm.org/ce/z/7Gu3xX

**Test Commit**
[0b2f3cfb72a76fa90f3ec2a234caabe0d0712590](https://github.com/llvm/llvm-project/commit/0b2f3cfb72a76fa90f3ec2a234caabe0d0712590)

**Description**
When converting `memcpy` to `load`/`store`, the optimization reads more bytes than were initialized, causing poison to be loaded and stored. Related: https://github.com/llvm/llvm-project/issues/143527

**Steps to reproduce**
- Minimized test case, `input.ll`
```
define void @test(ptr %in, ptr %out, i1 %0) {
entry:
br i1 %0, label %1, label %2

1: ; preds = %entry
call void @llvm.memcpy.p0.p0.i64(ptr %out, ptr %in, i64 1, i1 false)
br label %2

2: ; preds = %1, %entry
ret void
}

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #0

attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

```

**Output**
```

----------------------------------------
define void @test(ptr %in, ptr %out, i1 %#0) {
entry:
br i1 %#0, label %#1, label %#2

#1:
memcpy ptr %out align 1, ptr %in align 1, i64 1
br label %#2

#2:
ret void
}
=>
define void @test(ptr nocapture nowrite %in, ptr nocapture noread %out, i1 %#0) nofree willreturn memory(argmem: readwrite) {
entry:
br i1 %#0, label %#1, label %#2

#1:
%#3 = load i8, ptr nocapture nowrite %in, align 1
store i8 %#3, ptr nocapture noread %out, align 1
br label %#2

#2:
ret void
}
Transformation doesn't verify!

ERROR: Mismatch in memory

Example:
ptr %in = pointer(non-local, block_id=1, offset=8, attrs=8) / Address=#x0a
ptr %out = pointer(non-local, block_id=1, offset=0, attrs=8) / Address=#x02
i1 %#0 = #x1 (1)

Source:
>> Jump to %#1
>> Jump to %#2

SOURCE MEMORY STATE
===================
NON-LOCAL BLOCKS:
Block 0 > size: 0 align: 1 alloc type: 0 alive: false address: #x00
Block 1 > size: 12 align: 2 alloc type: 4 alive: true address: #x02
Contents:
8: pointer(non-local, block_id=0, offset=2), byte offset=2
2: poison
*: poison

Block 2 > size: 0 align: 1 alloc type: 0 alive: true address: #x01
Contents:
2: poison
*: poison

Target:
>> Jump to %#1
i8 %#3 = poison
>> Jump to %#2

TARGET MEMORY STATE
===================
NON-LOCAL BLOCKS:
Block 0 > size: 0 align: 1 alloc type: 0 alive: false address: #x00
Block 1 > size: 12 align: 2 alloc type: 4 alive: true address: #x02
Contents:
8: pointer(non-local, block_id=0, offset=2), byte offset=2
2: poison
*: poison

Block 2 > size: 0 align: 1 alloc type: 0 alive: true address: #x01
Contents:
2: poison
*: poison

Mismatch in pointer(non-local, block_id=1, offset=0)
Source value: pointer(non-local, block_id=0, offset=2), byte offset=2
Target value: poison

Summary:
0 correct transformations
1 incorrect transformations
0 failed-to-prove transformations
0 Alive2 errors
```

Contributor guide

Open the contributing guide

Research direction

Start with the minimized input.ll reproducer and run it through the memcpy-to-load conversion, then compare the result with the Alive2 link. Trace where the conversion widens the read beyond the initialized byte; done means the transformation no longer produces the reported poison mismatch and the reproducer verifies correctly.

Written by the indexing model from the issue text.

Assessment

Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.