blob: 8de3a3719e6181f01f7d48e52fbc4549c2add74a [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* Licensed under both the 3-Clause BSD License and the GPLv2, found in the
* LICENSE and LICENSE.GPL-2.0 files, respectively, in the root directory.
*
* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
*/
#ifndef DEMOS_CACHE_SIDECHANNEL_H_
#define DEMOS_CACHE_SIDECHANNEL_H_
#include <array>
#include <memory>
// Represents a cache-line in the oracle for each possible ASCII code.
// We can use this for a timing attack: if the CPU has loaded a given cache
// line, and the cache line it loaded was determined by secret data, we can
// figure out the secret byte by identifying which cache line was loaded.
// That can be done via a timing analysis.
//
// To do this, we create an array of 256 values, each of which do not share
// the same cache line as any other. To eliminate false positives due
// to prefetching, we also ensure no two values share the same page,
// by spacing them at intervals of 4096 bytes.
//
// See 2.3.5.4 Data Prefetching in the Intel Optimization Reference Manual:
// "Data prefetching is triggered by load operations when [...]
// The prefetched data is within the same 4K byte page as the load
// instruction that triggered it."
//
// ARM also has this constraint on prefetching:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0388i/CBBIAAAA.html
//
// Spacing of 4096 was used in the original Spectre paper as well:
// https://spectreattack.com/spectre.pdf
//
struct BigByte {
// Explicitly initialize the array. It doesn't matter what we write; it's
// only important that we write *something* to each page. Otherwise,
// there's an opportunity for the range to be allocated as zero-fill-on-
// demand (ZFOD), where all virtual pages are a read-only mapping to the
// *same* physical page of zeros. The cache in modern Intel CPUs is
// physically-tagged, so all of those virtual addresses would map to the
// same cache line and we wouldn't be able to observe a timing difference
// between accessed and unaccessed pages (modulo e.g. TLB lookups).
std::array<unsigned char, 4096> padding_ = {};
};
// The first and last value might be adjacent to other elements on the heap,
// so we only add padding from both side and use only the other elements, which
// are guaranteed to be on different cache lines, and even different pages,
// than any other value.
struct PaddedOracleArray {
BigByte pad_left;
std::array<BigByte, 256> oracles_;
BigByte pad_right;
};
// Provides an oracle of allocated memory indexed by 256 character ASCII
// codes in order to capture speculative cache loads.
//
// The client is expected to flush the oracle from the cache and access one of
// the indexing characters really (safe_offset_char) and one of them
// speculatively.
//
// Afterwards the client invokes the recomputation of the scores which
// computes which character was accessied speculatively and increases its
// score.
//
// This process (flushing oracle, speculative access of the oracle by the
// client and recomputation of scores) repeats until one of the characters
// accumulates a high enough score.
//
class CacheSideChannel {
public:
CacheSideChannel() = default;
// Not copyable or movable.
CacheSideChannel(const CacheSideChannel&) = delete;
CacheSideChannel& operator=(const CacheSideChannel&) = delete;
// Provides the oracle for speculative memory accesses.
const std::array<BigByte, 256> &GetOracle() const;
// Flushes all indexes in the oracle from the cache.
void FlushOracle() const;
// Finds which character was accessed speculatively and increases its score.
// If one of the characters got a high enough score, returns true and that
// character. Otherwise it returns false and any character that has the
// highest score.
std::pair<bool, char> RecomputeScores(char safe_offset_char);
// Adds an artifical cache-hit and recompute scores. Useful for demonstration
// that do not have natural architectural cache-hits.
std::pair<bool, char> AddHitAndRecomputeScores();
private:
// Oracle array cannot be allocated for stack because MSVC stack size is 1MB,
// so it would immediately overflow.
std::unique_ptr<PaddedOracleArray> padded_oracle_array_ =
std::unique_ptr<PaddedOracleArray>(new PaddedOracleArray);
std::array<int, 257> scores_ = {};
};
#endif // DEMOS_CACHE_SIDECHANNEL_H_