// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2018-2019 IBM Corporation. */ #define __SANE_USERSPACE_TYPES__ #include <sys/types.h> #include <stdint.h> #include <malloc.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/prctl.h> #include "utils.h" #include "../pmu/event.h" extern void pattern_cache_loop(void); extern void indirect_branch_loop(void); static int do_count_loop(struct event *events, bool is_p9, s64 *miss_percent) { u64 pred, mpred; prctl(PR_TASK_PERF_EVENTS_ENABLE); if (is_p9) pattern_cache_loop(); else indirect_branch_loop(); prctl(PR_TASK_PERF_EVENTS_DISABLE); event_read(&events[0]); event_read(&events[1]); // We could scale all the events by running/enabled but we're lazy // As long as the PMU is uncontended they should all run FAIL_IF(events[0].result.running != events[0].result.enabled); FAIL_IF(events[1].result.running != events[1].result.enabled); pred = events[0].result.value; mpred = events[1].result.value; if (is_p9) { event_read(&events[2]); event_read(&events[3]); FAIL_IF(events[2].result.running != events[2].result.enabled); FAIL_IF(events[3].result.running != events[3].result.enabled); pred += events[2].result.value; mpred += events[3].result.value; } *miss_percent = 100 * mpred / pred; return 0; } static void setup_event(struct event *e, u64 config, char *name) { event_init_named(e, config, name); e->attr.disabled = 1; e->attr.exclude_kernel = 1; e->attr.exclude_hv = 1; e->attr.exclude_idle = 1; } enum spectre_v2_state { VULNERABLE = 0, UNKNOWN = 1, // Works with FAIL_IF() NOT_AFFECTED, BRANCH_SERIALISATION, COUNT_CACHE_DISABLED, COUNT_CACHE_FLUSH_SW, COUNT_CACHE_FLUSH_HW, BTB_FLUSH, }; static enum spectre_v2_state get_sysfs_state(void) { enum spectre_v2_state state = UNKNOWN; char buf[256]; int len; memset(buf, 0, sizeof(buf)); FAIL_IF(read_sysfs_file("devices/system/cpu/vulnerabilities/spectre_v2", buf, sizeof(buf))); // Make sure it's NULL terminated buf[sizeof(buf) - 1] = '\0'; // Trim the trailing newline len = strlen(buf); FAIL_IF(len < 1); buf[len - 1] = '\0'; printf("sysfs reports: '%s'\n", buf); // Order matters if (strstr(buf, "Vulnerable")) state = VULNERABLE; else if (strstr(buf, "Not affected")) state = NOT_AFFECTED; else if (strstr(buf, "Indirect branch serialisation (kernel only)")) state = BRANCH_SERIALISATION; else if (strstr(buf, "Indirect branch cache disabled")) state = COUNT_CACHE_DISABLED; else if (strstr(buf, "Software count cache flush (hardware accelerated)")) state = COUNT_CACHE_FLUSH_HW; else if (strstr(buf, "Software count cache flush")) state = COUNT_CACHE_FLUSH_SW; else if (strstr(buf, "Branch predictor state flush")) state = BTB_FLUSH; return state; } #define PM_BR_PRED_CCACHE 0x040a4 // P8 + P9 #define PM_BR_MPRED_CCACHE 0x040ac // P8 + P9 #define PM_BR_PRED_PCACHE 0x048a0 // P9 only #define PM_BR_MPRED_PCACHE 0x048b0 // P9 only int spectre_v2_test(void) { enum spectre_v2_state state; struct event events[4]; s64 miss_percent; bool is_p9; // The PMU events we use only work on Power8 or later SKIP_IF(!have_hwcap2(PPC_FEATURE2_ARCH_2_07)); state = get_sysfs_state(); if (state == UNKNOWN) { printf("Error: couldn't determine spectre_v2 mitigation state?\n"); return -1; } memset(events, 0, sizeof(events)); setup_event(&events[0], PM_BR_PRED_CCACHE, "PM_BR_PRED_CCACHE"); setup_event(&events[1], PM_BR_MPRED_CCACHE, "PM_BR_MPRED_CCACHE"); FAIL_IF(event_open(&events[0])); FAIL_IF(event_open_with_group(&events[1], events[0].fd) == -1); is_p9 = ((mfspr(SPRN_PVR) >> 16) & 0xFFFF) == 0x4e; if (is_p9) { // Count pattern cache too setup_event(&events[2], PM_BR_PRED_PCACHE, "PM_BR_PRED_PCACHE"); setup_event(&events[3], PM_BR_MPRED_PCACHE, "PM_BR_MPRED_PCACHE"); FAIL_IF(event_open_with_group(&events[2], events[0].fd) == -1); FAIL_IF(event_open_with_group(&events[3], events[0].fd) == -1); } FAIL_IF(do_count_loop(events, is_p9, &miss_percent)); event_report_justified(&events[0], 18, 10); event_report_justified(&events[1], 18, 10); event_close(&events[0]); event_close(&events[1]); if (is_p9) { event_report_justified(&events[2], 18, 10); event_report_justified(&events[3], 18, 10); event_close(&events[2]); event_close(&events[3]); } printf("Miss percent %lld %%\n", miss_percent); switch (state) { case VULNERABLE: case NOT_AFFECTED: case COUNT_CACHE_FLUSH_SW: case COUNT_CACHE_FLUSH_HW: // These should all not affect userspace branch prediction if (miss_percent > 15) { if (miss_percent > 95) { /* * Such a mismatch may be caused by a system being unaware * the count cache is disabled. This may be to enable * guest migration between hosts with different settings. * Return skip code to avoid detecting this as an error. * We are not vulnerable and reporting otherwise, so * missing such a mismatch is safe. */ printf("Branch misses > 95%% unexpected in this configuration.\n"); printf("Count cache likely disabled without Linux knowing.\n"); if (state == COUNT_CACHE_FLUSH_SW) printf("WARNING: Kernel performing unnecessary flushes.\n"); return 4; } printf("Branch misses > 15%% unexpected in this configuration!\n"); printf("Possible mismatch between reported & actual mitigation\n"); return 1; } break; case BRANCH_SERIALISATION: // This seems to affect userspace branch prediction a bit? if (miss_percent > 25) { printf("Branch misses > 25%% unexpected in this configuration!\n"); printf("Possible mismatch between reported & actual mitigation\n"); return 1; } break; case COUNT_CACHE_DISABLED: if (miss_percent < 95) { printf("Branch misses < 95%% unexpected in this configuration!\n"); printf("Possible mismatch between reported & actual mitigation\n"); return 1; } break; case UNKNOWN: case BTB_FLUSH: printf("Not sure!\n"); return 1; } printf("OK - Measured branch prediction rates match reported spectre v2 mitigation.\n"); return 0; } int main(int argc, char *argv[]) { return test_harness(spectre_v2_test, "spectre_v2"); }