Skip to content

Commit ddf1e5a

Browse files
ppc64le: rewrite ASM code to not generate TEXTREL
Previous versions of libpulp for the ppc64le generated text relocations, which makes the dynamic linker resolve them when the library is being loaded. This actually causes a problem when the program is loaded with seccomp mprotect protection, because it needs to set the page to EXECUTABLE and WRITABLE for a short period of time, causing the application to crash. This commit fixes this by refatoring the code into not using TEXTREL. This comes with a performance cost because __tls_get_addr needs to be called in order for this to work, and more registers needs to be saved for this function to be successfully called. Signed-off-by: gbelinassi <gbelinassi@suse.de>
1 parent 4e5a091 commit ddf1e5a

File tree

5 files changed

+182
-81
lines changed

5 files changed

+182
-81
lines changed

Diff for: lib/arch/powerpc64le/patch.c

+6-2
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,10 @@ ulp_pthread_key_init(void)
249249
* In the ulp prologue in ppc64le we need to save the TOC and LR registers
250250
* before redirect into a new function, and we store it in a stack allocated
251251
* by mmap. This routine does exactly this.
252+
*
253+
* @return The address of the ulp_stack object.
252254
*/
253-
void ulp_stack_helper(void)
255+
void *ulp_stack_helper(void)
254256
{
255257
/* Comparison should have been done in trampoline_routine (this function
256258
caller), so just assert it here. */
@@ -276,7 +278,7 @@ void ulp_stack_helper(void)
276278
/* In this case the system is out of memory... And there is nothing
277279
we can do. */
278280
libpulp_crash("libpulp: mmap returned -1, application can not continue\n");
279-
return;
281+
return ulp_stack;
280282
}
281283

282284
/* In case we have a previous allocated buffer, then copy this. */
@@ -304,4 +306,6 @@ void ulp_stack_helper(void)
304306
/* Setup destructor for mmap memory, so we don't leak memory when a thread
305307
is destroyed. */
306308
pthread_once(&ulp_once_control, ulp_pthread_key_init);
309+
310+
return ulp_stack;
307311
}

Diff for: lib/arch/powerpc64le/ulp_prologue.S

+133-77
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
.globl ulp_stack_helper
3333
.type ulp_stack_helper, @function
3434

35+
# __tls_get_addr
36+
.globl __tls_get_addr
37+
.type __tls_get_addr, @function
38+
3539
.section ".text"
3640
.align 2
3741
.p2align 4,,15
@@ -40,44 +44,75 @@
4044
.globl trampoline_routine
4145
.type trampoline_routine, @function
4246
trampoline_routine:
43-
.cfi_startproc
47+
.cfi_startproc
4448

4549
# Concatenate two registers from prologue.
4650
rldimi %r6, %r5, 32, 0
4751

4852
# Move the target function ptr to control register so we can free r6.
4953
mtctr %r6
5054

51-
# Load the ulp_stack into r5 through r13 (thread local storage ptr)
52-
addis %r5, %r13, ulp_stack@tprel@ha
53-
addi %r5, %r5, ulp_stack@tprel@l
55+
# Save all volatile registers
56+
# r5 & r6 are designated temp regs, having data already on stack.
57+
# After return from expand_ulp_stack, both regs construct values
58+
# before use.
59+
std %r2, -24(%r1)
60+
std %r3, -32(%r1)
61+
std %r12, -40(%r1)
62+
mflr %r2
63+
std %r2, -48(%r1)
64+
65+
# Move stack register
66+
addi %r1, %r1, -(48 + 32 + 8) # 32 + 8 for padding
67+
68+
# Fix TOC. %r12 must be pointing to the address of trampoline_routine.
69+
addis %r2,%r12, .TOC.-trampoline_routine@ha
70+
addi %r2,%r2 , .TOC.-trampoline_routine@l
71+
72+
# Load ulp_stack
73+
addis %r3, %r2, ulp_stack@got@tlsgd@ha
74+
addi %r3, %r3, ulp_stack@got@tlsgd@l
5475

55-
# Load real_size
56-
ld %r6, ULP_STACK_REAL_SIZE(%r5) # Load real_size (allocated by mmap)
57-
ld %r5, ULP_STACK_USED_SIZE(%r5) # Load used_size (currently in use)
76+
# Get address of ulp_stack
77+
bl __tls_get_addr(ulp_stack@tlsgd)
78+
nop
5879

59-
# Check if we have space.
60-
cmpd %cr0, %r6, %r5
80+
# Load ulp_stack attributes
81+
ld %r6, ULP_STACK_REAL_SIZE(%r3) # Load real_size (allocated by mmap)
82+
ld %r5, ULP_STACK_USED_SIZE(%r3) # Load used_size
83+
84+
# Check if we have space
85+
cmpd %cr0, %r6, %r5
6186
ble %cr0, .Lexpand_ulp_stack
6287

6388
.Lcontinue_ulp_prologue:
6489

65-
# Reload the ulp_stack into r5 through r13 (thread local storage ptr)
66-
addis %r5, %r13, ulp_stack@tprel@ha
67-
addi %r5, %r5, ulp_stack@tprel@l
90+
# Here we must ensure that %r3 points to ulp_stack. If we are here from
91+
# the .Lexpand_ulp_stack, then r3 will point to it because
92+
# ulp_stack_helper returned it.
6893

6994
# Load used_size
70-
ld %r6, ULP_STACK_USED_SIZE(%r5)
95+
ld %r6, ULP_STACK_USED_SIZE(%r3)
7196

7297
# Update top_of_stack in the struct field.
7398
addi %r6, %r6, 16
74-
std %r6, ULP_STACK_USED_SIZE(%r5) # Store new used size value.
99+
std %r6, ULP_STACK_USED_SIZE(%r3) # Store new used size value.
75100

76101
# Load stack ptr
77-
ld %r5, ULP_STACK_PTR(%r5)
102+
ld %r5, ULP_STACK_PTR(%r3)
78103

79104
# Store TOC
80-
add %r5, %r5, %r6 # ulp_stack + used_size
105+
add %r5, %r5, %r6 # ulp_stack_ptr + used_size
106+
107+
# Restore stack register.
108+
addi %r1, %r1, (48 + 32 + 8)
109+
110+
# Load original LR
111+
ld %r2, -48(%r1)
112+
mtlr %r2
113+
114+
# Load original TOC
115+
ld %r6, -24(%r1)
81116

82117
# At this point, %r5 points to 16 bytes ahead of the slot where we shall
83118
# save TOC. Hence we have to subtract 16 bytes of the storing location,
@@ -86,29 +121,55 @@ trampoline_routine:
86121
# +----------------------------------v
87122
# | TOC1 | LR1 || ... || _8b_ | _8b_ |
88123
# +----------------------------------+
89-
std %r2, -16(%r5) # store in *(ulp_stack + used_size - 16)
90-
mflr %r2
124+
std %r6, -16(%r5) # store in *(ulp_stack + used_size - 16)
91125
std %r2, -8(%r5) # store in *(ulp_stack + used_size - 8)
92126

127+
93128
# Restore registers
94-
ld %r5, -8(%r1) # Restore register.
95-
ld %r6, -16(%r1) # Restore register.
129+
ld %r5, -8(%r1) # Restore register.
130+
ld %r6, -16(%r1) # Restore register.
131+
#ld %r2, -24(%r1) # r2 was already loaded
132+
ld %r3, -32(%r1)
133+
ld %r12, -40(%r1)
96134

97-
# Jump to target function
135+
# jump to target function
98136
mfctr %r12
99137
bctrl
100138

101-
# Load the ulp_stack into r5 through r13 (thread local storage ptr)
102-
addis %r5, %r13, ulp_stack@tprel@ha
103-
addi %r5, %r5, ulp_stack@tprel@l
139+
# Save return registers and ones used by __get_tls_addr.
140+
std %r3, -8(%r1)
141+
std %r12, -16(%r1)
142+
143+
# Move stack register
144+
addi %r1, %r1, -(16 + 32 + 8) # 32 + 8 for padding
145+
146+
# Do a trick to load PC into LR register.
147+
bl .return_to_caller
148+
.return_to_caller:
149+
mflr %r12
150+
151+
# Get the function address.
152+
addi %r12, %r12, trampoline_routine - .return_to_caller
153+
154+
# Fix TOC. %r12 must be pointing to the address of trampoline_routine.
155+
addis %r2,%r12, .TOC.-trampoline_routine@ha
156+
addi %r2,%r2 , .TOC.-trampoline_routine@l
157+
158+
# Load ulp_stack
159+
addis %r3, %r2, ulp_stack@got@tlsgd@ha
160+
addi %r3, %r3, ulp_stack@got@tlsgd@l
161+
162+
# Get address of ulp_stack
163+
bl __tls_get_addr(ulp_stack@tlsgd)
164+
nop
104165

