2 * Copyright (c) 2019 Apple Inc. All rights reserved.
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
32 #include <darwintest.h>
34 #include <architecture/i386/table.h>
35 #include <i386/user_ldt.h>
36 #include <mach/i386/vm_param.h>
37 #include <mach/i386/thread_status.h>
38 #include <mach/mach.h>
44 #include <sys/types.h>
45 #include <sys/signal.h>
46 #include <sys/sysctl.h>
52 #include <ldt_mach_exc.h>
56 T_META_NAMESPACE("xnu.intel"),
57 T_META_CHECK_LEAKS(false)
61 #define COMPAT_MODE_CS_SELECTOR 0x1f
62 #define SYSENTER_SELECTOR 0xb
64 #define P2ROUNDUP(x, align) (-(-((long)x) & -((long)align)))
67 #define NORMAL_RUN_TIME (10)
68 #define TIMEOUT_OVERHEAD (10)
71 * General theory of operation:
72 * ----------------------------
73 * (1) Ensure that all code and data to be accessed from compatibility mode is
74 * located in the low 4GiB of virtual address space.
75 * (2) Allocate required segments via the i386_set_ldt() system call, making
76 * sure to set the descriptor type correctly (code vs. data). Creating
77 * 64-bit code segments is not allowed (just use the existing 0x2b selector.)
78 * (3) Once you know which selector is associated with the desired code, use a
79 * trampoline (or thunk) to (a) switch to a stack that's located below 4GiB
80 * and (b) save ABI-mandated caller-saved state so that if it's trashed by
81 * compatibility-mode code, it can be restored before returning to 64-bit
82 * mode (if desired), and finally (c) long-jump or long-call (aka far call)
83 * to the segment and desired offset (this example uses an offset of 0 for
85 * (4) Once in compatibility mode, if a framework call or system call is required,
86 * the code must trampoline back to 64-bit mode to do so. System calls from
87 * compatibility mode code are not supported and will result in invalid opcode
88 * exceptions. This example includes a simple 64-bit trampoline (which must
89 * be located in the low 4GiB of virtual address space, since it's executed
90 * by compatibility-mode code.) Note that since the 64-bit ABI mandates that
91 * the stack must be aligned to a 16-byte boundary, the sample trampoline
92 * performs that rounding, to simplify compatibility-mode code. Additionally,
93 * since 64-bit native code makes use of thread-local storage, the user-mode
94 * GSbase must be restored. This sample includes two ways to do that-- (a) by
95 * calling into a C implementation that associates the thread-local storage
96 * pointer with a stack range (which will be unique for each thread.), and
97 * (b) by storing the original GSbase in a block of memory installed into
98 * GSbase before calling into compatibility-mode code. A special machdep
99 * system call restores GSbase as needed. Note that the sample trampoline
100 * does not save and restore %gs (or most other register state, so that is an
101 * area that may be tailored to the application's requirements.)
102 * (5) Once running in compatibility mode, should synchronous or asynchronous
103 * exceptions occur, this sample shows how a mach exception handler (running
104 * in a detached thread, handling exceptions for the entire task) can catch
105 * such exceptions and manipulate thread state to perform recovery (or not.)
106 * Other ways to handle exceptions include installing per-thread exception
107 * servers. Alternatively, BSD signal handlers can be used. Note that once a
108 * process installs a custom LDT, *ALL* future signal deliveries will include
109 * ucontext pointers to mcontext structures that include enhanced thread
110 * state embedded (e.g. the %ds, %es, %ss, and GSBase registers) [This assumes
111 * that the SA_SIGINFO is passed to sigaction(2) when registering handlers].
112 * The mcontext size (part of the ucontext) can be used to differentiate between
113 * different mcontext flavors (e.g. those with/without full thread state plus
114 * x87 FP state, AVX state, or AVX2/3 state).
118 * This test exercises the custom LDT functionality exposed via the i386_{get,set}_ldt
122 * (1a) Exception handling (due to an exception or another thread sending a signal) while
123 * running in compatibility mode;
124 * (1b) Signal handling while running in compatibility mode;
125 * (2) Thunking back to 64-bit mode and executing a framework function (e.g. printf)
126 * (3) Ensuring that transitions to compatibility mode and back to 64-bit mode
127 * do not negatively impact system calls and framework calls in 64-bit mode
128 * (4) Use of thread_get_state / thread_set_state to configure a thread to
129 * execute in compatibility mode with the proper LDT code segment (this is
130 * effectively what the exception handler does when the passed-in new_state
131 * is changed (or what the BSD signal handler return handling does when the
132 * mcontext is modified).)
133 * (5) Ensure that compatibility mode code cannot make system calls via sysenter or
134 * old-style int {0x80..0x82}.
135 * (6) Negative testing to ensure errors are returned if the consumer tries
136 * to set a disallowed segment type / Long flag. [TBD]
140 * Note that these addresses are not necessarily available due to ASLR, so
141 * a robust implementation should determine the proper range to use via
145 /* libdarwintest needs LOTs of stack */
147 #define FIXED_STACK_SIZE (PAGE_SIZE * 16)
148 #define FIXED_TRAMP_MAXLEN (PAGE_SIZE * 8)
159 uint64_t stack_limit
;
161 } stackaddr_to_gsbase_t
;
163 typedef struct thread_arg
{
164 pthread_mutex_t mutex
;
165 pthread_cond_t condvar
;
166 volatile boolean_t done
;
167 uint32_t compat_stackaddr
; /* Compatibility mode stack address */
170 typedef struct custom_tsd
{
171 struct custom_tsd
* this_tsd_base
;
172 uint64_t orig_tsd_base
;
175 typedef uint64_t (*compat_tramp_t
)(far_call_t
*fcp
, void *lowmemstk
, uint64_t arg_for_32bit
,
176 uint64_t callback
, uint64_t absolute_addr_of_thunk64
);
178 #define GS_RELATIVE volatile __attribute__((address_space(256)))
179 static custom_tsd_t GS_RELATIVE
*mytsd
= (custom_tsd_t GS_RELATIVE
*)0;
181 static far_call_t input_desc
= { .seg
= COMPAT_MODE_CS_SELECTOR
, .off
= 0 };
182 static uint64_t stackAddr
= 0;
183 static compat_tramp_t thunkit
= NULL
;
184 static uint64_t thunk64_addr
;
185 /* stack2gs[0] is initialized in map_lowmem_stack() */
186 static stackaddr_to_gsbase_t stack2gs
[] = { { 0 } };
188 extern int compat_mode_trampoline(far_call_t
*, void *, uint64_t);
189 extern void long_mode_trampoline(void);
190 extern boolean_t
mach_exc_server(mach_msg_header_t
*InHeadP
, mach_msg_header_t
*OutHeadP
);
192 extern void code_32(void);
194 kern_return_t
catch_mach_exception_raise_state_identity(mach_port_t exception_port
,
197 exception_type_t exception
,
198 mach_exception_data_t code
,
199 mach_msg_type_number_t code_count
,
201 thread_state_t old_state
,
202 mach_msg_type_number_t old_state_count
,
203 thread_state_t new_state
,
204 mach_msg_type_number_t
* new_state_count
);
207 catch_mach_exception_raise_state(mach_port_t exception_port
,
208 exception_type_t exception
,
209 const mach_exception_data_t code
,
210 mach_msg_type_number_t codeCnt
,
212 const thread_state_t old_state
,
213 mach_msg_type_number_t old_stateCnt
,
214 thread_state_t new_state
,
215 mach_msg_type_number_t
*new_stateCnt
);
218 catch_mach_exception_raise(mach_port_t exception_port
,
221 exception_type_t exception
,
222 mach_exception_data_t code
,
223 mach_msg_type_number_t codeCnt
,
225 thread_state_t old_state
,
226 mach_msg_type_number_t old_stateCnt
,
227 thread_state_t new_state
,
228 mach_msg_type_number_t
*new_stateCnt
);
230 extern void _thread_set_tsd_base(uint64_t);
231 static uint64_t stack_range_to_GSbase(uint64_t stackptr
, uint64_t GSbase
);
232 void restore_gsbase(uint64_t stackptr
);
237 struct thread_identifier_info tiinfo
;
238 unsigned int info_count
= THREAD_IDENTIFIER_INFO_COUNT
;
241 if ((kr
= thread_info(mach_thread_self(), THREAD_IDENTIFIER_INFO
,
242 (thread_info_t
) &tiinfo
, &info_count
)) != KERN_SUCCESS
) {
243 fprintf(stderr
, "Could not get tsd base address. This will not end well.\n");
247 return (uint64_t)tiinfo
.thread_handle
;
251 restore_gsbase(uint64_t stackptr
)
253 /* Restore GSbase so tsd is accessible in long mode */
254 uint64_t orig_GSbase
= stack_range_to_GSbase(stackptr
, 0);
256 assert(orig_GSbase
!= 0);
257 _thread_set_tsd_base(orig_GSbase
);
261 * Though we've directed all exceptions through the catch_mach_exception_raise_state_identity
262 * entry point, we still must provide these two other entry points, otherwise a linker error
266 catch_mach_exception_raise(mach_port_t exception_port
,
269 exception_type_t exception
,
270 mach_exception_data_t code
,
271 mach_msg_type_number_t codeCnt
,
273 thread_state_t old_state
,
274 mach_msg_type_number_t old_stateCnt
,
275 thread_state_t new_state
,
276 mach_msg_type_number_t
*new_stateCnt
)
278 #pragma unused(exception_port, thread, task, exception, code, codeCnt, flavor, old_state, old_stateCnt, new_state, new_stateCnt)
279 fprintf(stderr
, "Unexpected exception handler called: %s\n", __func__
);
284 catch_mach_exception_raise_state(mach_port_t exception_port
,
285 exception_type_t exception
,
286 const mach_exception_data_t code
,
287 mach_msg_type_number_t codeCnt
,
289 const thread_state_t old_state
,
290 mach_msg_type_number_t old_stateCnt
,
291 thread_state_t new_state
,
292 mach_msg_type_number_t
*new_stateCnt
)
294 #pragma unused(exception_port, exception, code, codeCnt, flavor, old_state, old_stateCnt, new_state, new_stateCnt)
295 fprintf(stderr
, "Unexpected exception handler called: %s\n", __func__
);
300 handle_arithmetic_exception(_STRUCT_X86_THREAD_FULL_STATE64
*xtfs64
, uint64_t *ip_skip_countp
)
302 fprintf(stderr
, "Caught divide-error exception\n");
303 fprintf(stderr
, "cs=0x%x rip=0x%x gs=0x%x ss=0x%x rsp=0x%llx\n",
304 (unsigned)xtfs64
->__ss64
.__cs
,
305 (unsigned)xtfs64
->__ss64
.__rip
, (unsigned)xtfs64
->__ss64
.__gs
,
306 (unsigned)xtfs64
->__ss
, xtfs64
->__ss64
.__rsp
);
311 handle_badinsn_exception(_STRUCT_X86_THREAD_FULL_STATE64
*xtfs64
, uint64_t __unused
*ip_skip_countp
)
313 extern void first_invalid_opcode(void);
314 extern void last_invalid_opcode(void);
316 uint64_t start_addr
= ((uintptr_t)first_invalid_opcode
- (uintptr_t)code_32
);
317 uint64_t end_addr
= ((uintptr_t)last_invalid_opcode
- (uintptr_t)code_32
);
319 fprintf(stderr
, "Caught invalid opcode exception\n");
320 fprintf(stderr
, "cs=%x rip=%x gs=%x ss=0x%x rsp=0x%llx | handling between 0x%llx and 0x%llx\n",
321 (unsigned)xtfs64
->__ss64
.__cs
,
322 (unsigned)xtfs64
->__ss64
.__rip
, (unsigned)xtfs64
->__ss64
.__gs
,
323 (unsigned)xtfs64
->__ss
, xtfs64
->__ss64
.__rsp
,
324 start_addr
, end_addr
);
327 * We expect to handle 4 invalid opcode exceptions:
332 * (Note that due to the way the invalid opcode indication was implemented,
333 * %rip is already set to the next instruction.)
335 if (xtfs64
->__ss64
.__rip
>= start_addr
&& xtfs64
->__ss64
.__rip
<= end_addr
) {
337 * On return from the failed sysenter, %cs is changed to the
338 * sysenter code selector and %ss is set to 0x23, so switch them
339 * back to sane values.
341 if ((unsigned)xtfs64
->__ss64
.__cs
== SYSENTER_SELECTOR
) {
342 xtfs64
->__ss64
.__cs
= COMPAT_MODE_CS_SELECTOR
;
343 xtfs64
->__ss
= 0x23; /* XXX */
349 catch_mach_exception_raise_state_identity(mach_port_t exception_port
,
352 exception_type_t exception
,
353 mach_exception_data_t code
,
354 mach_msg_type_number_t codeCnt
,
356 thread_state_t old_state
,
357 mach_msg_type_number_t old_stateCnt
,
358 thread_state_t new_state
,
359 mach_msg_type_number_t
* new_stateCnt
)
361 #pragma unused(exception_port, thread, task)
363 _STRUCT_X86_THREAD_FULL_STATE64
*xtfs64
= (_STRUCT_X86_THREAD_FULL_STATE64
*)(void *)old_state
;
364 _STRUCT_X86_THREAD_FULL_STATE64
*new_xtfs64
= (_STRUCT_X86_THREAD_FULL_STATE64
*)(void *)new_state
;
365 uint64_t rip_skip_count
= 0;
368 * Check the exception code and thread state.
369 * If we were executing 32-bit code (or 64-bit code on behalf of
370 * 32-bit code), we could update the thread state to effectively longjmp
371 * back to a safe location where the victim thread can recover.
372 * Then again, we could return KERN_NOT_SUPPORTED and allow the process
378 if (codeCnt
>= 1 && code
[0] == EXC_I386_DIV
) {
379 handle_arithmetic_exception(xtfs64
, &rip_skip_count
);
383 case EXC_BAD_INSTRUCTION
:
385 if (codeCnt
>= 1 && code
[0] == EXC_I386_INVOP
) {
386 handle_badinsn_exception(xtfs64
, &rip_skip_count
);
392 fprintf(stderr
, "Unsupported catch_mach_exception_raise_state_identity: code 0x%llx sub 0x%llx\n",
393 code
[0], codeCnt
> 1 ? code
[1] : 0LL);
394 fprintf(stderr
, "flavor=%d %%cs=0x%x %%rip=0x%llx\n", *flavor
, (unsigned)xtfs64
->__ss64
.__cs
,
395 xtfs64
->__ss64
.__rip
);
399 * If this exception happened in compatibility mode,
400 * assume it was the intentional division-by-zero and set the
401 * new state's cs register to just after the div instruction
402 * to enable the thread to resume.
404 if ((unsigned)xtfs64
->__ss64
.__cs
== COMPAT_MODE_CS_SELECTOR
) {
405 *new_stateCnt
= old_stateCnt
;
406 *new_xtfs64
= *xtfs64
;
407 new_xtfs64
->__ss64
.__rip
+= rip_skip_count
;
408 fprintf(stderr
, "new cs=0x%x rip=0x%llx\n", (unsigned)new_xtfs64
->__ss64
.__cs
,
409 new_xtfs64
->__ss64
.__rip
);
412 return KERN_NOT_SUPPORTED
;
417 handle_exceptions(void *arg
)
419 mach_port_t ePort
= (mach_port_t
)arg
;
422 kret
= mach_msg_server(mach_exc_server
, MACH_MSG_SIZE_RELIABLE
, ePort
, 0);
423 if (kret
!= KERN_SUCCESS
) {
424 fprintf(stderr
, "mach_msg_server: %s (%d)", mach_error_string(kret
), kret
);
431 init_task_exception_server(void)
434 task_t me
= mach_task_self();
435 pthread_t handler_thread
;
439 kr
= mach_port_allocate(me
, MACH_PORT_RIGHT_RECEIVE
, &ePort
);
440 if (kr
!= KERN_SUCCESS
) {
441 fprintf(stderr
, "allocate receive right: %d\n", kr
);
445 kr
= mach_port_insert_right(me
, ePort
, ePort
, MACH_MSG_TYPE_MAKE_SEND
);
446 if (kr
!= KERN_SUCCESS
) {
447 fprintf(stderr
, "insert right into port=[%d]: %d\n", ePort
, kr
);
451 kr
= task_set_exception_ports(me
, EXC_MASK_BAD_INSTRUCTION
| EXC_MASK_ARITHMETIC
, ePort
,
452 (exception_behavior_t
)(EXCEPTION_STATE_IDENTITY
| MACH_EXCEPTION_CODES
), x86_THREAD_FULL_STATE64
);
453 if (kr
!= KERN_SUCCESS
) {
454 fprintf(stderr
, "abort: error setting task exception ports on task=[%d], handler=[%d]: %d\n", me
, ePort
, kr
);
458 pthread_attr_init(&attr
);
459 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
461 if (pthread_create(&handler_thread
, &attr
, handle_exceptions
, (void *)(uintptr_t)ePort
) != 0) {
462 perror("pthread create error");
466 pthread_attr_destroy(&attr
);
469 static union ldt_entry
*descs
= 0;
471 static int saw_ud2
= 0;
472 static boolean_t ENV_set_ldt_in_sighandler
= FALSE
;
475 signal_handler(int signo
, siginfo_t
*sinfop
, void *ucontext
)
477 uint64_t rip_skip_count
= 0;
478 ucontext_t
*uctxp
= (ucontext_t
*)ucontext
;
480 _STRUCT_MCONTEXT_AVX512_64
*avx512_basep
;
481 _STRUCT_MCONTEXT_AVX512_64_FULL
*avx512_fullp
;
482 _STRUCT_MCONTEXT_AVX64
*avx64_basep
;
483 _STRUCT_MCONTEXT_AVX64_FULL
*avx64_fullp
;
484 _STRUCT_MCONTEXT64
*fp_basep
;
485 _STRUCT_MCONTEXT64_FULL
*fp_fullp
;
488 mctx
.fp_fullp
= (_STRUCT_MCONTEXT64_FULL
*)uctxp
->uc_mcontext
;
491 * Note that GSbase must be restored before calling into any frameworks
492 * that might access anything %gs-relative (e.g. TSD) if the signal
493 * handler was triggered while the thread was running with a non-default
494 * (system-established) GSbase.
497 if ((signo
!= SIGFPE
&& signo
!= SIGILL
) || sinfop
->si_signo
!= signo
) {
499 T_ASSERT_FAIL("Unexpected signal %d\n", signo
);
501 restore_gsbase(mctx
.fp_fullp
->__ss
.__ss64
.__rsp
);
502 fprintf(stderr
, "Not handling signal %d\n", signo
);
507 if (uctxp
->uc_mcsize
== sizeof(_STRUCT_MCONTEXT_AVX512_64
) ||
508 uctxp
->uc_mcsize
== sizeof(_STRUCT_MCONTEXT_AVX64
) ||
509 uctxp
->uc_mcsize
== sizeof(_STRUCT_MCONTEXT64
)) {
510 _STRUCT_X86_THREAD_STATE64
*ss64
= &mctx
.fp_basep
->__ss
;
513 * The following block is an illustration of what NOT to do.
514 * Configuring an LDT for the first time in a signal handler
515 * will likely cause the process to crash.
517 if (ENV_set_ldt_in_sighandler
== TRUE
&& !saw_ud2
) {
519 int cnt
= i386_set_ldt((int)idx
, &descs
[idx
], 1);
520 if (cnt
!= (int)idx
) {
522 fprintf(stderr
, "i386_set_ldt unexpectedly returned %d (errno = %s)\n", cnt
, strerror(errno
));
525 T_LOG("i386_set_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
526 T_ASSERT_FAIL("i386_set_ldt failure");
532 printf("i386_set_ldt returned %d\n", cnt
);
534 ss64
->__rip
+= 2; /* ud2 is 2 bytes */
539 * When we return here, the sigreturn processing code will try to copy a FULL
540 * thread context from the signal stack, which will likely cause the resumed
541 * thread to fault and be terminated.
546 restore_gsbase(ss64
->__rsp
);
549 * If we're in this block, either we are dispatching a signal received
550 * before we installed a custom LDT or we are on a kernel without
551 * BSD-signalling-sending-full-thread-state support. It's likely the latter case.
554 T_ASSERT_FAIL("This system doesn't support BSD signals with full thread state.");
556 fprintf(stderr
, "This system doesn't support BSD signals with full thread state. Aborting.\n");
559 } else if (uctxp
->uc_mcsize
== sizeof(_STRUCT_MCONTEXT_AVX512_64_FULL
) ||
560 uctxp
->uc_mcsize
== sizeof(_STRUCT_MCONTEXT_AVX64_FULL
) ||
561 uctxp
->uc_mcsize
== sizeof(_STRUCT_MCONTEXT64_FULL
)) {
562 _STRUCT_X86_THREAD_FULL_STATE64
*ss64
= &mctx
.fp_fullp
->__ss
;
565 * Since we're handing this signal on the same thread, we may need to
568 uint64_t orig_gsbase
= stack_range_to_GSbase(ss64
->__ss64
.__rsp
, 0);
569 if (orig_gsbase
!= 0 && orig_gsbase
!= ss64
->__gsbase
) {
570 restore_gsbase(ss64
->__ss64
.__rsp
);
573 if (signo
== SIGFPE
) {
574 handle_arithmetic_exception(ss64
, &rip_skip_count
);
575 } else if (signo
== SIGILL
) {
576 handle_badinsn_exception(ss64
, &rip_skip_count
);
580 * If this exception happened in compatibility mode,
581 * assume it was the intentional division-by-zero and set the
582 * new state's cs register to just after the div instruction
583 * to enable the thread to resume.
585 if ((unsigned)ss64
->__ss64
.__cs
== COMPAT_MODE_CS_SELECTOR
) {
586 ss64
->__ss64
.__rip
+= rip_skip_count
;
587 fprintf(stderr
, "new cs=0x%x rip=0x%llx\n", (unsigned)ss64
->__ss64
.__cs
,
591 _STRUCT_X86_THREAD_STATE64
*ss64
= &mctx
.fp_basep
->__ss
;
593 restore_gsbase(ss64
->__rsp
);
595 T_ASSERT_FAIL("Unknown mcontext size %lu: Aborting.", uctxp
->uc_mcsize
);
597 fprintf(stderr
, "Unknown mcontext size %lu: Aborting.\n", uctxp
->uc_mcsize
);
604 setup_signal_handling(void)
608 struct sigaction sa
= {
609 .__sigaction_u
= { .__sa_sigaction
= signal_handler
},
610 .sa_flags
= SA_SIGINFO
613 sigfillset(&sa
.sa_mask
);
615 rv
= sigaction(SIGFPE
, &sa
, NULL
);
618 T_ASSERT_FAIL("Failed to configure SIGFPE signal handler\n");
620 fprintf(stderr
, "Failed to configure SIGFPE signal handler\n");
625 rv
= sigaction(SIGILL
, &sa
, NULL
);
628 T_ASSERT_FAIL("Failed to configure SIGILL signal handler\n");
630 fprintf(stderr
, "Failed to configure SIGILL signal handler\n");
637 teardown_signal_handling(void)
639 if (signal(SIGFPE
, SIG_DFL
) == SIG_ERR
) {
641 T_ASSERT_FAIL("Error resetting SIGFPE signal disposition\n");
643 fprintf(stderr
, "Error resetting SIGFPE signal disposition\n");
648 if (signal(SIGILL
, SIG_DFL
) == SIG_ERR
) {
650 T_ASSERT_FAIL("Error resetting SIGILL signal disposition\n");
652 fprintf(stderr
, "Error resetting SIGILL signal disposition\n");
660 dump_desc(union ldt_entry
*entp
)
662 printf("base %p lim %p type 0x%x dpl %x present %x opsz %x granular %x\n",
663 (void *)(uintptr_t)(entp
->code
.base00
+ (entp
->code
.base16
<< 16) + (entp
->code
.base24
<< 24)),
664 (void *)(uintptr_t)(entp
->code
.limit00
+ (entp
->code
.limit16
<< 16)),
669 entp
->code
.granular
);
674 map_lowmem_stack(void **lowmemstk
)
679 if ((addr
= mmap(0, FIXED_STACK_SIZE
+ PAGE_SIZE
, PROT_READ
| PROT_WRITE
,
680 MAP_32BIT
| MAP_PRIVATE
| MAP_ANON
, -1, 0)) == MAP_FAILED
) {
684 if ((uintptr_t)addr
> 0xFFFFF000ULL
) {
685 /* Error: This kernel does not support MAP_32BIT or there's a bug. */
687 T_ASSERT_FAIL("%s: failed to map a 32-bit-accessible stack", __func__
);
689 fprintf(stderr
, "This kernel returned a virtual address > 4G (%p) despite MAP_32BIT. Aborting.\n", addr
);
694 /* Enforce one page of redzone at the bottom of the stack */
695 if (mprotect(addr
, PAGE_SIZE
, PROT_NONE
) < 0) {
697 (void) munmap(addr
, FIXED_STACK_SIZE
+ PAGE_SIZE
);
702 stack2gs
[0].stack_base
= (uintptr_t)addr
+ PAGE_SIZE
;
703 stack2gs
[0].stack_limit
= stack2gs
[0].stack_base
+ FIXED_STACK_SIZE
;
704 *lowmemstk
= (void *)((uintptr_t)addr
+ PAGE_SIZE
);
711 map_32bit_code_impl(uint8_t *code_src
, size_t code_len
, void **codeptr
,
715 size_t sz
= (size_t)P2ROUNDUP(code_len
, (unsigned)PAGE_SIZE
);
717 if (code_len
> szlimit
) {
722 printf("size = %lu, szlimit = %u\n", sz
, (unsigned)szlimit
);
725 if ((addr
= mmap(0, sz
, PROT_READ
| PROT_WRITE
| PROT_EXEC
,
726 MAP_32BIT
| MAP_PRIVATE
| MAP_ANON
, -1, 0)) == MAP_FAILED
) {
730 if ((uintptr_t)addr
> 0xFFFFF000ULL
) {
731 /* Error: This kernel does not support MAP_32BIT or there's a bug. */
733 T_ASSERT_FAIL("%s: failed to map a 32-bit-accessible trampoline", __func__
);
735 fprintf(stderr
, "This kernel returned a virtual address > 4G (%p) despite MAP_32BIT. Aborting.\n", addr
);
741 printf("Mapping code @%p..%p => %p..%p\n", (void *)code_src
,
742 (void *)((uintptr_t)code_src
+ (unsigned)code_len
),
743 addr
, (void *)((uintptr_t)addr
+ (unsigned)code_len
));
746 bcopy(code_src
, addr
, code_len
);
748 /* Fill the rest of the page with NOPs */
749 if ((sz
- code_len
) > 0) {
750 memset((void *)((uintptr_t)addr
+ code_len
), 0x90, sz
- code_len
);
761 map_32bit_trampoline(compat_tramp_t
*lowmemtrampp
)
763 extern int compat_mode_trampoline_len
;
765 return map_32bit_code_impl((uint8_t *)&compat_mode_trampoline
,
766 (size_t)compat_mode_trampoline_len
, (void **)lowmemtrampp
,
771 stack_range_to_GSbase(uint64_t stackptr
, uint64_t GSbase
)
775 for (i
= 0; i
< sizeof(stack2gs
) / sizeof(stack2gs
[0]); i
++) {
776 if (stackptr
>= stack2gs
[i
].stack_base
&&
777 stackptr
< stack2gs
[i
].stack_limit
) {
780 fprintf(stderr
, "Updated gsbase for stack at 0x%llx..0x%llx to 0x%llx\n",
781 stack2gs
[i
].stack_base
, stack2gs
[i
].stack_limit
, GSbase
);
783 stack2gs
[i
].GSbase
= GSbase
;
785 return stack2gs
[i
].GSbase
;
792 call_compatmode(uint32_t stackaddr
, uint64_t compat_arg
, uint64_t callback
)
797 * Depending on how this is used, this allocation may need to be
798 * made with an allocator that returns virtual addresses below 4G.
800 custom_tsd_t
*new_GSbase
= malloc(PAGE_SIZE
);
803 * Change the GSbase (so things like printf will fail unless GSbase is
806 if (new_GSbase
!= NULL
) {
808 fprintf(stderr
, "Setting new GS base: %p\n", (void *)new_GSbase
);
810 new_GSbase
->this_tsd_base
= new_GSbase
;
811 new_GSbase
->orig_tsd_base
= get_gsbase();
812 _thread_set_tsd_base((uintptr_t)new_GSbase
);
815 T_ASSERT_FAIL("Failed to allocate a page for new GSbase");
817 fprintf(stderr
, "Failed to allocate a page for new GSbase");
822 rv
= thunkit(&input_desc
, (void *)(uintptr_t)stackaddr
, compat_arg
,
823 callback
, thunk64_addr
);
825 restore_gsbase(stackaddr
);
836 __asm__
__volatile__ ("movq %%rsp, %0" : "=r" (curstk
) :: "memory");
841 hello_from_32bit(void)
843 uint64_t cur_tsd_base
= (uint64_t)(uintptr_t)mytsd
->this_tsd_base
;
844 restore_gsbase(get_cursp());
846 printf("Hello on behalf of 32-bit compatibility mode!\n");
848 _thread_set_tsd_base(cur_tsd_base
);
852 * Thread for executing 32-bit code
855 thread_32bit(void *arg
)
857 thread_arg_t
*targp
= (thread_arg_t
*)arg
;
858 uint64_t cthread_self
= 0;
860 /* Save the GSbase for context switch back to 64-bit mode */
861 cthread_self
= get_gsbase();
864 * Associate GSbase with the compat-mode stack (which will be used for long mode
865 * thunk calls as well.)
867 (void)stack_range_to_GSbase(targp
->compat_stackaddr
, cthread_self
);
870 printf("[thread %p] tsd base => %p\n", (void *)pthread_self(), (void *)cthread_self
);
873 pthread_mutex_lock(&targp
->mutex
);
876 if (targp
->done
== FALSE
) {
877 pthread_cond_wait(&targp
->condvar
, &targp
->mutex
);
880 /* Finally, execute the test */
881 if (call_compatmode(targp
->compat_stackaddr
, 0,
882 (uint64_t)&hello_from_32bit
) == 1) {
883 printf("32-bit code test passed\n");
885 printf("32-bit code test failed\n");
887 } while (targp
->done
== FALSE
);
889 pthread_mutex_unlock(&targp
->mutex
);
895 join_32bit_thread(pthread_t
*thridp
, thread_arg_t
*cmargp
)
897 (void)pthread_mutex_lock(&cmargp
->mutex
);
899 (void)pthread_cond_signal(&cmargp
->condvar
);
900 (void)pthread_mutex_unlock(&cmargp
->mutex
);
901 (void)pthread_join(*thridp
, NULL
);
906 create_worker_thread(thread_arg_t
*cmargp
, uint32_t stackaddr
, pthread_t
*cmthreadp
)
908 *cmargp
= (thread_arg_t
) { .mutex
= PTHREAD_MUTEX_INITIALIZER
,
909 .condvar
= PTHREAD_COND_INITIALIZER
,
911 .compat_stackaddr
= stackaddr
};
913 return pthread_create(cmthreadp
, NULL
, thread_32bit
, cmargp
);
917 ldt64_test_setup(pthread_t
*cmthreadp
, thread_arg_t
*cmargp
, boolean_t setldt_in_sighandler
)
919 extern void thunk64(void);
920 extern void thunk64_movabs(void);
924 uintptr_t thunk64_movabs_addr
;
926 descs
= malloc(sizeof(union ldt_entry
) * 256);
929 T_ASSERT_FAIL("Could not allocate descriptor storage");
931 fprintf(stderr
, "Could not allocate descriptor storage\n");
937 printf("32-bit code is at %p\n", (void *)&code_32
);
940 if ((err
= map_lowmem_stack(&addr
)) != 0) {
942 T_ASSERT_FAIL("failed to mmap lowmem stack: %s", strerror(err
));
944 fprintf(stderr
, "Failed to mmap lowmem stack: %s\n", strerror(err
));
949 stackAddr
= (uintptr_t)addr
+ FIXED_STACK_SIZE
- 16;
951 printf("lowstack addr = %p\n", (void *)stackAddr
);
954 if ((err
= map_32bit_trampoline(&thunkit
)) != 0) {
956 T_LOG("Failed to map trampoline into lowmem: %s\n", strerror(err
));
957 T_ASSERT_FAIL("Failed to map trampoline into lowmem");
959 fprintf(stderr
, "Failed to map trampoline into lowmem: %s\n", strerror(err
));
965 * Store long_mode_trampoline's address into the constant part of the movabs
966 * instruction in thunk64
968 thunk64_movabs_addr
= (uintptr_t)thunkit
+ ((uintptr_t)thunk64_movabs
- (uintptr_t)compat_mode_trampoline
);
969 *((uint64_t *)(thunk64_movabs_addr
+ 2)) = (uint64_t)&long_mode_trampoline
;
971 bzero(descs
, sizeof(union ldt_entry
) * 256);
973 if ((cnt
= i386_get_ldt(0, descs
, 1)) <= 0) {
975 T_LOG("i386_get_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
976 T_ASSERT_FAIL("i386_get_ldt failure");
978 fprintf(stderr
, "i386_get_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
984 printf("i386_get_ldt returned %d\n", cnt
);
987 idx
= (unsigned)cnt
; /* Put the desired descriptor in the first available slot */
990 * code_32's address for the purposes of this descriptor is the base mapped address of
991 * the thunkit function + the offset of code_32 from compat_mode_trampoline.
993 code_addr
= (uintptr_t)thunkit
+ ((uintptr_t)code_32
- (uintptr_t)compat_mode_trampoline
);
994 thunk64_addr
= (uintptr_t)thunkit
+ ((uintptr_t)thunk64
- (uintptr_t)compat_mode_trampoline
);
996 /* Initialize desired descriptor */
997 descs
[idx
].code
.limit00
= (unsigned short)(((code_addr
>> 12) + 1) & 0xFFFF);
998 descs
[idx
].code
.limit16
= (unsigned char)((((code_addr
>> 12) + 1) >> 16) & 0xF);
999 descs
[idx
].code
.base00
= (unsigned short)((code_addr
) & 0xFFFF);
1000 descs
[idx
].code
.base16
= (unsigned char)((code_addr
>> 16) & 0xFF);
1001 descs
[idx
].code
.base24
= (unsigned char)((code_addr
>> 24) & 0xFF);
1002 descs
[idx
].code
.type
= DESC_CODE_READ
;
1003 descs
[idx
].code
.opsz
= DESC_CODE_32B
;
1004 descs
[idx
].code
.granular
= DESC_GRAN_PAGE
;
1005 descs
[idx
].code
.dpl
= 3;
1006 descs
[idx
].code
.present
= 1;
1008 if (setldt_in_sighandler
== FALSE
) {
1010 cnt
= i386_set_ldt((int)idx
, &descs
[idx
], 1);
1011 if (cnt
!= (int)idx
) {
1013 T_LOG("i386_set_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
1014 T_ASSERT_FAIL("i386_set_ldt failure");
1016 fprintf(stderr
, "i386_set_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
1021 printf("i386_set_ldt returned %d\n", cnt
);
1024 __asm__
__volatile__ ("ud2" ::: "memory");
1028 /* Read back the LDT to ensure it was set properly */
1029 if ((cnt
= i386_get_ldt(0, descs
, (int)idx
)) > 0) {
1031 for (int i
= 0; i
< cnt
; i
++) {
1032 dump_desc(&descs
[i
]);
1037 T_LOG("i386_get_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
1038 T_ASSERT_FAIL("i386_get_ldt failure");
1040 fprintf(stderr
, "i386_get_ldt unexpectedly returned %d (errno: %s)\n", cnt
, strerror(errno
));
1047 if ((err
= create_worker_thread(cmargp
, (uint32_t)stackAddr
, cmthreadp
)) != 0) {
1049 fprintf(stderr
, "Fatal: Could not create thread: %s\n", strerror(err
));
1052 T_LOG("Fatal: Could not create thread: %s\n", strerror(err
));
1053 T_ASSERT_FAIL("Thread creation failure");
1062 test_ldt64_with_bsdsig(void)
1065 * Main test declarations
1067 T_DECL(ldt64_with_bsd_sighandling
,
1068 "Ensures that a 64-bit process can create LDT entries and can execute code in "
1069 "compatibility mode with BSD signal handling",
1070 T_META_TIMEOUT(NORMAL_RUN_TIME
+ TIMEOUT_OVERHEAD
))
1077 size_t translated_size
= sizeof(int);
1079 sysctlbyname("sysctl.proc_translated", &translated
, &translated_size
, NULL
, 0);
1082 T_SKIP("Skipping this test because it is translated");
1085 setup_signal_handling();
1090 ENV_set_ldt_in_sighandler
= (getenv("LDT_SET_IN_SIGHANDLER") != NULL
) ? TRUE
: FALSE
;
1091 ldt64_test_setup(&cmthread
, &cmarg
, ENV_set_ldt_in_sighandler
);
1096 join_32bit_thread(&cmthread
, &cmarg
);
1098 teardown_signal_handling();
1101 T_PASS("Successfully completed ldt64 test with BSD signal handling");
1103 fprintf(stderr
, "PASSED: ldt64_with_bsd_signal_handling\n");
1109 test_ldt64_with_machexc(void)
1111 T_DECL(ldt64_with_mach_exception_handling
,
1112 "Ensures that a 64-bit process can create LDT entries and can execute code in "
1113 "compatibility mode with Mach exception handling",
1114 T_META_TIMEOUT(NORMAL_RUN_TIME
+ TIMEOUT_OVERHEAD
))
1121 size_t translated_size
= sizeof(int);
1123 sysctlbyname("sysctl.proc_translated", &translated
, &translated_size
, NULL
, 0);
1126 T_SKIP("Skipping this test because it is translated");
1132 ldt64_test_setup(&cmthread
, &cmarg
, FALSE
);
1137 /* Now repeat with Mach exception handling */
1138 init_task_exception_server();
1140 join_32bit_thread(&cmthread
, &cmarg
);
1143 T_PASS("Successfully completed ldt64 test with mach exception handling");
1145 fprintf(stderr
, "PASSED: ldt64_with_mach_exception_handling\n");
1151 main(int __unused argc
, char ** __unused argv
)
1153 test_ldt64_with_bsdsig();
1154 test_ldt64_with_machexc();