root/tools/testing/selftests/powerpc/security/spectre_v2.c
// 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");
}