Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PAL/Linux-SGX] Add "parse frequency from /proc/cpuinfo" fallback #1180

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/include/arch/x86_64/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ enum extended_state_sub_leaf {
#define PROC_FREQ_LEAF 0x16
#define AMX_TILE_INFO_LEAF 0x1D
#define AMX_TMUL_INFO_LEAF 0x1E
#define HYPERVISOR_INFO_LEAF 0x40000000
#define HYPERVISOR_TIME_LEAF 0x40000010
#define MAX_INPUT_EXT_VALUE_LEAF 0x80000000
#define EXT_SIGNATURE_AND_FEATURES_LEAF 0x80000001
#define CPU_BRAND_LEAF 0x80000002
Expand Down
2 changes: 0 additions & 2 deletions pal/src/host/linux-sgx/pal_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ void _PalExceptionHandler(unsigned int exit_info, sgx_cpu_context_t* uc,
* its underlying type. */
void _PalHandleExternalEvent(long event_, sgx_cpu_context_t* uc, PAL_XREGS_STATE* xregs_state);

bool is_tsc_usable(void);
uint64_t get_tsc_hz(void);
void init_tsc(void);

int init_cpuid(void);
Expand Down
224 changes: 179 additions & 45 deletions pal/src/host/linux-sgx/pal_misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,180 @@ static uint64_t g_start_tsc = 0;
static uint64_t g_start_usec = 0;
static seqlock_t g_tsc_lock = INIT_SEQLOCK_UNLOCKED;

/**
* Initialize the data structures used for date/time emulation using TSC
*/
void init_tsc(void) {
if (is_tsc_usable()) {
g_tsc_hz = get_tsc_hz();
static bool is_tsc_usable(void) {
uint32_t words[CPUID_WORD_NUM];
_PalCpuIdRetrieve(INVARIANT_TSC_LEAF, 0, words);
return words[CPUID_WORD_EDX] & 1 << 8;
}

/* return TSC frequency or 0 if invariant TSC is not supported */
static uint64_t get_tsc_hz_baremetal(void) {
uint32_t words[CPUID_WORD_NUM];

_PalCpuIdRetrieve(TSC_FREQ_LEAF, 0, words);
if (!words[CPUID_WORD_EAX] || !words[CPUID_WORD_EBX]) {
/* TSC/core crystal clock ratio is not enumerated, can't use RDTSC for accurate time */
return 0;
}

if (words[CPUID_WORD_ECX] > 0) {
/* calculate TSC frequency as core crystal clock frequency (EAX) * EBX / EAX; cast to 64-bit
* first to prevent integer overflow */
uint64_t ecx_hz = words[CPUID_WORD_ECX];
return ecx_hz * words[CPUID_WORD_EBX] / words[CPUID_WORD_EAX];
}

/* some Intel CPUs do not report nominal frequency of crystal clock, let's calculate it
* based on Processor Frequency Information Leaf (CPUID 16H); this leaf always exists if
* TSC Frequency Leaf exists; logic is taken from Linux 5.11's arch/x86/kernel/tsc.c */
_PalCpuIdRetrieve(PROC_FREQ_LEAF, 0, words);
if (!words[CPUID_WORD_EAX]) {
/* processor base frequency (in MHz) is not enumerated, can't calculate frequency */
return 0;
}

/* processor base frequency is in MHz but we need to return TSC frequency in Hz; cast to 64-bit
* first to prevent integer overflow */
uint64_t base_frequency_mhz = words[CPUID_WORD_EAX];
return base_frequency_mhz * 1000000;
}

/* return TSC frequency or 0 if invariant TSC is not supported */
static uint64_t get_tsc_hz_hypervisor(void) {
uint32_t words[CPUID_WORD_NUM];

/*
* We rely on the Generic CPUID space for hypervisors:
* - 0x40000000: EAX: The maximum input value for CPUID supported by the hypervisor
* - EBX, ECX, EDX: Hypervisor vendor ID signature (`hypervisor_id`)
*
* If we detect QEMU/KVM or Cloud Hypervisor/KVM (hypervisor_id = "KVMKVMKVM") or VMWare
* ("VMwareVMware"), then we assume that leaf 0x40000010 contains virtual TSC frequency in kHz
* in EAX. We check hypervisor_id because leaf 0x40000010 is not standardized and e.g. Microsoft
* Hyper-V may use it for other purposes.
*
* Relevant materials:
* - https://github.com/qemu/qemu/commit/9954a1582e18b03ddb66f6c892dccf2c3508f4b2
* - qemu/target/i386/cpu.h, qemu/target/i386/cpu.c, qemu/target/i386/kvm/kvm.c sources
* - https://github.com/freebsd/freebsd-src/blob/9df6eea/sys/x86/x86/identcpu.c#L1372-L1377 (for
* the list of hypervisor_id values)
*/
_PalCpuIdRetrieve(HYPERVISOR_INFO_LEAF, 0, words);

uint32_t hypervisor_id[3];
hypervisor_id[0] = words[CPUID_WORD_EBX];
hypervisor_id[1] = words[CPUID_WORD_ECX];
hypervisor_id[2] = words[CPUID_WORD_EDX];
if (memcmp(hypervisor_id, "KVMKVMKVM\0\0\0", 12) && memcmp(hypervisor_id, "VMWareVMWare", 12)) {
/* QEMU/KVM, Cloud Hypervisor/KVM and VMWare expose TSC frequency in leaf 0x40000010 */
return 0;
}

if (words[CPUID_WORD_EAX] < HYPERVISOR_TIME_LEAF) {
/* virtual TSC frequency is not available */
return 0;
}

_PalCpuIdRetrieve(HYPERVISOR_TIME_LEAF, 0, words);
if (!words[CPUID_WORD_EAX]) {
/* TSC frequency (in kHz) is not enumerated, can't calculate frequency */
return 0;
}

/* TSC frequency is in kHz but we need to return TSC frequency in Hz; cast to 64-bit first to
* prevent integer overflow */
uint64_t tsc_frequency_khz = words[CPUID_WORD_EAX];
return tsc_frequency_khz * 1000;
}

/* return TSC frequency or 0 if cannot parse CPU model name */
static uint64_t get_tsc_hz_cpu_model_name(void) {
uint32_t words[CPUID_WORD_NUM];

char model_name[48 + 1] = {0};
static_assert(sizeof(model_name) == sizeof(uint32_t) * CPUID_WORD_NUM * 3 + 1,
"wrong sizeof(model_name)");

_PalCpuIdRetrieve(CPU_BRAND_LEAF, 0, words);
memcpy(&model_name[ 0], words, sizeof(uint32_t) * CPUID_WORD_NUM);
_PalCpuIdRetrieve(CPU_BRAND_CNTD_LEAF, 0, words);
memcpy(&model_name[16], words, sizeof(uint32_t) * CPUID_WORD_NUM);
_PalCpuIdRetrieve(CPU_BRAND_CNTD2_LEAF, 0, words);
memcpy(&model_name[32], words, sizeof(uint32_t) * CPUID_WORD_NUM);
model_name[sizeof(model_name) - 1] = '\0';

const char* s = strchr(model_name, '@');
if (!s) {
/* model name string is malformed; should have been smth like "CPU @ 3.0GHz" */
return 0;
}
s++;

char* end = NULL;
long base = 0, fractional = 0;

base = strtol(s, &end, 10);
if (end == s) {
/* no frequency specified at all (no base digits found) after "@" sign */
return 0;
}
if (base <= 0 || base >= 1000) {
/* unsupported format of smth like "-3GHz" or "1000GHz" */
return 0;
}
s = end;

if (*s == '.') {
s++;
fractional = strtol(s, &end, 10);
if (fractional <= 0 || end - s > 3) {
/* don't support non-positive fractional or more than 3 digits after dot */
return 0;
}
for (int i = 0; i < 3 - (end - s); i++)
fractional *= 10;
s = end;
}

/* base and fractional are less than 1000, so no danger of int overflow */
assert(base < 1000 && fractional < 1000);
if (*s == 'G') {
base *= 1000 * 1000 * 1000;
fractional *= 1000 * 1000;
} else if (*s == 'M') {
base *= 1000 * 1000;
fractional *= 1000;
} else {
/* don't support any other formats other than "3.0GHz" and "3.0MHz" */
return 0;
}
return base + fractional;
}

/* initialize the data structures used for date/time emulation using TSC */
void init_tsc(void) {
if (!is_tsc_usable())
return;

g_tsc_hz = get_tsc_hz_baremetal();
if (g_tsc_hz)
return;

/* hypervisors may not expose crystal-clock frequency CPUID leaves, so instead try
* hypervisor-special synthetic CPUID leaf 0x40000010 (Timing Information) */
g_tsc_hz = get_tsc_hz_hypervisor();
if (g_tsc_hz)
return;

/* final fallback -- parse "Processor Brand String" CPUID leaves (guaranteed to exist on CPUs
* with SGX), extract the CPU frequency from there and assume it reflects TSC frequency */
g_tsc_hz = get_tsc_hz_cpu_model_name();
if (g_tsc_hz)
return;

/* can't use log_warning because at this point we didn't parse the manifest yet */
log_error("Could not set up Invariant TSC (CPU is too old or you run on a VM that does not "
"expose corresponding CPUID leaves). This degrades performance.");
}

/* TODO: result comes from the untrusted host, introduce some schielding */
Expand Down Expand Up @@ -396,8 +563,13 @@ static const struct cpuid_leaf cpuid_known_leaves[] = {
{.leaf = 0x1F, .zero_subleaf = false, .cache = false}, /* Intel V2 Ext Topology Enumeration */
/* basic CPUID leaf functions end here */

/* hypervisor-specific CPUID leaf functions (0x40000000 - 0x400000FF) start here */
{.leaf = 0x40000000, .zero_subleaf = true, .cache = true}, /* CPUID Info */
{.leaf = 0x40000010, .zero_subleaf = true, .cache = true}, /* Timing Info */
/* NOTE: currently only the above two leaves are used, see also get_tsc_hz_hypervisor() */

/* invalid CPUID leaf functions (no existing or future CPU will return any meaningful
* information in these leaves) occupy 40000000 - 4FFFFFFFH -- they are treated the same as
* information in these leaves) occupy 0x40000100 - 0x4FFFFFFFH -- they are treated the same as
* unrecognized leaves, see code below */

/* extended CPUID leaf functions start here */
Expand Down Expand Up @@ -655,44 +827,6 @@ ssize_t read_file_buffer(const char* filename, char* buf, size_t buf_size) {
return n;
}

bool is_tsc_usable(void) {
uint32_t words[CPUID_WORD_NUM];
_PalCpuIdRetrieve(INVARIANT_TSC_LEAF, 0, words);
return words[CPUID_WORD_EDX] & 1 << 8;
}

/* return TSC frequency or 0 if invariant TSC is not supported */
uint64_t get_tsc_hz(void) {
uint32_t words[CPUID_WORD_NUM];

_PalCpuIdRetrieve(TSC_FREQ_LEAF, 0, words);
if (!words[CPUID_WORD_EAX] || !words[CPUID_WORD_EBX]) {
/* TSC/core crystal clock ratio is not enumerated, can't use RDTSC for accurate time */
return 0;
}

if (words[CPUID_WORD_ECX] > 0) {
/* calculate TSC frequency as core crystal clock frequency (EAX) * EBX / EAX; cast to 64-bit
* first to prevent integer overflow */
uint64_t ecx_hz = words[CPUID_WORD_ECX];
return ecx_hz * words[CPUID_WORD_EBX] / words[CPUID_WORD_EAX];
}

/* some Intel CPUs do not report nominal frequency of crystal clock, let's calculate it
* based on Processor Frequency Information Leaf (CPUID 16H); this leaf always exists if
* TSC Frequency Leaf exists; logic is taken from Linux 5.11's arch/x86/kernel/tsc.c */
_PalCpuIdRetrieve(PROC_FREQ_LEAF, 0, words);
if (!words[CPUID_WORD_EAX]) {
/* processor base frequency (in MHz) is not enumerated, can't calculate frequency */
return 0;
}

/* processor base frequency is in MHz but we need to return TSC frequency in Hz; cast to 64-bit
* first to prevent integer overflow */
uint64_t base_frequency_mhz = words[CPUID_WORD_EAX];
return base_frequency_mhz * 1000000;
}

int _PalRandomBitsRead(void* buffer, size_t size) {
uint32_t rand;
for (size_t i = 0; i < size; i += sizeof(rand)) {
Expand Down