105166
# Deference ulp_stack.
106-
ld %r6, ULP_STACK_USED_SIZE(%r5)
167+
ld %r6, ULP_STACK_USED_SIZE(%r3)
107168
addi %r6, %r6, -16 # Sub 16 bytes because the first entry stores the top of stack, and we need to store 2 longs.
108-
std %r6, ULP_STACK_USED_SIZE(%r5) # Store new used_size value.
169+
std %r6, ULP_STACK_USED_SIZE(%r3) # Store new used_size value.
109170

110171
# Load ulp_stack ptr field.
111-
ld %r5, ULP_STACK_PTR(%r5)
172+
ld %r5, ULP_STACK_PTR(%r3)
112173

113174
# Point to the top of stack but two, these two entries are popped in
114175
# previous step and accessed in next step (stack size decremented before access).
@@ -119,73 +180,68 @@ trampoline_routine:
119180
ld %r8, 8(%r5) # Restore LR
120181
mtlr %r8 # Load LR
121182

122-
# Return execution to caller.
183+
# Restore used registers
184+
addi %r1, %r1, (16 + 32 + 8) # 32 + 8 for padding
185+
ld %r3, -8(%r1)
186+
ld %r12, -16(%r1)
187+
188+
# Return.
123189
blr
124190

125191
.Lexpand_ulp_stack:
126192

127193
# Save all volatile registers
128194
# r5 & r6 are designated temp regs, having data already on stack.
195+
# r0, r2 & r12 is as well in this slow path.
129196
# After return from expand_ulp_stack, both regs construct values
130197
# before use.
131-
std %r2, -24(%r1)
132-
std %r3, -32(%r1)
133-
std %r4, -40(%r1)
134-
std %r7, -48(%r1)
135-
std %r8, -56(%r1)
136-
std %r9, -64(%r1)
137-
std %r10, -72(%r1)
138-
std %r11, -80(%r1)
139-
std %r12, -88(%r1)
140-
mfctr %r3
141-
std %r3, -96(%r1)
142-
mflr %r3,
143-
std %r3, -104(%r1)
144-
145-
# As per ppc64le ABIv2, the minimum stack frame is of 32 bytes and
146-
# additional 8 bytes padding is needed for alignment in stack frame.
147-
# The regs stored in redzone must have this 32+8 bytes padding to form
148-
# auxiliary stack frame before calling ulp_stack_helper which will
149-
# have its own proper stack frame.
150-
151-
# Move stack register
152-
addi %r1, %r1, -(104 + 32 + 8) # 32 + 8 for padding
153-
154-
# Fix TOC. %r12 must be pointing to the address of trampoline_routine.
155-
addis %r2,%r12, .TOC.-trampoline_routine@ha
156-
addi %r2,%r2 , .TOC.-trampoline_routine@l
198+
std %r4, -8(%r1)
199+
std %r7, -16(%r1)
200+
std %r8, -24(%r1)
201+
std %r9, -32(%r1)
202+
std %r10, -40(%r1)
203+
std %r11, -48(%r1)
204+
mfctr %r4
205+
std %r4, -56(%r1)
206+
mflr %r4,
207+
std %r4, -64(%r1)
208+
209+
# Setup stack frame
210+
addi %r1, %r1, -(64 + 32 + 8)
157211

158212
# Call C helper routine.
159-
bl ulp_stack_helper
213+
bl ulp_stack_helper
160214
nop
161215

162-
# Restore stack register.
163-
addi %r1, %r1, (104 + 32 + 8)
164-
165-
# Restore registers
166-
ld %r3, -104(%r1)
167-
mtlr %r3
168-
ld %r3, -96(%r1)
169-
mtctr %r3
170-
ld %r12, -88(%r1)
171-
ld %r11, -80(%r1)
172-
ld %r10, -72(%r1)
173-
ld %r9, -64(%r1)
174-
ld %r8, -56(%r1)
175-
ld %r7, -48(%r1)
176-
ld %r4, -40(%r1)
177-
ld %r3, -32(%r1)
178-
ld %r2, -24(%r1)
179-
216+
# Restore stack frame
217+
addi %r1, %r1, (64 + 32 + 8)
218+
219+
# Load back registers.
220+
ld %r7, -16(%r1)
221+
ld %r8, -24(%r1)
222+
ld %r9, -32(%r1)
223+
ld %r10, -40(%r1)
224+
ld %r11, -48(%r1)
225+
ld %r4, -56(%r1)
226+
mtctr %r4
227+
ld %r4, -64(%r1)
228+
mtlr %r4
229+
ld %r4, -8(%r1)
230+
231+
# Continue execution
180232
b .Lcontinue_ulp_prologue
181233

