Skip to content

release/22.x: [LLD][COFF] Fix out-of-bounds write when filling gaps with INT3 in code sections (#180411) - #198212

Closed
llvmbot wants to merge 1 commit into
llvm:release/22.xfrom
llvmbot:issue180411
Closed

release/22.x: [LLD][COFF] Fix out-of-bounds write when filling gaps with INT3 in code sections (#180411)#198212
llvmbot wants to merge 1 commit into
llvm:release/22.xfrom
llvmbot:issue180411

Conversation

@llvmbot

@llvmbot llvmbot commented May 17, 2026

Copy link
Copy Markdown
Member

Backport 6558595

Requested by: @mstorsjo

…de sections (llvm#180411)

When merging `.bss` into a code section (e.g., `/MERGE:.bss=.text`), the
INT3 gap-filling loop in `writeSections()` would write past the output
buffer. This happens because `.bss` chunks have `hasData=false`, so they
contribute to `VirtualSize` but not `SizeOfRawData`. The loop was using
chunk RVAs without checking if they exceeded the raw data region.

This caused a crash on Windows with `/FILEALIGN:1` (access violation
0xC0000005). The tight alignment leaves no slack in the mapped buffer,
so the overflow immediately hits unmapped memory.

The fix bounds all memset operations to `rawSize` and exits early when
encountering chunks beyond the raw data boundary.

Fixes llvm#180406

(cherry picked from commit 6558595)
@llvmbot

llvmbot commented May 17, 2026

Copy link
Copy Markdown
Member Author

@aganea What do you think about merging this PR to the release branch?

@llvmorg-github-actions

llvmorg-github-actions Bot commented May 17, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-platform-windows

@llvm/pr-subscribers-lld-coff

Author: llvmbot

Changes

Backport 6558595

Requested by: @mstorsjo


Full diff: https://github.com/llvm/llvm-project/pull/198212.diff

2 Files Affected:

  • (modified) lld/COFF/Writer.cpp (+7-2)
  • (added) lld/test/COFF/merge-bss-text-filealign1.test (+60)
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 3ac26681541ba..4451cfeb3dd63 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -2617,12 +2617,17 @@ void Writer::writeSections() {
     if ((sec->header.Characteristics & IMAGE_SCN_CNT_CODE) &&
         (ctx.config.machine == AMD64 || ctx.config.machine == I386)) {
       uint32_t prevEnd = 0;
+      uint32_t rawSize = sec->getRawSize();
       for (Chunk *c : sec->chunks) {
         uint32_t off = c->getRVA() - sec->getRVA();
+        // Chunks without data (e.g., .bss) have virtual addresses beyond
+        // rawSize; stop filling when we reach the end of raw data.
+        if (off >= rawSize)
+          break;
         memset(secBuf + prevEnd, 0xCC, off - prevEnd);
-        prevEnd = off + c->getSize();
+        prevEnd = std::min(off + static_cast<uint32_t>(c->getSize()), rawSize);
       }
-      memset(secBuf + prevEnd, 0xCC, sec->getRawSize() - prevEnd);
+      memset(secBuf + prevEnd, 0xCC, rawSize - prevEnd);
     }
 
     parallelForEach(sec->chunks, [&](Chunk *c) {
diff --git a/lld/test/COFF/merge-bss-text-filealign1.test b/lld/test/COFF/merge-bss-text-filealign1.test
new file mode 100644
index 0000000000000..4b03a11fae16b
--- /dev/null
+++ b/lld/test/COFF/merge-bss-text-filealign1.test
@@ -0,0 +1,60 @@
+# REQUIRES: x86
+# Test that merging .bss into .text with /FILEALIGN:1 doesn't crash.
+#
+# RUN: yaml2obj %s -o %t.obj
+# RUN: lld-link /out:%t.exe /entry:main /subsystem:console \
+# RUN:   /merge:.bss=.text /filealign:1 %t.obj
+# RUN: llvm-readobj --sections %t.exe | FileCheck %s
+
+# CHECK:      Name: .text
+# CHECK-NEXT: VirtualSize: 0x104
+# CHECK:      RawDataSize:
+# CHECK-NOT:  Name: .bss
+
+--- !COFF
+header:
+  Machine:         IMAGE_FILE_MACHINE_AMD64
+  Characteristics: [  ]
+sections:
+  - Name:            .text
+    Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+    Alignment:       4
+    SectionData:     'C3'
+    SizeOfRawData:   1
+  - Name:            .bss
+    Characteristics: [ IMAGE_SCN_CNT_UNINITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ]
+    Alignment:       4
+    SectionData:     ''
+    SizeOfRawData:   256
+symbols:
+  - Name:            .text
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_STATIC
+    SectionDefinition:
+      Length:          1
+      NumberOfRelocations: 0
+      NumberOfLinenumbers: 0
+      CheckSum:        0
+      Number:          1
+  - Name:            .bss
+    Value:           0
+    SectionNumber:   2
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_STATIC
+    SectionDefinition:
+      Length:          256
+      NumberOfRelocations: 0
+      NumberOfLinenumbers: 0
+      CheckSum:        0
+      Number:          2
+  - Name:            main
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_EXTERNAL
+...

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-lld

Author: llvmbot

Changes

Backport 6558595

Requested by: @mstorsjo


Full diff: https://github.com/llvm/llvm-project/pull/198212.diff

2 Files Affected:

  • (modified) lld/COFF/Writer.cpp (+7-2)
  • (added) lld/test/COFF/merge-bss-text-filealign1.test (+60)
diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp
index 3ac26681541ba..4451cfeb3dd63 100644
--- a/lld/COFF/Writer.cpp
+++ b/lld/COFF/Writer.cpp
@@ -2617,12 +2617,17 @@ void Writer::writeSections() {
     if ((sec->header.Characteristics & IMAGE_SCN_CNT_CODE) &&
         (ctx.config.machine == AMD64 || ctx.config.machine == I386)) {
       uint32_t prevEnd = 0;
+      uint32_t rawSize = sec->getRawSize();
       for (Chunk *c : sec->chunks) {
         uint32_t off = c->getRVA() - sec->getRVA();
+        // Chunks without data (e.g., .bss) have virtual addresses beyond
+        // rawSize; stop filling when we reach the end of raw data.
+        if (off >= rawSize)
+          break;
         memset(secBuf + prevEnd, 0xCC, off - prevEnd);
-        prevEnd = off + c->getSize();
+        prevEnd = std::min(off + static_cast<uint32_t>(c->getSize()), rawSize);
       }
-      memset(secBuf + prevEnd, 0xCC, sec->getRawSize() - prevEnd);
+      memset(secBuf + prevEnd, 0xCC, rawSize - prevEnd);
     }
 
     parallelForEach(sec->chunks, [&](Chunk *c) {
diff --git a/lld/test/COFF/merge-bss-text-filealign1.test b/lld/test/COFF/merge-bss-text-filealign1.test
new file mode 100644
index 0000000000000..4b03a11fae16b
--- /dev/null
+++ b/lld/test/COFF/merge-bss-text-filealign1.test
@@ -0,0 +1,60 @@
+# REQUIRES: x86
+# Test that merging .bss into .text with /FILEALIGN:1 doesn't crash.
+#
+# RUN: yaml2obj %s -o %t.obj
+# RUN: lld-link /out:%t.exe /entry:main /subsystem:console \
+# RUN:   /merge:.bss=.text /filealign:1 %t.obj
+# RUN: llvm-readobj --sections %t.exe | FileCheck %s
+
+# CHECK:      Name: .text
+# CHECK-NEXT: VirtualSize: 0x104
+# CHECK:      RawDataSize:
+# CHECK-NOT:  Name: .bss
+
+--- !COFF
+header:
+  Machine:         IMAGE_FILE_MACHINE_AMD64
+  Characteristics: [  ]
+sections:
+  - Name:            .text
+    Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+    Alignment:       4
+    SectionData:     'C3'
+    SizeOfRawData:   1
+  - Name:            .bss
+    Characteristics: [ IMAGE_SCN_CNT_UNINITIALIZED_DATA, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE ]
+    Alignment:       4
+    SectionData:     ''
+    SizeOfRawData:   256
+symbols:
+  - Name:            .text
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_STATIC
+    SectionDefinition:
+      Length:          1
+      NumberOfRelocations: 0
+      NumberOfLinenumbers: 0
+      CheckSum:        0
+      Number:          1
+  - Name:            .bss
+    Value:           0
+    SectionNumber:   2
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_STATIC
+    SectionDefinition:
+      Length:          256
+      NumberOfRelocations: 0
+      NumberOfLinenumbers: 0
+      CheckSum:        0
+      Number:          2
+  - Name:            main
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_NULL
+    StorageClass:    IMAGE_SYM_CLASS_EXTERNAL
+...

@dyung dyung moved this from Needs Triage to Needs Review in LLVM Release Status May 18, 2026
@c-rhodes

Copy link
Copy Markdown
Contributor

@mstorsjo @aganea I saw the original PR, it's been in trunk for over 3 months now with no issues so I'll defer to you on this one. If you're happy with it I'm happy to land.

@mstorsjo

Copy link
Copy Markdown
Member

@mstorsjo @aganea I saw the original PR, it's been in trunk for over 3 months now with no issues so I'll defer to you on this one. If you're happy with it I'm happy to land.

Yeah I'm happy to backport this (I didn't approve it myself first since I was the one that requested it), but if @aganea is unavailable I'll press the button.

@mstorsjo mstorsjo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-project-automation github-project-automation Bot moved this from Needs Review to Needs Merge in LLVM Release Status May 26, 2026
@Andarwinux

Andarwinux commented May 26, 2026

Copy link
Copy Markdown
Member

#180411 (comment) (Somehow GitHub has hidden it)

this exposed a new bug I guess:

clang_release_ble/DM40Native.exe //20.1.8
  FileSize=55550 EP=0xd8b5 SizeOfImage=0xd8fe SizeOfHeaders=0x198
  [.data] VA=0x198 VSize=0xd766 RawOff=0x198 RawSz=0xd766 Chars=0xe0000020  VSize-RawSz=0

clang_release_ble/DM40Native_broken.exe //trunk
  FileSize=55710 EP=0xd949 SizeOfImage=0xd9a4 SizeOfHeaders=0x198
  [.data] VA=0x198 VSize=0xd80c RawOff=0x198 RawSz=0xd806 Chars=0xe0000020  VSize-RawSz=6

clang_release_bt/DM40Native.exe //20.1.8
  FileSize=52542 EP=0xccc2 SizeOfImage=0xcd3e SizeOfHeaders=0x198
  [.data] VA=0x198 VSize=0xcba6 RawOff=0x198 RawSz=0xcba6 Chars=0xe0000020  VSize-RawSz=0

clang_release_bt/DM40Native_broken.exe //trunk
  FileSize=52718 EP=0xcd6a SizeOfImage=0xcdf4 SizeOfHeaders=0x198
  [.data] VA=0x198 VSize=0xcc5c RawOff=0x198 RawSz=0xcc56 Chars=0xe0000020  VSize-RawSz=6

PE loader rejects the exe

seems to be due to:

if (c->hasData)
    rawSize = alignTo(virtualSize, config->fileAlign);

since bss is not handled in this block it gets excluded by this, locally for me this fixed it but this probably isn't the correct solution since filealign 2 and 4 would still probably be outside the rawSize:

if (config->fileAlign == 1 && rawSize < virtualSize)
    rawSize = virtualSize;

so maybe something like this would be better:

if (rawSize < alignTo(virtualSize, config->fileAlign))
    rawSize = alignTo(virtualSize, config->fileAlign);

This might be a problem.

@mstorsjo

Copy link
Copy Markdown
Member

#180411 (comment) (Somehow GitHub has hidden it)

this exposed a new bug I guess:

PE loader rejects the exe

Thanks for pointing this out - I missed this comment. I'm not entirely convinced that the issue is caused by this commit though. But perhaps that's reason enough to hold back off backporting it after all?

@c-rhodes c-rhodes closed this Jun 1, 2026
@c-rhodes c-rhodes moved this from Needs Merge to Won't Merge in LLVM Release Status Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Won't Merge

Development

Successfully merging this pull request may close these issues.

6 participants