Merge tag 'powerpc-cve-2020-4788' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux

Pull powerpc fixes from Michael Ellerman:
 "Fixes for CVE-2020-4788.

  From Daniel's cover letter:

  IBM Power9 processors can speculatively operate on data in the L1
  cache before it has been completely validated, via a way-prediction
  mechanism. It is not possible for an attacker to determine the
  contents of impermissible memory using this method, since these
  systems implement a combination of hardware and software security
  measures to prevent scenarios where protected data could be leaked.

  However these measures don't address the scenario where an attacker
  induces the operating system to speculatively execute instructions
  using data that the attacker controls. This can be used for example to
  speculatively bypass "kernel user access prevention" techniques, as
  discovered by Anthony Steinhauser of Google's Safeside Project. This
  is not an attack by itself, but there is a possibility it could be
  used in conjunction with side-channels or other weaknesses in the
  privileged code to construct an attack.

  This issue can be mitigated by flushing the L1 cache between privilege
  boundaries of concern.

  This patch series flushes the L1 cache on kernel entry (patch 2) and
  after the kernel performs any user accesses (patch 3). It also adds a
  self-test and performs some related cleanups"

* tag 'powerpc-cve-2020-4788' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux:
  powerpc/64s: rename pnv|pseries_setup_rfi_flush to _setup_security_mitigations
  selftests/powerpc: refactor entry and rfi_flush tests
  selftests/powerpc: entry flush test
  powerpc: Only include kup-radix.h for 64-bit Book3S
  powerpc/64s: flush L1D after user accesses
  powerpc/64s: flush L1D on kernel entry
  selftests/powerpc: rfi_flush: disable entry flush if present
This commit is contained in:
Linus Torvalds
2020-11-19 11:32:31 -08:00
23 changed files with 693 additions and 147 deletions

View File

@@ -42,6 +42,11 @@ int perf_event_enable(int fd);
int perf_event_disable(int fd);
int perf_event_reset(int fd);
struct perf_event_read {
__u64 nr;
__u64 l1d_misses;
};
#if !defined(__GLIBC_PREREQ) || !__GLIBC_PREREQ(2, 30)
#include <unistd.h>
#include <sys/syscall.h>

View File

@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
rfi_flush
entry_flush

View File

@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0+
TEST_GEN_PROGS := rfi_flush spectre_v2
TEST_GEN_PROGS := rfi_flush entry_flush spectre_v2
top_srcdir = ../../../../..
CFLAGS += -I../../../../../usr/include
@@ -11,3 +11,5 @@ $(TEST_GEN_PROGS): ../harness.c ../utils.c
$(OUTPUT)/spectre_v2: CFLAGS += -m64
$(OUTPUT)/spectre_v2: ../pmu/event.c branch_loops.S
$(OUTPUT)/rfi_flush: flush_utils.c
$(OUTPUT)/entry_flush: flush_utils.c

View File

