| // 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; |
| } |