Skip to content

Commit eda3aaf

Browse files
libc/powerpc64: Fix swapcontext(3)
On PowerPC platforms a valid link to the Table of Contents (TOC) is required for PLT lookups to function. This TOC pointer is stored in a dedicated register, and is used along with the stack pointer by both C prologue and PLT lookup code. When calling swapcontext() with uc_link != NULL, a PLT lookup to setcontext(3) is attempted from within the _ctx_done context. The exiting process has usually trashed both r1 and r2 at this point, leading to a crash within the PLT lookup before setcontext(2) is reached to restore the linked context. Restore r1 and r2 from the incoming context to ensure the PLT lookup to setcontext(3) succeeds. As this subsequently calls setcontext(2), which overwrites r1 and r2 from the same context a second time, this should be safe. Signed-off-by: Timothy Pearson <[email protected]>
1 parent 88c8cba commit eda3aaf

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

lib/libc/powerpc64/gen/makecontext.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ _ctx_done(ucontext_t *ucp)
4848
/* invalidate context */
4949
ucp->uc_mcontext.mc_len = 0;
5050

51+
/*
52+
* Before continuing, we need to restore enough of the context
53+
* to allow the PLT lookup for syscontext(3) to work.
54+
* syscontext(2) will eventually restore the full context from
55+
* within syscontext(3).
56+
*
57+
* This is a bit of a hack, and is only safe because we never
58+
* return from this function.
59+
60+
* Restore the stack (r1) and TOC (r2) from the linked context
61+
* before calling setcontext(3) via the PLT.
62+
*/
63+
__asm__ __volatile__(
64+
"ld 1,8(%0)\n"
65+
"ld 2,16(%0)\n"
66+
: : "r"(&ucp->uc_link->uc_mcontext.mc_frame[0])
67+
);
68+
5169
setcontext((const ucontext_t *)ucp->uc_link);
5270

5371
abort(); /* should never return from above call */

lib/libc/tests/sys/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ ATF_TESTS_C+= brk_test
77
.endif
88
ATF_TESTS_C+= cpuset_test
99
ATF_TESTS_C+= errno_test
10+
ATF_TESTS_C+= swapcontext_test
1011
ATF_TESTS_C+= queue_test
1112
ATF_TESTS_C+= sendfile_test
1213

13-
# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg,
14-
# swapcontext
14+
# TODO: clone, lwp_create, lwp_ctl, posix_fadvise, recvmmsg
1515
NETBSD_ATF_TESTS_C+= access_test
1616
NETBSD_ATF_TESTS_C+= bind_test
1717
NETBSD_ATF_TESTS_C+= chroot_test
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*-
2+
* Copyright (c) 2025 Raptor Computing Systems, LLC
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <ucontext.h>
10+
#include <errno.h>
11+
12+
#include <atf-c.h>
13+
14+
#define STACK_SIZE (64ull << 10)
15+
16+
static volatile int callback_reached = 0;
17+
18+
static ucontext_t uctx_save, uctx_switch;
19+
20+
static void swapcontext_callback()
21+
{
22+
// Increment callback reached variable
23+
// If this is called multiple times, we will fail the test
24+
// If this is not called at all, we will fail the test
25+
callback_reached++;
26+
}
27+
28+
ATF_TC(swapcontext_basic);
29+
ATF_TC_HEAD(swapcontext_basic, tc)
30+
{
31+
atf_tc_set_md_var(tc, "descr",
32+
"Verify basic functionality of swapcontext");
33+
}
34+
35+
ATF_TC_BODY(swapcontext_basic, tc)
36+
{
37+
char *stack;
38+
int res;
39+
40+
stack = malloc(STACK_SIZE);
41+
ATF_REQUIRE_MSG(stack != NULL, "malloc failed: %s", strerror(errno));
42+
res = getcontext(&uctx_switch);
43+
ATF_REQUIRE_MSG(res == 0, "getcontext failed: %s", strerror(errno));
44+
45+
uctx_switch.uc_stack.ss_sp = stack;
46+
uctx_switch.uc_stack.ss_size = STACK_SIZE;
47+
uctx_switch.uc_link = &uctx_save;
48+
makecontext(&uctx_switch, swapcontext_callback, 0);
49+
50+
res = swapcontext(&uctx_save, &uctx_switch);
51+
52+
ATF_REQUIRE_MSG(res == 0, "swapcontext failed: %s", strerror(errno));
53+
ATF_REQUIRE_MSG(callback_reached == 1,
54+
"callback failed, reached %d times", callback_reached);
55+
}
56+
57+
ATF_TP_ADD_TCS(tp)
58+
{
59+
ATF_TP_ADD_TC(tp, swapcontext_basic);
60+
61+
return (atf_no_error());
62+
}
63+

0 commit comments

Comments
 (0)