@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 IBM Corporation.
*/
#define __SANE_USERSPACE_TYPES__
#include <sys/types.h>
#include <stdint.h>
#include <malloc.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utils.h"
#include "flush_utils.h"
int entry_flush_test(void)
{
char *p;
int repetitions = 10;
int fd, passes = 0, iter, rc = 0;
struct perf_event_read v;
__u64 l1d_misses_total = 0;
unsigned long iterations = 100000, zero_size = 24 * 1024;
unsigned long l1d_misses_expected;
int rfi_flush_orig;
int entry_flush, entry_flush_orig;
SKIP_IF(geteuid() != 0);
// The PMU event we use only works on Power7 or later
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_orig) < 0) {
perror("Unable to read powerpc/rfi_flush debugfs file");
SKIP_IF(1);
}
if (read_debugfs_file("powerpc/entry_flush", &entry_flush_orig) < 0) {
perror("Unable to read powerpc/entry_flush debugfs file");
SKIP_IF(1);
}
if (rfi_flush_orig != 0) {
if (write_debugfs_file("powerpc/rfi_flush", 0) < 0) {
perror("error writing to powerpc/rfi_flush debugfs file");
FAIL_IF(1);
}
}
entry_flush = entry_flush_orig;
fd = perf_event_open_counter(PERF_TYPE_RAW, /* L1d miss */ 0x400f0, -1);
FAIL_IF(fd < 0);
p = (char *)memalign(zero_size, CACHELINE_SIZE);
FAIL_IF(perf_event_enable(fd));
// disable L1 prefetching
set_dscr(1);
iter = repetitions;
/*
* We expect to see l1d miss for each cacheline access when entry_flush
* is set. Allow a small variation on this.
*/
l1d_misses_expected = iterations * (zero_size / CACHELINE_SIZE - 2);
again:
FAIL_IF(perf_event_reset(fd));
syscall_loop(p, iterations, zero_size);
FAIL_IF(read(fd, &v, sizeof(v)) != sizeof(v));
if (entry_flush && v.l1d_misses >= l1d_misses_expected)
passes++;
else if (!entry_flush && v.l1d_misses < (l1d_misses_expected / 2))
passes++;
l1d_misses_total += v.l1d_misses;
while (--iter)
goto again;
if (passes < repetitions) {
printf("FAIL (L1D misses with entry_flush=%d: %llu %c %lu) [%d/%d failures]\n",
entry_flush, l1d_misses_total, entry_flush ? '<' : '>',
entry_flush ? repetitions * l1d_misses_expected :
repetitions * l1d_misses_expected / 2,
repetitions - passes, repetitions);
rc = 1;
} else {
printf("PASS (L1D misses with entry_flush=%d: %llu %c %lu) [%d/%d pass]\n",
entry_flush, l1d_misses_total, entry_flush ? '>' : '<',
entry_flush ? repetitions * l1d_misses_expected :
repetitions * l1d_misses_expected / 2,
passes, repetitions);
}
if (entry_flush == entry_flush_orig) {
entry_flush = !entry_flush_orig;
if (write_debugfs_file("powerpc/entry_flush", entry_flush) < 0) {
perror("error writing to powerpc/entry_flush debugfs file");
return 1;
}
iter = repetitions;
l1d_misses_total = 0;
passes = 0;
goto again;
}
perf_event_disable(fd);
close(fd);
set_dscr(0);
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_orig) < 0) {
perror("unable to restore original value of powerpc/rfi_flush debugfs file");
return 1;
}
if (write_debugfs_file("powerpc/entry_flush", entry_flush_orig) < 0) {
perror("unable to restore original value of powerpc/entry_flush debugfs file");
return 1;
}
return rc;
}
int main(int argc, char *argv[])
{
return test_harness(entry_flush_test, "entry_flush_test");
}

View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 IBM Corporation.
*/
#define __SANE_USERSPACE_TYPES__
#include <sys/types.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utils.h"
#include "flush_utils.h"
static inline __u64 load(void *addr)
{
__u64 tmp;
asm volatile("ld %0,0(%1)" : "=r"(tmp) : "b"(addr));
return tmp;
}
void syscall_loop(char *p, unsigned long iterations,
unsigned long zero_size)
{
for (unsigned long i = 0; i < iterations; i++) {
for (unsigned long j = 0; j < zero_size; j += CACHELINE_SIZE)
load(p + j);
getppid();
}
}
static void sigill_handler(int signr, siginfo_t *info, void *unused)
{
static int warned;
ucontext_t *ctx = (ucontext_t *)unused;
unsigned long *pc = &UCONTEXT_NIA(ctx);
/* mtspr 3,RS to check for move to DSCR below */
if ((*((unsigned int *)*pc) & 0xfc1fffff) == 0x7c0303a6) {
if (!warned++)
printf("WARNING: Skipping over dscr setup. Consider running 'ppc64_cpu --dscr=1' manually.\n");
*pc += 4;
} else {
printf("SIGILL at %p\n", pc);
abort();
}
}
void set_dscr(unsigned long val)
{
static int init;
struct sigaction sa;
if (!init) {
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigill_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGILL, &sa, NULL))
perror("sigill_handler");
init = 1;
}
asm volatile("mtspr %1,%0" : : "r" (val), "i" (SPRN_DSCR));
}

View File

@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright 2018 IBM Corporation.
*/
#ifndef _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H
#define _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H
#define CACHELINE_SIZE 128
void syscall_loop(char *p, unsigned long iterations,
unsigned long zero_size);
void set_dscr(unsigned long val);
#endif /* _SELFTESTS_POWERPC_SECURITY_FLUSH_UTILS_H */

