-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[LibOS] Emulate in/out instructions as if they generated SIGSEGV
Executing I/O instructions (e.g., in/out) inside an SGX enclave generates a #UD fault. Gramine's PAL tries to handle this exception and propagates it to LibOS/app as a SIGILL signal. However, I/O instructions result in a #GP fault outside SGX (which raises a SIGSEGV signal) if I/O is not permitted. Let Gramine emulate these instructions as if they ended up in SIGSEGV. This helps some apps, e.g. `lscpu`. New LibOS test is added. Co-authored-by: Nirjhar Roy <nirjhar.roy@fortanix.com> Signed-off-by: Nirjhar Roy <nirjhar.roy@fortanix.com> Signed-off-by: Dmitrii Kuvaiskii <dmitrii.kuvaiskii@intel.com>
- Loading branch information
1 parent
6d77bcf
commit ac61ae1
Showing
9 changed files
with
269 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* SPDX-License-Identifier: LGPL-3.0-or-later */ | ||
/* Copyright (C) 2024 Fortanix Inc | ||
* Nirjhar Roy <nirjhar.roy@fortanix.com> | ||
*/ | ||
|
||
/* This file contains functions that check various features and flags specific to x86 */ | ||
|
||
#include <stddef.h> | ||
|
||
#include "api.h" | ||
#include "cpu.h" | ||
|
||
#define INSTR_SIZE_MAX 15 | ||
|
||
bool is_x86_instr_legacy_prefix(uint8_t op) { | ||
/* | ||
* Official source for this list is Intel SDM, Vol. 2, Chapter 2.1.1 "Instruction Prefixes". | ||
* These prefixes are called "legacy" for x86-64 (64-bit mode) instructions, see Intel SDM, | ||
* Vol. 2, Chapter 2.2.1 and Figure 2-3 "Prefix Ordering in 64-bit Mode". | ||
*/ | ||
switch (op) { | ||
/* Group 1 */ | ||
case 0xf0: /* LOCK prefix */ | ||
case 0xf2: /* REPNE/REPNZ prefix */ | ||
case 0xf3: /* REP or REPE/REPZ prefix */ | ||
/* Group 2 */ | ||
case 0x2e: /* CS segment override; Branch not taken */ | ||
case 0x36: /* SS segment override */ | ||
case 0x3e: /* DS segment override; Branch taken */ | ||
case 0x26: /* ES segment override */ | ||
case 0x64: /* FS segment override */ | ||
case 0x65: /* GS segment override */ | ||
/* Group 3 */ | ||
case 0x66: /* Operand-size override prefix */ | ||
/* Group 4 */ | ||
case 0x67: /* Address-size override prefix */ | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
bool is_x86_instr_rex_prefix(uint8_t op) { | ||
/* | ||
* Optional REX prefix is located after all legacy prefixes (see above) and right before the | ||
* opcode. REX prefix is 1 byte with bits [0100WRXB], from which follows that REX prefix can be | ||
* any of 0x40-0x4f. For details, see Intel SDM, Vol. 2, Chapter 2.2.1 "REX Prefixes". | ||
*/ | ||
return 0x40 <= op && op <= 0x4f; | ||
} | ||
|
||
bool has_lock_prefix(uint8_t* rip) { | ||
size_t idx = 0; | ||
while (is_x86_instr_legacy_prefix(rip[idx]) && idx < INSTR_SIZE_MAX) { | ||
if (rip[idx] == 0xf0) | ||
return true; | ||
idx++; | ||
} | ||
return false; | ||
} | ||
|
||
bool is_in_out(uint8_t* rip) { | ||
/* | ||
* x86-64 instructions may be at most 15 bytes in length and may have multiple instruction | ||
* prefixes. See description in Intel SDM, Vol. 2, Chapter 2.1.1 "Instruction Prefixes". | ||
*/ | ||
size_t idx = 0; | ||
while (is_x86_instr_legacy_prefix(rip[idx]) && idx < INSTR_SIZE_MAX) | ||
idx++; | ||
|
||
if (idx == INSTR_SIZE_MAX) | ||
return false; | ||
|
||
/* skip over the optional REX prefix */ | ||
if (is_x86_instr_rex_prefix(rip[idx])) | ||
idx++; | ||
|
||
if (idx == INSTR_SIZE_MAX) | ||
return false; | ||
|
||
switch (rip[idx]) { | ||
/* INS opcodes */ | ||
case 0x6c: | ||
case 0x6d: | ||
/* OUTS opcodes */ | ||
case 0x6e: | ||
case 0x6f: | ||
/* IN immediate opcodes */ | ||
case 0xe4: | ||
case 0xe5: | ||
/* OUT immediate opcodes */ | ||
case 0xe6: | ||
case 0xe7: | ||
/* IN register opcodes */ | ||
case 0xec: | ||
case 0xed: | ||
/* OUT register opcodes */ | ||
case 0xee: | ||
case 0xef: | ||
return true; | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* SPDX-License-Identifier: LGPL-3.0-or-later */ | ||
/* Copyright (C) 2024 Fortanix Inc | ||
* Nirjhar Roy <nirjhar.roy@fortanix.com> | ||
*/ | ||
|
||
/* | ||
* Verify that IN/OUT/INS/OUTS instructions generate SIGSEGV (and not SIGILL). | ||
* | ||
* This test is important for SGX PAL: IN/OUT/INS/OUTS instructions result in a #UD fault when | ||
* executed in SGX enclaves, but result in a #GP fault when executed by normal userspace code. | ||
* Gramine is supposed to transform the #UD fault into a #GP fault, which ends up as a SIGSEGV in | ||
* the application. | ||
*/ | ||
|
||
#define _GNU_SOURCE | ||
#include <err.h> | ||
#include <errno.h> | ||
#include <signal.h> | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <ucontext.h> | ||
|
||
#include "common.h" | ||
|
||
#ifndef __x86_64__ | ||
#error Unsupported architecture | ||
#endif | ||
|
||
#define EXPECTED_NUM_SIGSEGVS 2 | ||
|
||
static int g_sigsegv_triggered = 0; | ||
|
||
uint8_t inb_func(uint16_t port) __attribute__((visibility("internal"))); | ||
void outb_func(uint8_t value, uint16_t port) __attribute__((visibility("internal"))); | ||
void inb_instruction_addr(void) __attribute__((visibility("internal"))); | ||
void outb_instruction_addr(void) __attribute__((visibility("internal"))); | ||
void ret(void) __attribute__((visibility("internal"))); | ||
|
||
__asm__ ( | ||
".pushsection .text\n" | ||
".type inb_func, @function\n" | ||
".type outb_func, @function\n" | ||
".type inb_instruction_addr, @function\n" | ||
".type outb_instruction_addr, @function\n" | ||
".type ret, @function\n" | ||
"inb_func:\n" | ||
"mov %rdi, %rdx\n" | ||
"inb_instruction_addr:\n" | ||
"inb %dx, %al\n" | ||
"ret\n" | ||
"outb_func:\n" | ||
"mov %rsi, %rdx\n" | ||
"mov %rdi, %rax\n" | ||
"outb_instruction_addr:\n" | ||
"outb %al, %dx\n" | ||
"ret:\n" | ||
"ret\n" | ||
".popsection\n" | ||
); | ||
|
||
static void handler(int signum, siginfo_t* si, void* uc) { | ||
if (signum != SIGSEGV) { | ||
/* we registered a SIGSEGV handler but got another signal?! */ | ||
_Exit(1); | ||
} | ||
|
||
uint64_t rip = ((ucontext_t*)uc)->uc_mcontext.gregs[REG_RIP]; | ||
if (g_sigsegv_triggered == 0) { | ||
/* must be a fault on inb instruction */ | ||
if (rip != (uint64_t)(inb_instruction_addr)) | ||
_Exit(1); | ||
} else if (g_sigsegv_triggered == 1) { | ||
/* must be a fault on outb instruction */ | ||
if (rip != (uint64_t)(outb_instruction_addr)) | ||
_Exit(1); | ||
} else { | ||
/* too many segfaults?! */ | ||
_Exit(1); | ||
} | ||
|
||
g_sigsegv_triggered++; | ||
|
||
/* no need to fixup the context (other than RIP) as we only modified caller-saved RDX and RAX in | ||
* inb_func() and outb_func() */ | ||
((ucontext_t*)uc)->uc_mcontext.gregs[REG_RIP] = (uint64_t)ret; | ||
} | ||
|
||
int main(void) { | ||
struct sigaction sa = { | ||
.sa_sigaction = handler, | ||
.sa_flags = SA_RESTART | SA_SIGINFO, | ||
}; | ||
CHECK(sigaction(SIGSEGV, &sa, NULL)); | ||
|
||
uint8_t value = 0; | ||
uint16_t port = 0x3F8; | ||
|
||
inb_func(port); | ||
outb_func(value, port); | ||
|
||
if (g_sigsegv_triggered != EXPECTED_NUM_SIGSEGVS) | ||
errx(1, "Expected %d SIGSEGVs, got %d", EXPECTED_NUM_SIGSEGVS, g_sigsegv_triggered); | ||
|
||
puts("TEST OK"); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters