Skip to content

Commit 884cccc

Browse files
authored
Add support for smaller vdso trampolines (13 bytes -> 5 bytes) (#2275)
Fixes #2273
1 parent 0ef4cf6 commit 884cccc

2 files changed

Lines changed: 76 additions & 22 deletions

File tree

CHANGELOG

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ options. See https://github.com/shadow/shadow/issues/1945
1313
file description. https://github.com/shadow/shadow/issues/2260
1414

1515
* Fixed a bug causing `timerfd_settime` to not reset the internal timer's
16-
expiration count. https://github.com/shadow/shadow/pull/2279
16+
expiration count. https://github.com/shadow/shadow/pull/2279
17+
18+
* Fixed a panic when patching the VDSO in newer kernels, such as those in Ubuntu 22.04.
19+
https://github.com/shadow/shadow/issues/2273

src/lib/shim/patch_vdso.c

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -157,29 +157,48 @@ static int _replacement_getcpu(void* arg1, void* arg2, void* arg3) {
157157
return (int)syscall(SYS_getcpu, arg1, arg2, arg3);
158158
}
159159

160-
static void _inject_trampoline(struct ParsedElf* parsedElf, const char* vdsoFnName,
161-
void* replacementFn) {
162-
const Elf64_Sym* symbol = _findSymbol(parsedElf, vdsoFnName);
163-
if (symbol == NULL) {
164-
warning("Couldn't find symbol '%s' to override", vdsoFnName);
165-
return;
160+
// Inject a trampoline that uses a relative jump. Only needs 5 bytes, but requires
161+
// that the offset fits in an i32.
162+
//
163+
// Returns injected trampoline size on success, or 0 if the trampoline couldn't be injected.
164+
static size_t _inject_trampoline_relative(void* start, size_t symbolSize, void* replacementFn) {
165+
intptr_t jmp_offset = (intptr_t)replacementFn - ((intptr_t)start + 5);
166+
167+
if (jmp_offset > INT32_MAX || jmp_offset < INT32_MIN) {
168+
trace("Offset from %p to %p doesn't fit in i32", start, replacementFn);
169+
return 0;
170+
}
171+
172+
const size_t trampolineSize = 5;
173+
if (symbolSize < trampolineSize) {
174+
trace("Can't inject %zd byte trampoline into %zd byte symbol", trampolineSize, symbolSize);
175+
return 0;
166176
}
167177

168-
// Manually calculated trampoline size. Later validated in assertion.
178+
uint8_t* current = start;
179+
// opcode for jmp
180+
*(current++) = 0xe9;
181+
// jmp offset
182+
*(int32_t*)current = (int32_t)jmp_offset;
183+
current += sizeof(int32_t);
184+
185+
size_t actualTrampolineSize = (size_t)current - (size_t)start;
186+
assert(actualTrampolineSize == trampolineSize);
187+
return actualTrampolineSize;
188+
}
189+
190+
// Inject a trampoline using an absolute jump. Less efficient at runtime and
191+
// needs 13 bytes, but can jump to any target address.
192+
//
193+
// Returns injected trampoline size on success, or 0 if the trampoline couldn't be injected.
194+
static size_t _inject_trampoline_absolute(void* start, size_t symbolSize, void* replacementFn) {
169195
const size_t trampolineSize = 13;
170196

171-
if (symbol->st_size < trampolineSize) {
172-
// We *could* just log a warning here and return without injecting the
173-
// trampoline. Shim-side warnings are currently a bit hidden, though.
174-
// Better to err on the size of not accidentally proceeding with a
175-
// potentially invalid simulation.
176-
//
177-
// TODO: Revisit when https://github.com/shadow/shadow/issues/2023 is fixed.
178-
panic("Can't inject %zd byte trampoline into %zd byte symbol '%s'", trampolineSize,
179-
symbol->st_size, vdsoFnName);
197+
if (symbolSize < trampolineSize) {
198+
trace("Can't inject %zd byte trampoline into %zd byte symbol", trampolineSize, symbolSize);
199+
return 0;
180200
}
181201

182-
uint8_t* start = (void*)parsedElf->hdr + symbol->st_value;
183202
uint8_t* current = start;
184203

185204
// movabs $...,%r10
@@ -193,11 +212,43 @@ static void _inject_trampoline(struct ParsedElf* parsedElf, const char* vdsoFnNa
193212
*(current++) = 0xff;
194213
*(current++) = 0xe2;
195214

196-
// Validate trampolineSize.
197-
const size_t actualTrampolineSize = (size_t)current - (size_t)start;
198-
// In debug builds require equality.
215+
size_t actualTrampolineSize = (size_t)current - (size_t)start;
199216
assert(actualTrampolineSize == trampolineSize);
200-
// Otherwise just ensure we didn't actually clobber another symbol.
217+
return actualTrampolineSize;
218+
}
219+
220+
static void _inject_trampoline(struct ParsedElf* parsedElf, const char* vdsoFnName,
221+
void* replacementFn) {
222+
const Elf64_Sym* symbol = _findSymbol(parsedElf, vdsoFnName);
223+
if (symbol == NULL) {
224+
// This could happen e.g. if vdso is disabled at the system level.
225+
warning("Couldn't find symbol '%s' to override", vdsoFnName);
226+
return;
227+
}
228+
229+
uint8_t* start = (void*)parsedElf->hdr + symbol->st_value;
230+
231+
size_t actualTrampolineSize =
232+
_inject_trampoline_relative(start, symbol->st_size, replacementFn);
233+
if (actualTrampolineSize == 0) {
234+
actualTrampolineSize = _inject_trampoline_absolute(start, symbol->st_size, replacementFn);
235+
}
236+
// TODO: Some other trampoline strategies if neither of the above work:
237+
//
238+
// * In case the replacementFn is more than a 32-bit offset away, create a secondary
239+
// trampoline that *is* < 32-bit-offset away, e.g. using `mmap` with a supplied hint.
240+
//
241+
// * Inject `ud2; ret`, or even just `ud2`. `ud2` is only 2 bytes and raises
242+
// SIGILL; we could get control in the SIGILL signal handler and figure out
243+
// which patched function we're trying to execute by inspecting the
244+
// instruction pointer in the siginfo_t.
245+
246+
if (actualTrampolineSize == 0) {
247+
// TODO: Make make this a warning or error when shim-side logs are more visible.
248+
panic("Couldn't patch symbol '%s'", vdsoFnName);
249+
}
250+
251+
// Validate that we didn't actually clobber another symbol.
201252
if (symbol->st_size < actualTrampolineSize) {
202253
panic("Accidentally wrote %zd byte trampoline into %zd byte symbol %s",
203254
actualTrampolineSize, symbol->st_size, vdsoFnName);

0 commit comments

Comments
 (0)