blob: aa049bc31dffb316cbc380401bee82a633e4cd15 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
#include <stddef.h>
#include <stdint.h>
#include <ktl/bit.h>
#include <ktl/type_traits.h>
// Function arguments of types narrower than a 64-bit register are passed in a
// full 64-bit register or a full 64-bit stack slot. The machine calling
// conventions either say that the high bits are unspecified, or that the
// narrow integer value is zero-extended or sign-extended as appropriate to its
// type or thereabouts, or doesn't specify it clearly.
// * The aarch64 psABI says high bits are unspecified so the compiler is
// obliged to ignore them.
// * The x86-64 psABI doesn't clearly specify it (except that bool values are
// zero-extended from 1 bit to 8 bits); in observed fact, compilers do
// sometimes assume incoming register values have no excess high bits set.
// * The RISC-V psABI says that values of types narrower than 32 bits are
// extended as appropriate for their type to 32 bits, and then (they and
// original 32-bit values) are zero-extended to 64 bits.
// Even in a case like aarch64 where the compiler is unambiguously obliged to
// ignore the high bits rather than assume they have been correctly zeroed or
// sign-extended, the kernel should not trust the compiler not to slip up here,
// nor otherwise allow unintended input noise from the user to have any effect
// whatsoever on the kernel (ideally, not even littering the spilled values in
// kernel stack frames differently in case those are accessible via exploits).
// So this class handles sanitizing the user argument values into C++ values
// that are safe to trust the compiler with.
// Change this to `Safe = false` manually to exercise the test code without
// using the value-sanitizing code path so the test will fail if the compiler
// doesn't generate defensive code.
template <typename T, bool Safe = true>
struct SafeSyscallArgument;
template <typename T, bool Safe>
struct SafeSyscallArgument {
// This is the main implementation for handling integer types safely.
// This is the type that generated entry-point wrappers use in the argument
// declaration. It's always just a 64-bit integer in a register or stack.
using RawType = std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>;
// This returns a safely valid (if not trustworthy) value of type T by
// sanitizing the incoming value from the user of any excess high bits.
// This is a direct user value of type T and not to be considered "safe" as
// an input value, but it is safely actually of type T by C++ semantics
// rather than potentially having undefined behavior at the language level.
static constexpr T Sanitize(RawType value) {
if constexpr (sizeof(T) == sizeof(RawType)) {
return ktl::bit_cast<T>(value);
} else {
static_assert(sizeof(T) < sizeof(RawType));
static_assert(ktl::is_signed_v<T> == ktl::is_signed_v<RawType>);
// This will just truncate the high bits so they are sign- or
// zero-extended from the narrower integer type in the low bits.
return static_cast<T>(value);
// bool arguments are not currently used, but this would make them safe.
// (They must still be excluded from struct layouts used via copy-in.)
template <>
struct SafeSyscallArgument<bool, true> {
using RawType = uint64_t;
static constexpr bool Sanitize(RawType value) { return value & 1; }
// This is the unsafe implementation that can be hand-enabled for testing.
// This version approximates the kernel code's potentially-vulnerable state
// before this mitigation was implemented.
template <typename T>
struct SafeSyscallArgument<T, false> {
using RawType = T;
static constexpr T Sanitize(RawType value) { return value; }