blob: 3c664017ab35a8f55e43b8ec292e7a386724bd8a [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/secure_blob.h>
#include <crypto/scoped_openssl_types.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "cryptohome/cryptolib.h"
using brillo::Blob;
using brillo::BlobFromString;
using brillo::SecureBlob;
using crypto::ScopedRSA;
using cryptohome::CryptoLib;
namespace {
constexpr char kStaticFilesPath[] = "/usr/libexec/fuzzers/";
constexpr int kMaxPlaintextLength = 5;
constexpr int kMaxOaepLabelLength = 5;
struct Environment {
Environment();
// The RSA keys loaded from the data files installed next to the fuzzer.
static constexpr int kRsaKeyCount = 8;
ScopedRSA rsa_keys[kRsaKeyCount];
};
struct ScopedOpensslErrorClearer {
~ScopedOpensslErrorClearer() { ERR_clear_error(); }
};
ScopedRSA LoadRsaPrivateKeyFromPemFile(const base::FilePath& pem_file_path) {
std::string pem_data;
CHECK(base::ReadFileToString(pem_file_path, &pem_data));
crypto::ScopedBIO pem_data_bio(
BIO_new_mem_buf(pem_data.data(), pem_data.size()));
CHECK(pem_data_bio);
crypto::ScopedRSA rsa(PEM_read_bio_RSAPrivateKey(
pem_data_bio.get(), /*RSA **x=*/nullptr, /*pem_password_cb *cb=*/nullptr,
/*void *u=*/nullptr));
CHECK(rsa);
return rsa;
}
Environment::Environment() {
logging::SetMinLogLevel(logging::LOG_FATAL);
int key_index = 0;
for (int key_size : {512, 1024, 2048, 4096}) {
for (int current_key_number : {1, 2}) {
const base::FilePath key_file_path =
base::FilePath(kStaticFilesPath)
.AppendASCII(base::StringPrintf("cryptohome_fuzzer_key_rsa_%d_%d",
key_size, current_key_number));
CHECK_LT(key_index, kRsaKeyCount);
rsa_keys[key_index] = LoadRsaPrivateKeyFromPemFile(key_file_path);
++key_index;
}
}
}
// The "commands" that the MutateBlob() function uses for enterpreting the
// fuzzer input and performing the mutations it implements.
enum class BlobMutatorCommand {
kCopyRemainingData,
kCopyChunk,
kDeleteChunk,
kInsertByte,
kMaxValue = kInsertByte
};
// Returns the mutated version of the provided |input_blob|.
// The following mutations are applied:
// * Removing chunk(s) from the input blob;
// * Inserting "random" bytes into the input blob.
// The size of the resulting blob is guaranteed to be within [0; max_length].
Blob MutateBlob(const Blob& input_blob,
int max_length,
FuzzedDataProvider* fuzzed_data_provider) {
// Begin with an empty result blob. The code below will fill it with data,
// according to the parsed "commands".
Blob fuzzed_blob;
fuzzed_blob.reserve(max_length);
int input_index = 0;
while (fuzzed_blob.size() < max_length) {
switch (fuzzed_data_provider->ConsumeEnum<BlobMutatorCommand>()) {
case BlobMutatorCommand::kCopyRemainingData: {
// Take all remaining data from the input blob and stop.
const int bytes_to_copy = std::min(input_blob.size() - input_index,
max_length - fuzzed_blob.size());
fuzzed_blob.insert(fuzzed_blob.end(), input_blob.begin() + input_index,
input_blob.begin() + input_index + bytes_to_copy);
DCHECK_LE(fuzzed_blob.size(), max_length);
return fuzzed_blob;
}
case BlobMutatorCommand::kCopyChunk: {
// Take the specified number of bytes from the current position in the
// input blob.
const int max_bytes_to_copy = std::min(input_blob.size() - input_index,
max_length - fuzzed_blob.size());
const int bytes_to_copy =
fuzzed_data_provider->ConsumeIntegralInRange(0, max_bytes_to_copy);
fuzzed_blob.insert(fuzzed_blob.end(), input_blob.begin() + input_index,
input_blob.begin() + input_index + bytes_to_copy);
break;
}
case BlobMutatorCommand::kDeleteChunk: {
// Skip (delete) the specified number of bytes from the current position
// in the input blob.
const int max_bytes_to_delete = input_blob.size() - input_index;
const int bytes_to_delete =
fuzzed_data_provider->ConsumeIntegralInRange(0,
max_bytes_to_delete);
input_index += bytes_to_delete;
break;
}
case BlobMutatorCommand::kInsertByte: {
// Append the specified byte.
fuzzed_blob.push_back(fuzzed_data_provider->ConsumeIntegral<uint8_t>());
break;
}
}
}
DCHECK_LE(fuzzed_blob.size(), max_length);
return fuzzed_blob;
}
// Returns a mutated RSA-OAEP encrypted blob of the given plaintext.
Blob FuzzedRsaOaepEncrypt(const Blob& plaintext,
const Blob& oaep_label,
RSA* rsa,
FuzzedDataProvider* fuzzed_data_provider) {
// Explicitly do the padding step first, in order to be able to mutate its
// result before the actual RSA operation.
Blob padded_blob(RSA_size(rsa));
RSA_padding_add_PKCS1_OAEP_mgf1(
padded_blob.data(), padded_blob.size(), plaintext.data(),
plaintext.size(), oaep_label.data(), oaep_label.size(), nullptr, nullptr);
Blob fuzzed_padded_blob =
MutateBlob(padded_blob, RSA_size(rsa), fuzzed_data_provider);
fuzzed_padded_blob.resize(RSA_size(rsa));
Blob ciphertext(RSA_size(rsa));
RSA_public_encrypt(fuzzed_padded_blob.size(), fuzzed_padded_blob.data(),
ciphertext.data(), rsa, RSA_NO_PADDING);
return MutateBlob(ciphertext, RSA_size(rsa), fuzzed_data_provider);
}
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static Environment environment;
// Prevent OpenSSL errors from accumulating in the error queue and leaking the
// memory across fuzzer executions.
ScopedOpensslErrorClearer scoped_openssl_error_clearer;
FuzzedDataProvider fuzzed_data_provider(data, size);
RSA* const encryption_rsa =
environment
.rsa_keys[fuzzed_data_provider.ConsumeIntegralInRange(
0, Environment::kRsaKeyCount - 1)]
.get();
RSA* const decryption_rsa =
environment
.rsa_keys[fuzzed_data_provider.ConsumeIntegralInRange(
0, Environment::kRsaKeyCount - 1)]
.get();
// Prepare fuzzed parameters for the tested function, based off real
// RSA-encoded blobs.
const Blob plaintext = BlobFromString(
fuzzed_data_provider.ConsumeRandomLengthString(kMaxPlaintextLength));
const Blob oaep_label = BlobFromString(
fuzzed_data_provider.ConsumeRandomLengthString(kMaxOaepLabelLength));
const Blob fuzzed_ciphertext = FuzzedRsaOaepEncrypt(
plaintext, oaep_label, encryption_rsa, &fuzzed_data_provider);
const Blob fuzzed_oaep_label =
MutateBlob(oaep_label, kMaxOaepLabelLength, &fuzzed_data_provider);
// Run the fuzzed function.
SecureBlob decrypted_data;
if (CryptoLib::RsaOaepDecrypt(SecureBlob(fuzzed_ciphertext),
SecureBlob(fuzzed_oaep_label), decryption_rsa,
&decrypted_data)) {
// Assert that the decryption result must be equal to the plaintext that was
// encrypted above - it's unrealistic for the fuzzer to find a blob that is
// a valid ciphertext of some different blob.
CHECK(SecureBlob(plaintext) == decrypted_data);
}
return 0;
}