Skip to content

Commit f604dbc

Browse files
committed
Merge branch 'plt_cfg_edge' into 'main'
Fix bug that could drop inter-procedural CFG edges Closes #635 See merge request rewriting/ddisasm!1256
2 parents 6a7d544 + f9895ff commit f604dbc

6 files changed

Lines changed: 158 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Improve resolution of TLS-related symbolic expression
66
* Fix bug that could lead to functional errors due to false-positive symbolic operands or data.
77
* Fix bug that could drop fall-through edges from calls to conditionally no-return functions
8+
* Fix bug that could drop inter-procedural CFG edges.
89

910
# 1.9.2
1011

examples/ex_plt_cfg_edge/Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CC="gcc"
2+
CFLAGS=
3+
EXEC=
4+
5+
all: fun.c ex.c
6+
$(CC) -fPIC -shared $(CFLAGS) fun.c -o fun.so
7+
$(CC) ex.c -no-pie -fno-PIC $(CFLAGS) -o ex -L. -l:fun.so
8+
@ LD_LIBRARY_PATH=. $(EXEC) ./ex > out.txt
9+
clean:
10+
rm -f ex ex.gtirb fun.so *.s *.i *.o out.txt
11+
check:
12+
@ LD_LIBRARY_PATH=. $(EXEC) ./ex > /tmp/res.txt
13+
@ diff out.txt /tmp/res.txt && echo TEST OK

examples/ex_plt_cfg_edge/ex.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <stdio.h>
2+
3+
/*
4+
NOTE: This example is designed to produce the following PLT entry:
5+
#===================================
6+
.section .plt.sec ,"ax",@progbits
7+
#===================================
8+
#-----------------------------------
9+
.globl fun
10+
.type fun, @function
11+
#-----------------------------------
12+
fun:
13+
401070: endbr64
14+
401074: bnd jmp QWORD PTR [RIP+.L_404020]
15+
.size fun, . - fun
16+
17+
#===================================
18+
.section .got.plt ,"wa",@progbits
19+
#===================================
20+
.L_404020:
21+
404020: .quad fun
22+
23+
This C program must be compiled with `-fno-PIC` and linked with `-no-pie`
24+
(see the Makefile).
25+
26+
1. -fno-PIC: Forces the compiler to treat the address of `fun` as a fixed
27+
absolute constant.
28+
29+
2. -no-pie: Prevents the linker from making the binary position-independent,
30+
allowing the symbol `fun` to have a static VMA (Virtual Memory Address).
31+
32+
3. Function pointer: By taking the address `(void*)fun`, we force the Linker
33+
to create a `Canonical PLT`. Because the linker doesn't know the real
34+
address of `fun` (it's in fun.so), it uses the address of the PLT stub
35+
as the function's identity to satisfy the absolute resolution.
36+
*/
37+
38+
// External function defined in fun.so
39+
extern void fun();
40+
41+
int main()
42+
{
43+
puts("Calling fun...");
44+
45+
// Standard call
46+
fun();
47+
48+
// Address-of: Forces the .dynsym address to be non-zero (Canonical PLT).
49+
// The linker stamps the PLT address (e.g., 0x401070) into the symbol table
50+
// so that `&fun` returns the same pointer value throughout the execution.
51+
printf("Canonical PLT address of fun: %p\n", (void*)fun);
52+
53+
return 0;
54+
}

examples/ex_plt_cfg_edge/fun.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <stdio.h>
2+
3+
void fun()
4+
{
5+
printf("Called fun.\n");
6+
}

src/passes/Disassembler.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,20 @@ gtirb::EdgeType getEdgeType(const std::string &type)
10991099
return gtirb::EdgeType::Fallthrough;
11001100
}
11011101