View File

@@ -10,71 +10,12 @@
#include <stdint.h>
#include <malloc.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utils.h"
#include "flush_utils.h"
#define CACHELINE_SIZE 128
struct perf_event_read {
__u64 nr;
__u64 l1d_misses;
};
static inline __u64 load(void *addr)
{
__u64 tmp;
asm volatile("ld %0,0(%1)" : "=r"(tmp) : "b"(addr));
return tmp;
}
static void syscall_loop(char *p, unsigned long iterations,
unsigned long zero_size)
{
for (unsigned long i = 0; i < iterations; i++) {
for (unsigned long j = 0; j < zero_size; j += CACHELINE_SIZE)
load(p + j);
getppid();
}
}
static void sigill_handler(int signr, siginfo_t *info, void *unused)
{
static int warned = 0;
ucontext_t *ctx = (ucontext_t *)unused;
unsigned long *pc = &UCONTEXT_NIA(ctx);
/* mtspr 3,RS to check for move to DSCR below */
if ((*((unsigned int *)*pc) & 0xfc1fffff) == 0x7c0303a6) {
if (!warned++)
printf("WARNING: Skipping over dscr setup. Consider running 'ppc64_cpu --dscr=1' manually.\n");
*pc += 4;
} else {
printf("SIGILL at %p\n", pc);
abort();
}
}
static void set_dscr(unsigned long val)
{
static int init = 0;
struct sigaction sa;
if (!init) {
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sigill_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGILL, &sa, NULL))
perror("sigill_handler");
init = 1;
}
asm volatile("mtspr %1,%0" : : "r" (val), "i" (SPRN_DSCR));
}
int rfi_flush_test(void)
{
@@ -85,19 +26,33 @@ int rfi_flush_test(void)
__u64 l1d_misses_total = 0;
unsigned long iterations = 100000, zero_size = 24 * 1024;
unsigned long l1d_misses_expected;
int rfi_flush_org, rfi_flush;
int rfi_flush_orig, rfi_flush;
int have_entry_flush, entry_flush_orig;
SKIP_IF(geteuid() != 0);
// The PMU event we use only works on Power7 or later
SKIP_IF(!have_hwcap(PPC_FEATURE_ARCH_2_06));
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_org)) {
if (read_debugfs_file("powerpc/rfi_flush", &rfi_flush_orig) < 0) {
perror("Unable to read powerpc/rfi_flush debugfs file");
SKIP_IF(1);
}
rfi_flush = rfi_flush_org;
if (read_debugfs_file("powerpc/entry_flush", &entry_flush_orig) < 0) {
have_entry_flush = 0;
} else {
have_entry_flush = 1;
if (entry_flush_orig != 0) {
if (write_debugfs_file("powerpc/entry_flush", 0) < 0) {
perror("error writing to powerpc/entry_flush debugfs file");
return 1;
}
}
}
rfi_flush = rfi_flush_orig;
fd = perf_event_open_counter(PERF_TYPE_RAW, /* L1d miss */ 0x400f0, -1);
FAIL_IF(fd < 0);
@@ -106,6 +61,7 @@ int rfi_flush_test(void)
FAIL_IF(perf_event_enable(fd));
// disable L1 prefetching
set_dscr(1);
iter = repetitions;
@@ -147,8 +103,8 @@ again:
repetitions * l1d_misses_expected / 2,
passes, repetitions);
if (rfi_flush == rfi_flush_org) {
rfi_flush = !rfi_flush_org;
if (rfi_flush == rfi_flush_orig) {
rfi_flush = !rfi_flush_orig;
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush) < 0) {
perror("error writing to powerpc/rfi_flush debugfs file");
return 1;
@@ -164,11 +120,19 @@ again:
set_dscr(0);
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_org) < 0) {
if (write_debugfs_file("powerpc/rfi_flush", rfi_flush_orig) < 0) {
perror("unable to restore original value of powerpc/rfi_flush debugfs file");
return 1;
}
if (have_entry_flush) {
if (write_debugfs_file("powerpc/entry_flush", entry_flush_orig) < 0) {
perror("unable to restore original value of powerpc/entry_flush "
"debugfs file");
return 1;
}
}
return rc;
}