blob: 9b1cbb1932389b4df7e3fad38040885ba090f07c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FBL_CONDITIONAL_SELECT_NOSPEC_H_
#define FBL_CONDITIONAL_SELECT_NOSPEC_H_
#include <sys/types.h>
namespace fbl {
// conditional_select_nospec_*() returns one of its two integral arguments based on whether its
// |predicate| argument is true or false, like a ternary expression. It uses branchless
// sequences on every architecture and is immune to speculative execution information leak bugs
// such as Spectre V1.
//
// Use:
// conditional_select_nospec_eq() returns |a| if |x| == |y|, |b| otherwise.
// conditional_select_nospec_lt() returns |a| if |x| < |y|, |b| otherwise.
// It does so even in wrong-path speculative executions.
//
// Example (susceptible to bounds check bypass / Spectre V1):
// 1: Thing* lookup4(size_t index, uint64_t stamp) {
// 2: size_t safe_index = index & 0xff;
// 4: Thing* const thing = &table_[safe_index];
// 5: return (thing->stamp == stamp) ? thing : nullptr;
// 6: }
//
// Hostile code can cause the ternary expression at line 5 to return |thing| even if
// thing->stamp != stamp in wrong-path speculative execution. If dependent code uses values
// derived from |thing| to look up values in other data structures, the lookup cache side effects
// may be observable and allow hostile code to infer values in a structure that it should not
// have had access to.
//
// Converted:
// 1: Thing* lookup4(size_t index, uint64_t stamp) {
// 2: size_t safe_index = index & 0xff;
// 3: Thing* const thing = &table_[safe_index];
// 4: return fbl::conditional_select_nospec_eq(thing->stamp, stamp, thing, 0);
// 5: }
//
// To avoid Spectre V1-style attacks, a caller must also avoid branching on the return value of
// conditional_select_nospec(); it may do so by using a safe object for |b|; it may also be able
// to use nullptr if AARCH64 PAN / x86-64 SMAP are available.
#if __aarch64__
// (x == y) ? a : b
static inline size_t conditional_select_nospec_eq(size_t x, size_t y, size_t a, size_t b) {
size_t select;
// Use a conditional select and a CSDB barrier to enforce selection.
// See "Cache Speculation Side-channels" whitepaper, section "Software Mitigation".
// "" The combination of both a conditional select/conditional move and the new barrier are
// sufficient to address this problem on ALL Arm implementations... ""
asm("cmp %1, %2\n"
"csel %0, %3, %4, eq\n"
"csdb\n"
: "=r"(select)
: "r"(x), "r"(y), "r"(a), "r"(b)
: "cc");
return select;
}
// (x < y) ? a : b
static inline size_t conditional_select_nospec_lt(size_t x, size_t y, size_t a, size_t b) {
size_t select;
asm("cmp %1, %2\n"
"csel %0, %3, %4, lo\n"
"csdb\n"
: "=r"(select)
: "r"(x), "r"(y), "r"(a), "r"(b)
: "cc");
return select;
}
#elif __x86_64__
// (x == y) ? a : b
static inline size_t conditional_select_nospec_eq(size_t x, size_t y, size_t a, size_t b) {
size_t select = a;
// Use a test / conditional move to select between |a| and |b|.
// The conditional move has a data dependency on the result of a TEST instruction
// and cannot execute until the comparison is resolved.
// See "Software Techniques for Managing Speculation on AMD Processors", Mitigation V1-2.
// See "Analyzing potential bounds check bypass vulnerabilities", Revision 002,
// Section 5.2 Bounds clipping
__asm__(
"cmp %1, %2\n"
"cmovnz %3, %0\n"
: "+r"(select)
: "r"(x), "r"(y), "r"(b)
: "cc");
return select;
}
// (x < y) ? a : b
static inline size_t conditional_select_nospec_lt(size_t x, size_t y, size_t a, size_t b) {
size_t select = a;
__asm__(
"cmp %2, %1\n"
"cmovae %3, %0\n"
: "+r"(select)
: "r"(x), "r"(y), "r"(b)
: "cc");
return select;
}
#else
#error "Provide implementations of conditional_select for your ARCH here"
#endif
} // namespace fbl
#endif // FBL_CONDITIONAL_SELECT_NOSPEC_H_