1102+
std::map<gtirb::Addr, std::string> getPltBlocks(gtirb::Module &Module,
1103+
souffle::SouffleProgram &Program)
1104+
{
1105+
std::map<gtirb::Addr, std::string> Res;
1106+
for(auto &Output : *Program.getRelation("plt_block"))
1107+
{
1108+
gtirb::Addr Ea;
1109+
std::string Name;
1110+
Output >> Ea >> Name;
1111+
Res[Ea] = Name;
1112+
}
1113+
return Res;
1114+
}
1115+
11021116
void buildCFG(gtirb::Context &Context, gtirb::Module &Module, souffle::SouffleProgram &Program)
11031117
{
11041118
auto &Cfg = Module.getIR()->getCFG();
@@ -1134,6 +1148,7 @@ void buildCFG(gtirb::Context &Context, gtirb::Module &Module, souffle::SoufflePr
11341148
auto E = addEdge(Src, TopBlock, Cfg);
11351149
Cfg[*E] = std::make_tuple(isConditional, gtirb::DirectEdge::IsIndirect, EdgeType);
11361150
}
1151+
auto PltBlocks = getPltBlocks(Module, Program);
11371152
for(auto &T : *Program.getRelation("cfg_edge_to_symbol"))
11381153
{
11391154
gtirb::Addr EA;
@@ -1156,6 +1171,21 @@ void buildCFG(gtirb::Context &Context, gtirb::Module &Module, souffle::SoufflePr
11561171
{
11571172
gtirb::CodeBlock *TgtCodeBlock = Symbol.getReferent<gtirb::CodeBlock>();
11581173
if(TgtCodeBlock)
1174+
{
1175+
auto TgtAddr = TgtCodeBlock->getAddress();
1176+
if(TgtAddr.has_value())
1177+
{
1178+
// If TgtCodeBlock is a PltBlock, invalidate it here
1179+
// so that a ProxyBlock can be created instead.
1180+
// See the comment in examples/ex_plt_cfg_edge/ex.c
1181+
auto PltBlockIt = PltBlocks.find(TgtAddr.value());
1182+
if(PltBlockIt != PltBlocks.end())
1183+
{
1184+
TgtCodeBlock = nullptr;
1185+
}
1186+
}
1187+
}
1188+
if(TgtCodeBlock)
11591189
{
11601190
std::cerr
11611191
<< "WARNING: symbol " << Name

tests/cfg_test.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import subprocess
77
import os
88
from gtirb.cfg import EdgeType, EdgeLabel
9-
from typing import List, Dict, Tuple
9+
from typing import List, Dict, Tuple, Union
1010

1111

1212
ex_dir = Path("./examples/")
@@ -688,17 +688,24 @@ def check_edges(
688688
def check_plt_edges(
689689
self,
690690
module: gtirb.Module,
691-
plt_calls: List[Tuple[str, EdgeLabel, EdgeLabel, str]],
691+
plt_calls: List[
692+
Tuple[Union[str, gtirb.CodeBlock], EdgeLabel, EdgeLabel, str]
693+
],
694+
is_proxy_target: bool = False,
692695
) -> None:
693696
"""
694697
Check that each call represented in `plt_calls` has the right
695698
sequences of edges that lead to the expected target.
696699
697700
Each element in `plt_call` is a tuple with a starting
698-
symbol, two edge labels, and a target symbol.
701+
symbol name or source block, two edge labels, and a target symbol.
699702
"""
700703
for src, edge_label1, edge_label2, tgt in plt_calls:
701-
src_block = next(module.symbols_named(src)).referent
704+
src_block = (
705+
next(module.symbols_named(src)).referent
706+
if isinstance(src, str)
707+
else src
708+
)
702709
edges = [
703710
edge
704711
for edge in src_block.outgoing_edges
@@ -710,19 +717,24 @@ def check_plt_edges(
710717
f"Expected one edge with label {edge_label1} from {src}",
711718
)
712719
plt_block = edges[0].target
713-
self.assertEqual(plt_block.section.name, ".plt")
720+
self.assertIn(plt_block.section.name, [".plt", ".plt.sec"])
714721
edges_plt = [
715722
edge
716723
for edge in plt_block.outgoing_edges
717724
if edge.label == edge_label2
718725
]
726+
719727
self.assertEqual(
720728
len(edges_plt),
721729
1,
722730
f"Expected one edge with label {edge_label2} "
723731
f"from block at {plt_block.address:0x} called from {src}",
724732
)
725733
tgt_block = edges_plt[0].target
734+
735+
if is_proxy_target:
736+
self.assertIsInstance(tgt_block, gtirb.ProxyBlock)
737+
726738
self.assertIn(tgt, [s.name for s in tgt_block.references])
727739

728740
@unittest.skipUnless(
@@ -1094,6 +1106,43 @@ def test_arm64_calls(self):
10941106
]
10951107
self.check_plt_edges(m, plt_calls)
10961108

1109+
@unittest.skipUnless(
1110+
platform.system() == "Linux", "This test is linux only."
1111+
)
1112+
def test_x86_64_plt_cfg_edge(self):
1113+
"""
1114+
Test that a CFG edge is created from a PLT block to its external
1115+
target (ProxyBlock) when the PLT block and the target have
1116+
the same symbol.
1117+
"""
1118+
binary = Path("ex")
1119+
with cd(ex_dir / "ex_plt_cfg_edge"):
1120+
self.assertTrue(compile("gcc", "g++", "-O0", ["--save-temps"]))
1121+
ir_library = disassemble(binary).ir()
1122+
m = ir_library.modules[0]
1123+
1124+
main_sym = next(m.symbols_named("main"))
1125+
main_block = main_sym.referent
1126+
1127+
fallthrough = [
1128+
edge
1129+
for edge in main_block.outgoing_edges
1130+
if edge.label.type == EdgeType.Fallthrough
1131+
]
1132+
self.assertEqual(1, len(fallthrough))
1133+
1134+
call_fun_block = fallthrough[0].target
1135+
1136+
plt_calls = [
1137+
(
1138+
call_fun_block,
1139+
EdgeLabel(EdgeType.Call, False, True),
1140+
EdgeLabel(EdgeType.Branch, False, False),
1141+
"fun",
1142+
),
1143+
]
1144+
self.check_plt_edges(m, plt_calls, True)
1145+
10971146

10981147
if __name__ == "__main__":
10991148
unittest.main()

0 commit comments

Comments
 (0)