182234
.long 0
183235
.byte 0,0,0,0,0,0,0,0
184236
.cfi_endproc
185237
.size trampoline_routine,.-trampoline_routine
186238

187-
.globl ulp_prologue
188-
.type ulp_prologue, @function
239+
# The following function needs to be placed in .data, as it is a template to be
240+
# copied in the prologue of tha patched function. Placing this in .data avoids
241+
# text relocations.
242+
.section ".data"
243+
.globl ulp_prologue
244+
.type ulp_prologue, @function
189245
ulp_prologue:
190246
.cfi_startproc
191247
std %r5, -8(%r1) # Save one register used as function parameter
@@ -195,7 +251,7 @@ ulp_prologue:
195251
lis %r5, trampoline_routine@highest #0x1122
196252
ori %r5, %r5, trampoline_routine@higher #0x3344
197253
lis %r12, trampoline_routine@high #0x5566
198-
ori %r12, %r12, trampoline_routine@l #0x7788
254+
ori %r12, %r12, trampoline_routine@l #0x7788
199255

200256
# Concatenate two registers
201257
rldimi %r12, %r5, 32, 0
@@ -223,7 +279,7 @@ ulp_prologue_end = .
223279
.size ulp_prologue,.-ulp_prologue
224280
ulp_prologue_padding_end = .
225281

226-
.section ".data"
282+
.section ".rodata"
227283
.align 2
228284
.type ulp_prologue_size, @object
229285
.size ulp_prologue_size, 4

Diff for: tests/Makefile.am

+2-1
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,8 @@ TESTS = \
695695
insn_queue.py \
696696
chroot.py \
697697
visibility.py \
698-
notes.py
698+
notes.py \
699+
textrel.py
699700

700701
XFAIL_TESTS = \
701702
blocked.py \

Diff for: tests/testsuite.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
# Libpulp definitions
4040
builddir = os.getcwd()
4141
ulptool = builddir + '/../tools/ulp'
42+
libpulp_path = builddir + '/../lib/.libs/libpulp.so'
4243

4344
# Check if certain library is livepatchable.
4445
def is_library_livepatchable(library):
@@ -116,7 +117,7 @@ def __init__(self, testname, timeout=10, env=Ellipsis, log=sys.stdout,
116117

117118
# If env has not been provided, default to LD_PRELOAD'ing libpulp.so.
118119
if env == Ellipsis:
119-
env = {'LD_PRELOAD': builddir + '/../lib/.libs/libpulp.so'}
120+
env = {'LD_PRELOAD': libpulp_path}
120121

121122
# Spawn the testcase with pexpect and enable logging.
122123
super().__init__(testname, timeout=timeout, env=env, encoding=encoding,

Diff for: tests/textrel.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python3
2+
3+
# libpulp - User-space Livepatching Library
4+
#
5+
# Copyright (C) 2020-2025 SUSE Software Solutions GmbH
6+
#
7+
# This file is part of libpulp.
8+
#
9+
# libpulp is free software; you can redistribute it and/or
10+
# modify it under the terms of the GNU Lesser General Public
11+
# License as published by the Free Software Foundation; either
12+
# version 2.1 of the License, or (at your option) any later version.
13+
#
14+
# libpulp is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
# Lesser General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with libpulp. If not, see <http://www.gnu.org/licenses/>.
21+
22+
import re
23+
import testsuite
24+
import subprocess
25+
26+
# Make sure libpulp is built without TEXTREL.
27+
28+
command = ['readelf', '-d', testsuite.libpulp_path]
29+
try:
30+
output = subprocess.check_output(command, timeout=10, stderr=subprocess.STDOUT)
31+
textrel = re.search('TEXTREL', output.decode())
32+
if not textrel:
33+
exit(0)
34+
35+
except subprocess.TimeoutExpired:
36+
print('readelf timeout')
37+
exit(77)
38+
39+
exit(1)

0 commit comments

Comments
 (0)