diff options
author | Alex Vakulenko <avakulenko@google.com> | 2016-01-22 16:52:43 -0800 |
---|---|---|
committer | Alex Vakulenko <avakulenko@google.com> | 2016-01-22 17:02:32 -0800 |
commit | f6024733c0d1eed88f68520b5e6a20b96e212ad6 (patch) | |
tree | fed955593d9995a027a33cd46b41260882ddd3ea | |
parent | 340b8dd38ca56de409ca4f790b45b1c314c544dd (diff) | |
download | libchrome-f6024733c0d1eed88f68520b5e6a20b96e212ad6.tar.gz |
libchrome: Revert deleted files needed by Chrome OS
Some of the "unused" files were apparently needed for building libchrome
on Chrome OS. Reverting deletion of these files.
Change-Id: I02e32f112d16480206f43ca0087342a9de7f1e1b
145 files changed, 26091 insertions, 0 deletions
diff --git a/base/allocator/allocator_extension.cc b/base/allocator/allocator_extension.cc new file mode 100644 index 0000000000..83e460ac82 --- /dev/null +++ b/base/allocator/allocator_extension.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium 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 "base/allocator/allocator_extension.h" + +#include "base/logging.h" + +namespace base { +namespace allocator { + +bool GetAllocatorWasteSize(size_t* size) { + thunks::GetAllocatorWasteSizeFunction get_allocator_waste_size_function = + thunks::GetGetAllocatorWasteSizeFunction(); + return get_allocator_waste_size_function != NULL && + get_allocator_waste_size_function(size); +} + +void GetStats(char* buffer, int buffer_length) { + DCHECK_GT(buffer_length, 0); + thunks::GetStatsFunction get_stats_function = thunks::GetGetStatsFunction(); + if (get_stats_function) + get_stats_function(buffer, buffer_length); + else + buffer[0] = '\0'; +} + +void ReleaseFreeMemory() { + thunks::ReleaseFreeMemoryFunction release_free_memory_function = + thunks::GetReleaseFreeMemoryFunction(); + if (release_free_memory_function) + release_free_memory_function(); +} + +void SetGetAllocatorWasteSizeFunction( + thunks::GetAllocatorWasteSizeFunction get_allocator_waste_size_function) { + DCHECK_EQ(thunks::GetGetAllocatorWasteSizeFunction(), + reinterpret_cast<thunks::GetAllocatorWasteSizeFunction>(NULL)); + thunks::SetGetAllocatorWasteSizeFunction(get_allocator_waste_size_function); +} + +void SetGetStatsFunction(thunks::GetStatsFunction get_stats_function) { + DCHECK_EQ(thunks::GetGetStatsFunction(), + reinterpret_cast<thunks::GetStatsFunction>(NULL)); + thunks::SetGetStatsFunction(get_stats_function); +} + +void SetReleaseFreeMemoryFunction( + thunks::ReleaseFreeMemoryFunction release_free_memory_function) { + DCHECK_EQ(thunks::GetReleaseFreeMemoryFunction(), + reinterpret_cast<thunks::ReleaseFreeMemoryFunction>(NULL)); + thunks::SetReleaseFreeMemoryFunction(release_free_memory_function); +} + +} // namespace allocator +} // namespace base diff --git a/base/native_library_posix.cc b/base/native_library_posix.cc new file mode 100644 index 0000000000..3179a93833 --- /dev/null +++ b/base/native_library_posix.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2011 The Chromium 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 "base/native_library.h" + +#include <dlfcn.h> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +std::string NativeLibraryLoadError::ToString() const { + return message; +} + +// static +NativeLibrary LoadNativeLibrary(const FilePath& library_path, + NativeLibraryLoadError* error) { + // dlopen() opens the file off disk. + base::ThreadRestrictions::AssertIOAllowed(); + + // We deliberately do not use RTLD_DEEPBIND. For the history why, please + // refer to the bug tracker. Some useful bug reports to read include: + // http://crbug.com/17943, http://crbug.com/17557, http://crbug.com/36892, + // and http://crbug.com/40794. + void* dl = dlopen(library_path.value().c_str(), RTLD_LAZY); + if (!dl && error) + error->message = dlerror(); + + return dl; +} + +// static +void UnloadNativeLibrary(NativeLibrary library) { + int ret = dlclose(library); + if (ret < 0) { + DLOG(ERROR) << "dlclose failed: " << dlerror(); + NOTREACHED(); + } +} + +// static +void* GetFunctionPointerFromNativeLibrary(NativeLibrary library, + const char* name) { + return dlsym(library, name); +} + +// static +string16 GetNativeLibraryName(const string16& name) { + return ASCIIToUTF16("lib") + name + ASCIIToUTF16(".so"); +} + +} // namespace base diff --git a/base/timer/hi_res_timer_manager_posix.cc b/base/timer/hi_res_timer_manager_posix.cc new file mode 100644 index 0000000000..d2f152c8c0 --- /dev/null +++ b/base/timer/hi_res_timer_manager_posix.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium 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 "base/timer/hi_res_timer_manager.h" + +// On POSIX we don't need to do anything special with the system timer. + +namespace base { + +HighResolutionTimerManager::HighResolutionTimerManager() + : hi_res_clock_available_(false) { +} + +HighResolutionTimerManager::~HighResolutionTimerManager() { +} + +void HighResolutionTimerManager::OnPowerStateChange(bool on_battery_power) { +} + +void HighResolutionTimerManager::UseHiResClock(bool use) { +} + +} // namespace base diff --git a/base/timer/mock_timer.cc b/base/timer/mock_timer.cc new file mode 100644 index 0000000000..296071e8e3 --- /dev/null +++ b/base/timer/mock_timer.cc @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium 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 "base/timer/mock_timer.h" + +namespace base { + +MockTimer::MockTimer(bool retain_user_task, bool is_repeating) + : Timer(retain_user_task, is_repeating), + is_running_(false) { +} + +MockTimer::MockTimer(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task, + bool is_repeating) + : Timer(true, is_repeating), + delay_(delay), + is_running_(false) { +} + +MockTimer::~MockTimer() { +} + +bool MockTimer::IsRunning() const { + return is_running_; +} + +base::TimeDelta MockTimer::GetCurrentDelay() const { + return delay_; +} + +void MockTimer::Start(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task) { + delay_ = delay; + user_task_ = user_task; + Reset(); +} + +void MockTimer::Stop() { + is_running_ = false; + if (!retain_user_task()) + user_task_.Reset(); +} + +void MockTimer::Reset() { + DCHECK(!user_task_.is_null()); + is_running_ = true; +} + +void MockTimer::Fire() { + DCHECK(is_running_); + base::Closure old_task = user_task_; + if (is_repeating()) + Reset(); + else + Stop(); + old_task.Run(); +} + +} // namespace base diff --git a/base/timer/mock_timer.h b/base/timer/mock_timer.h new file mode 100644 index 0000000000..e18a5c0489 --- /dev/null +++ b/base/timer/mock_timer.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TIMER_MOCK_TIMER_H_ +#define BASE_TIMER_MOCK_TIMER_H_ + +#include "base/timer/timer.h" + +namespace base { + +class BASE_EXPORT MockTimer : public Timer { + public: + MockTimer(bool retain_user_task, bool is_repeating); + MockTimer(const tracked_objects::Location& posted_from, + TimeDelta delay, + const base::Closure& user_task, + bool is_repeating); + ~MockTimer() override; + + // base::Timer implementation. + bool IsRunning() const override; + base::TimeDelta GetCurrentDelay() const override; + void Start(const tracked_objects::Location& posted_from, + base::TimeDelta delay, + const base::Closure& user_task) override; + void Stop() override; + void Reset() override; + + // Testing methods. + void Fire(); + + private: + base::Closure user_task_; + TimeDelta delay_; + bool is_running_; +}; + +} // namespace base + +#endif // BASE_TIMER_MOCK_TIMER_H_ diff --git a/base/timer/mock_timer_unittest.cc b/base/timer/mock_timer_unittest.cc new file mode 100644 index 0000000000..f6b6953648 --- /dev/null +++ b/base/timer/mock_timer_unittest.cc @@ -0,0 +1,82 @@ +// Copyright 2014 The Chromium 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 "base/timer/mock_timer.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void CallMeMaybe(int *number) { + (*number)++; +} + +TEST(MockTimerTest, FiresOnce) { + int calls = 0; + base::MockTimer timer(false, false); + base::TimeDelta delay = base::TimeDelta::FromSeconds(2); + timer.Start(FROM_HERE, delay, + base::Bind(&CallMeMaybe, + base::Unretained(&calls))); + EXPECT_EQ(delay, timer.GetCurrentDelay()); + EXPECT_TRUE(timer.IsRunning()); + timer.Fire(); + EXPECT_FALSE(timer.IsRunning()); + EXPECT_EQ(1, calls); +} + +TEST(MockTimerTest, FiresRepeatedly) { + int calls = 0; + base::MockTimer timer(true, true); + base::TimeDelta delay = base::TimeDelta::FromSeconds(2); + timer.Start(FROM_HERE, delay, + base::Bind(&CallMeMaybe, + base::Unretained(&calls))); + timer.Fire(); + EXPECT_TRUE(timer.IsRunning()); + timer.Fire(); + timer.Fire(); + EXPECT_TRUE(timer.IsRunning()); + EXPECT_EQ(3, calls); +} + +TEST(MockTimerTest, Stops) { + int calls = 0; + base::MockTimer timer(true, true); + base::TimeDelta delay = base::TimeDelta::FromSeconds(2); + timer.Start(FROM_HERE, delay, + base::Bind(&CallMeMaybe, + base::Unretained(&calls))); + EXPECT_TRUE(timer.IsRunning()); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); +} + +class HasWeakPtr : public base::SupportsWeakPtr<HasWeakPtr> { + public: + HasWeakPtr() {} + virtual ~HasWeakPtr() {} + + private: + DISALLOW_COPY_AND_ASSIGN(HasWeakPtr); +}; + +void DoNothingWithWeakPtr(HasWeakPtr* has_weak_ptr) { +} + +TEST(MockTimerTest, DoesNotRetainClosure) { + HasWeakPtr *has_weak_ptr = new HasWeakPtr(); + base::WeakPtr<HasWeakPtr> weak_ptr(has_weak_ptr->AsWeakPtr()); + base::MockTimer timer(false, false); + base::TimeDelta delay = base::TimeDelta::FromSeconds(2); + ASSERT_TRUE(weak_ptr.get()); + timer.Start(FROM_HERE, delay, + base::Bind(&DoNothingWithWeakPtr, + base::Owned(has_weak_ptr))); + ASSERT_TRUE(weak_ptr.get()); + timer.Fire(); + ASSERT_FALSE(weak_ptr.get()); +} + +} // namespace diff --git a/crypto/hmac.cc b/crypto/hmac.cc new file mode 100644 index 0000000000..c9a2b74d98 --- /dev/null +++ b/crypto/hmac.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/hmac.h" + +#include <algorithm> + +#include "base/logging.h" +#include "crypto/secure_util.h" +#include "crypto/symmetric_key.h" + +namespace crypto { + +bool HMAC::Init(SymmetricKey* key) { + std::string raw_key; + bool result = key->GetRawKey(&raw_key) && Init(raw_key); + // Zero out key copy. This might get optimized away, but one can hope. + // Using std::string to store key info at all is a larger problem. + std::fill(raw_key.begin(), raw_key.end(), 0); + return result; +} + +size_t HMAC::DigestLength() const { + switch (hash_alg_) { + case SHA1: + return 20; + case SHA256: + return 32; + default: + NOTREACHED(); + return 0; + } +} + +bool HMAC::Verify(const base::StringPiece& data, + const base::StringPiece& digest) const { + if (digest.size() != DigestLength()) + return false; + return VerifyTruncated(data, digest); +} + +bool HMAC::VerifyTruncated(const base::StringPiece& data, + const base::StringPiece& digest) const { + if (digest.empty()) + return false; + size_t digest_length = DigestLength(); + scoped_ptr<unsigned char[]> computed_digest( + new unsigned char[digest_length]); + if (!Sign(data, computed_digest.get(), digest_length)) + return false; + + return SecureMemEqual(digest.data(), computed_digest.get(), + std::min(digest.size(), digest_length)); +} + +} // namespace crypto diff --git a/crypto/hmac.h b/crypto/hmac.h new file mode 100644 index 0000000000..c7b22fa1bd --- /dev/null +++ b/crypto/hmac.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Utility class for calculating the HMAC for a given message. We currently +// only support SHA1 for the hash algorithm, but this can be extended easily. + +#ifndef CRYPTO_HMAC_H_ +#define CRYPTO_HMAC_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" +#include "crypto/crypto_export.h" + +namespace crypto { + +// Simplify the interface and reduce includes by abstracting out the internals. +struct HMACPlatformData; +class SymmetricKey; + +class CRYPTO_EXPORT HMAC { + public: + // The set of supported hash functions. Extend as required. + enum HashAlgorithm { + SHA1, + SHA256, + }; + + explicit HMAC(HashAlgorithm hash_alg); + ~HMAC(); + + // Returns the length of digest that this HMAC will create. + size_t DigestLength() const; + + // TODO(abarth): Add a PreferredKeyLength() member function. + + // Initializes this instance using |key| of the length |key_length|. Call Init + // only once. It returns false on the second or later calls. + // + // NOTE: the US Federal crypto standard FIPS 198, Section 3 says: + // The size of the key, K, shall be equal to or greater than L/2, where L + // is the size of the hash function output. + // In FIPS 198-1 (and SP-800-107, which describes key size recommendations), + // this requirement is gone. But a system crypto library may still enforce + // this old requirement. If the key is shorter than this recommended value, + // Init() may fail. + bool Init(const unsigned char* key, size_t key_length) WARN_UNUSED_RESULT; + + // Initializes this instance using |key|. Call Init + // only once. It returns false on the second or later calls. + bool Init(SymmetricKey* key) WARN_UNUSED_RESULT; + + // Initializes this instance using |key|. Call Init only once. It returns + // false on the second or later calls. + bool Init(const base::StringPiece& key) WARN_UNUSED_RESULT { + return Init(reinterpret_cast<const unsigned char*>(key.data()), + key.size()); + } + + // Calculates the HMAC for the message in |data| using the algorithm supplied + // to the constructor and the key supplied to the Init method. The HMAC is + // returned in |digest|, which has |digest_length| bytes of storage available. + bool Sign(const base::StringPiece& data, unsigned char* digest, + size_t digest_length) const WARN_UNUSED_RESULT; + + // Verifies that the HMAC for the message in |data| equals the HMAC provided + // in |digest|, using the algorithm supplied to the constructor and the key + // supplied to the Init method. Use of this method is strongly recommended + // over using Sign() with a manual comparison (such as memcmp), as such + // comparisons may result in side-channel disclosures, such as timing, that + // undermine the cryptographic integrity. |digest| must be exactly + // |DigestLength()| bytes long. + bool Verify(const base::StringPiece& data, + const base::StringPiece& digest) const WARN_UNUSED_RESULT; + + // Verifies a truncated HMAC, behaving identical to Verify(), except + // that |digest| is allowed to be smaller than |DigestLength()|. + bool VerifyTruncated( + const base::StringPiece& data, + const base::StringPiece& digest) const WARN_UNUSED_RESULT; + + private: + HashAlgorithm hash_alg_; + scoped_ptr<HMACPlatformData> plat_; + + DISALLOW_COPY_AND_ASSIGN(HMAC); +}; + +} // namespace crypto + +#endif // CRYPTO_HMAC_H_ diff --git a/crypto/hmac_nss.cc b/crypto/hmac_nss.cc new file mode 100644 index 0000000000..e14282c916 --- /dev/null +++ b/crypto/hmac_nss.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/hmac.h" + +#include <nss.h> +#include <pk11pub.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" + +namespace crypto { + +struct HMACPlatformData { + CK_MECHANISM_TYPE mechanism_; + ScopedPK11Slot slot_; + ScopedPK11SymKey sym_key_; +}; + +HMAC::HMAC(HashAlgorithm hash_alg) + : hash_alg_(hash_alg), plat_(new HMACPlatformData()) { + // Only SHA-1 and SHA-256 hash algorithms are supported. + switch (hash_alg_) { + case SHA1: + plat_->mechanism_ = CKM_SHA_1_HMAC; + break; + case SHA256: + plat_->mechanism_ = CKM_SHA256_HMAC; + break; + default: + NOTREACHED() << "Unsupported hash algorithm"; + break; + } +} + +HMAC::~HMAC() { +} + +bool HMAC::Init(const unsigned char *key, size_t key_length) { + EnsureNSSInit(); + + if (plat_->slot_.get()) { + // Init must not be called more than twice on the same HMAC object. + NOTREACHED(); + return false; + } + + plat_->slot_.reset(PK11_GetInternalSlot()); + if (!plat_->slot_.get()) { + NOTREACHED(); + return false; + } + + SECItem key_item; + key_item.type = siBuffer; + key_item.data = const_cast<unsigned char*>(key); // NSS API isn't const. + key_item.len = key_length; + + plat_->sym_key_.reset(PK11_ImportSymKey(plat_->slot_.get(), + plat_->mechanism_, + PK11_OriginUnwrap, + CKA_SIGN, + &key_item, + NULL)); + if (!plat_->sym_key_.get()) { + NOTREACHED(); + return false; + } + + return true; +} + +bool HMAC::Sign(const base::StringPiece& data, + unsigned char* digest, + size_t digest_length) const { + if (!plat_->sym_key_.get()) { + // Init has not been called before Sign. + NOTREACHED(); + return false; + } + + SECItem param = { siBuffer, NULL, 0 }; + ScopedPK11Context context(PK11_CreateContextBySymKey(plat_->mechanism_, + CKA_SIGN, + plat_->sym_key_.get(), + ¶m)); + if (!context.get()) { + NOTREACHED(); + return false; + } + + if (PK11_DigestBegin(context.get()) != SECSuccess) { + NOTREACHED(); + return false; + } + + if (PK11_DigestOp(context.get(), + reinterpret_cast<const unsigned char*>(data.data()), + data.length()) != SECSuccess) { + NOTREACHED(); + return false; + } + + unsigned int len = 0; + if (PK11_DigestFinal(context.get(), + digest, &len, digest_length) != SECSuccess) { + NOTREACHED(); + return false; + } + + return true; +} + +} // namespace crypto diff --git a/crypto/hmac_openssl.cc b/crypto/hmac_openssl.cc new file mode 100644 index 0000000000..ef20290e22 --- /dev/null +++ b/crypto/hmac_openssl.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/hmac.h" + +#include <openssl/hmac.h> + +#include <algorithm> +#include <vector> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "crypto/openssl_util.h" + +namespace crypto { + +struct HMACPlatformData { + std::vector<unsigned char> key; +}; + +HMAC::HMAC(HashAlgorithm hash_alg) : hash_alg_(hash_alg) { + // Only SHA-1 and SHA-256 hash algorithms are supported now. + DCHECK(hash_alg_ == SHA1 || hash_alg_ == SHA256); +} + +bool HMAC::Init(const unsigned char* key, size_t key_length) { + // Init must not be called more than once on the same HMAC object. + DCHECK(!plat_); + plat_.reset(new HMACPlatformData()); + plat_->key.assign(key, key + key_length); + return true; +} + +HMAC::~HMAC() { + if (plat_) { + // Zero out key copy. + plat_->key.assign(plat_->key.size(), 0); + STLClearObject(&plat_->key); + } +} + +bool HMAC::Sign(const base::StringPiece& data, + unsigned char* digest, + size_t digest_length) const { + DCHECK(plat_); // Init must be called before Sign. + + ScopedOpenSSLSafeSizeBuffer<EVP_MAX_MD_SIZE> result(digest, digest_length); + return !!::HMAC(hash_alg_ == SHA1 ? EVP_sha1() : EVP_sha256(), + vector_as_array(&plat_->key), plat_->key.size(), + reinterpret_cast<const unsigned char*>(data.data()), + data.size(), result.safe_buffer(), NULL); +} + +} // namespace crypto diff --git a/crypto/hmac_unittest.cc b/crypto/hmac_unittest.cc new file mode 100644 index 0000000000..91eccd68e2 --- /dev/null +++ b/crypto/hmac_unittest.cc @@ -0,0 +1,295 @@ +// Copyright (c) 2011 The Chromium 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 <string> + +#include "crypto/hmac.h" +#include "testing/gtest/include/gtest/gtest.h" + +static const size_t kSHA1DigestSize = 20; +static const size_t kSHA256DigestSize = 32; + +static const char* kSimpleKey = + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"; +static const size_t kSimpleKeyLength = 80; + +static const struct { + const char *data; + const int data_len; + const char *digest; +} kSimpleHmacCases[] = { + { "Test Using Larger Than Block-Size Key - Hash Key First", 54, + "\xAA\x4A\xE5\xE1\x52\x72\xD0\x0E\x95\x70\x56\x37\xCE\x8A\x3B\x55" + "\xED\x40\x21\x12" }, + { "Test Using Larger Than Block-Size Key and Larger " + "Than One Block-Size Data", 73, + "\xE8\xE9\x9D\x0F\x45\x23\x7D\x78\x6D\x6B\xBA\xA7\x96\x5C\x78\x08" + "\xBB\xFF\x1A\x91" } +}; + +TEST(HMACTest, HmacSafeBrowsingResponseTest) { + const int kKeySize = 16; + + // Client key. + const unsigned char kClientKey[kKeySize] = + { 0xbf, 0xf6, 0x83, 0x4b, 0x3e, 0xa3, 0x23, 0xdd, + 0x96, 0x78, 0x70, 0x8e, 0xa1, 0x9d, 0x3b, 0x40 }; + + // Expected HMAC result using kMessage and kClientKey. + const unsigned char kReceivedHmac[kSHA1DigestSize] = + { 0xb9, 0x3c, 0xd6, 0xf0, 0x49, 0x47, 0xe2, 0x52, + 0x59, 0x7a, 0xbd, 0x1f, 0x2b, 0x4c, 0x83, 0xad, + 0x86, 0xd2, 0x48, 0x85 }; + + const char kMessage[] = +"n:1896\ni:goog-malware-shavar\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shav" +"ar_s_445-450\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_439-444\nu:s" +".ytimg.com/safebrowsing/rd/goog-malware-shavar_s_437\nu:s.ytimg.com/safebrowsi" +"ng/rd/goog-malware-shavar_s_436\nu:s.ytimg.com/safebrowsing/rd/goog-malware-sh" +"avar_s_433-435\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_431\nu:s.y" +"timg.com/safebrowsing/rd/goog-malware-shavar_s_430\nu:s.ytimg.com/safebrowsing" +"/rd/goog-malware-shavar_s_429\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shav" +"ar_s_428\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_426\nu:s.ytimg.c" +"om/safebrowsing/rd/goog-malware-shavar_s_424\nu:s.ytimg.com/safebrowsing/rd/go" +"og-malware-shavar_s_423\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_4" +"22\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_420\nu:s.ytimg.com/saf" +"ebrowsing/rd/goog-malware-shavar_s_419\nu:s.ytimg.com/safebrowsing/rd/goog-mal" +"ware-shavar_s_414\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_409-411" +"\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_405\nu:s.ytimg.com/safeb" +"rowsing/rd/goog-malware-shavar_s_404\nu:s.ytimg.com/safebrowsing/rd/goog-malwa" +"re-shavar_s_402\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_s_401\nu:s." +"ytimg.com/safebrowsing/rd/goog-malware-shavar_a_973-978\nu:s.ytimg.com/safebro" +"wsing/rd/goog-malware-shavar_a_937-972\nu:s.ytimg.com/safebrowsing/rd/goog-mal" +"ware-shavar_a_931-936\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_a_925" +"-930\nu:s.ytimg.com/safebrowsing/rd/goog-malware-shavar_a_919-924\ni:goog-phis" +"h-shavar\nu:s.ytimg.com/safebrowsing/rd/goog-phish-shavar_a_2633\nu:s.ytimg.co" +"m/safebrowsing/rd/goog-phish-shavar_a_2632\nu:s.ytimg.com/safebrowsing/rd/goog" +"-phish-shavar_a_2629-2631\nu:s.ytimg.com/safebrowsing/rd/goog-phish-shavar_a_2" +"626-2628\nu:s.ytimg.com/safebrowsing/rd/goog-phish-shavar_a_2625\n"; + + std::string message_data(kMessage); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + ASSERT_TRUE(hmac.Init(kClientKey, kKeySize)); + unsigned char calculated_hmac[kSHA1DigestSize]; + + EXPECT_TRUE(hmac.Sign(message_data, calculated_hmac, kSHA1DigestSize)); + EXPECT_EQ(0, memcmp(kReceivedHmac, calculated_hmac, kSHA1DigestSize)); +} + +// Test cases from RFC 2202 section 3 +TEST(HMACTest, RFC2202TestCases) { + const struct { + const char *key; + const int key_len; + const char *data; + const int data_len; + const char *digest; + } cases[] = { + { "\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B\x0B" + "\x0B\x0B\x0B\x0B", 20, + "Hi There", 8, + "\xB6\x17\x31\x86\x55\x05\x72\x64\xE2\x8B\xC0\xB6\xFB\x37\x8C\x8E" + "\xF1\x46\xBE\x00" }, + { "Jefe", 4, + "what do ya want for nothing?", 28, + "\xEF\xFC\xDF\x6A\xE5\xEB\x2F\xA2\xD2\x74\x16\xD5\xF1\x84\xDF\x9C" + "\x25\x9A\x7C\x79" }, + { "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA", 20, + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD", 50, + "\x12\x5D\x73\x42\xB9\xAC\x11\xCD\x91\xA3\x9A\xF4\x8A\xA1\x7B\x4F" + "\x63\xF1\x75\xD3" }, + { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18\x19", 25, + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD", 50, + "\x4C\x90\x07\xF4\x02\x62\x50\xC6\xBC\x84\x14\xF9\xBF\x50\xC8\x6C" + "\x2D\x72\x35\xDA" }, + { "\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C\x0C" + "\x0C\x0C\x0C\x0C", 20, + "Test With Truncation", 20, + "\x4C\x1A\x03\x42\x4B\x55\xE0\x7F\xE7\xF2\x7B\xE1\xD5\x8B\xB9\x32" + "\x4A\x9A\x5A\x04" }, + { "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA", + 80, + "Test Using Larger Than Block-Size Key - Hash Key First", 54, + "\xAA\x4A\xE5\xE1\x52\x72\xD0\x0E\x95\x70\x56\x37\xCE\x8A\x3B\x55" + "\xED\x40\x21\x12" }, + { "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" + "\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA", + 80, + "Test Using Larger Than Block-Size Key and Larger " + "Than One Block-Size Data", 73, + "\xE8\xE9\x9D\x0F\x45\x23\x7D\x78\x6D\x6B\xBA\xA7\x96\x5C\x78\x08" + "\xBB\xFF\x1A\x91" } + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + crypto::HMAC hmac(crypto::HMAC::SHA1); + ASSERT_TRUE(hmac.Init(reinterpret_cast<const unsigned char*>(cases[i].key), + cases[i].key_len)); + std::string data_string(cases[i].data, cases[i].data_len); + unsigned char digest[kSHA1DigestSize]; + EXPECT_TRUE(hmac.Sign(data_string, digest, kSHA1DigestSize)); + EXPECT_EQ(0, memcmp(cases[i].digest, digest, kSHA1DigestSize)); + } +} + +// TODO(wtc): add other test vectors from RFC 4231. +TEST(HMACTest, RFC4231TestCase6) { + unsigned char key[131]; + for (size_t i = 0; i < sizeof(key); ++i) + key[i] = 0xaa; + + std::string data = "Test Using Larger Than Block-Size Key - Hash Key First"; + ASSERT_EQ(54U, data.size()); + + static unsigned char kKnownHMACSHA256[] = { + 0x60, 0xe4, 0x31, 0x59, 0x1e, 0xe0, 0xb6, 0x7f, + 0x0d, 0x8a, 0x26, 0xaa, 0xcb, 0xf5, 0xb7, 0x7f, + 0x8e, 0x0b, 0xc6, 0x21, 0x37, 0x28, 0xc5, 0x14, + 0x05, 0x46, 0x04, 0x0f, 0x0e, 0xe3, 0x7f, 0x54 + }; + + crypto::HMAC hmac(crypto::HMAC::SHA256); + ASSERT_TRUE(hmac.Init(key, sizeof(key))); + unsigned char calculated_hmac[kSHA256DigestSize]; + + EXPECT_EQ(kSHA256DigestSize, hmac.DigestLength()); + EXPECT_TRUE(hmac.Sign(data, calculated_hmac, kSHA256DigestSize)); + EXPECT_EQ(0, memcmp(kKnownHMACSHA256, calculated_hmac, kSHA256DigestSize)); +} + +// Based on NSS's FIPS HMAC power-up self-test. +TEST(HMACTest, NSSFIPSPowerUpSelfTest) { + static const char kKnownMessage[] = + "The test message for the MD2, MD5, and SHA-1 hashing algorithms."; + + static const unsigned char kKnownSecretKey[] = { + 0x46, 0x69, 0x72, 0x65, 0x66, 0x6f, 0x78, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x54, 0x68, 0x75, 0x6e, + 0x64, 0x65, 0x72, 0x42, 0x69, 0x72, 0x64, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x61, 0x77, 0x65, 0x73, + 0x6f, 0x6d, 0x65, 0x21, 0x00 + }; + + static const size_t kKnownSecretKeySize = sizeof(kKnownSecretKey); + + // HMAC-SHA-1 known answer (20 bytes). + static const unsigned char kKnownHMACSHA1[] = { + 0xd5, 0x85, 0xf6, 0x5b, 0x39, 0xfa, 0xb9, 0x05, + 0x3b, 0x57, 0x1d, 0x61, 0xe7, 0xb8, 0x84, 0x1e, + 0x5d, 0x0e, 0x1e, 0x11 + }; + + // HMAC-SHA-256 known answer (32 bytes). + static const unsigned char kKnownHMACSHA256[] = { + 0x05, 0x75, 0x9a, 0x9e, 0x70, 0x5e, 0xe7, 0x44, + 0xe2, 0x46, 0x4b, 0x92, 0x22, 0x14, 0x22, 0xe0, + 0x1b, 0x92, 0x8a, 0x0c, 0xfe, 0xf5, 0x49, 0xe9, + 0xa7, 0x1b, 0x56, 0x7d, 0x1d, 0x29, 0x40, 0x48 + }; + + std::string message_data(kKnownMessage); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + ASSERT_TRUE(hmac.Init(kKnownSecretKey, kKnownSecretKeySize)); + unsigned char calculated_hmac[kSHA1DigestSize]; + + EXPECT_EQ(kSHA1DigestSize, hmac.DigestLength()); + EXPECT_TRUE(hmac.Sign(message_data, calculated_hmac, kSHA1DigestSize)); + EXPECT_EQ(0, memcmp(kKnownHMACSHA1, calculated_hmac, kSHA1DigestSize)); + EXPECT_TRUE(hmac.Verify( + message_data, + base::StringPiece(reinterpret_cast<const char*>(kKnownHMACSHA1), + kSHA1DigestSize))); + EXPECT_TRUE(hmac.VerifyTruncated( + message_data, + base::StringPiece(reinterpret_cast<const char*>(kKnownHMACSHA1), + kSHA1DigestSize / 2))); + + crypto::HMAC hmac2(crypto::HMAC::SHA256); + ASSERT_TRUE(hmac2.Init(kKnownSecretKey, kKnownSecretKeySize)); + unsigned char calculated_hmac2[kSHA256DigestSize]; + + EXPECT_TRUE(hmac2.Sign(message_data, calculated_hmac2, kSHA256DigestSize)); + EXPECT_EQ(0, memcmp(kKnownHMACSHA256, calculated_hmac2, kSHA256DigestSize)); +} + +TEST(HMACTest, HMACObjectReuse) { + crypto::HMAC hmac(crypto::HMAC::SHA1); + ASSERT_TRUE( + hmac.Init(reinterpret_cast<const unsigned char*>(kSimpleKey), + kSimpleKeyLength)); + for (size_t i = 0; i < arraysize(kSimpleHmacCases); ++i) { + std::string data_string(kSimpleHmacCases[i].data, + kSimpleHmacCases[i].data_len); + unsigned char digest[kSHA1DigestSize]; + EXPECT_TRUE(hmac.Sign(data_string, digest, kSHA1DigestSize)); + EXPECT_EQ(0, memcmp(kSimpleHmacCases[i].digest, digest, kSHA1DigestSize)); + } +} + +TEST(HMACTest, Verify) { + crypto::HMAC hmac(crypto::HMAC::SHA1); + ASSERT_TRUE( + hmac.Init(reinterpret_cast<const unsigned char*>(kSimpleKey), + kSimpleKeyLength)); + const char empty_digest[kSHA1DigestSize] = { 0 }; + for (size_t i = 0; i < arraysize(kSimpleHmacCases); ++i) { + // Expected results + EXPECT_TRUE(hmac.Verify( + base::StringPiece(kSimpleHmacCases[i].data, + kSimpleHmacCases[i].data_len), + base::StringPiece(kSimpleHmacCases[i].digest, + kSHA1DigestSize))); + // Mismatched size + EXPECT_FALSE(hmac.Verify( + base::StringPiece(kSimpleHmacCases[i].data, + kSimpleHmacCases[i].data_len), + base::StringPiece(kSimpleHmacCases[i].data, + kSimpleHmacCases[i].data_len))); + + // Expected size, mismatched data + EXPECT_FALSE(hmac.Verify( + base::StringPiece(kSimpleHmacCases[i].data, + kSimpleHmacCases[i].data_len), + base::StringPiece(empty_digest, kSHA1DigestSize))); + } +} + +TEST(HMACTest, EmptyKey) { + // Test vector from https://en.wikipedia.org/wiki/HMAC + const char* kExpectedDigest = + "\xFB\xDB\x1D\x1B\x18\xAA\x6C\x08\x32\x4B\x7D\x64\xB7\x1F\xB7\x63" + "\x70\x69\x0E\x1D"; + base::StringPiece data(""); + + crypto::HMAC hmac(crypto::HMAC::SHA1); + ASSERT_TRUE(hmac.Init(NULL, 0)); + + unsigned char digest[kSHA1DigestSize]; + EXPECT_TRUE(hmac.Sign(data, digest, kSHA1DigestSize)); + EXPECT_EQ(0, memcmp(kExpectedDigest, digest, kSHA1DigestSize)); + + EXPECT_TRUE(hmac.Verify( + data, base::StringPiece(kExpectedDigest, kSHA1DigestSize))); +} diff --git a/crypto/hmac_win.cc b/crypto/hmac_win.cc new file mode 100644 index 0000000000..99b3a60d57 --- /dev/null +++ b/crypto/hmac_win.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/hmac.h" + +#include <windows.h> + +#include <algorithm> +#include <vector> + +#include "base/logging.h" +#include "crypto/scoped_capi_types.h" +#include "crypto/third_party/nss/chromium-blapi.h" +#include "crypto/third_party/nss/chromium-sha256.h" +#include "crypto/wincrypt_shim.h" + +namespace crypto { + +namespace { + +// Implementation of HMAC-SHA-256: +// +// SHA-256 is supported in Windows XP SP3 or later. We still need to support +// Windows XP SP2, so unfortunately we have to implement HMAC-SHA-256 here. + +enum { + SHA256_BLOCK_SIZE = 64 // Block size (in bytes) of the input to SHA-256. +}; + +// NSS doesn't accept size_t for text size, divide the data into smaller +// chunks as needed. +void Wrapped_SHA256_Update(SHA256Context* ctx, const unsigned char* text, + size_t text_len) { + const unsigned int kChunkSize = 1 << 30; + while (text_len > kChunkSize) { + SHA256_Update(ctx, text, kChunkSize); + text += kChunkSize; + text_len -= kChunkSize; + } + SHA256_Update(ctx, text, (unsigned int)text_len); +} + +// See FIPS 198: The Keyed-Hash Message Authentication Code (HMAC). +void ComputeHMACSHA256(const unsigned char* key, size_t key_len, + const unsigned char* text, size_t text_len, + unsigned char* output, size_t output_len) { + SHA256Context ctx; + + // Pre-process the key, if necessary. + unsigned char key0[SHA256_BLOCK_SIZE]; + if (key_len > SHA256_BLOCK_SIZE) { + SHA256_Begin(&ctx); + Wrapped_SHA256_Update(&ctx, key, key_len); + SHA256_End(&ctx, key0, NULL, SHA256_LENGTH); + memset(key0 + SHA256_LENGTH, 0, SHA256_BLOCK_SIZE - SHA256_LENGTH); + } else { + memcpy(key0, key, key_len); + if (key_len < SHA256_BLOCK_SIZE) + memset(key0 + key_len, 0, SHA256_BLOCK_SIZE - key_len); + } + + unsigned char padded_key[SHA256_BLOCK_SIZE]; + unsigned char inner_hash[SHA256_LENGTH]; + + // XOR key0 with ipad. + for (int i = 0; i < SHA256_BLOCK_SIZE; ++i) + padded_key[i] = key0[i] ^ 0x36; + + // Compute the inner hash. + SHA256_Begin(&ctx); + SHA256_Update(&ctx, padded_key, SHA256_BLOCK_SIZE); + Wrapped_SHA256_Update(&ctx, text, text_len); + SHA256_End(&ctx, inner_hash, NULL, SHA256_LENGTH); + + // XOR key0 with opad. + for (int i = 0; i < SHA256_BLOCK_SIZE; ++i) + padded_key[i] = key0[i] ^ 0x5c; + + // Compute the outer hash. + SHA256_Begin(&ctx); + SHA256_Update(&ctx, padded_key, SHA256_BLOCK_SIZE); + SHA256_Update(&ctx, inner_hash, SHA256_LENGTH); + SHA256_End(&ctx, output, NULL, (unsigned int) output_len); +} + +} // namespace + +struct HMACPlatformData { + ~HMACPlatformData() { + if (!raw_key_.empty()) { + SecureZeroMemory(&raw_key_[0], raw_key_.size()); + } + + // Destroy the key before releasing the provider. + key_.reset(); + } + + ScopedHCRYPTPROV provider_; + ScopedHCRYPTKEY key_; + + // For HMAC-SHA-256 only. + std::vector<unsigned char> raw_key_; +}; + +HMAC::HMAC(HashAlgorithm hash_alg) + : hash_alg_(hash_alg), plat_(new HMACPlatformData()) { + // Only SHA-1 and SHA-256 hash algorithms are supported now. + DCHECK(hash_alg_ == SHA1 || hash_alg_ == SHA256); +} + +bool HMAC::Init(const unsigned char* key, size_t key_length) { + if (plat_->provider_ || plat_->key_ || !plat_->raw_key_.empty()) { + // Init must not be called more than once on the same HMAC object. + NOTREACHED(); + return false; + } + + if (hash_alg_ == SHA256) { + plat_->raw_key_.assign(key, key + key_length); + return true; + } + + if (!CryptAcquireContext(plat_->provider_.receive(), NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + NOTREACHED(); + return false; + } + + // This code doesn't work on Win2k because PLAINTEXTKEYBLOB and + // CRYPT_IPSEC_HMAC_KEY are not supported on Windows 2000. PLAINTEXTKEYBLOB + // allows the import of an unencrypted key. For Win2k support, a cubmbersome + // exponent-of-one key procedure must be used: + // http://support.microsoft.com/kb/228786/en-us + // CRYPT_IPSEC_HMAC_KEY allows keys longer than 16 bytes. + + struct KeyBlob { + BLOBHEADER header; + DWORD key_size; + BYTE key_data[1]; + }; + size_t key_blob_size = std::max(offsetof(KeyBlob, key_data) + key_length, + sizeof(KeyBlob)); + std::vector<BYTE> key_blob_storage = std::vector<BYTE>(key_blob_size); + KeyBlob* key_blob = reinterpret_cast<KeyBlob*>(&key_blob_storage[0]); + key_blob->header.bType = PLAINTEXTKEYBLOB; + key_blob->header.bVersion = CUR_BLOB_VERSION; + key_blob->header.reserved = 0; + key_blob->header.aiKeyAlg = CALG_RC2; + key_blob->key_size = static_cast<DWORD>(key_length); + memcpy(key_blob->key_data, key, key_length); + + if (!CryptImportKey(plat_->provider_, &key_blob_storage[0], + (DWORD)key_blob_storage.size(), 0, + CRYPT_IPSEC_HMAC_KEY, plat_->key_.receive())) { + NOTREACHED(); + return false; + } + + // Destroy the copy of the key. + SecureZeroMemory(key_blob->key_data, key_length); + + return true; +} + +HMAC::~HMAC() { +} + +bool HMAC::Sign(const base::StringPiece& data, + unsigned char* digest, + size_t digest_length) const { + if (hash_alg_ == SHA256) { + if (plat_->raw_key_.empty()) + return false; + ComputeHMACSHA256(&plat_->raw_key_[0], plat_->raw_key_.size(), + reinterpret_cast<const unsigned char*>(data.data()), + data.size(), digest, digest_length); + return true; + } + + if (!plat_->provider_ || !plat_->key_) + return false; + + if (hash_alg_ != SHA1) { + NOTREACHED(); + return false; + } + + ScopedHCRYPTHASH hash; + if (!CryptCreateHash(plat_->provider_, CALG_HMAC, plat_->key_, 0, + hash.receive())) + return false; + + HMAC_INFO hmac_info; + memset(&hmac_info, 0, sizeof(hmac_info)); + hmac_info.HashAlgid = CALG_SHA1; + if (!CryptSetHashParam(hash, HP_HMAC_INFO, + reinterpret_cast<BYTE*>(&hmac_info), 0)) + return false; + + if (!CryptHashData(hash, reinterpret_cast<const BYTE*>(data.data()), + static_cast<DWORD>(data.size()), 0)) + return false; + + DWORD sha1_size = static_cast<DWORD>(digest_length); + return !!CryptGetHashParam(hash, HP_HASHVAL, digest, &sha1_size, 0); +} + +} // namespace crypto diff --git a/crypto/nss_key_util.cc b/crypto/nss_key_util.cc new file mode 100644 index 0000000000..77435fba36 --- /dev/null +++ b/crypto/nss_key_util.cc @@ -0,0 +1,163 @@ +// Copyright 2015 The Chromium 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 "crypto/nss_key_util.h" + +#include <cryptohi.h> +#include <keyhi.h> +#include <pk11pub.h> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "crypto/nss_util.h" + +#if defined(USE_NSS_CERTS) +#include <secmod.h> +#include "crypto/nss_util_internal.h" +#endif + +namespace crypto { + +namespace { + +#if defined(USE_NSS_CERTS) + +struct PublicKeyInfoDeleter { + inline void operator()(CERTSubjectPublicKeyInfo* spki) { + SECKEY_DestroySubjectPublicKeyInfo(spki); + } +}; + +typedef scoped_ptr<CERTSubjectPublicKeyInfo, PublicKeyInfoDeleter> + ScopedPublicKeyInfo; + +// Decodes |input| as a SubjectPublicKeyInfo and returns a SECItem containing +// the CKA_ID of that public key or nullptr on error. +ScopedSECItem MakeIDFromSPKI(const std::vector<uint8_t>& input) { + // First, decode and save the public key. + SECItem key_der; + key_der.type = siBuffer; + key_der.data = const_cast<unsigned char*>(vector_as_array(&input)); + key_der.len = input.size(); + + ScopedPublicKeyInfo spki(SECKEY_DecodeDERSubjectPublicKeyInfo(&key_der)); + if (!spki) + return nullptr; + + ScopedSECKEYPublicKey result(SECKEY_ExtractPublicKey(spki.get())); + if (!result) + return nullptr; + + // See pk11_MakeIDFromPublicKey from NSS. For now, only RSA keys are + // supported. + if (SECKEY_GetPublicKeyType(result.get()) != rsaKey) + return nullptr; + + return ScopedSECItem(PK11_MakeIDFromPubKey(&result->u.rsa.modulus)); +} + +#endif // defined(USE_NSS_CERTS) + +} // namespace + +bool GenerateRSAKeyPairNSS(PK11SlotInfo* slot, + uint16_t num_bits, + bool permanent, + ScopedSECKEYPublicKey* public_key, + ScopedSECKEYPrivateKey* private_key) { + DCHECK(slot); + + PK11RSAGenParams param; + param.keySizeInBits = num_bits; + param.pe = 65537L; + SECKEYPublicKey* public_key_raw = nullptr; + private_key->reset(PK11_GenerateKeyPair(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, + ¶m, &public_key_raw, permanent, + permanent /* sensitive */, nullptr)); + if (!*private_key) + return false; + + public_key->reset(public_key_raw); + return true; +} + +ScopedSECKEYPrivateKey ImportNSSKeyFromPrivateKeyInfo( + PK11SlotInfo* slot, + const std::vector<uint8_t>& input, + bool permanent) { + DCHECK(slot); + + ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + DCHECK(arena); + + // Excess data is illegal, but NSS silently accepts it, so first ensure that + // |input| consists of a single ASN.1 element. + SECItem input_item; + input_item.data = const_cast<unsigned char*>(vector_as_array(&input)); + input_item.len = input.size(); + SECItem der_private_key_info; + SECStatus rv = + SEC_QuickDERDecodeItem(arena.get(), &der_private_key_info, + SEC_ASN1_GET(SEC_AnyTemplate), &input_item); + if (rv != SECSuccess) + return nullptr; + + // Allow the private key to be used for key unwrapping, data decryption, + // and signature generation. + const unsigned int key_usage = + KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT | KU_DIGITAL_SIGNATURE; + SECKEYPrivateKey* key_raw = nullptr; + rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( + slot, &der_private_key_info, nullptr, nullptr, permanent, + permanent /* sensitive */, key_usage, &key_raw, nullptr); + if (rv != SECSuccess) + return nullptr; + return ScopedSECKEYPrivateKey(key_raw); +} + +#if defined(USE_NSS_CERTS) + +ScopedSECKEYPrivateKey FindNSSKeyFromPublicKeyInfo( + const std::vector<uint8_t>& input) { + EnsureNSSInit(); + + ScopedSECItem cka_id(MakeIDFromSPKI(input)); + if (!cka_id) + return nullptr; + + // Search all slots in all modules for the key with the given ID. + AutoSECMODListReadLock auto_lock; + const SECMODModuleList* head = SECMOD_GetDefaultModuleList(); + for (const SECMODModuleList* item = head; item != nullptr; + item = item->next) { + int slot_count = item->module->loaded ? item->module->slotCount : 0; + for (int i = 0; i < slot_count; i++) { + // Look for the key in slot |i|. + ScopedSECKEYPrivateKey key( + PK11_FindKeyByKeyID(item->module->slots[i], cka_id.get(), nullptr)); + if (key) + return key.Pass(); + } + } + + // The key wasn't found in any module. + return nullptr; +} + +ScopedSECKEYPrivateKey FindNSSKeyFromPublicKeyInfoInSlot( + const std::vector<uint8_t>& input, + PK11SlotInfo* slot) { + DCHECK(slot); + + ScopedSECItem cka_id(MakeIDFromSPKI(input)); + if (!cka_id) + return nullptr; + + return ScopedSECKEYPrivateKey( + PK11_FindKeyByKeyID(slot, cka_id.get(), nullptr)); +} + +#endif // defined(USE_NSS_CERTS) + +} // namespace crypto diff --git a/crypto/nss_key_util.h b/crypto/nss_key_util.h new file mode 100644 index 0000000000..12b948d25b --- /dev/null +++ b/crypto/nss_key_util.h @@ -0,0 +1,58 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_NSS_KEY_UTIL_H_ +#define CRYPTO_NSS_KEY_UTIL_H_ + +#include <stdint.h> + +#include <vector> + +#include "build/build_config.h" +#include "crypto/crypto_export.h" +#include "crypto/scoped_nss_types.h" + +typedef struct PK11SlotInfoStr PK11SlotInfo; + +namespace crypto { + +// Generates a new RSA keypair of size |num_bits| in |slot|. Returns true on +// success and false on failure. If |permanent| is true, the resulting key is +// permanent and is not exportable in plaintext form. +CRYPTO_EXPORT bool GenerateRSAKeyPairNSS( + PK11SlotInfo* slot, + uint16_t num_bits, + bool permanent, + ScopedSECKEYPublicKey* out_public_key, + ScopedSECKEYPrivateKey* out_private_key); + +// Imports a private key from |input| into |slot|. |input| is interpreted as a +// DER-encoded PrivateKeyInfo block from PKCS #8. Returns nullptr on error. If +// |permanent| is true, the resulting key is permanent and is not exportable in +// plaintext form. +CRYPTO_EXPORT ScopedSECKEYPrivateKey +ImportNSSKeyFromPrivateKeyInfo(PK11SlotInfo* slot, + const std::vector<uint8_t>& input, + bool permanent); + +#if defined(USE_NSS_CERTS) + +// Decodes |input| as a DER-encoded X.509 SubjectPublicKeyInfo and searches for +// the private key half in the key database. Returns the private key on success +// or nullptr on error. +CRYPTO_EXPORT ScopedSECKEYPrivateKey +FindNSSKeyFromPublicKeyInfo(const std::vector<uint8_t>& input); + +// Decodes |input| as a DER-encoded X.509 SubjectPublicKeyInfo and searches for +// the private key half in the slot specified by |slot|. Returns the private key +// on success or nullptr on error. +CRYPTO_EXPORT ScopedSECKEYPrivateKey +FindNSSKeyFromPublicKeyInfoInSlot(const std::vector<uint8_t>& input, + PK11SlotInfo* slot); + +#endif // defined(USE_NSS_CERTS) + +} // namespace crypto + +#endif // CRYPTO_NSS_KEY_UTIL_H_ diff --git a/crypto/nss_key_util_unittest.cc b/crypto/nss_key_util_unittest.cc new file mode 100644 index 0000000000..ff4d55a146 --- /dev/null +++ b/crypto/nss_key_util_unittest.cc @@ -0,0 +1,87 @@ +// Copyright 2015 The Chromium 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 "crypto/nss_key_util.h" + +#include <keyhi.h> +#include <pk11pub.h> + +#include <vector> + +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +class NSSKeyUtilTest : public testing::Test { + public: + void SetUp() override { + EnsureNSSInit(); + + internal_slot_.reset(PK11_GetInternalSlot()); + ASSERT_TRUE(internal_slot_); + } + + PK11SlotInfo* internal_slot() { return internal_slot_.get(); } + + private: + ScopedPK11Slot internal_slot_; +}; + +TEST_F(NSSKeyUtilTest, GenerateRSAKeyPairNSS) { + const int kKeySizeBits = 1024; + + ScopedSECKEYPublicKey public_key; + ScopedSECKEYPrivateKey private_key; + ASSERT_TRUE(GenerateRSAKeyPairNSS(internal_slot(), kKeySizeBits, + false /* not permanent */, &public_key, + &private_key)); + + EXPECT_EQ(rsaKey, SECKEY_GetPublicKeyType(public_key.get())); + EXPECT_EQ(rsaKey, SECKEY_GetPrivateKeyType(private_key.get())); + EXPECT_EQ((kKeySizeBits + 7) / 8, + PK11_GetPrivateModulusLen(private_key.get())); +} + +#if defined(USE_NSS_CERTS) +TEST_F(NSSKeyUtilTest, FindNSSKeyFromPublicKeyInfo) { + // Create an NSS keypair, which will put the keys in the user's NSSDB. + ScopedSECKEYPublicKey public_key; + ScopedSECKEYPrivateKey private_key; + ASSERT_TRUE(GenerateRSAKeyPairNSS(internal_slot(), 512, + false /* not permanent */, &public_key, + &private_key)); + + ScopedSECItem item(SECKEY_EncodeDERSubjectPublicKeyInfo(public_key.get())); + ASSERT_TRUE(item); + std::vector<uint8_t> public_key_der(item->data, item->data + item->len); + + ScopedSECKEYPrivateKey private_key2 = + FindNSSKeyFromPublicKeyInfo(public_key_der); + ASSERT_TRUE(private_key2); + EXPECT_EQ(private_key->pkcs11ID, private_key2->pkcs11ID); +} + +TEST_F(NSSKeyUtilTest, FailedFindNSSKeyFromPublicKeyInfo) { + // Create an NSS keypair, which will put the keys in the user's NSSDB. + ScopedSECKEYPublicKey public_key; + ScopedSECKEYPrivateKey private_key; + ASSERT_TRUE(GenerateRSAKeyPairNSS(internal_slot(), 512, + false /* not permanent */, &public_key, + &private_key)); + + ScopedSECItem item(SECKEY_EncodeDERSubjectPublicKeyInfo(public_key.get())); + ASSERT_TRUE(item); + std::vector<uint8_t> public_key_der(item->data, item->data + item->len); + + // Remove the keys from the DB, and make sure we can't find them again. + PK11_DestroyTokenObject(private_key->pkcs11Slot, private_key->pkcs11ID); + PK11_DestroyTokenObject(public_key->pkcs11Slot, public_key->pkcs11ID); + + EXPECT_FALSE(FindNSSKeyFromPublicKeyInfo(public_key_der)); +} +#endif // defined(USE_NSS_CERTS) + +} // namespace crypto diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc new file mode 100644 index 0000000000..125591c73f --- /dev/null +++ b/crypto/nss_util.cc @@ -0,0 +1,1131 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" + +#include <nss.h> +#include <pk11pub.h> +#include <plarena.h> +#include <prerror.h> +#include <prinit.h> +#include <prtime.h> +#include <secmod.h> + +#if defined(OS_OPENBSD) +#include <sys/mount.h> +#include <sys/param.h> +#endif + +#if defined(OS_CHROMEOS) +#include <dlfcn.h> +#endif + +#include <map> +#include <vector> + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/cpu.h" +#include "base/debug/alias.h" +#include "base/debug/stack_trace.h" +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/native_library.h" +#include "base/path_service.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_checker.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/worker_pool.h" +#include "build/build_config.h" + +// USE_NSS_CERTS means NSS is used for certificates and platform integration. +// This requires additional support to manage the platform certificate and key +// stores. +#if defined(USE_NSS_CERTS) +#include "base/synchronization/lock.h" +#include "crypto/nss_crypto_module_delegate.h" +#endif // defined(USE_NSS_CERTS) + +namespace crypto { + +namespace { + +#if defined(OS_CHROMEOS) +const char kUserNSSDatabaseName[] = "UserNSSDB"; + +// Constants for loading the Chrome OS TPM-backed PKCS #11 library. +const char kChapsModuleName[] = "Chaps"; +const char kChapsPath[] = "libchaps.so"; + +// Fake certificate authority database used for testing. +static const base::FilePath::CharType kReadOnlyCertDB[] = + FILE_PATH_LITERAL("/etc/fake_root_ca/nssdb"); +#endif // defined(OS_CHROMEOS) + +std::string GetNSSErrorMessage() { + std::string result; + if (PR_GetErrorTextLength()) { + scoped_ptr<char[]> error_text(new char[PR_GetErrorTextLength() + 1]); + PRInt32 copied = PR_GetErrorText(error_text.get()); + result = std::string(error_text.get(), copied); + } else { + result = base::StringPrintf("NSS error code: %d", PR_GetError()); + } + return result; +} + +#if defined(USE_NSS_CERTS) +#if !defined(OS_CHROMEOS) +base::FilePath GetDefaultConfigDirectory() { + base::FilePath dir; + PathService::Get(base::DIR_HOME, &dir); + if (dir.empty()) { + LOG(ERROR) << "Failed to get home directory."; + return dir; + } + dir = dir.AppendASCII(".pki").AppendASCII("nssdb"); + if (!base::CreateDirectory(dir)) { + LOG(ERROR) << "Failed to create " << dir.value() << " directory."; + dir.clear(); + } + DVLOG(2) << "DefaultConfigDirectory: " << dir.value(); + return dir; +} +#endif // !defined(IS_CHROMEOS) + +// On non-Chrome OS platforms, return the default config directory. On Chrome OS +// test images, return a read-only directory with fake root CA certs (which are +// used by the local Google Accounts server mock we use when testing our login +// code). On Chrome OS non-test images (where the read-only directory doesn't +// exist), return an empty path. +base::FilePath GetInitialConfigDirectory() { +#if defined(OS_CHROMEOS) + base::FilePath database_dir = base::FilePath(kReadOnlyCertDB); + if (!base::PathExists(database_dir)) + database_dir.clear(); + return database_dir; +#else + return GetDefaultConfigDirectory(); +#endif // defined(OS_CHROMEOS) +} + +// This callback for NSS forwards all requests to a caller-specified +// CryptoModuleBlockingPasswordDelegate object. +char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) { + crypto::CryptoModuleBlockingPasswordDelegate* delegate = + reinterpret_cast<crypto::CryptoModuleBlockingPasswordDelegate*>(arg); + if (delegate) { + bool cancelled = false; + std::string password = delegate->RequestPassword(PK11_GetTokenName(slot), + retry != PR_FALSE, + &cancelled); + if (cancelled) + return NULL; + char* result = PORT_Strdup(password.c_str()); + password.replace(0, password.size(), password.size(), 0); + return result; + } + DLOG(ERROR) << "PK11 password requested with NULL arg"; + return NULL; +} + +// NSS creates a local cache of the sqlite database if it detects that the +// filesystem the database is on is much slower than the local disk. The +// detection doesn't work with the latest versions of sqlite, such as 3.6.22 +// (NSS bug https://bugzilla.mozilla.org/show_bug.cgi?id=578561). So we set +// the NSS environment variable NSS_SDB_USE_CACHE to "yes" to override NSS's +// detection when database_dir is on NFS. See http://crbug.com/48585. +// +// TODO(wtc): port this function to other USE_NSS_CERTS platforms. It is +// defined only for OS_LINUX and OS_OPENBSD simply because the statfs structure +// is OS-specific. +// +// Because this function sets an environment variable it must be run before we +// go multi-threaded. +void UseLocalCacheOfNSSDatabaseIfNFS(const base::FilePath& database_dir) { + bool db_on_nfs = false; +#if defined(OS_LINUX) + base::FileSystemType fs_type = base::FILE_SYSTEM_UNKNOWN; + if (base::GetFileSystemType(database_dir, &fs_type)) + db_on_nfs = (fs_type == base::FILE_SYSTEM_NFS); +#elif defined(OS_OPENBSD) + struct statfs buf; + if (statfs(database_dir.value().c_str(), &buf) == 0) + db_on_nfs = (strcmp(buf.f_fstypename, MOUNT_NFS) == 0); +#else + NOTIMPLEMENTED(); +#endif + + if (db_on_nfs) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + static const char kUseCacheEnvVar[] = "NSS_SDB_USE_CACHE"; + if (!env->HasVar(kUseCacheEnvVar)) + env->SetVar(kUseCacheEnvVar, "yes"); + } +} + +#endif // defined(USE_NSS_CERTS) + +// A singleton to initialize/deinitialize NSPR. +// Separate from the NSS singleton because we initialize NSPR on the UI thread. +// Now that we're leaking the singleton, we could merge back with the NSS +// singleton. +class NSPRInitSingleton { + private: + friend struct base::DefaultLazyInstanceTraits<NSPRInitSingleton>; + + NSPRInitSingleton() { + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + } + + // NOTE(willchan): We don't actually execute this code since we leak NSS to + // prevent non-joinable threads from using NSS after it's already been shut + // down. + ~NSPRInitSingleton() { + PL_ArenaFinish(); + PRStatus prstatus = PR_Cleanup(); + if (prstatus != PR_SUCCESS) + LOG(ERROR) << "PR_Cleanup failed; was NSPR initialized on wrong thread?"; + } +}; + +base::LazyInstance<NSPRInitSingleton>::Leaky + g_nspr_singleton = LAZY_INSTANCE_INITIALIZER; + +// Force a crash with error info on NSS_NoDB_Init failure. +void CrashOnNSSInitFailure() { + int nss_error = PR_GetError(); + int os_error = PR_GetOSError(); + base::debug::Alias(&nss_error); + base::debug::Alias(&os_error); + LOG(ERROR) << "Error initializing NSS without a persistent database: " + << GetNSSErrorMessage(); + LOG(FATAL) << "nss_error=" << nss_error << ", os_error=" << os_error; +} + +#if defined(OS_CHROMEOS) +class ChromeOSUserData { + public: + explicit ChromeOSUserData(ScopedPK11Slot public_slot) + : public_slot_(public_slot.Pass()), + private_slot_initialization_started_(false) {} + ~ChromeOSUserData() { + if (public_slot_) { + SECStatus status = SECMOD_CloseUserDB(public_slot_.get()); + if (status != SECSuccess) + PLOG(ERROR) << "SECMOD_CloseUserDB failed: " << PORT_GetError(); + } + } + + ScopedPK11Slot GetPublicSlot() { + return ScopedPK11Slot( + public_slot_ ? PK11_ReferenceSlot(public_slot_.get()) : NULL); + } + + ScopedPK11Slot GetPrivateSlot( + const base::Callback<void(ScopedPK11Slot)>& callback) { + if (private_slot_) + return ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())); + if (!callback.is_null()) + tpm_ready_callback_list_.push_back(callback); + return ScopedPK11Slot(); + } + + void SetPrivateSlot(ScopedPK11Slot private_slot) { + DCHECK(!private_slot_); + private_slot_ = private_slot.Pass(); + + SlotReadyCallbackList callback_list; + callback_list.swap(tpm_ready_callback_list_); + for (SlotReadyCallbackList::iterator i = callback_list.begin(); + i != callback_list.end(); + ++i) { + (*i).Run(ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()))); + } + } + + bool private_slot_initialization_started() const { + return private_slot_initialization_started_; + } + + void set_private_slot_initialization_started() { + private_slot_initialization_started_ = true; + } + + private: + ScopedPK11Slot public_slot_; + ScopedPK11Slot private_slot_; + + bool private_slot_initialization_started_; + + typedef std::vector<base::Callback<void(ScopedPK11Slot)> > + SlotReadyCallbackList; + SlotReadyCallbackList tpm_ready_callback_list_; +}; + +class ScopedChapsLoadFixup { + public: + ScopedChapsLoadFixup(); + ~ScopedChapsLoadFixup(); + + private: +#if defined(COMPONENT_BUILD) + void *chaps_handle_; +#endif +}; + +#if defined(COMPONENT_BUILD) + +ScopedChapsLoadFixup::ScopedChapsLoadFixup() { + // HACK: libchaps links the system protobuf and there are symbol conflicts + // with the bundled copy. Load chaps with RTLD_DEEPBIND to workaround. + chaps_handle_ = dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND); +} + +ScopedChapsLoadFixup::~ScopedChapsLoadFixup() { + // LoadModule() will have taken a 2nd reference. + if (chaps_handle_) + dlclose(chaps_handle_); +} + +#else + +ScopedChapsLoadFixup::ScopedChapsLoadFixup() {} +ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {} + +#endif // defined(COMPONENT_BUILD) +#endif // defined(OS_CHROMEOS) + +class NSSInitSingleton { + public: +#if defined(OS_CHROMEOS) + // Used with PostTaskAndReply to pass handles to worker thread and back. + struct TPMModuleAndSlot { + explicit TPMModuleAndSlot(SECMODModule* init_chaps_module) + : chaps_module(init_chaps_module) {} + SECMODModule* chaps_module; + crypto::ScopedPK11Slot tpm_slot; + }; + + ScopedPK11Slot OpenPersistentNSSDBForPath(const std::string& db_name, + const base::FilePath& path) { + DCHECK(thread_checker_.CalledOnValidThread()); + // NSS is allowed to do IO on the current thread since dispatching + // to a dedicated thread would still have the affect of blocking + // the current thread, due to NSS's internal locking requirements + base::ThreadRestrictions::ScopedAllowIO allow_io; + + base::FilePath nssdb_path = path.AppendASCII(".pki").AppendASCII("nssdb"); + if (!base::CreateDirectory(nssdb_path)) { + LOG(ERROR) << "Failed to create " << nssdb_path.value() << " directory."; + return ScopedPK11Slot(); + } + return OpenSoftwareNSSDB(nssdb_path, db_name); + } + + void EnableTPMTokenForNSS() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // If this gets set, then we'll use the TPM for certs with + // private keys, otherwise we'll fall back to the software + // implementation. + tpm_token_enabled_for_nss_ = true; + } + + bool IsTPMTokenEnabledForNSS() { + DCHECK(thread_checker_.CalledOnValidThread()); + return tpm_token_enabled_for_nss_; + } + + void InitializeTPMTokenAndSystemSlot( + int system_slot_id, + const base::Callback<void(bool)>& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + // Should not be called while there is already an initialization in + // progress. + DCHECK(!initializing_tpm_token_); + // If EnableTPMTokenForNSS hasn't been called, return false. + if (!tpm_token_enabled_for_nss_) { + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, false)); + return; + } + + // If everything is already initialized, then return true. + // Note that only |tpm_slot_| is checked, since |chaps_module_| could be + // NULL in tests while |tpm_slot_| has been set to the test DB. + if (tpm_slot_) { + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, true)); + return; + } + + // Note that a reference is not taken to chaps_module_. This is safe since + // NSSInitSingleton is Leaky, so the reference it holds is never released. + scoped_ptr<TPMModuleAndSlot> tpm_args(new TPMModuleAndSlot(chaps_module_)); + TPMModuleAndSlot* tpm_args_ptr = tpm_args.get(); + if (base::WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread, + system_slot_id, + tpm_args_ptr), + base::Bind(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot, + base::Unretained(this), // NSSInitSingleton is leaky + callback, + base::Passed(&tpm_args)), + true /* task_is_slow */ + )) { + initializing_tpm_token_ = true; + } else { + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, false)); + } + } + + static void InitializeTPMTokenOnWorkerThread(CK_SLOT_ID token_slot_id, + TPMModuleAndSlot* tpm_args) { + // This tries to load the Chaps module so NSS can talk to the hardware + // TPM. + if (!tpm_args->chaps_module) { + ScopedChapsLoadFixup chaps_loader; + + DVLOG(3) << "Loading chaps..."; + tpm_args->chaps_module = LoadModule( + kChapsModuleName, + kChapsPath, + // For more details on these parameters, see: + // https://developer.mozilla.org/en/PKCS11_Module_Specs + // slotFlags=[PublicCerts] -- Certificates and public keys can be + // read from this slot without requiring a call to C_Login. + // askpw=only -- Only authenticate to the token when necessary. + "NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\""); + } + if (tpm_args->chaps_module) { + tpm_args->tpm_slot = + GetTPMSlotForIdOnWorkerThread(tpm_args->chaps_module, token_slot_id); + } + } + + void OnInitializedTPMTokenAndSystemSlot( + const base::Callback<void(bool)>& callback, + scoped_ptr<TPMModuleAndSlot> tpm_args) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(2) << "Loaded chaps: " << !!tpm_args->chaps_module + << ", got tpm slot: " << !!tpm_args->tpm_slot; + + chaps_module_ = tpm_args->chaps_module; + tpm_slot_ = tpm_args->tpm_slot.Pass(); + if (!chaps_module_ && test_system_slot_) { + // chromeos_unittests try to test the TPM initialization process. If we + // have a test DB open, pretend that it is the TPM slot. + tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get())); + } + initializing_tpm_token_ = false; + + if (tpm_slot_) + RunAndClearTPMReadyCallbackList(); + + callback.Run(!!tpm_slot_); + } + + void RunAndClearTPMReadyCallbackList() { + TPMReadyCallbackList callback_list; + callback_list.swap(tpm_ready_callback_list_); + for (TPMReadyCallbackList::iterator i = callback_list.begin(); + i != callback_list.end(); + ++i) { + i->Run(); + } + } + + bool IsTPMTokenReady(const base::Closure& callback) { + if (!callback.is_null()) { + // Cannot DCHECK in the general case yet, but since the callback is + // a new addition to the API, DCHECK to make sure at least the new uses + // don't regress. + DCHECK(thread_checker_.CalledOnValidThread()); + } else if (!thread_checker_.CalledOnValidThread()) { + // TODO(mattm): Change to DCHECK when callers have been fixed. + DVLOG(1) << "Called on wrong thread.\n" + << base::debug::StackTrace().ToString(); + } + + if (tpm_slot_) + return true; + + if (!callback.is_null()) + tpm_ready_callback_list_.push_back(callback); + + return false; + } + + // Note that CK_SLOT_ID is an unsigned long, but cryptohome gives us the slot + // id as an int. This should be safe since this is only used with chaps, which + // we also control. + static crypto::ScopedPK11Slot GetTPMSlotForIdOnWorkerThread( + SECMODModule* chaps_module, + CK_SLOT_ID slot_id) { + DCHECK(chaps_module); + + DVLOG(3) << "Poking chaps module."; + SECStatus rv = SECMOD_UpdateSlotList(chaps_module); + if (rv != SECSuccess) + PLOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError(); + + PK11SlotInfo* slot = SECMOD_LookupSlot(chaps_module->moduleID, slot_id); + if (!slot) + LOG(ERROR) << "TPM slot " << slot_id << " not found."; + return crypto::ScopedPK11Slot(slot); + } + + bool InitializeNSSForChromeOSUser(const std::string& username_hash, + const base::FilePath& path) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()) { + // This user already exists in our mapping. + DVLOG(2) << username_hash << " already initialized."; + return false; + } + + DVLOG(2) << "Opening NSS DB " << path.value(); + std::string db_name = base::StringPrintf( + "%s %s", kUserNSSDatabaseName, username_hash.c_str()); + ScopedPK11Slot public_slot(OpenPersistentNSSDBForPath(db_name, path)); + chromeos_user_map_[username_hash] = + new ChromeOSUserData(public_slot.Pass()); + return true; + } + + bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()); + + return !chromeos_user_map_[username_hash] + ->private_slot_initialization_started(); + } + + void WillInitializeTPMForChromeOSUser(const std::string& username_hash) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()); + + chromeos_user_map_[username_hash] + ->set_private_slot_initialization_started(); + } + + void InitializeTPMForChromeOSUser(const std::string& username_hash, + CK_SLOT_ID slot_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()); + DCHECK(chromeos_user_map_[username_hash]-> + private_slot_initialization_started()); + + if (!chaps_module_) + return; + + // Note that a reference is not taken to chaps_module_. This is safe since + // NSSInitSingleton is Leaky, so the reference it holds is never released. + scoped_ptr<TPMModuleAndSlot> tpm_args(new TPMModuleAndSlot(chaps_module_)); + TPMModuleAndSlot* tpm_args_ptr = tpm_args.get(); + base::WorkerPool::PostTaskAndReply( + FROM_HERE, + base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread, + slot_id, + tpm_args_ptr), + base::Bind(&NSSInitSingleton::OnInitializedTPMForChromeOSUser, + base::Unretained(this), // NSSInitSingleton is leaky + username_hash, + base::Passed(&tpm_args)), + true /* task_is_slow */ + ); + } + + void OnInitializedTPMForChromeOSUser(const std::string& username_hash, + scoped_ptr<TPMModuleAndSlot> tpm_args) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(2) << "Got tpm slot for " << username_hash << " " + << !!tpm_args->tpm_slot; + chromeos_user_map_[username_hash]->SetPrivateSlot( + tpm_args->tpm_slot.Pass()); + } + + void InitializePrivateSoftwareSlotForChromeOSUser( + const std::string& username_hash) { + DCHECK(thread_checker_.CalledOnValidThread()); + VLOG(1) << "using software private slot for " << username_hash; + DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()); + DCHECK(chromeos_user_map_[username_hash]-> + private_slot_initialization_started()); + + chromeos_user_map_[username_hash]->SetPrivateSlot( + chromeos_user_map_[username_hash]->GetPublicSlot()); + } + + ScopedPK11Slot GetPublicSlotForChromeOSUser( + const std::string& username_hash) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (username_hash.empty()) { + DVLOG(2) << "empty username_hash"; + return ScopedPK11Slot(); + } + + if (chromeos_user_map_.find(username_hash) == chromeos_user_map_.end()) { + LOG(ERROR) << username_hash << " not initialized."; + return ScopedPK11Slot(); + } + return chromeos_user_map_[username_hash]->GetPublicSlot(); + } + + ScopedPK11Slot GetPrivateSlotForChromeOSUser( + const std::string& username_hash, + const base::Callback<void(ScopedPK11Slot)>& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (username_hash.empty()) { + DVLOG(2) << "empty username_hash"; + if (!callback.is_null()) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(callback, base::Passed(ScopedPK11Slot()))); + } + return ScopedPK11Slot(); + } + + DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()); + + return chromeos_user_map_[username_hash]->GetPrivateSlot(callback); + } + + void CloseChromeOSUserForTesting(const std::string& username_hash) { + DCHECK(thread_checker_.CalledOnValidThread()); + ChromeOSUserMap::iterator i = chromeos_user_map_.find(username_hash); + DCHECK(i != chromeos_user_map_.end()); + delete i->second; + chromeos_user_map_.erase(i); + } + + void SetSystemKeySlotForTesting(ScopedPK11Slot slot) { + // Ensure that a previous value of test_system_slot_ is not overwritten. + // Unsetting, i.e. setting a NULL, however is allowed. + DCHECK(!slot || !test_system_slot_); + test_system_slot_ = slot.Pass(); + if (test_system_slot_) { + tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get())); + RunAndClearTPMReadyCallbackList(); + } else { + tpm_slot_.reset(); + } + } +#endif // defined(OS_CHROMEOS) + +#if !defined(OS_CHROMEOS) + PK11SlotInfo* GetPersistentNSSKeySlot() { + // TODO(mattm): Change to DCHECK when callers have been fixed. + if (!thread_checker_.CalledOnValidThread()) { + DVLOG(1) << "Called on wrong thread.\n" + << base::debug::StackTrace().ToString(); + } + + return PK11_GetInternalKeySlot(); + } +#endif + +#if defined(OS_CHROMEOS) + void GetSystemNSSKeySlotCallback( + const base::Callback<void(ScopedPK11Slot)>& callback) { + callback.Run(ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get()))); + } + + ScopedPK11Slot GetSystemNSSKeySlot( + const base::Callback<void(ScopedPK11Slot)>& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + // TODO(mattm): chromeos::TPMTokenloader always calls + // InitializeTPMTokenAndSystemSlot with slot 0. If the system slot is + // disabled, tpm_slot_ will be the first user's slot instead. Can that be + // detected and return NULL instead? + + base::Closure wrapped_callback; + if (!callback.is_null()) { + wrapped_callback = + base::Bind(&NSSInitSingleton::GetSystemNSSKeySlotCallback, + base::Unretained(this) /* singleton is leaky */, + callback); + } + if (IsTPMTokenReady(wrapped_callback)) + return ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())); + return ScopedPK11Slot(); + } +#endif + +#if defined(USE_NSS_CERTS) + base::Lock* write_lock() { + return &write_lock_; + } +#endif // defined(USE_NSS_CERTS) + + // This method is used to force NSS to be initialized without a DB. + // Call this method before NSSInitSingleton() is constructed. + static void ForceNoDBInit() { + force_nodb_init_ = true; + } + + private: + friend struct base::DefaultLazyInstanceTraits<NSSInitSingleton>; + + NSSInitSingleton() + : tpm_token_enabled_for_nss_(false), + initializing_tpm_token_(false), + chaps_module_(NULL), + root_(NULL) { + // It's safe to construct on any thread, since LazyInstance will prevent any + // other threads from accessing until the constructor is done. + thread_checker_.DetachFromThread(); + + DisableAESNIIfNeeded(); + + EnsureNSPRInit(); + + // We *must* have NSS >= 3.14.3. + static_assert( + (NSS_VMAJOR == 3 && NSS_VMINOR == 14 && NSS_VPATCH >= 3) || + (NSS_VMAJOR == 3 && NSS_VMINOR > 14) || + (NSS_VMAJOR > 3), + "nss version check failed"); + // Also check the run-time NSS version. + // NSS_VersionCheck is a >= check, not strict equality. + if (!NSS_VersionCheck("3.14.3")) { + LOG(FATAL) << "NSS_VersionCheck(\"3.14.3\") failed. NSS >= 3.14.3 is " + "required. Please upgrade to the latest NSS, and if you " + "still get this error, contact your distribution " + "maintainer."; + } + + SECStatus status = SECFailure; + bool nodb_init = force_nodb_init_; + +#if !defined(USE_NSS_CERTS) + // Use the system certificate store, so initialize NSS without database. + nodb_init = true; +#endif + + if (nodb_init) { + status = NSS_NoDB_Init(NULL); + if (status != SECSuccess) { + CrashOnNSSInitFailure(); + return; + } +#if defined(OS_IOS) + root_ = InitDefaultRootCerts(); +#endif // defined(OS_IOS) + } else { +#if defined(USE_NSS_CERTS) + base::FilePath database_dir = GetInitialConfigDirectory(); + if (!database_dir.empty()) { + // This duplicates the work which should have been done in + // EarlySetupForNSSInit. However, this function is idempotent so + // there's no harm done. + UseLocalCacheOfNSSDatabaseIfNFS(database_dir); + + // Initialize with a persistent database (likely, ~/.pki/nssdb). + // Use "sql:" which can be shared by multiple processes safely. + std::string nss_config_dir = + base::StringPrintf("sql:%s", database_dir.value().c_str()); +#if defined(OS_CHROMEOS) + status = NSS_Init(nss_config_dir.c_str()); +#else + status = NSS_InitReadWrite(nss_config_dir.c_str()); +#endif + if (status != SECSuccess) { + LOG(ERROR) << "Error initializing NSS with a persistent " + "database (" << nss_config_dir + << "): " << GetNSSErrorMessage(); + } + } + if (status != SECSuccess) { + VLOG(1) << "Initializing NSS without a persistent database."; + status = NSS_NoDB_Init(NULL); + if (status != SECSuccess) { + CrashOnNSSInitFailure(); + return; + } + } + + PK11_SetPasswordFunc(PKCS11PasswordFunc); + + // If we haven't initialized the password for the NSS databases, + // initialize an empty-string password so that we don't need to + // log in. + PK11SlotInfo* slot = PK11_GetInternalKeySlot(); + if (slot) { + // PK11_InitPin may write to the keyDB, but no other thread can use NSS + // yet, so we don't need to lock. + if (PK11_NeedUserInit(slot)) + PK11_InitPin(slot, NULL, NULL); + PK11_FreeSlot(slot); + } + + root_ = InitDefaultRootCerts(); +#endif // defined(USE_NSS_CERTS) + } + + // Disable MD5 certificate signatures. (They are disabled by default in + // NSS 3.14.) + NSS_SetAlgorithmPolicy(SEC_OID_MD5, 0, NSS_USE_ALG_IN_CERT_SIGNATURE); + NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, + 0, NSS_USE_ALG_IN_CERT_SIGNATURE); + } + + // NOTE(willchan): We don't actually execute this code since we leak NSS to + // prevent non-joinable threads from using NSS after it's already been shut + // down. + ~NSSInitSingleton() { +#if defined(OS_CHROMEOS) + STLDeleteValues(&chromeos_user_map_); +#endif + tpm_slot_.reset(); + if (root_) { + SECMOD_UnloadUserModule(root_); + SECMOD_DestroyModule(root_); + root_ = NULL; + } + if (chaps_module_) { + SECMOD_UnloadUserModule(chaps_module_); + SECMOD_DestroyModule(chaps_module_); + chaps_module_ = NULL; + } + + SECStatus status = NSS_Shutdown(); + if (status != SECSuccess) { + // We VLOG(1) because this failure is relatively harmless (leaking, but + // we're shutting down anyway). + VLOG(1) << "NSS_Shutdown failed; see http://crbug.com/4609"; + } + } + +#if defined(USE_NSS_CERTS) || defined(OS_IOS) + // Load nss's built-in root certs. + SECMODModule* InitDefaultRootCerts() { + SECMODModule* root = LoadModule("Root Certs", "libnssckbi.so", NULL); + if (root) + return root; + + // Aw, snap. Can't find/load root cert shared library. + // This will make it hard to talk to anybody via https. + // TODO(mattm): Re-add the NOTREACHED here when crbug.com/310972 is fixed. + return NULL; + } + + // Load the given module for this NSS session. + static SECMODModule* LoadModule(const char* name, + const char* library_path, + const char* params) { + std::string modparams = base::StringPrintf( + "name=\"%s\" library=\"%s\" %s", + name, library_path, params ? params : ""); + + // Shouldn't need to const_cast here, but SECMOD doesn't properly + // declare input string arguments as const. Bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=642546 was filed + // on NSS codebase to address this. + SECMODModule* module = SECMOD_LoadUserModule( + const_cast<char*>(modparams.c_str()), NULL, PR_FALSE); + if (!module) { + LOG(ERROR) << "Error loading " << name << " module into NSS: " + << GetNSSErrorMessage(); + return NULL; + } + if (!module->loaded) { + LOG(ERROR) << "After loading " << name << ", loaded==false: " + << GetNSSErrorMessage(); + SECMOD_DestroyModule(module); + return NULL; + } + return module; + } +#endif + + static void DisableAESNIIfNeeded() { + if (NSS_VersionCheck("3.15") && !NSS_VersionCheck("3.15.4")) { + // Some versions of NSS have a bug that causes AVX instructions to be + // used without testing whether XSAVE is enabled by the operating system. + // In order to work around this, we disable AES-NI in NSS when we find + // that |has_avx()| is false (which includes the XSAVE test). See + // https://bugzilla.mozilla.org/show_bug.cgi?id=940794 + base::CPU cpu; + + if (cpu.has_avx_hardware() && !cpu.has_avx()) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + env->SetVar("NSS_DISABLE_HW_AES", "1"); + } + } + } + + // If this is set to true NSS is forced to be initialized without a DB. + static bool force_nodb_init_; + + bool tpm_token_enabled_for_nss_; + bool initializing_tpm_token_; + typedef std::vector<base::Closure> TPMReadyCallbackList; + TPMReadyCallbackList tpm_ready_callback_list_; + SECMODModule* chaps_module_; + crypto::ScopedPK11Slot tpm_slot_; + SECMODModule* root_; +#if defined(OS_CHROMEOS) + typedef std::map<std::string, ChromeOSUserData*> ChromeOSUserMap; + ChromeOSUserMap chromeos_user_map_; + ScopedPK11Slot test_system_slot_; +#endif +#if defined(USE_NSS_CERTS) + // TODO(davidben): When https://bugzilla.mozilla.org/show_bug.cgi?id=564011 + // is fixed, we will no longer need the lock. + base::Lock write_lock_; +#endif // defined(USE_NSS_CERTS) + + base::ThreadChecker thread_checker_; +}; + +// static +bool NSSInitSingleton::force_nodb_init_ = false; + +base::LazyInstance<NSSInitSingleton>::Leaky + g_nss_singleton = LAZY_INSTANCE_INITIALIZER; +} // namespace + +#if defined(USE_NSS_CERTS) +ScopedPK11Slot OpenSoftwareNSSDB(const base::FilePath& path, + const std::string& description) { + const std::string modspec = + base::StringPrintf("configDir='sql:%s' tokenDescription='%s'", + path.value().c_str(), + description.c_str()); + PK11SlotInfo* db_slot = SECMOD_OpenUserDB(modspec.c_str()); + if (db_slot) { + if (PK11_NeedUserInit(db_slot)) + PK11_InitPin(db_slot, NULL, NULL); + } else { + LOG(ERROR) << "Error opening persistent database (" << modspec + << "): " << GetNSSErrorMessage(); + } + return ScopedPK11Slot(db_slot); +} + +void EarlySetupForNSSInit() { + base::FilePath database_dir = GetInitialConfigDirectory(); + if (!database_dir.empty()) + UseLocalCacheOfNSSDatabaseIfNFS(database_dir); +} +#endif + +void EnsureNSPRInit() { + g_nspr_singleton.Get(); +} + +void InitNSSSafely() { + // We might fork, but we haven't loaded any security modules. + DisableNSSForkCheck(); + // If we're sandboxed, we shouldn't be able to open user security modules, + // but it's more correct to tell NSS to not even try. + // Loading user security modules would have security implications. + ForceNSSNoDBInit(); + // Initialize NSS. + EnsureNSSInit(); +} + +void EnsureNSSInit() { + // Initializing SSL causes us to do blocking IO. + // Temporarily allow it until we fix + // http://code.google.com/p/chromium/issues/detail?id=59847 + base::ThreadRestrictions::ScopedAllowIO allow_io; + g_nss_singleton.Get(); +} + +void ForceNSSNoDBInit() { + NSSInitSingleton::ForceNoDBInit(); +} + +void DisableNSSForkCheck() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + env->SetVar("NSS_STRICT_NOFORK", "DISABLED"); +} + +void LoadNSSLibraries() { + // Some NSS libraries are linked dynamically so load them here. +#if defined(USE_NSS_CERTS) + // Try to search for multiple directories to load the libraries. + std::vector<base::FilePath> paths; + + // Use relative path to Search PATH for the library files. + paths.push_back(base::FilePath()); + + // For Debian derivatives NSS libraries are located here. + paths.push_back(base::FilePath("/usr/lib/nss")); + + // Ubuntu 11.10 (Oneiric) and Debian Wheezy place the libraries here. +#if defined(ARCH_CPU_X86_64) + paths.push_back(base::FilePath("/usr/lib/x86_64-linux-gnu/nss")); +#elif defined(ARCH_CPU_X86) + paths.push_back(base::FilePath("/usr/lib/i386-linux-gnu/nss")); +#elif defined(ARCH_CPU_ARMEL) +#if defined(__ARM_PCS_VFP) + paths.push_back(base::FilePath("/usr/lib/arm-linux-gnueabihf/nss")); +#else + paths.push_back(base::FilePath("/usr/lib/arm-linux-gnueabi/nss")); +#endif // defined(__ARM_PCS_VFP) +#elif defined(ARCH_CPU_MIPSEL) + paths.push_back(base::FilePath("/usr/lib/mipsel-linux-gnu/nss")); +#endif // defined(ARCH_CPU_X86_64) + + // A list of library files to load. + std::vector<std::string> libs; + libs.push_back("libsoftokn3.so"); + libs.push_back("libfreebl3.so"); + + // For each combination of library file and path, check for existence and + // then load. + size_t loaded = 0; + for (size_t i = 0; i < libs.size(); ++i) { + for (size_t j = 0; j < paths.size(); ++j) { + base::FilePath path = paths[j].Append(libs[i]); + base::NativeLibrary lib = base::LoadNativeLibrary(path, NULL); + if (lib) { + ++loaded; + break; + } + } + } + + if (loaded == libs.size()) { + VLOG(3) << "NSS libraries loaded."; + } else { + LOG(ERROR) << "Failed to load NSS libraries."; + } +#endif // defined(USE_NSS_CERTS) +} + +bool CheckNSSVersion(const char* version) { + return !!NSS_VersionCheck(version); +} + +#if defined(USE_NSS_CERTS) +base::Lock* GetNSSWriteLock() { + return g_nss_singleton.Get().write_lock(); +} + +AutoNSSWriteLock::AutoNSSWriteLock() : lock_(GetNSSWriteLock()) { + // May be NULL if the lock is not needed in our version of NSS. + if (lock_) + lock_->Acquire(); +} + +AutoNSSWriteLock::~AutoNSSWriteLock() { + if (lock_) { + lock_->AssertAcquired(); + lock_->Release(); + } +} + +AutoSECMODListReadLock::AutoSECMODListReadLock() + : lock_(SECMOD_GetDefaultModuleListLock()) { + SECMOD_GetReadLock(lock_); + } + +AutoSECMODListReadLock::~AutoSECMODListReadLock() { + SECMOD_ReleaseReadLock(lock_); +} +#endif // defined(USE_NSS_CERTS) + +#if defined(OS_CHROMEOS) +ScopedPK11Slot GetSystemNSSKeySlot( + const base::Callback<void(ScopedPK11Slot)>& callback) { + return g_nss_singleton.Get().GetSystemNSSKeySlot(callback); +} + +void SetSystemKeySlotForTesting(ScopedPK11Slot slot) { + g_nss_singleton.Get().SetSystemKeySlotForTesting(slot.Pass()); +} + +void EnableTPMTokenForNSS() { + g_nss_singleton.Get().EnableTPMTokenForNSS(); +} + +bool IsTPMTokenEnabledForNSS() { + return g_nss_singleton.Get().IsTPMTokenEnabledForNSS(); +} + +bool IsTPMTokenReady(const base::Closure& callback) { + return g_nss_singleton.Get().IsTPMTokenReady(callback); +} + +void InitializeTPMTokenAndSystemSlot( + int token_slot_id, + const base::Callback<void(bool)>& callback) { + g_nss_singleton.Get().InitializeTPMTokenAndSystemSlot(token_slot_id, + callback); +} + +bool InitializeNSSForChromeOSUser(const std::string& username_hash, + const base::FilePath& path) { + return g_nss_singleton.Get().InitializeNSSForChromeOSUser(username_hash, + path); +} + +bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) { + return g_nss_singleton.Get().ShouldInitializeTPMForChromeOSUser( + username_hash); +} + +void WillInitializeTPMForChromeOSUser(const std::string& username_hash) { + g_nss_singleton.Get().WillInitializeTPMForChromeOSUser(username_hash); +} + +void InitializeTPMForChromeOSUser( + const std::string& username_hash, + CK_SLOT_ID slot_id) { + g_nss_singleton.Get().InitializeTPMForChromeOSUser(username_hash, slot_id); +} + +void InitializePrivateSoftwareSlotForChromeOSUser( + const std::string& username_hash) { + g_nss_singleton.Get().InitializePrivateSoftwareSlotForChromeOSUser( + username_hash); +} + +ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) { + return g_nss_singleton.Get().GetPublicSlotForChromeOSUser(username_hash); +} + +ScopedPK11Slot GetPrivateSlotForChromeOSUser( + const std::string& username_hash, + const base::Callback<void(ScopedPK11Slot)>& callback) { + return g_nss_singleton.Get().GetPrivateSlotForChromeOSUser(username_hash, + callback); +} + +void CloseChromeOSUserForTesting(const std::string& username_hash) { + g_nss_singleton.Get().CloseChromeOSUserForTesting(username_hash); +} +#endif // defined(OS_CHROMEOS) + +base::Time PRTimeToBaseTime(PRTime prtime) { + return base::Time::FromInternalValue( + prtime + base::Time::UnixEpoch().ToInternalValue()); +} + +PRTime BaseTimeToPRTime(base::Time time) { + return time.ToInternalValue() - base::Time::UnixEpoch().ToInternalValue(); +} + +#if !defined(OS_CHROMEOS) +PK11SlotInfo* GetPersistentNSSKeySlot() { + return g_nss_singleton.Get().GetPersistentNSSKeySlot(); +} +#endif + +} // namespace crypto diff --git a/crypto/nss_util.h b/crypto/nss_util.h new file mode 100644 index 0000000000..1ca0de3e77 --- /dev/null +++ b/crypto/nss_util.h @@ -0,0 +1,155 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_NSS_UTIL_H_ +#define CRYPTO_NSS_UTIL_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "crypto/crypto_export.h" + +namespace base { +class FilePath; +class Lock; +class Time; +} // namespace base + +// This file specifically doesn't depend on any NSS or NSPR headers because it +// is included by various (non-crypto) parts of chrome to call the +// initialization functions. +namespace crypto { + +#if defined(USE_NSS_CERTS) +// EarlySetupForNSSInit performs lightweight setup which must occur before the +// process goes multithreaded. This does not initialise NSS. For test, see +// EnsureNSSInit. +CRYPTO_EXPORT void EarlySetupForNSSInit(); +#endif + +// Initialize NRPR if it isn't already initialized. This function is +// thread-safe, and NSPR will only ever be initialized once. +CRYPTO_EXPORT void EnsureNSPRInit(); + +// Initialize NSS safely for strict sandboxing. This function tells NSS to not +// load user security modules, and makes sure NSS will have proper entropy in a +// restricted, sandboxed environment. +// +// As a defense in depth measure, this function should be called in a sandboxed +// environment. That way, in the event of a bug, NSS will still not be able to +// load security modules that could expose private data and keys. +// +// Make sure to get an LGTM from the Chrome Security Team if you use this. +CRYPTO_EXPORT void InitNSSSafely(); + +// Initialize NSS if it isn't already initialized. This must be called before +// any other NSS functions. This function is thread-safe, and NSS will only +// ever be initialized once. +CRYPTO_EXPORT void EnsureNSSInit(); + +// Call this before calling EnsureNSSInit() will force NSS to initialize +// without a persistent DB. This is used for the special case where access of +// persistent DB is prohibited. +// +// TODO(hclam): Isolate loading default root certs. +// +// NSS will be initialized without loading any user security modules, including +// the built-in root certificates module. User security modules need to be +// loaded manually after NSS initialization. +// +// If EnsureNSSInit() is called before then this function has no effect. +// +// Calling this method only has effect on Linux. +// +// WARNING: Use this with caution. +CRYPTO_EXPORT void ForceNSSNoDBInit(); + +// This method is used to disable checks in NSS when used in a forked process. +// NSS checks whether it is running a forked process to avoid problems when +// using user security modules in a forked process. However if we are sure +// there are no modules loaded before the process is forked then there is no +// harm disabling the check. +// +// This method must be called before EnsureNSSInit() to take effect. +// +// WARNING: Use this with caution. +CRYPTO_EXPORT void DisableNSSForkCheck(); + +// Load NSS library files. This function has no effect on Mac and Windows. +// This loads the necessary NSS library files so that NSS can be initialized +// after loading additional library files is disallowed, for example when the +// sandbox is active. +// +// Note that this does not load libnssckbi.so which contains the root +// certificates. +CRYPTO_EXPORT void LoadNSSLibraries(); + +// Check if the current NSS version is greater than or equals to |version|. +// A sample version string is "3.12.3". +bool CheckNSSVersion(const char* version); + +#if defined(OS_CHROMEOS) +// Indicates that NSS should use the Chaps library so that we +// can access the TPM through NSS. InitializeTPMTokenAndSystemSlot and +// InitializeTPMForChromeOSUser must still be called to load the slots. +CRYPTO_EXPORT void EnableTPMTokenForNSS(); + +// Returns true if EnableTPMTokenForNSS has been called. +CRYPTO_EXPORT bool IsTPMTokenEnabledForNSS(); + +// Returns true if the TPM is owned and PKCS#11 initialized with the +// user and security officer PINs, and has been enabled in NSS by +// calling EnableTPMForNSS, and Chaps has been successfully +// loaded into NSS. +// If |callback| is non-null and the function returns false, the |callback| will +// be run once the TPM is ready. |callback| will never be run if the function +// returns true. +CRYPTO_EXPORT bool IsTPMTokenReady(const base::Closure& callback) + WARN_UNUSED_RESULT; + +// Initialize the TPM token and system slot. The |callback| will run on the same +// thread with true if the token and slot were successfully loaded or were +// already initialized. |callback| will be passed false if loading failed. Once +// called, InitializeTPMTokenAndSystemSlot must not be called again until the +// |callback| has been run. +CRYPTO_EXPORT void InitializeTPMTokenAndSystemSlot( + int system_slot_id, + const base::Callback<void(bool)>& callback); +#endif + +// Convert a NSS PRTime value into a base::Time object. +// We use a int64 instead of PRTime here to avoid depending on NSPR headers. +CRYPTO_EXPORT base::Time PRTimeToBaseTime(int64 prtime); + +// Convert a base::Time object into a PRTime value. +// We use a int64 instead of PRTime here to avoid depending on NSPR headers. +CRYPTO_EXPORT int64 BaseTimeToPRTime(base::Time time); + +#if defined(USE_NSS_CERTS) +// NSS has a bug which can cause a deadlock or stall in some cases when writing +// to the certDB and keyDB. It also has a bug which causes concurrent key pair +// generations to scribble over each other. To work around this, we synchronize +// writes to the NSS databases with a global lock. The lock is hidden beneath a +// function for easy disabling when the bug is fixed. Callers should allow for +// it to return NULL in the future. +// +// See https://bugzilla.mozilla.org/show_bug.cgi?id=564011 +base::Lock* GetNSSWriteLock(); + +// A helper class that acquires the NSS write Lock while the AutoNSSWriteLock +// is in scope. +class CRYPTO_EXPORT AutoNSSWriteLock { + public: + AutoNSSWriteLock(); + ~AutoNSSWriteLock(); + private: + base::Lock *lock_; + DISALLOW_COPY_AND_ASSIGN(AutoNSSWriteLock); +}; +#endif // defined(USE_NSS_CERTS) + +} // namespace crypto + +#endif // CRYPTO_NSS_UTIL_H_ diff --git a/crypto/nss_util_unittest.cc b/crypto/nss_util_unittest.cc new file mode 100644 index 0000000000..28591916d3 --- /dev/null +++ b/crypto/nss_util_unittest.cc @@ -0,0 +1,43 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/nss_util.h" + +#include <prtime.h> + +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +TEST(NSSUtilTest, PRTimeConversion) { + EXPECT_EQ(base::Time::UnixEpoch(), PRTimeToBaseTime(0)); + EXPECT_EQ(0, BaseTimeToPRTime(base::Time::UnixEpoch())); + + PRExplodedTime prxtime; + prxtime.tm_params.tp_gmt_offset = 0; + prxtime.tm_params.tp_dst_offset = 0; + base::Time::Exploded exploded; + exploded.year = prxtime.tm_year = 2011; + exploded.month = 12; + prxtime.tm_month = 11; + // PRExplodedTime::tm_wday is a smaller type than Exploded::day_of_week, so + // assigning the two in this order instead of the reverse avoids potential + // warnings about type downcasting. + exploded.day_of_week = prxtime.tm_wday = 0; // Should be unused. + exploded.day_of_month = prxtime.tm_mday = 10; + exploded.hour = prxtime.tm_hour = 2; + exploded.minute = prxtime.tm_min = 52; + exploded.second = prxtime.tm_sec = 19; + exploded.millisecond = 342; + prxtime.tm_usec = 342000; + + PRTime pr_time = PR_ImplodeTime(&prxtime); + base::Time base_time = base::Time::FromUTCExploded(exploded); + + EXPECT_EQ(base_time, PRTimeToBaseTime(pr_time)); + EXPECT_EQ(pr_time, BaseTimeToPRTime(base_time)); +} + +} // namespace crypto diff --git a/crypto/openssl_bio_string.cc b/crypto/openssl_bio_string.cc new file mode 100644 index 0000000000..48805001ef --- /dev/null +++ b/crypto/openssl_bio_string.cc @@ -0,0 +1,77 @@ +// Copyright 2014 The Chromium 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 "crypto/openssl_bio_string.h" + +#include <openssl/bio.h> +#include <string.h> + +namespace crypto { + +namespace { + +int bio_string_write(BIO* bio, const char* data, int len) { + reinterpret_cast<std::string*>(bio->ptr)->append(data, len); + return len; +} + +int bio_string_puts(BIO* bio, const char* data) { + // Note: unlike puts(), BIO_puts does not add a newline. + return bio_string_write(bio, data, strlen(data)); +} + +long bio_string_ctrl(BIO* bio, int cmd, long num, void* ptr) { + std::string* str = reinterpret_cast<std::string*>(bio->ptr); + switch (cmd) { + case BIO_CTRL_RESET: + str->clear(); + return 1; + case BIO_C_FILE_SEEK: + return -1; + case BIO_C_FILE_TELL: + return str->size(); + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + } +} + +int bio_string_new(BIO* bio) { + bio->ptr = NULL; + bio->init = 0; + return 1; +} + +int bio_string_free(BIO* bio) { + // The string is owned by the caller, so there's nothing to do here. + return bio != NULL; +} + +BIO_METHOD bio_string_methods = { + // TODO(mattm): Should add some type number too? (bio.h uses 1-24) + BIO_TYPE_SOURCE_SINK, + "bio_string", + bio_string_write, + NULL, /* read */ + bio_string_puts, + NULL, /* gets */ + bio_string_ctrl, + bio_string_new, + bio_string_free, + NULL, /* callback_ctrl */ +}; + +} // namespace + +BIO* BIO_new_string(std::string* out) { + BIO* bio = BIO_new(&bio_string_methods); + if (!bio) + return bio; + bio->ptr = out; + bio->init = 1; + return bio; +} + +} // namespace crypto diff --git a/crypto/openssl_bio_string.h b/crypto/openssl_bio_string.h new file mode 100644 index 0000000000..ca46c12de8 --- /dev/null +++ b/crypto/openssl_bio_string.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_OPENSSL_BIO_STRING_H_ +#define CRYPTO_OPENSSL_BIO_STRING_H_ + +#include <string> + +#include "crypto/crypto_export.h" + +// From <openssl/bio.h> +typedef struct bio_st BIO; + +namespace crypto { + +// Creates a new BIO that can be used with OpenSSL's various output functions, +// and which will write all output directly into |out|. This is primarily +// intended as a utility to reduce the amount of copying and separate +// allocations when performing extensive string modifications or streaming +// within OpenSSL. +// +// Note: |out| must remain valid for the duration of the BIO. +CRYPTO_EXPORT BIO* BIO_new_string(std::string* out); + +} // namespace crypto + +#endif // CRYPTO_OPENSSL_BIO_STRING_H_ + diff --git a/crypto/openssl_bio_string_unittest.cc b/crypto/openssl_bio_string_unittest.cc new file mode 100644 index 0000000000..9dfa0e70f7 --- /dev/null +++ b/crypto/openssl_bio_string_unittest.cc @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium 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 "crypto/openssl_bio_string.h" + +#include <openssl/bio.h> + +#include "crypto/scoped_openssl_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +TEST(OpenSSLBIOString, TestWrite) { + std::string s; + const std::string expected1("a one\nb 2\n"); + const std::string expected2("c d e f"); + const std::string expected3("g h i"); + { + ScopedBIO bio(BIO_new_string(&s)); + ASSERT_TRUE(bio.get()); + + EXPECT_EQ(static_cast<int>(expected1.size()), + BIO_printf(bio.get(), "a %s\nb %i\n", "one", 2)); + EXPECT_EQ(expected1, s); + + EXPECT_EQ(1, BIO_flush(bio.get())); + EXPECT_EQ(expected1, s); + + EXPECT_EQ(static_cast<int>(expected2.size()), + BIO_write(bio.get(), expected2.data(), expected2.size())); + EXPECT_EQ(expected1 + expected2, s); + + EXPECT_EQ(static_cast<int>(expected3.size()), + BIO_puts(bio.get(), expected3.c_str())); + EXPECT_EQ(expected1 + expected2 + expected3, s); + } + EXPECT_EQ(expected1 + expected2 + expected3, s); +} + +TEST(OpenSSLBIOString, TestReset) { + std::string s; + const std::string expected1("a b c\n"); + const std::string expected2("d e f g\n"); + { + ScopedBIO bio(BIO_new_string(&s)); + ASSERT_TRUE(bio.get()); + + EXPECT_EQ(static_cast<int>(expected1.size()), + BIO_write(bio.get(), expected1.data(), expected1.size())); + EXPECT_EQ(expected1, s); + + EXPECT_EQ(1, BIO_reset(bio.get())); + EXPECT_EQ(std::string(), s); + + EXPECT_EQ(static_cast<int>(expected2.size()), + BIO_write(bio.get(), expected2.data(), expected2.size())); + EXPECT_EQ(expected2, s); + } + EXPECT_EQ(expected2, s); +} + +} // namespace crypto diff --git a/crypto/openssl_util.cc b/crypto/openssl_util.cc new file mode 100644 index 0000000000..964d83b843 --- /dev/null +++ b/crypto/openssl_util.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/openssl_util.h" + +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <openssl/cpu.h> + +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/singleton.h" +#include "base/strings/string_piece.h" +#include "base/synchronization/lock.h" +#include "build/build_config.h" + +#if defined(OS_ANDROID) && defined(ARCH_CPU_ARMEL) +#include <cpu-features.h> +#include "base/cpu.h" +#endif + +namespace crypto { + +namespace { + +void CurrentThreadId(CRYPTO_THREADID* id) { + CRYPTO_THREADID_set_numeric( + id, static_cast<unsigned long>(base::PlatformThread::CurrentId())); +} + +// Singleton for initializing and cleaning up the OpenSSL library. +class OpenSSLInitSingleton { + public: + static OpenSSLInitSingleton* GetInstance() { + // We allow the SSL environment to leak for multiple reasons: + // - it is used from a non-joinable worker thread that is not stopped on + // shutdown, hence may still be using OpenSSL library after the AtExit + // runner has completed. + // - There are other OpenSSL related singletons (e.g. the client socket + // context) who's cleanup depends on the global environment here, but + // we can't control the order the AtExit handlers will run in so + // allowing the global environment to leak at least ensures it is + // available for those other singletons to reliably cleanup. + return Singleton<OpenSSLInitSingleton, + LeakySingletonTraits<OpenSSLInitSingleton> >::get(); + } + private: + friend struct DefaultSingletonTraits<OpenSSLInitSingleton>; + OpenSSLInitSingleton() { +#if defined(OS_ANDROID) && defined(ARCH_CPU_ARMEL) + const bool has_neon = + (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0; + // CRYPTO_set_NEON_capable is called before |SSL_library_init| because this + // stops BoringSSL from probing for NEON support via SIGILL in the case + // that getauxval isn't present. + CRYPTO_set_NEON_capable(has_neon); + // See https://code.google.com/p/chromium/issues/detail?id=341598 + base::CPU cpu; + CRYPTO_set_NEON_functional(!cpu.has_broken_neon()); +#endif + + SSL_load_error_strings(); + SSL_library_init(); + int num_locks = CRYPTO_num_locks(); + locks_.reserve(num_locks); + for (int i = 0; i < num_locks; ++i) + locks_.push_back(new base::Lock()); + CRYPTO_set_locking_callback(LockingCallback); + CRYPTO_THREADID_set_callback(CurrentThreadId); + } + + ~OpenSSLInitSingleton() { + CRYPTO_set_locking_callback(NULL); + EVP_cleanup(); + ERR_free_strings(); + } + + static void LockingCallback(int mode, int n, const char* file, int line) { + OpenSSLInitSingleton::GetInstance()->OnLockingCallback(mode, n, file, line); + } + + void OnLockingCallback(int mode, int n, const char* file, int line) { + CHECK_LT(static_cast<size_t>(n), locks_.size()); + if (mode & CRYPTO_LOCK) + locks_[n]->Acquire(); + else + locks_[n]->Release(); + } + + // These locks are used and managed by OpenSSL via LockingCallback(). + ScopedVector<base::Lock> locks_; + + DISALLOW_COPY_AND_ASSIGN(OpenSSLInitSingleton); +}; + +// Callback routine for OpenSSL to print error messages. |str| is a +// NULL-terminated string of length |len| containing diagnostic information +// such as the library, function and reason for the error, the file and line +// where the error originated, plus potentially any context-specific +// information about the error. |context| contains a pointer to user-supplied +// data, which is currently unused. +// If this callback returns a value <= 0, OpenSSL will stop processing the +// error queue and return, otherwise it will continue calling this function +// until all errors have been removed from the queue. +int OpenSSLErrorCallback(const char* str, size_t len, void* context) { + DVLOG(1) << "\t" << base::StringPiece(str, len); + return 1; +} + +} // namespace + +void EnsureOpenSSLInit() { + (void)OpenSSLInitSingleton::GetInstance(); +} + +void ClearOpenSSLERRStack(const tracked_objects::Location& location) { + if (logging::DEBUG_MODE && VLOG_IS_ON(1)) { + uint32_t error_num = ERR_peek_error(); + if (error_num == 0) + return; + + std::string message; + location.Write(true, true, &message); + DVLOG(1) << "OpenSSL ERR_get_error stack from " << message; + ERR_print_errors_cb(&OpenSSLErrorCallback, NULL); + } else { + ERR_clear_error(); + } +} + +} // namespace crypto diff --git a/crypto/openssl_util.h b/crypto/openssl_util.h new file mode 100644 index 0000000000..2743883dc1 --- /dev/null +++ b/crypto/openssl_util.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_OPENSSL_UTIL_H_ +#define CRYPTO_OPENSSL_UTIL_H_ + +#include "base/basictypes.h" +#include "base/location.h" +#include "crypto/crypto_export.h" + +namespace crypto { + +// Provides a buffer of at least MIN_SIZE bytes, for use when calling OpenSSL's +// SHA256, HMAC, etc functions, adapting the buffer sizing rules to meet those +// of the our base wrapper APIs. +// This allows the library to write directly to the caller's buffer if it is of +// sufficient size, but if not it will write to temporary |min_sized_buffer_| +// of required size and then its content is automatically copied out on +// destruction, with truncation as appropriate. +template<int MIN_SIZE> +class ScopedOpenSSLSafeSizeBuffer { + public: + ScopedOpenSSLSafeSizeBuffer(unsigned char* output, size_t output_len) + : output_(output), + output_len_(output_len) { + } + + ~ScopedOpenSSLSafeSizeBuffer() { + if (output_len_ < MIN_SIZE) { + // Copy the temporary buffer out, truncating as needed. + memcpy(output_, min_sized_buffer_, output_len_); + } + // else... any writing already happened directly into |output_|. + } + + unsigned char* safe_buffer() { + return output_len_ < MIN_SIZE ? min_sized_buffer_ : output_; + } + + private: + // Pointer to the caller's data area and its associated size, where data + // written via safe_buffer() will [eventually] end up. + unsigned char* output_; + size_t output_len_; + + // Temporary buffer writen into in the case where the caller's + // buffer is not of sufficient size. + unsigned char min_sized_buffer_[MIN_SIZE]; + + DISALLOW_COPY_AND_ASSIGN(ScopedOpenSSLSafeSizeBuffer); +}; + +// Initialize OpenSSL if it isn't already initialized. This must be called +// before any other OpenSSL functions though it is safe and cheap to call this +// multiple times. +// This function is thread-safe, and OpenSSL will only ever be initialized once. +// OpenSSL will be properly shut down on program exit. +void CRYPTO_EXPORT EnsureOpenSSLInit(); + +// Drains the OpenSSL ERR_get_error stack. On a debug build the error codes +// are send to VLOG(1), on a release build they are disregarded. In most +// cases you should pass FROM_HERE as the |location|. +void CRYPTO_EXPORT ClearOpenSSLERRStack( + const tracked_objects::Location& location); + +// Place an instance of this class on the call stack to automatically clear +// the OpenSSL error stack on function exit. +class OpenSSLErrStackTracer { + public: + // Pass FROM_HERE as |location|, to help track the source of OpenSSL error + // messages. Note any diagnostic emitted will be tagged with the location of + // the constructor call as it's not possible to trace a destructor's callsite. + explicit OpenSSLErrStackTracer(const tracked_objects::Location& location) + : location_(location) { + EnsureOpenSSLInit(); + } + ~OpenSSLErrStackTracer() { + ClearOpenSSLERRStack(location_); + } + + private: + const tracked_objects::Location location_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(OpenSSLErrStackTracer); +}; + +} // namespace crypto + +#endif // CRYPTO_OPENSSL_UTIL_H_ diff --git a/crypto/p224.cc b/crypto/p224.cc new file mode 100644 index 0000000000..11946a9413 --- /dev/null +++ b/crypto/p224.cc @@ -0,0 +1,758 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is an implementation of the P224 elliptic curve group. It's written to +// be short and simple rather than fast, although it's still constant-time. +// +// See http://www.imperialviolet.org/2010/12/04/ecc.html ([1]) for background. + +#include "crypto/p224.h" + +#include <string.h> + +#include "base/sys_byteorder.h" + +namespace { + +using base::HostToNet32; +using base::NetToHost32; + +// Field element functions. +// +// The field that we're dealing with is ℤ/pℤ where p = 2**224 - 2**96 + 1. +// +// Field elements are represented by a FieldElement, which is a typedef to an +// array of 8 uint32's. The value of a FieldElement, a, is: +// a[0] + 2**28·a[1] + 2**56·a[1] + ... + 2**196·a[7] +// +// Using 28-bit limbs means that there's only 4 bits of headroom, which is less +// than we would really like. But it has the useful feature that we hit 2**224 +// exactly, making the reflections during a reduce much nicer. + +using crypto::p224::FieldElement; + +// kP is the P224 prime. +const FieldElement kP = { + 1, 0, 0, 268431360, + 268435455, 268435455, 268435455, 268435455, +}; + +void Contract(FieldElement* inout); + +// IsZero returns 0xffffffff if a == 0 mod p and 0 otherwise. +uint32 IsZero(const FieldElement& a) { + FieldElement minimal; + memcpy(&minimal, &a, sizeof(minimal)); + Contract(&minimal); + + uint32 is_zero = 0, is_p = 0; + for (unsigned i = 0; i < 8; i++) { + is_zero |= minimal[i]; + is_p |= minimal[i] - kP[i]; + } + + // If either is_zero or is_p is 0, then we should return 1. + is_zero |= is_zero >> 16; + is_zero |= is_zero >> 8; + is_zero |= is_zero >> 4; + is_zero |= is_zero >> 2; + is_zero |= is_zero >> 1; + + is_p |= is_p >> 16; + is_p |= is_p >> 8; + is_p |= is_p >> 4; + is_p |= is_p >> 2; + is_p |= is_p >> 1; + + // For is_zero and is_p, the LSB is 0 iff all the bits are zero. + is_zero &= is_p & 1; + is_zero = (~is_zero) << 31; + is_zero = static_cast<int32>(is_zero) >> 31; + return is_zero; +} + +// Add computes *out = a+b +// +// a[i] + b[i] < 2**32 +void Add(FieldElement* out, const FieldElement& a, const FieldElement& b) { + for (int i = 0; i < 8; i++) { + (*out)[i] = a[i] + b[i]; + } +} + +static const uint32 kTwo31p3 = (1u<<31) + (1u<<3); +static const uint32 kTwo31m3 = (1u<<31) - (1u<<3); +static const uint32 kTwo31m15m3 = (1u<<31) - (1u<<15) - (1u<<3); +// kZero31ModP is 0 mod p where bit 31 is set in all limbs so that we can +// subtract smaller amounts without underflow. See the section "Subtraction" in +// [1] for why. +static const FieldElement kZero31ModP = { + kTwo31p3, kTwo31m3, kTwo31m3, kTwo31m15m3, + kTwo31m3, kTwo31m3, kTwo31m3, kTwo31m3 +}; + +// Subtract computes *out = a-b +// +// a[i], b[i] < 2**30 +// out[i] < 2**32 +void Subtract(FieldElement* out, const FieldElement& a, const FieldElement& b) { + for (int i = 0; i < 8; i++) { + // See the section on "Subtraction" in [1] for details. + (*out)[i] = a[i] + kZero31ModP[i] - b[i]; + } +} + +static const uint64 kTwo63p35 = (1ull<<63) + (1ull<<35); +static const uint64 kTwo63m35 = (1ull<<63) - (1ull<<35); +static const uint64 kTwo63m35m19 = (1ull<<63) - (1ull<<35) - (1ull<<19); +// kZero63ModP is 0 mod p where bit 63 is set in all limbs. See the section +// "Subtraction" in [1] for why. +static const uint64 kZero63ModP[8] = { + kTwo63p35, kTwo63m35, kTwo63m35, kTwo63m35, + kTwo63m35m19, kTwo63m35, kTwo63m35, kTwo63m35, +}; + +static const uint32 kBottom28Bits = 0xfffffff; + +// LargeFieldElement also represents an element of the field. The limbs are +// still spaced 28-bits apart and in little-endian order. So the limbs are at +// 0, 28, 56, ..., 392 bits, each 64-bits wide. +typedef uint64 LargeFieldElement[15]; + +// ReduceLarge converts a LargeFieldElement to a FieldElement. +// +// in[i] < 2**62 + +// GCC 4.9 incorrectly vectorizes the first coefficient elimination loop, so +// disable that optimization via pragma. Don't use the pragma under Clang, since +// clang doesn't understand it. +// TODO(wez): Remove this when crbug.com/439566 is fixed. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC optimize("no-tree-vectorize") +#endif + +void ReduceLarge(FieldElement* out, LargeFieldElement* inptr) { + LargeFieldElement& in(*inptr); + + for (int i = 0; i < 8; i++) { + in[i] += kZero63ModP[i]; + } + + // Eliminate the coefficients at 2**224 and greater while maintaining the + // same value mod p. + for (int i = 14; i >= 8; i--) { + in[i-8] -= in[i]; // reflection off the "+1" term of p. + in[i-5] += (in[i] & 0xffff) << 12; // part of the "-2**96" reflection. + in[i-4] += in[i] >> 16; // the rest of the "-2**96" reflection. + } + in[8] = 0; + // in[0..8] < 2**64 + + // As the values become small enough, we start to store them in |out| and use + // 32-bit operations. + for (int i = 1; i < 8; i++) { + in[i+1] += in[i] >> 28; + (*out)[i] = static_cast<uint32>(in[i] & kBottom28Bits); + } + // Eliminate the term at 2*224 that we introduced while keeping the same + // value mod p. + in[0] -= in[8]; // reflection off the "+1" term of p. + (*out)[3] += static_cast<uint32>(in[8] & 0xffff) << 12; // "-2**96" term + (*out)[4] += static_cast<uint32>(in[8] >> 16); // rest of "-2**96" term + // in[0] < 2**64 + // out[3] < 2**29 + // out[4] < 2**29 + // out[1,2,5..7] < 2**28 + + (*out)[0] = static_cast<uint32>(in[0] & kBottom28Bits); + (*out)[1] += static_cast<uint32>((in[0] >> 28) & kBottom28Bits); + (*out)[2] += static_cast<uint32>(in[0] >> 56); + // out[0] < 2**28 + // out[1..4] < 2**29 + // out[5..7] < 2**28 +} + +// TODO(wez): Remove this when crbug.com/439566 is fixed. +#if defined(__GNUC__) && !defined(__clang__) +// Reenable "tree-vectorize" optimization if it got disabled for ReduceLarge. +#pragma GCC reset_options +#endif + +// Mul computes *out = a*b +// +// a[i] < 2**29, b[i] < 2**30 (or vice versa) +// out[i] < 2**29 +void Mul(FieldElement* out, const FieldElement& a, const FieldElement& b) { + LargeFieldElement tmp; + memset(&tmp, 0, sizeof(tmp)); + + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + tmp[i+j] += static_cast<uint64>(a[i]) * static_cast<uint64>(b[j]); + } + } + + ReduceLarge(out, &tmp); +} + +// Square computes *out = a*a +// +// a[i] < 2**29 +// out[i] < 2**29 +void Square(FieldElement* out, const FieldElement& a) { + LargeFieldElement tmp; + memset(&tmp, 0, sizeof(tmp)); + + for (int i = 0; i < 8; i++) { + for (int j = 0; j <= i; j++) { + uint64 r = static_cast<uint64>(a[i]) * static_cast<uint64>(a[j]); + if (i == j) { + tmp[i+j] += r; + } else { + tmp[i+j] += r << 1; + } + } + } + + ReduceLarge(out, &tmp); +} + +// Reduce reduces the coefficients of in_out to smaller bounds. +// +// On entry: a[i] < 2**31 + 2**30 +// On exit: a[i] < 2**29 +void Reduce(FieldElement* in_out) { + FieldElement& a = *in_out; + + for (int i = 0; i < 7; i++) { + a[i+1] += a[i] >> 28; + a[i] &= kBottom28Bits; + } + uint32 top = a[7] >> 28; + a[7] &= kBottom28Bits; + + // top < 2**4 + // Constant-time: mask = (top != 0) ? 0xffffffff : 0 + uint32 mask = top; + mask |= mask >> 2; + mask |= mask >> 1; + mask <<= 31; + mask = static_cast<uint32>(static_cast<int32>(mask) >> 31); + + // Eliminate top while maintaining the same value mod p. + a[0] -= top; + a[3] += top << 12; + + // We may have just made a[0] negative but, if we did, then we must + // have added something to a[3], thus it's > 2**12. Therefore we can + // carry down to a[0]. + a[3] -= 1 & mask; + a[2] += mask & ((1<<28) - 1); + a[1] += mask & ((1<<28) - 1); + a[0] += mask & (1<<28); +} + +// Invert calcuates *out = in**-1 by computing in**(2**224 - 2**96 - 1), i.e. +// Fermat's little theorem. +void Invert(FieldElement* out, const FieldElement& in) { + FieldElement f1, f2, f3, f4; + + Square(&f1, in); // 2 + Mul(&f1, f1, in); // 2**2 - 1 + Square(&f1, f1); // 2**3 - 2 + Mul(&f1, f1, in); // 2**3 - 1 + Square(&f2, f1); // 2**4 - 2 + Square(&f2, f2); // 2**5 - 4 + Square(&f2, f2); // 2**6 - 8 + Mul(&f1, f1, f2); // 2**6 - 1 + Square(&f2, f1); // 2**7 - 2 + for (int i = 0; i < 5; i++) { // 2**12 - 2**6 + Square(&f2, f2); + } + Mul(&f2, f2, f1); // 2**12 - 1 + Square(&f3, f2); // 2**13 - 2 + for (int i = 0; i < 11; i++) { // 2**24 - 2**12 + Square(&f3, f3); + } + Mul(&f2, f3, f2); // 2**24 - 1 + Square(&f3, f2); // 2**25 - 2 + for (int i = 0; i < 23; i++) { // 2**48 - 2**24 + Square(&f3, f3); + } + Mul(&f3, f3, f2); // 2**48 - 1 + Square(&f4, f3); // 2**49 - 2 + for (int i = 0; i < 47; i++) { // 2**96 - 2**48 + Square(&f4, f4); + } + Mul(&f3, f3, f4); // 2**96 - 1 + Square(&f4, f3); // 2**97 - 2 + for (int i = 0; i < 23; i++) { // 2**120 - 2**24 + Square(&f4, f4); + } + Mul(&f2, f4, f2); // 2**120 - 1 + for (int i = 0; i < 6; i++) { // 2**126 - 2**6 + Square(&f2, f2); + } + Mul(&f1, f1, f2); // 2**126 - 1 + Square(&f1, f1); // 2**127 - 2 + Mul(&f1, f1, in); // 2**127 - 1 + for (int i = 0; i < 97; i++) { // 2**224 - 2**97 + Square(&f1, f1); + } + Mul(out, f1, f3); // 2**224 - 2**96 - 1 +} + +// Contract converts a FieldElement to its minimal, distinguished form. +// +// On entry, in[i] < 2**29 +// On exit, in[i] < 2**28 +void Contract(FieldElement* inout) { + FieldElement& out = *inout; + + // Reduce the coefficients to < 2**28. + for (int i = 0; i < 7; i++) { + out[i+1] += out[i] >> 28; + out[i] &= kBottom28Bits; + } + uint32 top = out[7] >> 28; + out[7] &= kBottom28Bits; + + // Eliminate top while maintaining the same value mod p. + out[0] -= top; + out[3] += top << 12; + + // We may just have made out[0] negative. So we carry down. If we made + // out[0] negative then we know that out[3] is sufficiently positive + // because we just added to it. + for (int i = 0; i < 3; i++) { + uint32 mask = static_cast<uint32>(static_cast<int32>(out[i]) >> 31); + out[i] += (1 << 28) & mask; + out[i+1] -= 1 & mask; + } + + // We might have pushed out[3] over 2**28 so we perform another, partial + // carry chain. + for (int i = 3; i < 7; i++) { + out[i+1] += out[i] >> 28; + out[i] &= kBottom28Bits; + } + top = out[7] >> 28; + out[7] &= kBottom28Bits; + + // Eliminate top while maintaining the same value mod p. + out[0] -= top; + out[3] += top << 12; + + // There are two cases to consider for out[3]: + // 1) The first time that we eliminated top, we didn't push out[3] over + // 2**28. In this case, the partial carry chain didn't change any values + // and top is zero. + // 2) We did push out[3] over 2**28 the first time that we eliminated top. + // The first value of top was in [0..16), therefore, prior to eliminating + // the first top, 0xfff1000 <= out[3] <= 0xfffffff. Therefore, after + // overflowing and being reduced by the second carry chain, out[3] <= + // 0xf000. Thus it cannot have overflowed when we eliminated top for the + // second time. + + // Again, we may just have made out[0] negative, so do the same carry down. + // As before, if we made out[0] negative then we know that out[3] is + // sufficiently positive. + for (int i = 0; i < 3; i++) { + uint32 mask = static_cast<uint32>(static_cast<int32>(out[i]) >> 31); + out[i] += (1 << 28) & mask; + out[i+1] -= 1 & mask; + } + + // The value is < 2**224, but maybe greater than p. In order to reduce to a + // unique, minimal value we see if the value is >= p and, if so, subtract p. + + // First we build a mask from the top four limbs, which must all be + // equal to bottom28Bits if the whole value is >= p. If top_4_all_ones + // ends up with any zero bits in the bottom 28 bits, then this wasn't + // true. + uint32 top_4_all_ones = 0xffffffffu; + for (int i = 4; i < 8; i++) { + top_4_all_ones &= out[i]; + } + top_4_all_ones |= 0xf0000000; + // Now we replicate any zero bits to all the bits in top_4_all_ones. + top_4_all_ones &= top_4_all_ones >> 16; + top_4_all_ones &= top_4_all_ones >> 8; + top_4_all_ones &= top_4_all_ones >> 4; + top_4_all_ones &= top_4_all_ones >> 2; + top_4_all_ones &= top_4_all_ones >> 1; + top_4_all_ones = + static_cast<uint32>(static_cast<int32>(top_4_all_ones << 31) >> 31); + + // Now we test whether the bottom three limbs are non-zero. + uint32 bottom_3_non_zero = out[0] | out[1] | out[2]; + bottom_3_non_zero |= bottom_3_non_zero >> 16; + bottom_3_non_zero |= bottom_3_non_zero >> 8; + bottom_3_non_zero |= bottom_3_non_zero >> 4; + bottom_3_non_zero |= bottom_3_non_zero >> 2; + bottom_3_non_zero |= bottom_3_non_zero >> 1; + bottom_3_non_zero = + static_cast<uint32>(static_cast<int32>(bottom_3_non_zero) >> 31); + + // Everything depends on the value of out[3]. + // If it's > 0xffff000 and top_4_all_ones != 0 then the whole value is >= p + // If it's = 0xffff000 and top_4_all_ones != 0 and bottom_3_non_zero != 0, + // then the whole value is >= p + // If it's < 0xffff000, then the whole value is < p + uint32 n = out[3] - 0xffff000; + uint32 out_3_equal = n; + out_3_equal |= out_3_equal >> 16; + out_3_equal |= out_3_equal >> 8; + out_3_equal |= out_3_equal >> 4; + out_3_equal |= out_3_equal >> 2; + out_3_equal |= out_3_equal >> 1; + out_3_equal = + ~static_cast<uint32>(static_cast<int32>(out_3_equal << 31) >> 31); + + // If out[3] > 0xffff000 then n's MSB will be zero. + uint32 out_3_gt = ~static_cast<uint32>(static_cast<int32>(n << 31) >> 31); + + uint32 mask = top_4_all_ones & ((out_3_equal & bottom_3_non_zero) | out_3_gt); + out[0] -= 1 & mask; + out[3] -= 0xffff000 & mask; + out[4] -= 0xfffffff & mask; + out[5] -= 0xfffffff & mask; + out[6] -= 0xfffffff & mask; + out[7] -= 0xfffffff & mask; +} + + +// Group element functions. +// +// These functions deal with group elements. The group is an elliptic curve +// group with a = -3 defined in FIPS 186-3, section D.2.2. + +using crypto::p224::Point; + +// kB is parameter of the elliptic curve. +const FieldElement kB = { + 55967668, 11768882, 265861671, 185302395, + 39211076, 180311059, 84673715, 188764328, +}; + +void CopyConditional(Point* out, const Point& a, uint32 mask); +void DoubleJacobian(Point* out, const Point& a); + +// AddJacobian computes *out = a+b where a != b. +void AddJacobian(Point *out, + const Point& a, + const Point& b) { + // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl + FieldElement z1z1, z2z2, u1, u2, s1, s2, h, i, j, r, v; + + uint32 z1_is_zero = IsZero(a.z); + uint32 z2_is_zero = IsZero(b.z); + + // Z1Z1 = Z1² + Square(&z1z1, a.z); + + // Z2Z2 = Z2² + Square(&z2z2, b.z); + + // U1 = X1*Z2Z2 + Mul(&u1, a.x, z2z2); + + // U2 = X2*Z1Z1 + Mul(&u2, b.x, z1z1); + + // S1 = Y1*Z2*Z2Z2 + Mul(&s1, b.z, z2z2); + Mul(&s1, a.y, s1); + + // S2 = Y2*Z1*Z1Z1 + Mul(&s2, a.z, z1z1); + Mul(&s2, b.y, s2); + + // H = U2-U1 + Subtract(&h, u2, u1); + Reduce(&h); + uint32 x_equal = IsZero(h); + + // I = (2*H)² + for (int k = 0; k < 8; k++) { + i[k] = h[k] << 1; + } + Reduce(&i); + Square(&i, i); + + // J = H*I + Mul(&j, h, i); + // r = 2*(S2-S1) + Subtract(&r, s2, s1); + Reduce(&r); + uint32 y_equal = IsZero(r); + + if (x_equal && y_equal && !z1_is_zero && !z2_is_zero) { + // The two input points are the same therefore we must use the dedicated + // doubling function as the slope of the line is undefined. + DoubleJacobian(out, a); + return; + } + + for (int k = 0; k < 8; k++) { + r[k] <<= 1; + } + Reduce(&r); + + // V = U1*I + Mul(&v, u1, i); + + // Z3 = ((Z1+Z2)²-Z1Z1-Z2Z2)*H + Add(&z1z1, z1z1, z2z2); + Add(&z2z2, a.z, b.z); + Reduce(&z2z2); + Square(&z2z2, z2z2); + Subtract(&out->z, z2z2, z1z1); + Reduce(&out->z); + Mul(&out->z, out->z, h); + + // X3 = r²-J-2*V + for (int k = 0; k < 8; k++) { + z1z1[k] = v[k] << 1; + } + Add(&z1z1, j, z1z1); + Reduce(&z1z1); + Square(&out->x, r); + Subtract(&out->x, out->x, z1z1); + Reduce(&out->x); + + // Y3 = r*(V-X3)-2*S1*J + for (int k = 0; k < 8; k++) { + s1[k] <<= 1; + } + Mul(&s1, s1, j); + Subtract(&z1z1, v, out->x); + Reduce(&z1z1); + Mul(&z1z1, z1z1, r); + Subtract(&out->y, z1z1, s1); + Reduce(&out->y); + + CopyConditional(out, a, z2_is_zero); + CopyConditional(out, b, z1_is_zero); +} + +// DoubleJacobian computes *out = a+a. +void DoubleJacobian(Point* out, const Point& a) { + // See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b + FieldElement delta, gamma, beta, alpha, t; + + Square(&delta, a.z); + Square(&gamma, a.y); + Mul(&beta, a.x, gamma); + + // alpha = 3*(X1-delta)*(X1+delta) + Add(&t, a.x, delta); + for (int i = 0; i < 8; i++) { + t[i] += t[i] << 1; + } + Reduce(&t); + Subtract(&alpha, a.x, delta); + Reduce(&alpha); + Mul(&alpha, alpha, t); + + // Z3 = (Y1+Z1)²-gamma-delta + Add(&out->z, a.y, a.z); + Reduce(&out->z); + Square(&out->z, out->z); + Subtract(&out->z, out->z, gamma); + Reduce(&out->z); + Subtract(&out->z, out->z, delta); + Reduce(&out->z); + + // X3 = alpha²-8*beta + for (int i = 0; i < 8; i++) { + delta[i] = beta[i] << 3; + } + Reduce(&delta); + Square(&out->x, alpha); + Subtract(&out->x, out->x, delta); + Reduce(&out->x); + + // Y3 = alpha*(4*beta-X3)-8*gamma² + for (int i = 0; i < 8; i++) { + beta[i] <<= 2; + } + Reduce(&beta); + Subtract(&beta, beta, out->x); + Reduce(&beta); + Square(&gamma, gamma); + for (int i = 0; i < 8; i++) { + gamma[i] <<= 3; + } + Reduce(&gamma); + Mul(&out->y, alpha, beta); + Subtract(&out->y, out->y, gamma); + Reduce(&out->y); +} + +// CopyConditional sets *out=a if mask is 0xffffffff. mask must be either 0 of +// 0xffffffff. +void CopyConditional(Point* out, + const Point& a, + uint32 mask) { + for (int i = 0; i < 8; i++) { + out->x[i] ^= mask & (a.x[i] ^ out->x[i]); + out->y[i] ^= mask & (a.y[i] ^ out->y[i]); + out->z[i] ^= mask & (a.z[i] ^ out->z[i]); + } +} + +// ScalarMult calculates *out = a*scalar where scalar is a big-endian number of +// length scalar_len and != 0. +void ScalarMult(Point* out, const Point& a, + const uint8* scalar, size_t scalar_len) { + memset(out, 0, sizeof(*out)); + Point tmp; + + for (size_t i = 0; i < scalar_len; i++) { + for (unsigned int bit_num = 0; bit_num < 8; bit_num++) { + DoubleJacobian(out, *out); + uint32 bit = static_cast<uint32>(static_cast<int32>( + (((scalar[i] >> (7 - bit_num)) & 1) << 31) >> 31)); + AddJacobian(&tmp, a, *out); + CopyConditional(out, tmp, bit); + } + } +} + +// Get224Bits reads 7 words from in and scatters their contents in +// little-endian form into 8 words at out, 28 bits per output word. +void Get224Bits(uint32* out, const uint32* in) { + out[0] = NetToHost32(in[6]) & kBottom28Bits; + out[1] = ((NetToHost32(in[5]) << 4) | + (NetToHost32(in[6]) >> 28)) & kBottom28Bits; + out[2] = ((NetToHost32(in[4]) << 8) | + (NetToHost32(in[5]) >> 24)) & kBottom28Bits; + out[3] = ((NetToHost32(in[3]) << 12) | + (NetToHost32(in[4]) >> 20)) & kBottom28Bits; + out[4] = ((NetToHost32(in[2]) << 16) | + (NetToHost32(in[3]) >> 16)) & kBottom28Bits; + out[5] = ((NetToHost32(in[1]) << 20) | + (NetToHost32(in[2]) >> 12)) & kBottom28Bits; + out[6] = ((NetToHost32(in[0]) << 24) | + (NetToHost32(in[1]) >> 8)) & kBottom28Bits; + out[7] = (NetToHost32(in[0]) >> 4) & kBottom28Bits; +} + +// Put224Bits performs the inverse operation to Get224Bits: taking 28 bits from +// each of 8 input words and writing them in big-endian order to 7 words at +// out. +void Put224Bits(uint32* out, const uint32* in) { + out[6] = HostToNet32((in[0] >> 0) | (in[1] << 28)); + out[5] = HostToNet32((in[1] >> 4) | (in[2] << 24)); + out[4] = HostToNet32((in[2] >> 8) | (in[3] << 20)); + out[3] = HostToNet32((in[3] >> 12) | (in[4] << 16)); + out[2] = HostToNet32((in[4] >> 16) | (in[5] << 12)); + out[1] = HostToNet32((in[5] >> 20) | (in[6] << 8)); + out[0] = HostToNet32((in[6] >> 24) | (in[7] << 4)); +} + +} // anonymous namespace + +namespace crypto { + +namespace p224 { + +bool Point::SetFromString(const base::StringPiece& in) { + if (in.size() != 2*28) + return false; + const uint32* inwords = reinterpret_cast<const uint32*>(in.data()); + Get224Bits(x, inwords); + Get224Bits(y, inwords + 7); + memset(&z, 0, sizeof(z)); + z[0] = 1; + + // Check that the point is on the curve, i.e. that y² = x³ - 3x + b. + FieldElement lhs; + Square(&lhs, y); + Contract(&lhs); + + FieldElement rhs; + Square(&rhs, x); + Mul(&rhs, x, rhs); + + FieldElement three_x; + for (int i = 0; i < 8; i++) { + three_x[i] = x[i] * 3; + } + Reduce(&three_x); + Subtract(&rhs, rhs, three_x); + Reduce(&rhs); + + ::Add(&rhs, rhs, kB); + Contract(&rhs); + return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; +} + +std::string Point::ToString() const { + FieldElement zinv, zinv_sq, xx, yy; + + // If this is the point at infinity we return a string of all zeros. + if (IsZero(this->z)) { + static const char zeros[56] = {0}; + return std::string(zeros, sizeof(zeros)); + } + + Invert(&zinv, this->z); + Square(&zinv_sq, zinv); + Mul(&xx, x, zinv_sq); + Mul(&zinv_sq, zinv_sq, zinv); + Mul(&yy, y, zinv_sq); + + Contract(&xx); + Contract(&yy); + + uint32 outwords[14]; + Put224Bits(outwords, xx); + Put224Bits(outwords + 7, yy); + return std::string(reinterpret_cast<const char*>(outwords), sizeof(outwords)); +} + +void ScalarMult(const Point& in, const uint8* scalar, Point* out) { + ::ScalarMult(out, in, scalar, 28); +} + +// kBasePoint is the base point (generator) of the elliptic curve group. +static const Point kBasePoint = { + {22813985, 52956513, 34677300, 203240812, + 12143107, 133374265, 225162431, 191946955}, + {83918388, 223877528, 122119236, 123340192, + 266784067, 263504429, 146143011, 198407736}, + {1, 0, 0, 0, 0, 0, 0, 0}, +}; + +void ScalarBaseMult(const uint8* scalar, Point* out) { + ::ScalarMult(out, kBasePoint, scalar, 28); +} + +void Add(const Point& a, const Point& b, Point* out) { + AddJacobian(out, a, b); +} + +void Negate(const Point& in, Point* out) { + // Guide to elliptic curve cryptography, page 89 suggests that (X : X+Y : Z) + // is the negative in Jacobian coordinates, but it doesn't actually appear to + // be true in testing so this performs the negation in affine coordinates. + FieldElement zinv, zinv_sq, y; + Invert(&zinv, in.z); + Square(&zinv_sq, zinv); + Mul(&out->x, in.x, zinv_sq); + Mul(&zinv_sq, zinv_sq, zinv); + Mul(&y, in.y, zinv_sq); + + Subtract(&out->y, kP, y); + Reduce(&out->y); + + memset(&out->z, 0, sizeof(out->z)); + out->z[0] = 1; +} + +} // namespace p224 + +} // namespace crypto diff --git a/crypto/p224.h b/crypto/p224.h new file mode 100644 index 0000000000..2efecfab55 --- /dev/null +++ b/crypto/p224.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_P224_H_ +#define CRYPTO_P224_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "crypto/crypto_export.h" + +namespace crypto { + +// P224 implements an elliptic curve group, commonly known as P224 and defined +// in FIPS 186-3, section D.2.2. +namespace p224 { + +// An element of the field (ℤ/pℤ) is represented with 8, 28-bit limbs in +// little endian order. +typedef uint32 FieldElement[8]; + +struct CRYPTO_EXPORT Point { + // SetFromString the value of the point from the 56 byte, external + // representation. The external point representation is an (x, y) pair of a + // point on the curve. Each field element is represented as a big-endian + // number < p. + bool SetFromString(const base::StringPiece& in); + + // ToString returns an external representation of the Point. + std::string ToString() const; + + // An Point is represented in Jacobian form (x/z², y/z³). + FieldElement x, y, z; +}; + +// kScalarBytes is the number of bytes needed to represent an element of the +// P224 field. +static const size_t kScalarBytes = 28; + +// ScalarMult computes *out = in*scalar where scalar is a 28-byte, big-endian +// number. +void CRYPTO_EXPORT ScalarMult(const Point& in, const uint8* scalar, Point* out); + +// ScalarBaseMult computes *out = g*scalar where g is the base point of the +// curve and scalar is a 28-byte, big-endian number. +void CRYPTO_EXPORT ScalarBaseMult(const uint8* scalar, Point* out); + +// Add computes *out = a+b. +void CRYPTO_EXPORT Add(const Point& a, const Point& b, Point* out); + +// Negate calculates out = -a; +void CRYPTO_EXPORT Negate(const Point& a, Point* out); + +} // namespace p224 + +} // namespace crypto + +#endif // CRYPTO_P224_H_ diff --git a/crypto/p224_spake.cc b/crypto/p224_spake.cc new file mode 100644 index 0000000000..a6dec40568 --- /dev/null +++ b/crypto/p224_spake.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This code implements SPAKE2, a variant of EKE: +// http://www.di.ens.fr/~pointche/pub.php?reference=AbPo04 + +#include <crypto/p224_spake.h> + +#include <algorithm> + +#include <base/logging.h> +#include <crypto/p224.h> +#include <crypto/random.h> +#include <crypto/secure_util.h> + +namespace { + +// The following two points (M and N in the protocol) are verifiable random +// points on the curve and can be generated with the following code: + +// #include <stdint.h> +// #include <stdio.h> +// #include <string.h> +// +// #include <openssl/ec.h> +// #include <openssl/obj_mac.h> +// #include <openssl/sha.h> +// +// static const char kSeed1[] = "P224 point generation seed (M)"; +// static const char kSeed2[] = "P224 point generation seed (N)"; +// +// void find_seed(const char* seed) { +// SHA256_CTX sha256; +// uint8_t digest[SHA256_DIGEST_LENGTH]; +// +// SHA256_Init(&sha256); +// SHA256_Update(&sha256, seed, strlen(seed)); +// SHA256_Final(digest, &sha256); +// +// BIGNUM x, y; +// EC_GROUP* p224 = EC_GROUP_new_by_curve_name(NID_secp224r1); +// EC_POINT* p = EC_POINT_new(p224); +// +// for (unsigned i = 0;; i++) { +// BN_init(&x); +// BN_bin2bn(digest, 28, &x); +// +// if (EC_POINT_set_compressed_coordinates_GFp( +// p224, p, &x, digest[28] & 1, NULL)) { +// BN_init(&y); +// EC_POINT_get_affine_coordinates_GFp(p224, p, &x, &y, NULL); +// char* x_str = BN_bn2hex(&x); +// char* y_str = BN_bn2hex(&y); +// printf("Found after %u iterations:\n%s\n%s\n", i, x_str, y_str); +// OPENSSL_free(x_str); +// OPENSSL_free(y_str); +// BN_free(&x); +// BN_free(&y); +// break; +// } +// +// SHA256_Init(&sha256); +// SHA256_Update(&sha256, digest, sizeof(digest)); +// SHA256_Final(digest, &sha256); +// +// BN_free(&x); +// } +// +// EC_POINT_free(p); +// EC_GROUP_free(p224); +// } +// +// int main() { +// find_seed(kSeed1); +// find_seed(kSeed2); +// return 0; +// } + +const crypto::p224::Point kM = { + {174237515, 77186811, 235213682, 33849492, + 33188520, 48266885, 177021753, 81038478}, + {104523827, 245682244, 266509668, 236196369, + 28372046, 145351378, 198520366, 113345994}, + {1, 0, 0, 0, 0, 0, 0, 0}, +}; + +const crypto::p224::Point kN = { + {136176322, 263523628, 251628795, 229292285, + 5034302, 185981975, 171998428, 11653062}, + {197567436, 51226044, 60372156, 175772188, + 42075930, 8083165, 160827401, 65097570}, + {1, 0, 0, 0, 0, 0, 0, 0}, +}; + +} // anonymous namespace + +namespace crypto { + +P224EncryptedKeyExchange::P224EncryptedKeyExchange( + PeerType peer_type, const base::StringPiece& password) + : state_(kStateInitial), + is_server_(peer_type == kPeerTypeServer) { + memset(&x_, 0, sizeof(x_)); + memset(&expected_authenticator_, 0, sizeof(expected_authenticator_)); + + // x_ is a random scalar. + RandBytes(x_, sizeof(x_)); + + // Calculate |password| hash to get SPAKE password value. + SHA256HashString(std::string(password.data(), password.length()), + pw_, sizeof(pw_)); + + Init(); +} + +void P224EncryptedKeyExchange::Init() { + // X = g**x_ + p224::Point X; + p224::ScalarBaseMult(x_, &X); + + // The client masks the Diffie-Hellman value, X, by adding M**pw and the + // server uses N**pw. + p224::Point MNpw; + p224::ScalarMult(is_server_ ? kN : kM, pw_, &MNpw); + + // X* = X + (N|M)**pw + p224::Point Xstar; + p224::Add(X, MNpw, &Xstar); + + next_message_ = Xstar.ToString(); +} + +const std::string& P224EncryptedKeyExchange::GetNextMessage() { + if (state_ == kStateInitial) { + state_ = kStateRecvDH; + return next_message_; + } else if (state_ == kStateSendHash) { + state_ = kStateRecvHash; + return next_message_; + } + + LOG(FATAL) << "P224EncryptedKeyExchange::GetNextMessage called in" + " bad state " << state_; + next_message_ = ""; + return next_message_; +} + +P224EncryptedKeyExchange::Result P224EncryptedKeyExchange::ProcessMessage( + const base::StringPiece& message) { + if (state_ == kStateRecvHash) { + // This is the final state of the protocol: we are reading the peer's + // authentication hash and checking that it matches the one that we expect. + if (message.size() != sizeof(expected_authenticator_)) { + error_ = "peer's hash had an incorrect size"; + return kResultFailed; + } + if (!SecureMemEqual(message.data(), expected_authenticator_, + message.size())) { + error_ = "peer's hash had incorrect value"; + return kResultFailed; + } + state_ = kStateDone; + return kResultSuccess; + } + + if (state_ != kStateRecvDH) { + LOG(FATAL) << "P224EncryptedKeyExchange::ProcessMessage called in" + " bad state " << state_; + error_ = "internal error"; + return kResultFailed; + } + + // Y* is the other party's masked, Diffie-Hellman value. + p224::Point Ystar; + if (!Ystar.SetFromString(message)) { + error_ = "failed to parse peer's masked Diffie-Hellman value"; + return kResultFailed; + } + + // We calculate the mask value: (N|M)**pw + p224::Point MNpw, minus_MNpw, Y, k; + p224::ScalarMult(is_server_ ? kM : kN, pw_, &MNpw); + p224::Negate(MNpw, &minus_MNpw); + + // Y = Y* - (N|M)**pw + p224::Add(Ystar, minus_MNpw, &Y); + + // K = Y**x_ + p224::ScalarMult(Y, x_, &k); + + // If everything worked out, then K is the same for both parties. + key_ = k.ToString(); + + std::string client_masked_dh, server_masked_dh; + if (is_server_) { + client_masked_dh = message.as_string(); + server_masked_dh = next_message_; + } else { + client_masked_dh = next_message_; + server_masked_dh = message.as_string(); + } + + // Now we calculate the hashes that each side will use to prove to the other + // that they derived the correct value for K. + uint8 client_hash[kSHA256Length], server_hash[kSHA256Length]; + CalculateHash(kPeerTypeClient, client_masked_dh, server_masked_dh, key_, + client_hash); + CalculateHash(kPeerTypeServer, client_masked_dh, server_masked_dh, key_, + server_hash); + + const uint8* my_hash = is_server_ ? server_hash : client_hash; + const uint8* their_hash = is_server_ ? client_hash : server_hash; + + next_message_ = + std::string(reinterpret_cast<const char*>(my_hash), kSHA256Length); + memcpy(expected_authenticator_, their_hash, kSHA256Length); + state_ = kStateSendHash; + return kResultPending; +} + +void P224EncryptedKeyExchange::CalculateHash( + PeerType peer_type, + const std::string& client_masked_dh, + const std::string& server_masked_dh, + const std::string& k, + uint8* out_digest) { + std::string hash_contents; + + if (peer_type == kPeerTypeServer) { + hash_contents = "server"; + } else { + hash_contents = "client"; + } + + hash_contents += client_masked_dh; + hash_contents += server_masked_dh; + hash_contents += + std::string(reinterpret_cast<const char *>(pw_), sizeof(pw_)); + hash_contents += k; + + SHA256HashString(hash_contents, out_digest, kSHA256Length); +} + +const std::string& P224EncryptedKeyExchange::error() const { + return error_; +} + +const std::string& P224EncryptedKeyExchange::GetKey() const { + DCHECK_EQ(state_, kStateDone); + return GetUnverifiedKey(); +} + +const std::string& P224EncryptedKeyExchange::GetUnverifiedKey() const { + // Key is already final when state is kStateSendHash. Subsequent states are + // used only for verification of the key. Some users may combine verification + // with sending verifiable data instead of |expected_authenticator_|. + DCHECK_GE(state_, kStateSendHash); + return key_; +} + +void P224EncryptedKeyExchange::SetXForTesting(const std::string& x) { + memset(&x_, 0, sizeof(x_)); + memcpy(&x_, x.data(), std::min(x.size(), sizeof(x_))); + Init(); +} + +} // namespace crypto diff --git a/crypto/p224_spake.h b/crypto/p224_spake.h new file mode 100644 index 0000000000..556b15cd09 --- /dev/null +++ b/crypto/p224_spake.h @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_P224_SPAKE_H_ +#define CRYPTO_P224_SPAKE_H_ + +#include <base/gtest_prod_util.h> +#include <base/strings/string_piece.h> +#include <crypto/p224.h> +#include <crypto/sha2.h> + +namespace crypto { + +// P224EncryptedKeyExchange implements SPAKE2, a variant of Encrypted +// Key Exchange. It allows two parties that have a secret common +// password to establish a common secure key by exchanging messages +// over an insecure channel without disclosing the password. +// +// The password can be low entropy as authenticating with an attacker only +// gives the attacker a one-shot password oracle. No other information about +// the password is leaked. (However, you must be sure to limit the number of +// permitted authentication attempts otherwise they get many one-shot oracles.) +// +// The protocol requires several RTTs (actually two, but you shouldn't assume +// that.) To use the object, call GetNextMessage() and pass that message to the +// peer. Get a message from the peer and feed it into ProcessMessage. Then +// examine the return value of ProcessMessage: +// kResultPending: Another round is required. Call GetNextMessage and repeat. +// kResultFailed: The authentication has failed. You can get a human readable +// error message by calling error(). +// kResultSuccess: The authentication was successful. +// +// In each exchange, each peer always sends a message. +class CRYPTO_EXPORT P224EncryptedKeyExchange { + public: + enum Result { + kResultPending, + kResultFailed, + kResultSuccess, + }; + + // PeerType's values are named client and server due to convention. But + // they could be called "A" and "B" as far as the protocol is concerned so + // long as the two parties don't both get the same label. + enum PeerType { + kPeerTypeClient, + kPeerTypeServer, + }; + + // peer_type: the type of the local authentication party. + // password: secret session password. Both parties to the + // authentication must pass the same value. For the case of a + // TLS connection, see RFC 5705. + P224EncryptedKeyExchange(PeerType peer_type, + const base::StringPiece& password); + + // GetNextMessage returns a byte string which must be passed to the other + // party in the authentication. + const std::string& GetNextMessage(); + + // ProcessMessage processes a message which must have been generated by a + // call to GetNextMessage() by the other party. + Result ProcessMessage(const base::StringPiece& message); + + // In the event that ProcessMessage() returns kResultFailed, error will + // return a human readable error message. + const std::string& error() const; + + // The key established as result of the key exchange. Must be called + // at then end after ProcessMessage() returns kResultSuccess. + const std::string& GetKey() const; + + // The key established as result of the key exchange. Can be called after + // the first ProcessMessage() + const std::string& GetUnverifiedKey() const; + + private: + // The authentication state machine is very simple and each party proceeds + // through each of these states, in order. + enum State { + kStateInitial, + kStateRecvDH, + kStateSendHash, + kStateRecvHash, + kStateDone, + }; + + FRIEND_TEST_ALL_PREFIXES(MutualAuth, ExpectedValues); + + void Init(); + + // Sets internal random scalar. Should be used by tests only. + void SetXForTesting(const std::string& x); + + State state_; + const bool is_server_; + // next_message_ contains a value for GetNextMessage() to return. + std::string next_message_; + std::string error_; + + // CalculateHash computes the verification hash for the given peer and writes + // |kSHA256Length| bytes at |out_digest|. + void CalculateHash( + PeerType peer_type, + const std::string& client_masked_dh, + const std::string& server_masked_dh, + const std::string& k, + uint8* out_digest); + + // x_ is the secret Diffie-Hellman exponent (see paper referenced in .cc + // file). + uint8 x_[p224::kScalarBytes]; + // pw_ is SHA256(P(password), P(session))[:28] where P() prepends a uint32, + // big-endian length prefix (see paper referenced in .cc file). + uint8 pw_[p224::kScalarBytes]; + // expected_authenticator_ is used to store the hash value expected from the + // other party. + uint8 expected_authenticator_[kSHA256Length]; + + std::string key_; +}; + +} // namespace crypto + +#endif // CRYPTO_P224_SPAKE_H_ diff --git a/crypto/p224_spake_unittest.cc b/crypto/p224_spake_unittest.cc new file mode 100644 index 0000000000..15b5be2684 --- /dev/null +++ b/crypto/p224_spake_unittest.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/p224_spake.h" + +#include <string> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +namespace { + +std::string HexEncodeString(const std::string& binary_data) { + return base::HexEncode(binary_data.c_str(), binary_data.size()); +} + +bool RunExchange(P224EncryptedKeyExchange* client, + P224EncryptedKeyExchange* server, + bool is_password_same) { + for (;;) { + std::string client_message, server_message; + client_message = client->GetNextMessage(); + server_message = server->GetNextMessage(); + + P224EncryptedKeyExchange::Result client_result, server_result; + client_result = client->ProcessMessage(server_message); + server_result = server->ProcessMessage(client_message); + + // Check that we never hit the case where only one succeeds. + EXPECT_EQ(client_result == P224EncryptedKeyExchange::kResultSuccess, + server_result == P224EncryptedKeyExchange::kResultSuccess); + + if (client_result == P224EncryptedKeyExchange::kResultFailed || + server_result == P224EncryptedKeyExchange::kResultFailed) { + return false; + } + + EXPECT_EQ(is_password_same, + client->GetUnverifiedKey() == server->GetUnverifiedKey()); + + if (client_result == P224EncryptedKeyExchange::kResultSuccess && + server_result == P224EncryptedKeyExchange::kResultSuccess) { + return true; + } + + EXPECT_EQ(P224EncryptedKeyExchange::kResultPending, client_result); + EXPECT_EQ(P224EncryptedKeyExchange::kResultPending, server_result); + } +} + +const char kPassword[] = "foo"; + +} // namespace + +TEST(MutualAuth, CorrectAuth) { + P224EncryptedKeyExchange client( + P224EncryptedKeyExchange::kPeerTypeClient, kPassword); + P224EncryptedKeyExchange server( + P224EncryptedKeyExchange::kPeerTypeServer, kPassword); + + EXPECT_TRUE(RunExchange(&client, &server, true)); + EXPECT_EQ(client.GetKey(), server.GetKey()); +} + +TEST(MutualAuth, IncorrectPassword) { + P224EncryptedKeyExchange client( + P224EncryptedKeyExchange::kPeerTypeClient, + kPassword); + P224EncryptedKeyExchange server( + P224EncryptedKeyExchange::kPeerTypeServer, + "wrongpassword"); + + EXPECT_FALSE(RunExchange(&client, &server, false)); +} + +TEST(MutualAuth, ExpectedValues) { + P224EncryptedKeyExchange client(P224EncryptedKeyExchange::kPeerTypeClient, + kPassword); + client.SetXForTesting("Client x"); + P224EncryptedKeyExchange server(P224EncryptedKeyExchange::kPeerTypeServer, + kPassword); + server.SetXForTesting("Server x"); + + std::string client_message = client.GetNextMessage(); + EXPECT_EQ( + "3508EF7DECC8AB9F9C439FBB0154288BBECC0A82E8448F4CF29554EB" + "BE9D486686226255EAD1D077C635B1A41F46AC91D7F7F32CED9EC3E0", + HexEncodeString(client_message)); + + std::string server_message = server.GetNextMessage(); + EXPECT_EQ( + "A3088C18B75D2C2B107105661AEC85424777475EB29F1DDFB8C14AFB" + "F1603D0DF38413A00F420ACF2059E7997C935F5A957A193D09A2B584", + HexEncodeString(server_message)); + + EXPECT_EQ(P224EncryptedKeyExchange::kResultPending, + client.ProcessMessage(server_message)); + EXPECT_EQ(P224EncryptedKeyExchange::kResultPending, + server.ProcessMessage(client_message)); + + EXPECT_EQ(client.GetUnverifiedKey(), server.GetUnverifiedKey()); + // Must stay the same. External implementations should be able to pair with. + EXPECT_EQ( + "CE7CCFC435CDA4F01EC8826788B1F8B82EF7D550A34696B371096E64" + "C487D4FE193F7D1A6FF6820BC7F807796BA3889E8F999BBDEFC32FFA", + HexEncodeString(server.GetUnverifiedKey())); + + EXPECT_TRUE(RunExchange(&client, &server, true)); + EXPECT_EQ(client.GetKey(), server.GetKey()); +} + +TEST(MutualAuth, Fuzz) { + static const unsigned kIterations = 40; + + for (unsigned i = 0; i < kIterations; i++) { + P224EncryptedKeyExchange client( + P224EncryptedKeyExchange::kPeerTypeClient, kPassword); + P224EncryptedKeyExchange server( + P224EncryptedKeyExchange::kPeerTypeServer, kPassword); + + // We'll only be testing small values of i, but we don't want that to bias + // the test coverage. So we disperse the value of i by multiplying by the + // FNV, 32-bit prime, producing a poor-man's PRNG. + const uint32 rand = i * 16777619; + + for (unsigned round = 0;; round++) { + std::string client_message, server_message; + client_message = client.GetNextMessage(); + server_message = server.GetNextMessage(); + + if ((rand & 1) == round) { + const bool server_or_client = rand & 2; + std::string* m = server_or_client ? &server_message : &client_message; + if (rand & 4) { + // Truncate + *m = m->substr(0, (i >> 3) % m->size()); + } else { + // Corrupt + const size_t bits = m->size() * 8; + const size_t bit_to_corrupt = (rand >> 3) % bits; + const_cast<char*>(m->data())[bit_to_corrupt / 8] ^= + 1 << (bit_to_corrupt % 8); + } + } + + P224EncryptedKeyExchange::Result client_result, server_result; + client_result = client.ProcessMessage(server_message); + server_result = server.ProcessMessage(client_message); + + // If we have corrupted anything, we expect the authentication to fail, + // although one side can succeed if we happen to corrupt the second round + // message to the other. + ASSERT_FALSE( + client_result == P224EncryptedKeyExchange::kResultSuccess && + server_result == P224EncryptedKeyExchange::kResultSuccess); + + if (client_result == P224EncryptedKeyExchange::kResultFailed || + server_result == P224EncryptedKeyExchange::kResultFailed) { + break; + } + + ASSERT_EQ(P224EncryptedKeyExchange::kResultPending, + client_result); + ASSERT_EQ(P224EncryptedKeyExchange::kResultPending, + server_result); + } + } +} + +} // namespace crypto diff --git a/crypto/p224_unittest.cc b/crypto/p224_unittest.cc new file mode 100644 index 0000000000..aaf5f59f47 --- /dev/null +++ b/crypto/p224_unittest.cc @@ -0,0 +1,824 @@ +// Copyright (c) 2012 The Chromium 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 <string.h> +#include <stdio.h> + +#include "crypto/p224.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace crypto { + +using p224::Point; + +// kBasePointExternal is the P224 base point in external representation. +static const uint8 kBasePointExternal[56] = { + 0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f, + 0x32, 0x13, 0x90, 0xb9, 0x4a, 0x03, 0xc1, 0xd3, + 0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6, + 0x11, 0x5c, 0x1d, 0x21, 0xbd, 0x37, 0x63, 0x88, + 0xb5, 0xf7, 0x23, 0xfb, 0x4c, 0x22, 0xdf, 0xe6, + 0xcd, 0x43, 0x75, 0xa0, 0x5a, 0x07, 0x47, 0x64, + 0x44, 0xd5, 0x81, 0x99, 0x85, 0x00, 0x7e, 0x34, +}; + +// TestVector represents a test of scalar multiplication of the base point. +// |scalar| is a big-endian scalar and |affine| is the external representation +// of g*scalar. +struct TestVector { + uint8 scalar[28]; + uint8 affine[28*2]; +}; + +static const int kNumNISTTestVectors = 52; + +// kNISTTestVectors are the NIST test vectors for P224. +static const TestVector kNISTTestVectors[kNumNISTTestVectors] = { + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01}, + {0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f, + 0x32, 0x13, 0x90, 0xb9, 0x4a, 0x03, 0xc1, 0xd3, + 0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6, + 0x11, 0x5c, 0x1d, 0x21, 0xbd, 0x37, 0x63, 0x88, + 0xb5, 0xf7, 0x23, 0xfb, 0x4c, 0x22, 0xdf, 0xe6, + 0xcd, 0x43, 0x75, 0xa0, 0x5a, 0x07, 0x47, 0x64, + 0x44, 0xd5, 0x81, 0x99, 0x85, 0x00, 0x7e, 0x34 + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, }, + + {0x70, 0x6a, 0x46, 0xdc, 0x76, 0xdc, 0xb7, 0x67, + 0x98, 0xe6, 0x0e, 0x6d, 0x89, 0x47, 0x47, 0x88, + 0xd1, 0x6d, 0xc1, 0x80, 0x32, 0xd2, 0x68, 0xfd, + 0x1a, 0x70, 0x4f, 0xa6, 0x1c, 0x2b, 0x76, 0xa7, + 0xbc, 0x25, 0xe7, 0x70, 0x2a, 0x70, 0x4f, 0xa9, + 0x86, 0x89, 0x28, 0x49, 0xfc, 0xa6, 0x29, 0x48, + 0x7a, 0xcf, 0x37, 0x09, 0xd2, 0xe4, 0xe8, 0xbb, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, }, + {0xdf, 0x1b, 0x1d, 0x66, 0xa5, 0x51, 0xd0, 0xd3, + 0x1e, 0xff, 0x82, 0x25, 0x58, 0xb9, 0xd2, 0xcc, + 0x75, 0xc2, 0x18, 0x02, 0x79, 0xfe, 0x0d, 0x08, + 0xfd, 0x89, 0x6d, 0x04, 0xa3, 0xf7, 0xf0, 0x3c, + 0xad, 0xd0, 0xbe, 0x44, 0x4c, 0x0a, 0xa5, 0x68, + 0x30, 0x13, 0x0d, 0xdf, 0x77, 0xd3, 0x17, 0x34, + 0x4e, 0x1a, 0xf3, 0x59, 0x19, 0x81, 0xa9, 0x25, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, }, + {0xae, 0x99, 0xfe, 0xeb, 0xb5, 0xd2, 0x69, 0x45, + 0xb5, 0x48, 0x92, 0x09, 0x2a, 0x8a, 0xee, 0x02, + 0x91, 0x29, 0x30, 0xfa, 0x41, 0xcd, 0x11, 0x4e, + 0x40, 0x44, 0x73, 0x01, 0x04, 0x82, 0x58, 0x0a, + 0x0e, 0xc5, 0xbc, 0x47, 0xe8, 0x8b, 0xc8, 0xc3, + 0x78, 0x63, 0x2c, 0xd1, 0x96, 0xcb, 0x3f, 0xa0, + 0x58, 0xa7, 0x11, 0x4e, 0xb0, 0x30, 0x54, 0xc9, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, }, + {0x31, 0xc4, 0x9a, 0xe7, 0x5b, 0xce, 0x78, 0x07, + 0xcd, 0xff, 0x22, 0x05, 0x5d, 0x94, 0xee, 0x90, + 0x21, 0xfe, 0xdb, 0xb5, 0xab, 0x51, 0xc5, 0x75, + 0x26, 0xf0, 0x11, 0xaa, 0x27, 0xe8, 0xbf, 0xf1, + 0x74, 0x56, 0x35, 0xec, 0x5b, 0xa0, 0xc9, 0xf1, + 0xc2, 0xed, 0xe1, 0x54, 0x14, 0xc6, 0x50, 0x7d, + 0x29, 0xff, 0xe3, 0x7e, 0x79, 0x0a, 0x07, 0x9b, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, }, + {0x1f, 0x24, 0x83, 0xf8, 0x25, 0x72, 0x25, 0x1f, + 0xca, 0x97, 0x5f, 0xea, 0x40, 0xdb, 0x82, 0x1d, + 0xf8, 0xad, 0x82, 0xa3, 0xc0, 0x02, 0xee, 0x6c, + 0x57, 0x11, 0x24, 0x08, 0x89, 0xfa, 0xf0, 0xcc, + 0xb7, 0x50, 0xd9, 0x9b, 0x55, 0x3c, 0x57, 0x4f, + 0xad, 0x7e, 0xcf, 0xb0, 0x43, 0x85, 0x86, 0xeb, + 0x39, 0x52, 0xaf, 0x5b, 0x4b, 0x15, 0x3c, 0x7e, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, }, + {0xdb, 0x2f, 0x6b, 0xe6, 0x30, 0xe2, 0x46, 0xa5, + 0xcf, 0x7d, 0x99, 0xb8, 0x51, 0x94, 0xb1, 0x23, + 0xd4, 0x87, 0xe2, 0xd4, 0x66, 0xb9, 0x4b, 0x24, + 0xa0, 0x3c, 0x3e, 0x28, 0x0f, 0x3a, 0x30, 0x08, + 0x54, 0x97, 0xf2, 0xf6, 0x11, 0xee, 0x25, 0x17, + 0xb1, 0x63, 0xef, 0x8c, 0x53, 0xb7, 0x15, 0xd1, + 0x8b, 0xb4, 0xe4, 0x80, 0x8d, 0x02, 0xb9, 0x63, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, }, + {0x85, 0x8e, 0x6f, 0x9c, 0xc6, 0xc1, 0x2c, 0x31, + 0xf5, 0xdf, 0x12, 0x4a, 0xa7, 0x77, 0x67, 0xb0, + 0x5c, 0x8b, 0xc0, 0x21, 0xbd, 0x68, 0x3d, 0x2b, + 0x55, 0x57, 0x15, 0x50, 0x04, 0x6d, 0xcd, 0x3e, + 0xa5, 0xc4, 0x38, 0x98, 0xc5, 0xc5, 0xfc, 0x4f, + 0xda, 0xc7, 0xdb, 0x39, 0xc2, 0xf0, 0x2e, 0xbe, + 0xe4, 0xe3, 0x54, 0x1d, 0x1e, 0x78, 0x04, 0x7a, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, }, + {0x2f, 0xdc, 0xcc, 0xfe, 0xe7, 0x20, 0xa7, 0x7e, + 0xf6, 0xcb, 0x3b, 0xfb, 0xb4, 0x47, 0xf9, 0x38, + 0x31, 0x17, 0xe3, 0xda, 0xa4, 0xa0, 0x7e, 0x36, + 0xed, 0x15, 0xf7, 0x8d, 0x37, 0x17, 0x32, 0xe4, + 0xf4, 0x1b, 0xf4, 0xf7, 0x88, 0x30, 0x35, 0xe6, + 0xa7, 0x9f, 0xce, 0xdc, 0x0e, 0x19, 0x6e, 0xb0, + 0x7b, 0x48, 0x17, 0x16, 0x97, 0x51, 0x74, 0x63, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0a, }, + {0xae, 0xa9, 0xe1, 0x7a, 0x30, 0x65, 0x17, 0xeb, + 0x89, 0x15, 0x2a, 0xa7, 0x09, 0x6d, 0x2c, 0x38, + 0x1e, 0xc8, 0x13, 0xc5, 0x1a, 0xa8, 0x80, 0xe7, + 0xbe, 0xe2, 0xc0, 0xfd, 0x39, 0xbb, 0x30, 0xea, + 0xb3, 0x37, 0xe0, 0xa5, 0x21, 0xb6, 0xcb, 0xa1, + 0xab, 0xe4, 0xb2, 0xb3, 0xa3, 0xe5, 0x24, 0xc1, + 0x4a, 0x3f, 0xe3, 0xeb, 0x11, 0x6b, 0x65, 0x5f, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, }, + {0xef, 0x53, 0xb6, 0x29, 0x4a, 0xca, 0x43, 0x1f, + 0x0f, 0x3c, 0x22, 0xdc, 0x82, 0xeb, 0x90, 0x50, + 0x32, 0x4f, 0x1d, 0x88, 0xd3, 0x77, 0xe7, 0x16, + 0x44, 0x8e, 0x50, 0x7c, 0x20, 0xb5, 0x10, 0x00, + 0x40, 0x92, 0xe9, 0x66, 0x36, 0xcf, 0xb7, 0xe3, + 0x2e, 0xfd, 0xed, 0x82, 0x65, 0xc2, 0x66, 0xdf, + 0xb7, 0x54, 0xfa, 0x6d, 0x64, 0x91, 0xa6, 0xda, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, }, + {0x6e, 0x31, 0xee, 0x1d, 0xc1, 0x37, 0xf8, 0x1b, + 0x05, 0x67, 0x52, 0xe4, 0xde, 0xab, 0x14, 0x43, + 0xa4, 0x81, 0x03, 0x3e, 0x9b, 0x4c, 0x93, 0xa3, + 0x04, 0x4f, 0x4f, 0x7a, 0x20, 0x7d, 0xdd, 0xf0, + 0x38, 0x5b, 0xfd, 0xea, 0xb6, 0xe9, 0xac, 0xda, + 0x8d, 0xa0, 0x6b, 0x3b, 0xbe, 0xf2, 0x24, 0xa9, + 0x3a, 0xb1, 0xe9, 0xe0, 0x36, 0x10, 0x9d, 0x13, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0d, }, + {0x34, 0xe8, 0xe1, 0x7a, 0x43, 0x0e, 0x43, 0x28, + 0x97, 0x93, 0xc3, 0x83, 0xfa, 0xc9, 0x77, 0x42, + 0x47, 0xb4, 0x0e, 0x9e, 0xbd, 0x33, 0x66, 0x98, + 0x1f, 0xcf, 0xae, 0xca, 0x25, 0x28, 0x19, 0xf7, + 0x1c, 0x7f, 0xb7, 0xfb, 0xcb, 0x15, 0x9b, 0xe3, + 0x37, 0xd3, 0x7d, 0x33, 0x36, 0xd7, 0xfe, 0xb9, + 0x63, 0x72, 0x4f, 0xdf, 0xb0, 0xec, 0xb7, 0x67, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0e, }, + {0xa5, 0x36, 0x40, 0xc8, 0x3d, 0xc2, 0x08, 0x60, + 0x3d, 0xed, 0x83, 0xe4, 0xec, 0xf7, 0x58, 0xf2, + 0x4c, 0x35, 0x7d, 0x7c, 0xf4, 0x80, 0x88, 0xb2, + 0xce, 0x01, 0xe9, 0xfa, 0xd5, 0x81, 0x4c, 0xd7, + 0x24, 0x19, 0x9c, 0x4a, 0x5b, 0x97, 0x4a, 0x43, + 0x68, 0x5f, 0xbf, 0x5b, 0x8b, 0xac, 0x69, 0x45, + 0x9c, 0x94, 0x69, 0xbc, 0x8f, 0x23, 0xcc, 0xaf, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0f, }, + {0xba, 0xa4, 0xd8, 0x63, 0x55, 0x11, 0xa7, 0xd2, + 0x88, 0xae, 0xbe, 0xed, 0xd1, 0x2c, 0xe5, 0x29, + 0xff, 0x10, 0x2c, 0x91, 0xf9, 0x7f, 0x86, 0x7e, + 0x21, 0x91, 0x6b, 0xf9, 0x97, 0x9a, 0x5f, 0x47, + 0x59, 0xf8, 0x0f, 0x4f, 0xb4, 0xec, 0x2e, 0x34, + 0xf5, 0x56, 0x6d, 0x59, 0x56, 0x80, 0xa1, 0x17, + 0x35, 0xe7, 0xb6, 0x10, 0x46, 0x12, 0x79, 0x89, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, }, + {0x0b, 0x6e, 0xc4, 0xfe, 0x17, 0x77, 0x38, 0x24, + 0x04, 0xef, 0x67, 0x99, 0x97, 0xba, 0x8d, 0x1c, + 0xc5, 0xcd, 0x8e, 0x85, 0x34, 0x92, 0x59, 0xf5, + 0x90, 0xc4, 0xc6, 0x6d, 0x33, 0x99, 0xd4, 0x64, + 0x34, 0x59, 0x06, 0xb1, 0x1b, 0x00, 0xe3, 0x63, + 0xef, 0x42, 0x92, 0x21, 0xf2, 0xec, 0x72, 0x0d, + 0x2f, 0x66, 0x5d, 0x7d, 0xea, 0xd5, 0xb4, 0x82, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x11, }, + {0xb8, 0x35, 0x7c, 0x3a, 0x6c, 0xee, 0xf2, 0x88, + 0x31, 0x0e, 0x17, 0xb8, 0xbf, 0xef, 0xf9, 0x20, + 0x08, 0x46, 0xca, 0x8c, 0x19, 0x42, 0x49, 0x7c, + 0x48, 0x44, 0x03, 0xbc, 0xff, 0x14, 0x9e, 0xfa, + 0x66, 0x06, 0xa6, 0xbd, 0x20, 0xef, 0x7d, 0x1b, + 0x06, 0xbd, 0x92, 0xf6, 0x90, 0x46, 0x39, 0xdc, + 0xe5, 0x17, 0x4d, 0xb6, 0xcc, 0x55, 0x4a, 0x26, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12, }, + {0xc9, 0xff, 0x61, 0xb0, 0x40, 0x87, 0x4c, 0x05, + 0x68, 0x47, 0x92, 0x16, 0x82, 0x4a, 0x15, 0xea, + 0xb1, 0xa8, 0x38, 0xa7, 0x97, 0xd1, 0x89, 0x74, + 0x62, 0x26, 0xe4, 0xcc, 0xea, 0x98, 0xd6, 0x0e, + 0x5f, 0xfc, 0x9b, 0x8f, 0xcf, 0x99, 0x9f, 0xab, + 0x1d, 0xf7, 0xe7, 0xef, 0x70, 0x84, 0xf2, 0x0d, + 0xdb, 0x61, 0xbb, 0x04, 0x5a, 0x6c, 0xe0, 0x02, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x13, }, + {0xa1, 0xe8, 0x1c, 0x04, 0xf3, 0x0c, 0xe2, 0x01, + 0xc7, 0xc9, 0xac, 0xe7, 0x85, 0xed, 0x44, 0xcc, + 0x33, 0xb4, 0x55, 0xa0, 0x22, 0xf2, 0xac, 0xdb, + 0xc6, 0xca, 0xe8, 0x3c, 0xdc, 0xf1, 0xf6, 0xc3, + 0xdb, 0x09, 0xc7, 0x0a, 0xcc, 0x25, 0x39, 0x1d, + 0x49, 0x2f, 0xe2, 0x5b, 0x4a, 0x18, 0x0b, 0xab, + 0xd6, 0xce, 0xa3, 0x56, 0xc0, 0x47, 0x19, 0xcd, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x14, }, + {0xfc, 0xc7, 0xf2, 0xb4, 0x5d, 0xf1, 0xcd, 0x5a, + 0x3c, 0x0c, 0x07, 0x31, 0xca, 0x47, 0xa8, 0xaf, + 0x75, 0xcf, 0xb0, 0x34, 0x7e, 0x83, 0x54, 0xee, + 0xfe, 0x78, 0x24, 0x55, 0x0d, 0x5d, 0x71, 0x10, + 0x27, 0x4c, 0xba, 0x7c, 0xde, 0xe9, 0x0e, 0x1a, + 0x8b, 0x0d, 0x39, 0x4c, 0x37, 0x6a, 0x55, 0x73, + 0xdb, 0x6b, 0xe0, 0xbf, 0x27, 0x47, 0xf5, 0x30, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x8e, 0xbb, 0xb9, + 0x5e, 0xed, 0x0e, 0x13, }, + {0x61, 0xf0, 0x77, 0xc6, 0xf6, 0x2e, 0xd8, 0x02, + 0xda, 0xd7, 0xc2, 0xf3, 0x8f, 0x5c, 0x67, 0xf2, + 0xcc, 0x45, 0x36, 0x01, 0xe6, 0x1b, 0xd0, 0x76, + 0xbb, 0x46, 0x17, 0x9e, 0x22, 0x72, 0xf9, 0xe9, + 0xf5, 0x93, 0x3e, 0x70, 0x38, 0x8e, 0xe6, 0x52, + 0x51, 0x34, 0x43, 0xb5, 0xe2, 0x89, 0xdd, 0x13, + 0x5d, 0xcc, 0x0d, 0x02, 0x99, 0xb2, 0x25, 0xe4, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x9d, 0x89, + 0x3d, 0x4c, 0xdd, 0x74, 0x72, 0x46, 0xcd, 0xca, + 0x43, 0x59, 0x0e, 0x13, }, + {0x02, 0x98, 0x95, 0xf0, 0xaf, 0x49, 0x6b, 0xfc, + 0x62, 0xb6, 0xef, 0x8d, 0x8a, 0x65, 0xc8, 0x8c, + 0x61, 0x39, 0x49, 0xb0, 0x36, 0x68, 0xaa, 0xb4, + 0xf0, 0x42, 0x9e, 0x35, 0x3e, 0xa6, 0xe5, 0x3f, + 0x9a, 0x84, 0x1f, 0x20, 0x19, 0xec, 0x24, 0xbd, + 0xe1, 0xa7, 0x56, 0x77, 0xaa, 0x9b, 0x59, 0x02, + 0xe6, 0x10, 0x81, 0xc0, 0x10, 0x64, 0xde, 0x93, + }, + }, + { + {0x41, 0xff, 0xc1, 0xff, 0xff, 0xfe, 0x01, 0xff, + 0xfc, 0x00, 0x03, 0xff, 0xfe, 0x00, 0x07, 0xc0, + 0x01, 0xff, 0xf0, 0x00, 0x03, 0xff, 0xf0, 0x7f, + 0xfe, 0x00, 0x07, 0xc0, }, + {0xab, 0x68, 0x99, 0x30, 0xbc, 0xae, 0x4a, 0x4a, + 0xa5, 0xf5, 0xcb, 0x08, 0x5e, 0x82, 0x3e, 0x8a, + 0xe3, 0x0f, 0xd3, 0x65, 0xeb, 0x1d, 0xa4, 0xab, + 0xa9, 0xcf, 0x03, 0x79, 0x33, 0x45, 0xa1, 0x21, + 0xbb, 0xd2, 0x33, 0x54, 0x8a, 0xf0, 0xd2, 0x10, + 0x65, 0x4e, 0xb4, 0x0b, 0xab, 0x78, 0x8a, 0x03, + 0x66, 0x64, 0x19, 0xbe, 0x6f, 0xbd, 0x34, 0xe7, + }, + }, + { + {0x7f, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xc0, 0x03, + 0xff, 0xff, 0xfc, 0x00, 0x7f, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x0e, 0x00, 0xff, }, + {0xbd, 0xb6, 0xa8, 0x81, 0x7c, 0x1f, 0x89, 0xda, + 0x1c, 0x2f, 0x3d, 0xd8, 0xe9, 0x7f, 0xeb, 0x44, + 0x94, 0xf2, 0xed, 0x30, 0x2a, 0x4c, 0xe2, 0xbc, + 0x7f, 0x5f, 0x40, 0x25, 0x4c, 0x70, 0x20, 0xd5, + 0x7c, 0x00, 0x41, 0x18, 0x89, 0x46, 0x2d, 0x77, + 0xa5, 0x43, 0x8b, 0xb4, 0xe9, 0x7d, 0x17, 0x77, + 0x00, 0xbf, 0x72, 0x43, 0xa0, 0x7f, 0x16, 0x80, + }, + }, + { + {0x7f, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xf8, 0xff, 0xff, + 0xc0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, + 0x00, 0x0f, 0xff, 0xff, }, + {0xd5, 0x8b, 0x61, 0xaa, 0x41, 0xc3, 0x2d, 0xd5, + 0xeb, 0xa4, 0x62, 0x64, 0x7d, 0xba, 0x75, 0xc5, + 0xd6, 0x7c, 0x83, 0x60, 0x6c, 0x0a, 0xf2, 0xbd, + 0x92, 0x84, 0x46, 0xa9, 0xd2, 0x4b, 0xa6, 0xa8, + 0x37, 0xbe, 0x04, 0x60, 0xdd, 0x10, 0x7a, 0xe7, + 0x77, 0x25, 0x69, 0x6d, 0x21, 0x14, 0x46, 0xc5, + 0x60, 0x9b, 0x45, 0x95, 0x97, 0x6b, 0x16, 0xbd, + }, + }, + { + {0x7f, 0xff, 0xff, 0xc0, 0x00, 0xff, 0xfe, 0x3f, + 0xff, 0xfc, 0x10, 0x00, 0x00, 0x20, 0x00, 0x3f, + 0xff, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, + 0x3f, 0xff, 0xff, 0xff, }, + {0xdc, 0x9f, 0xa7, 0x79, 0x78, 0xa0, 0x05, 0x51, + 0x09, 0x80, 0xe9, 0x29, 0xa1, 0x48, 0x5f, 0x63, + 0x71, 0x6d, 0xf6, 0x95, 0xd7, 0xa0, 0xc1, 0x8b, + 0xb5, 0x18, 0xdf, 0x03, 0xed, 0xe2, 0xb0, 0x16, + 0xf2, 0xdd, 0xff, 0xc2, 0xa8, 0xc0, 0x15, 0xb1, + 0x34, 0x92, 0x82, 0x75, 0xce, 0x09, 0xe5, 0x66, + 0x1b, 0x7a, 0xb1, 0x4c, 0xe0, 0xd1, 0xd4, 0x03, + }, + }, + { + {0x70, 0x01, 0xf0, 0x00, 0x1c, 0x00, 0x01, 0xc0, + 0x00, 0x00, 0x1f, 0xff, 0xff, 0xfc, 0x00, 0x00, + 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x0f, 0xc0, 0x00, + 0x00, 0x01, 0xfc, 0x00, }, + {0x49, 0x9d, 0x8b, 0x28, 0x29, 0xcf, 0xb8, 0x79, + 0xc9, 0x01, 0xf7, 0xd8, 0x5d, 0x35, 0x70, 0x45, + 0xed, 0xab, 0x55, 0x02, 0x88, 0x24, 0xd0, 0xf0, + 0x5b, 0xa2, 0x79, 0xba, 0xbf, 0x92, 0x95, 0x37, + 0xb0, 0x6e, 0x40, 0x15, 0x91, 0x96, 0x39, 0xd9, + 0x4f, 0x57, 0x83, 0x8f, 0xa3, 0x3f, 0xc3, 0xd9, + 0x52, 0x59, 0x8d, 0xcd, 0xbb, 0x44, 0xd6, 0x38, + }, + }, + { + {0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, + 0x00, 0xff, 0xf0, 0x30, 0x00, 0x1f, 0x00, 0x00, + 0xff, 0xff, 0xf0, 0x00, 0x00, 0x38, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, }, + {0x82, 0x46, 0xc9, 0x99, 0x13, 0x71, 0x86, 0x63, + 0x2c, 0x5f, 0x9e, 0xdd, 0xf3, 0xb1, 0xb0, 0xe1, + 0x76, 0x4c, 0x5e, 0x8b, 0xd0, 0xe0, 0xd8, 0xa5, + 0x54, 0xb9, 0xcb, 0x77, 0xe8, 0x0e, 0xd8, 0x66, + 0x0b, 0xc1, 0xcb, 0x17, 0xac, 0x7d, 0x84, 0x5b, + 0xe4, 0x0a, 0x7a, 0x02, 0x2d, 0x33, 0x06, 0xf1, + 0x16, 0xae, 0x9f, 0x81, 0xfe, 0xa6, 0x59, 0x47, + }, + }, + { + {0x7f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xfe, 0x08, 0x00, 0x00, 0x1f, + 0xf0, 0x00, 0x1f, 0xff, }, + {0x66, 0x70, 0xc2, 0x0a, 0xfc, 0xce, 0xae, 0xa6, + 0x72, 0xc9, 0x7f, 0x75, 0xe2, 0xe9, 0xdd, 0x5c, + 0x84, 0x60, 0xe5, 0x4b, 0xb3, 0x85, 0x38, 0xeb, + 0xb4, 0xbd, 0x30, 0xeb, 0xf2, 0x80, 0xd8, 0x00, + 0x8d, 0x07, 0xa4, 0xca, 0xf5, 0x42, 0x71, 0xf9, + 0x93, 0x52, 0x7d, 0x46, 0xff, 0x3f, 0xf4, 0x6f, + 0xd1, 0x19, 0x0a, 0x3f, 0x1f, 0xaa, 0x4f, 0x74, + }, + }, + { + {0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xc0, 0x00, 0x07, 0xff, 0xff, 0xe0, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, }, + {0x00, 0x0e, 0xca, 0x93, 0x42, 0x47, 0x42, 0x5c, + 0xfd, 0x94, 0x9b, 0x79, 0x5c, 0xb5, 0xce, 0x1e, + 0xff, 0x40, 0x15, 0x50, 0x38, 0x6e, 0x28, 0xd1, + 0xa4, 0xc5, 0xa8, 0xeb, 0xd4, 0xc0, 0x10, 0x40, + 0xdb, 0xa1, 0x96, 0x28, 0x93, 0x1b, 0xc8, 0x85, + 0x53, 0x70, 0x31, 0x7c, 0x72, 0x2c, 0xbd, 0x9c, + 0xa6, 0x15, 0x69, 0x85, 0xf1, 0xc2, 0xe9, 0xce, + }, + }, + { + {0x7f, 0xff, 0xfc, 0x03, 0xff, 0x80, 0x7f, 0xff, + 0xe0, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x0f, 0xff, + 0x80, 0x00, 0x01, 0xff, 0xff, 0x00, 0x01, 0xff, + 0xff, 0xfe, 0x00, 0x1f, }, + {0xef, 0x35, 0x3b, 0xf5, 0xc7, 0x3c, 0xd5, 0x51, + 0xb9, 0x6d, 0x59, 0x6f, 0xbc, 0x9a, 0x67, 0xf1, + 0x6d, 0x61, 0xdd, 0x9f, 0xe5, 0x6a, 0xf1, 0x9d, + 0xe1, 0xfb, 0xa9, 0xcd, 0x21, 0x77, 0x1b, 0x9c, + 0xdc, 0xe3, 0xe8, 0x43, 0x0c, 0x09, 0xb3, 0x83, + 0x8b, 0xe7, 0x0b, 0x48, 0xc2, 0x1e, 0x15, 0xbc, + 0x09, 0xee, 0x1f, 0x2d, 0x79, 0x45, 0xb9, 0x1f, + }, + }, + { + {0x00, 0x00, 0x00, 0x07, 0xff, 0xc0, 0x7f, 0xff, + 0xff, 0xff, 0x01, 0xff, 0xfe, 0x03, 0xff, 0xfe, + 0x40, 0x00, 0x38, 0x00, 0x07, 0xe0, 0x00, 0x3f, + 0xfe, 0x00, 0x00, 0x00, }, + {0x40, 0x36, 0x05, 0x2a, 0x30, 0x91, 0xeb, 0x48, + 0x10, 0x46, 0xad, 0x32, 0x89, 0xc9, 0x5d, 0x3a, + 0xc9, 0x05, 0xca, 0x00, 0x23, 0xde, 0x2c, 0x03, + 0xec, 0xd4, 0x51, 0xcf, 0xd7, 0x68, 0x16, 0x5a, + 0x38, 0xa2, 0xb9, 0x6f, 0x81, 0x25, 0x86, 0xa9, + 0xd5, 0x9d, 0x41, 0x36, 0x03, 0x5d, 0x9c, 0x85, + 0x3a, 0x5b, 0xf2, 0xe1, 0xc8, 0x6a, 0x49, 0x93, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x29, }, + {0xfc, 0xc7, 0xf2, 0xb4, 0x5d, 0xf1, 0xcd, 0x5a, + 0x3c, 0x0c, 0x07, 0x31, 0xca, 0x47, 0xa8, 0xaf, + 0x75, 0xcf, 0xb0, 0x34, 0x7e, 0x83, 0x54, 0xee, + 0xfe, 0x78, 0x24, 0x55, 0xf2, 0xa2, 0x8e, 0xef, + 0xd8, 0xb3, 0x45, 0x83, 0x21, 0x16, 0xf1, 0xe5, + 0x74, 0xf2, 0xc6, 0xb2, 0xc8, 0x95, 0xaa, 0x8c, + 0x24, 0x94, 0x1f, 0x40, 0xd8, 0xb8, 0x0a, 0xd1, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x2a, }, + {0xa1, 0xe8, 0x1c, 0x04, 0xf3, 0x0c, 0xe2, 0x01, + 0xc7, 0xc9, 0xac, 0xe7, 0x85, 0xed, 0x44, 0xcc, + 0x33, 0xb4, 0x55, 0xa0, 0x22, 0xf2, 0xac, 0xdb, + 0xc6, 0xca, 0xe8, 0x3c, 0x23, 0x0e, 0x09, 0x3c, + 0x24, 0xf6, 0x38, 0xf5, 0x33, 0xda, 0xc6, 0xe2, + 0xb6, 0xd0, 0x1d, 0xa3, 0xb5, 0xe7, 0xf4, 0x54, + 0x29, 0x31, 0x5c, 0xa9, 0x3f, 0xb8, 0xe6, 0x34, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x2b, }, + {0xc9, 0xff, 0x61, 0xb0, 0x40, 0x87, 0x4c, 0x05, + 0x68, 0x47, 0x92, 0x16, 0x82, 0x4a, 0x15, 0xea, + 0xb1, 0xa8, 0x38, 0xa7, 0x97, 0xd1, 0x89, 0x74, + 0x62, 0x26, 0xe4, 0xcc, 0x15, 0x67, 0x29, 0xf1, + 0xa0, 0x03, 0x64, 0x70, 0x30, 0x66, 0x60, 0x54, + 0xe2, 0x08, 0x18, 0x0f, 0x8f, 0x7b, 0x0d, 0xf2, + 0x24, 0x9e, 0x44, 0xfb, 0xa5, 0x93, 0x1f, 0xff, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x2c, }, + {0xb8, 0x35, 0x7c, 0x3a, 0x6c, 0xee, 0xf2, 0x88, + 0x31, 0x0e, 0x17, 0xb8, 0xbf, 0xef, 0xf9, 0x20, + 0x08, 0x46, 0xca, 0x8c, 0x19, 0x42, 0x49, 0x7c, + 0x48, 0x44, 0x03, 0xbc, 0x00, 0xeb, 0x61, 0x05, + 0x99, 0xf9, 0x59, 0x42, 0xdf, 0x10, 0x82, 0xe4, + 0xf9, 0x42, 0x6d, 0x08, 0x6f, 0xb9, 0xc6, 0x23, + 0x1a, 0xe8, 0xb2, 0x49, 0x33, 0xaa, 0xb5, 0xdb, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x2d, }, + {0x0b, 0x6e, 0xc4, 0xfe, 0x17, 0x77, 0x38, 0x24, + 0x04, 0xef, 0x67, 0x99, 0x97, 0xba, 0x8d, 0x1c, + 0xc5, 0xcd, 0x8e, 0x85, 0x34, 0x92, 0x59, 0xf5, + 0x90, 0xc4, 0xc6, 0x6d, 0xcc, 0x66, 0x2b, 0x9b, + 0xcb, 0xa6, 0xf9, 0x4e, 0xe4, 0xff, 0x1c, 0x9c, + 0x10, 0xbd, 0x6d, 0xdd, 0x0d, 0x13, 0x8d, 0xf2, + 0xd0, 0x99, 0xa2, 0x82, 0x15, 0x2a, 0x4b, 0x7f, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x2e, }, + {0xba, 0xa4, 0xd8, 0x63, 0x55, 0x11, 0xa7, 0xd2, + 0x88, 0xae, 0xbe, 0xed, 0xd1, 0x2c, 0xe5, 0x29, + 0xff, 0x10, 0x2c, 0x91, 0xf9, 0x7f, 0x86, 0x7e, + 0x21, 0x91, 0x6b, 0xf9, 0x68, 0x65, 0xa0, 0xb8, + 0xa6, 0x07, 0xf0, 0xb0, 0x4b, 0x13, 0xd1, 0xcb, + 0x0a, 0xa9, 0x92, 0xa5, 0xa9, 0x7f, 0x5e, 0xe8, + 0xca, 0x18, 0x49, 0xef, 0xb9, 0xed, 0x86, 0x78, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x2f, }, + {0xa5, 0x36, 0x40, 0xc8, 0x3d, 0xc2, 0x08, 0x60, + 0x3d, 0xed, 0x83, 0xe4, 0xec, 0xf7, 0x58, 0xf2, + 0x4c, 0x35, 0x7d, 0x7c, 0xf4, 0x80, 0x88, 0xb2, + 0xce, 0x01, 0xe9, 0xfa, 0x2a, 0x7e, 0xb3, 0x28, + 0xdb, 0xe6, 0x63, 0xb5, 0xa4, 0x68, 0xb5, 0xbc, + 0x97, 0xa0, 0x40, 0xa3, 0x74, 0x53, 0x96, 0xba, + 0x63, 0x6b, 0x96, 0x43, 0x70, 0xdc, 0x33, 0x52, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x30, }, + {0x34, 0xe8, 0xe1, 0x7a, 0x43, 0x0e, 0x43, 0x28, + 0x97, 0x93, 0xc3, 0x83, 0xfa, 0xc9, 0x77, 0x42, + 0x47, 0xb4, 0x0e, 0x9e, 0xbd, 0x33, 0x66, 0x98, + 0x1f, 0xcf, 0xae, 0xca, 0xda, 0xd7, 0xe6, 0x08, + 0xe3, 0x80, 0x48, 0x04, 0x34, 0xea, 0x64, 0x1c, + 0xc8, 0x2c, 0x82, 0xcb, 0xc9, 0x28, 0x01, 0x46, + 0x9c, 0x8d, 0xb0, 0x20, 0x4f, 0x13, 0x48, 0x9a, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x31, }, + {0x6e, 0x31, 0xee, 0x1d, 0xc1, 0x37, 0xf8, 0x1b, + 0x05, 0x67, 0x52, 0xe4, 0xde, 0xab, 0x14, 0x43, + 0xa4, 0x81, 0x03, 0x3e, 0x9b, 0x4c, 0x93, 0xa3, + 0x04, 0x4f, 0x4f, 0x7a, 0xdf, 0x82, 0x22, 0x0f, + 0xc7, 0xa4, 0x02, 0x15, 0x49, 0x16, 0x53, 0x25, + 0x72, 0x5f, 0x94, 0xc3, 0x41, 0x0d, 0xdb, 0x56, + 0xc5, 0x4e, 0x16, 0x1f, 0xc9, 0xef, 0x62, 0xee, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x32, }, + {0xef, 0x53, 0xb6, 0x29, 0x4a, 0xca, 0x43, 0x1f, + 0x0f, 0x3c, 0x22, 0xdc, 0x82, 0xeb, 0x90, 0x50, + 0x32, 0x4f, 0x1d, 0x88, 0xd3, 0x77, 0xe7, 0x16, + 0x44, 0x8e, 0x50, 0x7c, 0xdf, 0x4a, 0xef, 0xff, + 0xbf, 0x6d, 0x16, 0x99, 0xc9, 0x30, 0x48, 0x1c, + 0xd1, 0x02, 0x12, 0x7c, 0x9a, 0x3d, 0x99, 0x20, + 0x48, 0xab, 0x05, 0x92, 0x9b, 0x6e, 0x59, 0x27, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x33, }, + {0xae, 0xa9, 0xe1, 0x7a, 0x30, 0x65, 0x17, 0xeb, + 0x89, 0x15, 0x2a, 0xa7, 0x09, 0x6d, 0x2c, 0x38, + 0x1e, 0xc8, 0x13, 0xc5, 0x1a, 0xa8, 0x80, 0xe7, + 0xbe, 0xe2, 0xc0, 0xfd, 0xc6, 0x44, 0xcf, 0x15, + 0x4c, 0xc8, 0x1f, 0x5a, 0xde, 0x49, 0x34, 0x5e, + 0x54, 0x1b, 0x4d, 0x4b, 0x5c, 0x1a, 0xdb, 0x3e, + 0xb5, 0xc0, 0x1c, 0x14, 0xee, 0x94, 0x9a, 0xa2, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x34, }, + {0x2f, 0xdc, 0xcc, 0xfe, 0xe7, 0x20, 0xa7, 0x7e, + 0xf6, 0xcb, 0x3b, 0xfb, 0xb4, 0x47, 0xf9, 0x38, + 0x31, 0x17, 0xe3, 0xda, 0xa4, 0xa0, 0x7e, 0x36, + 0xed, 0x15, 0xf7, 0x8d, 0xc8, 0xe8, 0xcd, 0x1b, + 0x0b, 0xe4, 0x0b, 0x08, 0x77, 0xcf, 0xca, 0x19, + 0x58, 0x60, 0x31, 0x22, 0xf1, 0xe6, 0x91, 0x4f, + 0x84, 0xb7, 0xe8, 0xe9, 0x68, 0xae, 0x8b, 0x9e, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x35, }, + {0x85, 0x8e, 0x6f, 0x9c, 0xc6, 0xc1, 0x2c, 0x31, + 0xf5, 0xdf, 0x12, 0x4a, 0xa7, 0x77, 0x67, 0xb0, + 0x5c, 0x8b, 0xc0, 0x21, 0xbd, 0x68, 0x3d, 0x2b, + 0x55, 0x57, 0x15, 0x50, 0xfb, 0x92, 0x32, 0xc1, + 0x5a, 0x3b, 0xc7, 0x67, 0x3a, 0x3a, 0x03, 0xb0, + 0x25, 0x38, 0x24, 0xc5, 0x3d, 0x0f, 0xd1, 0x41, + 0x1b, 0x1c, 0xab, 0xe2, 0xe1, 0x87, 0xfb, 0x87, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x36, }, + {0xdb, 0x2f, 0x6b, 0xe6, 0x30, 0xe2, 0x46, 0xa5, + 0xcf, 0x7d, 0x99, 0xb8, 0x51, 0x94, 0xb1, 0x23, + 0xd4, 0x87, 0xe2, 0xd4, 0x66, 0xb9, 0x4b, 0x24, + 0xa0, 0x3c, 0x3e, 0x28, 0xf0, 0xc5, 0xcf, 0xf7, + 0xab, 0x68, 0x0d, 0x09, 0xee, 0x11, 0xda, 0xe8, + 0x4e, 0x9c, 0x10, 0x72, 0xac, 0x48, 0xea, 0x2e, + 0x74, 0x4b, 0x1b, 0x7f, 0x72, 0xfd, 0x46, 0x9e, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x37, }, + {0x1f, 0x24, 0x83, 0xf8, 0x25, 0x72, 0x25, 0x1f, + 0xca, 0x97, 0x5f, 0xea, 0x40, 0xdb, 0x82, 0x1d, + 0xf8, 0xad, 0x82, 0xa3, 0xc0, 0x02, 0xee, 0x6c, + 0x57, 0x11, 0x24, 0x08, 0x76, 0x05, 0x0f, 0x33, + 0x48, 0xaf, 0x26, 0x64, 0xaa, 0xc3, 0xa8, 0xb0, + 0x52, 0x81, 0x30, 0x4e, 0xbc, 0x7a, 0x79, 0x14, + 0xc6, 0xad, 0x50, 0xa4, 0xb4, 0xea, 0xc3, 0x83, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x38, }, + {0x31, 0xc4, 0x9a, 0xe7, 0x5b, 0xce, 0x78, 0x07, + 0xcd, 0xff, 0x22, 0x05, 0x5d, 0x94, 0xee, 0x90, + 0x21, 0xfe, 0xdb, 0xb5, 0xab, 0x51, 0xc5, 0x75, + 0x26, 0xf0, 0x11, 0xaa, 0xd8, 0x17, 0x40, 0x0e, + 0x8b, 0xa9, 0xca, 0x13, 0xa4, 0x5f, 0x36, 0x0e, + 0x3d, 0x12, 0x1e, 0xaa, 0xeb, 0x39, 0xaf, 0x82, + 0xd6, 0x00, 0x1c, 0x81, 0x86, 0xf5, 0xf8, 0x66, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x39, }, + {0xae, 0x99, 0xfe, 0xeb, 0xb5, 0xd2, 0x69, 0x45, + 0xb5, 0x48, 0x92, 0x09, 0x2a, 0x8a, 0xee, 0x02, + 0x91, 0x29, 0x30, 0xfa, 0x41, 0xcd, 0x11, 0x4e, + 0x40, 0x44, 0x73, 0x01, 0xfb, 0x7d, 0xa7, 0xf5, + 0xf1, 0x3a, 0x43, 0xb8, 0x17, 0x74, 0x37, 0x3c, + 0x87, 0x9c, 0xd3, 0x2d, 0x69, 0x34, 0xc0, 0x5f, + 0xa7, 0x58, 0xee, 0xb1, 0x4f, 0xcf, 0xab, 0x38, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x3a, }, + {0xdf, 0x1b, 0x1d, 0x66, 0xa5, 0x51, 0xd0, 0xd3, + 0x1e, 0xff, 0x82, 0x25, 0x58, 0xb9, 0xd2, 0xcc, + 0x75, 0xc2, 0x18, 0x02, 0x79, 0xfe, 0x0d, 0x08, + 0xfd, 0x89, 0x6d, 0x04, 0x5c, 0x08, 0x0f, 0xc3, + 0x52, 0x2f, 0x41, 0xbb, 0xb3, 0xf5, 0x5a, 0x97, + 0xcf, 0xec, 0xf2, 0x1f, 0x88, 0x2c, 0xe8, 0xcb, + 0xb1, 0xe5, 0x0c, 0xa6, 0xe6, 0x7e, 0x56, 0xdc, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x3b, }, + {0x70, 0x6a, 0x46, 0xdc, 0x76, 0xdc, 0xb7, 0x67, + 0x98, 0xe6, 0x0e, 0x6d, 0x89, 0x47, 0x47, 0x88, + 0xd1, 0x6d, 0xc1, 0x80, 0x32, 0xd2, 0x68, 0xfd, + 0x1a, 0x70, 0x4f, 0xa6, 0xe3, 0xd4, 0x89, 0x58, + 0x43, 0xda, 0x18, 0x8f, 0xd5, 0x8f, 0xb0, 0x56, + 0x79, 0x76, 0xd7, 0xb5, 0x03, 0x59, 0xd6, 0xb7, + 0x85, 0x30, 0xc8, 0xf6, 0x2d, 0x1b, 0x17, 0x46, + }, + }, + { + {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x3c, }, + {0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f, + 0x32, 0x13, 0x90, 0xb9, 0x4a, 0x03, 0xc1, 0xd3, + 0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6, + 0x11, 0x5c, 0x1d, 0x21, 0x42, 0xc8, 0x9c, 0x77, + 0x4a, 0x08, 0xdc, 0x04, 0xb3, 0xdd, 0x20, 0x19, + 0x32, 0xbc, 0x8a, 0x5e, 0xa5, 0xf8, 0xb8, 0x9b, + 0xbb, 0x2a, 0x7e, 0x66, 0x7a, 0xff, 0x81, 0xcd, + }, + }, +}; + +TEST(P224, ExternalToInternalAndBack) { + Point point; + + EXPECT_TRUE(point.SetFromString(base::StringPiece( + reinterpret_cast<const char *>(kBasePointExternal), + sizeof(kBasePointExternal)))); + + const std::string external = point.ToString(); + + ASSERT_EQ(external.size(), 56u); + EXPECT_TRUE(memcmp(external.data(), kBasePointExternal, + sizeof(kBasePointExternal)) == 0); +} + +TEST(P224, ScalarBaseMult) { + Point point; + + for (size_t i = 0; i < arraysize(kNISTTestVectors); i++) { + p224::ScalarBaseMult(kNISTTestVectors[i].scalar, &point); + const std::string external = point.ToString(); + ASSERT_EQ(external.size(), 56u); + EXPECT_TRUE(memcmp(external.data(), kNISTTestVectors[i].affine, + external.size()) == 0); + } +} + +TEST(P224, Addition) { + Point a, b, minus_b, sum, a_again; + + ASSERT_TRUE(a.SetFromString(base::StringPiece( + reinterpret_cast<const char *>(kNISTTestVectors[10].affine), 56))); + ASSERT_TRUE(b.SetFromString(base::StringPiece( + reinterpret_cast<const char *>(kNISTTestVectors[11].affine), 56))); + + p224::Negate(b, &minus_b); + p224::Add(a, b, &sum); + EXPECT_TRUE(memcmp(&sum, &a, sizeof(sum)) != 0); + p224::Add(minus_b, sum, &a_again); + EXPECT_TRUE(a_again.ToString() == a.ToString()); +} + +TEST(P224, Infinity) { + char zeros[56]; + memset(zeros, 0, sizeof(zeros)); + + // Test that x^0 = ∞. + Point a; + p224::ScalarBaseMult(reinterpret_cast<const uint8*>(zeros), &a); + EXPECT_TRUE(memcmp(zeros, a.ToString().data(), sizeof(zeros)) == 0); + + // We shouldn't allow ∞ to be imported. + EXPECT_FALSE(a.SetFromString(std::string(zeros, sizeof(zeros)))); +} + +} // namespace crypto diff --git a/crypto/random.cc b/crypto/random.cc new file mode 100644 index 0000000000..a19bb1a11c --- /dev/null +++ b/crypto/random.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/random.h" + +#include "base/rand_util.h" + +namespace crypto { + +void RandBytes(void *bytes, size_t length) { + // It's OK to call base::RandBytes(), because it's already strongly random. + // But _other_ code should go through this function to ensure that code which + // needs secure randomness is easily discoverable. + base::RandBytes(bytes, length); +} + +} // namespace crypto + diff --git a/crypto/random.h b/crypto/random.h new file mode 100644 index 0000000000..002616bd30 --- /dev/null +++ b/crypto/random.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_RANDOM_H_ +#define CRYPTO_RANDOM_H_ + +#include <stddef.h> + +#include "crypto/crypto_export.h" + +namespace crypto { + +// Fills the given buffer with |length| random bytes of cryptographically +// secure random numbers. +// |length| must be positive. +CRYPTO_EXPORT void RandBytes(void *bytes, size_t length); + +} + +#endif diff --git a/crypto/random_unittest.cc b/crypto/random_unittest.cc new file mode 100644 index 0000000000..846d9b66f3 --- /dev/null +++ b/crypto/random_unittest.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/random.h" + +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Basic functionality tests. Does NOT test the security of the random data. + +// Ensures we don't have all trivial data, i.e. that the data is indeed random. +// Currently, that means the bytes cannot be all the same (e.g. all zeros). +bool IsTrivial(const std::string& bytes) { + for (size_t i = 0; i < bytes.size(); i++) { + if (bytes[i] != bytes[0]) { + return false; + } + } + return true; +} + +TEST(RandBytes, RandBytes) { + std::string bytes(16, '\0'); + crypto::RandBytes(WriteInto(&bytes, bytes.size()), bytes.size()); + EXPECT_TRUE(!IsTrivial(bytes)); +} diff --git a/crypto/rsa_private_key.cc b/crypto/rsa_private_key.cc new file mode 100644 index 0000000000..812d9fa16e --- /dev/null +++ b/crypto/rsa_private_key.cc @@ -0,0 +1,384 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/rsa_private_key.h" + +#include <algorithm> +#include <list> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" + +// This file manually encodes and decodes RSA private keys using PrivateKeyInfo +// from PKCS #8 and RSAPrivateKey from PKCS #1. These structures are: +// +// PrivateKeyInfo ::= SEQUENCE { +// version Version, +// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, +// privateKey PrivateKey, +// attributes [0] IMPLICIT Attributes OPTIONAL +// } +// +// RSAPrivateKey ::= SEQUENCE { +// version Version, +// modulus INTEGER, +// publicExponent INTEGER, +// privateExponent INTEGER, +// prime1 INTEGER, +// prime2 INTEGER, +// exponent1 INTEGER, +// exponent2 INTEGER, +// coefficient INTEGER +// } + +namespace { +// Helper for error handling during key import. +#define READ_ASSERT(truth) \ + if (!(truth)) { \ + NOTREACHED(); \ + return false; \ + } +} // namespace + +namespace crypto { + +const uint8 PrivateKeyInfoCodec::kRsaAlgorithmIdentifier[] = { + 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, + 0x05, 0x00 +}; + +PrivateKeyInfoCodec::PrivateKeyInfoCodec(bool big_endian) + : big_endian_(big_endian) {} + +PrivateKeyInfoCodec::~PrivateKeyInfoCodec() {} + +bool PrivateKeyInfoCodec::Export(std::vector<uint8>* output) { + std::list<uint8> content; + + // Version (always zero) + uint8 version = 0; + + PrependInteger(coefficient_, &content); + PrependInteger(exponent2_, &content); + PrependInteger(exponent1_, &content); + PrependInteger(prime2_, &content); + PrependInteger(prime1_, &content); + PrependInteger(private_exponent_, &content); + PrependInteger(public_exponent_, &content); + PrependInteger(modulus_, &content); + PrependInteger(&version, 1, &content); + PrependTypeHeaderAndLength(kSequenceTag, content.size(), &content); + PrependTypeHeaderAndLength(kOctetStringTag, content.size(), &content); + + // RSA algorithm OID + for (size_t i = sizeof(kRsaAlgorithmIdentifier); i > 0; --i) + content.push_front(kRsaAlgorithmIdentifier[i - 1]); + + PrependInteger(&version, 1, &content); + PrependTypeHeaderAndLength(kSequenceTag, content.size(), &content); + + // Copy everying into the output. + output->reserve(content.size()); + output->assign(content.begin(), content.end()); + + return true; +} + +bool PrivateKeyInfoCodec::ExportPublicKeyInfo(std::vector<uint8>* output) { + // Create a sequence with the modulus (n) and public exponent (e). + std::vector<uint8> bit_string; + if (!ExportPublicKey(&bit_string)) + return false; + + // Add the sequence as the contents of a bit string. + std::list<uint8> content; + PrependBitString(&bit_string[0], static_cast<int>(bit_string.size()), + &content); + + // Add the RSA algorithm OID. + for (size_t i = sizeof(kRsaAlgorithmIdentifier); i > 0; --i) + content.push_front(kRsaAlgorithmIdentifier[i - 1]); + + // Finally, wrap everything in a sequence. + PrependTypeHeaderAndLength(kSequenceTag, content.size(), &content); + + // Copy everything into the output. + output->reserve(content.size()); + output->assign(content.begin(), content.end()); + + return true; +} + +bool PrivateKeyInfoCodec::ExportPublicKey(std::vector<uint8>* output) { + // Create a sequence with the modulus (n) and public exponent (e). + std::list<uint8> content; + PrependInteger(&public_exponent_[0], + static_cast<int>(public_exponent_.size()), + &content); + PrependInteger(&modulus_[0], static_cast<int>(modulus_.size()), &content); + PrependTypeHeaderAndLength(kSequenceTag, content.size(), &content); + + // Copy everything into the output. + output->reserve(content.size()); + output->assign(content.begin(), content.end()); + + return true; +} + +bool PrivateKeyInfoCodec::Import(const std::vector<uint8>& input) { + if (input.empty()) { + return false; + } + + // Parse the private key info up to the public key values, ignoring + // the subsequent private key values. + uint8* src = const_cast<uint8*>(&input.front()); + uint8* end = src + input.size(); + if (!ReadSequence(&src, end) || + !ReadVersion(&src, end) || + !ReadAlgorithmIdentifier(&src, end) || + !ReadTypeHeaderAndLength(&src, end, kOctetStringTag, NULL) || + !ReadSequence(&src, end) || + !ReadVersion(&src, end) || + !ReadInteger(&src, end, &modulus_)) + return false; + + int mod_size = modulus_.size(); + READ_ASSERT(mod_size % 2 == 0); + int primes_size = mod_size / 2; + + if (!ReadIntegerWithExpectedSize(&src, end, 4, &public_exponent_) || + !ReadIntegerWithExpectedSize(&src, end, mod_size, &private_exponent_) || + !ReadIntegerWithExpectedSize(&src, end, primes_size, &prime1_) || + !ReadIntegerWithExpectedSize(&src, end, primes_size, &prime2_) || + !ReadIntegerWithExpectedSize(&src, end, primes_size, &exponent1_) || + !ReadIntegerWithExpectedSize(&src, end, primes_size, &exponent2_) || + !ReadIntegerWithExpectedSize(&src, end, primes_size, &coefficient_)) + return false; + + READ_ASSERT(src == end); + + + return true; +} + +void PrivateKeyInfoCodec::PrependInteger(const std::vector<uint8>& in, + std::list<uint8>* out) { + uint8* ptr = const_cast<uint8*>(&in.front()); + PrependIntegerImpl(ptr, in.size(), out, big_endian_); +} + +// Helper to prepend an ASN.1 integer. +void PrivateKeyInfoCodec::PrependInteger(uint8* val, + int num_bytes, + std::list<uint8>* data) { + PrependIntegerImpl(val, num_bytes, data, big_endian_); +} + +void PrivateKeyInfoCodec::PrependIntegerImpl(uint8* val, + int num_bytes, + std::list<uint8>* data, + bool big_endian) { + // Reverse input if little-endian. + std::vector<uint8> tmp; + if (!big_endian) { + tmp.assign(val, val + num_bytes); + std::reverse(tmp.begin(), tmp.end()); + val = &tmp.front(); + } + + // ASN.1 integers are unpadded byte arrays, so skip any null padding bytes + // from the most-significant end of the integer. + int start = 0; + while (start < (num_bytes - 1) && val[start] == 0x00) { + start++; + num_bytes--; + } + PrependBytes(val, start, num_bytes, data); + + // ASN.1 integers are signed. To encode a positive integer whose sign bit + // (the most significant bit) would otherwise be set and make the number + // negative, ASN.1 requires a leading null byte to force the integer to be + // positive. + uint8 front = data->front(); + if ((front & 0x80) != 0) { + data->push_front(0x00); + num_bytes++; + } + + PrependTypeHeaderAndLength(kIntegerTag, num_bytes, data); +} + +bool PrivateKeyInfoCodec::ReadInteger(uint8** pos, + uint8* end, + std::vector<uint8>* out) { + return ReadIntegerImpl(pos, end, out, big_endian_); +} + +bool PrivateKeyInfoCodec::ReadIntegerWithExpectedSize(uint8** pos, + uint8* end, + size_t expected_size, + std::vector<uint8>* out) { + std::vector<uint8> temp; + if (!ReadIntegerImpl(pos, end, &temp, true)) // Big-Endian + return false; + + int pad = expected_size - temp.size(); + int index = 0; + if (out->size() == expected_size + 1) { + READ_ASSERT(out->front() == 0x00); + pad++; + index++; + } else { + READ_ASSERT(out->size() <= expected_size); + } + + out->insert(out->end(), pad, 0x00); + out->insert(out->end(), temp.begin(), temp.end()); + + // Reverse output if little-endian. + if (!big_endian_) + std::reverse(out->begin(), out->end()); + return true; +} + +bool PrivateKeyInfoCodec::ReadIntegerImpl(uint8** pos, + uint8* end, + std::vector<uint8>* out, + bool big_endian) { + uint32 length = 0; + if (!ReadTypeHeaderAndLength(pos, end, kIntegerTag, &length) || !length) + return false; + + // The first byte can be zero to force positiveness. We can ignore this. + if (**pos == 0x00) { + ++(*pos); + --length; + } + + if (length) + out->insert(out->end(), *pos, (*pos) + length); + + (*pos) += length; + + // Reverse output if little-endian. + if (!big_endian) + std::reverse(out->begin(), out->end()); + return true; +} + +void PrivateKeyInfoCodec::PrependBytes(uint8* val, + int start, + int num_bytes, + std::list<uint8>* data) { + while (num_bytes > 0) { + --num_bytes; + data->push_front(val[start + num_bytes]); + } +} + +void PrivateKeyInfoCodec::PrependLength(size_t size, std::list<uint8>* data) { + // The high bit is used to indicate whether additional octets are needed to + // represent the length. + if (size < 0x80) { + data->push_front(static_cast<uint8>(size)); + } else { + uint8 num_bytes = 0; + while (size > 0) { + data->push_front(static_cast<uint8>(size & 0xFF)); + size >>= 8; + num_bytes++; + } + CHECK_LE(num_bytes, 4); + data->push_front(0x80 | num_bytes); + } +} + +void PrivateKeyInfoCodec::PrependTypeHeaderAndLength(uint8 type, + uint32 length, + std::list<uint8>* output) { + PrependLength(length, output); + output->push_front(type); +} + +void PrivateKeyInfoCodec::PrependBitString(uint8* val, + int num_bytes, + std::list<uint8>* output) { + // Start with the data. + PrependBytes(val, 0, num_bytes, output); + // Zero unused bits. + output->push_front(0); + // Add the length. + PrependLength(num_bytes + 1, output); + // Finally, add the bit string tag. + output->push_front((uint8) kBitStringTag); +} + +bool PrivateKeyInfoCodec::ReadLength(uint8** pos, uint8* end, uint32* result) { + READ_ASSERT(*pos < end); + int length = 0; + + // If the MSB is not set, the length is just the byte itself. + if (!(**pos & 0x80)) { + length = **pos; + (*pos)++; + } else { + // Otherwise, the lower 7 indicate the length of the length. + int length_of_length = **pos & 0x7F; + READ_ASSERT(length_of_length <= 4); + (*pos)++; + READ_ASSERT(*pos + length_of_length < end); + + length = 0; + for (int i = 0; i < length_of_length; ++i) { + length <<= 8; + length |= **pos; + (*pos)++; + } + } + + READ_ASSERT(*pos + length <= end); + if (result) *result = length; + return true; +} + +bool PrivateKeyInfoCodec::ReadTypeHeaderAndLength(uint8** pos, + uint8* end, + uint8 expected_tag, + uint32* length) { + READ_ASSERT(*pos < end); + READ_ASSERT(**pos == expected_tag); + (*pos)++; + + return ReadLength(pos, end, length); +} + +bool PrivateKeyInfoCodec::ReadSequence(uint8** pos, uint8* end) { + return ReadTypeHeaderAndLength(pos, end, kSequenceTag, NULL); +} + +bool PrivateKeyInfoCodec::ReadAlgorithmIdentifier(uint8** pos, uint8* end) { + READ_ASSERT(*pos + sizeof(kRsaAlgorithmIdentifier) < end); + READ_ASSERT(memcmp(*pos, kRsaAlgorithmIdentifier, + sizeof(kRsaAlgorithmIdentifier)) == 0); + (*pos) += sizeof(kRsaAlgorithmIdentifier); + return true; +} + +bool PrivateKeyInfoCodec::ReadVersion(uint8** pos, uint8* end) { + uint32 length = 0; + if (!ReadTypeHeaderAndLength(pos, end, kIntegerTag, &length)) + return false; + + // The version should be zero. + for (uint32 i = 0; i < length; ++i) { + READ_ASSERT(**pos == 0x00); + (*pos)++; + } + + return true; +} + +} // namespace crypto diff --git a/crypto/rsa_private_key.h b/crypto/rsa_private_key.h new file mode 100644 index 0000000000..637be38836 --- /dev/null +++ b/crypto/rsa_private_key.h @@ -0,0 +1,231 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_RSA_PRIVATE_KEY_H_ +#define CRYPTO_RSA_PRIVATE_KEY_H_ + +#include "build/build_config.h" + +#include <list> +#include <vector> + +#include "base/basictypes.h" +#include "crypto/crypto_export.h" + +#if defined(USE_NSS_CERTS) +#include "base/gtest_prod_util.h" +#endif + +#if defined(USE_OPENSSL) +// Forward declaration for openssl/*.h +typedef struct evp_pkey_st EVP_PKEY; +#else +// Forward declaration. +typedef struct PK11SlotInfoStr PK11SlotInfo; +typedef struct SECKEYPrivateKeyStr SECKEYPrivateKey; +typedef struct SECKEYPublicKeyStr SECKEYPublicKey; +#endif + + +namespace crypto { + +// Used internally by RSAPrivateKey for serializing and deserializing +// PKCS #8 PrivateKeyInfo and PublicKeyInfo. +class PrivateKeyInfoCodec { + public: + + // ASN.1 encoding of the AlgorithmIdentifier from PKCS #8. + static const uint8 kRsaAlgorithmIdentifier[]; + + // ASN.1 tags for some types we use. + static const uint8 kBitStringTag = 0x03; + static const uint8 kIntegerTag = 0x02; + static const uint8 kNullTag = 0x05; + static const uint8 kOctetStringTag = 0x04; + static const uint8 kSequenceTag = 0x30; + + // |big_endian| here specifies the byte-significance of the integer components + // that will be parsed & serialized (modulus(), etc...) during Import(), + // Export() and ExportPublicKeyInfo() -- not the ASN.1 DER encoding of the + // PrivateKeyInfo/PublicKeyInfo (which is always big-endian). + explicit PrivateKeyInfoCodec(bool big_endian); + + ~PrivateKeyInfoCodec(); + + // Exports the contents of the integer components to the ASN.1 DER encoding + // of the PrivateKeyInfo structure to |output|. + bool Export(std::vector<uint8>* output); + + // Exports the contents of the integer components to the ASN.1 DER encoding + // of the PublicKeyInfo structure to |output|. + bool ExportPublicKeyInfo(std::vector<uint8>* output); + + // Exports the contents of the integer components to the ASN.1 DER encoding + // of the RSAPublicKey structure to |output|. + bool ExportPublicKey(std::vector<uint8>* output); + + // Parses the ASN.1 DER encoding of the PrivateKeyInfo structure in |input| + // and populates the integer components with |big_endian_| byte-significance. + // IMPORTANT NOTE: This is currently *not* security-approved for importing + // keys from unstrusted sources. + bool Import(const std::vector<uint8>& input); + + // Accessors to the contents of the integer components of the PrivateKeyInfo + // structure. + std::vector<uint8>* modulus() { return &modulus_; }; + std::vector<uint8>* public_exponent() { return &public_exponent_; }; + std::vector<uint8>* private_exponent() { return &private_exponent_; }; + std::vector<uint8>* prime1() { return &prime1_; }; + std::vector<uint8>* prime2() { return &prime2_; }; + std::vector<uint8>* exponent1() { return &exponent1_; }; + std::vector<uint8>* exponent2() { return &exponent2_; }; + std::vector<uint8>* coefficient() { return &coefficient_; }; + + private: + // Utility wrappers for PrependIntegerImpl that use the class's |big_endian_| + // value. + void PrependInteger(const std::vector<uint8>& in, std::list<uint8>* out); + void PrependInteger(uint8* val, int num_bytes, std::list<uint8>* data); + + // Prepends the integer stored in |val| - |val + num_bytes| with |big_endian| + // byte-significance into |data| as an ASN.1 integer. + void PrependIntegerImpl(uint8* val, + int num_bytes, + std::list<uint8>* data, + bool big_endian); + + // Utility wrappers for ReadIntegerImpl that use the class's |big_endian_| + // value. + bool ReadInteger(uint8** pos, uint8* end, std::vector<uint8>* out); + bool ReadIntegerWithExpectedSize(uint8** pos, + uint8* end, + size_t expected_size, + std::vector<uint8>* out); + + // Reads an ASN.1 integer from |pos|, and stores the result into |out| with + // |big_endian| byte-significance. + bool ReadIntegerImpl(uint8** pos, + uint8* end, + std::vector<uint8>* out, + bool big_endian); + + // Prepends the integer stored in |val|, starting a index |start|, for + // |num_bytes| bytes onto |data|. + void PrependBytes(uint8* val, + int start, + int num_bytes, + std::list<uint8>* data); + + // Helper to prepend an ASN.1 length field. + void PrependLength(size_t size, std::list<uint8>* data); + + // Helper to prepend an ASN.1 type header. + void PrependTypeHeaderAndLength(uint8 type, + uint32 length, + std::list<uint8>* output); + + // Helper to prepend an ASN.1 bit string + void PrependBitString(uint8* val, int num_bytes, std::list<uint8>* output); + + // Read an ASN.1 length field. This also checks that the length does not + // extend beyond |end|. + bool ReadLength(uint8** pos, uint8* end, uint32* result); + + // Read an ASN.1 type header and its length. + bool ReadTypeHeaderAndLength(uint8** pos, + uint8* end, + uint8 expected_tag, + uint32* length); + + // Read an ASN.1 sequence declaration. This consumes the type header and + // length field, but not the contents of the sequence. + bool ReadSequence(uint8** pos, uint8* end); + + // Read the RSA AlgorithmIdentifier. + bool ReadAlgorithmIdentifier(uint8** pos, uint8* end); + + // Read one of the two version fields in PrivateKeyInfo. + bool ReadVersion(uint8** pos, uint8* end); + + // The byte-significance of the stored components (modulus, etc..). + bool big_endian_; + + // Component integers of the PrivateKeyInfo + std::vector<uint8> modulus_; + std::vector<uint8> public_exponent_; + std::vector<uint8> private_exponent_; + std::vector<uint8> prime1_; + std::vector<uint8> prime2_; + std::vector<uint8> exponent1_; + std::vector<uint8> exponent2_; + std::vector<uint8> coefficient_; + + DISALLOW_COPY_AND_ASSIGN(PrivateKeyInfoCodec); +}; + +// Encapsulates an RSA private key. Can be used to generate new keys, export +// keys to other formats, or to extract a public key. +// TODO(hclam): This class should be ref-counted so it can be reused easily. +class CRYPTO_EXPORT RSAPrivateKey { + public: + ~RSAPrivateKey(); + + // Create a new random instance. Can return NULL if initialization fails. + static RSAPrivateKey* Create(uint16 num_bits); + + // Create a new instance by importing an existing private key. The format is + // an ASN.1-encoded PrivateKeyInfo block from PKCS #8. This can return NULL if + // initialization fails. + static RSAPrivateKey* CreateFromPrivateKeyInfo( + const std::vector<uint8>& input); + +#if defined(USE_OPENSSL) + // Create a new instance from an existing EVP_PKEY, taking a + // reference to it. |key| must be an RSA key. Returns NULL on + // failure. + static RSAPrivateKey* CreateFromKey(EVP_PKEY* key); +#else + // Create a new instance by referencing an existing private key + // structure. Does not import the key. + static RSAPrivateKey* CreateFromKey(SECKEYPrivateKey* key); +#endif + +#if defined(USE_OPENSSL) + EVP_PKEY* key() { return key_; } +#else + SECKEYPrivateKey* key() { return key_; } + SECKEYPublicKey* public_key() { return public_key_; } +#endif + + // Creates a copy of the object. + RSAPrivateKey* Copy() const; + + // Exports the private key to a PKCS #1 PrivateKey block. + bool ExportPrivateKey(std::vector<uint8>* output) const; + + // Exports the public key to an X509 SubjectPublicKeyInfo block. + bool ExportPublicKey(std::vector<uint8>* output) const; + + private: +#if defined(USE_NSS_CERTS) + FRIEND_TEST_ALL_PREFIXES(RSAPrivateKeyNSSTest, FindFromPublicKey); + FRIEND_TEST_ALL_PREFIXES(RSAPrivateKeyNSSTest, FailedFindFromPublicKey); +#endif + + // Constructor is private. Use one of the Create*() methods above instead. + RSAPrivateKey(); + +#if defined(USE_OPENSSL) + EVP_PKEY* key_; +#else + SECKEYPrivateKey* key_; + SECKEYPublicKey* public_key_; +#endif + + DISALLOW_COPY_AND_ASSIGN(RSAPrivateKey); +}; + +} // namespace crypto + +#endif // CRYPTO_RSA_PRIVATE_KEY_H_ diff --git a/crypto/rsa_private_key_nss.cc b/crypto/rsa_private_key_nss.cc new file mode 100644 index 0000000000..374e6f9051 --- /dev/null +++ b/crypto/rsa_private_key_nss.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/rsa_private_key.h" + +#include <cryptohi.h> +#include <keyhi.h> +#include <pk11pub.h> + +#include <list> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "crypto/nss_key_util.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" + +// TODO(rafaelw): Consider using NSS's ASN.1 encoder. +namespace { + +static bool ReadAttribute(SECKEYPrivateKey* key, + CK_ATTRIBUTE_TYPE type, + std::vector<uint8>* output) { + SECItem item; + SECStatus rv; + rv = PK11_ReadRawAttribute(PK11_TypePrivKey, key, type, &item); + if (rv != SECSuccess) { + NOTREACHED(); + return false; + } + + output->assign(item.data, item.data + item.len); + SECITEM_FreeItem(&item, PR_FALSE); + return true; +} + +} // namespace + +namespace crypto { + +RSAPrivateKey::~RSAPrivateKey() { + if (key_) + SECKEY_DestroyPrivateKey(key_); + if (public_key_) + SECKEY_DestroyPublicKey(public_key_); +} + +// static +RSAPrivateKey* RSAPrivateKey::Create(uint16 num_bits) { + EnsureNSSInit(); + + ScopedPK11Slot slot(PK11_GetInternalSlot()); + if (!slot) { + NOTREACHED(); + return nullptr; + } + + ScopedSECKEYPublicKey public_key; + ScopedSECKEYPrivateKey private_key; + if (!GenerateRSAKeyPairNSS(slot.get(), num_bits, false /* not permanent */, + &public_key, &private_key)) { + return nullptr; + } + + RSAPrivateKey* rsa_key = new RSAPrivateKey; + rsa_key->public_key_ = public_key.release(); + rsa_key->key_ = private_key.release(); + return rsa_key; +} + +// static +RSAPrivateKey* RSAPrivateKey::CreateFromPrivateKeyInfo( + const std::vector<uint8>& input) { + EnsureNSSInit(); + + ScopedPK11Slot slot(PK11_GetInternalSlot()); + if (!slot) { + NOTREACHED(); + return nullptr; + } + ScopedSECKEYPrivateKey key(ImportNSSKeyFromPrivateKeyInfo( + slot.get(), input, false /* not permanent */)); + if (!key || SECKEY_GetPrivateKeyType(key.get()) != rsaKey) + return nullptr; + return RSAPrivateKey::CreateFromKey(key.get()); +} + +// static +RSAPrivateKey* RSAPrivateKey::CreateFromKey(SECKEYPrivateKey* key) { + DCHECK(key); + if (SECKEY_GetPrivateKeyType(key) != rsaKey) + return NULL; + RSAPrivateKey* copy = new RSAPrivateKey(); + copy->key_ = SECKEY_CopyPrivateKey(key); + copy->public_key_ = SECKEY_ConvertToPublicKey(key); + if (!copy->key_ || !copy->public_key_) { + NOTREACHED(); + delete copy; + return NULL; + } + return copy; +} + +RSAPrivateKey* RSAPrivateKey::Copy() const { + RSAPrivateKey* copy = new RSAPrivateKey(); + copy->key_ = SECKEY_CopyPrivateKey(key_); + copy->public_key_ = SECKEY_CopyPublicKey(public_key_); + return copy; +} + +bool RSAPrivateKey::ExportPrivateKey(std::vector<uint8>* output) const { + PrivateKeyInfoCodec private_key_info(true); + + // Manually read the component attributes of the private key and build up + // the PrivateKeyInfo. + if (!ReadAttribute(key_, CKA_MODULUS, private_key_info.modulus()) || + !ReadAttribute(key_, CKA_PUBLIC_EXPONENT, + private_key_info.public_exponent()) || + !ReadAttribute(key_, CKA_PRIVATE_EXPONENT, + private_key_info.private_exponent()) || + !ReadAttribute(key_, CKA_PRIME_1, private_key_info.prime1()) || + !ReadAttribute(key_, CKA_PRIME_2, private_key_info.prime2()) || + !ReadAttribute(key_, CKA_EXPONENT_1, private_key_info.exponent1()) || + !ReadAttribute(key_, CKA_EXPONENT_2, private_key_info.exponent2()) || + !ReadAttribute(key_, CKA_COEFFICIENT, private_key_info.coefficient())) { + NOTREACHED(); + return false; + } + + return private_key_info.Export(output); +} + +bool RSAPrivateKey::ExportPublicKey(std::vector<uint8>* output) const { + ScopedSECItem der_pubkey(SECKEY_EncodeDERSubjectPublicKeyInfo(public_key_)); + if (!der_pubkey.get()) { + NOTREACHED(); + return false; + } + + output->assign(der_pubkey->data, der_pubkey->data + der_pubkey->len); + return true; +} + +RSAPrivateKey::RSAPrivateKey() : key_(NULL), public_key_(NULL) { + EnsureNSSInit(); +} + +} // namespace crypto diff --git a/crypto/rsa_private_key_openssl.cc b/crypto/rsa_private_key_openssl.cc new file mode 100644 index 0000000000..52a0a7a181 --- /dev/null +++ b/crypto/rsa_private_key_openssl.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/rsa_private_key.h" + +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/evp.h> +#include <openssl/pkcs12.h> +#include <openssl/rsa.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/openssl_util.h" +#include "crypto/scoped_openssl_types.h" + +namespace crypto { + +namespace { + +using ScopedPKCS8_PRIV_KEY_INFO = + ScopedOpenSSL<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>; + +// Function pointer definition, for injecting the required key export function +// into ExportKey, below. The supplied function should export EVP_PKEY into +// the supplied BIO, returning 1 on success or 0 on failure. +using ExportFunction = int (*)(BIO*, EVP_PKEY*); + +// Helper to export |key| into |output| via the specified ExportFunction. +bool ExportKey(EVP_PKEY* key, + ExportFunction export_fn, + std::vector<uint8>* output) { + if (!key) + return false; + + OpenSSLErrStackTracer err_tracer(FROM_HERE); + ScopedBIO bio(BIO_new(BIO_s_mem())); + + int res = export_fn(bio.get(), key); + if (!res) + return false; + + char* data = NULL; + long len = BIO_get_mem_data(bio.get(), &data); + if (!data || len < 0) + return false; + + output->assign(data, data + len); + return true; +} + +} // namespace + +// static +RSAPrivateKey* RSAPrivateKey::Create(uint16 num_bits) { + OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedRSA rsa_key(RSA_new()); + ScopedBIGNUM bn(BN_new()); + if (!rsa_key.get() || !bn.get() || !BN_set_word(bn.get(), 65537L)) + return NULL; + + if (!RSA_generate_key_ex(rsa_key.get(), num_bits, bn.get(), NULL)) + return NULL; + + scoped_ptr<RSAPrivateKey> result(new RSAPrivateKey); + result->key_ = EVP_PKEY_new(); + if (!result->key_ || !EVP_PKEY_set1_RSA(result->key_, rsa_key.get())) + return NULL; + + return result.release(); +} + +// static +RSAPrivateKey* RSAPrivateKey::CreateFromPrivateKeyInfo( + const std::vector<uint8>& input) { + if (input.empty()) + return NULL; + + OpenSSLErrStackTracer err_tracer(FROM_HERE); + + // Importing is a little more involved than exporting, as we must first + // PKCS#8 decode the input, and then import the EVP_PKEY from Private Key + // Info structure returned. + const uint8_t* ptr = &input[0]; + ScopedPKCS8_PRIV_KEY_INFO p8inf( + d2i_PKCS8_PRIV_KEY_INFO(nullptr, &ptr, input.size())); + if (!p8inf.get() || ptr != &input[0] + input.size()) + return NULL; + + scoped_ptr<RSAPrivateKey> result(new RSAPrivateKey); + result->key_ = EVP_PKCS82PKEY(p8inf.get()); + if (!result->key_ || EVP_PKEY_id(result->key_) != EVP_PKEY_RSA) + return NULL; + + return result.release(); +} + +// static +RSAPrivateKey* RSAPrivateKey::CreateFromKey(EVP_PKEY* key) { + DCHECK(key); + if (EVP_PKEY_type(key->type) != EVP_PKEY_RSA) + return NULL; + RSAPrivateKey* copy = new RSAPrivateKey(); + copy->key_ = EVP_PKEY_up_ref(key); + return copy; +} + +RSAPrivateKey::RSAPrivateKey() + : key_(NULL) { +} + +RSAPrivateKey::~RSAPrivateKey() { + if (key_) + EVP_PKEY_free(key_); +} + +RSAPrivateKey* RSAPrivateKey::Copy() const { + scoped_ptr<RSAPrivateKey> copy(new RSAPrivateKey()); + ScopedRSA rsa(EVP_PKEY_get1_RSA(key_)); + if (!rsa) + return NULL; + copy->key_ = EVP_PKEY_new(); + if (!EVP_PKEY_set1_RSA(copy->key_, rsa.get())) + return NULL; + return copy.release(); +} + +bool RSAPrivateKey::ExportPrivateKey(std::vector<uint8>* output) const { + return ExportKey(key_, i2d_PKCS8PrivateKeyInfo_bio, output); +} + +bool RSAPrivateKey::ExportPublicKey(std::vector<uint8>* output) const { + return ExportKey(key_, i2d_PUBKEY_bio, output); +} + +} // namespace crypto diff --git a/crypto/rsa_private_key_unittest.cc b/crypto/rsa_private_key_unittest.cc new file mode 100644 index 0000000000..9e7f6ff51f --- /dev/null +++ b/crypto/rsa_private_key_unittest.cc @@ -0,0 +1,470 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/rsa_private_key.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const uint8 kTestPrivateKeyInfo[] = { + 0x30, 0x82, 0x02, 0x78, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x02, 0x62, 0x30, 0x82, 0x02, 0x5e, 0x02, 0x01, + 0x00, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, + 0x20, 0xdc, 0x7c, 0x9b, 0x0c, 0xdc, 0x51, 0x61, + 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08, + 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, + 0x85, 0x7b, 0x0c, 0x04, 0x13, 0x3f, 0x8d, 0xf4, + 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a, + 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, + 0x9e, 0x36, 0x74, 0x30, 0xda, 0x8a, 0x31, 0x4f, + 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17, + 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, + 0xb2, 0x65, 0x7a, 0x89, 0x4e, 0xb6, 0x47, 0xff, + 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85, + 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, + 0x96, 0xd0, 0xd6, 0x14, 0x6f, 0x13, 0x8d, 0xc5, + 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18, + 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, + 0xe7, 0x1f, 0x0f, 0xe6, 0x0f, 0x02, 0x03, 0x01, + 0x00, 0x01, 0x02, 0x81, 0x80, 0x03, 0x61, 0x89, + 0x37, 0xcb, 0xf2, 0x98, 0xa0, 0xce, 0xb4, 0xcb, + 0x16, 0x13, 0xf0, 0xe6, 0xaf, 0x5c, 0xc5, 0xa7, + 0x69, 0x71, 0xca, 0xba, 0x8d, 0xe0, 0x4d, 0xdd, + 0xed, 0xb8, 0x48, 0x8b, 0x16, 0x93, 0x36, 0x95, + 0xc2, 0x91, 0x40, 0x65, 0x17, 0xbd, 0x7f, 0xd6, + 0xad, 0x9e, 0x30, 0x28, 0x46, 0xe4, 0x3e, 0xcc, + 0x43, 0x78, 0xf9, 0xfe, 0x1f, 0x33, 0x23, 0x1e, + 0x31, 0x12, 0x9d, 0x3c, 0xa7, 0x08, 0x82, 0x7b, + 0x7d, 0x25, 0x4e, 0x5e, 0x19, 0xa8, 0x9b, 0xed, + 0x86, 0xb2, 0xcb, 0x3c, 0xfe, 0x4e, 0xa1, 0xfa, + 0x62, 0x87, 0x3a, 0x17, 0xf7, 0x60, 0xec, 0x38, + 0x29, 0xe8, 0x4f, 0x34, 0x9f, 0x76, 0x9d, 0xee, + 0xa3, 0xf6, 0x85, 0x6b, 0x84, 0x43, 0xc9, 0x1e, + 0x01, 0xff, 0xfd, 0xd0, 0x29, 0x4c, 0xfa, 0x8e, + 0x57, 0x0c, 0xc0, 0x71, 0xa5, 0xbb, 0x88, 0x46, + 0x29, 0x5c, 0xc0, 0x4f, 0x01, 0x02, 0x41, 0x00, + 0xf5, 0x83, 0xa4, 0x64, 0x4a, 0xf2, 0xdd, 0x8c, + 0x2c, 0xed, 0xa8, 0xd5, 0x60, 0x5a, 0xe4, 0xc7, + 0xcc, 0x61, 0xcd, 0x38, 0x42, 0x20, 0xd3, 0x82, + 0x18, 0xf2, 0x35, 0x00, 0x72, 0x2d, 0xf7, 0x89, + 0x80, 0x67, 0xb5, 0x93, 0x05, 0x5f, 0xdd, 0x42, + 0xba, 0x16, 0x1a, 0xea, 0x15, 0xc6, 0xf0, 0xb8, + 0x8c, 0xbc, 0xbf, 0x54, 0x9e, 0xf1, 0xc1, 0xb2, + 0xb3, 0x8b, 0xb6, 0x26, 0x02, 0x30, 0xc4, 0x81, + 0x02, 0x41, 0x00, 0xc0, 0x60, 0x62, 0x80, 0xe1, + 0x22, 0x78, 0xf6, 0x9d, 0x83, 0x18, 0xeb, 0x72, + 0x45, 0xd7, 0xc8, 0x01, 0x7f, 0xa9, 0xca, 0x8f, + 0x7d, 0xd6, 0xb8, 0x31, 0x2b, 0x84, 0x7f, 0x62, + 0xd9, 0xa9, 0x22, 0x17, 0x7d, 0x06, 0x35, 0x6c, + 0xf3, 0xc1, 0x94, 0x17, 0x85, 0x5a, 0xaf, 0x9c, + 0x5c, 0x09, 0x3c, 0xcf, 0x2f, 0x44, 0x9d, 0xb6, + 0x52, 0x68, 0x5f, 0xf9, 0x59, 0xc8, 0x84, 0x2b, + 0x39, 0x22, 0x8f, 0x02, 0x41, 0x00, 0xb2, 0x04, + 0xe2, 0x0e, 0x56, 0xca, 0x03, 0x1a, 0xc0, 0xf9, + 0x12, 0x92, 0xa5, 0x6b, 0x42, 0xb8, 0x1c, 0xda, + 0x4d, 0x93, 0x9d, 0x5f, 0x6f, 0xfd, 0xc5, 0x58, + 0xda, 0x55, 0x98, 0x74, 0xfc, 0x28, 0x17, 0x93, + 0x1b, 0x75, 0x9f, 0x50, 0x03, 0x7f, 0x7e, 0xae, + 0xc8, 0x95, 0x33, 0x75, 0x2c, 0xd6, 0xa4, 0x35, + 0xb8, 0x06, 0x03, 0xba, 0x08, 0x59, 0x2b, 0x17, + 0x02, 0xdc, 0x4c, 0x7a, 0x50, 0x01, 0x02, 0x41, + 0x00, 0x9d, 0xdb, 0x39, 0x59, 0x09, 0xe4, 0x30, + 0xa0, 0x24, 0xf5, 0xdb, 0x2f, 0xf0, 0x2f, 0xf1, + 0x75, 0x74, 0x0d, 0x5e, 0xb5, 0x11, 0x73, 0xb0, + 0x0a, 0xaa, 0x86, 0x4c, 0x0d, 0xff, 0x7e, 0x1d, + 0xb4, 0x14, 0xd4, 0x09, 0x91, 0x33, 0x5a, 0xfd, + 0xa0, 0x58, 0x80, 0x9b, 0xbe, 0x78, 0x2e, 0x69, + 0x82, 0x15, 0x7c, 0x72, 0xf0, 0x7b, 0x18, 0x39, + 0xff, 0x6e, 0xeb, 0xc6, 0x86, 0xf5, 0xb4, 0xc7, + 0x6f, 0x02, 0x41, 0x00, 0x8d, 0x1a, 0x37, 0x0f, + 0x76, 0xc4, 0x82, 0xfa, 0x5c, 0xc3, 0x79, 0x35, + 0x3e, 0x70, 0x8a, 0xbf, 0x27, 0x49, 0xb0, 0x99, + 0x63, 0xcb, 0x77, 0x5f, 0xa8, 0x82, 0x65, 0xf6, + 0x03, 0x52, 0x51, 0xf1, 0xae, 0x2e, 0x05, 0xb3, + 0xc6, 0xa4, 0x92, 0xd1, 0xce, 0x6c, 0x72, 0xfb, + 0x21, 0xb3, 0x02, 0x87, 0xe4, 0xfd, 0x61, 0xca, + 0x00, 0x42, 0x19, 0xf0, 0xda, 0x5a, 0x53, 0xe3, + 0xb1, 0xc5, 0x15, 0xf3 +}; + +} // namespace + +// Generate random private keys with two different sizes. Reimport, then +// export them again. We should get back the same exact bytes. +TEST(RSAPrivateKeyUnitTest, InitRandomTest) { + scoped_ptr<crypto::RSAPrivateKey> keypair1( + crypto::RSAPrivateKey::Create(1024)); + scoped_ptr<crypto::RSAPrivateKey> keypair2( + crypto::RSAPrivateKey::Create(2048)); + ASSERT_TRUE(keypair1.get()); + ASSERT_TRUE(keypair2.get()); + + std::vector<uint8> privkey1; + std::vector<uint8> privkey2; + std::vector<uint8> pubkey1; + std::vector<uint8> pubkey2; + + ASSERT_TRUE(keypair1->ExportPrivateKey(&privkey1)); + ASSERT_TRUE(keypair2->ExportPrivateKey(&privkey2)); + ASSERT_TRUE(keypair1->ExportPublicKey(&pubkey1)); + ASSERT_TRUE(keypair2->ExportPublicKey(&pubkey2)); + + scoped_ptr<crypto::RSAPrivateKey> keypair3( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(privkey1)); + scoped_ptr<crypto::RSAPrivateKey> keypair4( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(privkey2)); + ASSERT_TRUE(keypair3.get()); + ASSERT_TRUE(keypair4.get()); + + std::vector<uint8> privkey3; + std::vector<uint8> privkey4; + ASSERT_TRUE(keypair3->ExportPrivateKey(&privkey3)); + ASSERT_TRUE(keypair4->ExportPrivateKey(&privkey4)); + + ASSERT_EQ(privkey1.size(), privkey3.size()); + ASSERT_EQ(privkey2.size(), privkey4.size()); + ASSERT_TRUE(0 == memcmp(&privkey1.front(), &privkey3.front(), + privkey1.size())); + ASSERT_TRUE(0 == memcmp(&privkey2.front(), &privkey4.front(), + privkey2.size())); +} + +// Test Copy() method. +TEST(RSAPrivateKeyUnitTest, CopyTest) { + std::vector<uint8> input( + kTestPrivateKeyInfo, kTestPrivateKeyInfo + sizeof(kTestPrivateKeyInfo)); + + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input)); + + scoped_ptr<crypto::RSAPrivateKey> key_copy(key->Copy()); + ASSERT_TRUE(key_copy.get()); + + std::vector<uint8> privkey_copy; + ASSERT_TRUE(key_copy->ExportPrivateKey(&privkey_copy)); + ASSERT_EQ(input, privkey_copy); +} + +// Test that CreateFromPrivateKeyInfo fails if there is extra data after the RSA +// key. +TEST(RSAPrivateKeyUnitTest, ExtraData) { + std::vector<uint8> input( + kTestPrivateKeyInfo, kTestPrivateKeyInfo + sizeof(kTestPrivateKeyInfo)); + input.push_back(0); + + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input)); + + // Import should fail. + EXPECT_FALSE(key); +} + +TEST(RSAPrivateKeyUnitTest, NotRsaKey) { + // Defines a valid P-256 private key. + const uint8 kTestEcPrivateKeyInfo[] = { + 0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, + 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, + 0x03, 0x01, 0x07, 0x04, 0x6D, 0x30, 0x6B, 0x02, 0x01, 0x01, 0x04, 0x20, + 0x1F, 0xE3, 0x39, 0x50, 0xC5, 0xF4, 0x61, 0x12, 0x4A, 0xE9, 0x92, 0xC2, + 0xBD, 0xFD, 0xF1, 0xC7, 0x3B, 0x16, 0x15, 0xF5, 0x71, 0xBD, 0x56, 0x7E, + 0x60, 0xD1, 0x9A, 0xA1, 0xF4, 0x8C, 0xDF, 0x42, 0xA1, 0x44, 0x03, 0x42, + 0x00, 0x04, 0x7C, 0x11, 0x0C, 0x66, 0xDC, 0xFD, 0xA8, 0x07, 0xF6, 0xE6, + 0x9E, 0x45, 0xDD, 0xB3, 0xC7, 0x4F, 0x69, 0xA1, 0x48, 0x4D, 0x20, 0x3E, + 0x8D, 0xC5, 0xAD, 0xA8, 0xE9, 0xA9, 0xDD, 0x7C, 0xB3, 0xC7, 0x0D, 0xF4, + 0x48, 0x98, 0x6E, 0x51, 0xBD, 0xE5, 0xD1, 0x57, 0x6F, 0x99, 0x90, 0x1F, + 0x9C, 0x2C, 0x6A, 0x80, 0x6A, 0x47, 0xFD, 0x90, 0x76, 0x43, 0xA7, 0x2B, + 0x83, 0x55, 0x97, 0xEF, 0xC8, 0xC6 + }; + + std::vector<uint8> input( + kTestEcPrivateKeyInfo, + kTestEcPrivateKeyInfo + sizeof(kTestEcPrivateKeyInfo)); + + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input)); + + // Import should fail as the given PKCS8 bytes were for an EC key not RSA key. + EXPECT_FALSE(key); +} + +// Verify that generated public keys look good. This test data was generated +// with the openssl command line tool. +TEST(RSAPrivateKeyUnitTest, PublicKeyTest) { + const uint8 expected_public_key_info[] = { + 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, + 0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, + 0x20, 0xdc, 0x7c, 0x9b, 0x0c, 0xdc, 0x51, 0x61, + 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08, + 0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, + 0x85, 0x7b, 0x0c, 0x04, 0x13, 0x3f, 0x8d, 0xf4, + 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a, + 0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, + 0x9e, 0x36, 0x74, 0x30, 0xda, 0x8a, 0x31, 0x4f, + 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17, + 0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, + 0xb2, 0x65, 0x7a, 0x89, 0x4e, 0xb6, 0x47, 0xff, + 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85, + 0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, + 0x96, 0xd0, 0xd6, 0x14, 0x6f, 0x13, 0x8d, 0xc5, + 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18, + 0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, + 0xe7, 0x1f, 0x0f, 0xe6, 0x0f, 0x02, 0x03, 0x01, + 0x00, 0x01 + }; + + std::vector<uint8> input( + kTestPrivateKeyInfo, kTestPrivateKeyInfo + sizeof(kTestPrivateKeyInfo)); + + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input)); + ASSERT_TRUE(key.get()); + + std::vector<uint8> output; + ASSERT_TRUE(key->ExportPublicKey(&output)); + + ASSERT_TRUE( + memcmp(expected_public_key_info, &output.front(), output.size()) == 0); +} + +// These two test keys each contain an integer that has 0x00 for its most +// significant byte. When encoded as ASN.1, this byte is dropped and there are +// two interesting sub-cases. When the sign bit of the integer is set, an extra +// null byte is added back to force the encoded value to be positive. When the +// sign bit is not set, the encoded integer is just left shorter than usual. +// See also: http://code.google.com/p/chromium/issues/detail?id=14877. +// +// Before we were handling this correctly, we would see one of two failures: +// * RSAPrivateKey::CreateFromPrivateKeyInfo would return null because the +// underlying windows API failed to import the key. +// * The import would succeed, but incorrectly interpret the data. On export, +// the key would contain different values. +// +// This test case verifies these two failures modes don't occur. +TEST(RSAPrivateKeyUnitTest, ShortIntegers) { + const uint8 short_integer_with_high_bit[] = { + 0x30, 0x82, 0x02, 0x77, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x02, 0x61, 0x30, 0x82, 0x02, 0x5d, 0x02, 0x01, + 0x00, 0x02, 0x81, 0x81, 0x00, 0x92, 0x59, 0x32, + 0x7d, 0x8e, 0xaf, 0x2e, 0xd5, 0xb2, 0x5c, 0x67, + 0xc8, 0x7d, 0x48, 0xb7, 0x84, 0x12, 0xd0, 0x76, + 0xda, 0xe1, 0xa3, 0x1e, 0x40, 0x01, 0x14, 0x5c, + 0xef, 0x26, 0x6e, 0x28, 0xa2, 0xf7, 0xa5, 0xb4, + 0x02, 0x37, 0xd0, 0x53, 0x10, 0xcb, 0x7c, 0x6a, + 0xf4, 0x53, 0x9f, 0xb8, 0xe0, 0x83, 0x93, 0xd1, + 0x19, 0xd8, 0x28, 0xd1, 0xd1, 0xd8, 0x87, 0x8f, + 0x92, 0xfd, 0x73, 0xc0, 0x4d, 0x3e, 0x07, 0x22, + 0x1f, 0xc1, 0x20, 0xb0, 0x70, 0xb2, 0x3b, 0xea, + 0xb1, 0xe5, 0x0a, 0xfd, 0x56, 0x49, 0x5e, 0x39, + 0x90, 0x91, 0xce, 0x04, 0x83, 0x29, 0xaa, 0xfd, + 0x12, 0xa4, 0x42, 0x26, 0x6c, 0x6e, 0x79, 0x70, + 0x77, 0x03, 0xb2, 0x07, 0x01, 0x3d, 0x85, 0x81, + 0x95, 0x9e, 0xda, 0x5a, 0xa3, 0xf4, 0x2d, 0x38, + 0x04, 0x58, 0xf5, 0x6b, 0xc9, 0xf1, 0xb5, 0x65, + 0xfe, 0x66, 0x0d, 0xa2, 0xd5, 0x02, 0x03, 0x01, + 0x00, 0x01, 0x02, 0x81, 0x80, 0x5e, 0x01, 0x5f, + 0xb6, 0x59, 0x1d, 0xdc, 0x36, 0xb6, 0x60, 0x36, + 0xe6, 0x08, 0xdb, 0xd9, 0xcd, 0xc3, 0x8c, 0x16, + 0x9c, 0x98, 0x8d, 0x7f, 0xd3, 0xdb, 0x1d, 0xaa, + 0x68, 0x8f, 0xc5, 0xf8, 0xe2, 0x5d, 0xb3, 0x19, + 0xc2, 0xc6, 0xf9, 0x51, 0x32, 0x1b, 0x93, 0x6a, + 0xdc, 0x50, 0x8e, 0xeb, 0x61, 0x84, 0x03, 0x42, + 0x30, 0x98, 0xb1, 0xf7, 0xbd, 0x14, 0x9a, 0x57, + 0x36, 0x33, 0x09, 0xd4, 0x3e, 0x90, 0xda, 0xef, + 0x09, 0x6e, 0xef, 0x49, 0xb6, 0x60, 0x68, 0x5e, + 0x54, 0x17, 0x25, 0x5b, 0x37, 0xe3, 0x35, 0x63, + 0x5b, 0x60, 0x3c, 0xbd, 0x50, 0xdf, 0x46, 0x43, + 0x08, 0xa4, 0x71, 0x21, 0xf1, 0x30, 0x71, 0xdc, + 0xda, 0xd7, 0x6f, 0xd2, 0x18, 0xbd, 0x39, 0xf1, + 0xe1, 0xbe, 0xa8, 0x8d, 0x62, 0xdf, 0xa2, 0x3e, + 0xb6, 0x15, 0x26, 0xb6, 0x57, 0xbd, 0x63, 0xdb, + 0xc1, 0x91, 0xec, 0xb8, 0x01, 0x02, 0x41, 0x00, + 0xc6, 0x1a, 0x06, 0x48, 0xf2, 0x12, 0x1c, 0x9f, + 0x74, 0x20, 0x5c, 0x85, 0xa2, 0xda, 0xe5, 0x62, + 0x96, 0x8d, 0x22, 0x7b, 0x78, 0x73, 0xea, 0xbb, + 0x9f, 0x59, 0x42, 0x13, 0x15, 0xc8, 0x11, 0x50, + 0x6c, 0x55, 0xf6, 0xdf, 0x8b, 0xfe, 0xc7, 0xdd, + 0xa8, 0xca, 0x54, 0x41, 0xe8, 0xce, 0xbe, 0x7d, + 0xbd, 0xe2, 0x13, 0x4b, 0x5b, 0x61, 0xeb, 0x69, + 0x6c, 0xb1, 0x9b, 0x28, 0x68, 0x5b, 0xd6, 0x01, + 0x02, 0x41, 0x00, 0xbd, 0x1e, 0xfe, 0x51, 0x99, + 0xb6, 0xe3, 0x84, 0xfe, 0xf1, 0x9e, 0xfd, 0x9c, + 0xe7, 0x86, 0x43, 0x68, 0x7f, 0x2f, 0x6a, 0x2a, + 0x4c, 0xae, 0xa6, 0x41, 0x1c, 0xf0, 0x10, 0x37, + 0x54, 0x23, 0xba, 0x05, 0x0d, 0x18, 0x27, 0x8d, + 0xb8, 0xe4, 0x8f, 0xf2, 0x25, 0x73, 0x8a, 0xd7, + 0x05, 0x98, 0x6b, 0x3d, 0x55, 0xb7, 0x6f, 0x7c, + 0xec, 0x77, 0x61, 0x54, 0x7b, 0xb6, 0x6b, 0x31, + 0xec, 0x94, 0xd5, 0x02, 0x41, 0x00, 0x90, 0xa2, + 0xa5, 0x9e, 0x12, 0xa7, 0x68, 0xa0, 0x7e, 0xdf, + 0xb5, 0xcd, 0x98, 0x26, 0xab, 0xbd, 0xbc, 0x5f, + 0xd5, 0x22, 0x42, 0xc2, 0x97, 0x4a, 0x5f, 0x40, + 0x82, 0xfe, 0x7e, 0x33, 0xb1, 0x78, 0x7f, 0x70, + 0x90, 0x2b, 0x8d, 0x01, 0xfb, 0x18, 0xfa, 0x48, + 0xa7, 0x15, 0xec, 0x0d, 0x2e, 0x85, 0x8d, 0xe2, + 0x86, 0xe5, 0xc9, 0x15, 0x88, 0x14, 0x53, 0xd8, + 0xa4, 0x88, 0xef, 0x10, 0xc6, 0x01, 0x02, 0x41, + 0x00, 0xba, 0xe4, 0xaf, 0x14, 0xfa, 0xdf, 0xf6, + 0xd5, 0xce, 0x8f, 0xfe, 0xbb, 0xc8, 0x5c, 0x30, + 0x9d, 0xda, 0xdd, 0x9d, 0x80, 0xc0, 0x0e, 0x89, + 0xa5, 0xb8, 0xc1, 0x1d, 0x28, 0x19, 0x55, 0x67, + 0xfd, 0x03, 0xd2, 0xdd, 0xe4, 0xf0, 0xb4, 0x20, + 0x03, 0x74, 0x9b, 0xb8, 0x24, 0x23, 0xbb, 0xde, + 0xd5, 0x53, 0x86, 0xaa, 0xc1, 0x5d, 0x65, 0xdd, + 0xcf, 0xec, 0x8a, 0x59, 0x4a, 0x73, 0xca, 0xc5, + 0x85, 0x02, 0x40, 0x00, 0xc4, 0x5e, 0x8d, 0xa4, + 0xea, 0xbb, 0x6a, 0x9b, 0xe6, 0x3a, 0x4d, 0xc1, + 0xdb, 0xe5, 0x52, 0x38, 0xf9, 0x59, 0x91, 0x2d, + 0x90, 0x82, 0xe3, 0x31, 0x1b, 0x48, 0xb7, 0x42, + 0xfa, 0x1d, 0x83, 0xd5, 0x3d, 0x02, 0xc2, 0x12, + 0x71, 0x10, 0x3a, 0xbd, 0x92, 0x8f, 0x9b, 0xa2, + 0x6b, 0x2d, 0x21, 0xa4, 0x65, 0xe9, 0xfa, 0x8c, + 0x30, 0x2a, 0x89, 0xce, 0xd0, 0xa7, 0x67, 0xd8, + 0x45, 0x84, 0xb0 + }; + + const uint8 short_integer_without_high_bit[] = { + 0x30, 0x82, 0x02, 0x76, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x02, 0x60, 0x30, 0x82, 0x02, 0x5c, 0x02, 0x01, + 0x00, 0x02, 0x81, 0x81, 0x00, 0xc3, 0x9e, 0x8d, + 0xc4, 0x6d, 0x38, 0xe8, 0x0e, 0x9f, 0x84, 0x03, + 0x40, 0x8e, 0x81, 0x2e, 0x56, 0x67, 0x78, 0x11, + 0x85, 0x27, 0x81, 0x52, 0xf2, 0x1b, 0x3e, 0x5b, + 0xf8, 0xab, 0xfc, 0xaf, 0xca, 0x5c, 0x26, 0xd5, + 0xfa, 0xd4, 0x55, 0x50, 0x38, 0xb9, 0x9d, 0x89, + 0x92, 0x7e, 0x34, 0xcf, 0x37, 0x82, 0x48, 0x2d, + 0xaa, 0xc4, 0x6a, 0x0e, 0x93, 0xea, 0xad, 0x8a, + 0x33, 0xf0, 0x42, 0x23, 0xe0, 0x4c, 0x98, 0xbf, + 0x01, 0x00, 0x1b, 0xfe, 0x06, 0x15, 0xc6, 0xe3, + 0x80, 0x79, 0x6d, 0xfe, 0x48, 0xcd, 0x40, 0xbb, + 0xf9, 0x58, 0xe6, 0xbf, 0xd5, 0x4c, 0x29, 0x48, + 0x53, 0x78, 0x06, 0x03, 0x0d, 0x59, 0xf5, 0x20, + 0xe0, 0xe6, 0x8c, 0xb2, 0xf5, 0xd8, 0x61, 0x52, + 0x7e, 0x40, 0x83, 0xd7, 0x69, 0xae, 0xd7, 0x75, + 0x02, 0x2d, 0x49, 0xd5, 0x15, 0x5b, 0xf1, 0xd9, + 0x4d, 0x60, 0x7d, 0x62, 0xa5, 0x02, 0x03, 0x01, + 0x00, 0x01, 0x02, 0x7f, 0x6d, 0x45, 0x23, 0xeb, + 0x95, 0x17, 0x34, 0x88, 0xf6, 0x91, 0xc7, 0x3f, + 0x48, 0x5a, 0xe0, 0x87, 0x63, 0x44, 0xae, 0x84, + 0xb2, 0x8c, 0x8a, 0xc8, 0xb2, 0x6f, 0x22, 0xf0, + 0xc5, 0x21, 0x61, 0x10, 0xa8, 0x69, 0x09, 0x1e, + 0x13, 0x7d, 0x94, 0x52, 0x1b, 0x5c, 0xe4, 0x7b, + 0xf0, 0x03, 0x8f, 0xbc, 0x72, 0x09, 0xdf, 0x78, + 0x84, 0x3e, 0xb9, 0xe5, 0xe6, 0x31, 0x0a, 0x01, + 0xf9, 0x32, 0xf8, 0xd6, 0x57, 0xa3, 0x87, 0xe6, + 0xf5, 0x98, 0xbc, 0x8e, 0x41, 0xb9, 0x50, 0x17, + 0x7b, 0xd3, 0x97, 0x5a, 0x44, 0x3a, 0xee, 0xff, + 0x6b, 0xb3, 0x3a, 0x52, 0xe7, 0xa4, 0x96, 0x9a, + 0xf6, 0x83, 0xc8, 0x97, 0x1c, 0x63, 0xa1, 0xd6, + 0xb3, 0xa8, 0xb2, 0xc7, 0x73, 0x25, 0x0f, 0x58, + 0x36, 0xb9, 0x7a, 0x47, 0xa7, 0x4d, 0x30, 0xfe, + 0x4d, 0x74, 0x56, 0xe8, 0xfb, 0xd6, 0x50, 0xe5, + 0xe0, 0x28, 0x15, 0x02, 0x41, 0x00, 0xeb, 0x15, + 0x62, 0xb6, 0x37, 0x41, 0x7c, 0xc5, 0x00, 0x22, + 0x2c, 0x5a, 0x5e, 0xe4, 0xb2, 0x11, 0x87, 0x89, + 0xad, 0xf4, 0x57, 0x68, 0x90, 0xb7, 0x9f, 0xe2, + 0x79, 0x20, 0x6b, 0x98, 0x00, 0x0d, 0x3a, 0x3b, + 0xc1, 0xcd, 0x36, 0xf9, 0x27, 0xda, 0x40, 0x36, + 0x1d, 0xb8, 0x5c, 0x96, 0xeb, 0x04, 0x08, 0xe1, + 0x3f, 0xfa, 0x94, 0x8b, 0x0f, 0xa0, 0xff, 0xc1, + 0x51, 0xea, 0x90, 0xad, 0x15, 0xc7, 0x02, 0x41, + 0x00, 0xd5, 0x06, 0x45, 0xd7, 0x55, 0x63, 0x1a, + 0xf0, 0x89, 0x81, 0xae, 0x87, 0x23, 0xa2, 0x39, + 0xfe, 0x3d, 0x82, 0xc7, 0xcb, 0x15, 0xb9, 0xe3, + 0xe2, 0x5b, 0xc6, 0xd2, 0x55, 0xdd, 0xab, 0x55, + 0x29, 0x7c, 0xda, 0x0e, 0x1c, 0x09, 0xfc, 0x73, + 0x0d, 0x01, 0xed, 0x6d, 0x2f, 0x05, 0xd0, 0xd5, + 0x1d, 0xce, 0x18, 0x7f, 0xb0, 0xc8, 0x47, 0x77, + 0xd2, 0xa9, 0x9e, 0xfc, 0x39, 0x4b, 0x3d, 0x94, + 0x33, 0x02, 0x41, 0x00, 0x8f, 0x94, 0x09, 0x2d, + 0x17, 0x44, 0x75, 0x0a, 0xf1, 0x10, 0xee, 0x1b, + 0xe7, 0xd7, 0x2f, 0xf6, 0xca, 0xdc, 0x49, 0x15, + 0x72, 0x09, 0x58, 0x51, 0xfe, 0x61, 0xd8, 0xee, + 0xf7, 0x27, 0xe7, 0xe8, 0x2c, 0x47, 0xf1, 0x0f, + 0x00, 0x63, 0x5e, 0x76, 0xcb, 0x3f, 0x02, 0x19, + 0xe6, 0xda, 0xfa, 0x01, 0x05, 0xd7, 0x65, 0x37, + 0x0b, 0x60, 0x7f, 0x94, 0x2a, 0x80, 0x8d, 0x22, + 0x81, 0x68, 0x65, 0x63, 0x02, 0x41, 0x00, 0xc2, + 0xd4, 0x18, 0xde, 0x47, 0x9e, 0xfb, 0x8d, 0x91, + 0x05, 0xc5, 0x3c, 0x9d, 0xcf, 0x8a, 0x60, 0xc7, + 0x9b, 0x2b, 0xe5, 0xc6, 0xba, 0x1b, 0xfc, 0xf3, + 0xd9, 0x54, 0x97, 0xe9, 0xc4, 0x00, 0x80, 0x90, + 0x4a, 0xd2, 0x6a, 0xbc, 0x8b, 0x62, 0x22, 0x3c, + 0x68, 0x0c, 0xda, 0xdb, 0xe3, 0xd2, 0x76, 0x8e, + 0xff, 0x03, 0x12, 0x09, 0x2a, 0xac, 0x21, 0x44, + 0xb7, 0x3e, 0x91, 0x9c, 0x09, 0xf6, 0xd7, 0x02, + 0x41, 0x00, 0xc0, 0xa1, 0xbb, 0x70, 0xdc, 0xf8, + 0xeb, 0x17, 0x61, 0xd4, 0x8c, 0x7c, 0x3b, 0x82, + 0x91, 0x58, 0xff, 0xf9, 0x19, 0xac, 0x3a, 0x73, + 0xa7, 0x20, 0xe5, 0x22, 0x02, 0xc4, 0xf6, 0xb9, + 0xb9, 0x43, 0x53, 0x35, 0x88, 0xe1, 0x05, 0xb6, + 0x43, 0x9b, 0x39, 0xc8, 0x04, 0x4d, 0x2b, 0x01, + 0xf7, 0xe6, 0x1b, 0x8d, 0x7e, 0x89, 0xe3, 0x43, + 0xd4, 0xf3, 0xab, 0x28, 0xd4, 0x5a, 0x1f, 0x20, + 0xea, 0xbe + }; + + std::vector<uint8> input1; + std::vector<uint8> input2; + + input1.resize(sizeof(short_integer_with_high_bit)); + input2.resize(sizeof(short_integer_without_high_bit)); + + memcpy(&input1.front(), short_integer_with_high_bit, + sizeof(short_integer_with_high_bit)); + memcpy(&input2.front(), short_integer_without_high_bit, + sizeof(short_integer_without_high_bit)); + + scoped_ptr<crypto::RSAPrivateKey> keypair1( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input1)); + scoped_ptr<crypto::RSAPrivateKey> keypair2( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(input2)); + ASSERT_TRUE(keypair1.get()); + ASSERT_TRUE(keypair2.get()); + + std::vector<uint8> output1; + std::vector<uint8> output2; + ASSERT_TRUE(keypair1->ExportPrivateKey(&output1)); + ASSERT_TRUE(keypair2->ExportPrivateKey(&output2)); + + ASSERT_EQ(input1.size(), output1.size()); + ASSERT_EQ(input2.size(), output2.size()); + ASSERT_TRUE(0 == memcmp(&output1.front(), &input1.front(), + input1.size())); + ASSERT_TRUE(0 == memcmp(&output2.front(), &input2.front(), + input2.size())); +} + +TEST(RSAPrivateKeyUnitTest, CreateFromKeyTest) { + scoped_ptr<crypto::RSAPrivateKey> key_pair( + crypto::RSAPrivateKey::Create(512)); + ASSERT_TRUE(key_pair.get()); + + scoped_ptr<crypto::RSAPrivateKey> key_copy( + crypto::RSAPrivateKey::CreateFromKey(key_pair->key())); + ASSERT_TRUE(key_copy.get()); + + std::vector<uint8> privkey; + std::vector<uint8> pubkey; + ASSERT_TRUE(key_pair->ExportPrivateKey(&privkey)); + ASSERT_TRUE(key_pair->ExportPublicKey(&pubkey)); + + std::vector<uint8> privkey_copy; + std::vector<uint8> pubkey_copy; + ASSERT_TRUE(key_copy->ExportPrivateKey(&privkey_copy)); + ASSERT_TRUE(key_copy->ExportPublicKey(&pubkey_copy)); + + ASSERT_EQ(privkey, privkey_copy); + ASSERT_EQ(pubkey, pubkey_copy); +} + diff --git a/crypto/scoped_test_nss_chromeos_user.cc b/crypto/scoped_test_nss_chromeos_user.cc new file mode 100644 index 0000000000..aec25d8dff --- /dev/null +++ b/crypto/scoped_test_nss_chromeos_user.cc @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium 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 "crypto/scoped_test_nss_chromeos_user.h" + +#include "base/logging.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" + +namespace crypto { + +ScopedTestNSSChromeOSUser::ScopedTestNSSChromeOSUser( + const std::string& username_hash) + : username_hash_(username_hash), constructed_successfully_(false) { + if (!temp_dir_.CreateUniqueTempDir()) + return; + // This opens a software DB in the given folder. In production code that is in + // the home folder, but for testing the temp folder is used. + constructed_successfully_ = + InitializeNSSForChromeOSUser(username_hash, temp_dir_.path()); +} + +ScopedTestNSSChromeOSUser::~ScopedTestNSSChromeOSUser() { + if (constructed_successfully_) + CloseChromeOSUserForTesting(username_hash_); +} + +void ScopedTestNSSChromeOSUser::FinishInit() { + DCHECK(constructed_successfully_); + if (!ShouldInitializeTPMForChromeOSUser(username_hash_)) + return; + WillInitializeTPMForChromeOSUser(username_hash_); + InitializePrivateSoftwareSlotForChromeOSUser(username_hash_); +} + +} // namespace crypto diff --git a/crypto/scoped_test_nss_chromeos_user.h b/crypto/scoped_test_nss_chromeos_user.h new file mode 100644 index 0000000000..1638517704 --- /dev/null +++ b/crypto/scoped_test_nss_chromeos_user.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_SCOPED_TEST_NSS_CHROMEOS_USER_H_ +#define CRYPTO_SCOPED_TEST_NSS_CHROMEOS_USER_H_ + +#include <string> + +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "crypto/crypto_export.h" + +namespace crypto { + +// Opens a persistent NSS software database in a temporary directory for the +// user with |username_hash|. This database will be used for both the user's +// public and private slot. +class CRYPTO_EXPORT_PRIVATE ScopedTestNSSChromeOSUser { + public: + // Opens the software database and sets the public slot for the user. The + // private slot will not be initialized until FinishInit() is called. + explicit ScopedTestNSSChromeOSUser(const std::string& username_hash); + ~ScopedTestNSSChromeOSUser(); + + std::string username_hash() const { return username_hash_; } + bool constructed_successfully() const { return constructed_successfully_; } + + // Completes initialization of user. Causes any waiting private slot callbacks + // to run, see GetPrivateSlotForChromeOSUser(). + void FinishInit(); + + private: + const std::string username_hash_; + base::ScopedTempDir temp_dir_; + bool constructed_successfully_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTestNSSChromeOSUser); +}; + +} // namespace crypto + +#endif // CRYPTO_SCOPED_TEST_NSS_CHROMEOS_USER_H_ diff --git a/crypto/scoped_test_nss_db.cc b/crypto/scoped_test_nss_db.cc new file mode 100644 index 0000000000..452c26d73e --- /dev/null +++ b/crypto/scoped_test_nss_db.cc @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium 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 "crypto/scoped_test_nss_db.h" + +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" + +namespace crypto { + +ScopedTestNSSDB::ScopedTestNSSDB() { + EnsureNSSInit(); + // NSS is allowed to do IO on the current thread since dispatching + // to a dedicated thread would still have the affect of blocking + // the current thread, due to NSS's internal locking requirements + base::ThreadRestrictions::ScopedAllowIO allow_io; + + if (!temp_dir_.CreateUniqueTempDir()) + return; + + const char kTestDescription[] = "Test DB"; + slot_ = OpenSoftwareNSSDB(temp_dir_.path(), kTestDescription); +} + +ScopedTestNSSDB::~ScopedTestNSSDB() { + // Don't close when NSS is < 3.15.1, because it would require an additional + // sleep for 1 second after closing the database, due to + // http://bugzil.la/875601. + if (!NSS_VersionCheck("3.15.1")) { + LOG(ERROR) << "NSS version is < 3.15.1, test DB will not be closed."; + temp_dir_.Take(); + return; + } + + // NSS is allowed to do IO on the current thread since dispatching + // to a dedicated thread would still have the affect of blocking + // the current thread, due to NSS's internal locking requirements + base::ThreadRestrictions::ScopedAllowIO allow_io; + + if (slot_) { + SECStatus status = SECMOD_CloseUserDB(slot_.get()); + if (status != SECSuccess) + PLOG(ERROR) << "SECMOD_CloseUserDB failed: " << PORT_GetError(); + } + + if (!temp_dir_.Delete()) + LOG(ERROR) << "Could not delete temporary directory."; +} + +} // namespace crypto diff --git a/crypto/scoped_test_nss_db.h b/crypto/scoped_test_nss_db.h new file mode 100644 index 0000000000..88c2d55dbe --- /dev/null +++ b/crypto/scoped_test_nss_db.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_SCOPED_TEST_NSS_DB_H_ +#define CRYPTO_SCOPED_TEST_NSS_DB_H_ + +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "crypto/crypto_export.h" +#include "crypto/scoped_nss_types.h" + +namespace crypto { + +// Opens a persistent NSS database in a temporary directory. +// Prior NSS version 3.15.1, because of http://bugzil.la/875601 , the opened DB +// will not be closed automatically. +class CRYPTO_EXPORT_PRIVATE ScopedTestNSSDB { + public: + ScopedTestNSSDB(); + ~ScopedTestNSSDB(); + + bool is_open() const { return slot_; } + PK11SlotInfo* slot() const { return slot_.get(); } + + private: + base::ScopedTempDir temp_dir_; + ScopedPK11Slot slot_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTestNSSDB); +}; + +} // namespace crypto + +#endif // CRYPTO_SCOPED_TEST_NSS_DB_H_ diff --git a/crypto/scoped_test_system_nss_key_slot.cc b/crypto/scoped_test_system_nss_key_slot.cc new file mode 100644 index 0000000000..53fbbffc1a --- /dev/null +++ b/crypto/scoped_test_system_nss_key_slot.cc @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium 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 "crypto/scoped_test_system_nss_key_slot.h" + +#include "crypto/nss_util_internal.h" +#include "crypto/scoped_test_nss_db.h" + +namespace crypto { + +ScopedTestSystemNSSKeySlot::ScopedTestSystemNSSKeySlot() + : test_db_(new ScopedTestNSSDB) { + if (!test_db_->is_open()) + return; + SetSystemKeySlotForTesting( + ScopedPK11Slot(PK11_ReferenceSlot(test_db_->slot()))); +} + +ScopedTestSystemNSSKeySlot::~ScopedTestSystemNSSKeySlot() { + SetSystemKeySlotForTesting(ScopedPK11Slot()); +} + +bool ScopedTestSystemNSSKeySlot::ConstructedSuccessfully() const { + return test_db_->is_open(); +} + +PK11SlotInfo* ScopedTestSystemNSSKeySlot::slot() const { + return test_db_->slot(); +} + +} // namespace crypto diff --git a/crypto/scoped_test_system_nss_key_slot.h b/crypto/scoped_test_system_nss_key_slot.h new file mode 100644 index 0000000000..ac3b72c4ce --- /dev/null +++ b/crypto/scoped_test_system_nss_key_slot.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_SCOPED_TEST_SYSTEM_NSS_KEY_SLOT_H_ +#define CRYPTO_SCOPED_TEST_SYSTEM_NSS_KEY_SLOT_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/crypto_export.h" + +// Forward declaration, from <pk11pub.h> +typedef struct PK11SlotInfoStr PK11SlotInfo; + +namespace crypto { + +class ScopedTestNSSDB; + +// Opens a persistent NSS software database in a temporary directory and sets +// the test system slot to the opened database. This helper should be created in +// tests to fake the system token that is usually provided by the Chaps module. +// |slot| is exposed through |GetSystemNSSKeySlot| and |IsTPMTokenReady| will +// return true. +// |InitializeTPMTokenAndSystemSlot|, which triggers the TPM initialization, +// does not have to be called if this helper is used. +// At most one instance of this helper must be used at a time. +class CRYPTO_EXPORT_PRIVATE ScopedTestSystemNSSKeySlot { + public: + explicit ScopedTestSystemNSSKeySlot(); + ~ScopedTestSystemNSSKeySlot(); + + bool ConstructedSuccessfully() const; + PK11SlotInfo* slot() const; + + private: + scoped_ptr<ScopedTestNSSDB> test_db_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTestSystemNSSKeySlot); +}; + +} // namespace crypto + +#endif // CRYPTO_SCOPED_TEST_SYSTEM_NSS_KEY_SLOT_H_ diff --git a/crypto/secure_hash_default.cc b/crypto/secure_hash_default.cc new file mode 100644 index 0000000000..739b402b6c --- /dev/null +++ b/crypto/secure_hash_default.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/secure_hash.h" + +#include "base/logging.h" +#include "base/pickle.h" +#include "crypto/third_party/nss/chromium-blapi.h" +#include "crypto/third_party/nss/chromium-sha256.h" + +namespace crypto { + +namespace { + +const char kSHA256Descriptor[] = "NSS"; + +class SecureHashSHA256NSS : public SecureHash { + public: + static const int kSecureHashVersion = 1; + + SecureHashSHA256NSS() { + SHA256_Begin(&ctx_); + } + + ~SecureHashSHA256NSS() override { memset(&ctx_, 0, sizeof(ctx_)); } + + // SecureHash implementation: + void Update(const void* input, size_t len) override { + SHA256_Update(&ctx_, static_cast<const unsigned char*>(input), len); + } + + void Finish(void* output, size_t len) override { + SHA256_End(&ctx_, static_cast<unsigned char*>(output), NULL, + static_cast<unsigned int>(len)); + } + + bool Serialize(base::Pickle* pickle) override; + bool Deserialize(base::PickleIterator* data_iterator) override; + + private: + SHA256Context ctx_; +}; + +bool SecureHashSHA256NSS::Serialize(base::Pickle* pickle) { + if (!pickle) + return false; + + if (!pickle->WriteInt(kSecureHashVersion) || + !pickle->WriteString(kSHA256Descriptor) || + !pickle->WriteBytes(&ctx_, sizeof(ctx_))) { + return false; + } + + return true; +} + +bool SecureHashSHA256NSS::Deserialize(base::PickleIterator* data_iterator) { + int version; + if (!data_iterator->ReadInt(&version)) + return false; + + if (version > kSecureHashVersion) + return false; // We don't know how to deal with this. + + std::string type; + if (!data_iterator->ReadString(&type)) + return false; + + if (type != kSHA256Descriptor) + return false; // It's the wrong kind. + + const char* data = NULL; + if (!data_iterator->ReadBytes(&data, sizeof(ctx_))) + return false; + + memcpy(&ctx_, data, sizeof(ctx_)); + + return true; +} + +} // namespace + +SecureHash* SecureHash::Create(Algorithm algorithm) { + switch (algorithm) { + case SHA256: + return new SecureHashSHA256NSS(); + default: + NOTIMPLEMENTED(); + return NULL; + } +} + +} // namespace crypto diff --git a/crypto/secure_hash_openssl.cc b/crypto/secure_hash_openssl.cc new file mode 100644 index 0000000000..1033b8e25e --- /dev/null +++ b/crypto/secure_hash_openssl.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/secure_hash.h" + +#include <openssl/mem.h> +#include <openssl/sha.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "crypto/openssl_util.h" + +namespace crypto { + +namespace { + +const char kSHA256Descriptor[] = "OpenSSL"; + +class SecureHashSHA256OpenSSL : public SecureHash { + public: + static const int kSecureHashVersion = 1; + + SecureHashSHA256OpenSSL() { + SHA256_Init(&ctx_); + } + + ~SecureHashSHA256OpenSSL() override { + OPENSSL_cleanse(&ctx_, sizeof(ctx_)); + } + + void Update(const void* input, size_t len) override { + SHA256_Update(&ctx_, static_cast<const unsigned char*>(input), len); + } + + void Finish(void* output, size_t len) override { + ScopedOpenSSLSafeSizeBuffer<SHA256_DIGEST_LENGTH> result( + static_cast<unsigned char*>(output), len); + SHA256_Final(result.safe_buffer(), &ctx_); + } + + bool Serialize(base::Pickle* pickle) override; + bool Deserialize(base::PickleIterator* data_iterator) override; + + private: + SHA256_CTX ctx_; +}; + +bool SecureHashSHA256OpenSSL::Serialize(base::Pickle* pickle) { + if (!pickle) + return false; + + if (!pickle->WriteInt(kSecureHashVersion) || + !pickle->WriteString(kSHA256Descriptor) || + !pickle->WriteBytes(&ctx_, sizeof(ctx_))) { + return false; + } + + return true; +} + +bool SecureHashSHA256OpenSSL::Deserialize(base::PickleIterator* data_iterator) { + if (!data_iterator) + return false; + + int version; + if (!data_iterator->ReadInt(&version)) + return false; + + if (version > kSecureHashVersion) + return false; // We don't know how to deal with this. + + std::string type; + if (!data_iterator->ReadString(&type)) + return false; + + if (type != kSHA256Descriptor) + return false; // It's the wrong kind. + + const char* data = NULL; + if (!data_iterator->ReadBytes(&data, sizeof(ctx_))) + return false; + + memcpy(&ctx_, data, sizeof(ctx_)); + + return true; +} + +} // namespace + +SecureHash* SecureHash::Create(Algorithm algorithm) { + switch (algorithm) { + case SHA256: + return new SecureHashSHA256OpenSSL(); + default: + NOTIMPLEMENTED(); + return NULL; + } +} + +} // namespace crypto diff --git a/crypto/secure_hash_unittest.cc b/crypto/secure_hash_unittest.cc new file mode 100644 index 0000000000..facf476e0a --- /dev/null +++ b/crypto/secure_hash_unittest.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/secure_hash.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "crypto/sha2.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SecureHashTest, TestUpdate) { + // Example B.3 from FIPS 180-2: long message. + std::string input3(500000, 'a'); // 'a' repeated half a million times + int expected3[] = { 0xcd, 0xc7, 0x6e, 0x5c, + 0x99, 0x14, 0xfb, 0x92, + 0x81, 0xa1, 0xc7, 0xe2, + 0x84, 0xd7, 0x3e, 0x67, + 0xf1, 0x80, 0x9a, 0x48, + 0xa4, 0x97, 0x20, 0x0e, + 0x04, 0x6d, 0x39, 0xcc, + 0xc7, 0x11, 0x2c, 0xd0 }; + + uint8 output3[crypto::kSHA256Length]; + + scoped_ptr<crypto::SecureHash> ctx(crypto::SecureHash::Create( + crypto::SecureHash::SHA256)); + ctx->Update(input3.data(), input3.size()); + ctx->Update(input3.data(), input3.size()); + + ctx->Finish(output3, sizeof(output3)); + for (size_t i = 0; i < crypto::kSHA256Length; i++) + EXPECT_EQ(expected3[i], static_cast<int>(output3[i])); +} + +// Save the crypto state mid-stream, and create another instance with the +// saved state. Then feed the same data afterwards to both. +// When done, both should have the same hash value. +TEST(SecureHashTest, TestSerialization) { + std::string input1(10001, 'a'); // 'a' repeated 10001 times + std::string input2(10001, 'b'); // 'b' repeated 10001 times + std::string input3(10001, 'c'); // 'c' repeated 10001 times + std::string input4(10001, 'd'); // 'd' repeated 10001 times + std::string input5(10001, 'e'); // 'e' repeated 10001 times + + uint8 output1[crypto::kSHA256Length]; + uint8 output2[crypto::kSHA256Length]; + + scoped_ptr<crypto::SecureHash> ctx1(crypto::SecureHash::Create( + crypto::SecureHash::SHA256)); + scoped_ptr<crypto::SecureHash> ctx2(crypto::SecureHash::Create( + crypto::SecureHash::SHA256)); + base::Pickle pickle; + ctx1->Update(input1.data(), input1.size()); + ctx1->Update(input2.data(), input2.size()); + ctx1->Update(input3.data(), input3.size()); + + EXPECT_TRUE(ctx1->Serialize(&pickle)); + ctx1->Update(input4.data(), input4.size()); + ctx1->Update(input5.data(), input5.size()); + + ctx1->Finish(output1, sizeof(output1)); + + base::PickleIterator data_iterator(pickle); + EXPECT_TRUE(ctx2->Deserialize(&data_iterator)); + ctx2->Update(input4.data(), input4.size()); + ctx2->Update(input5.data(), input5.size()); + + ctx2->Finish(output2, sizeof(output2)); + + EXPECT_EQ(0, memcmp(output1, output2, crypto::kSHA256Length)); +} diff --git a/crypto/secure_util.cc b/crypto/secure_util.cc new file mode 100644 index 0000000000..3fe8aa961a --- /dev/null +++ b/crypto/secure_util.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/secure_util.h" + +namespace crypto { + +bool SecureMemEqual(const void* s1, const void* s2, size_t n) { + const unsigned char* s1_ptr = reinterpret_cast<const unsigned char*>(s1); + const unsigned char* s2_ptr = reinterpret_cast<const unsigned char*>(s2); + unsigned char tmp = 0; + for (size_t i = 0; i < n; ++i, ++s1_ptr, ++s2_ptr) + tmp |= *s1_ptr ^ *s2_ptr; + return (tmp == 0); +} + +} // namespace crypto + diff --git a/crypto/secure_util.h b/crypto/secure_util.h new file mode 100644 index 0000000000..cfe05ca155 --- /dev/null +++ b/crypto/secure_util.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_SECURE_UTIL_H_ +#define CRYPTO_SECURE_UTIL_H_ + +#include <stddef.h> + +#include "crypto/crypto_export.h" + +namespace crypto { + +// Performs a constant-time comparison of two strings, returning true if the +// strings are equal. +// +// For cryptographic operations, comparison functions such as memcmp() may +// expose side-channel information about input, allowing an attacker to +// perform timing analysis to determine what the expected bits should be. In +// order to avoid such attacks, the comparison must execute in constant time, +// so as to not to reveal to the attacker where the difference(s) are. +// For an example attack, see +// http://groups.google.com/group/keyczar-discuss/browse_thread/thread/5571eca0948b2a13 +CRYPTO_EXPORT bool SecureMemEqual(const void* s1, const void* s2, size_t n); + +} // namespace crypto + +#endif // CRYPTO_SECURE_UTIL_H_ + diff --git a/crypto/sha2.cc b/crypto/sha2.cc new file mode 100644 index 0000000000..6f36237961 --- /dev/null +++ b/crypto/sha2.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/sha2.h" + +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "crypto/secure_hash.h" + +namespace crypto { + +void SHA256HashString(const base::StringPiece& str, void* output, size_t len) { + scoped_ptr<SecureHash> ctx(SecureHash::Create(SecureHash::SHA256)); + ctx->Update(str.data(), str.length()); + ctx->Finish(output, len); +} + +std::string SHA256HashString(const base::StringPiece& str) { + std::string output(kSHA256Length, 0); + SHA256HashString(str, string_as_array(&output), output.size()); + return output; +} + +} // namespace crypto diff --git a/crypto/sha2.h b/crypto/sha2.h new file mode 100644 index 0000000000..7e279d3e94 --- /dev/null +++ b/crypto/sha2.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CRYPTO_SHA2_H_ +#define CRYPTO_SHA2_H_ + +#include <string> + +#include "base/strings/string_piece.h" +#include "crypto/crypto_export.h" + +namespace crypto { + +// These functions perform SHA-256 operations. +// +// Functions for SHA-384 and SHA-512 can be added when the need arises. + +static const size_t kSHA256Length = 32; // Length in bytes of a SHA-256 hash. + +// Computes the SHA-256 hash of the input string 'str' and stores the first +// 'len' bytes of the hash in the output buffer 'output'. If 'len' > 32, +// only 32 bytes (the full hash) are stored in the 'output' buffer. +CRYPTO_EXPORT void SHA256HashString(const base::StringPiece& str, + void* output, size_t len); + +// Convenience version of the above that returns the result in a 32-byte +// string. +CRYPTO_EXPORT std::string SHA256HashString(const base::StringPiece& str); + +} // namespace crypto + +#endif // CRYPTO_SHA2_H_ diff --git a/crypto/sha2_unittest.cc b/crypto/sha2_unittest.cc new file mode 100644 index 0000000000..78da1360d4 --- /dev/null +++ b/crypto/sha2_unittest.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/sha2.h" + +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(Sha256Test, Test1) { + // Example B.1 from FIPS 180-2: one-block message. + std::string input1 = "abc"; + int expected1[] = { 0xba, 0x78, 0x16, 0xbf, + 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, + 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, + 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, + 0xf2, 0x00, 0x15, 0xad }; + + uint8 output1[crypto::kSHA256Length]; + crypto::SHA256HashString(input1, output1, sizeof(output1)); + for (size_t i = 0; i < crypto::kSHA256Length; i++) + EXPECT_EQ(expected1[i], static_cast<int>(output1[i])); + + uint8 output_truncated1[4]; // 4 bytes == 32 bits + crypto::SHA256HashString(input1, + output_truncated1, sizeof(output_truncated1)); + for (size_t i = 0; i < sizeof(output_truncated1); i++) + EXPECT_EQ(expected1[i], static_cast<int>(output_truncated1[i])); +} + +TEST(Sha256Test, Test1_String) { + // Same as the above, but using the wrapper that returns a std::string. + // Example B.1 from FIPS 180-2: one-block message. + std::string input1 = "abc"; + int expected1[] = { 0xba, 0x78, 0x16, 0xbf, + 0x8f, 0x01, 0xcf, 0xea, + 0x41, 0x41, 0x40, 0xde, + 0x5d, 0xae, 0x22, 0x23, + 0xb0, 0x03, 0x61, 0xa3, + 0x96, 0x17, 0x7a, 0x9c, + 0xb4, 0x10, 0xff, 0x61, + 0xf2, 0x00, 0x15, 0xad }; + + std::string output1 = crypto::SHA256HashString(input1); + ASSERT_EQ(crypto::kSHA256Length, output1.size()); + for (size_t i = 0; i < crypto::kSHA256Length; i++) + EXPECT_EQ(expected1[i], static_cast<uint8>(output1[i])); +} + +TEST(Sha256Test, Test2) { + // Example B.2 from FIPS 180-2: multi-block message. + std::string input2 = + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; + int expected2[] = { 0x24, 0x8d, 0x6a, 0x61, + 0xd2, 0x06, 0x38, 0xb8, + 0xe5, 0xc0, 0x26, 0x93, + 0x0c, 0x3e, 0x60, 0x39, + 0xa3, 0x3c, 0xe4, 0x59, + 0x64, 0xff, 0x21, 0x67, + 0xf6, 0xec, 0xed, 0xd4, + 0x19, 0xdb, 0x06, 0xc1 }; + + uint8 output2[crypto::kSHA256Length]; + crypto::SHA256HashString(input2, output2, sizeof(output2)); + for (size_t i = 0; i < crypto::kSHA256Length; i++) + EXPECT_EQ(expected2[i], static_cast<int>(output2[i])); + + uint8 output_truncated2[6]; + crypto::SHA256HashString(input2, + output_truncated2, sizeof(output_truncated2)); + for (size_t i = 0; i < sizeof(output_truncated2); i++) + EXPECT_EQ(expected2[i], static_cast<int>(output_truncated2[i])); +} + +TEST(Sha256Test, Test3) { + // Example B.3 from FIPS 180-2: long message. + std::string input3(1000000, 'a'); // 'a' repeated a million times + int expected3[] = { 0xcd, 0xc7, 0x6e, 0x5c, + 0x99, 0x14, 0xfb, 0x92, + 0x81, 0xa1, 0xc7, 0xe2, + 0x84, 0xd7, 0x3e, 0x67, + 0xf1, 0x80, 0x9a, 0x48, + 0xa4, 0x97, 0x20, 0x0e, + 0x04, 0x6d, 0x39, 0xcc, + 0xc7, 0x11, 0x2c, 0xd0 }; + + uint8 output3[crypto::kSHA256Length]; + crypto::SHA256HashString(input3, output3, sizeof(output3)); + for (size_t i = 0; i < crypto::kSHA256Length; i++) + EXPECT_EQ(expected3[i], static_cast<int>(output3[i])); + + uint8 output_truncated3[12]; + crypto::SHA256HashString(input3, + output_truncated3, sizeof(output_truncated3)); + for (size_t i = 0; i < sizeof(output_truncated3); i++) + EXPECT_EQ(expected3[i], static_cast<int>(output_truncated3[i])); +} diff --git a/crypto/signature_creator_nss.cc b/crypto/signature_creator_nss.cc new file mode 100644 index 0000000000..da03312881 --- /dev/null +++ b/crypto/signature_creator_nss.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/signature_creator.h" + +#include <cryptohi.h> +#include <keyhi.h> +#include <stdlib.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/nss_util.h" +#include "crypto/rsa_private_key.h" + +namespace crypto { + +namespace { + +SECOidTag ToNSSSigOid(SignatureCreator::HashAlgorithm hash_alg) { + switch (hash_alg) { + case SignatureCreator::SHA1: + return SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; + case SignatureCreator::SHA256: + return SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; + } + return SEC_OID_UNKNOWN; +} + +SECOidTag ToNSSHashOid(SignatureCreator::HashAlgorithm hash_alg) { + switch (hash_alg) { + case SignatureCreator::SHA1: + return SEC_OID_SHA1; + case SignatureCreator::SHA256: + return SEC_OID_SHA256; + } + return SEC_OID_UNKNOWN; +} + +} // namespace + +SignatureCreator::~SignatureCreator() { + if (sign_context_) { + SGN_DestroyContext(sign_context_, PR_TRUE); + sign_context_ = NULL; + } +} + +// static +SignatureCreator* SignatureCreator::Create(RSAPrivateKey* key, + HashAlgorithm hash_alg) { + scoped_ptr<SignatureCreator> result(new SignatureCreator); + result->sign_context_ = SGN_NewContext(ToNSSSigOid(hash_alg), key->key()); + if (!result->sign_context_) { + NOTREACHED(); + return NULL; + } + + SECStatus rv = SGN_Begin(result->sign_context_); + if (rv != SECSuccess) { + NOTREACHED(); + return NULL; + } + + return result.release(); +} + +// static +bool SignatureCreator::Sign(RSAPrivateKey* key, + HashAlgorithm hash_alg, + const uint8* data, + int data_len, + std::vector<uint8>* signature) { + SECItem data_item; + data_item.type = siBuffer; + data_item.data = const_cast<unsigned char*>(data); + data_item.len = data_len; + + SECItem signature_item; + SECStatus rv = SGN_Digest(key->key(), ToNSSHashOid(hash_alg), &signature_item, + &data_item); + if (rv != SECSuccess) { + NOTREACHED(); + return false; + } + signature->assign(signature_item.data, + signature_item.data + signature_item.len); + SECITEM_FreeItem(&signature_item, PR_FALSE); + return true; +} + +bool SignatureCreator::Update(const uint8* data_part, int data_part_len) { + SECStatus rv = SGN_Update(sign_context_, data_part, data_part_len); + if (rv != SECSuccess) { + NOTREACHED(); + return false; + } + + return true; +} + +bool SignatureCreator::Final(std::vector<uint8>* signature) { + SECItem signature_item; + SECStatus rv = SGN_End(sign_context_, &signature_item); + if (rv != SECSuccess) { + return false; + } + signature->assign(signature_item.data, + signature_item.data + signature_item.len); + SECITEM_FreeItem(&signature_item, PR_FALSE); + return true; +} + +SignatureCreator::SignatureCreator() : sign_context_(NULL) { + EnsureNSSInit(); +} + +} // namespace crypto diff --git a/crypto/signature_creator_openssl.cc b/crypto/signature_creator_openssl.cc new file mode 100644 index 0000000000..0d90d50044 --- /dev/null +++ b/crypto/signature_creator_openssl.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/signature_creator.h" + +#include <openssl/evp.h> +#include <openssl/rsa.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "crypto/openssl_util.h" +#include "crypto/rsa_private_key.h" +#include "crypto/scoped_openssl_types.h" + +namespace crypto { + +namespace { + +const EVP_MD* ToOpenSSLDigest(SignatureCreator::HashAlgorithm hash_alg) { + switch (hash_alg) { + case SignatureCreator::SHA1: + return EVP_sha1(); + case SignatureCreator::SHA256: + return EVP_sha256(); + } + return NULL; +} + +int ToOpenSSLDigestType(SignatureCreator::HashAlgorithm hash_alg) { + switch (hash_alg) { + case SignatureCreator::SHA1: + return NID_sha1; + case SignatureCreator::SHA256: + return NID_sha256; + } + return NID_undef; +} + +} // namespace + +// static +SignatureCreator* SignatureCreator::Create(RSAPrivateKey* key, + HashAlgorithm hash_alg) { + OpenSSLErrStackTracer err_tracer(FROM_HERE); + scoped_ptr<SignatureCreator> result(new SignatureCreator); + const EVP_MD* const digest = ToOpenSSLDigest(hash_alg); + DCHECK(digest); + if (!digest) { + return NULL; + } + if (!EVP_DigestSignInit(result->sign_context_, NULL, digest, NULL, + key->key())) { + return NULL; + } + return result.release(); +} + +// static +bool SignatureCreator::Sign(RSAPrivateKey* key, + HashAlgorithm hash_alg, + const uint8* data, + int data_len, + std::vector<uint8>* signature) { + ScopedRSA rsa_key(EVP_PKEY_get1_RSA(key->key())); + if (!rsa_key) + return false; + signature->resize(RSA_size(rsa_key.get())); + + unsigned int len = 0; + if (!RSA_sign(ToOpenSSLDigestType(hash_alg), data, data_len, + vector_as_array(signature), &len, rsa_key.get())) { + signature->clear(); + return false; + } + signature->resize(len); + return true; +} + +SignatureCreator::SignatureCreator() + : sign_context_(EVP_MD_CTX_create()) { +} + +SignatureCreator::~SignatureCreator() { + EVP_MD_CTX_destroy(sign_context_); +} + +bool SignatureCreator::Update(const uint8* data_part, int data_part_len) { + OpenSSLErrStackTracer err_tracer(FROM_HERE); + return !!EVP_DigestSignUpdate(sign_context_, data_part, data_part_len); +} + +bool SignatureCreator::Final(std::vector<uint8>* signature) { + OpenSSLErrStackTracer err_tracer(FROM_HERE); + + // Determine the maximum length of the signature. + size_t len = 0; + if (!EVP_DigestSignFinal(sign_context_, NULL, &len)) { + signature->clear(); + return false; + } + signature->resize(len); + + // Sign it. + if (!EVP_DigestSignFinal(sign_context_, vector_as_array(signature), &len)) { + signature->clear(); + return false; + } + signature->resize(len); + return true; +} + +} // namespace crypto diff --git a/crypto/signature_creator_unittest.cc b/crypto/signature_creator_unittest.cc new file mode 100644 index 0000000000..694becdcb4 --- /dev/null +++ b/crypto/signature_creator_unittest.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium 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 <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "crypto/rsa_private_key.h" +#include "crypto/sha2.h" +#include "crypto/signature_creator.h" +#include "crypto/signature_verifier.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// This is the algorithm ID for SHA-1 with RSA encryption. +const uint8 kSHA1WithRSAAlgorithmID[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00 +}; + +// This is the algorithm ID for SHA-1 with RSA encryption. +const uint8 kSHA256WithRSAAlgorithmID[] = { + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0B, 0x05, 0x00 +}; + +} + +TEST(SignatureCreatorTest, BasicTest) { + // Do a verify round trip. + scoped_ptr<crypto::RSAPrivateKey> key_original( + crypto::RSAPrivateKey::Create(1024)); + ASSERT_TRUE(key_original.get()); + + std::vector<uint8> key_info; + key_original->ExportPrivateKey(&key_info); + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_info)); + ASSERT_TRUE(key.get()); + + scoped_ptr<crypto::SignatureCreator> signer( + crypto::SignatureCreator::Create(key.get(), + crypto::SignatureCreator::SHA1)); + ASSERT_TRUE(signer.get()); + + std::string data("Hello, World!"); + ASSERT_TRUE(signer->Update(reinterpret_cast<const uint8*>(data.c_str()), + data.size())); + + std::vector<uint8> signature; + ASSERT_TRUE(signer->Final(&signature)); + + std::vector<uint8> public_key_info; + ASSERT_TRUE(key_original->ExportPublicKey(&public_key_info)); + + crypto::SignatureVerifier verifier; + ASSERT_TRUE(verifier.VerifyInit( + kSHA1WithRSAAlgorithmID, sizeof(kSHA1WithRSAAlgorithmID), + &signature.front(), signature.size(), + &public_key_info.front(), public_key_info.size())); + + verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), + data.size()); + ASSERT_TRUE(verifier.VerifyFinal()); +} + +TEST(SignatureCreatorTest, SignDigestTest) { + // Do a verify round trip. + scoped_ptr<crypto::RSAPrivateKey> key_original( + crypto::RSAPrivateKey::Create(1024)); + ASSERT_TRUE(key_original.get()); + + std::vector<uint8> key_info; + key_original->ExportPrivateKey(&key_info); + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_info)); + ASSERT_TRUE(key.get()); + + std::string data("Hello, World!"); + std::string sha1 = base::SHA1HashString(data); + // Sign sha1 of the input data. + std::vector<uint8> signature; + ASSERT_TRUE(crypto::SignatureCreator::Sign( + key.get(), + crypto::SignatureCreator::SHA1, + reinterpret_cast<const uint8*>(sha1.c_str()), + sha1.size(), + &signature)); + + std::vector<uint8> public_key_info; + ASSERT_TRUE(key_original->ExportPublicKey(&public_key_info)); + + // Verify the input data. + crypto::SignatureVerifier verifier; + ASSERT_TRUE(verifier.VerifyInit( + kSHA1WithRSAAlgorithmID, sizeof(kSHA1WithRSAAlgorithmID), + &signature.front(), signature.size(), + &public_key_info.front(), public_key_info.size())); + + verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), + data.size()); + ASSERT_TRUE(verifier.VerifyFinal()); +} + +TEST(SignatureCreatorTest, SignSHA256DigestTest) { + // Do a verify round trip. + scoped_ptr<crypto::RSAPrivateKey> key_original( + crypto::RSAPrivateKey::Create(1024)); + ASSERT_TRUE(key_original.get()); + + std::vector<uint8> key_info; + key_original->ExportPrivateKey(&key_info); + scoped_ptr<crypto::RSAPrivateKey> key( + crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_info)); + ASSERT_TRUE(key.get()); + + std::string data("Hello, World!"); + std::string sha256 = crypto::SHA256HashString(data); + // Sign sha256 of the input data. + std::vector<uint8> signature; + ASSERT_TRUE(crypto::SignatureCreator::Sign( + key.get(), + crypto::SignatureCreator::HashAlgorithm::SHA256, + reinterpret_cast<const uint8*>(sha256.c_str()), + sha256.size(), + &signature)); + + std::vector<uint8> public_key_info; + ASSERT_TRUE(key_original->ExportPublicKey(&public_key_info)); + + // Verify the input data. + crypto::SignatureVerifier verifier; + ASSERT_TRUE(verifier.VerifyInit( + kSHA256WithRSAAlgorithmID, sizeof(kSHA256WithRSAAlgorithmID), + &signature.front(), signature.size(), + &public_key_info.front(), public_key_info.size())); + + verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), + data.size()); + ASSERT_TRUE(verifier.VerifyFinal()); +} diff --git a/crypto/signature_verifier_nss.cc b/crypto/signature_verifier_nss.cc new file mode 100644 index 0000000000..5be620da6b --- /dev/null +++ b/crypto/signature_verifier_nss.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/signature_verifier.h" + +#include <cryptohi.h> +#include <keyhi.h> +#include <pk11pub.h> +#include <secerr.h> +#include <sechash.h> +#include <stdlib.h> + +#include "base/logging.h" +#include "crypto/nss_util.h" +#include "crypto/third_party/nss/chromium-nss.h" + +namespace crypto { + +namespace { + +HASH_HashType ToNSSHashType(SignatureVerifier::HashAlgorithm hash_alg) { + switch (hash_alg) { + case SignatureVerifier::SHA1: + return HASH_AlgSHA1; + case SignatureVerifier::SHA256: + return HASH_AlgSHA256; + } + return HASH_AlgNULL; +} + +SECStatus VerifyRSAPSS_End(SECKEYPublicKey* public_key, + HASHContext* hash_context, + HASH_HashType mask_hash_alg, + unsigned int salt_len, + const unsigned char* signature, + unsigned int signature_len) { + unsigned int hash_len = HASH_ResultLenContext(hash_context); + std::vector<unsigned char> hash(hash_len); + HASH_End(hash_context, &hash[0], &hash_len, hash.size()); + + unsigned int modulus_len = SECKEY_PublicKeyStrength(public_key); + if (signature_len != modulus_len) { + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + return SECFailure; + } + std::vector<unsigned char> enc(signature_len); + SECStatus rv = PK11_PubEncryptRaw(public_key, &enc[0], + const_cast<unsigned char*>(signature), + signature_len, NULL); + if (rv != SECSuccess) { + LOG(WARNING) << "PK11_PubEncryptRaw failed"; + return rv; + } + return emsa_pss_verify(&hash[0], &enc[0], enc.size(), + HASH_GetType(hash_context), mask_hash_alg, + salt_len); +} + +} // namespace + +SignatureVerifier::SignatureVerifier() + : vfy_context_(NULL), + hash_alg_(SHA1), + mask_hash_alg_(SHA1), + salt_len_(0), + public_key_(NULL), + hash_context_(NULL) { + EnsureNSSInit(); +} + +SignatureVerifier::~SignatureVerifier() { + Reset(); +} + +bool SignatureVerifier::VerifyInit(const uint8* signature_algorithm, + int signature_algorithm_len, + const uint8* signature, + int signature_len, + const uint8* public_key_info, + int public_key_info_len) { + if (vfy_context_ || hash_context_) + return false; + + signature_.assign(signature, signature + signature_len); + + SECKEYPublicKey* public_key = DecodePublicKeyInfo(public_key_info, + public_key_info_len); + if (!public_key) + return false; + + PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + SECKEY_DestroyPublicKey(public_key); + return false; + } + + SECItem sig_alg_der; + sig_alg_der.type = siBuffer; + sig_alg_der.data = const_cast<uint8*>(signature_algorithm); + sig_alg_der.len = signature_algorithm_len; + SECAlgorithmID sig_alg_id; + SECStatus rv; + rv = SEC_QuickDERDecodeItem(arena, &sig_alg_id, + SEC_ASN1_GET(SECOID_AlgorithmIDTemplate), + &sig_alg_der); + if (rv != SECSuccess) { + SECKEY_DestroyPublicKey(public_key); + PORT_FreeArena(arena, PR_TRUE); + return false; + } + + SECItem sig; + sig.type = siBuffer; + sig.data = const_cast<uint8*>(signature); + sig.len = signature_len; + SECOidTag hash_alg_tag; + vfy_context_ = VFY_CreateContextWithAlgorithmID(public_key, &sig, + &sig_alg_id, &hash_alg_tag, + NULL); + SECKEY_DestroyPublicKey(public_key); // Done with public_key. + PORT_FreeArena(arena, PR_TRUE); // Done with sig_alg_id. + if (!vfy_context_) { + // A corrupted RSA signature could be detected without the data, so + // VFY_CreateContextWithAlgorithmID may fail with SEC_ERROR_BAD_SIGNATURE + // (-8182). + return false; + } + + rv = VFY_Begin(vfy_context_); + if (rv != SECSuccess) { + NOTREACHED(); + return false; + } + return true; +} + +bool SignatureVerifier::VerifyInitRSAPSS(HashAlgorithm hash_alg, + HashAlgorithm mask_hash_alg, + int salt_len, + const uint8* signature, + int signature_len, + const uint8* public_key_info, + int public_key_info_len) { + if (vfy_context_ || hash_context_) + return false; + + signature_.assign(signature, signature + signature_len); + + SECKEYPublicKey* public_key = DecodePublicKeyInfo(public_key_info, + public_key_info_len); + if (!public_key) + return false; + + public_key_ = public_key; + hash_alg_ = hash_alg; + mask_hash_alg_ = mask_hash_alg; + salt_len_ = salt_len; + hash_context_ = HASH_Create(ToNSSHashType(hash_alg_)); + if (!hash_context_) + return false; + HASH_Begin(hash_context_); + return true; +} + +void SignatureVerifier::VerifyUpdate(const uint8* data_part, + int data_part_len) { + if (vfy_context_) { + SECStatus rv = VFY_Update(vfy_context_, data_part, data_part_len); + DCHECK_EQ(SECSuccess, rv); + } else { + HASH_Update(hash_context_, data_part, data_part_len); + } +} + +bool SignatureVerifier::VerifyFinal() { + SECStatus rv; + if (vfy_context_) { + rv = VFY_End(vfy_context_); + } else { + rv = VerifyRSAPSS_End(public_key_, hash_context_, + ToNSSHashType(mask_hash_alg_), salt_len_, + signature_.data(), + signature_.size()); + } + Reset(); + + // If signature verification fails, the error code is + // SEC_ERROR_BAD_SIGNATURE (-8182). + return (rv == SECSuccess); +} + +// static +SECKEYPublicKey* SignatureVerifier::DecodePublicKeyInfo( + const uint8* public_key_info, + int public_key_info_len) { + CERTSubjectPublicKeyInfo* spki = NULL; + SECItem spki_der; + spki_der.type = siBuffer; + spki_der.data = const_cast<uint8*>(public_key_info); + spki_der.len = public_key_info_len; + spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_der); + if (!spki) + return NULL; + SECKEYPublicKey* public_key = SECKEY_ExtractPublicKey(spki); + SECKEY_DestroySubjectPublicKeyInfo(spki); // Done with spki. + return public_key; +} + +void SignatureVerifier::Reset() { + if (vfy_context_) { + VFY_DestroyContext(vfy_context_, PR_TRUE); + vfy_context_ = NULL; + } + if (hash_context_) { + HASH_Destroy(hash_context_); + hash_context_ = NULL; + } + if (public_key_) { + SECKEY_DestroyPublicKey(public_key_); + public_key_ = NULL; + } + signature_.clear(); +} + +} // namespace crypto diff --git a/crypto/signature_verifier_openssl.cc b/crypto/signature_verifier_openssl.cc new file mode 100644 index 0000000000..a33d665ec1 --- /dev/null +++ b/crypto/signature_verifier_openssl.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/signature_verifier.h" + +#include <openssl/evp.h> +#include <openssl/x509.h> + +#include <vector> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "crypto/openssl_util.h" +#include "crypto/scoped_openssl_types.h" + +namespace crypto { + +namespace { + +const EVP_MD* ToOpenSSLDigest(SignatureVerifier::HashAlgorithm hash_alg) { + switch (hash_alg) { + case SignatureVerifier::SHA1: + return EVP_sha1(); + case SignatureVerifier::SHA256: + return EVP_sha256(); + } + return NULL; +} + +} // namespace + +struct SignatureVerifier::VerifyContext { + ScopedEVP_MD_CTX ctx; +}; + +SignatureVerifier::SignatureVerifier() + : verify_context_(NULL) { +} + +SignatureVerifier::~SignatureVerifier() { + Reset(); +} + +bool SignatureVerifier::VerifyInit(const uint8* signature_algorithm, + int signature_algorithm_len, + const uint8* signature, + int signature_len, + const uint8* public_key_info, + int public_key_info_len) { + OpenSSLErrStackTracer err_tracer(FROM_HERE); + ScopedOpenSSL<X509_ALGOR, X509_ALGOR_free> algorithm( + d2i_X509_ALGOR(NULL, &signature_algorithm, signature_algorithm_len)); + if (!algorithm.get()) + return false; + int nid = OBJ_obj2nid(algorithm.get()->algorithm); + const EVP_MD* digest; + if (nid == NID_ecdsa_with_SHA1) { + digest = EVP_sha1(); + } else if (nid == NID_ecdsa_with_SHA256) { + digest = EVP_sha256(); + } else { + // This works for PKCS #1 v1.5 RSA signatures, but not for ECDSA + // signatures. + digest = EVP_get_digestbyobj(algorithm.get()->algorithm); + } + if (!digest) + return false; + + return CommonInit(digest, signature, signature_len, public_key_info, + public_key_info_len, NULL); +} + +bool SignatureVerifier::VerifyInitRSAPSS(HashAlgorithm hash_alg, + HashAlgorithm mask_hash_alg, + int salt_len, + const uint8* signature, + int signature_len, + const uint8* public_key_info, + int public_key_info_len) { + OpenSSLErrStackTracer err_tracer(FROM_HERE); + const EVP_MD* const digest = ToOpenSSLDigest(hash_alg); + DCHECK(digest); + if (!digest) { + return false; + } + + EVP_PKEY_CTX* pkey_ctx; + if (!CommonInit(digest, signature, signature_len, public_key_info, + public_key_info_len, &pkey_ctx)) { + return false; + } + + int rv = EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING); + if (rv != 1) + return false; + const EVP_MD* const mgf_digest = ToOpenSSLDigest(mask_hash_alg); + DCHECK(mgf_digest); + if (!mgf_digest) { + return false; + } + rv = EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf_digest); + if (rv != 1) + return false; + rv = EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len); + return rv == 1; +} + +void SignatureVerifier::VerifyUpdate(const uint8* data_part, + int data_part_len) { + DCHECK(verify_context_); + OpenSSLErrStackTracer err_tracer(FROM_HERE); + int rv = EVP_DigestVerifyUpdate(verify_context_->ctx.get(), + data_part, data_part_len); + DCHECK_EQ(rv, 1); +} + +bool SignatureVerifier::VerifyFinal() { + DCHECK(verify_context_); + OpenSSLErrStackTracer err_tracer(FROM_HERE); + int rv = EVP_DigestVerifyFinal(verify_context_->ctx.get(), + vector_as_array(&signature_), + signature_.size()); + DCHECK_EQ(static_cast<int>(!!rv), rv); + Reset(); + return rv == 1; +} + +bool SignatureVerifier::CommonInit(const EVP_MD* digest, + const uint8* signature, + int signature_len, + const uint8* public_key_info, + int public_key_info_len, + EVP_PKEY_CTX** pkey_ctx) { + if (verify_context_) + return false; + + verify_context_ = new VerifyContext; + + signature_.assign(signature, signature + signature_len); + + const uint8_t* ptr = public_key_info; + ScopedEVP_PKEY public_key(d2i_PUBKEY(nullptr, &ptr, public_key_info_len)); + if (!public_key.get() || ptr != public_key_info + public_key_info_len) + return false; + + verify_context_->ctx.reset(EVP_MD_CTX_create()); + int rv = EVP_DigestVerifyInit(verify_context_->ctx.get(), pkey_ctx, + digest, nullptr, public_key.get()); + return rv == 1; +} + +void SignatureVerifier::Reset() { + delete verify_context_; + verify_context_ = NULL; + signature_.clear(); +} + +} // namespace crypto diff --git a/crypto/signature_verifier_unittest.cc b/crypto/signature_verifier_unittest.cc new file mode 100644 index 0000000000..a661ff7f8a --- /dev/null +++ b/crypto/signature_verifier_unittest.cc @@ -0,0 +1,1167 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/signature_verifier.h" + +#include "base/numerics/safe_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SignatureVerifierTest, BasicTest) { + // The input data in this test comes from real certificates. + // + // tbs_certificate ("to-be-signed certificate", the part of a certificate + // that is signed), signature_algorithm, and algorithm come from the + // certificate of bugs.webkit.org. + // + // public_key_info comes from the certificate of the issuer, Go Daddy Secure + // Certification Authority. + // + // The bytes in the array initializers are formatted to expose the DER + // encoding of the ASN.1 structures. + + // The data that is signed is the following ASN.1 structure: + // TBSCertificate ::= SEQUENCE { + // ... -- omitted, not important + // } + const uint8 tbs_certificate[1017] = { + 0x30, 0x82, 0x03, 0xf5, // a SEQUENCE of length 1013 (0x3f5) + 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x03, 0x43, 0xdd, 0x63, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, + 0x00, 0x30, 0x81, 0xca, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, + 0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x1a, 0x30, 0x18, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x11, 0x47, 0x6f, 0x44, 0x61, 0x64, + 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x31, 0x33, 0x30, 0x31, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x2a, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, + 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x6f, 0x72, 0x79, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x13, 0x27, 0x47, 0x6f, 0x20, 0x44, 0x61, 0x64, 0x64, 0x79, + 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x31, 0x11, 0x30, 0x0f, 0x06, + 0x03, 0x55, 0x04, 0x05, 0x13, 0x08, 0x30, 0x37, 0x39, 0x36, 0x39, 0x32, + 0x38, 0x37, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x38, 0x30, 0x33, 0x31, 0x38, + 0x32, 0x33, 0x33, 0x35, 0x31, 0x39, 0x5a, 0x17, 0x0d, 0x31, 0x31, 0x30, + 0x33, 0x31, 0x38, 0x32, 0x33, 0x33, 0x35, 0x31, 0x39, 0x5a, 0x30, 0x79, + 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, + 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, + 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x12, + 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x09, 0x43, 0x75, 0x70, + 0x65, 0x72, 0x74, 0x69, 0x6e, 0x6f, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, + 0x55, 0x04, 0x0a, 0x13, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x49, + 0x6e, 0x63, 0x2e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x13, 0x0c, 0x4d, 0x61, 0x63, 0x20, 0x4f, 0x53, 0x20, 0x46, 0x6f, 0x72, + 0x67, 0x65, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x0c, 0x2a, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, 0x74, 0x2e, 0x6f, 0x72, + 0x67, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, + 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xa7, 0x62, 0x79, 0x41, 0xda, 0x28, + 0xf2, 0xc0, 0x4f, 0xe0, 0x25, 0xaa, 0xa1, 0x2e, 0x3b, 0x30, 0x94, 0xb5, + 0xc9, 0x26, 0x3a, 0x1b, 0xe2, 0xd0, 0xcc, 0xa2, 0x95, 0xe2, 0x91, 0xc0, + 0xf0, 0x40, 0x9e, 0x27, 0x6e, 0xbd, 0x6e, 0xde, 0x7c, 0xb6, 0x30, 0x5c, + 0xb8, 0x9b, 0x01, 0x2f, 0x92, 0x04, 0xa1, 0xef, 0x4a, 0xb1, 0x6c, 0xb1, + 0x7e, 0x8e, 0xcd, 0xa6, 0xf4, 0x40, 0x73, 0x1f, 0x2c, 0x96, 0xad, 0xff, + 0x2a, 0x6d, 0x0e, 0xba, 0x52, 0x84, 0x83, 0xb0, 0x39, 0xee, 0xc9, 0x39, + 0xdc, 0x1e, 0x34, 0xd0, 0xd8, 0x5d, 0x7a, 0x09, 0xac, 0xa9, 0xee, 0xca, + 0x65, 0xf6, 0x85, 0x3a, 0x6b, 0xee, 0xe4, 0x5c, 0x5e, 0xf8, 0xda, 0xd1, + 0xce, 0x88, 0x47, 0xcd, 0x06, 0x21, 0xe0, 0xb9, 0x4b, 0xe4, 0x07, 0xcb, + 0x57, 0xdc, 0xca, 0x99, 0x54, 0xf7, 0x0e, 0xd5, 0x17, 0x95, 0x05, 0x2e, + 0xe9, 0xb1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0xce, 0x30, + 0x82, 0x01, 0xca, 0x30, 0x09, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x02, + 0x30, 0x00, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, + 0x02, 0x05, 0xa0, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x16, + 0x30, 0x14, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x57, + 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x50, 0x30, 0x4e, 0x30, 0x4c, 0xa0, + 0x4a, 0xa0, 0x48, 0x86, 0x46, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, + 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, + 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x69, 0x73, 0x73, 0x75, 0x69, 0x6e, 0x67, 0x33, 0x2e, + 0x63, 0x72, 0x6c, 0x30, 0x52, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x4b, + 0x30, 0x49, 0x30, 0x47, 0x06, 0x0b, 0x60, 0x86, 0x48, 0x01, 0x86, 0xfd, + 0x6d, 0x01, 0x07, 0x17, 0x02, 0x30, 0x38, 0x30, 0x36, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x2a, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, 0x79, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, + 0x6f, 0x72, 0x79, 0x30, 0x7f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, + 0x07, 0x01, 0x01, 0x04, 0x73, 0x30, 0x71, 0x30, 0x23, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x86, 0x17, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x2e, 0x67, 0x6f, 0x64, + 0x61, 0x64, 0x64, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x4a, 0x06, 0x08, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x2e, 0x67, 0x6f, 0x64, 0x61, 0x64, 0x64, + 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x67, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x2e, 0x63, 0x72, 0x74, + 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x48, + 0xdf, 0x60, 0x32, 0xcc, 0x89, 0x01, 0xb6, 0xdc, 0x2f, 0xe3, 0x73, 0xb5, + 0x9c, 0x16, 0x58, 0x32, 0x68, 0xa9, 0xc3, 0x30, 0x1f, 0x06, 0x03, 0x55, + 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xfd, 0xac, 0x61, 0x32, + 0x93, 0x6c, 0x45, 0xd6, 0xe2, 0xee, 0x85, 0x5f, 0x9a, 0xba, 0xe7, 0x76, + 0x99, 0x68, 0xcc, 0xe7, 0x30, 0x23, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, + 0x1c, 0x30, 0x1a, 0x82, 0x0c, 0x2a, 0x2e, 0x77, 0x65, 0x62, 0x6b, 0x69, + 0x74, 0x2e, 0x6f, 0x72, 0x67, 0x82, 0x0a, 0x77, 0x65, 0x62, 0x6b, 0x69, + 0x74, 0x2e, 0x6f, 0x72, 0x67 + }; + + // The signature algorithm is specified as the following ASN.1 structure: + // AlgorithmIdentifier ::= SEQUENCE { + // algorithm OBJECT IDENTIFIER, + // parameters ANY DEFINED BY algorithm OPTIONAL } + // + const uint8 signature_algorithm[15] = { + 0x30, 0x0d, // a SEQUENCE of length 13 (0xd) + 0x06, 0x09, // an OBJECT IDENTIFIER of length 9 + // 1.2.840.113549.1.1.5 - sha1WithRSAEncryption + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, + 0x05, 0x00, // a NULL of length 0 + }; + + // RSA signature, a big integer in the big-endian byte order. + const uint8 signature[256] = { + 0x1e, 0x6a, 0xe7, 0xe0, 0x4f, 0xe7, 0x4d, 0xd0, 0x69, 0x7c, 0xf8, 0x8f, + 0x99, 0xb4, 0x18, 0x95, 0x36, 0x24, 0x0f, 0x0e, 0xa3, 0xea, 0x34, 0x37, + 0xf4, 0x7d, 0xd5, 0x92, 0x35, 0x53, 0x72, 0x76, 0x3f, 0x69, 0xf0, 0x82, + 0x56, 0xe3, 0x94, 0x7a, 0x1d, 0x1a, 0x81, 0xaf, 0x9f, 0xc7, 0x43, 0x01, + 0x64, 0xd3, 0x7c, 0x0d, 0xc8, 0x11, 0x4e, 0x4a, 0xe6, 0x1a, 0xc3, 0x01, + 0x74, 0xe8, 0x35, 0x87, 0x5c, 0x61, 0xaa, 0x8a, 0x46, 0x06, 0xbe, 0x98, + 0x95, 0x24, 0x9e, 0x01, 0xe3, 0xe6, 0xa0, 0x98, 0xee, 0x36, 0x44, 0x56, + 0x8d, 0x23, 0x9c, 0x65, 0xea, 0x55, 0x6a, 0xdf, 0x66, 0xee, 0x45, 0xe8, + 0xa0, 0xe9, 0x7d, 0x9a, 0xba, 0x94, 0xc5, 0xc8, 0xc4, 0x4b, 0x98, 0xff, + 0x9a, 0x01, 0x31, 0x6d, 0xf9, 0x2b, 0x58, 0xe7, 0xe7, 0x2a, 0xc5, 0x4d, + 0xbb, 0xbb, 0xcd, 0x0d, 0x70, 0xe1, 0xad, 0x03, 0xf5, 0xfe, 0xf4, 0x84, + 0x71, 0x08, 0xd2, 0xbc, 0x04, 0x7b, 0x26, 0x1c, 0xa8, 0x0f, 0x9c, 0xd8, + 0x12, 0x6a, 0x6f, 0x2b, 0x67, 0xa1, 0x03, 0x80, 0x9a, 0x11, 0x0b, 0xe9, + 0xe0, 0xb5, 0xb3, 0xb8, 0x19, 0x4e, 0x0c, 0xa4, 0xd9, 0x2b, 0x3b, 0xc2, + 0xca, 0x20, 0xd3, 0x0c, 0xa4, 0xff, 0x93, 0x13, 0x1f, 0xfc, 0xba, 0x94, + 0x93, 0x8c, 0x64, 0x15, 0x2e, 0x28, 0xa9, 0x55, 0x8c, 0x2c, 0x48, 0xd3, + 0xd3, 0xc1, 0x50, 0x69, 0x19, 0xe8, 0x34, 0xd3, 0xf1, 0x04, 0x9f, 0x0a, + 0x7a, 0x21, 0x87, 0xbf, 0xb9, 0x59, 0x37, 0x2e, 0xf4, 0x71, 0xa5, 0x3e, + 0xbe, 0xcd, 0x70, 0x83, 0x18, 0xf8, 0x8a, 0x72, 0x85, 0x45, 0x1f, 0x08, + 0x01, 0x6f, 0x37, 0xf5, 0x2b, 0x7b, 0xea, 0xb9, 0x8b, 0xa3, 0xcc, 0xfd, + 0x35, 0x52, 0xdd, 0x66, 0xde, 0x4f, 0x30, 0xc5, 0x73, 0x81, 0xb6, 0xe8, + 0x3c, 0xd8, 0x48, 0x8a + }; + + // The public key is specified as the following ASN.1 structure: + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING } + const uint8 public_key_info[294] = { + 0x30, 0x82, 0x01, 0x22, // a SEQUENCE of length 290 (0x122) + // algorithm + 0x30, 0x0d, // a SEQUENCE of length 13 + 0x06, 0x09, // an OBJECT IDENTIFIER of length 9 + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, // a NULL of length 0 + // subjectPublicKey + 0x03, 0x82, 0x01, 0x0f, // a BIT STRING of length 271 (0x10f) + 0x00, // number of unused bits + 0x30, 0x82, 0x01, 0x0a, // a SEQUENCE of length 266 (0x10a) + // modulus + 0x02, 0x82, 0x01, 0x01, // an INTEGER of length 257 (0x101) + 0x00, 0xc4, 0x2d, 0xd5, 0x15, 0x8c, 0x9c, 0x26, 0x4c, 0xec, + 0x32, 0x35, 0xeb, 0x5f, 0xb8, 0x59, 0x01, 0x5a, 0xa6, 0x61, + 0x81, 0x59, 0x3b, 0x70, 0x63, 0xab, 0xe3, 0xdc, 0x3d, 0xc7, + 0x2a, 0xb8, 0xc9, 0x33, 0xd3, 0x79, 0xe4, 0x3a, 0xed, 0x3c, + 0x30, 0x23, 0x84, 0x8e, 0xb3, 0x30, 0x14, 0xb6, 0xb2, 0x87, + 0xc3, 0x3d, 0x95, 0x54, 0x04, 0x9e, 0xdf, 0x99, 0xdd, 0x0b, + 0x25, 0x1e, 0x21, 0xde, 0x65, 0x29, 0x7e, 0x35, 0xa8, 0xa9, + 0x54, 0xeb, 0xf6, 0xf7, 0x32, 0x39, 0xd4, 0x26, 0x55, 0x95, + 0xad, 0xef, 0xfb, 0xfe, 0x58, 0x86, 0xd7, 0x9e, 0xf4, 0x00, + 0x8d, 0x8c, 0x2a, 0x0c, 0xbd, 0x42, 0x04, 0xce, 0xa7, 0x3f, + 0x04, 0xf6, 0xee, 0x80, 0xf2, 0xaa, 0xef, 0x52, 0xa1, 0x69, + 0x66, 0xda, 0xbe, 0x1a, 0xad, 0x5d, 0xda, 0x2c, 0x66, 0xea, + 0x1a, 0x6b, 0xbb, 0xe5, 0x1a, 0x51, 0x4a, 0x00, 0x2f, 0x48, + 0xc7, 0x98, 0x75, 0xd8, 0xb9, 0x29, 0xc8, 0xee, 0xf8, 0x66, + 0x6d, 0x0a, 0x9c, 0xb3, 0xf3, 0xfc, 0x78, 0x7c, 0xa2, 0xf8, + 0xa3, 0xf2, 0xb5, 0xc3, 0xf3, 0xb9, 0x7a, 0x91, 0xc1, 0xa7, + 0xe6, 0x25, 0x2e, 0x9c, 0xa8, 0xed, 0x12, 0x65, 0x6e, 0x6a, + 0xf6, 0x12, 0x44, 0x53, 0x70, 0x30, 0x95, 0xc3, 0x9c, 0x2b, + 0x58, 0x2b, 0x3d, 0x08, 0x74, 0x4a, 0xf2, 0xbe, 0x51, 0xb0, + 0xbf, 0x87, 0xd0, 0x4c, 0x27, 0x58, 0x6b, 0xb5, 0x35, 0xc5, + 0x9d, 0xaf, 0x17, 0x31, 0xf8, 0x0b, 0x8f, 0xee, 0xad, 0x81, + 0x36, 0x05, 0x89, 0x08, 0x98, 0xcf, 0x3a, 0xaf, 0x25, 0x87, + 0xc0, 0x49, 0xea, 0xa7, 0xfd, 0x67, 0xf7, 0x45, 0x8e, 0x97, + 0xcc, 0x14, 0x39, 0xe2, 0x36, 0x85, 0xb5, 0x7e, 0x1a, 0x37, + 0xfd, 0x16, 0xf6, 0x71, 0x11, 0x9a, 0x74, 0x30, 0x16, 0xfe, + 0x13, 0x94, 0xa3, 0x3f, 0x84, 0x0d, 0x4f, + // public exponent + 0x02, 0x03, // an INTEGER of length 3 + 0x01, 0x00, 0x01 + }; + + // We use the signature verifier to perform four signature verification + // tests. + crypto::SignatureVerifier verifier; + bool ok; + + // Test 1: feed all of the data to the verifier at once (a single + // VerifyUpdate call). + ok = verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + signature, sizeof(signature), + public_key_info, sizeof(public_key_info)); + EXPECT_TRUE(ok); + verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate)); + ok = verifier.VerifyFinal(); + EXPECT_TRUE(ok); + + // Test 2: feed the data to the verifier in three parts (three VerifyUpdate + // calls). + ok = verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + signature, sizeof(signature), + public_key_info, sizeof(public_key_info)); + EXPECT_TRUE(ok); + verifier.VerifyUpdate(tbs_certificate, 256); + verifier.VerifyUpdate(tbs_certificate + 256, 256); + verifier.VerifyUpdate(tbs_certificate + 512, sizeof(tbs_certificate) - 512); + ok = verifier.VerifyFinal(); + EXPECT_TRUE(ok); + + // Test 3: verify the signature with incorrect data. + uint8 bad_tbs_certificate[sizeof(tbs_certificate)]; + memcpy(bad_tbs_certificate, tbs_certificate, sizeof(tbs_certificate)); + bad_tbs_certificate[10] += 1; // Corrupt one byte of the data. + ok = verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + signature, sizeof(signature), + public_key_info, sizeof(public_key_info)); + EXPECT_TRUE(ok); + verifier.VerifyUpdate(bad_tbs_certificate, sizeof(bad_tbs_certificate)); + ok = verifier.VerifyFinal(); + EXPECT_FALSE(ok); + + // Test 4: verify a bad signature. + uint8 bad_signature[sizeof(signature)]; + memcpy(bad_signature, signature, sizeof(signature)); + bad_signature[10] += 1; // Corrupt one byte of the signature. + ok = verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + bad_signature, sizeof(bad_signature), + public_key_info, sizeof(public_key_info)); + + // A crypto library (e.g., NSS) may detect that the signature is corrupted + // and cause VerifyInit to return false, so it is fine for 'ok' to be false. + if (ok) { + verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate)); + ok = verifier.VerifyFinal(); + EXPECT_FALSE(ok); + } + + // Test 5: import an invalid key. + uint8_t bad_public_key_info[sizeof(public_key_info)]; + memcpy(bad_public_key_info, public_key_info, sizeof(public_key_info)); + bad_public_key_info[0] += 1; // Corrupt part of the SPKI syntax. + ok = verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + signature, sizeof(signature), + bad_public_key_info, sizeof(bad_public_key_info)); + EXPECT_FALSE(ok); + + // Test 6: import a key with extra data. + uint8_t long_public_key_info[sizeof(public_key_info) + 5]; + memset(long_public_key_info, 0, sizeof(long_public_key_info)); + memcpy(long_public_key_info, public_key_info, sizeof(public_key_info)); + ok = verifier.VerifyInit(signature_algorithm, + sizeof(signature_algorithm), + signature, sizeof(signature), + long_public_key_info, sizeof(long_public_key_info)); + EXPECT_FALSE(ok); +} + +////////////////////////////////////////////////////////////////////// +// +// RSA-PSS signature verification known answer test +// +////////////////////////////////////////////////////////////////////// + +// The following RSA-PSS signature test vectors come from the pss-vect.txt +// file downloaded from +// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip. +// +// For each key, 6 random messages of length between 1 and 256 octets have +// been RSASSA-PSS signed. +// +// Hash function: SHA-1 +// Mask generation function: MGF1 with SHA-1 +// Salt length: 20 octets + +// Example 1: A 1024-bit RSA Key Pair" + +// RSA modulus n: +static const char rsa_modulus_n_1[] = + "a5 6e 4a 0e 70 10 17 58 9a 51 87 dc 7e a8 41 d1 " + "56 f2 ec 0e 36 ad 52 a4 4d fe b1 e6 1f 7a d9 91 " + "d8 c5 10 56 ff ed b1 62 b4 c0 f2 83 a1 2a 88 a3 " + "94 df f5 26 ab 72 91 cb b3 07 ce ab fc e0 b1 df " + "d5 cd 95 08 09 6d 5b 2b 8b 6d f5 d6 71 ef 63 77 " + "c0 92 1c b2 3c 27 0a 70 e2 59 8e 6f f8 9d 19 f1 " + "05 ac c2 d3 f0 cb 35 f2 92 80 e1 38 6b 6f 64 c4 " + "ef 22 e1 e1 f2 0d 0c e8 cf fb 22 49 bd 9a 21 37 "; +// RSA public exponent e: " +static const char rsa_public_exponent_e_1[] = + "01 00 01 "; + +// RSASSA-PSS Signature Example 1.1 +// Message to be signed: +static const char message_1_1[] = + "cd c8 7d a2 23 d7 86 df 3b 45 e0 bb bc 72 13 26 " + "d1 ee 2a f8 06 cc 31 54 75 cc 6f 0d 9c 66 e1 b6 " + "23 71 d4 5c e2 39 2e 1a c9 28 44 c3 10 10 2f 15 " + "6a 0d 8d 52 c1 f4 c4 0b a3 aa 65 09 57 86 cb 76 " + "97 57 a6 56 3b a9 58 fe d0 bc c9 84 e8 b5 17 a3 " + "d5 f5 15 b2 3b 8a 41 e7 4a a8 67 69 3f 90 df b0 " + "61 a6 e8 6d fa ae e6 44 72 c0 0e 5f 20 94 57 29 " + "cb eb e7 7f 06 ce 78 e0 8f 40 98 fb a4 1f 9d 61 " + "93 c0 31 7e 8b 60 d4 b6 08 4a cb 42 d2 9e 38 08 " + "a3 bc 37 2d 85 e3 31 17 0f cb f7 cc 72 d0 b7 1c " + "29 66 48 b3 a4 d1 0f 41 62 95 d0 80 7a a6 25 ca " + "b2 74 4f d9 ea 8f d2 23 c4 25 37 02 98 28 bd 16 " + "be 02 54 6f 13 0f d2 e3 3b 93 6d 26 76 e0 8a ed " + "1b 73 31 8b 75 0a 01 67 d0 "; +// Salt: +static const char salt_1_1[] = + "de e9 59 c7 e0 64 11 36 14 20 ff 80 18 5e d5 7f " + "3e 67 76 af "; +// Signature: +static const char signature_1_1[] = + "90 74 30 8f b5 98 e9 70 1b 22 94 38 8e 52 f9 71 " + "fa ac 2b 60 a5 14 5a f1 85 df 52 87 b5 ed 28 87 " + "e5 7c e7 fd 44 dc 86 34 e4 07 c8 e0 e4 36 0b c2 " + "26 f3 ec 22 7f 9d 9e 54 63 8e 8d 31 f5 05 12 15 " + "df 6e bb 9c 2f 95 79 aa 77 59 8a 38 f9 14 b5 b9 " + "c1 bd 83 c4 e2 f9 f3 82 a0 d0 aa 35 42 ff ee 65 " + "98 4a 60 1b c6 9e b2 8d eb 27 dc a1 2c 82 c2 d4 " + "c3 f6 6c d5 00 f1 ff 2b 99 4d 8a 4e 30 cb b3 3c "; + +// RSASSA-PSS Signature Example 1.2 +// Message to be signed: +static const char message_1_2[] = + "85 13 84 cd fe 81 9c 22 ed 6c 4c cb 30 da eb 5c " + "f0 59 bc 8e 11 66 b7 e3 53 0c 4c 23 3e 2b 5f 8f " + "71 a1 cc a5 82 d4 3e cc 72 b1 bc a1 6d fc 70 13 " + "22 6b 9e "; +// Salt: +static const char salt_1_2[] = + "ef 28 69 fa 40 c3 46 cb 18 3d ab 3d 7b ff c9 8f " + "d5 6d f4 2d "; +// Signature: +static const char signature_1_2[] = + "3e f7 f4 6e 83 1b f9 2b 32 27 41 42 a5 85 ff ce " + "fb dc a7 b3 2a e9 0d 10 fb 0f 0c 72 99 84 f0 4e " + "f2 9a 9d f0 78 07 75 ce 43 73 9b 97 83 83 90 db " + "0a 55 05 e6 3d e9 27 02 8d 9d 29 b2 19 ca 2c 45 " + "17 83 25 58 a5 5d 69 4a 6d 25 b9 da b6 60 03 c4 " + "cc cd 90 78 02 19 3b e5 17 0d 26 14 7d 37 b9 35 " + "90 24 1b e5 1c 25 05 5f 47 ef 62 75 2c fb e2 14 " + "18 fa fe 98 c2 2c 4d 4d 47 72 4f db 56 69 e8 43 "; + +// RSASSA-PSS Signature Example 1.3 +// Message to be signed: +static const char message_1_3[] = + "a4 b1 59 94 17 61 c4 0c 6a 82 f2 b8 0d 1b 94 f5 " + "aa 26 54 fd 17 e1 2d 58 88 64 67 9b 54 cd 04 ef " + "8b d0 30 12 be 8d c3 7f 4b 83 af 79 63 fa ff 0d " + "fa 22 54 77 43 7c 48 01 7f f2 be 81 91 cf 39 55 " + "fc 07 35 6e ab 3f 32 2f 7f 62 0e 21 d2 54 e5 db " + "43 24 27 9f e0 67 e0 91 0e 2e 81 ca 2c ab 31 c7 " + "45 e6 7a 54 05 8e b5 0d 99 3c db 9e d0 b4 d0 29 " + "c0 6d 21 a9 4c a6 61 c3 ce 27 fa e1 d6 cb 20 f4 " + "56 4d 66 ce 47 67 58 3d 0e 5f 06 02 15 b5 90 17 " + "be 85 ea 84 89 39 12 7b d8 c9 c4 d4 7b 51 05 6c " + "03 1c f3 36 f1 7c 99 80 f3 b8 f5 b9 b6 87 8e 8b " + "79 7a a4 3b 88 26 84 33 3e 17 89 3f e9 ca a6 aa " + "29 9f 7e d1 a1 8e e2 c5 48 64 b7 b2 b9 9b 72 61 " + "8f b0 25 74 d1 39 ef 50 f0 19 c9 ee f4 16 97 13 " + "38 e7 d4 70 "; +// Salt: +static const char salt_1_3[] = + "71 0b 9c 47 47 d8 00 d4 de 87 f1 2a fd ce 6d f1 " + "81 07 cc 77 "; +// Signature: +static const char signature_1_3[] = + "66 60 26 fb a7 1b d3 e7 cf 13 15 7c c2 c5 1a 8e " + "4a a6 84 af 97 78 f9 18 49 f3 43 35 d1 41 c0 01 " + "54 c4 19 76 21 f9 62 4a 67 5b 5a bc 22 ee 7d 5b " + "aa ff aa e1 c9 ba ca 2c c3 73 b3 f3 3e 78 e6 14 " + "3c 39 5a 91 aa 7f ac a6 64 eb 73 3a fd 14 d8 82 " + "72 59 d9 9a 75 50 fa ca 50 1e f2 b0 4e 33 c2 3a " + "a5 1f 4b 9e 82 82 ef db 72 8c c0 ab 09 40 5a 91 " + "60 7c 63 69 96 1b c8 27 0d 2d 4f 39 fc e6 12 b1 "; + +// RSASSA-PSS Signature Example 1.4 +// Message to be signed: +static const char message_1_4[] = + "bc 65 67 47 fa 9e af b3 f0 "; +// Salt: +static const char salt_1_4[] = + "05 6f 00 98 5d e1 4d 8e f5 ce a9 e8 2f 8c 27 be " + "f7 20 33 5e "; +// Signature: +static const char signature_1_4[] = + "46 09 79 3b 23 e9 d0 93 62 dc 21 bb 47 da 0b 4f " + "3a 76 22 64 9a 47 d4 64 01 9b 9a ea fe 53 35 9c " + "17 8c 91 cd 58 ba 6b cb 78 be 03 46 a7 bc 63 7f " + "4b 87 3d 4b ab 38 ee 66 1f 19 96 34 c5 47 a1 ad " + "84 42 e0 3d a0 15 b1 36 e5 43 f7 ab 07 c0 c1 3e " + "42 25 b8 de 8c ce 25 d4 f6 eb 84 00 f8 1f 7e 18 " + "33 b7 ee 6e 33 4d 37 09 64 ca 79 fd b8 72 b4 d7 " + "52 23 b5 ee b0 81 01 59 1f b5 32 d1 55 a6 de 87 "; + +// RSASSA-PSS Signature Example 1.5 +// Message to be signed: +static const char message_1_5[] = + "b4 55 81 54 7e 54 27 77 0c 76 8e 8b 82 b7 55 64 " + "e0 ea 4e 9c 32 59 4d 6b ff 70 65 44 de 0a 87 76 " + "c7 a8 0b 45 76 55 0e ee 1b 2a ca bc 7e 8b 7d 3e " + "f7 bb 5b 03 e4 62 c1 10 47 ea dd 00 62 9a e5 75 " + "48 0a c1 47 0f e0 46 f1 3a 2b f5 af 17 92 1d c4 " + "b0 aa 8b 02 be e6 33 49 11 65 1d 7f 85 25 d1 0f " + "32 b5 1d 33 be 52 0d 3d df 5a 70 99 55 a3 df e7 " + "82 83 b9 e0 ab 54 04 6d 15 0c 17 7f 03 7f dc cc " + "5b e4 ea 5f 68 b5 e5 a3 8c 9d 7e dc cc c4 97 5f " + "45 5a 69 09 b4 "; +// Salt: +static const char salt_1_5[] = + "80 e7 0f f8 6a 08 de 3e c6 09 72 b3 9b 4f bf dc " + "ea 67 ae 8e "; +// Signature: +static const char signature_1_5[] = + "1d 2a ad 22 1c a4 d3 1d df 13 50 92 39 01 93 98 " + "e3 d1 4b 32 dc 34 dc 5a f4 ae ae a3 c0 95 af 73 " + "47 9c f0 a4 5e 56 29 63 5a 53 a0 18 37 76 15 b1 " + "6c b9 b1 3b 3e 09 d6 71 eb 71 e3 87 b8 54 5c 59 " + "60 da 5a 64 77 6e 76 8e 82 b2 c9 35 83 bf 10 4c " + "3f db 23 51 2b 7b 4e 89 f6 33 dd 00 63 a5 30 db " + "45 24 b0 1c 3f 38 4c 09 31 0e 31 5a 79 dc d3 d6 " + "84 02 2a 7f 31 c8 65 a6 64 e3 16 97 8b 75 9f ad "; + +// RSASSA-PSS Signature Example 1.6 +// Message to be signed: +static const char message_1_6[] = + "10 aa e9 a0 ab 0b 59 5d 08 41 20 7b 70 0d 48 d7 " + "5f ae dd e3 b7 75 cd 6b 4c c8 8a e0 6e 46 94 ec " + "74 ba 18 f8 52 0d 4f 5e a6 9c bb e7 cc 2b eb a4 " + "3e fd c1 02 15 ac 4e b3 2d c3 02 a1 f5 3d c6 c4 " + "35 22 67 e7 93 6c fe bf 7c 8d 67 03 57 84 a3 90 " + "9f a8 59 c7 b7 b5 9b 8e 39 c5 c2 34 9f 18 86 b7 " + "05 a3 02 67 d4 02 f7 48 6a b4 f5 8c ad 5d 69 ad " + "b1 7a b8 cd 0c e1 ca f5 02 5a f4 ae 24 b1 fb 87 " + "94 c6 07 0c c0 9a 51 e2 f9 91 13 11 e3 87 7d 00 " + "44 c7 1c 57 a9 93 39 50 08 80 6b 72 3a c3 83 73 " + "d3 95 48 18 18 52 8c 1e 70 53 73 92 82 05 35 29 " + "51 0e 93 5c d0 fa 77 b8 fa 53 cc 2d 47 4b d4 fb " + "3c c5 c6 72 d6 ff dc 90 a0 0f 98 48 71 2c 4b cf " + "e4 6c 60 57 36 59 b1 1e 64 57 e8 61 f0 f6 04 b6 " + "13 8d 14 4f 8c e4 e2 da 73 "; +// Salt: +static const char salt_1_6[] = + "a8 ab 69 dd 80 1f 00 74 c2 a1 fc 60 64 98 36 c6 " + "16 d9 96 81 "; +// Signature: +static const char signature_1_6[] = + "2a 34 f6 12 5e 1f 6b 0b f9 71 e8 4f bd 41 c6 32 " + "be 8f 2c 2a ce 7d e8 b6 92 6e 31 ff 93 e9 af 98 " + "7f bc 06 e5 1e 9b e1 4f 51 98 f9 1f 3f 95 3b d6 " + "7d a6 0a 9d f5 97 64 c3 dc 0f e0 8e 1c be f0 b7 " + "5f 86 8d 10 ad 3f ba 74 9f ef 59 fb 6d ac 46 a0 " + "d6 e5 04 36 93 31 58 6f 58 e4 62 8f 39 aa 27 89 " + "82 54 3b c0 ee b5 37 dc 61 95 80 19 b3 94 fb 27 " + "3f 21 58 58 a0 a0 1a c4 d6 50 b9 55 c6 7f 4c 58 "; + +// Example 9: A 1536-bit RSA Key Pair + +// RSA modulus n: +static const char rsa_modulus_n_9[] = + "e6 bd 69 2a c9 66 45 79 04 03 fd d0 f5 be b8 b9 " + "bf 92 ed 10 00 7f c3 65 04 64 19 dd 06 c0 5c 5b " + "5b 2f 48 ec f9 89 e4 ce 26 91 09 97 9c bb 40 b4 " + "a0 ad 24 d2 24 83 d1 ee 31 5a d4 cc b1 53 42 68 " + "35 26 91 c5 24 f6 dd 8e 6c 29 d2 24 cf 24 69 73 " + "ae c8 6c 5b f6 b1 40 1a 85 0d 1b 9a d1 bb 8c bc " + "ec 47 b0 6f 0f 8c 7f 45 d3 fc 8f 31 92 99 c5 43 " + "3d db c2 b3 05 3b 47 de d2 ec d4 a4 ca ef d6 14 " + "83 3d c8 bb 62 2f 31 7e d0 76 b8 05 7f e8 de 3f " + "84 48 0a d5 e8 3e 4a 61 90 4a 4f 24 8f b3 97 02 " + "73 57 e1 d3 0e 46 31 39 81 5c 6f d4 fd 5a c5 b8 " + "17 2a 45 23 0e cb 63 18 a0 4f 14 55 d8 4e 5a 8b "; +// RSA public exponent e: +static const char rsa_public_exponent_e_9[] = + "01 00 01 "; + +// RSASSA-PSS Signature Example 9.1 +// Message to be signed: +static const char message_9_1[] = + "a8 8e 26 58 55 e9 d7 ca 36 c6 87 95 f0 b3 1b 59 " + "1c d6 58 7c 71 d0 60 a0 b3 f7 f3 ea ef 43 79 59 " + "22 02 8b c2 b6 ad 46 7c fc 2d 7f 65 9c 53 85 aa " + "70 ba 36 72 cd de 4c fe 49 70 cc 79 04 60 1b 27 " + "88 72 bf 51 32 1c 4a 97 2f 3c 95 57 0f 34 45 d4 " + "f5 79 80 e0 f2 0d f5 48 46 e6 a5 2c 66 8f 12 88 " + "c0 3f 95 00 6e a3 2f 56 2d 40 d5 2a f9 fe b3 2f " + "0f a0 6d b6 5b 58 8a 23 7b 34 e5 92 d5 5c f9 79 " + "f9 03 a6 42 ef 64 d2 ed 54 2a a8 c7 7d c1 dd 76 " + "2f 45 a5 93 03 ed 75 e5 41 ca 27 1e 2b 60 ca 70 " + "9e 44 fa 06 61 13 1e 8d 5d 41 63 fd 8d 39 85 66 " + "ce 26 de 87 30 e7 2f 9c ca 73 76 41 c2 44 15 94 " + "20 63 70 28 df 0a 18 07 9d 62 08 ea 8b 47 11 a2 " + "c7 50 f5 "; +// Salt: +static const char salt_9_1[] = + "c0 a4 25 31 3d f8 d7 56 4b d2 43 4d 31 15 23 d5 " + "25 7e ed 80 "; +// Signature: +static const char signature_9_1[] = + "58 61 07 22 6c 3c e0 13 a7 c8 f0 4d 1a 6a 29 59 " + "bb 4b 8e 20 5b a4 3a 27 b5 0f 12 41 11 bc 35 ef " + "58 9b 03 9f 59 32 18 7c b6 96 d7 d9 a3 2c 0c 38 " + "30 0a 5c dd a4 83 4b 62 d2 eb 24 0a f3 3f 79 d1 " + "3d fb f0 95 bf 59 9e 0d 96 86 94 8c 19 64 74 7b " + "67 e8 9c 9a ba 5c d8 50 16 23 6f 56 6c c5 80 2c " + "b1 3e ad 51 bc 7c a6 be f3 b9 4d cb db b1 d5 70 " + "46 97 71 df 0e 00 b1 a8 a0 67 77 47 2d 23 16 27 " + "9e da e8 64 74 66 8d 4e 1e ff f9 5f 1d e6 1c 60 " + "20 da 32 ae 92 bb f1 65 20 fe f3 cf 4d 88 f6 11 " + "21 f2 4b bd 9f e9 1b 59 ca f1 23 5b 2a 93 ff 81 " + "fc 40 3a dd f4 eb de a8 49 34 a9 cd af 8e 1a 9e "; + +// RSASSA-PSS Signature Example 9.2 +// Message to be signed: +static const char message_9_2[] = + "c8 c9 c6 af 04 ac da 41 4d 22 7e f2 3e 08 20 c3 " + "73 2c 50 0d c8 72 75 e9 5b 0d 09 54 13 99 3c 26 " + "58 bc 1d 98 85 81 ba 87 9c 2d 20 1f 14 cb 88 ce " + "d1 53 a0 19 69 a7 bf 0a 7b e7 9c 84 c1 48 6b c1 " + "2b 3f a6 c5 98 71 b6 82 7c 8c e2 53 ca 5f ef a8 " + "a8 c6 90 bf 32 6e 8e 37 cd b9 6d 90 a8 2e ba b6 " + "9f 86 35 0e 18 22 e8 bd 53 6a 2e "; +// Salt: +static const char salt_9_2[] = + "b3 07 c4 3b 48 50 a8 da c2 f1 5f 32 e3 78 39 ef " + "8c 5c 0e 91 "; +// Signature: +static const char signature_9_2[] = + "80 b6 d6 43 25 52 09 f0 a4 56 76 38 97 ac 9e d2 " + "59 d4 59 b4 9c 28 87 e5 88 2e cb 44 34 cf d6 6d " + "d7 e1 69 93 75 38 1e 51 cd 7f 55 4f 2c 27 17 04 " + "b3 99 d4 2b 4b e2 54 0a 0e ca 61 95 1f 55 26 7f " + "7c 28 78 c1 22 84 2d ad b2 8b 01 bd 5f 8c 02 5f " + "7e 22 84 18 a6 73 c0 3d 6b c0 c7 36 d0 a2 95 46 " + "bd 67 f7 86 d9 d6 92 cc ea 77 8d 71 d9 8c 20 63 " + "b7 a7 10 92 18 7a 4d 35 af 10 81 11 d8 3e 83 ea " + "e4 6c 46 aa 34 27 7e 06 04 45 89 90 37 88 f1 d5 " + "e7 ce e2 5f b4 85 e9 29 49 11 88 14 d6 f2 c3 ee " + "36 14 89 01 6f 32 7f b5 bc 51 7e b5 04 70 bf fa " + "1a fa 5f 4c e9 aa 0c e5 b8 ee 19 bf 55 01 b9 58 "; + +// RSASSA-PSS Signature Example 9.3 +// Message to be signed: +static const char message_9_3[] = + "0a fa d4 2c cd 4f c6 06 54 a5 50 02 d2 28 f5 2a " + "4a 5f e0 3b 8b bb 08 ca 82 da ca 55 8b 44 db e1 " + "26 6e 50 c0 e7 45 a3 6d 9d 29 04 e3 40 8a bc d1 " + "fd 56 99 94 06 3f 4a 75 cc 72 f2 fe e2 a0 cd 89 " + "3a 43 af 1c 5b 8b 48 7d f0 a7 16 10 02 4e 4f 6d " + "df 9f 28 ad 08 13 c1 aa b9 1b cb 3c 90 64 d5 ff " + "74 2d ef fe a6 57 09 41 39 36 9e 5e a6 f4 a9 63 " + "19 a5 cc 82 24 14 5b 54 50 62 75 8f ef d1 fe 34 " + "09 ae 16 92 59 c6 cd fd 6b 5f 29 58 e3 14 fa ec " + "be 69 d2 ca ce 58 ee 55 17 9a b9 b3 e6 d1 ec c1 " + "4a 55 7c 5f eb e9 88 59 52 64 fc 5d a1 c5 71 46 " + "2e ca 79 8a 18 a1 a4 94 0c da b4 a3 e9 20 09 cc " + "d4 2e 1e 94 7b 13 14 e3 22 38 a2 de ce 7d 23 a8 " + "9b 5b 30 c7 51 fd 0a 4a 43 0d 2c 54 85 94 "; +// Salt: +static const char salt_9_3[] = + "9a 2b 00 7e 80 97 8b bb 19 2c 35 4e b7 da 9a ed " + "fc 74 db f5 "; +// Signature: +static const char signature_9_3[] = + "48 44 08 f3 89 8c d5 f5 34 83 f8 08 19 ef bf 27 " + "08 c3 4d 27 a8 b2 a6 fa e8 b3 22 f9 24 02 37 f9 " + "81 81 7a ca 18 46 f1 08 4d aa 6d 7c 07 95 f6 e5 " + "bf 1a f5 9c 38 e1 85 84 37 ce 1f 7e c4 19 b9 8c " + "87 36 ad f6 dd 9a 00 b1 80 6d 2b d3 ad 0a 73 77 " + "5e 05 f5 2d fe f3 a5 9a b4 b0 81 43 f0 df 05 cd " + "1a d9 d0 4b ec ec a6 da a4 a2 12 98 03 e2 00 cb " + "c7 77 87 ca f4 c1 d0 66 3a 6c 59 87 b6 05 95 20 " + "19 78 2c af 2e c1 42 6d 68 fb 94 ed 1d 4b e8 16 " + "a7 ed 08 1b 77 e6 ab 33 0b 3f fc 07 38 20 fe cd " + "e3 72 7f cb e2 95 ee 61 a0 50 a3 43 65 86 37 c3 " + "fd 65 9c fb 63 73 6d e3 2d 9f 90 d3 c2 f6 3e ca "; + +// RSASSA-PSS Signature Example 9.4 +// Message to be signed: +static const char message_9_4[] = + "1d fd 43 b4 6c 93 db 82 62 9b da e2 bd 0a 12 b8 " + "82 ea 04 c3 b4 65 f5 cf 93 02 3f 01 05 96 26 db " + "be 99 f2 6b b1 be 94 9d dd d1 6d c7 f3 de bb 19 " + "a1 94 62 7f 0b 22 44 34 df 7d 87 00 e9 e9 8b 06 " + "e3 60 c1 2f db e3 d1 9f 51 c9 68 4e b9 08 9e cb " + "b0 a2 f0 45 03 99 d3 f5 9e ac 72 94 08 5d 04 4f " + "53 93 c6 ce 73 74 23 d8 b8 6c 41 53 70 d3 89 e3 " + "0b 9f 0a 3c 02 d2 5d 00 82 e8 ad 6f 3f 1e f2 4a " + "45 c3 cf 82 b3 83 36 70 63 a4 d4 61 3e 42 64 f0 " + "1b 2d ac 2e 5a a4 20 43 f8 fb 5f 69 fa 87 1d 14 " + "fb 27 3e 76 7a 53 1c 40 f0 2f 34 3b c2 fb 45 a0 " + "c7 e0 f6 be 25 61 92 3a 77 21 1d 66 a6 e2 db b4 " + "3c 36 63 50 be ae 22 da 3a c2 c1 f5 07 70 96 fc " + "b5 c4 bf 25 5f 75 74 35 1a e0 b1 e1 f0 36 32 81 " + "7c 08 56 d4 a8 ba 97 af bd c8 b8 58 55 40 2b c5 " + "69 26 fc ec 20 9f 9e a8 "; +// Salt: +static const char salt_9_4[] = + "70 f3 82 bd df 4d 5d 2d d8 8b 3b c7 b7 30 8b e6 " + "32 b8 40 45 "; +// Signature: +static const char signature_9_4[] = + "84 eb eb 48 1b e5 98 45 b4 64 68 ba fb 47 1c 01 " + "12 e0 2b 23 5d 84 b5 d9 11 cb d1 92 6e e5 07 4a " + "e0 42 44 95 cb 20 e8 23 08 b8 eb b6 5f 41 9a 03 " + "fb 40 e7 2b 78 98 1d 88 aa d1 43 05 36 85 17 2c " + "97 b2 9c 8b 7b f0 ae 73 b5 b2 26 3c 40 3d a0 ed " + "2f 80 ff 74 50 af 78 28 eb 8b 86 f0 02 8b d2 a8 " + "b1 76 a4 d2 28 cc ce a1 83 94 f2 38 b0 9f f7 58 " + "cc 00 bc 04 30 11 52 35 57 42 f2 82 b5 4e 66 3a " + "91 9e 70 9d 8d a2 4a de 55 00 a7 b9 aa 50 22 6e " + "0c a5 29 23 e6 c2 d8 60 ec 50 ff 48 0f a5 74 77 " + "e8 2b 05 65 f4 37 9f 79 c7 72 d5 c2 da 80 af 9f " + "bf 32 5e ce 6f c2 0b 00 96 16 14 be e8 9a 18 3e "; + +// RSASSA-PSS Signature Example 9.5 +// Message to be signed: +static const char message_9_5[] = + "1b dc 6e 7c 98 fb 8c f5 4e 9b 09 7b 66 a8 31 e9 " + "cf e5 2d 9d 48 88 44 8e e4 b0 97 80 93 ba 1d 7d " + "73 ae 78 b3 a6 2b a4 ad 95 cd 28 9c cb 9e 00 52 " + "26 bb 3d 17 8b cc aa 82 1f b0 44 a4 e2 1e e9 76 " + "96 c1 4d 06 78 c9 4c 2d ae 93 b0 ad 73 92 22 18 " + "55 3d aa 7e 44 eb e5 77 25 a7 a4 5c c7 2b 9b 21 " + "38 a6 b1 7c 8d b4 11 ce 82 79 ee 12 41 af f0 a8 " + "be c6 f7 7f 87 ed b0 c6 9c b2 72 36 e3 43 5a 80 " + "0b 19 2e 4f 11 e5 19 e3 fe 30 fc 30 ea cc ca 4f " + "bb 41 76 90 29 bf 70 8e 81 7a 9e 68 38 05 be 67 " + "fa 10 09 84 68 3b 74 83 8e 3b cf fa 79 36 6e ed " + "1d 48 1c 76 72 91 18 83 8f 31 ba 8a 04 8a 93 c1 " + "be 44 24 59 8e 8d f6 32 8b 7a 77 88 0a 3f 9c 7e " + "2e 8d fc a8 eb 5a 26 fb 86 bd c5 56 d4 2b be 01 " + "d9 fa 6e d8 06 46 49 1c 93 41 "; +// Salt: +static const char salt_9_5[] = + "d6 89 25 7a 86 ef fa 68 21 2c 5e 0c 61 9e ca 29 " + "5f b9 1b 67 "; +// Signature: +static const char signature_9_5[] = + "82 10 2d f8 cb 91 e7 17 99 19 a0 4d 26 d3 35 d6 " + "4f bc 2f 87 2c 44 83 39 43 24 1d e8 45 48 10 27 " + "4c df 3d b5 f4 2d 42 3d b1 52 af 71 35 f7 01 42 " + "0e 39 b4 94 a6 7c bf d1 9f 91 19 da 23 3a 23 da " + "5c 64 39 b5 ba 0d 2b c3 73 ee e3 50 70 01 37 8d " + "4a 40 73 85 6b 7f e2 ab a0 b5 ee 93 b2 7f 4a fe " + "c7 d4 d1 20 92 1c 83 f6 06 76 5b 02 c1 9e 4d 6a " + "1a 3b 95 fa 4c 42 29 51 be 4f 52 13 10 77 ef 17 " + "17 97 29 cd df bd b5 69 50 db ac ee fe 78 cb 16 " + "64 0a 09 9e a5 6d 24 38 9e ef 10 f8 fe cb 31 ba " + "3e a3 b2 27 c0 a8 66 98 bb 89 e3 e9 36 39 05 bf " + "22 77 7b 2a 3a a5 21 b6 5b 4c ef 76 d8 3b de 4c "; + +// RSASSA-PSS Signature Example 9.6 +// Message to be signed: +static const char message_9_6[] = + "88 c7 a9 f1 36 04 01 d9 0e 53 b1 01 b6 1c 53 25 " + "c3 c7 5d b1 b4 11 fb eb 8e 83 0b 75 e9 6b 56 67 " + "0a d2 45 40 4e 16 79 35 44 ee 35 4b c6 13 a9 0c " + "c9 84 87 15 a7 3d b5 89 3e 7f 6d 27 98 15 c0 c1 " + "de 83 ef 8e 29 56 e3 a5 6e d2 6a 88 8d 7a 9c dc " + "d0 42 f4 b1 6b 7f a5 1e f1 a0 57 36 62 d1 6a 30 " + "2d 0e c5 b2 85 d2 e0 3a d9 65 29 c8 7b 3d 37 4d " + "b3 72 d9 5b 24 43 d0 61 b6 b1 a3 50 ba 87 80 7e " + "d0 83 af d1 eb 05 c3 f5 2f 4e ba 5e d2 22 77 14 " + "fd b5 0b 9d 9d 9d d6 81 4f 62 f6 27 2f cd 5c db " + "ce 7a 9e f7 97 "; +// Salt: +static const char salt_9_6[] = + "c2 5f 13 bf 67 d0 81 67 1a 04 81 a1 f1 82 0d 61 " + "3b ba 22 76 "; +// Signature: +static const char signature_9_6[] = + "a7 fd b0 d2 59 16 5c a2 c8 8d 00 bb f1 02 8a 86 " + "7d 33 76 99 d0 61 19 3b 17 a9 64 8e 14 cc bb aa " + "de ac aa cd ec 81 5e 75 71 29 4e bb 8a 11 7a f2 " + "05 fa 07 8b 47 b0 71 2c 19 9e 3a d0 51 35 c5 04 " + "c2 4b 81 70 51 15 74 08 02 48 79 92 ff d5 11 d4 " + "af c6 b8 54 49 1e b3 f0 dd 52 31 39 54 2f f1 5c " + "31 01 ee 85 54 35 17 c6 a3 c7 94 17 c6 7e 2d d9 " + "aa 74 1e 9a 29 b0 6d cb 59 3c 23 36 b3 67 0a e3 " + "af ba c7 c3 e7 6e 21 54 73 e8 66 e3 38 ca 24 4d " + "e0 0b 62 62 4d 6b 94 26 82 2c ea e9 f8 cc 46 08 " + "95 f4 12 50 07 3f d4 5c 5a 1e 7b 42 5c 20 4a 42 " + "3a 69 91 59 f6 90 3e 71 0b 37 a7 bb 2b c8 04 9f "; + +// Example 10: A 2048-bit RSA Key Pair + +// RSA modulus n: +static const char rsa_modulus_n_10[] = + "a5 dd 86 7a c4 cb 02 f9 0b 94 57 d4 8c 14 a7 70 " + "ef 99 1c 56 c3 9c 0e c6 5f d1 1a fa 89 37 ce a5 " + "7b 9b e7 ac 73 b4 5c 00 17 61 5b 82 d6 22 e3 18 " + "75 3b 60 27 c0 fd 15 7b e1 2f 80 90 fe e2 a7 ad " + "cd 0e ef 75 9f 88 ba 49 97 c7 a4 2d 58 c9 aa 12 " + "cb 99 ae 00 1f e5 21 c1 3b b5 43 14 45 a8 d5 ae " + "4f 5e 4c 7e 94 8a c2 27 d3 60 40 71 f2 0e 57 7e " + "90 5f be b1 5d fa f0 6d 1d e5 ae 62 53 d6 3a 6a " + "21 20 b3 1a 5d a5 da bc 95 50 60 0e 20 f2 7d 37 " + "39 e2 62 79 25 fe a3 cc 50 9f 21 df f0 4e 6e ea " + "45 49 c5 40 d6 80 9f f9 30 7e ed e9 1f ff 58 73 " + "3d 83 85 a2 37 d6 d3 70 5a 33 e3 91 90 09 92 07 " + "0d f7 ad f1 35 7c f7 e3 70 0c e3 66 7d e8 3f 17 " + "b8 df 17 78 db 38 1d ce 09 cb 4a d0 58 a5 11 00 " + "1a 73 81 98 ee 27 cf 55 a1 3b 75 45 39 90 65 82 " + "ec 8b 17 4b d5 8d 5d 1f 3d 76 7c 61 37 21 ae 05 "; +// RSA public exponent e: +static const char rsa_public_exponent_e_10[] = + "01 00 01 "; + +// RSASSA-PSS Signature Example 10.1 +// Message to be signed: +static const char message_10_1[] = + "88 31 77 e5 12 6b 9b e2 d9 a9 68 03 27 d5 37 0c " + "6f 26 86 1f 58 20 c4 3d a6 7a 3a d6 09 "; +// Salt: +static const char salt_10_1[] = + "04 e2 15 ee 6f f9 34 b9 da 70 d7 73 0c 87 34 ab " + "fc ec de 89 "; +// Signature: +static const char signature_10_1[] = + "82 c2 b1 60 09 3b 8a a3 c0 f7 52 2b 19 f8 73 54 " + "06 6c 77 84 7a bf 2a 9f ce 54 2d 0e 84 e9 20 c5 " + "af b4 9f fd fd ac e1 65 60 ee 94 a1 36 96 01 14 " + "8e ba d7 a0 e1 51 cf 16 33 17 91 a5 72 7d 05 f2 " + "1e 74 e7 eb 81 14 40 20 69 35 d7 44 76 5a 15 e7 " + "9f 01 5c b6 6c 53 2c 87 a6 a0 59 61 c8 bf ad 74 " + "1a 9a 66 57 02 28 94 39 3e 72 23 73 97 96 c0 2a " + "77 45 5d 0f 55 5b 0e c0 1d df 25 9b 62 07 fd 0f " + "d5 76 14 ce f1 a5 57 3b aa ff 4e c0 00 69 95 16 " + "59 b8 5f 24 30 0a 25 16 0c a8 52 2d c6 e6 72 7e " + "57 d0 19 d7 e6 36 29 b8 fe 5e 89 e2 5c c1 5b eb " + "3a 64 75 77 55 92 99 28 0b 9b 28 f7 9b 04 09 00 " + "0b e2 5b bd 96 40 8b a3 b4 3c c4 86 18 4d d1 c8 " + "e6 25 53 fa 1a f4 04 0f 60 66 3d e7 f5 e4 9c 04 " + "38 8e 25 7f 1c e8 9c 95 da b4 8a 31 5d 9b 66 b1 " + "b7 62 82 33 87 6f f2 38 52 30 d0 70 d0 7e 16 66 "; + +// RSASSA-PSS Signature Example 10.2 +// Message to be signed: +static const char message_10_2[] = + "dd 67 0a 01 46 58 68 ad c9 3f 26 13 19 57 a5 0c " + "52 fb 77 7c db aa 30 89 2c 9e 12 36 11 64 ec 13 " + "97 9d 43 04 81 18 e4 44 5d b8 7b ee 58 dd 98 7b " + "34 25 d0 20 71 d8 db ae 80 70 8b 03 9d bb 64 db " + "d1 de 56 57 d9 fe d0 c1 18 a5 41 43 74 2e 0f f3 " + "c8 7f 74 e4 58 57 64 7a f3 f7 9e b0 a1 4c 9d 75 " + "ea 9a 1a 04 b7 cf 47 8a 89 7a 70 8f d9 88 f4 8e " + "80 1e db 0b 70 39 df 8c 23 bb 3c 56 f4 e8 21 ac "; +// Salt: +static const char salt_10_2[] = + "8b 2b dd 4b 40 fa f5 45 c7 78 dd f9 bc 1a 49 cb " + "57 f9 b7 1b "; +// Signature: +static const char signature_10_2[] = + "14 ae 35 d9 dd 06 ba 92 f7 f3 b8 97 97 8a ed 7c " + "d4 bf 5f f0 b5 85 a4 0b d4 6c e1 b4 2c d2 70 30 " + "53 bb 90 44 d6 4e 81 3d 8f 96 db 2d d7 00 7d 10 " + "11 8f 6f 8f 84 96 09 7a d7 5e 1f f6 92 34 1b 28 " + "92 ad 55 a6 33 a1 c5 5e 7f 0a 0a d5 9a 0e 20 3a " + "5b 82 78 ae c5 4d d8 62 2e 28 31 d8 71 74 f8 ca " + "ff 43 ee 6c 46 44 53 45 d8 4a 59 65 9b fb 92 ec " + "d4 c8 18 66 86 95 f3 47 06 f6 68 28 a8 99 59 63 " + "7f 2b f3 e3 25 1c 24 bd ba 4d 4b 76 49 da 00 22 " + "21 8b 11 9c 84 e7 9a 65 27 ec 5b 8a 5f 86 1c 15 " + "99 52 e2 3e c0 5e 1e 71 73 46 fa ef e8 b1 68 68 " + "25 bd 2b 26 2f b2 53 10 66 c0 de 09 ac de 2e 42 " + "31 69 07 28 b5 d8 5e 11 5a 2f 6b 92 b7 9c 25 ab " + "c9 bd 93 99 ff 8b cf 82 5a 52 ea 1f 56 ea 76 dd " + "26 f4 3b aa fa 18 bf a9 2a 50 4c bd 35 69 9e 26 " + "d1 dc c5 a2 88 73 85 f3 c6 32 32 f0 6f 32 44 c3 "; + +// RSASSA-PSS Signature Example 10.3 +// Message to be signed: +static const char message_10_3[] = + "48 b2 b6 a5 7a 63 c8 4c ea 85 9d 65 c6 68 28 4b " + "08 d9 6b dc aa be 25 2d b0 e4 a9 6c b1 ba c6 01 " + "93 41 db 6f be fb 8d 10 6b 0e 90 ed a6 bc c6 c6 " + "26 2f 37 e7 ea 9c 7e 5d 22 6b d7 df 85 ec 5e 71 " + "ef ff 2f 54 c5 db 57 7f f7 29 ff 91 b8 42 49 1d " + "e2 74 1d 0c 63 16 07 df 58 6b 90 5b 23 b9 1a f1 " + "3d a1 23 04 bf 83 ec a8 a7 3e 87 1f f9 db "; +// Salt: +static const char salt_10_3[] = + "4e 96 fc 1b 39 8f 92 b4 46 71 01 0c 0d c3 ef d6 " + "e2 0c 2d 73 "; +// Signature: +static const char signature_10_3[] = + "6e 3e 4d 7b 6b 15 d2 fb 46 01 3b 89 00 aa 5b bb " + "39 39 cf 2c 09 57 17 98 70 42 02 6e e6 2c 74 c5 " + "4c ff d5 d7 d5 7e fb bf 95 0a 0f 5c 57 4f a0 9d " + "3f c1 c9 f5 13 b0 5b 4f f5 0d d8 df 7e df a2 01 " + "02 85 4c 35 e5 92 18 01 19 a7 0c e5 b0 85 18 2a " + "a0 2d 9e a2 aa 90 d1 df 03 f2 da ae 88 5b a2 f5 " + "d0 5a fd ac 97 47 6f 06 b9 3b 5b c9 4a 1a 80 aa " + "91 16 c4 d6 15 f3 33 b0 98 89 2b 25 ff ac e2 66 " + "f5 db 5a 5a 3b cc 10 a8 24 ed 55 aa d3 5b 72 78 " + "34 fb 8c 07 da 28 fc f4 16 a5 d9 b2 22 4f 1f 8b " + "44 2b 36 f9 1e 45 6f de a2 d7 cf e3 36 72 68 de " + "03 07 a4 c7 4e 92 41 59 ed 33 39 3d 5e 06 55 53 " + "1c 77 32 7b 89 82 1b de df 88 01 61 c7 8c d4 19 " + "6b 54 19 f7 ac c3 f1 3e 5e bf 16 1b 6e 7c 67 24 " + "71 6c a3 3b 85 c2 e2 56 40 19 2a c2 85 96 51 d5 " + "0b de 7e b9 76 e5 1c ec 82 8b 98 b6 56 3b 86 bb "; + +// RSASSA-PSS Signature Example 10.4 +// Message to be signed: +static const char message_10_4[] = + "0b 87 77 c7 f8 39 ba f0 a6 4b bb db c5 ce 79 75 " + "5c 57 a2 05 b8 45 c1 74 e2 d2 e9 05 46 a0 89 c4 " + "e6 ec 8a df fa 23 a7 ea 97 ba e6 b6 5d 78 2b 82 " + "db 5d 2b 5a 56 d2 2a 29 a0 5e 7c 44 33 e2 b8 2a " + "62 1a bb a9 0a dd 05 ce 39 3f c4 8a 84 05 42 45 " + "1a "; +// Salt: +static const char salt_10_4[] = + "c7 cd 69 8d 84 b6 51 28 d8 83 5e 3a 8b 1e b0 e0 " + "1c b5 41 ec "; +// Signature: +static const char signature_10_4[] = + "34 04 7f f9 6c 4d c0 dc 90 b2 d4 ff 59 a1 a3 61 " + "a4 75 4b 25 5d 2e e0 af 7d 8b f8 7c 9b c9 e7 dd " + "ee de 33 93 4c 63 ca 1c 0e 3d 26 2c b1 45 ef 93 " + "2a 1f 2c 0a 99 7a a6 a3 4f 8e ae e7 47 7d 82 cc " + "f0 90 95 a6 b8 ac ad 38 d4 ee c9 fb 7e ab 7a d0 " + "2d a1 d1 1d 8e 54 c1 82 5e 55 bf 58 c2 a2 32 34 " + "b9 02 be 12 4f 9e 90 38 a8 f6 8f a4 5d ab 72 f6 " + "6e 09 45 bf 1d 8b ac c9 04 4c 6f 07 09 8c 9f ce " + "c5 8a 3a ab 10 0c 80 51 78 15 5f 03 0a 12 4c 45 " + "0e 5a cb da 47 d0 e4 f1 0b 80 a2 3f 80 3e 77 4d " + "02 3b 00 15 c2 0b 9f 9b be 7c 91 29 63 38 d5 ec " + "b4 71 ca fb 03 20 07 b6 7a 60 be 5f 69 50 4a 9f " + "01 ab b3 cb 46 7b 26 0e 2b ce 86 0b e8 d9 5b f9 " + "2c 0c 8e 14 96 ed 1e 52 85 93 a4 ab b6 df 46 2d " + "de 8a 09 68 df fe 46 83 11 68 57 a2 32 f5 eb f6 " + "c8 5b e2 38 74 5a d0 f3 8f 76 7a 5f db f4 86 fb "; + +// RSASSA-PSS Signature Example 10.5 +// Message to be signed: +static const char message_10_5[] = + "f1 03 6e 00 8e 71 e9 64 da dc 92 19 ed 30 e1 7f " + "06 b4 b6 8a 95 5c 16 b3 12 b1 ed df 02 8b 74 97 " + "6b ed 6b 3f 6a 63 d4 e7 78 59 24 3c 9c cc dc 98 " + "01 65 23 ab b0 24 83 b3 55 91 c3 3a ad 81 21 3b " + "b7 c7 bb 1a 47 0a ab c1 0d 44 25 6c 4d 45 59 d9 " + "16 "; +// Salt: +static const char salt_10_5[] = + "ef a8 bf f9 62 12 b2 f4 a3 f3 71 a1 0d 57 41 52 " + "65 5f 5d fb "; +// Signature: +static const char signature_10_5[] = + "7e 09 35 ea 18 f4 d6 c1 d1 7c e8 2e b2 b3 83 6c " + "55 b3 84 58 9c e1 9d fe 74 33 63 ac 99 48 d1 f3 " + "46 b7 bf dd fe 92 ef d7 8a db 21 fa ef c8 9a de " + "42 b1 0f 37 40 03 fe 12 2e 67 42 9a 1c b8 cb d1 " + "f8 d9 01 45 64 c4 4d 12 01 16 f4 99 0f 1a 6e 38 " + "77 4c 19 4b d1 b8 21 32 86 b0 77 b0 49 9d 2e 7b " + "3f 43 4a b1 22 89 c5 56 68 4d ee d7 81 31 93 4b " + "b3 dd 65 37 23 6f 7c 6f 3d cb 09 d4 76 be 07 72 " + "1e 37 e1 ce ed 9b 2f 7b 40 68 87 bd 53 15 73 05 " + "e1 c8 b4 f8 4d 73 3b c1 e1 86 fe 06 cc 59 b6 ed " + "b8 f4 bd 7f fe fd f4 f7 ba 9c fb 9d 57 06 89 b5 " + "a1 a4 10 9a 74 6a 69 08 93 db 37 99 25 5a 0c b9 " + "21 5d 2d 1c d4 90 59 0e 95 2e 8c 87 86 aa 00 11 " + "26 52 52 47 0c 04 1d fb c3 ee c7 c3 cb f7 1c 24 " + "86 9d 11 5c 0c b4 a9 56 f5 6d 53 0b 80 ab 58 9a " + "cf ef c6 90 75 1d df 36 e8 d3 83 f8 3c ed d2 cc "; + +// RSASSA-PSS Signature Example 10.6 +// Message to be signed: +static const char message_10_6[] = + "25 f1 08 95 a8 77 16 c1 37 45 0b b9 51 9d fa a1 " + "f2 07 fa a9 42 ea 88 ab f7 1e 9c 17 98 00 85 b5 " + "55 ae ba b7 62 64 ae 2a 3a b9 3c 2d 12 98 11 91 " + "dd ac 6f b5 94 9e b3 6a ee 3c 5d a9 40 f0 07 52 " + "c9 16 d9 46 08 fa 7d 97 ba 6a 29 15 b6 88 f2 03 " + "23 d4 e9 d9 68 01 d8 9a 72 ab 58 92 dc 21 17 c0 " + "74 34 fc f9 72 e0 58 cf 8c 41 ca 4b 4f f5 54 f7 " + "d5 06 8a d3 15 5f ce d0 f3 12 5b c0 4f 91 93 37 " + "8a 8f 5c 4c 3b 8c b4 dd 6d 1c c6 9d 30 ec ca 6e " + "aa 51 e3 6a 05 73 0e 9e 34 2e 85 5b af 09 9d ef " + "b8 af d7 "; +// Salt: +static const char salt_10_6[] = + "ad 8b 15 23 70 36 46 22 4b 66 0b 55 08 85 91 7c " + "a2 d1 df 28 "; +// Signature: +static const char signature_10_6[] = + "6d 3b 5b 87 f6 7e a6 57 af 21 f7 54 41 97 7d 21 " + "80 f9 1b 2c 5f 69 2d e8 29 55 69 6a 68 67 30 d9 " + "b9 77 8d 97 07 58 cc b2 60 71 c2 20 9f fb d6 12 " + "5b e2 e9 6e a8 1b 67 cb 9b 93 08 23 9f da 17 f7 " + "b2 b6 4e cd a0 96 b6 b9 35 64 0a 5a 1c b4 2a 91 " + "55 b1 c9 ef 7a 63 3a 02 c5 9f 0d 6e e5 9b 85 2c " + "43 b3 50 29 e7 3c 94 0f f0 41 0e 8f 11 4e ed 46 " + "bb d0 fa e1 65 e4 2b e2 52 8a 40 1c 3b 28 fd 81 " + "8e f3 23 2d ca 9f 4d 2a 0f 51 66 ec 59 c4 23 96 " + "d6 c1 1d bc 12 15 a5 6f a1 71 69 db 95 75 34 3e " + "f3 4f 9d e3 2a 49 cd c3 17 49 22 f2 29 c2 3e 18 " + "e4 5d f9 35 31 19 ec 43 19 ce dc e7 a1 7c 64 08 " + "8c 1f 6f 52 be 29 63 41 00 b3 91 9d 38 f3 d1 ed " + "94 e6 89 1e 66 a7 3b 8f b8 49 f5 87 4d f5 94 59 " + "e2 98 c7 bb ce 2e ee 78 2a 19 5a a6 6f e2 d0 73 " + "2b 25 e5 95 f5 7d 3e 06 1b 1f c3 e4 06 3b f9 8f "; + +struct SignatureExample { + const char* message; + const char* salt; + const char* signature; +}; + +struct PSSTestVector { + const char* modulus_n; + const char* public_exponent_e; + SignatureExample example[6]; +}; + +static const PSSTestVector pss_test[] = { + { + rsa_modulus_n_1, + rsa_public_exponent_e_1, + { + { message_1_1, salt_1_1, signature_1_1 }, + { message_1_2, salt_1_2, signature_1_2 }, + { message_1_3, salt_1_3, signature_1_3 }, + { message_1_4, salt_1_4, signature_1_4 }, + { message_1_5, salt_1_5, signature_1_5 }, + { message_1_6, salt_1_6, signature_1_6 }, + } + }, + { + rsa_modulus_n_9, + rsa_public_exponent_e_9, + { + { message_9_1, salt_9_1, signature_9_1 }, + { message_9_2, salt_9_2, signature_9_2 }, + { message_9_3, salt_9_3, signature_9_3 }, + { message_9_4, salt_9_4, signature_9_4 }, + { message_9_5, salt_9_5, signature_9_5 }, + { message_9_6, salt_9_6, signature_9_6 }, + } + }, + { + rsa_modulus_n_10, + rsa_public_exponent_e_10, + { + { message_10_1, salt_10_1, signature_10_1 }, + { message_10_2, salt_10_2, signature_10_2 }, + { message_10_3, salt_10_3, signature_10_3 }, + { message_10_4, salt_10_4, signature_10_4 }, + { message_10_5, salt_10_5, signature_10_5 }, + { message_10_6, salt_10_6, signature_10_6 }, + } + }, +}; + +static uint8 HexDigitValue(char digit) { + if ('0' <= digit && digit <= '9') + return digit - '0'; + if ('a' <= digit && digit <= 'f') + return digit - 'a' + 10; + return digit - 'A' + 10; +} + +static bool DecodeTestInput(const char* in, std::vector<uint8>* out) { + out->clear(); + while (in[0] != '\0') { + if (!isxdigit(in[0]) || !isxdigit(in[1]) || in[2] != ' ') + return false; + uint8 octet = HexDigitValue(in[0]) * 16 + HexDigitValue(in[1]); + out->push_back(octet); + in += 3; + } + return true; +} + +// PrependASN1Length prepends an ASN.1 serialized length to the beginning of +// |out|. +static void PrependASN1Length(std::vector<uint8>* out, size_t len) { + if (len < 128) { + out->insert(out->begin(), static_cast<uint8>(len)); + } else if (len < 256) { + out->insert(out->begin(), static_cast<uint8>(len)); + out->insert(out->begin(), 0x81); + } else if (len < 0x10000) { + out->insert(out->begin(), static_cast<uint8>(len)); + out->insert(out->begin(), static_cast<uint8>(len >> 8)); + out->insert(out->begin(), 0x82); + } else { + CHECK(false) << "ASN.1 length not handled: " << len; + } +} + +static bool EncodeRSAPublicKey(const std::vector<uint8>& modulus_n, + const std::vector<uint8>& public_exponent_e, + std::vector<uint8>* public_key_info) { + // The public key is specified as the following ASN.1 structure: + // SubjectPublicKeyInfo ::= SEQUENCE { + // algorithm AlgorithmIdentifier, + // subjectPublicKey BIT STRING } + // + // The signature algorithm is specified as the following ASN.1 structure: + // AlgorithmIdentifier ::= SEQUENCE { + // algorithm OBJECT IDENTIFIER, + // parameters ANY DEFINED BY algorithm OPTIONAL } + // + // An RSA public key is specified as the following ASN.1 structure: + // RSAPublicKey ::= SEQUENCE { + // modulus INTEGER, -- n + // publicExponent INTEGER -- e + // } + static const uint8 kIntegerTag = 0x02; + static const uint8 kBitStringTag = 0x03; + static const uint8 kSequenceTag = 0x30; + public_key_info->clear(); + + // Encode the public exponent e as an INTEGER. + public_key_info->insert(public_key_info->begin(), + public_exponent_e.begin(), + public_exponent_e.end()); + PrependASN1Length(public_key_info, public_exponent_e.size()); + public_key_info->insert(public_key_info->begin(), kIntegerTag); + + // Encode the modulus n as an INTEGER. + public_key_info->insert(public_key_info->begin(), + modulus_n.begin(), modulus_n.end()); + size_t modulus_size = modulus_n.size(); + if (modulus_n[0] & 0x80) { + public_key_info->insert(public_key_info->begin(), 0x00); + modulus_size++; + } + PrependASN1Length(public_key_info, modulus_size); + public_key_info->insert(public_key_info->begin(), kIntegerTag); + + // Encode the RSAPublicKey SEQUENCE. + PrependASN1Length(public_key_info, public_key_info->size()); + public_key_info->insert(public_key_info->begin(), kSequenceTag); + + // Encode the BIT STRING. + // Number of unused bits. + public_key_info->insert(public_key_info->begin(), 0x00); + PrependASN1Length(public_key_info, public_key_info->size()); + public_key_info->insert(public_key_info->begin(), kBitStringTag); + + // Encode the AlgorithmIdentifier. + static const uint8 algorithm[] = { + 0x30, 0x0d, // a SEQUENCE of length 13 + 0x06, 0x09, // an OBJECT IDENTIFIER of length 9 + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, + }; + public_key_info->insert(public_key_info->begin(), + algorithm, algorithm + sizeof(algorithm)); + + // Encode the outermost SEQUENCE. + PrependASN1Length(public_key_info, public_key_info->size()); + public_key_info->insert(public_key_info->begin(), kSequenceTag); + + return true; +} + +TEST(SignatureVerifierTest, VerifyRSAPSS) { + for (unsigned int i = 0; i < arraysize(pss_test); i++) { + SCOPED_TRACE(i); + std::vector<uint8> modulus_n; + std::vector<uint8> public_exponent_e; + ASSERT_TRUE(DecodeTestInput(pss_test[i].modulus_n, &modulus_n)); + ASSERT_TRUE(DecodeTestInput(pss_test[i].public_exponent_e, + &public_exponent_e)); + std::vector<uint8> public_key_info; + ASSERT_TRUE(EncodeRSAPublicKey(modulus_n, public_exponent_e, + &public_key_info)); + + for (unsigned int j = 0; j < arraysize(pss_test[i].example); j++) { + SCOPED_TRACE(j); + std::vector<uint8> message; + std::vector<uint8> salt; + std::vector<uint8> signature; + ASSERT_TRUE(DecodeTestInput(pss_test[i].example[j].message, &message)); + ASSERT_TRUE(DecodeTestInput(pss_test[i].example[j].salt, &salt)); + ASSERT_TRUE(DecodeTestInput(pss_test[i].example[j].signature, + &signature)); + + crypto::SignatureVerifier verifier; + bool ok; + + // Positive test. + ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1, + crypto::SignatureVerifier::SHA1, + salt.size(), + &signature[0], signature.size(), + &public_key_info[0], + public_key_info.size()); + ASSERT_TRUE(ok); + verifier.VerifyUpdate(&message[0], message.size()); + ok = verifier.VerifyFinal(); + EXPECT_TRUE(ok); + + // Modify the first byte of the message. + ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1, + crypto::SignatureVerifier::SHA1, + salt.size(), + &signature[0], signature.size(), + &public_key_info[0], + public_key_info.size()); + ASSERT_TRUE(ok); + message[0] += 1; + verifier.VerifyUpdate(&message[0], message.size()); + message[0] -= 1; + ok = verifier.VerifyFinal(); + EXPECT_FALSE(ok); + + // Truncate the message. + ASSERT_FALSE(message.empty()); + ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1, + crypto::SignatureVerifier::SHA1, + salt.size(), + &signature[0], signature.size(), + &public_key_info[0], + public_key_info.size()); + ASSERT_TRUE(ok); + verifier.VerifyUpdate(&message[0], message.size() - 1); + ok = verifier.VerifyFinal(); + EXPECT_FALSE(ok); + + // Corrupt the signature. + signature[0] += 1; + ok = verifier.VerifyInitRSAPSS(crypto::SignatureVerifier::SHA1, + crypto::SignatureVerifier::SHA1, + salt.size(), + &signature[0], signature.size(), + &public_key_info[0], + public_key_info.size()); + signature[0] -= 1; + ASSERT_TRUE(ok); + verifier.VerifyUpdate(&message[0], message.size()); + ok = verifier.VerifyFinal(); + EXPECT_FALSE(ok); + } + } +} diff --git a/crypto/symmetric_key_nss.cc b/crypto/symmetric_key_nss.cc new file mode 100644 index 0000000000..95ca9bd073 --- /dev/null +++ b/crypto/symmetric_key_nss.cc @@ -0,0 +1,149 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/symmetric_key.h" + +#include <nss.h> +#include <pk11pub.h> + +#include "base/logging.h" +#include "crypto/nss_util.h" + +namespace crypto { + +SymmetricKey::~SymmetricKey() {} + +// static +SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, + size_t key_size_in_bits) { + DCHECK_EQ(AES, algorithm); + + EnsureNSSInit(); + + // Whitelist supported key sizes to avoid accidentaly relying on + // algorithms available in NSS but not BoringSSL and vice + // versa. Note that BoringSSL does not support AES-192. + if (key_size_in_bits != 128 && key_size_in_bits != 256) + return NULL; + + ScopedPK11Slot slot(PK11_GetInternalSlot()); + if (!slot.get()) + return NULL; + + PK11SymKey* sym_key = PK11_KeyGen(slot.get(), CKM_AES_KEY_GEN, NULL, + key_size_in_bits / 8, NULL); + if (!sym_key) + return NULL; + + return new SymmetricKey(sym_key); +} + +// static +SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm, + const std::string& password, + const std::string& salt, + size_t iterations, + size_t key_size_in_bits) { + EnsureNSSInit(); + if (salt.empty() || iterations == 0 || key_size_in_bits == 0) + return NULL; + + if (algorithm == AES) { + // Whitelist supported key sizes to avoid accidentaly relying on + // algorithms available in NSS but not BoringSSL and vice + // versa. Note that BoringSSL does not support AES-192. + if (key_size_in_bits != 128 && key_size_in_bits != 256) + return NULL; + } + + SECItem password_item; + password_item.type = siBuffer; + password_item.data = reinterpret_cast<unsigned char*>( + const_cast<char *>(password.data())); + password_item.len = password.size(); + + SECItem salt_item; + salt_item.type = siBuffer; + salt_item.data = reinterpret_cast<unsigned char*>( + const_cast<char *>(salt.data())); + salt_item.len = salt.size(); + + SECOidTag cipher_algorithm = + algorithm == AES ? SEC_OID_AES_256_CBC : SEC_OID_HMAC_SHA1; + ScopedSECAlgorithmID alg_id(PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2, + cipher_algorithm, + SEC_OID_HMAC_SHA1, + key_size_in_bits / 8, + iterations, + &salt_item)); + if (!alg_id.get()) + return NULL; + + ScopedPK11Slot slot(PK11_GetInternalSlot()); + if (!slot.get()) + return NULL; + + PK11SymKey* sym_key = PK11_PBEKeyGen(slot.get(), alg_id.get(), &password_item, + PR_FALSE, NULL); + if (!sym_key) + return NULL; + + return new SymmetricKey(sym_key); +} + +// static +SymmetricKey* SymmetricKey::Import(Algorithm algorithm, + const std::string& raw_key) { + EnsureNSSInit(); + + if (algorithm == AES) { + // Whitelist supported key sizes to avoid accidentaly relying on + // algorithms available in NSS but not BoringSSL and vice + // versa. Note that BoringSSL does not support AES-192. + if (raw_key.size() != 128/8 && raw_key.size() != 256/8) + return NULL; + } + + CK_MECHANISM_TYPE cipher = + algorithm == AES ? CKM_AES_CBC : CKM_SHA_1_HMAC; + + SECItem key_item; + key_item.type = siBuffer; + key_item.data = reinterpret_cast<unsigned char*>( + const_cast<char *>(raw_key.data())); + key_item.len = raw_key.size(); + + ScopedPK11Slot slot(PK11_GetInternalSlot()); + if (!slot.get()) + return NULL; + + // The exact value of the |origin| argument doesn't matter to NSS as long as + // it's not PK11_OriginFortezzaHack, so we pass PK11_OriginUnwrap as a + // placeholder. + PK11SymKey* sym_key = PK11_ImportSymKey(slot.get(), cipher, PK11_OriginUnwrap, + CKA_ENCRYPT, &key_item, NULL); + if (!sym_key) + return NULL; + + return new SymmetricKey(sym_key); +} + +bool SymmetricKey::GetRawKey(std::string* raw_key) { + SECStatus rv = PK11_ExtractKeyValue(key_.get()); + if (SECSuccess != rv) + return false; + + SECItem* key_item = PK11_GetKeyData(key_.get()); + if (!key_item) + return false; + + raw_key->assign(reinterpret_cast<char*>(key_item->data), key_item->len); + return true; +} + +SymmetricKey::SymmetricKey(PK11SymKey* key) : key_(key) { + DCHECK(key); +} + +} // namespace crypto diff --git a/crypto/symmetric_key_openssl.cc b/crypto/symmetric_key_openssl.cc new file mode 100644 index 0000000000..e268a1d0f9 --- /dev/null +++ b/crypto/symmetric_key_openssl.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/symmetric_key.h" + +#include <openssl/evp.h> +#include <openssl/rand.h> + +#include <algorithm> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "crypto/openssl_util.h" + +namespace crypto { + +SymmetricKey::~SymmetricKey() { + std::fill(key_.begin(), key_.end(), '\0'); // Zero out the confidential key. +} + +// static +SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, + size_t key_size_in_bits) { + DCHECK_EQ(AES, algorithm); + + // Whitelist supported key sizes to avoid accidentaly relying on + // algorithms available in NSS but not BoringSSL and vice + // versa. Note that BoringSSL does not support AES-192. + if (key_size_in_bits != 128 && key_size_in_bits != 256) + return NULL; + + size_t key_size_in_bytes = key_size_in_bits / 8; + DCHECK_EQ(key_size_in_bits, key_size_in_bytes * 8); + + if (key_size_in_bytes == 0) + return NULL; + + OpenSSLErrStackTracer err_tracer(FROM_HERE); + scoped_ptr<SymmetricKey> key(new SymmetricKey); + uint8* key_data = + reinterpret_cast<uint8*>(WriteInto(&key->key_, key_size_in_bytes + 1)); + + int rv = RAND_bytes(key_data, static_cast<int>(key_size_in_bytes)); + return rv == 1 ? key.release() : NULL; +} + +// static +SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm, + const std::string& password, + const std::string& salt, + size_t iterations, + size_t key_size_in_bits) { + DCHECK(algorithm == AES || algorithm == HMAC_SHA1); + + if (algorithm == AES) { + // Whitelist supported key sizes to avoid accidentaly relying on + // algorithms available in NSS but not BoringSSL and vice + // versa. Note that BoringSSL does not support AES-192. + if (key_size_in_bits != 128 && key_size_in_bits != 256) + return NULL; + } + + size_t key_size_in_bytes = key_size_in_bits / 8; + DCHECK_EQ(key_size_in_bits, key_size_in_bytes * 8); + + if (key_size_in_bytes == 0) + return NULL; + + OpenSSLErrStackTracer err_tracer(FROM_HERE); + scoped_ptr<SymmetricKey> key(new SymmetricKey); + uint8* key_data = + reinterpret_cast<uint8*>(WriteInto(&key->key_, key_size_in_bytes + 1)); + int rv = PKCS5_PBKDF2_HMAC_SHA1(password.data(), password.length(), + reinterpret_cast<const uint8*>(salt.data()), + salt.length(), iterations, + static_cast<int>(key_size_in_bytes), + key_data); + return rv == 1 ? key.release() : NULL; +} + +// static +SymmetricKey* SymmetricKey::Import(Algorithm algorithm, + const std::string& raw_key) { + if (algorithm == AES) { + // Whitelist supported key sizes to avoid accidentaly relying on + // algorithms available in NSS but not BoringSSL and vice + // versa. Note that BoringSSL does not support AES-192. + if (raw_key.size() != 128/8 && raw_key.size() != 256/8) + return NULL; + } + + scoped_ptr<SymmetricKey> key(new SymmetricKey); + key->key_ = raw_key; + return key.release(); +} + +bool SymmetricKey::GetRawKey(std::string* raw_key) { + *raw_key = key_; + return true; +} + +} // namespace crypto diff --git a/crypto/symmetric_key_unittest.cc b/crypto/symmetric_key_unittest.cc new file mode 100644 index 0000000000..28e44d27da --- /dev/null +++ b/crypto/symmetric_key_unittest.cc @@ -0,0 +1,225 @@ +// Copyright (c) 2011 The Chromium 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 "crypto/symmetric_key.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(SymmetricKeyTest, GenerateRandomKey) { + scoped_ptr<crypto::SymmetricKey> key( + crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES, 256)); + ASSERT_TRUE(NULL != key.get()); + std::string raw_key; + EXPECT_TRUE(key->GetRawKey(&raw_key)); + EXPECT_EQ(32U, raw_key.size()); + + // Do it again and check that the keys are different. + // (Note: this has a one-in-10^77 chance of failure!) + scoped_ptr<crypto::SymmetricKey> key2( + crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES, 256)); + ASSERT_TRUE(NULL != key2.get()); + std::string raw_key2; + EXPECT_TRUE(key2->GetRawKey(&raw_key2)); + EXPECT_EQ(32U, raw_key2.size()); + EXPECT_NE(raw_key, raw_key2); +} + +TEST(SymmetricKeyTest, ImportGeneratedKey) { + scoped_ptr<crypto::SymmetricKey> key1( + crypto::SymmetricKey::GenerateRandomKey(crypto::SymmetricKey::AES, 256)); + ASSERT_TRUE(NULL != key1.get()); + std::string raw_key1; + EXPECT_TRUE(key1->GetRawKey(&raw_key1)); + + scoped_ptr<crypto::SymmetricKey> key2( + crypto::SymmetricKey::Import(crypto::SymmetricKey::AES, raw_key1)); + ASSERT_TRUE(NULL != key2.get()); + + std::string raw_key2; + EXPECT_TRUE(key2->GetRawKey(&raw_key2)); + + EXPECT_EQ(raw_key1, raw_key2); +} + +TEST(SymmetricKeyTest, ImportDerivedKey) { + scoped_ptr<crypto::SymmetricKey> key1( + crypto::SymmetricKey::DeriveKeyFromPassword( + crypto::SymmetricKey::HMAC_SHA1, "password", "somesalt", 1024, 160)); + ASSERT_TRUE(NULL != key1.get()); + std::string raw_key1; + EXPECT_TRUE(key1->GetRawKey(&raw_key1)); + + scoped_ptr<crypto::SymmetricKey> key2( + crypto::SymmetricKey::Import(crypto::SymmetricKey::HMAC_SHA1, raw_key1)); + ASSERT_TRUE(NULL != key2.get()); + + std::string raw_key2; + EXPECT_TRUE(key2->GetRawKey(&raw_key2)); + + EXPECT_EQ(raw_key1, raw_key2); +} + +struct PBKDF2TestVector { + crypto::SymmetricKey::Algorithm algorithm; + const char* password; + const char* salt; + unsigned int rounds; + unsigned int key_size_in_bits; + const char* expected; // ASCII encoded hex bytes +}; + +class SymmetricKeyDeriveKeyFromPasswordTest + : public testing::TestWithParam<PBKDF2TestVector> { +}; + +TEST_P(SymmetricKeyDeriveKeyFromPasswordTest, DeriveKeyFromPassword) { + PBKDF2TestVector test_data(GetParam()); +#if defined(OS_MACOSX) && !defined(OS_IOS) + // The OS X crypto libraries have minimum salt and iteration requirements + // so some of the tests below will cause them to barf. Skip these. + if (strlen(test_data.salt) < 8 || test_data.rounds < 1000) { + VLOG(1) << "Skipped test vector for " << test_data.expected; + return; + } +#endif // OS_MACOSX + + scoped_ptr<crypto::SymmetricKey> key( + crypto::SymmetricKey::DeriveKeyFromPassword( + test_data.algorithm, + test_data.password, test_data.salt, + test_data.rounds, test_data.key_size_in_bits)); + ASSERT_TRUE(NULL != key.get()); + + std::string raw_key; + key->GetRawKey(&raw_key); + EXPECT_EQ(test_data.key_size_in_bits / 8, raw_key.size()); + EXPECT_EQ(test_data.expected, + base::StringToLowerASCII(base::HexEncode(raw_key.data(), + raw_key.size()))); +} + +static const PBKDF2TestVector kTestVectors[] = { + // These tests come from + // http://www.ietf.org/id/draft-josefsson-pbkdf2-test-vectors-00.txt + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "salt", + 1, + 160, + "0c60c80f961f0e71f3a9b524af6012062fe037a6", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "salt", + 2, + 160, + "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "salt", + 4096, + 160, + "4b007901b765489abead49d926f721d065a429c1", + }, + // This test takes over 30s to run on the trybots. +#if 0 + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "salt", + 16777216, + 160, + "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984", + }, +#endif + + // These tests come from RFC 3962, via BSD source code at + // http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/bioctl/pbkdf2.c?rev=HEAD&content-type=text/plain + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "ATHENA.MIT.EDUraeburn", + 1, + 160, + "cdedb5281bb2f801565a1122b25635150ad1f7a0", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "ATHENA.MIT.EDUraeburn", + 2, + 160, + "01dbee7f4a9e243e988b62c73cda935da05378b9", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "ATHENA.MIT.EDUraeburn", + 1200, + 160, + "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddb", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "password", + "\022" "4VxxV4\022", /* 0x1234567878563412 */ + 5, + 160, + "d1daa78615f287e6a1c8b120d7062a493f98d203", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "pass phrase equals block size", + 1200, + 160, + "139c30c0966bc32ba55fdbf212530ac9c5ec59f1", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "pass phrase exceeds block size", + 1200, + 160, + "9ccad6d468770cd51b10e6a68721be611a8b4d28", + }, + { + crypto::SymmetricKey::HMAC_SHA1, + "\360\235\204\236", /* g-clef (0xf09d849e) */ + "EXAMPLE.COMpianist", + 50, + 160, + "6b9cf26d45455a43a5b8bb276a403b39e7fe37a0", + }, + + // Regression tests for AES keys, derived from the Linux NSS implementation. + { + crypto::SymmetricKey::AES, + "A test password", + "saltsalt", + 1, + 256, + "44899a7777f0e6e8b752f875f02044b8ac593de146de896f2e8a816e315a36de", + }, + { + crypto::SymmetricKey::AES, + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "pass phrase exceeds block size", + 20, + 256, + "e0739745dc28b8721ba402e05214d2ac1eab54cf72bee1fba388297a09eb493c", + }, +}; + +INSTANTIATE_TEST_CASE_P(, SymmetricKeyDeriveKeyFromPasswordTest, + testing::ValuesIn(kTestVectors)); diff --git a/crypto/symmetric_key_win.cc b/crypto/symmetric_key_win.cc new file mode 100644 index 0000000000..b3d65f6613 --- /dev/null +++ b/crypto/symmetric_key_win.cc @@ -0,0 +1,536 @@ +// Copyright (c) 2012 The Chromium 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 "crypto/symmetric_key.h" + +#include <vector> + +// TODO(wtc): replace scoped_array by std::vector. +#include "base/memory/scoped_ptr.h" +#include "base/sys_byteorder.h" + +namespace crypto { + +namespace { + +// The following is a non-public Microsoft header documented in MSDN under +// CryptImportKey / CryptExportKey. Following the header is the byte array of +// the actual plaintext key. +struct PlaintextBlobHeader { + BLOBHEADER hdr; + DWORD cbKeySize; +}; + +// CryptoAPI makes use of three distinct ALG_IDs for AES, rather than just +// CALG_AES (which exists, but depending on the functions you are calling, may +// result in function failure, whereas the subtype would succeed). +ALG_ID GetAESAlgIDForKeySize(size_t key_size_in_bits) { + // Only AES-128/-192/-256 is supported in CryptoAPI. + switch (key_size_in_bits) { + case 128: + return CALG_AES_128; + case 192: + return CALG_AES_192; + case 256: + return CALG_AES_256; + default: + NOTREACHED(); + return 0; + } +}; + +// Imports a raw/plaintext key of |key_size| stored in |*key_data| into a new +// key created for the specified |provider|. |alg| contains the algorithm of +// the key being imported. +// If |key_data| is intended to be used as an HMAC key, then |alg| should be +// CALG_HMAC. +// If successful, returns true and stores the imported key in |*key|. +// TODO(wtc): use this function in hmac_win.cc. +bool ImportRawKey(HCRYPTPROV provider, + ALG_ID alg, + const void* key_data, size_t key_size, + ScopedHCRYPTKEY* key) { + DCHECK_GT(key_size, 0); + + DWORD actual_size = + static_cast<DWORD>(sizeof(PlaintextBlobHeader) + key_size); + std::vector<BYTE> tmp_data(actual_size); + BYTE* actual_key = &tmp_data[0]; + memcpy(actual_key + sizeof(PlaintextBlobHeader), key_data, key_size); + PlaintextBlobHeader* key_header = + reinterpret_cast<PlaintextBlobHeader*>(actual_key); + memset(key_header, 0, sizeof(PlaintextBlobHeader)); + + key_header->hdr.bType = PLAINTEXTKEYBLOB; + key_header->hdr.bVersion = CUR_BLOB_VERSION; + key_header->hdr.aiKeyAlg = alg; + + key_header->cbKeySize = static_cast<DWORD>(key_size); + + HCRYPTKEY unsafe_key = NULL; + DWORD flags = CRYPT_EXPORTABLE; + if (alg == CALG_HMAC) { + // Though it may appear odd that IPSEC and RC2 are being used, this is + // done in accordance with Microsoft's FIPS 140-2 Security Policy for the + // RSA Enhanced Provider, as the approved means of using arbitrary HMAC + // key material. + key_header->hdr.aiKeyAlg = CALG_RC2; + flags |= CRYPT_IPSEC_HMAC_KEY; + } + + BOOL ok = + CryptImportKey(provider, actual_key, actual_size, 0, flags, &unsafe_key); + + // Clean up the temporary copy of key, regardless of whether it was imported + // sucessfully or not. + SecureZeroMemory(actual_key, actual_size); + + if (!ok) + return false; + + key->reset(unsafe_key); + return true; +} + +// Attempts to generate a random AES key of |key_size_in_bits|. Returns true +// if generation is successful, storing the generated key in |*key| and the +// key provider (CSP) in |*provider|. +bool GenerateAESKey(size_t key_size_in_bits, + ScopedHCRYPTPROV* provider, + ScopedHCRYPTKEY* key) { + DCHECK(provider); + DCHECK(key); + + ALG_ID alg = GetAESAlgIDForKeySize(key_size_in_bits); + if (alg == 0) + return false; + + ScopedHCRYPTPROV safe_provider; + // Note: The only time NULL is safe to be passed as pszContainer is when + // dwFlags contains CRYPT_VERIFYCONTEXT, as all keys generated and/or used + // will be treated as ephemeral keys and not persisted. + BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL, + PROV_RSA_AES, CRYPT_VERIFYCONTEXT); + if (!ok) + return false; + + ScopedHCRYPTKEY safe_key; + // In the FIPS 140-2 Security Policy for CAPI on XP/Vista+, Microsoft notes + // that CryptGenKey makes use of the same functionality exposed via + // CryptGenRandom. The reason this is being used, as opposed to + // CryptGenRandom and CryptImportKey is for compliance with the security + // policy + ok = CryptGenKey(safe_provider.get(), alg, CRYPT_EXPORTABLE, + safe_key.receive()); + if (!ok) + return false; + + key->swap(safe_key); + provider->swap(safe_provider); + + return true; +} + +// Returns true if the HMAC key size meets the requirement of FIPS 198 +// Section 3. |alg| is the hash function used in the HMAC. +bool CheckHMACKeySize(size_t key_size_in_bits, ALG_ID alg) { + DWORD hash_size = 0; + switch (alg) { + case CALG_SHA1: + hash_size = 20; + break; + case CALG_SHA_256: + hash_size = 32; + break; + case CALG_SHA_384: + hash_size = 48; + break; + case CALG_SHA_512: + hash_size = 64; + break; + } + if (hash_size == 0) + return false; + + // An HMAC key must be >= L/2, where L is the output size of the hash + // function being used. + return (key_size_in_bits >= (hash_size / 2 * 8) && + (key_size_in_bits % 8) == 0); +} + +// Attempts to generate a random, |key_size_in_bits|-long HMAC key, for use +// with the hash function |alg|. +// |key_size_in_bits| must be >= 1/2 the hash size of |alg| for security. +// Returns true if generation is successful, storing the generated key in +// |*key| and the key provider (CSP) in |*provider|. +bool GenerateHMACKey(size_t key_size_in_bits, + ALG_ID alg, + ScopedHCRYPTPROV* provider, + ScopedHCRYPTKEY* key, + scoped_ptr<BYTE[]>* raw_key) { + DCHECK(provider); + DCHECK(key); + DCHECK(raw_key); + + if (!CheckHMACKeySize(key_size_in_bits, alg)) + return false; + + ScopedHCRYPTPROV safe_provider; + // See comment in GenerateAESKey as to why NULL is acceptable for the + // container name. + BOOL ok = CryptAcquireContext(safe_provider.receive(), NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + if (!ok) + return false; + + DWORD key_size_in_bytes = static_cast<DWORD>(key_size_in_bits / 8); + scoped_ptr<BYTE[]> random(new BYTE[key_size_in_bytes]); + ok = CryptGenRandom(safe_provider, key_size_in_bytes, random.get()); + if (!ok) + return false; + + ScopedHCRYPTKEY safe_key; + bool rv = ImportRawKey(safe_provider, CALG_HMAC, random.get(), + key_size_in_bytes, &safe_key); + if (rv) { + key->swap(safe_key); + provider->swap(safe_provider); + raw_key->swap(random); + } + + SecureZeroMemory(random.get(), key_size_in_bytes); + return rv; +} + +// Attempts to create an HMAC hash instance using the specified |provider| +// and |key|. The inner hash function will be |hash_alg|. If successful, +// returns true and stores the hash in |*hash|. +// TODO(wtc): use this function in hmac_win.cc. +bool CreateHMACHash(HCRYPTPROV provider, + HCRYPTKEY key, + ALG_ID hash_alg, + ScopedHCRYPTHASH* hash) { + ScopedHCRYPTHASH safe_hash; + BOOL ok = CryptCreateHash(provider, CALG_HMAC, key, 0, safe_hash.receive()); + if (!ok) + return false; + + HMAC_INFO hmac_info; + memset(&hmac_info, 0, sizeof(hmac_info)); + hmac_info.HashAlgid = hash_alg; + + ok = CryptSetHashParam(safe_hash, HP_HMAC_INFO, + reinterpret_cast<const BYTE*>(&hmac_info), 0); + if (!ok) + return false; + + hash->swap(safe_hash); + return true; +} + +// Computes a block of the derived key using the PBKDF2 function F for the +// specified |block_index| using the PRF |hash|, writing the output to +// |output_buf|. +// |output_buf| must have enough space to accomodate the output of the PRF +// specified by |hash|. +// Returns true if the block was successfully computed. +bool ComputePBKDF2Block(HCRYPTHASH hash, + DWORD hash_size, + const std::string& salt, + size_t iterations, + uint32 block_index, + BYTE* output_buf) { + // From RFC 2898: + // 3. <snip> The function F is defined as the exclusive-or sum of the first + // c iterates of the underlying pseudorandom function PRF applied to the + // password P and the concatenation of the salt S and the block index i: + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c + // where + // U_1 = PRF(P, S || INT (i)) + // U_2 = PRF(P, U_1) + // ... + // U_c = PRF(P, U_{c-1}) + ScopedHCRYPTHASH safe_hash; + BOOL ok = CryptDuplicateHash(hash, NULL, 0, safe_hash.receive()); + if (!ok) + return false; + + // Iteration U_1: Compute PRF for S. + ok = CryptHashData(safe_hash, reinterpret_cast<const BYTE*>(salt.data()), + static_cast<DWORD>(salt.size()), 0); + if (!ok) + return false; + + // Iteration U_1: and append (big-endian) INT (i). + uint32 big_endian_block_index = base::HostToNet32(block_index); + ok = CryptHashData(safe_hash, + reinterpret_cast<BYTE*>(&big_endian_block_index), + sizeof(big_endian_block_index), 0); + + std::vector<BYTE> hash_value(hash_size); + + DWORD size = hash_size; + ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0); + if (!ok || size != hash_size) + return false; + + memcpy(output_buf, &hash_value[0], hash_size); + + // Iteration 2 - c: Compute U_{iteration} by applying the PRF to + // U_{iteration - 1}, then xor the resultant hash with |output|, which + // contains U_1 ^ U_2 ^ ... ^ U_{iteration - 1}. + for (size_t iteration = 2; iteration <= iterations; ++iteration) { + safe_hash.reset(); + ok = CryptDuplicateHash(hash, NULL, 0, safe_hash.receive()); + if (!ok) + return false; + + ok = CryptHashData(safe_hash, &hash_value[0], hash_size, 0); + if (!ok) + return false; + + size = hash_size; + ok = CryptGetHashParam(safe_hash, HP_HASHVAL, &hash_value[0], &size, 0); + if (!ok || size != hash_size) + return false; + + for (int i = 0; i < hash_size; ++i) + output_buf[i] ^= hash_value[i]; + } + + return true; +} + +} // namespace + +SymmetricKey::~SymmetricKey() { + // TODO(wtc): create a "secure" string type that zeroes itself in the + // destructor. + if (!raw_key_.empty()) + SecureZeroMemory(const_cast<char *>(raw_key_.data()), raw_key_.size()); +} + +// static +SymmetricKey* SymmetricKey::GenerateRandomKey(Algorithm algorithm, + size_t key_size_in_bits) { + DCHECK_GE(key_size_in_bits, 8); + + ScopedHCRYPTPROV provider; + ScopedHCRYPTKEY key; + + bool ok = false; + scoped_ptr<BYTE[]> raw_key; + + switch (algorithm) { + case AES: + ok = GenerateAESKey(key_size_in_bits, &provider, &key); + break; + case HMAC_SHA1: + ok = GenerateHMACKey(key_size_in_bits, CALG_SHA1, &provider, + &key, &raw_key); + break; + } + + if (!ok) { + NOTREACHED(); + return NULL; + } + + size_t key_size_in_bytes = key_size_in_bits / 8; + if (raw_key == NULL) + key_size_in_bytes = 0; + + SymmetricKey* result = new SymmetricKey(provider.release(), + key.release(), + raw_key.get(), + key_size_in_bytes); + if (raw_key != NULL) + SecureZeroMemory(raw_key.get(), key_size_in_bytes); + + return result; +} + +// static +SymmetricKey* SymmetricKey::DeriveKeyFromPassword(Algorithm algorithm, + const std::string& password, + const std::string& salt, + size_t iterations, + size_t key_size_in_bits) { + // CryptoAPI lacks routines to perform PBKDF2 derivation as specified + // in RFC 2898, so it must be manually implemented. Only HMAC-SHA1 is + // supported as the PRF. + + // While not used until the end, sanity-check the input before proceeding + // with the expensive computation. + DWORD provider_type = 0; + ALG_ID alg = 0; + switch (algorithm) { + case AES: + provider_type = PROV_RSA_AES; + alg = GetAESAlgIDForKeySize(key_size_in_bits); + break; + case HMAC_SHA1: + provider_type = PROV_RSA_FULL; + alg = CALG_HMAC; + break; + default: + NOTREACHED(); + break; + } + if (provider_type == 0 || alg == 0) + return NULL; + + ScopedHCRYPTPROV provider; + BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type, + CRYPT_VERIFYCONTEXT); + if (!ok) + return NULL; + + // Convert the user password into a key suitable to be fed into the PRF + // function. + ScopedHCRYPTKEY password_as_key; + BYTE* password_as_bytes = + const_cast<BYTE*>(reinterpret_cast<const BYTE*>(password.data())); + if (!ImportRawKey(provider, CALG_HMAC, password_as_bytes, + password.size(), &password_as_key)) + return NULL; + + // Configure the PRF function. Only HMAC variants are supported, with the + // only hash function supported being SHA1. + // TODO(rsleevi): Support SHA-256 on XP SP3+. + ScopedHCRYPTHASH prf; + if (!CreateHMACHash(provider, password_as_key, CALG_SHA1, &prf)) + return NULL; + + DWORD hLen = 0; + DWORD param_size = sizeof(hLen); + ok = CryptGetHashParam(prf, HP_HASHSIZE, + reinterpret_cast<BYTE*>(&hLen), ¶m_size, 0); + if (!ok || hLen == 0) + return NULL; + + // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop. + size_t dkLen = key_size_in_bits / 8; + DCHECK_GT(dkLen, 0); + + if ((dkLen / hLen) > 0xFFFFFFFF) { + DLOG(ERROR) << "Derived key too long."; + return NULL; + } + + // 2. Let l be the number of hLen-octet blocks in the derived key, + // rounding up, and let r be the number of octets in the last + // block: + size_t L = (dkLen + hLen - 1) / hLen; + DCHECK_GT(L, 0); + + size_t total_generated_size = L * hLen; + std::vector<BYTE> generated_key(total_generated_size); + BYTE* block_offset = &generated_key[0]; + + // 3. For each block of the derived key apply the function F defined below + // to the password P, the salt S, the iteration count c, and the block + // index to compute the block: + // T_1 = F (P, S, c, 1) + // T_2 = F (P, S, c, 2) + // ... + // T_l = F (P, S, c, l) + // <snip> + // 4. Concatenate the blocks and extract the first dkLen octets to produce + // a derived key DK: + // DK = T_1 || T_2 || ... || T_l<0..r-1> + for (uint32 block_index = 1; block_index <= L; ++block_index) { + if (!ComputePBKDF2Block(prf, hLen, salt, iterations, block_index, + block_offset)) + return NULL; + block_offset += hLen; + } + + // Convert the derived key bytes into a key handle for the desired algorithm. + ScopedHCRYPTKEY key; + if (!ImportRawKey(provider, alg, &generated_key[0], dkLen, &key)) + return NULL; + + SymmetricKey* result = new SymmetricKey(provider.release(), key.release(), + &generated_key[0], dkLen); + + SecureZeroMemory(&generated_key[0], total_generated_size); + + return result; +} + +// static +SymmetricKey* SymmetricKey::Import(Algorithm algorithm, + const std::string& raw_key) { + DWORD provider_type = 0; + ALG_ID alg = 0; + switch (algorithm) { + case AES: + provider_type = PROV_RSA_AES; + alg = GetAESAlgIDForKeySize(raw_key.size() * 8); + break; + case HMAC_SHA1: + provider_type = PROV_RSA_FULL; + alg = CALG_HMAC; + break; + default: + NOTREACHED(); + break; + } + if (provider_type == 0 || alg == 0) + return NULL; + + ScopedHCRYPTPROV provider; + BOOL ok = CryptAcquireContext(provider.receive(), NULL, NULL, provider_type, + CRYPT_VERIFYCONTEXT); + if (!ok) + return NULL; + + ScopedHCRYPTKEY key; + if (!ImportRawKey(provider, alg, raw_key.data(), raw_key.size(), &key)) + return NULL; + + return new SymmetricKey(provider.release(), key.release(), + raw_key.data(), raw_key.size()); +} + +bool SymmetricKey::GetRawKey(std::string* raw_key) { + // Short circuit for when the key was supplied to the constructor. + if (!raw_key_.empty()) { + *raw_key = raw_key_; + return true; + } + + DWORD size = 0; + BOOL ok = CryptExportKey(key_, 0, PLAINTEXTKEYBLOB, 0, NULL, &size); + if (!ok) + return false; + + std::vector<BYTE> result(size); + + ok = CryptExportKey(key_, 0, PLAINTEXTKEYBLOB, 0, &result[0], &size); + if (!ok) + return false; + + PlaintextBlobHeader* header = + reinterpret_cast<PlaintextBlobHeader*>(&result[0]); + raw_key->assign(reinterpret_cast<char*>(&result[sizeof(*header)]), + header->cbKeySize); + + SecureZeroMemory(&result[0], size); + + return true; +} + +SymmetricKey::SymmetricKey(HCRYPTPROV provider, + HCRYPTKEY key, + const void* key_data, size_t key_size_in_bytes) + : provider_(provider), key_(key) { + if (key_data) { + raw_key_.assign(reinterpret_cast<const char*>(key_data), + key_size_in_bytes); + } +} + +} // namespace crypto diff --git a/crypto/third_party/nss/pk11akey.cc b/crypto/third_party/nss/pk11akey.cc new file mode 100644 index 0000000000..4db582fc6c --- /dev/null +++ b/crypto/third_party/nss/pk11akey.cc @@ -0,0 +1,98 @@ + /* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dr Stephen Henson <stephen.henson@gemplus.com> + * Dr Vipul Gupta <vipul.gupta@sun.com>, and + * Douglas Stebila <douglas@stebila.ca>, Sun Microsystems Laboratories + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "crypto/third_party/nss/chromium-nss.h" + +#include <pk11pub.h> + +#include "base/logging.h" + +// Based on PK11_ImportEncryptedPrivateKeyInfo function in +// mozilla/security/nss/lib/pk11wrap/pk11akey.c. +SECStatus ImportEncryptedECPrivateKeyInfoAndReturnKey( + PK11SlotInfo* slot, + SECKEYEncryptedPrivateKeyInfo* epki, + SECItem* password, + SECItem* nickname, + SECItem* public_value, + PRBool permanent, + PRBool sensitive, + SECKEYPrivateKey** private_key, + void* wincx) { + SECItem* crypto_param = NULL; + + CK_ATTRIBUTE_TYPE usage = CKA_SIGN; + + PK11SymKey* key = PK11_PBEKeyGen(slot, + &epki->algorithm, + password, + PR_FALSE, // faulty3DES + wincx); + if (key == NULL) { + DLOG(ERROR) << "PK11_PBEKeyGen: " << PORT_GetError(); + return SECFailure; + } + + CK_MECHANISM_TYPE crypto_mech_type = PK11_GetPBECryptoMechanism( + &epki->algorithm, &crypto_param, password); + if (crypto_mech_type == CKM_INVALID_MECHANISM) { + DLOG(ERROR) << "PK11_GetPBECryptoMechanism: " << PORT_GetError(); + PK11_FreeSymKey(key); + return SECFailure; + } + + crypto_mech_type = PK11_GetPadMechanism(crypto_mech_type); + + *private_key = PK11_UnwrapPrivKey(slot, key, crypto_mech_type, crypto_param, + &epki->encryptedData, nickname, + public_value, permanent, sensitive, CKK_EC, + &usage, 1, wincx); + + if (crypto_param != NULL) + SECITEM_ZfreeItem(crypto_param, PR_TRUE); + + PK11_FreeSymKey(key); + + if (!*private_key) { + DLOG(ERROR) << "PK11_UnwrapPrivKey: " << PORT_GetError(); + return SECFailure; + } + + return SECSuccess; +} diff --git a/crypto/third_party/nss/secsign.cc b/crypto/third_party/nss/secsign.cc new file mode 100644 index 0000000000..a788defc70 --- /dev/null +++ b/crypto/third_party/nss/secsign.cc @@ -0,0 +1,132 @@ +/* + * Signature stuff. + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1994-2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Dr Vipul Gupta <vipul.gupta@sun.com>, Sun Microsystems Laboratories + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "crypto/third_party/nss/chromium-nss.h" + +#include <vector> + +#include <cryptohi.h> +#include <pk11pub.h> +#include <secerr.h> +#include <sechash.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "build/build_config.h" + +SECStatus DerSignData(PLArenaPool *arena, + SECItem *result, + SECItem *input, + SECKEYPrivateKey *key, + SECOidTag algo_id) { + if (key->keyType != ecKey) { + return SEC_DerSignData(arena, result, input->data, input->len, key, + algo_id); + } + + // NSS has a private function sec_DecodeSigAlg it uses to figure out the + // correct hash from the algorithm id. + HASH_HashType hash_type; + switch (algo_id) { + case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE: + hash_type = HASH_AlgSHA1; + break; +#ifdef SHA224_LENGTH + case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE: + hash_type = HASH_AlgSHA224; + break; +#endif + case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE: + hash_type = HASH_AlgSHA256; + break; + case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE: + hash_type = HASH_AlgSHA384; + break; + case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE: + hash_type = HASH_AlgSHA512; + break; + default: + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } + + // Hash the input. + std::vector<uint8> hash_data(HASH_ResultLen(hash_type)); + SECStatus rv = HASH_HashBuf( + hash_type, &hash_data[0], input->data, input->len); + if (rv != SECSuccess) + return rv; + SECItem hash = {siBuffer, &hash_data[0], + static_cast<unsigned int>(hash_data.size())}; + + // Compute signature of hash. + int signature_len = PK11_SignatureLen(key); + std::vector<uint8> signature_data(signature_len); + SECItem sig = {siBuffer, &signature_data[0], + static_cast<unsigned int>(signature_len)}; + rv = PK11_Sign(key, &sig, &hash); + if (rv != SECSuccess) + return rv; + + CERTSignedData sd; + PORT_Memset(&sd, 0, sizeof(sd)); + // Fill in tbsCertificate. + sd.data.data = (unsigned char*) input->data; + sd.data.len = input->len; + + // Fill in signatureAlgorithm. + rv = SECOID_SetAlgorithmID(arena, &sd.signatureAlgorithm, algo_id, 0); + if (rv != SECSuccess) + return rv; + + // Fill in signatureValue. + rv = DSAU_EncodeDerSigWithLen(&sd.signature, &sig, sig.len); + if (rv != SECSuccess) + return rv; + sd.signature.len <<= 3; // Convert to bit string. + + // DER encode the signed data object. + void* encode_result = SEC_ASN1EncodeItem( + arena, result, &sd, SEC_ASN1_GET(CERT_SignedDataTemplate)); + + PORT_Free(sd.signature.data); + + return encode_result ? SECSuccess : SECFailure; +} diff --git a/crypto/third_party/nss/sha512.cc b/crypto/third_party/nss/sha512.cc new file mode 100644 index 0000000000..5ef4e5062d --- /dev/null +++ b/crypto/third_party/nss/sha512.cc @@ -0,0 +1,1391 @@ +/* + * sha512.c - implementation of SHA256, SHA384 and SHA512 + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 2002 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +/* $Id: sha512.c,v 1.9 2006/10/13 16:54:04 wtchang%redhat.com Exp $ */ + +// Prevent manual unrolling in the sha256 code, which reduces the binary code +// size from ~10k to ~1k. The performance should be reasonable for our use. +#define NOUNROLL256 1 + +#include "crypto/third_party/nss/chromium-prtypes.h" /* for PRUintXX */ +#if defined(_X86_) || defined(SHA_NO_LONG_LONG) +#define NOUNROLL512 1 +#undef HAVE_LONG_LONG +#endif +#include "crypto/third_party/nss/chromium-blapi.h" +#include "crypto/third_party/nss/chromium-sha256.h" /* for struct SHA256ContextStr */ + +#include <stdlib.h> +#include <string.h> +#define PORT_New(type) static_cast<type*>(malloc(sizeof(type))) +#define PORT_ZFree(ptr, len) do { memset(ptr, 0, len); free(ptr); } while (0) +#define PORT_Strlen(s) static_cast<unsigned int>(strlen(s)) +#define PORT_Memcpy memcpy + +/* ============= Common constants and defines ======================= */ + +#define W ctx->u.w +#define B ctx->u.b +#define H ctx->h + +#define SHR(x,n) (x >> n) +#define SHL(x,n) (x << n) +#define Ch(x,y,z) ((x & y) ^ (~x & z)) +#define Maj(x,y,z) ((x & y) ^ (x & z) ^ (y & z)) + +/* Padding used with all flavors of SHA */ +static const PRUint8 pad[240] = { +0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + /* compiler will fill the rest in with zeros */ +}; + +/* ============= SHA256 implemenmtation ================================== */ + +/* SHA-256 constants, K256. */ +static const PRUint32 K256[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +/* SHA-256 initial hash values */ +static const PRUint32 H256[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +#if defined(_MSC_VER) && defined(_X86_) +#ifndef FORCEINLINE +#if (_MSC_VER >= 1200) +#define FORCEINLINE __forceinline +#else +#define FORCEINLINE __inline +#endif +#endif +#define FASTCALL __fastcall + +static FORCEINLINE PRUint32 FASTCALL +swap4b(PRUint32 dwd) +{ + __asm { + mov eax,dwd + bswap eax + } +} + +#define SHA_HTONL(x) swap4b(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) + +#elif defined(LINUX) && defined(_X86_) +#undef __OPTIMIZE__ +#define __OPTIMIZE__ 1 +#undef __pentium__ +#define __pentium__ 1 +#include <byteswap.h> +#define SHA_HTONL(x) bswap_32(x) +#define BYTESWAP4(x) x = SHA_HTONL(x) + +#else /* neither windows nor Linux PC */ +#define SWAP4MASK 0x00FF00FF +#define SHA_HTONL(x) (t1 = (x), t1 = (t1 << 16) | (t1 >> 16), \ + ((t1 & SWAP4MASK) << 8) | ((t1 >> 8) & SWAP4MASK)) +#define BYTESWAP4(x) x = SHA_HTONL(x) +#endif + +#if defined(_MSC_VER) && defined(_X86_) +#pragma intrinsic (_lrotr, _lrotl) +#define ROTR32(x,n) _lrotr(x,n) +#define ROTL32(x,n) _lrotl(x,n) +#else +#define ROTR32(x,n) ((x >> n) | (x << ((8 * sizeof x) - n))) +#define ROTL32(x,n) ((x << n) | (x >> ((8 * sizeof x) - n))) +#endif + +/* Capitol Sigma and lower case sigma functions */ +#define S0(x) (ROTR32(x, 2) ^ ROTR32(x,13) ^ ROTR32(x,22)) +#define S1(x) (ROTR32(x, 6) ^ ROTR32(x,11) ^ ROTR32(x,25)) +#define s0(x) (t1 = x, ROTR32(t1, 7) ^ ROTR32(t1,18) ^ SHR(t1, 3)) +#define s1(x) (t2 = x, ROTR32(t2,17) ^ ROTR32(t2,19) ^ SHR(t2,10)) + +SHA256Context * +SHA256_NewContext(void) +{ + SHA256Context *ctx = PORT_New(SHA256Context); + return ctx; +} + +void +SHA256_DestroyContext(SHA256Context *ctx, PRBool freeit) +{ + if (freeit) { + PORT_ZFree(ctx, sizeof *ctx); + } +} + +void +SHA256_Begin(SHA256Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H256, sizeof H256); +} + +static void +SHA256_Compress(SHA256Context *ctx) +{ + { + register PRUint32 t1, t2; + +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(W[0]); + BYTESWAP4(W[1]); + BYTESWAP4(W[2]); + BYTESWAP4(W[3]); + BYTESWAP4(W[4]); + BYTESWAP4(W[5]); + BYTESWAP4(W[6]); + BYTESWAP4(W[7]); + BYTESWAP4(W[8]); + BYTESWAP4(W[9]); + BYTESWAP4(W[10]); + BYTESWAP4(W[11]); + BYTESWAP4(W[12]); + BYTESWAP4(W[13]); + BYTESWAP4(W[14]); + BYTESWAP4(W[15]); +#endif + +#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16]) + + /* prepare the "message schedule" */ +#ifdef NOUNROLL256 + { + int t; + for (t = 16; t < 64; ++t) { + INITW(t); + } + } +#else + INITW(16); + INITW(17); + INITW(18); + INITW(19); + + INITW(20); + INITW(21); + INITW(22); + INITW(23); + INITW(24); + INITW(25); + INITW(26); + INITW(27); + INITW(28); + INITW(29); + + INITW(30); + INITW(31); + INITW(32); + INITW(33); + INITW(34); + INITW(35); + INITW(36); + INITW(37); + INITW(38); + INITW(39); + + INITW(40); + INITW(41); + INITW(42); + INITW(43); + INITW(44); + INITW(45); + INITW(46); + INITW(47); + INITW(48); + INITW(49); + + INITW(50); + INITW(51); + INITW(52); + INITW(53); + INITW(54); + INITW(55); + INITW(56); + INITW(57); + INITW(58); + INITW(59); + + INITW(60); + INITW(61); + INITW(62); + INITW(63); + +#endif +#undef INITW + } + { + PRUint32 a, b, c, d, e, f, g, h; + + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + h += S1(e) + Ch(e,f,g) + K256[n] + W[n]; \ + d += h; \ + h += S0(a) + Maj(a,b,c); + +#ifdef NOUNROLL256 + { + int t; + for (t = 0; t < 64; t+= 8) { + ROUND(t+0,a,b,c,d,e,f,g,h) + ROUND(t+1,h,a,b,c,d,e,f,g) + ROUND(t+2,g,h,a,b,c,d,e,f) + ROUND(t+3,f,g,h,a,b,c,d,e) + ROUND(t+4,e,f,g,h,a,b,c,d) + ROUND(t+5,d,e,f,g,h,a,b,c) + ROUND(t+6,c,d,e,f,g,h,a,b) + ROUND(t+7,b,c,d,e,f,g,h,a) + } + } +#else + ROUND( 0,a,b,c,d,e,f,g,h) + ROUND( 1,h,a,b,c,d,e,f,g) + ROUND( 2,g,h,a,b,c,d,e,f) + ROUND( 3,f,g,h,a,b,c,d,e) + ROUND( 4,e,f,g,h,a,b,c,d) + ROUND( 5,d,e,f,g,h,a,b,c) + ROUND( 6,c,d,e,f,g,h,a,b) + ROUND( 7,b,c,d,e,f,g,h,a) + + ROUND( 8,a,b,c,d,e,f,g,h) + ROUND( 9,h,a,b,c,d,e,f,g) + ROUND(10,g,h,a,b,c,d,e,f) + ROUND(11,f,g,h,a,b,c,d,e) + ROUND(12,e,f,g,h,a,b,c,d) + ROUND(13,d,e,f,g,h,a,b,c) + ROUND(14,c,d,e,f,g,h,a,b) + ROUND(15,b,c,d,e,f,g,h,a) + + ROUND(16,a,b,c,d,e,f,g,h) + ROUND(17,h,a,b,c,d,e,f,g) + ROUND(18,g,h,a,b,c,d,e,f) + ROUND(19,f,g,h,a,b,c,d,e) + ROUND(20,e,f,g,h,a,b,c,d) + ROUND(21,d,e,f,g,h,a,b,c) + ROUND(22,c,d,e,f,g,h,a,b) + ROUND(23,b,c,d,e,f,g,h,a) + + ROUND(24,a,b,c,d,e,f,g,h) + ROUND(25,h,a,b,c,d,e,f,g) + ROUND(26,g,h,a,b,c,d,e,f) + ROUND(27,f,g,h,a,b,c,d,e) + ROUND(28,e,f,g,h,a,b,c,d) + ROUND(29,d,e,f,g,h,a,b,c) + ROUND(30,c,d,e,f,g,h,a,b) + ROUND(31,b,c,d,e,f,g,h,a) + + ROUND(32,a,b,c,d,e,f,g,h) + ROUND(33,h,a,b,c,d,e,f,g) + ROUND(34,g,h,a,b,c,d,e,f) + ROUND(35,f,g,h,a,b,c,d,e) + ROUND(36,e,f,g,h,a,b,c,d) + ROUND(37,d,e,f,g,h,a,b,c) + ROUND(38,c,d,e,f,g,h,a,b) + ROUND(39,b,c,d,e,f,g,h,a) + + ROUND(40,a,b,c,d,e,f,g,h) + ROUND(41,h,a,b,c,d,e,f,g) + ROUND(42,g,h,a,b,c,d,e,f) + ROUND(43,f,g,h,a,b,c,d,e) + ROUND(44,e,f,g,h,a,b,c,d) + ROUND(45,d,e,f,g,h,a,b,c) + ROUND(46,c,d,e,f,g,h,a,b) + ROUND(47,b,c,d,e,f,g,h,a) + + ROUND(48,a,b,c,d,e,f,g,h) + ROUND(49,h,a,b,c,d,e,f,g) + ROUND(50,g,h,a,b,c,d,e,f) + ROUND(51,f,g,h,a,b,c,d,e) + ROUND(52,e,f,g,h,a,b,c,d) + ROUND(53,d,e,f,g,h,a,b,c) + ROUND(54,c,d,e,f,g,h,a,b) + ROUND(55,b,c,d,e,f,g,h,a) + + ROUND(56,a,b,c,d,e,f,g,h) + ROUND(57,h,a,b,c,d,e,f,g) + ROUND(58,g,h,a,b,c,d,e,f) + ROUND(59,f,g,h,a,b,c,d,e) + ROUND(60,e,f,g,h,a,b,c,d) + ROUND(61,d,e,f,g,h,a,b,c) + ROUND(62,c,d,e,f,g,h,a,b) + ROUND(63,b,c,d,e,f,g,h,a) +#endif + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; + } +#undef ROUND +} + +#undef s0 +#undef s1 +#undef S0 +#undef S1 + +void +SHA256_Update(SHA256Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + unsigned int inBuf = ctx->sizeLo & 0x3f; + if (!inputLen) + return; + + /* Add inputLen into the count of bytes processed, before processing */ + if ((ctx->sizeLo += inputLen) < inputLen) + ctx->sizeHi++; + + /* if data already in buffer, attemp to fill rest of buffer */ + if (inBuf) { + unsigned int todo = SHA256_BLOCK_LENGTH - inBuf; + if (inputLen < todo) + todo = inputLen; + memcpy(B + inBuf, input, todo); + input += todo; + inputLen -= todo; + if (inBuf + todo == SHA256_BLOCK_LENGTH) + SHA256_Compress(ctx); + } + + /* if enough data to fill one or more whole buffers, process them. */ + while (inputLen >= SHA256_BLOCK_LENGTH) { + memcpy(B, input, SHA256_BLOCK_LENGTH); + input += SHA256_BLOCK_LENGTH; + inputLen -= SHA256_BLOCK_LENGTH; + SHA256_Compress(ctx); + } + /* if data left over, fill it into buffer */ + if (inputLen) + memcpy(B, input, inputLen); +} + +void +SHA256_End(SHA256Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ + unsigned int inBuf = ctx->sizeLo & 0x3f; + unsigned int padLen = (inBuf < 56) ? (56 - inBuf) : (56 + 64 - inBuf); + PRUint32 hi, lo; +#ifdef SWAP4MASK + PRUint32 t1; +#endif + + hi = (ctx->sizeHi << 3) | (ctx->sizeLo >> 29); + lo = (ctx->sizeLo << 3); + + SHA256_Update(ctx, pad, padLen); + +#if defined(IS_LITTLE_ENDIAN) + W[14] = SHA_HTONL(hi); + W[15] = SHA_HTONL(lo); +#else + W[14] = hi; + W[15] = lo; +#endif + SHA256_Compress(ctx); + + /* now output the answer */ +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP4(H[0]); + BYTESWAP4(H[1]); + BYTESWAP4(H[2]); + BYTESWAP4(H[3]); + BYTESWAP4(H[4]); + BYTESWAP4(H[5]); + BYTESWAP4(H[6]); + BYTESWAP4(H[7]); +#endif + padLen = PR_MIN(SHA256_LENGTH, maxDigestLen); + memcpy(digest, H, padLen); + if (digestLen) + *digestLen = padLen; +} + +/* Comment out unused code, mostly the SHA384 and SHA512 implementations. */ +#if 0 +SECStatus +SHA256_HashBuf(unsigned char *dest, const unsigned char *src, + unsigned int src_length) +{ + SHA256Context ctx; + unsigned int outLen; + + SHA256_Begin(&ctx); + SHA256_Update(&ctx, src, src_length); + SHA256_End(&ctx, dest, &outLen, SHA256_LENGTH); + + return SECSuccess; +} + + +SECStatus +SHA256_Hash(unsigned char *dest, const char *src) +{ + return SHA256_HashBuf(dest, (const unsigned char *)src, PORT_Strlen(src)); +} + + +void SHA256_TraceState(SHA256Context *ctx) { } + +unsigned int +SHA256_FlattenSize(SHA256Context *ctx) +{ + return sizeof *ctx; +} + +SECStatus +SHA256_Flatten(SHA256Context *ctx,unsigned char *space) +{ + PORT_Memcpy(space, ctx, sizeof *ctx); + return SECSuccess; +} + +SHA256Context * +SHA256_Resurrect(unsigned char *space, void *arg) +{ + SHA256Context *ctx = SHA256_NewContext(); + if (ctx) + PORT_Memcpy(ctx, space, sizeof *ctx); + return ctx; +} + +void SHA256_Clone(SHA256Context *dest, SHA256Context *src) +{ + memcpy(dest, src, sizeof *dest); +} + + +/* ======= SHA512 and SHA384 common constants and defines ================= */ + +/* common #defines for SHA512 and SHA384 */ +#if defined(HAVE_LONG_LONG) +#define ROTR64(x,n) ((x >> n) | (x << (64 - n))) +#define ROTL64(x,n) ((x << n) | (x >> (64 - n))) + +#define S0(x) (ROTR64(x,28) ^ ROTR64(x,34) ^ ROTR64(x,39)) +#define S1(x) (ROTR64(x,14) ^ ROTR64(x,18) ^ ROTR64(x,41)) +#define s0(x) (t1 = x, ROTR64(t1, 1) ^ ROTR64(t1, 8) ^ SHR(t1,7)) +#define s1(x) (t2 = x, ROTR64(t2,19) ^ ROTR64(t2,61) ^ SHR(t2,6)) + +#if PR_BYTES_PER_LONG == 8 +#define ULLC(hi,lo) 0x ## hi ## lo ## UL +#elif defined(_MSC_VER) +#define ULLC(hi,lo) 0x ## hi ## lo ## ui64 +#else +#define ULLC(hi,lo) 0x ## hi ## lo ## ULL +#endif + +#define SHA_MASK16 ULLC(0000FFFF,0000FFFF) +#define SHA_MASK8 ULLC(00FF00FF,00FF00FF) +#define SHA_HTONLL(x) (t1 = x, \ + t1 = ((t1 & SHA_MASK8 ) << 8) | ((t1 >> 8) & SHA_MASK8 ), \ + t1 = ((t1 & SHA_MASK16) << 16) | ((t1 >> 16) & SHA_MASK16), \ + (t1 >> 32) | (t1 << 32)) +#define BYTESWAP8(x) x = SHA_HTONLL(x) + +#else /* no long long */ + +#if defined(IS_LITTLE_ENDIAN) +#define ULLC(hi,lo) { 0x ## lo ## U, 0x ## hi ## U } +#else +#define ULLC(hi,lo) { 0x ## hi ## U, 0x ## lo ## U } +#endif + +#define SHA_HTONLL(x) ( BYTESWAP4(x.lo), BYTESWAP4(x.hi), \ + x.hi ^= x.lo ^= x.hi ^= x.lo, x) +#define BYTESWAP8(x) do { PRUint32 tmp; BYTESWAP4(x.lo); BYTESWAP4(x.hi); \ + tmp = x.lo; x.lo = x.hi; x.hi = tmp; } while (0) +#endif + +/* SHA-384 and SHA-512 constants, K512. */ +static const PRUint64 K512[80] = { +#if PR_BYTES_PER_LONG == 8 + 0x428a2f98d728ae22UL , 0x7137449123ef65cdUL , + 0xb5c0fbcfec4d3b2fUL , 0xe9b5dba58189dbbcUL , + 0x3956c25bf348b538UL , 0x59f111f1b605d019UL , + 0x923f82a4af194f9bUL , 0xab1c5ed5da6d8118UL , + 0xd807aa98a3030242UL , 0x12835b0145706fbeUL , + 0x243185be4ee4b28cUL , 0x550c7dc3d5ffb4e2UL , + 0x72be5d74f27b896fUL , 0x80deb1fe3b1696b1UL , + 0x9bdc06a725c71235UL , 0xc19bf174cf692694UL , + 0xe49b69c19ef14ad2UL , 0xefbe4786384f25e3UL , + 0x0fc19dc68b8cd5b5UL , 0x240ca1cc77ac9c65UL , + 0x2de92c6f592b0275UL , 0x4a7484aa6ea6e483UL , + 0x5cb0a9dcbd41fbd4UL , 0x76f988da831153b5UL , + 0x983e5152ee66dfabUL , 0xa831c66d2db43210UL , + 0xb00327c898fb213fUL , 0xbf597fc7beef0ee4UL , + 0xc6e00bf33da88fc2UL , 0xd5a79147930aa725UL , + 0x06ca6351e003826fUL , 0x142929670a0e6e70UL , + 0x27b70a8546d22ffcUL , 0x2e1b21385c26c926UL , + 0x4d2c6dfc5ac42aedUL , 0x53380d139d95b3dfUL , + 0x650a73548baf63deUL , 0x766a0abb3c77b2a8UL , + 0x81c2c92e47edaee6UL , 0x92722c851482353bUL , + 0xa2bfe8a14cf10364UL , 0xa81a664bbc423001UL , + 0xc24b8b70d0f89791UL , 0xc76c51a30654be30UL , + 0xd192e819d6ef5218UL , 0xd69906245565a910UL , + 0xf40e35855771202aUL , 0x106aa07032bbd1b8UL , + 0x19a4c116b8d2d0c8UL , 0x1e376c085141ab53UL , + 0x2748774cdf8eeb99UL , 0x34b0bcb5e19b48a8UL , + 0x391c0cb3c5c95a63UL , 0x4ed8aa4ae3418acbUL , + 0x5b9cca4f7763e373UL , 0x682e6ff3d6b2b8a3UL , + 0x748f82ee5defb2fcUL , 0x78a5636f43172f60UL , + 0x84c87814a1f0ab72UL , 0x8cc702081a6439ecUL , + 0x90befffa23631e28UL , 0xa4506cebde82bde9UL , + 0xbef9a3f7b2c67915UL , 0xc67178f2e372532bUL , + 0xca273eceea26619cUL , 0xd186b8c721c0c207UL , + 0xeada7dd6cde0eb1eUL , 0xf57d4f7fee6ed178UL , + 0x06f067aa72176fbaUL , 0x0a637dc5a2c898a6UL , + 0x113f9804bef90daeUL , 0x1b710b35131c471bUL , + 0x28db77f523047d84UL , 0x32caab7b40c72493UL , + 0x3c9ebe0a15c9bebcUL , 0x431d67c49c100d4cUL , + 0x4cc5d4becb3e42b6UL , 0x597f299cfc657e2aUL , + 0x5fcb6fab3ad6faecUL , 0x6c44198c4a475817UL +#else + ULLC(428a2f98,d728ae22), ULLC(71374491,23ef65cd), + ULLC(b5c0fbcf,ec4d3b2f), ULLC(e9b5dba5,8189dbbc), + ULLC(3956c25b,f348b538), ULLC(59f111f1,b605d019), + ULLC(923f82a4,af194f9b), ULLC(ab1c5ed5,da6d8118), + ULLC(d807aa98,a3030242), ULLC(12835b01,45706fbe), + ULLC(243185be,4ee4b28c), ULLC(550c7dc3,d5ffb4e2), + ULLC(72be5d74,f27b896f), ULLC(80deb1fe,3b1696b1), + ULLC(9bdc06a7,25c71235), ULLC(c19bf174,cf692694), + ULLC(e49b69c1,9ef14ad2), ULLC(efbe4786,384f25e3), + ULLC(0fc19dc6,8b8cd5b5), ULLC(240ca1cc,77ac9c65), + ULLC(2de92c6f,592b0275), ULLC(4a7484aa,6ea6e483), + ULLC(5cb0a9dc,bd41fbd4), ULLC(76f988da,831153b5), + ULLC(983e5152,ee66dfab), ULLC(a831c66d,2db43210), + ULLC(b00327c8,98fb213f), ULLC(bf597fc7,beef0ee4), + ULLC(c6e00bf3,3da88fc2), ULLC(d5a79147,930aa725), + ULLC(06ca6351,e003826f), ULLC(14292967,0a0e6e70), + ULLC(27b70a85,46d22ffc), ULLC(2e1b2138,5c26c926), + ULLC(4d2c6dfc,5ac42aed), ULLC(53380d13,9d95b3df), + ULLC(650a7354,8baf63de), ULLC(766a0abb,3c77b2a8), + ULLC(81c2c92e,47edaee6), ULLC(92722c85,1482353b), + ULLC(a2bfe8a1,4cf10364), ULLC(a81a664b,bc423001), + ULLC(c24b8b70,d0f89791), ULLC(c76c51a3,0654be30), + ULLC(d192e819,d6ef5218), ULLC(d6990624,5565a910), + ULLC(f40e3585,5771202a), ULLC(106aa070,32bbd1b8), + ULLC(19a4c116,b8d2d0c8), ULLC(1e376c08,5141ab53), + ULLC(2748774c,df8eeb99), ULLC(34b0bcb5,e19b48a8), + ULLC(391c0cb3,c5c95a63), ULLC(4ed8aa4a,e3418acb), + ULLC(5b9cca4f,7763e373), ULLC(682e6ff3,d6b2b8a3), + ULLC(748f82ee,5defb2fc), ULLC(78a5636f,43172f60), + ULLC(84c87814,a1f0ab72), ULLC(8cc70208,1a6439ec), + ULLC(90befffa,23631e28), ULLC(a4506ceb,de82bde9), + ULLC(bef9a3f7,b2c67915), ULLC(c67178f2,e372532b), + ULLC(ca273ece,ea26619c), ULLC(d186b8c7,21c0c207), + ULLC(eada7dd6,cde0eb1e), ULLC(f57d4f7f,ee6ed178), + ULLC(06f067aa,72176fba), ULLC(0a637dc5,a2c898a6), + ULLC(113f9804,bef90dae), ULLC(1b710b35,131c471b), + ULLC(28db77f5,23047d84), ULLC(32caab7b,40c72493), + ULLC(3c9ebe0a,15c9bebc), ULLC(431d67c4,9c100d4c), + ULLC(4cc5d4be,cb3e42b6), ULLC(597f299c,fc657e2a), + ULLC(5fcb6fab,3ad6faec), ULLC(6c44198c,4a475817) +#endif +}; + +struct SHA512ContextStr { + union { + PRUint64 w[80]; /* message schedule, input buffer, plus 64 words */ + PRUint32 l[160]; + PRUint8 b[640]; + } u; + PRUint64 h[8]; /* 8 state variables */ + PRUint64 sizeLo; /* 64-bit count of hashed bytes. */ +}; + +/* =========== SHA512 implementation ===================================== */ + +/* SHA-512 initial hash values */ +static const PRUint64 H512[8] = { +#if PR_BYTES_PER_LONG == 8 + 0x6a09e667f3bcc908UL , 0xbb67ae8584caa73bUL , + 0x3c6ef372fe94f82bUL , 0xa54ff53a5f1d36f1UL , + 0x510e527fade682d1UL , 0x9b05688c2b3e6c1fUL , + 0x1f83d9abfb41bd6bUL , 0x5be0cd19137e2179UL +#else + ULLC(6a09e667,f3bcc908), ULLC(bb67ae85,84caa73b), + ULLC(3c6ef372,fe94f82b), ULLC(a54ff53a,5f1d36f1), + ULLC(510e527f,ade682d1), ULLC(9b05688c,2b3e6c1f), + ULLC(1f83d9ab,fb41bd6b), ULLC(5be0cd19,137e2179) +#endif +}; + + +SHA512Context * +SHA512_NewContext(void) +{ + SHA512Context *ctx = PORT_New(SHA512Context); + return ctx; +} + +void +SHA512_DestroyContext(SHA512Context *ctx, PRBool freeit) +{ + if (freeit) { + PORT_ZFree(ctx, sizeof *ctx); + } +} + +void +SHA512_Begin(SHA512Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H512, sizeof H512); +} + +#if defined(SHA512_TRACE) +#if defined(HAVE_LONG_LONG) +#define DUMP(n,a,d,e,h) printf(" t = %2d, %s = %016lx, %s = %016lx\n", \ + n, #e, d, #a, h); +#else +#define DUMP(n,a,d,e,h) printf(" t = %2d, %s = %08x%08x, %s = %08x%08x\n", \ + n, #e, d.hi, d.lo, #a, h.hi, h.lo); +#endif +#else +#define DUMP(n,a,d,e,h) +#endif + +#if defined(HAVE_LONG_LONG) + +#define ADDTO(x,y) y += x + +#define INITW(t) W[t] = (s1(W[t-2]) + W[t-7] + s0(W[t-15]) + W[t-16]) + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + h += S1(e) + Ch(e,f,g) + K512[n] + W[n]; \ + d += h; \ + h += S0(a) + Maj(a,b,c); \ + DUMP(n,a,d,e,h) + +#else /* use only 32-bit variables, and don't unroll loops */ + +#undef NOUNROLL512 +#define NOUNROLL512 1 + +#define ADDTO(x,y) y.lo += x.lo; y.hi += x.hi + (x.lo > y.lo) + +#define ROTR64a(x,n,lo,hi) (x.lo >> n | x.hi << (32-n)) +#define ROTR64A(x,n,lo,hi) (x.lo << (64-n) | x.hi >> (n-32)) +#define SHR64a(x,n,lo,hi) (x.lo >> n | x.hi << (32-n)) + +/* Capitol Sigma and lower case sigma functions */ +#define s0lo(x) (ROTR64a(x,1,lo,hi) ^ ROTR64a(x,8,lo,hi) ^ SHR64a(x,7,lo,hi)) +#define s0hi(x) (ROTR64a(x,1,hi,lo) ^ ROTR64a(x,8,hi,lo) ^ (x.hi >> 7)) + +#define s1lo(x) (ROTR64a(x,19,lo,hi) ^ ROTR64A(x,61,lo,hi) ^ SHR64a(x,6,lo,hi)) +#define s1hi(x) (ROTR64a(x,19,hi,lo) ^ ROTR64A(x,61,hi,lo) ^ (x.hi >> 6)) + +#define S0lo(x)(ROTR64a(x,28,lo,hi) ^ ROTR64A(x,34,lo,hi) ^ ROTR64A(x,39,lo,hi)) +#define S0hi(x)(ROTR64a(x,28,hi,lo) ^ ROTR64A(x,34,hi,lo) ^ ROTR64A(x,39,hi,lo)) + +#define S1lo(x)(ROTR64a(x,14,lo,hi) ^ ROTR64a(x,18,lo,hi) ^ ROTR64A(x,41,lo,hi)) +#define S1hi(x)(ROTR64a(x,14,hi,lo) ^ ROTR64a(x,18,hi,lo) ^ ROTR64A(x,41,hi,lo)) + +/* 32-bit versions of Ch and Maj */ +#define Chxx(x,y,z,lo) ((x.lo & y.lo) ^ (~x.lo & z.lo)) +#define Majx(x,y,z,lo) ((x.lo & y.lo) ^ (x.lo & z.lo) ^ (y.lo & z.lo)) + +#define INITW(t) \ + do { \ + PRUint32 lo, tm; \ + PRUint32 cy = 0; \ + lo = s1lo(W[t-2]); \ + lo += (tm = W[t-7].lo); if (lo < tm) cy++; \ + lo += (tm = s0lo(W[t-15])); if (lo < tm) cy++; \ + lo += (tm = W[t-16].lo); if (lo < tm) cy++; \ + W[t].lo = lo; \ + W[t].hi = cy + s1hi(W[t-2]) + W[t-7].hi + s0hi(W[t-15]) + W[t-16].hi; \ + } while (0) + +#define ROUND(n,a,b,c,d,e,f,g,h) \ + { \ + PRUint32 lo, tm, cy; \ + lo = S1lo(e); \ + lo += (tm = Chxx(e,f,g,lo)); cy = (lo < tm); \ + lo += (tm = K512[n].lo); if (lo < tm) cy++; \ + lo += (tm = W[n].lo); if (lo < tm) cy++; \ + h.lo += lo; if (h.lo < lo) cy++; \ + h.hi += cy + S1hi(e) + Chxx(e,f,g,hi) + K512[n].hi + W[n].hi; \ + d.lo += h.lo; \ + d.hi += h.hi + (d.lo < h.lo); \ + lo = S0lo(a); \ + lo += (tm = Majx(a,b,c,lo)); cy = (lo < tm); \ + h.lo += lo; if (h.lo < lo) cy++; \ + h.hi += cy + S0hi(a) + Majx(a,b,c,hi); \ + DUMP(n,a,d,e,h) \ + } +#endif + +static void +SHA512_Compress(SHA512Context *ctx) +{ +#if defined(IS_LITTLE_ENDIAN) + { +#if defined(HAVE_LONG_LONG) + PRUint64 t1; +#else + PRUint32 t1; +#endif + BYTESWAP8(W[0]); + BYTESWAP8(W[1]); + BYTESWAP8(W[2]); + BYTESWAP8(W[3]); + BYTESWAP8(W[4]); + BYTESWAP8(W[5]); + BYTESWAP8(W[6]); + BYTESWAP8(W[7]); + BYTESWAP8(W[8]); + BYTESWAP8(W[9]); + BYTESWAP8(W[10]); + BYTESWAP8(W[11]); + BYTESWAP8(W[12]); + BYTESWAP8(W[13]); + BYTESWAP8(W[14]); + BYTESWAP8(W[15]); + } +#endif + + { + PRUint64 t1, t2; +#ifdef NOUNROLL512 + { + /* prepare the "message schedule" */ + int t; + for (t = 16; t < 80; ++t) { + INITW(t); + } + } +#else + INITW(16); + INITW(17); + INITW(18); + INITW(19); + + INITW(20); + INITW(21); + INITW(22); + INITW(23); + INITW(24); + INITW(25); + INITW(26); + INITW(27); + INITW(28); + INITW(29); + + INITW(30); + INITW(31); + INITW(32); + INITW(33); + INITW(34); + INITW(35); + INITW(36); + INITW(37); + INITW(38); + INITW(39); + + INITW(40); + INITW(41); + INITW(42); + INITW(43); + INITW(44); + INITW(45); + INITW(46); + INITW(47); + INITW(48); + INITW(49); + + INITW(50); + INITW(51); + INITW(52); + INITW(53); + INITW(54); + INITW(55); + INITW(56); + INITW(57); + INITW(58); + INITW(59); + + INITW(60); + INITW(61); + INITW(62); + INITW(63); + INITW(64); + INITW(65); + INITW(66); + INITW(67); + INITW(68); + INITW(69); + + INITW(70); + INITW(71); + INITW(72); + INITW(73); + INITW(74); + INITW(75); + INITW(76); + INITW(77); + INITW(78); + INITW(79); +#endif + } +#ifdef SHA512_TRACE + { + int i; + for (i = 0; i < 80; ++i) { +#ifdef HAVE_LONG_LONG + printf("W[%2d] = %016lx\n", i, W[i]); +#else + printf("W[%2d] = %08x%08x\n", i, W[i].hi, W[i].lo); +#endif + } + } +#endif + { + PRUint64 a, b, c, d, e, f, g, h; + + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + +#ifdef NOUNROLL512 + { + int t; + for (t = 0; t < 80; t+= 8) { + ROUND(t+0,a,b,c,d,e,f,g,h) + ROUND(t+1,h,a,b,c,d,e,f,g) + ROUND(t+2,g,h,a,b,c,d,e,f) + ROUND(t+3,f,g,h,a,b,c,d,e) + ROUND(t+4,e,f,g,h,a,b,c,d) + ROUND(t+5,d,e,f,g,h,a,b,c) + ROUND(t+6,c,d,e,f,g,h,a,b) + ROUND(t+7,b,c,d,e,f,g,h,a) + } + } +#else + ROUND( 0,a,b,c,d,e,f,g,h) + ROUND( 1,h,a,b,c,d,e,f,g) + ROUND( 2,g,h,a,b,c,d,e,f) + ROUND( 3,f,g,h,a,b,c,d,e) + ROUND( 4,e,f,g,h,a,b,c,d) + ROUND( 5,d,e,f,g,h,a,b,c) + ROUND( 6,c,d,e,f,g,h,a,b) + ROUND( 7,b,c,d,e,f,g,h,a) + + ROUND( 8,a,b,c,d,e,f,g,h) + ROUND( 9,h,a,b,c,d,e,f,g) + ROUND(10,g,h,a,b,c,d,e,f) + ROUND(11,f,g,h,a,b,c,d,e) + ROUND(12,e,f,g,h,a,b,c,d) + ROUND(13,d,e,f,g,h,a,b,c) + ROUND(14,c,d,e,f,g,h,a,b) + ROUND(15,b,c,d,e,f,g,h,a) + + ROUND(16,a,b,c,d,e,f,g,h) + ROUND(17,h,a,b,c,d,e,f,g) + ROUND(18,g,h,a,b,c,d,e,f) + ROUND(19,f,g,h,a,b,c,d,e) + ROUND(20,e,f,g,h,a,b,c,d) + ROUND(21,d,e,f,g,h,a,b,c) + ROUND(22,c,d,e,f,g,h,a,b) + ROUND(23,b,c,d,e,f,g,h,a) + + ROUND(24,a,b,c,d,e,f,g,h) + ROUND(25,h,a,b,c,d,e,f,g) + ROUND(26,g,h,a,b,c,d,e,f) + ROUND(27,f,g,h,a,b,c,d,e) + ROUND(28,e,f,g,h,a,b,c,d) + ROUND(29,d,e,f,g,h,a,b,c) + ROUND(30,c,d,e,f,g,h,a,b) + ROUND(31,b,c,d,e,f,g,h,a) + + ROUND(32,a,b,c,d,e,f,g,h) + ROUND(33,h,a,b,c,d,e,f,g) + ROUND(34,g,h,a,b,c,d,e,f) + ROUND(35,f,g,h,a,b,c,d,e) + ROUND(36,e,f,g,h,a,b,c,d) + ROUND(37,d,e,f,g,h,a,b,c) + ROUND(38,c,d,e,f,g,h,a,b) + ROUND(39,b,c,d,e,f,g,h,a) + + ROUND(40,a,b,c,d,e,f,g,h) + ROUND(41,h,a,b,c,d,e,f,g) + ROUND(42,g,h,a,b,c,d,e,f) + ROUND(43,f,g,h,a,b,c,d,e) + ROUND(44,e,f,g,h,a,b,c,d) + ROUND(45,d,e,f,g,h,a,b,c) + ROUND(46,c,d,e,f,g,h,a,b) + ROUND(47,b,c,d,e,f,g,h,a) + + ROUND(48,a,b,c,d,e,f,g,h) + ROUND(49,h,a,b,c,d,e,f,g) + ROUND(50,g,h,a,b,c,d,e,f) + ROUND(51,f,g,h,a,b,c,d,e) + ROUND(52,e,f,g,h,a,b,c,d) + ROUND(53,d,e,f,g,h,a,b,c) + ROUND(54,c,d,e,f,g,h,a,b) + ROUND(55,b,c,d,e,f,g,h,a) + + ROUND(56,a,b,c,d,e,f,g,h) + ROUND(57,h,a,b,c,d,e,f,g) + ROUND(58,g,h,a,b,c,d,e,f) + ROUND(59,f,g,h,a,b,c,d,e) + ROUND(60,e,f,g,h,a,b,c,d) + ROUND(61,d,e,f,g,h,a,b,c) + ROUND(62,c,d,e,f,g,h,a,b) + ROUND(63,b,c,d,e,f,g,h,a) + + ROUND(64,a,b,c,d,e,f,g,h) + ROUND(65,h,a,b,c,d,e,f,g) + ROUND(66,g,h,a,b,c,d,e,f) + ROUND(67,f,g,h,a,b,c,d,e) + ROUND(68,e,f,g,h,a,b,c,d) + ROUND(69,d,e,f,g,h,a,b,c) + ROUND(70,c,d,e,f,g,h,a,b) + ROUND(71,b,c,d,e,f,g,h,a) + + ROUND(72,a,b,c,d,e,f,g,h) + ROUND(73,h,a,b,c,d,e,f,g) + ROUND(74,g,h,a,b,c,d,e,f) + ROUND(75,f,g,h,a,b,c,d,e) + ROUND(76,e,f,g,h,a,b,c,d) + ROUND(77,d,e,f,g,h,a,b,c) + ROUND(78,c,d,e,f,g,h,a,b) + ROUND(79,b,c,d,e,f,g,h,a) +#endif + + ADDTO(a,H[0]); + ADDTO(b,H[1]); + ADDTO(c,H[2]); + ADDTO(d,H[3]); + ADDTO(e,H[4]); + ADDTO(f,H[5]); + ADDTO(g,H[6]); + ADDTO(h,H[7]); + } +} + +void +SHA512_Update(SHA512Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + unsigned int inBuf; + if (!inputLen) + return; + +#if defined(HAVE_LONG_LONG) + inBuf = (unsigned int)ctx->sizeLo & 0x7f; + /* Add inputLen into the count of bytes processed, before processing */ + ctx->sizeLo += inputLen; +#else + inBuf = (unsigned int)ctx->sizeLo.lo & 0x7f; + ctx->sizeLo.lo += inputLen; + if (ctx->sizeLo.lo < inputLen) ctx->sizeLo.hi++; +#endif + + /* if data already in buffer, attemp to fill rest of buffer */ + if (inBuf) { + unsigned int todo = SHA512_BLOCK_LENGTH - inBuf; + if (inputLen < todo) + todo = inputLen; + memcpy(B + inBuf, input, todo); + input += todo; + inputLen -= todo; + if (inBuf + todo == SHA512_BLOCK_LENGTH) + SHA512_Compress(ctx); + } + + /* if enough data to fill one or more whole buffers, process them. */ + while (inputLen >= SHA512_BLOCK_LENGTH) { + memcpy(B, input, SHA512_BLOCK_LENGTH); + input += SHA512_BLOCK_LENGTH; + inputLen -= SHA512_BLOCK_LENGTH; + SHA512_Compress(ctx); + } + /* if data left over, fill it into buffer */ + if (inputLen) + memcpy(B, input, inputLen); +} + +void +SHA512_End(SHA512Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ +#if defined(HAVE_LONG_LONG) + unsigned int inBuf = (unsigned int)ctx->sizeLo & 0x7f; + unsigned int padLen = (inBuf < 112) ? (112 - inBuf) : (112 + 128 - inBuf); + PRUint64 lo, t1; + lo = (ctx->sizeLo << 3); +#else + unsigned int inBuf = (unsigned int)ctx->sizeLo.lo & 0x7f; + unsigned int padLen = (inBuf < 112) ? (112 - inBuf) : (112 + 128 - inBuf); + PRUint64 lo = ctx->sizeLo; + PRUint32 t1; + lo.lo <<= 3; +#endif + + SHA512_Update(ctx, pad, padLen); + +#if defined(HAVE_LONG_LONG) + W[14] = 0; +#else + W[14].lo = 0; + W[14].hi = 0; +#endif + + W[15] = lo; +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP8(W[15]); +#endif + SHA512_Compress(ctx); + + /* now output the answer */ +#if defined(IS_LITTLE_ENDIAN) + BYTESWAP8(H[0]); + BYTESWAP8(H[1]); + BYTESWAP8(H[2]); + BYTESWAP8(H[3]); + BYTESWAP8(H[4]); + BYTESWAP8(H[5]); + BYTESWAP8(H[6]); + BYTESWAP8(H[7]); +#endif + padLen = PR_MIN(SHA512_LENGTH, maxDigestLen); + memcpy(digest, H, padLen); + if (digestLen) + *digestLen = padLen; +} + +SECStatus +SHA512_HashBuf(unsigned char *dest, const unsigned char *src, + unsigned int src_length) +{ + SHA512Context ctx; + unsigned int outLen; + + SHA512_Begin(&ctx); + SHA512_Update(&ctx, src, src_length); + SHA512_End(&ctx, dest, &outLen, SHA512_LENGTH); + + return SECSuccess; +} + + +SECStatus +SHA512_Hash(unsigned char *dest, const char *src) +{ + return SHA512_HashBuf(dest, (const unsigned char *)src, PORT_Strlen(src)); +} + + +void SHA512_TraceState(SHA512Context *ctx) { } + +unsigned int +SHA512_FlattenSize(SHA512Context *ctx) +{ + return sizeof *ctx; +} + +SECStatus +SHA512_Flatten(SHA512Context *ctx,unsigned char *space) +{ + PORT_Memcpy(space, ctx, sizeof *ctx); + return SECSuccess; +} + +SHA512Context * +SHA512_Resurrect(unsigned char *space, void *arg) +{ + SHA512Context *ctx = SHA512_NewContext(); + if (ctx) + PORT_Memcpy(ctx, space, sizeof *ctx); + return ctx; +} + +void SHA512_Clone(SHA512Context *dest, SHA512Context *src) +{ + memcpy(dest, src, sizeof *dest); +} + +/* ======================================================================= */ +/* SHA384 uses a SHA512Context as the real context. +** The only differences between SHA384 an SHA512 are: +** a) the intialization values for the context, and +** b) the number of bytes of data produced as output. +*/ + +/* SHA-384 initial hash values */ +static const PRUint64 H384[8] = { +#if PR_BYTES_PER_LONG == 8 + 0xcbbb9d5dc1059ed8UL , 0x629a292a367cd507UL , + 0x9159015a3070dd17UL , 0x152fecd8f70e5939UL , + 0x67332667ffc00b31UL , 0x8eb44a8768581511UL , + 0xdb0c2e0d64f98fa7UL , 0x47b5481dbefa4fa4UL +#else + ULLC(cbbb9d5d,c1059ed8), ULLC(629a292a,367cd507), + ULLC(9159015a,3070dd17), ULLC(152fecd8,f70e5939), + ULLC(67332667,ffc00b31), ULLC(8eb44a87,68581511), + ULLC(db0c2e0d,64f98fa7), ULLC(47b5481d,befa4fa4) +#endif +}; + +SHA384Context * +SHA384_NewContext(void) +{ + return SHA512_NewContext(); +} + +void +SHA384_DestroyContext(SHA384Context *ctx, PRBool freeit) +{ + SHA512_DestroyContext(ctx, freeit); +} + +void +SHA384_Begin(SHA384Context *ctx) +{ + memset(ctx, 0, sizeof *ctx); + memcpy(H, H384, sizeof H384); +} + +void +SHA384_Update(SHA384Context *ctx, const unsigned char *input, + unsigned int inputLen) +{ + SHA512_Update(ctx, input, inputLen); +} + +void +SHA384_End(SHA384Context *ctx, unsigned char *digest, + unsigned int *digestLen, unsigned int maxDigestLen) +{ +#define SHA_MIN(a,b) (a < b ? a : b) + unsigned int maxLen = SHA_MIN(maxDigestLen, SHA384_LENGTH); + SHA512_End(ctx, digest, digestLen, maxLen); +} + +SECStatus +SHA384_HashBuf(unsigned char *dest, const unsigned char *src, + unsigned int src_length) +{ + SHA512Context ctx; + unsigned int outLen; + + SHA384_Begin(&ctx); + SHA512_Update(&ctx, src, src_length); + SHA512_End(&ctx, dest, &outLen, SHA384_LENGTH); + + return SECSuccess; +} + +SECStatus +SHA384_Hash(unsigned char *dest, const char *src) +{ + return SHA384_HashBuf(dest, (const unsigned char *)src, PORT_Strlen(src)); +} + +void SHA384_TraceState(SHA384Context *ctx) { } + +unsigned int +SHA384_FlattenSize(SHA384Context *ctx) +{ + return sizeof(SHA384Context); +} + +SECStatus +SHA384_Flatten(SHA384Context *ctx,unsigned char *space) +{ + return SHA512_Flatten(ctx, space); +} + +SHA384Context * +SHA384_Resurrect(unsigned char *space, void *arg) +{ + return SHA512_Resurrect(space, arg); +} + +void SHA384_Clone(SHA384Context *dest, SHA384Context *src) +{ + memcpy(dest, src, sizeof *dest); +} +#endif /* Comment out unused code. */ + +/* ======================================================================= */ +#ifdef SELFTEST +#include <stdio.h> + +static const char abc[] = { "abc" }; +static const char abcdbc[] = { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +}; +static const char abcdef[] = { + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu" +}; + +void +dumpHash32(const unsigned char *buf, unsigned int bufLen) +{ + unsigned int i; + for (i = 0; i < bufLen; i += 4) { + printf(" %02x%02x%02x%02x", buf[i], buf[i+1], buf[i+2], buf[i+3]); + } + printf("\n"); +} + +void test256(void) +{ + unsigned char outBuf[SHA256_LENGTH]; + + printf("SHA256, input = %s\n", abc); + SHA256_Hash(outBuf, abc); + dumpHash32(outBuf, sizeof outBuf); + + printf("SHA256, input = %s\n", abcdbc); + SHA256_Hash(outBuf, abcdbc); + dumpHash32(outBuf, sizeof outBuf); +} + +void +dumpHash64(const unsigned char *buf, unsigned int bufLen) +{ + unsigned int i; + for (i = 0; i < bufLen; i += 8) { + if (i % 32 == 0) + printf("\n"); + printf(" %02x%02x%02x%02x%02x%02x%02x%02x", + buf[i ], buf[i+1], buf[i+2], buf[i+3], + buf[i+4], buf[i+5], buf[i+6], buf[i+7]); + } + printf("\n"); +} + +void test512(void) +{ + unsigned char outBuf[SHA512_LENGTH]; + + printf("SHA512, input = %s\n", abc); + SHA512_Hash(outBuf, abc); + dumpHash64(outBuf, sizeof outBuf); + + printf("SHA512, input = %s\n", abcdef); + SHA512_Hash(outBuf, abcdef); + dumpHash64(outBuf, sizeof outBuf); +} + +void time512(void) +{ + unsigned char outBuf[SHA512_LENGTH]; + + SHA512_Hash(outBuf, abc); + SHA512_Hash(outBuf, abcdef); +} + +void test384(void) +{ + unsigned char outBuf[SHA384_LENGTH]; + + printf("SHA384, input = %s\n", abc); + SHA384_Hash(outBuf, abc); + dumpHash64(outBuf, sizeof outBuf); + + printf("SHA384, input = %s\n", abcdef); + SHA384_Hash(outBuf, abcdef); + dumpHash64(outBuf, sizeof outBuf); +} + +int main (int argc, char *argv[], char *envp[]) +{ + int i = 1; + if (argc > 1) { + i = atoi(argv[1]); + } + if (i < 2) { + test256(); + test512(); + test384(); + } else { + while (i-- > 0) { + time512(); + } + printf("done\n"); + } + return 0; +} + +#endif diff --git a/sandbox/linux/bpf_dsl/bpf_dsl.cc b/sandbox/linux/bpf_dsl/bpf_dsl.cc new file mode 100644 index 0000000000..3a35903ec9 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl.cc @@ -0,0 +1,363 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/bpf_dsl/bpf_dsl.h" + +#include <limits> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" + +namespace sandbox { +namespace bpf_dsl { +namespace { + +intptr_t BPFFailure(const struct arch_seccomp_data&, void* aux) { + SANDBOX_DIE(static_cast<char*>(aux)); +} + +class AllowResultExprImpl : public internal::ResultExprImpl { + public: + AllowResultExprImpl() {} + + ErrorCode Compile(PolicyCompiler* pc) const override { + return ErrorCode(ErrorCode::ERR_ALLOWED); + } + + bool IsAllow() const override { return true; } + + private: + ~AllowResultExprImpl() override {} + + DISALLOW_COPY_AND_ASSIGN(AllowResultExprImpl); +}; + +class ErrorResultExprImpl : public internal::ResultExprImpl { + public: + explicit ErrorResultExprImpl(int err) : err_(err) { + CHECK(err_ >= ErrorCode::ERR_MIN_ERRNO && err_ <= ErrorCode::ERR_MAX_ERRNO); + } + + ErrorCode Compile(PolicyCompiler* pc) const override { + return pc->Error(err_); + } + + bool IsDeny() const override { return true; } + + private: + ~ErrorResultExprImpl() override {} + + int err_; + + DISALLOW_COPY_AND_ASSIGN(ErrorResultExprImpl); +}; + +class TraceResultExprImpl : public internal::ResultExprImpl { + public: + TraceResultExprImpl(uint16_t aux) : aux_(aux) {} + + ErrorCode Compile(PolicyCompiler* pc) const override { + return ErrorCode(ErrorCode::ERR_TRACE + aux_); + } + + private: + ~TraceResultExprImpl() override {} + + uint16_t aux_; + + DISALLOW_COPY_AND_ASSIGN(TraceResultExprImpl); +}; + +class TrapResultExprImpl : public internal::ResultExprImpl { + public: + TrapResultExprImpl(TrapRegistry::TrapFnc func, const void* arg, bool safe) + : func_(func), arg_(arg), safe_(safe) { + DCHECK(func_); + } + + ErrorCode Compile(PolicyCompiler* pc) const override { + return pc->Trap(func_, arg_, safe_); + } + + bool HasUnsafeTraps() const override { return safe_ == false; } + + bool IsDeny() const override { return true; } + + private: + ~TrapResultExprImpl() override {} + + TrapRegistry::TrapFnc func_; + const void* arg_; + bool safe_; + + DISALLOW_COPY_AND_ASSIGN(TrapResultExprImpl); +}; + +class IfThenResultExprImpl : public internal::ResultExprImpl { + public: + IfThenResultExprImpl(const BoolExpr& cond, + const ResultExpr& then_result, + const ResultExpr& else_result) + : cond_(cond), then_result_(then_result), else_result_(else_result) {} + + ErrorCode Compile(PolicyCompiler* pc) const override { + return cond_->Compile( + pc, then_result_->Compile(pc), else_result_->Compile(pc)); + } + + bool HasUnsafeTraps() const override { + return then_result_->HasUnsafeTraps() || else_result_->HasUnsafeTraps(); + } + + private: + ~IfThenResultExprImpl() override {} + + BoolExpr cond_; + ResultExpr then_result_; + ResultExpr else_result_; + + DISALLOW_COPY_AND_ASSIGN(IfThenResultExprImpl); +}; + +class ConstBoolExprImpl : public internal::BoolExprImpl { + public: + ConstBoolExprImpl(bool value) : value_(value) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return value_ ? true_ec : false_ec; + } + + private: + ~ConstBoolExprImpl() override {} + + bool value_; + + DISALLOW_COPY_AND_ASSIGN(ConstBoolExprImpl); +}; + +class PrimitiveBoolExprImpl : public internal::BoolExprImpl { + public: + PrimitiveBoolExprImpl(int argno, + ErrorCode::ArgType is_32bit, + uint64_t mask, + uint64_t value) + : argno_(argno), is_32bit_(is_32bit), mask_(mask), value_(value) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return pc->CondMaskedEqual( + argno_, is_32bit_, mask_, value_, true_ec, false_ec); + } + + private: + ~PrimitiveBoolExprImpl() override {} + + int argno_; + ErrorCode::ArgType is_32bit_; + uint64_t mask_; + uint64_t value_; + + DISALLOW_COPY_AND_ASSIGN(PrimitiveBoolExprImpl); +}; + +class NegateBoolExprImpl : public internal::BoolExprImpl { + public: + explicit NegateBoolExprImpl(const BoolExpr& cond) : cond_(cond) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return cond_->Compile(pc, false_ec, true_ec); + } + + private: + ~NegateBoolExprImpl() override {} + + BoolExpr cond_; + + DISALLOW_COPY_AND_ASSIGN(NegateBoolExprImpl); +}; + +class AndBoolExprImpl : public internal::BoolExprImpl { + public: + AndBoolExprImpl(const BoolExpr& lhs, const BoolExpr& rhs) + : lhs_(lhs), rhs_(rhs) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return lhs_->Compile(pc, rhs_->Compile(pc, true_ec, false_ec), false_ec); + } + + private: + ~AndBoolExprImpl() override {} + + BoolExpr lhs_; + BoolExpr rhs_; + + DISALLOW_COPY_AND_ASSIGN(AndBoolExprImpl); +}; + +class OrBoolExprImpl : public internal::BoolExprImpl { + public: + OrBoolExprImpl(const BoolExpr& lhs, const BoolExpr& rhs) + : lhs_(lhs), rhs_(rhs) {} + + ErrorCode Compile(PolicyCompiler* pc, + ErrorCode true_ec, + ErrorCode false_ec) const override { + return lhs_->Compile(pc, true_ec, rhs_->Compile(pc, true_ec, false_ec)); + } + + private: + ~OrBoolExprImpl() override {} + + BoolExpr lhs_; + BoolExpr rhs_; + + DISALLOW_COPY_AND_ASSIGN(OrBoolExprImpl); +}; + +} // namespace + +namespace internal { + +bool ResultExprImpl::HasUnsafeTraps() const { + return false; +} + +bool ResultExprImpl::IsAllow() const { + return false; +} + +bool ResultExprImpl::IsDeny() const { + return false; +} + +uint64_t DefaultMask(size_t size) { + switch (size) { + case 4: + return std::numeric_limits<uint32_t>::max(); + case 8: + return std::numeric_limits<uint64_t>::max(); + default: + CHECK(false) << "Unimplemented DefaultMask case"; + return 0; + } +} + +BoolExpr ArgEq(int num, size_t size, uint64_t mask, uint64_t val) { + CHECK(size == 4 || size == 8); + + // TODO(mdempsky): Should we just always use TP_64BIT? + const ErrorCode::ArgType arg_type = + (size == 4) ? ErrorCode::TP_32BIT : ErrorCode::TP_64BIT; + + return BoolExpr(new const PrimitiveBoolExprImpl(num, arg_type, mask, val)); +} + +} // namespace internal + +ResultExpr Allow() { + return ResultExpr(new const AllowResultExprImpl()); +} + +ResultExpr Error(int err) { + return ResultExpr(new const ErrorResultExprImpl(err)); +} + +ResultExpr Kill(const char* msg) { + return Trap(BPFFailure, msg); +} + +ResultExpr Trace(uint16_t aux) { + return ResultExpr(new const TraceResultExprImpl(aux)); +} + +ResultExpr Trap(TrapRegistry::TrapFnc trap_func, const void* aux) { + return ResultExpr( + new const TrapResultExprImpl(trap_func, aux, true /* safe */)); +} + +ResultExpr UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux) { + return ResultExpr( + new const TrapResultExprImpl(trap_func, aux, false /* unsafe */)); +} + +BoolExpr BoolConst(bool value) { + return BoolExpr(new const ConstBoolExprImpl(value)); +} + +BoolExpr operator!(const BoolExpr& cond) { + return BoolExpr(new const NegateBoolExprImpl(cond)); +} + +BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs) { + return BoolExpr(new const AndBoolExprImpl(lhs, rhs)); +} + +BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs) { + return BoolExpr(new const OrBoolExprImpl(lhs, rhs)); +} + +Elser If(const BoolExpr& cond, const ResultExpr& then_result) { + return Elser(nullptr).ElseIf(cond, then_result); +} + +Elser::Elser(cons::List<Clause> clause_list) : clause_list_(clause_list) { +} + +Elser::Elser(const Elser& elser) : clause_list_(elser.clause_list_) { +} + +Elser::~Elser() { +} + +Elser Elser::ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const { + return Elser(Cons(std::make_pair(cond, then_result), clause_list_)); +} + +ResultExpr Elser::Else(const ResultExpr& else_result) const { + // We finally have the default result expression for this + // if/then/else sequence. Also, we've already accumulated all + // if/then pairs into a list of reverse order (i.e., lower priority + // conditions are listed before higher priority ones). E.g., an + // expression like + // + // If(b1, e1).ElseIf(b2, e2).ElseIf(b3, e3).Else(e4) + // + // will have built up a list like + // + // [(b3, e3), (b2, e2), (b1, e1)]. + // + // Now that we have e4, we can walk the list and create a ResultExpr + // tree like: + // + // expr = e4 + // expr = (b3 ? e3 : expr) = (b3 ? e3 : e4) + // expr = (b2 ? e2 : expr) = (b2 ? e2 : (b3 ? e3 : e4)) + // expr = (b1 ? e1 : expr) = (b1 ? e1 : (b2 ? e2 : (b3 ? e3 : e4))) + // + // and end up with an appropriately chained tree. + + ResultExpr expr = else_result; + for (const Clause& clause : clause_list_) { + expr = ResultExpr( + new const IfThenResultExprImpl(clause.first, clause.second, expr)); + } + return expr; +} + +} // namespace bpf_dsl +} // namespace sandbox + +template class scoped_refptr<const sandbox::bpf_dsl::internal::BoolExprImpl>; +template class scoped_refptr<const sandbox::bpf_dsl::internal::ResultExprImpl>; diff --git a/sandbox/linux/bpf_dsl/bpf_dsl.h b/sandbox/linux/bpf_dsl/bpf_dsl.h new file mode 100644 index 0000000000..365e9b5466 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl.h @@ -0,0 +1,317 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ + +#include <stdint.h> + +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/cons.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/sandbox_export.h" + +// The sandbox::bpf_dsl namespace provides a domain-specific language +// to make writing BPF policies more expressive. In general, the +// object types all have value semantics (i.e., they can be copied +// around, returned from or passed to function calls, etc. without any +// surprising side effects), though not all support assignment. +// +// An idiomatic and demonstrative (albeit silly) example of this API +// would be: +// +// #include "sandbox/linux/bpf_dsl/bpf_dsl.h" +// +// using namespace sandbox::bpf_dsl; +// +// class SillyPolicy : public Policy { +// public: +// SillyPolicy() {} +// ~SillyPolicy() override {} +// ResultExpr EvaluateSyscall(int sysno) const override { +// if (sysno == __NR_fcntl) { +// Arg<int> fd(0), cmd(1); +// Arg<unsigned long> flags(2); +// const uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK; +// return If(fd == 0 && cmd == F_SETFL && (flags & ~kGoodFlags) == 0, +// Allow()) +// .ElseIf(cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC, +// Error(EMFILE)) +// .Else(Trap(SetFlagHandler, NULL)); +// } else { +// return Allow(); +// } +// } +// +// private: +// DISALLOW_COPY_AND_ASSIGN(SillyPolicy); +// }; +// +// More generally, the DSL currently supports the following grammar: +// +// result = Allow() | Error(errno) | Kill(msg) | Trace(aux) +// | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux) +// | If(bool, result)[.ElseIf(bool, result)].Else(result) +// | Switch(arg)[.Case(val, result)].Default(result) +// bool = BoolConst(boolean) | !bool | bool && bool | bool || bool +// | arg == val | arg != val +// arg = Arg<T>(num) | arg & mask +// +// The semantics of each function and operator are intended to be +// intuitive, but are described in more detail below. +// +// (Credit to Sean Parent's "Inheritance is the Base Class of Evil" +// talk at Going Native 2013 for promoting value semantics via shared +// pointers to immutable state.) + +namespace sandbox { +namespace bpf_dsl { + +// ResultExpr is an opaque reference to an immutable result expression tree. +typedef scoped_refptr<const internal::ResultExprImpl> ResultExpr; + +// BoolExpr is an opaque reference to an immutable boolean expression tree. +typedef scoped_refptr<const internal::BoolExprImpl> BoolExpr; + +// Allow specifies a result that the system call should be allowed to +// execute normally. +SANDBOX_EXPORT ResultExpr Allow(); + +// Error specifies a result that the system call should fail with +// error number |err|. As a special case, Error(0) will result in the +// system call appearing to have succeeded, but without having any +// side effects. +SANDBOX_EXPORT ResultExpr Error(int err); + +// Kill specifies a result to kill the program and print an error message. +SANDBOX_EXPORT ResultExpr Kill(const char* msg); + +// Trace specifies a result to notify a tracing process via the +// PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call. +// The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG. +SANDBOX_EXPORT ResultExpr Trace(uint16_t aux); + +// Trap specifies a result that the system call should be handled by +// trapping back into userspace and invoking |trap_func|, passing +// |aux| as the second parameter. +SANDBOX_EXPORT ResultExpr + Trap(TrapRegistry::TrapFnc trap_func, const void* aux); + +// UnsafeTrap is like Trap, except the policy is marked as "unsafe" +// and allowed to use SandboxSyscall to invoke any system call. +// +// NOTE: This feature, by definition, disables all security features of +// the sandbox. It should never be used in production, but it can be +// very useful to diagnose code that is incompatible with the sandbox. +// If even a single system call returns "UnsafeTrap", the security of +// entire sandbox should be considered compromised. +SANDBOX_EXPORT ResultExpr + UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux); + +// BoolConst converts a bool value into a BoolExpr. +SANDBOX_EXPORT BoolExpr BoolConst(bool value); + +// Various ways to combine boolean expressions into more complex expressions. +// They follow standard boolean algebra laws. +SANDBOX_EXPORT BoolExpr operator!(const BoolExpr& cond); +SANDBOX_EXPORT BoolExpr operator&&(const BoolExpr& lhs, const BoolExpr& rhs); +SANDBOX_EXPORT BoolExpr operator||(const BoolExpr& lhs, const BoolExpr& rhs); + +template <typename T> +class SANDBOX_EXPORT Arg { + public: + // Initializes the Arg to represent the |num|th system call + // argument (indexed from 0), which is of type |T|. + explicit Arg(int num); + + Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {} + + // Returns an Arg representing the current argument, but after + // bitwise-and'ing it with |rhs|. + friend Arg operator&(const Arg& lhs, uint64_t rhs) { + return Arg(lhs.num_, lhs.mask_ & rhs); + } + + // Returns a boolean expression comparing whether the system call argument + // (after applying any bitmasks, if appropriate) equals |rhs|. + friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); } + + // Returns a boolean expression comparing whether the system call argument + // (after applying any bitmasks, if appropriate) does not equal |rhs|. + friend BoolExpr operator!=(const Arg& lhs, T rhs) { return !(lhs == rhs); } + + private: + Arg(int num, uint64_t mask) : num_(num), mask_(mask) {} + + BoolExpr EqualTo(T val) const; + + int num_; + uint64_t mask_; + + DISALLOW_ASSIGN(Arg); +}; + +// If begins a conditional result expression predicated on the +// specified boolean expression. +SANDBOX_EXPORT Elser If(const BoolExpr& cond, const ResultExpr& then_result); + +class SANDBOX_EXPORT Elser { + public: + Elser(const Elser& elser); + ~Elser(); + + // ElseIf extends the conditional result expression with another + // "if then" clause, predicated on the specified boolean expression. + Elser ElseIf(const BoolExpr& cond, const ResultExpr& then_result) const; + + // Else terminates a conditional result expression using |else_result| as + // the default fallback result expression. + ResultExpr Else(const ResultExpr& else_result) const; + + private: + typedef std::pair<BoolExpr, ResultExpr> Clause; + + explicit Elser(cons::List<Clause> clause_list); + + cons::List<Clause> clause_list_; + + friend Elser If(const BoolExpr&, const ResultExpr&); + template <typename T> + friend Caser<T> Switch(const Arg<T>&); + DISALLOW_ASSIGN(Elser); +}; + +// Switch begins a switch expression dispatched according to the +// specified argument value. +template <typename T> +SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg); + +template <typename T> +class SANDBOX_EXPORT Caser { + public: + Caser(const Caser<T>& caser) : arg_(caser.arg_), elser_(caser.elser_) {} + ~Caser() {} + + // Case adds a single-value "case" clause to the switch. + Caser<T> Case(T value, ResultExpr result) const; + + // Cases adds a multiple-value "case" clause to the switch. + // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way + // of using this function. + Caser<T> Cases(const std::vector<T>& values, ResultExpr result) const; + + // Terminate the switch with a "default" clause. + ResultExpr Default(ResultExpr result) const; + + private: + Caser(const Arg<T>& arg, Elser elser) : arg_(arg), elser_(elser) {} + + Arg<T> arg_; + Elser elser_; + + template <typename U> + friend Caser<U> Switch(const Arg<U>&); + DISALLOW_ASSIGN(Caser); +}; + +// Recommended usage is to put +// #define CASES SANDBOX_BPF_DSL_CASES +// near the top of the .cc file (e.g., nearby any "using" statements), then +// use like: +// Switch(arg).CASES((3, 5, 7), result)...; +#define SANDBOX_BPF_DSL_CASES(values, result) \ + Cases(SANDBOX_BPF_DSL_CASES_HELPER values, result) + +// Helper macro to construct a std::vector from an initializer list. +// TODO(mdempsky): Convert to use C++11 initializer lists instead. +#define SANDBOX_BPF_DSL_CASES_HELPER(value, ...) \ + ({ \ + const __typeof__(value) bpf_dsl_cases_values[] = {value, __VA_ARGS__}; \ + std::vector<__typeof__(value)>( \ + bpf_dsl_cases_values, \ + bpf_dsl_cases_values + arraysize(bpf_dsl_cases_values)); \ + }) + +// ===================================================================== +// Official API ends here. +// ===================================================================== + +namespace internal { + +// Make argument-dependent lookup work. This is necessary because although +// BoolExpr is defined in bpf_dsl, since it's merely a typedef for +// scoped_refptr<const internal::BoolExplImpl>, argument-dependent lookup only +// searches the "internal" nested namespace. +using bpf_dsl::operator!; +using bpf_dsl::operator||; +using bpf_dsl::operator&&; + +// Returns a boolean expression that represents whether system call +// argument |num| of size |size| is equal to |val|, when masked +// according to |mask|. Users should use the Arg template class below +// instead of using this API directly. +SANDBOX_EXPORT BoolExpr + ArgEq(int num, size_t size, uint64_t mask, uint64_t val); + +// Returns the default mask for a system call argument of the specified size. +SANDBOX_EXPORT uint64_t DefaultMask(size_t size); + +} // namespace internal + +template <typename T> +Arg<T>::Arg(int num) + : num_(num), mask_(internal::DefaultMask(sizeof(T))) { +} + +// Definition requires ArgEq to have been declared. Moved out-of-line +// to minimize how much internal clutter users have to ignore while +// reading the header documentation. +// +// Additionally, we use this helper member function to avoid linker errors +// caused by defining operator== out-of-line. For a more detailed explanation, +// see http://www.parashift.com/c++-faq-lite/template-friends.html. +template <typename T> +BoolExpr Arg<T>::EqualTo(T val) const { + return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val)); +} + +template <typename T> +SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) { + return Caser<T>(arg, Elser(nullptr)); +} + +template <typename T> +Caser<T> Caser<T>::Case(T value, ResultExpr result) const { + return SANDBOX_BPF_DSL_CASES((value), result); +} + +template <typename T> +Caser<T> Caser<T>::Cases(const std::vector<T>& values, + ResultExpr result) const { + // Theoretically we could evaluate arg_ just once and emit a more efficient + // dispatch table, but for now we simply translate into an equivalent + // If/ElseIf/Else chain. + + typedef typename std::vector<T>::const_iterator Iter; + BoolExpr test = BoolConst(false); + for (Iter i = values.begin(), end = values.end(); i != end; ++i) { + test = test || (arg_ == *i); + } + return Caser<T>(arg_, elser_.ElseIf(test, result)); +} + +template <typename T> +ResultExpr Caser<T>::Default(ResultExpr result) const { + return elser_.Else(result); +} + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc b/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc new file mode 100644 index 0000000000..398ec59ef1 --- /dev/null +++ b/sandbox/linux/bpf_dsl/bpf_dsl_unittest.cc @@ -0,0 +1,486 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/bpf_dsl/bpf_dsl.h" + +#include <errno.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/utsname.h> +#include <unistd.h> + +#include <map> +#include <utility> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/bpf_dsl/verifier.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define CASES SANDBOX_BPF_DSL_CASES + +namespace sandbox { +namespace bpf_dsl { +namespace { + +// Helper function to construct fake arch_seccomp_data objects. +struct arch_seccomp_data FakeSyscall(int nr, + uint64_t p0 = 0, + uint64_t p1 = 0, + uint64_t p2 = 0, + uint64_t p3 = 0, + uint64_t p4 = 0, + uint64_t p5 = 0) { + // Made up program counter for syscall address. + const uint64_t kFakePC = 0x543210; + + struct arch_seccomp_data data = { + nr, + SECCOMP_ARCH, + kFakePC, + { + p0, p1, p2, p3, p4, p5, + }, + }; + + return data; +} + +class FakeTrapRegistry : public TrapRegistry { + public: + FakeTrapRegistry() : map_() {} + virtual ~FakeTrapRegistry() {} + + uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override { + EXPECT_TRUE(safe); + + const uint16_t next_id = map_.size() + 1; + return map_.insert(std::make_pair(Key(fnc, aux), next_id)).first->second; + } + + bool EnableUnsafeTraps() override { + ADD_FAILURE() << "Unimplemented"; + return false; + } + + private: + using Key = std::pair<TrapFnc, const void*>; + + std::map<Key, uint16_t> map_; + + DISALLOW_COPY_AND_ASSIGN(FakeTrapRegistry); +}; + +intptr_t FakeTrapFuncOne(const arch_seccomp_data& data, void* aux) { return 1; } +intptr_t FakeTrapFuncTwo(const arch_seccomp_data& data, void* aux) { return 2; } + +// Test that FakeTrapRegistry correctly assigns trap IDs to trap handlers. +TEST(FakeTrapRegistry, TrapIDs) { + struct { + TrapRegistry::TrapFnc fnc; + const void* aux; + } funcs[] = { + {FakeTrapFuncOne, nullptr}, + {FakeTrapFuncTwo, nullptr}, + {FakeTrapFuncOne, funcs}, + {FakeTrapFuncTwo, funcs}, + }; + + FakeTrapRegistry traps; + + // Add traps twice to test that IDs are reused correctly. + for (int i = 0; i < 2; ++i) { + for (size_t j = 0; j < arraysize(funcs); ++j) { + // Trap IDs start at 1. + EXPECT_EQ(j + 1, traps.Add(funcs[j].fnc, funcs[j].aux, true)); + } + } +} + +class PolicyEmulator { + public: + explicit PolicyEmulator(const Policy* policy) : program_(), traps_() { + program_ = *PolicyCompiler(policy, &traps_).Compile(true /* verify */); + } + ~PolicyEmulator() {} + + uint32_t Emulate(const struct arch_seccomp_data& data) const { + const char* err = nullptr; + uint32_t res = Verifier::EvaluateBPF(program_, data, &err); + if (err) { + ADD_FAILURE() << err; + return 0; + } + return res; + } + + void ExpectAllow(const struct arch_seccomp_data& data) const { + EXPECT_EQ(SECCOMP_RET_ALLOW, Emulate(data)); + } + + void ExpectErrno(uint16_t err, const struct arch_seccomp_data& data) const { + EXPECT_EQ(SECCOMP_RET_ERRNO | err, Emulate(data)); + } + + private: + CodeGen::Program program_; + FakeTrapRegistry traps_; + + DISALLOW_COPY_AND_ASSIGN(PolicyEmulator); +}; + +class BasicPolicy : public Policy { + public: + BasicPolicy() {} + ~BasicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_getpgid) { + const Arg<pid_t> pid(0); + return If(pid == 0, Error(EPERM)).Else(Error(EINVAL)); + } + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If(uid != 42, Error(ESRCH)).Else(Error(ENOMEM)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicPolicy); +}; + +TEST(BPFDSL, Basic) { + BasicPolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_getpgid, 0)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_getpgid, 1)); + + emulator.ExpectErrno(ENOMEM, FakeSyscall(__NR_setuid, 42)); + emulator.ExpectErrno(ESRCH, FakeSyscall(__NR_setuid, 43)); +} + +/* On IA-32, socketpair() is implemented via socketcall(). :-( */ +#if !defined(ARCH_CPU_X86) +class BooleanLogicPolicy : public Policy { + public: + BooleanLogicPolicy() {} + ~BooleanLogicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_socketpair) { + const Arg<int> domain(0), type(1), protocol(2); + return If(domain == AF_UNIX && + (type == SOCK_STREAM || type == SOCK_DGRAM) && + protocol == 0, + Error(EPERM)).Else(Error(EINVAL)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BooleanLogicPolicy); +}; + +TEST(BPFDSL, BooleanLogic) { + BooleanLogicPolicy policy; + PolicyEmulator emulator(&policy); + + const intptr_t kFakeSV = 0x12345; + + // Acceptable combinations that should return EPERM. + emulator.ExpectErrno( + EPERM, FakeSyscall(__NR_socketpair, AF_UNIX, SOCK_STREAM, 0, kFakeSV)); + emulator.ExpectErrno( + EPERM, FakeSyscall(__NR_socketpair, AF_UNIX, SOCK_DGRAM, 0, kFakeSV)); + + // Combinations that are invalid for only one reason; should return EINVAL. + emulator.ExpectErrno( + EINVAL, FakeSyscall(__NR_socketpair, AF_INET, SOCK_STREAM, 0, kFakeSV)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_socketpair, AF_UNIX, + SOCK_SEQPACKET, 0, kFakeSV)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_socketpair, AF_UNIX, + SOCK_STREAM, IPPROTO_TCP, kFakeSV)); + + // Completely unacceptable combination; should also return EINVAL. + emulator.ExpectErrno( + EINVAL, FakeSyscall(__NR_socketpair, AF_INET, SOCK_SEQPACKET, IPPROTO_UDP, + kFakeSV)); +} +#endif // !ARCH_CPU_X86 + +class MoreBooleanLogicPolicy : public Policy { + public: + MoreBooleanLogicPolicy() {} + ~MoreBooleanLogicPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setresuid) { + const Arg<uid_t> ruid(0), euid(1), suid(2); + return If(ruid == 0 || euid == 0 || suid == 0, Error(EPERM)) + .ElseIf(ruid == 1 && euid == 1 && suid == 1, Error(EAGAIN)) + .Else(Error(EINVAL)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MoreBooleanLogicPolicy); +}; + +TEST(BPFDSL, MoreBooleanLogic) { + MoreBooleanLogicPolicy policy; + PolicyEmulator emulator(&policy); + + // Expect EPERM if any set to 0. + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 0, 5, 5)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 5, 0, 5)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_setresuid, 5, 5, 0)); + + // Expect EAGAIN if all set to 1. + emulator.ExpectErrno(EAGAIN, FakeSyscall(__NR_setresuid, 1, 1, 1)); + + // Expect EINVAL for anything else. + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 5, 1, 1)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 1, 5, 1)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 1, 1, 5)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setresuid, 3, 4, 5)); +} + +static const uintptr_t kDeadBeefAddr = + static_cast<uintptr_t>(0xdeadbeefdeadbeefULL); + +class ArgSizePolicy : public Policy { + public: + ArgSizePolicy() {} + ~ArgSizePolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_uname) { + const Arg<uintptr_t> addr(0); + return If(addr == kDeadBeefAddr, Error(EPERM)).Else(Allow()); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ArgSizePolicy); +}; + +TEST(BPFDSL, ArgSizeTest) { + ArgSizePolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectAllow(FakeSyscall(__NR_uname, 0)); + emulator.ExpectErrno(EPERM, FakeSyscall(__NR_uname, kDeadBeefAddr)); +} + +#if 0 +// TODO(mdempsky): This is really an integration test. + +class TrappingPolicy : public Policy { + public: + TrappingPolicy() {} + ~TrappingPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_uname) { + return Trap(UnameTrap, &count_); + } + return Allow(); + } + + private: + static intptr_t count_; + + static intptr_t UnameTrap(const struct arch_seccomp_data& data, void* aux) { + BPF_ASSERT_EQ(&count_, aux); + return ++count_; + } + + DISALLOW_COPY_AND_ASSIGN(TrappingPolicy); +}; + +intptr_t TrappingPolicy::count_; + +BPF_TEST_C(BPFDSL, TrapTest, TrappingPolicy) { + ASSERT_SYSCALL_RESULT(1, uname, NULL); + ASSERT_SYSCALL_RESULT(2, uname, NULL); + ASSERT_SYSCALL_RESULT(3, uname, NULL); +} +#endif + +class MaskingPolicy : public Policy { + public: + MaskingPolicy() {} + ~MaskingPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If((uid & 0xf) == 0, Error(EINVAL)).Else(Error(EACCES)); + } + if (sysno == __NR_setgid) { + const Arg<gid_t> gid(0); + return If((gid & 0xf0) == 0xf0, Error(EINVAL)).Else(Error(EACCES)); + } + if (sysno == __NR_setpgid) { + const Arg<pid_t> pid(0); + return If((pid & 0xa5) == 0xa0, Error(EINVAL)).Else(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MaskingPolicy); +}; + +TEST(BPFDSL, MaskTest) { + MaskingPolicy policy; + PolicyEmulator emulator(&policy); + + for (uid_t uid = 0; uid < 0x100; ++uid) { + const int expect_errno = (uid & 0xf) == 0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setuid, uid)); + } + + for (gid_t gid = 0; gid < 0x100; ++gid) { + const int expect_errno = (gid & 0xf0) == 0xf0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setgid, gid)); + } + + for (pid_t pid = 0; pid < 0x100; ++pid) { + const int expect_errno = (pid & 0xa5) == 0xa0 ? EINVAL : EACCES; + emulator.ExpectErrno(expect_errno, FakeSyscall(__NR_setpgid, pid, 0)); + } +} + +class ElseIfPolicy : public Policy { + public: + ElseIfPolicy() {} + ~ElseIfPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_setuid) { + const Arg<uid_t> uid(0); + return If((uid & 0xfff) == 0, Error(0)) + .ElseIf((uid & 0xff0) == 0, Error(EINVAL)) + .ElseIf((uid & 0xf00) == 0, Error(EEXIST)) + .Else(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ElseIfPolicy); +}; + +TEST(BPFDSL, ElseIfTest) { + ElseIfPolicy policy; + PolicyEmulator emulator(&policy); + + emulator.ExpectErrno(0, FakeSyscall(__NR_setuid, 0)); + + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setuid, 0x0001)); + emulator.ExpectErrno(EINVAL, FakeSyscall(__NR_setuid, 0x0002)); + + emulator.ExpectErrno(EEXIST, FakeSyscall(__NR_setuid, 0x0011)); + emulator.ExpectErrno(EEXIST, FakeSyscall(__NR_setuid, 0x0022)); + + emulator.ExpectErrno(EACCES, FakeSyscall(__NR_setuid, 0x0111)); + emulator.ExpectErrno(EACCES, FakeSyscall(__NR_setuid, 0x0222)); +} + +class SwitchPolicy : public Policy { + public: + SwitchPolicy() {} + ~SwitchPolicy() override {} + ResultExpr EvaluateSyscall(int sysno) const override { + if (sysno == __NR_fcntl) { + const Arg<int> cmd(1); + const Arg<unsigned long> long_arg(2); + return Switch(cmd) + .CASES((F_GETFL, F_GETFD), Error(ENOENT)) + .Case(F_SETFD, If(long_arg == O_CLOEXEC, Allow()).Else(Error(EINVAL))) + .Case(F_SETFL, Error(EPERM)) + .Default(Error(EACCES)); + } + return Allow(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SwitchPolicy); +}; + +TEST(BPFDSL, SwitchTest) { + SwitchPolicy policy; + PolicyEmulator emulator(&policy); + + const int kFakeSockFD = 42; + + emulator.ExpectErrno(ENOENT, FakeSyscall(__NR_fcntl, kFakeSockFD, F_GETFD)); + emulator.ExpectErrno(ENOENT, FakeSyscall(__NR_fcntl, kFakeSockFD, F_GETFL)); + + emulator.ExpectAllow( + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFD, O_CLOEXEC)); + emulator.ExpectErrno(EINVAL, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFD, 0)); + + emulator.ExpectErrno(EPERM, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_SETFL, O_RDONLY)); + + emulator.ExpectErrno(EACCES, + FakeSyscall(__NR_fcntl, kFakeSockFD, F_DUPFD, 0)); +} + +static intptr_t DummyTrap(const struct arch_seccomp_data& data, void* aux) { + return 0; +} + +TEST(BPFDSL, IsAllowDeny) { + ResultExpr allow = Allow(); + EXPECT_TRUE(allow->IsAllow()); + EXPECT_FALSE(allow->IsDeny()); + + ResultExpr error = Error(ENOENT); + EXPECT_FALSE(error->IsAllow()); + EXPECT_TRUE(error->IsDeny()); + + ResultExpr trace = Trace(42); + EXPECT_FALSE(trace->IsAllow()); + EXPECT_FALSE(trace->IsDeny()); + + ResultExpr trap = Trap(DummyTrap, nullptr); + EXPECT_FALSE(trap->IsAllow()); + EXPECT_TRUE(trap->IsDeny()); + + const Arg<int> arg(0); + ResultExpr maybe = If(arg == 0, Allow()).Else(Error(EPERM)); + EXPECT_FALSE(maybe->IsAllow()); + EXPECT_FALSE(maybe->IsDeny()); +} + +TEST(BPFDSL, HasUnsafeTraps) { + ResultExpr allow = Allow(); + EXPECT_FALSE(allow->HasUnsafeTraps()); + + ResultExpr safe = Trap(DummyTrap, nullptr); + EXPECT_FALSE(safe->HasUnsafeTraps()); + + ResultExpr unsafe = UnsafeTrap(DummyTrap, nullptr); + EXPECT_TRUE(unsafe->HasUnsafeTraps()); + + const Arg<int> arg(0); + ResultExpr maybe = If(arg == 0, allow).Else(unsafe); + EXPECT_TRUE(maybe->HasUnsafeTraps()); +} + +} // namespace +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/codegen.cc b/sandbox/linux/bpf_dsl/codegen.cc new file mode 100644 index 0000000000..2d5c8e406e --- /dev/null +++ b/sandbox/linux/bpf_dsl/codegen.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/bpf_dsl/codegen.h" + +#include <limits> +#include <utility> + +#include "base/logging.h" +#include "sandbox/linux/system_headers/linux_filter.h" + +// This CodeGen implementation strives for simplicity while still +// generating acceptable BPF programs under typical usage patterns +// (e.g., by PolicyCompiler). +// +// The key to its simplicity is that BPF programs only support forward +// jumps/branches, which allows constraining the DAG construction API +// to make instruction nodes immutable. Immutable nodes admits a +// simple greedy approach of emitting new instructions as needed and +// then reusing existing ones that have already been emitted. This +// cleanly avoids any need to compute basic blocks or apply +// topological sorting because the API effectively sorts instructions +// for us (e.g., before MakeInstruction() can be called to emit a +// branch instruction, it must have already been called for each +// branch path). +// +// This greedy algorithm is not without (theoretical) weakness though: +// +// 1. In the general case, we don't eliminate dead code. If needed, +// we could trace back through the program in Compile() and elide +// any unneeded instructions, but in practice we only emit live +// instructions anyway. +// +// 2. By not dividing instructions into basic blocks and sorting, we +// lose an opportunity to move non-branch/non-return instructions +// adjacent to their successor instructions, which means we might +// need to emit additional jumps. But in practice, they'll +// already be nearby as long as callers don't go out of their way +// to interleave MakeInstruction() calls for unrelated code +// sequences. + +namespace sandbox { + +// kBranchRange is the maximum value that can be stored in +// sock_filter's 8-bit jt and jf fields. +const size_t kBranchRange = std::numeric_limits<uint8_t>::max(); + +const CodeGen::Node CodeGen::kNullNode; + +CodeGen::CodeGen() : program_(), equivalent_(), memos_() { +} + +CodeGen::~CodeGen() { +} + +void CodeGen::Compile(CodeGen::Node head, Program* out) { + DCHECK(out); + out->assign(program_.rbegin() + Offset(head), program_.rend()); +} + +CodeGen::Node CodeGen::MakeInstruction(uint16_t code, + uint32_t k, + Node jt, + Node jf) { + // To avoid generating redundant code sequences, we memoize the + // results from AppendInstruction(). + auto res = memos_.insert(std::make_pair(MemoKey(code, k, jt, jf), kNullNode)); + CodeGen::Node* node = &res.first->second; + if (res.second) { // Newly inserted memo entry. + *node = AppendInstruction(code, k, jt, jf); + } + return *node; +} + +CodeGen::Node CodeGen::AppendInstruction(uint16_t code, + uint32_t k, + Node jt, + Node jf) { + if (BPF_CLASS(code) == BPF_JMP) { + CHECK_NE(BPF_JA, BPF_OP(code)) << "CodeGen inserts JAs as needed"; + + // Optimally adding jumps is rather tricky, so we use a quick + // approximation: by artificially reducing |jt|'s range, |jt| will + // stay within its true range even if we add a jump for |jf|. + jt = WithinRange(jt, kBranchRange - 1); + jf = WithinRange(jf, kBranchRange); + return Append(code, k, Offset(jt), Offset(jf)); + } + + CHECK_EQ(kNullNode, jf) << "Non-branch instructions shouldn't provide jf"; + if (BPF_CLASS(code) == BPF_RET) { + CHECK_EQ(kNullNode, jt) << "Return instructions shouldn't provide jt"; + } else { + // For non-branch/non-return instructions, execution always + // proceeds to the next instruction; so we need to arrange for + // that to be |jt|. + jt = WithinRange(jt, 0); + CHECK_EQ(0U, Offset(jt)) << "ICE: Failed to setup next instruction"; + } + return Append(code, k, 0, 0); +} + +CodeGen::Node CodeGen::WithinRange(Node target, size_t range) { + // Just use |target| if it's already within range. + if (Offset(target) <= range) { + return target; + } + + // Alternatively, look for an equivalent instruction within range. + if (Offset(equivalent_.at(target)) <= range) { + return equivalent_.at(target); + } + + // Otherwise, fall back to emitting a jump instruction. + Node jump = Append(BPF_JMP | BPF_JA, Offset(target), 0, 0); + equivalent_.at(target) = jump; + return jump; +} + +CodeGen::Node CodeGen::Append(uint16_t code, uint32_t k, size_t jt, size_t jf) { + if (BPF_CLASS(code) == BPF_JMP && BPF_OP(code) != BPF_JA) { + CHECK_LE(jt, kBranchRange); + CHECK_LE(jf, kBranchRange); + } else { + CHECK_EQ(0U, jt); + CHECK_EQ(0U, jf); + } + + CHECK_LT(program_.size(), static_cast<size_t>(BPF_MAXINSNS)); + CHECK_EQ(program_.size(), equivalent_.size()); + + Node res = program_.size(); + program_.push_back(sock_filter{ + code, static_cast<uint8_t>(jt), static_cast<uint8_t>(jf), k}); + equivalent_.push_back(res); + return res; +} + +size_t CodeGen::Offset(Node target) const { + CHECK_LT(target, program_.size()) << "Bogus offset target node"; + return (program_.size() - 1) - target; +} + +// TODO(mdempsky): Move into a general base::Tuple helper library. +bool CodeGen::MemoKeyLess::operator()(const MemoKey& lhs, + const MemoKey& rhs) const { + if (base::get<0>(lhs) != base::get<0>(rhs)) + return base::get<0>(lhs) < base::get<0>(rhs); + if (base::get<1>(lhs) != base::get<1>(rhs)) + return base::get<1>(lhs) < base::get<1>(rhs); + if (base::get<2>(lhs) != base::get<2>(rhs)) + return base::get<2>(lhs) < base::get<2>(rhs); + if (base::get<3>(lhs) != base::get<3>(rhs)) + return base::get<3>(lhs) < base::get<3>(rhs); + return false; +} + +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/codegen.h b/sandbox/linux/bpf_dsl/codegen.h new file mode 100644 index 0000000000..9d898030b9 --- /dev/null +++ b/sandbox/linux/bpf_dsl/codegen.h @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ +#define SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <vector> + +#include "base/macros.h" +#include "base/tuple.h" +#include "sandbox/sandbox_export.h" + +struct sock_filter; + +namespace sandbox { + +// The code generator implements a basic assembler that can convert a +// graph of BPF instructions into a well-formed array of BPF +// instructions. Most notably, it ensures that jumps are always +// forward and don't exceed the limit of 255 instructions imposed by +// the instruction set. +// +// Callers would typically create a new CodeGen object and then use it +// to build a DAG of instruction nodes. They'll eventually call +// Compile() to convert this DAG to a Program. +// +// CodeGen gen; +// CodeGen::Node allow, branch, dag; +// +// allow = +// gen.MakeInstruction(BPF_RET+BPF_K, +// ErrorCode(ErrorCode::ERR_ALLOWED).err())); +// branch = +// gen.MakeInstruction(BPF_JMP+BPF_EQ+BPF_K, __NR_getpid, +// Trap(GetPidHandler, NULL), allow); +// dag = +// gen.MakeInstruction(BPF_LD+BPF_W+BPF_ABS, +// offsetof(struct arch_seccomp_data, nr), branch); +// +// // Simplified code follows; in practice, it is important to avoid calling +// // any C++ destructors after starting the sandbox. +// CodeGen::Program program; +// gen.Compile(dag, program); +// const struct sock_fprog prog = { +// static_cast<unsigned short>(program->size()), &program[0] }; +// prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); +// +class SANDBOX_EXPORT CodeGen { + public: + // A vector of BPF instructions that need to be installed as a filter + // program in the kernel. + typedef std::vector<struct sock_filter> Program; + + // Node represents a node within the instruction DAG being compiled. + using Node = Program::size_type; + + // kNullNode represents the "null" node; i.e., the reserved node + // value guaranteed to not equal any actual nodes. + static const Node kNullNode = -1; + + CodeGen(); + ~CodeGen(); + + // MakeInstruction creates a node representing the specified + // instruction, or returns and existing equivalent node if one + // exists. For details on the possible parameters refer to + // https://www.kernel.org/doc/Documentation/networking/filter.txt. + // TODO(mdempsky): Reconsider using default arguments here. + Node MakeInstruction(uint16_t code, + uint32_t k, + Node jt = kNullNode, + Node jf = kNullNode); + + // Compile linearizes the instruction DAG rooted at |head| into a + // program that can be executed by a BPF virtual machine. + void Compile(Node head, Program* program); + + private: + using MemoKey = base::Tuple<uint16_t, uint32_t, Node, Node>; + struct MemoKeyLess { + bool operator()(const MemoKey& lhs, const MemoKey& rhs) const; + }; + + // AppendInstruction adds a new instruction, ensuring that |jt| and + // |jf| are within range as necessary for |code|. + Node AppendInstruction(uint16_t code, uint32_t k, Node jt, Node jf); + + // WithinRange returns a node equivalent to |next| that is at most + // |range| instructions away from the (logical) beginning of the + // program. + Node WithinRange(Node next, size_t range); + + // Append appends a new instruction to the physical end (i.e., + // logical beginning) of |program_|. + Node Append(uint16_t code, uint32_t k, size_t jt, size_t jf); + + // Offset returns how many instructions exist in |program_| after |target|. + size_t Offset(Node target) const; + + // NOTE: program_ is the compiled program in *reverse*, so that + // indices remain stable as we add instructions. + Program program_; + + // equivalent_ stores the most recent semantically-equivalent node for each + // instruction in program_. A node is defined as semantically-equivalent to N + // if it has the same instruction code and constant as N and its successor + // nodes (if any) are semantically-equivalent to N's successor nodes, or + // if it's an unconditional jump to a node semantically-equivalent to N. + std::vector<Node> equivalent_; + + std::map<MemoKey, Node, MemoKeyLess> memos_; + + DISALLOW_COPY_AND_ASSIGN(CodeGen); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ diff --git a/sandbox/linux/bpf_dsl/codegen_unittest.cc b/sandbox/linux/bpf_dsl/codegen_unittest.cc new file mode 100644 index 0000000000..5961822123 --- /dev/null +++ b/sandbox/linux/bpf_dsl/codegen_unittest.cc @@ -0,0 +1,402 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/bpf_dsl/codegen.h" + +#include <map> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/md5.h" +#include "base/strings/string_piece.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace { + +// Hash provides an abstraction for building "hash trees" from BPF +// control flow graphs, and efficiently identifying equivalent graphs. +// +// For simplicity, we use MD5, because base happens to provide a +// convenient API for its use. However, any collision-resistant hash +// should suffice. +class Hash { + public: + static const Hash kZero; + + Hash() : digest_() {} + + Hash(uint16_t code, + uint32_t k, + const Hash& jt = kZero, + const Hash& jf = kZero) + : digest_() { + base::MD5Context ctx; + base::MD5Init(&ctx); + HashValue(&ctx, code); + HashValue(&ctx, k); + HashValue(&ctx, jt); + HashValue(&ctx, jf); + base::MD5Final(&digest_, &ctx); + } + + Hash(const Hash& hash) = default; + Hash& operator=(const Hash& rhs) = default; + + friend bool operator==(const Hash& lhs, const Hash& rhs) { + return lhs.Base16() == rhs.Base16(); + } + friend bool operator!=(const Hash& lhs, const Hash& rhs) { + return !(lhs == rhs); + } + + private: + template <typename T> + void HashValue(base::MD5Context* ctx, const T& value) { + base::MD5Update(ctx, + base::StringPiece(reinterpret_cast<const char*>(&value), + sizeof(value))); + } + + std::string Base16() const { + return base::MD5DigestToBase16(digest_); + } + + base::MD5Digest digest_; +}; + +const Hash Hash::kZero; + +// Sanity check that equality and inequality work on Hash as required. +TEST(CodeGen, HashSanity) { + std::vector<Hash> hashes; + + // Push a bunch of logically distinct hashes. + hashes.push_back(Hash::kZero); + for (int i = 0; i < 4; ++i) { + hashes.push_back(Hash(i & 1, i & 2)); + } + for (int i = 0; i < 16; ++i) { + hashes.push_back(Hash(i & 1, i & 2, Hash(i & 4, i & 8))); + } + for (int i = 0; i < 64; ++i) { + hashes.push_back( + Hash(i & 1, i & 2, Hash(i & 4, i & 8), Hash(i & 16, i & 32))); + } + + for (const Hash& a : hashes) { + for (const Hash& b : hashes) { + // Hashes should equal themselves, but not equal all others. + if (&a == &b) { + EXPECT_EQ(a, b); + } else { + EXPECT_NE(a, b); + } + } + } +} + +// ProgramTest provides a fixture for writing compiling sample +// programs with CodeGen and verifying the linearized output matches +// the input DAG. +class ProgramTest : public ::testing::Test { + protected: + ProgramTest() : gen_(), node_hashes_() {} + + // MakeInstruction calls CodeGen::MakeInstruction() and associated + // the returned address with a hash of the instruction. + CodeGen::Node MakeInstruction(uint16_t code, + uint32_t k, + CodeGen::Node jt = CodeGen::kNullNode, + CodeGen::Node jf = CodeGen::kNullNode) { + CodeGen::Node res = gen_.MakeInstruction(code, k, jt, jf); + EXPECT_NE(CodeGen::kNullNode, res); + + Hash digest(code, k, Lookup(jt), Lookup(jf)); + auto it = node_hashes_.insert(std::make_pair(res, digest)); + EXPECT_EQ(digest, it.first->second); + + return res; + } + + // RunTest compiles the program and verifies that the output matches + // what is expected. It should be called at the end of each program + // test case. + void RunTest(CodeGen::Node head) { + // Compile the program + CodeGen::Program program; + gen_.Compile(head, &program); + + // Walk the program backwards, and compute the hash for each instruction. + std::vector<Hash> prog_hashes(program.size()); + for (size_t i = program.size(); i > 0; --i) { + const sock_filter& insn = program.at(i - 1); + Hash& hash = prog_hashes.at(i - 1); + + if (BPF_CLASS(insn.code) == BPF_JMP) { + if (BPF_OP(insn.code) == BPF_JA) { + // The compiler adds JA instructions as needed, so skip them. + hash = prog_hashes.at(i + insn.k); + } else { + hash = Hash(insn.code, insn.k, prog_hashes.at(i + insn.jt), + prog_hashes.at(i + insn.jf)); + } + } else if (BPF_CLASS(insn.code) == BPF_RET) { + hash = Hash(insn.code, insn.k); + } else { + hash = Hash(insn.code, insn.k, prog_hashes.at(i)); + } + } + + EXPECT_EQ(Lookup(head), prog_hashes.at(0)); + } + + private: + const Hash& Lookup(CodeGen::Node next) const { + if (next == CodeGen::kNullNode) { + return Hash::kZero; + } + auto it = node_hashes_.find(next); + if (it == node_hashes_.end()) { + ADD_FAILURE() << "No hash found for node " << next; + return Hash::kZero; + } + return it->second; + } + + CodeGen gen_; + std::map<CodeGen::Node, Hash> node_hashes_; + + DISALLOW_COPY_AND_ASSIGN(ProgramTest); +}; + +TEST_F(ProgramTest, OneInstruction) { + // Create the most basic valid BPF program: + // RET 0 + CodeGen::Node head = MakeInstruction(BPF_RET + BPF_K, 0); + RunTest(head); +} + +TEST_F(ProgramTest, SimpleBranch) { + // Create a program with a single branch: + // JUMP if eq 42 then $0 else $1 + // 0: RET 1 + // 1: RET 0 + CodeGen::Node head = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, + MakeInstruction(BPF_RET + BPF_K, 1), + MakeInstruction(BPF_RET + BPF_K, 0)); + RunTest(head); +} + +TEST_F(ProgramTest, AtypicalBranch) { + // Create a program with a single branch: + // JUMP if eq 42 then $0 else $0 + // 0: RET 0 + + CodeGen::Node ret = MakeInstruction(BPF_RET + BPF_K, 0); + CodeGen::Node head = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, ret, ret); + + // N.B.: As the instructions in both sides of the branch are already + // the same object, we do not actually have any "mergeable" branches. + // This needs to be reflected in our choice of "flags". + RunTest(head); +} + +TEST_F(ProgramTest, Complex) { + // Creates a basic BPF program that we'll use to test some of the code: + // JUMP if eq 42 the $0 else $1 (insn6) + // 0: LD 23 (insn5) + // 1: JUMP if eq 42 then $2 else $4 (insn4) + // 2: JUMP to $3 (insn2) + // 3: LD 42 (insn1) + // RET 42 (insn0) + // 4: LD 42 (insn3) + // RET 42 (insn3+) + CodeGen::Node insn0 = MakeInstruction(BPF_RET + BPF_K, 42); + CodeGen::Node insn1 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 42, insn0); + CodeGen::Node insn2 = insn1; // Implicit JUMP + + // We explicitly duplicate instructions to test that they're merged. + CodeGen::Node insn3 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 42, + MakeInstruction(BPF_RET + BPF_K, 42)); + EXPECT_EQ(insn2, insn3); + + CodeGen::Node insn4 = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn2, insn3); + CodeGen::Node insn5 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 23, insn4); + + // Force a basic block that ends in neither a jump instruction nor a return + // instruction. It only contains "insn5". This exercises one of the less + // common code paths in the topo-sort algorithm. + // This also gives us a diamond-shaped pattern in our graph, which stresses + // another aspect of the topo-sort algorithm (namely, the ability to + // correctly count the incoming branches for subtrees that are not disjunct). + CodeGen::Node insn6 = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 42, insn5, insn4); + + RunTest(insn6); +} + +TEST_F(ProgramTest, ConfusingTails) { + // This simple program demonstrates https://crbug.com/351103/ + // The two "LOAD 0" instructions are blocks of their own. MergeTails() could + // be tempted to merge them since they are the same. However, they are + // not mergeable because they fall-through to non semantically equivalent + // blocks. + // Without the fix for this bug, this program should trigger the check in + // CompileAndCompare: the serialized graphs from the program and its compiled + // version will differ. + // + // 0) LOAD 1 // ??? + // 1) if A == 0x1; then JMP 2 else JMP 3 + // 2) LOAD 0 // System call number + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) LOAD 0 // System call number + // 5) if A == 0x1; then JMP 6 else JMP 7 + // 6) RET 0 + // 7) RET 1 + + CodeGen::Node i7 = MakeInstruction(BPF_RET + BPF_K, 1); + CodeGen::Node i6 = MakeInstruction(BPF_RET + BPF_K, 0); + CodeGen::Node i5 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7); + CodeGen::Node i4 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5); + CodeGen::Node i3 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + CodeGen::Node i2 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3); + CodeGen::Node i1 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + CodeGen::Node i0 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + RunTest(i0); +} + +TEST_F(ProgramTest, ConfusingTailsBasic) { + // Without the fix for https://crbug.com/351103/, (see + // SampleProgramConfusingTails()), this would generate a cyclic graph and + // crash as the two "LOAD 0" instructions would get merged. + // + // 0) LOAD 1 // ??? + // 1) if A == 0x1; then JMP 2 else JMP 3 + // 2) LOAD 0 // System call number + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) LOAD 0 // System call number + // 5) RET 1 + + CodeGen::Node i5 = MakeInstruction(BPF_RET + BPF_K, 1); + CodeGen::Node i4 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i5); + CodeGen::Node i3 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + CodeGen::Node i2 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, i3); + CodeGen::Node i1 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + CodeGen::Node i0 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + RunTest(i0); +} + +TEST_F(ProgramTest, ConfusingTailsMergeable) { + // This is similar to SampleProgramConfusingTails(), except that + // instructions 2 and 4 are now RET instructions. + // In PointerCompare(), this exercises the path where two blocks are of the + // same length and identical and the last instruction is a JMP or RET, so the + // following blocks don't need to be looked at and the blocks are mergeable. + // + // 0) LOAD 1 // ??? + // 1) if A == 0x1; then JMP 2 else JMP 3 + // 2) RET 42 + // 3) if A == 0x2; then JMP 4 else JMP 5 + // 4) RET 42 + // 5) if A == 0x1; then JMP 6 else JMP 7 + // 6) RET 0 + // 7) RET 1 + + CodeGen::Node i7 = MakeInstruction(BPF_RET + BPF_K, 1); + CodeGen::Node i6 = MakeInstruction(BPF_RET + BPF_K, 0); + CodeGen::Node i5 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i6, i7); + CodeGen::Node i4 = MakeInstruction(BPF_RET + BPF_K, 42); + CodeGen::Node i3 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 2, i4, i5); + CodeGen::Node i2 = MakeInstruction(BPF_RET + BPF_K, 42); + CodeGen::Node i1 = MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, i2, i3); + CodeGen::Node i0 = MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 1, i1); + + RunTest(i0); +} + +TEST_F(ProgramTest, InstructionFolding) { + // Check that simple instructions are folded as expected. + CodeGen::Node a = MakeInstruction(BPF_RET + BPF_K, 0); + EXPECT_EQ(a, MakeInstruction(BPF_RET + BPF_K, 0)); + CodeGen::Node b = MakeInstruction(BPF_RET + BPF_K, 1); + EXPECT_EQ(a, MakeInstruction(BPF_RET + BPF_K, 0)); + EXPECT_EQ(b, MakeInstruction(BPF_RET + BPF_K, 1)); + EXPECT_EQ(b, MakeInstruction(BPF_RET + BPF_K, 1)); + + // Check that complex sequences are folded too. + CodeGen::Node c = + MakeInstruction(BPF_LD + BPF_W + BPF_ABS, 0, + MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, 0x100, a, b)); + EXPECT_EQ(c, MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, 0, + MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, 0x100, a, b))); + + RunTest(c); +} + +TEST_F(ProgramTest, FarBranches) { + // BPF instructions use 8-bit fields for branch offsets, which means + // branch targets must be within 255 instructions of the branch + // instruction. CodeGen abstracts away this detail by inserting jump + // instructions as needed, which we test here by generating programs + // that should trigger any interesting boundary conditions. + + // Populate with 260 initial instruction nodes. + std::vector<CodeGen::Node> nodes; + nodes.push_back(MakeInstruction(BPF_RET + BPF_K, 0)); + for (size_t i = 1; i < 260; ++i) { + nodes.push_back( + MakeInstruction(BPF_ALU + BPF_ADD + BPF_K, i, nodes.back())); + } + + // Exhaustively test branch offsets near BPF's limits. + for (size_t jt = 250; jt < 260; ++jt) { + for (size_t jf = 250; jf < 260; ++jf) { + nodes.push_back(MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 0, + nodes.rbegin()[jt], nodes.rbegin()[jf])); + RunTest(nodes.back()); + } + } +} + +TEST_F(ProgramTest, JumpReuse) { + // As a code size optimization, we try to reuse jumps when possible + // instead of emitting new ones. Here we make sure that optimization + // is working as intended. + // + // NOTE: To simplify testing, we rely on implementation details + // about what CodeGen::Node values indicate (i.e., vector indices), + // but CodeGen users should treat them as opaque values. + + // Populate with 260 initial instruction nodes. + std::vector<CodeGen::Node> nodes; + nodes.push_back(MakeInstruction(BPF_RET + BPF_K, 0)); + for (size_t i = 1; i < 260; ++i) { + nodes.push_back( + MakeInstruction(BPF_ALU + BPF_ADD + BPF_K, i, nodes.back())); + } + + // Branching to nodes[0] and nodes[1] should require 3 new + // instructions: two far jumps plus the branch itself. + CodeGen::Node one = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 0, nodes[0], nodes[1]); + EXPECT_EQ(nodes.back() + 3, one); // XXX: Implementation detail! + RunTest(one); + + // Branching again to the same target nodes should require only one + // new instruction, as we can reuse the previous branch's jumps. + CodeGen::Node two = + MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, 1, nodes[0], nodes[1]); + EXPECT_EQ(one + 1, two); // XXX: Implementation detail! + RunTest(two); +} + +} // namespace +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/dump_bpf.cc b/sandbox/linux/bpf_dsl/dump_bpf.cc new file mode 100644 index 0000000000..d0c8f75073 --- /dev/null +++ b/sandbox/linux/bpf_dsl/dump_bpf.cc @@ -0,0 +1,109 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/bpf_dsl/dump_bpf.h" + +#include <stdio.h> + +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace bpf_dsl { + +void DumpBPF::PrintProgram(const CodeGen::Program& program) { + for (CodeGen::Program::const_iterator iter = program.begin(); + iter != program.end(); + ++iter) { + int ip = (int)(iter - program.begin()); + fprintf(stderr, "%3d) ", ip); + switch (BPF_CLASS(iter->code)) { + case BPF_LD: + if (iter->code == BPF_LD + BPF_W + BPF_ABS) { + fprintf(stderr, "LOAD %d // ", (int)iter->k); + if (iter->k == offsetof(struct arch_seccomp_data, nr)) { + fprintf(stderr, "System call number\n"); + } else if (iter->k == offsetof(struct arch_seccomp_data, arch)) { + fprintf(stderr, "Architecture\n"); + } else if (iter->k == + offsetof(struct arch_seccomp_data, instruction_pointer)) { + fprintf(stderr, "Instruction pointer (LSB)\n"); + } else if (iter->k == + offsetof(struct arch_seccomp_data, instruction_pointer) + + 4) { + fprintf(stderr, "Instruction pointer (MSB)\n"); + } else if (iter->k >= offsetof(struct arch_seccomp_data, args) && + iter->k < offsetof(struct arch_seccomp_data, args) + 48 && + (iter->k - offsetof(struct arch_seccomp_data, args)) % 4 == + 0) { + fprintf( + stderr, + "Argument %d (%cSB)\n", + (int)(iter->k - offsetof(struct arch_seccomp_data, args)) / 8, + (iter->k - offsetof(struct arch_seccomp_data, args)) % 8 ? 'M' + : 'L'); + } else { + fprintf(stderr, "???\n"); + } + } else { + fprintf(stderr, "LOAD ???\n"); + } + break; + case BPF_JMP: + if (BPF_OP(iter->code) == BPF_JA) { + fprintf(stderr, "JMP %d\n", ip + iter->k + 1); + } else { + fprintf(stderr, "if A %s 0x%x; then JMP %d else JMP %d\n", + BPF_OP(iter->code) == BPF_JSET ? "&" : + BPF_OP(iter->code) == BPF_JEQ ? "==" : + BPF_OP(iter->code) == BPF_JGE ? ">=" : + BPF_OP(iter->code) == BPF_JGT ? ">" : "???", + (int)iter->k, + ip + iter->jt + 1, ip + iter->jf + 1); + } + break; + case BPF_RET: + fprintf(stderr, "RET 0x%x // ", iter->k); + if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP) { + fprintf(stderr, "Trap #%d\n", iter->k & SECCOMP_RET_DATA); + } else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + fprintf(stderr, "errno = %d\n", iter->k & SECCOMP_RET_DATA); + } else if ((iter->k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE) { + fprintf(stderr, "Trace #%d\n", iter->k & SECCOMP_RET_DATA); + } else if (iter->k == SECCOMP_RET_ALLOW) { + fprintf(stderr, "Allowed\n"); + } else { + fprintf(stderr, "???\n"); + } + break; + case BPF_ALU: + if (BPF_OP(iter->code) == BPF_NEG) { + fprintf(stderr, "A := -A\n"); + } else { + fprintf(stderr, "A := A %s 0x%x\n", + BPF_OP(iter->code) == BPF_ADD ? "+" : + BPF_OP(iter->code) == BPF_SUB ? "-" : + BPF_OP(iter->code) == BPF_MUL ? "*" : + BPF_OP(iter->code) == BPF_DIV ? "/" : + BPF_OP(iter->code) == BPF_MOD ? "%" : + BPF_OP(iter->code) == BPF_OR ? "|" : + BPF_OP(iter->code) == BPF_XOR ? "^" : + BPF_OP(iter->code) == BPF_AND ? "&" : + BPF_OP(iter->code) == BPF_LSH ? "<<" : + BPF_OP(iter->code) == BPF_RSH ? ">>" : "???", + (int)iter->k); + } + break; + default: + fprintf(stderr, "???\n"); + break; + } + } + return; +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/dump_bpf.h b/sandbox/linux/bpf_dsl/dump_bpf.h new file mode 100644 index 0000000000..cd12be793d --- /dev/null +++ b/sandbox/linux/bpf_dsl/dump_bpf.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +class SANDBOX_EXPORT DumpBPF { + public: + // PrintProgram writes |program| in a human-readable format to stderr. + static void PrintProgram(const CodeGen::Program& program); +}; + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/policy.cc b/sandbox/linux/bpf_dsl/policy.cc new file mode 100644 index 0000000000..c20edc6da8 --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy.cc @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/bpf_dsl/policy.h" + +#include <errno.h> + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" + +namespace sandbox { +namespace bpf_dsl { + +ResultExpr Policy::InvalidSyscall() const { + return Error(ENOSYS); +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/policy.h b/sandbox/linux/bpf_dsl/policy.h new file mode 100644 index 0000000000..6c67589456 --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_POLICY_H_ +#define SANDBOX_LINUX_BPF_DSL_POLICY_H_ + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +// Interface to implement to define a BPF sandbox policy. +class SANDBOX_EXPORT Policy { + public: + Policy() {} + virtual ~Policy() {} + + // User extension point for writing custom sandbox policies. + // The returned ResultExpr will control how the kernel responds to the + // specified system call number. + virtual ResultExpr EvaluateSyscall(int sysno) const = 0; + + // Optional overload for specifying alternate behavior for invalid + // system calls. The default is to return ENOSYS. + virtual ResultExpr InvalidSyscall() const; + + private: + DISALLOW_COPY_AND_ASSIGN(Policy); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_POLICY_H_ diff --git a/sandbox/linux/bpf_dsl/policy_compiler.cc b/sandbox/linux/bpf_dsl/policy_compiler.cc new file mode 100644 index 0000000000..f38232f85f --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy_compiler.cc @@ -0,0 +1,499 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/bpf_dsl/policy_compiler.h" + +#include <errno.h> +#include <sys/syscall.h> + +#include <limits> + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/dump_bpf.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/bpf_dsl/verifier.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +#if defined(__i386__) || defined(__x86_64__) +const bool kIsIntel = true; +#else +const bool kIsIntel = false; +#endif +#if defined(__x86_64__) && defined(__ILP32__) +const bool kIsX32 = true; +#else +const bool kIsX32 = false; +#endif + +const int kSyscallsRequiredForUnsafeTraps[] = { + __NR_rt_sigprocmask, + __NR_rt_sigreturn, +#if defined(__NR_sigprocmask) + __NR_sigprocmask, +#endif +#if defined(__NR_sigreturn) + __NR_sigreturn, +#endif +}; + +bool HasExactlyOneBit(uint64_t x) { + // Common trick; e.g., see http://stackoverflow.com/a/108329. + return x != 0 && (x & (x - 1)) == 0; +} + +// A Trap() handler that returns an "errno" value. The value is encoded +// in the "aux" parameter. +intptr_t ReturnErrno(const struct arch_seccomp_data&, void* aux) { + // TrapFnc functions report error by following the native kernel convention + // of returning an exit code in the range of -1..-4096. They do not try to + // set errno themselves. The glibc wrapper that triggered the SIGSYS will + // ultimately do so for us. + int err = reinterpret_cast<intptr_t>(aux) & SECCOMP_RET_DATA; + return -err; +} + +bool HasUnsafeTraps(const Policy* policy) { + DCHECK(policy); + for (uint32_t sysnum : SyscallSet::ValidOnly()) { + if (policy->EvaluateSyscall(sysnum)->HasUnsafeTraps()) { + return true; + } + } + return policy->InvalidSyscall()->HasUnsafeTraps(); +} + +} // namespace + +struct PolicyCompiler::Range { + uint32_t from; + CodeGen::Node node; +}; + +PolicyCompiler::PolicyCompiler(const Policy* policy, TrapRegistry* registry) + : policy_(policy), + registry_(registry), + escapepc_(0), + conds_(), + gen_(), + has_unsafe_traps_(HasUnsafeTraps(policy_)) { + DCHECK(policy); +} + +PolicyCompiler::~PolicyCompiler() { +} + +scoped_ptr<CodeGen::Program> PolicyCompiler::Compile(bool verify) { + CHECK(policy_->InvalidSyscall()->IsDeny()) + << "Policies should deny invalid system calls"; + + // If our BPF program has unsafe traps, enable support for them. + if (has_unsafe_traps_) { + CHECK_NE(0U, escapepc_) << "UnsafeTrap() requires a valid escape PC"; + + for (int sysnum : kSyscallsRequiredForUnsafeTraps) { + CHECK(policy_->EvaluateSyscall(sysnum)->IsAllow()) + << "Policies that use UnsafeTrap() must unconditionally allow all " + "required system calls"; + } + + CHECK(registry_->EnableUnsafeTraps()) + << "We'd rather die than enable unsafe traps"; + } + + // Assemble the BPF filter program. + scoped_ptr<CodeGen::Program> program(new CodeGen::Program()); + gen_.Compile(AssemblePolicy(), program.get()); + + // Make sure compilation resulted in a BPF program that executes + // correctly. Otherwise, there is an internal error in our BPF compiler. + // There is really nothing the caller can do until the bug is fixed. + if (verify) { + const char* err = nullptr; + if (!Verifier::VerifyBPF(this, *program, *policy_, &err)) { + DumpBPF::PrintProgram(*program); + LOG(FATAL) << err; + } + } + + return program.Pass(); +} + +void PolicyCompiler::DangerousSetEscapePC(uint64_t escapepc) { + escapepc_ = escapepc; +} + +CodeGen::Node PolicyCompiler::AssemblePolicy() { + // A compiled policy consists of three logical parts: + // 1. Check that the "arch" field matches the expected architecture. + // 2. If the policy involves unsafe traps, check if the syscall was + // invoked by Syscall::Call, and then allow it unconditionally. + // 3. Check the system call number and jump to the appropriate compiled + // system call policy number. + return CheckArch(MaybeAddEscapeHatch(DispatchSyscall())); +} + +CodeGen::Node PolicyCompiler::CheckArch(CodeGen::Node passed) { + // If the architecture doesn't match SECCOMP_ARCH, disallow the + // system call. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_ARCH_IDX, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_ARCH, passed, + CompileResult(Kill("Invalid audit architecture in BPF filter")))); +} + +CodeGen::Node PolicyCompiler::MaybeAddEscapeHatch(CodeGen::Node rest) { + // If no unsafe traps, then simply return |rest|. + if (!has_unsafe_traps_) { + return rest; + } + + // We already enabled unsafe traps in Compile, but enable them again to give + // the trap registry a second chance to complain before we add the backdoor. + CHECK(registry_->EnableUnsafeTraps()); + + // Allow system calls, if they originate from our magic return address. + const uint32_t lopc = static_cast<uint32_t>(escapepc_); + const uint32_t hipc = static_cast<uint32_t>(escapepc_ >> 32); + + // BPF cannot do native 64-bit comparisons, so we have to compare + // both 32-bit halves of the instruction pointer. If they match what + // we expect, we return ERR_ALLOWED. If either or both don't match, + // we continue evalutating the rest of the sandbox policy. + // + // For simplicity, we check the full 64-bit instruction pointer even + // on 32-bit architectures. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_LSB_IDX, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, lopc, + gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_MSB_IDX, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, hipc, + CompileResult(Allow()), rest)), + rest)); +} + +CodeGen::Node PolicyCompiler::DispatchSyscall() { + // Evaluate all possible system calls and group their ErrorCodes into + // ranges of identical codes. + Ranges ranges; + FindRanges(&ranges); + + // Compile the system call ranges to an optimized BPF jumptable + CodeGen::Node jumptable = AssembleJumpTable(ranges.begin(), ranges.end()); + + // Grab the system call number, so that we can check it and then + // execute the jump table. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_NR_IDX, CheckSyscallNumber(jumptable)); +} + +CodeGen::Node PolicyCompiler::CheckSyscallNumber(CodeGen::Node passed) { + if (kIsIntel) { + // On Intel architectures, verify that system call numbers are in the + // expected number range. + CodeGen::Node invalidX32 = + CompileResult(Kill("Illegal mixing of system call ABIs")); + if (kIsX32) { + // The newer x32 API always sets bit 30. + return gen_.MakeInstruction( + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, passed, invalidX32); + } else { + // The older i386 and x86-64 APIs clear bit 30 on all system calls. + return gen_.MakeInstruction( + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, invalidX32, passed); + } + } + + // TODO(mdempsky): Similar validation for other architectures? + return passed; +} + +void PolicyCompiler::FindRanges(Ranges* ranges) { + // Please note that "struct seccomp_data" defines system calls as a signed + // int32_t, but BPF instructions always operate on unsigned quantities. We + // deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL, + // and then verifying that the rest of the number range (both positive and + // negative) all return the same ErrorCode. + const CodeGen::Node invalid_node = CompileResult(policy_->InvalidSyscall()); + uint32_t old_sysnum = 0; + CodeGen::Node old_node = + SyscallSet::IsValid(old_sysnum) + ? CompileResult(policy_->EvaluateSyscall(old_sysnum)) + : invalid_node; + + for (uint32_t sysnum : SyscallSet::All()) { + CodeGen::Node node = + SyscallSet::IsValid(sysnum) + ? CompileResult(policy_->EvaluateSyscall(static_cast<int>(sysnum))) + : invalid_node; + // N.B., here we rely on CodeGen folding (i.e., returning the same + // node value for) identical code sequences, otherwise our jump + // table will blow up in size. + if (node != old_node) { + ranges->push_back(Range{old_sysnum, old_node}); + old_sysnum = sysnum; + old_node = node; + } + } + ranges->push_back(Range{old_sysnum, old_node}); +} + +CodeGen::Node PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start, + Ranges::const_iterator stop) { + // We convert the list of system call ranges into jump table that performs + // a binary search over the ranges. + // As a sanity check, we need to have at least one distinct ranges for us + // to be able to build a jump table. + CHECK(start < stop) << "Invalid iterator range"; + const auto n = stop - start; + if (n == 1) { + // If we have narrowed things down to a single range object, we can + // return from the BPF filter program. + return start->node; + } + + // Pick the range object that is located at the mid point of our list. + // We compare our system call number against the lowest valid system call + // number in this range object. If our number is lower, it is outside of + // this range object. If it is greater or equal, it might be inside. + Ranges::const_iterator mid = start + n / 2; + + // Sub-divide the list of ranges and continue recursively. + CodeGen::Node jf = AssembleJumpTable(start, mid); + CodeGen::Node jt = AssembleJumpTable(mid, stop); + return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf); +} + +CodeGen::Node PolicyCompiler::CompileResult(const ResultExpr& res) { + return RetExpression(res->Compile(this)); +} + +CodeGen::Node PolicyCompiler::RetExpression(const ErrorCode& err) { + switch (err.error_type()) { + case ErrorCode::ET_COND: + return CondExpression(err); + case ErrorCode::ET_SIMPLE: + case ErrorCode::ET_TRAP: + return gen_.MakeInstruction(BPF_RET + BPF_K, err.err()); + default: + LOG(FATAL) + << "ErrorCode is not suitable for returning from a BPF program"; + return CodeGen::kNullNode; + } +} + +CodeGen::Node PolicyCompiler::CondExpression(const ErrorCode& cond) { + // Sanity check that |cond| makes sense. + CHECK(cond.argno_ >= 0 && cond.argno_ < 6) << "Invalid argument number " + << cond.argno_; + CHECK(cond.width_ == ErrorCode::TP_32BIT || + cond.width_ == ErrorCode::TP_64BIT) + << "Invalid argument width " << cond.width_; + CHECK_NE(0U, cond.mask_) << "Zero mask is invalid"; + CHECK_EQ(cond.value_, cond.value_ & cond.mask_) + << "Value contains masked out bits"; + if (sizeof(void*) == 4) { + CHECK_EQ(ErrorCode::TP_32BIT, cond.width_) + << "Invalid width on 32-bit platform"; + } + if (cond.width_ == ErrorCode::TP_32BIT) { + CHECK_EQ(0U, cond.mask_ >> 32) << "Mask exceeds argument size"; + CHECK_EQ(0U, cond.value_ >> 32) << "Value exceeds argument size"; + } + + CodeGen::Node passed = RetExpression(*cond.passed_); + CodeGen::Node failed = RetExpression(*cond.failed_); + + // We want to emit code to check "(arg & mask) == value" where arg, mask, and + // value are 64-bit values, but the BPF machine is only 32-bit. We implement + // this by independently testing the upper and lower 32-bits and continuing to + // |passed| if both evaluate true, or to |failed| if either evaluate false. + return CondExpressionHalf(cond, + UpperHalf, + CondExpressionHalf(cond, LowerHalf, passed, failed), + failed); +} + +CodeGen::Node PolicyCompiler::CondExpressionHalf(const ErrorCode& cond, + ArgHalf half, + CodeGen::Node passed, + CodeGen::Node failed) { + if (cond.width_ == ErrorCode::TP_32BIT && half == UpperHalf) { + // Special logic for sanity checking the upper 32-bits of 32-bit system + // call arguments. + + // TODO(mdempsky): Compile Unexpected64bitArgument() just per program. + CodeGen::Node invalid_64bit = RetExpression(Unexpected64bitArgument()); + + const uint32_t upper = SECCOMP_ARG_MSB_IDX(cond.argno_); + const uint32_t lower = SECCOMP_ARG_LSB_IDX(cond.argno_); + + if (sizeof(void*) == 4) { + // On 32-bit platforms, the upper 32-bits should always be 0: + // LDW [upper] + // JEQ 0, passed, invalid + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + upper, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, 0, passed, invalid_64bit)); + } + + // On 64-bit platforms, the upper 32-bits may be 0 or ~0; but we only allow + // ~0 if the sign bit of the lower 32-bits is set too: + // LDW [upper] + // JEQ 0, passed, (next) + // JEQ ~0, (next), invalid + // LDW [lower] + // JSET (1<<31), passed, invalid + // + // TODO(mdempsky): The JSET instruction could perhaps jump to passed->next + // instead, as the first instruction of passed should be "LDW [lower]". + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + upper, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + 0, + passed, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + std::numeric_limits<uint32_t>::max(), + gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + lower, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, + 1U << 31, + passed, + invalid_64bit)), + invalid_64bit))); + } + + const uint32_t idx = (half == UpperHalf) ? SECCOMP_ARG_MSB_IDX(cond.argno_) + : SECCOMP_ARG_LSB_IDX(cond.argno_); + const uint32_t mask = (half == UpperHalf) ? cond.mask_ >> 32 : cond.mask_; + const uint32_t value = (half == UpperHalf) ? cond.value_ >> 32 : cond.value_; + + // Emit a suitable instruction sequence for (arg & mask) == value. + + // For (arg & 0) == 0, just return passed. + if (mask == 0) { + CHECK_EQ(0U, value); + return passed; + } + + // For (arg & ~0) == value, emit: + // LDW [idx] + // JEQ value, passed, failed + if (mask == std::numeric_limits<uint32_t>::max()) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed)); + } + + // For (arg & mask) == 0, emit: + // LDW [idx] + // JSET mask, failed, passed + // (Note: failed and passed are intentionally swapped.) + if (value == 0) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, failed, passed)); + } + + // For (arg & x) == x where x is a single-bit value, emit: + // LDW [idx] + // JSET mask, passed, failed + if (mask == value && HasExactlyOneBit(mask)) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, passed, failed)); + } + + // Generic fallback: + // LDW [idx] + // AND mask + // JEQ value, passed, failed + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction( + BPF_ALU + BPF_AND + BPF_K, + mask, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed))); +} + +ErrorCode PolicyCompiler::Unexpected64bitArgument() { + return Kill("Unexpected 64bit argument detected")->Compile(this); +} + +ErrorCode PolicyCompiler::Error(int err) { + if (has_unsafe_traps_) { + // When inside an UnsafeTrap() callback, we want to allow all system calls. + // This means, we must conditionally disable the sandbox -- and that's not + // something that kernel-side BPF filters can do, as they cannot inspect + // any state other than the syscall arguments. + // But if we redirect all error handlers to user-space, then we can easily + // make this decision. + // The performance penalty for this extra round-trip to user-space is not + // actually that bad, as we only ever pay it for denied system calls; and a + // typical program has very few of these. + return Trap(ReturnErrno, reinterpret_cast<void*>(err), true); + } + + return ErrorCode(err); +} + +ErrorCode PolicyCompiler::Trap(TrapRegistry::TrapFnc fnc, + const void* aux, + bool safe) { + uint16_t trap_id = registry_->Add(fnc, aux, safe); + return ErrorCode(trap_id, fnc, aux, safe); +} + +bool PolicyCompiler::IsRequiredForUnsafeTrap(int sysno) { + for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) { + if (sysno == kSyscallsRequiredForUnsafeTraps[i]) { + return true; + } + } + return false; +} + +ErrorCode PolicyCompiler::CondMaskedEqual(int argno, + ErrorCode::ArgType width, + uint64_t mask, + uint64_t value, + const ErrorCode& passed, + const ErrorCode& failed) { + return ErrorCode(argno, + width, + mask, + value, + &*conds_.insert(passed).first, + &*conds_.insert(failed).first); +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/policy_compiler.h b/sandbox/linux/bpf_dsl/policy_compiler.h new file mode 100644 index 0000000000..df38d4ccbc --- /dev/null +++ b/sandbox/linux/bpf_dsl/policy_compiler.h @@ -0,0 +1,159 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ +#define SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ + +#include <stdint.h> + +#include <map> +#include <set> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { +class Policy; + +// PolicyCompiler implements the bpf_dsl compiler, allowing users to +// transform bpf_dsl policies into BPF programs to be executed by the +// Linux kernel. +class SANDBOX_EXPORT PolicyCompiler { + public: + PolicyCompiler(const Policy* policy, TrapRegistry* registry); + ~PolicyCompiler(); + + // Compile registers any trap handlers needed by the policy and + // compiles the policy to a BPF program, which it returns. + scoped_ptr<CodeGen::Program> Compile(bool verify); + + // DangerousSetEscapePC sets the "escape PC" that is allowed to issue any + // system calls, regardless of policy. + void DangerousSetEscapePC(uint64_t escapepc); + + // Error returns an ErrorCode to indicate the system call should fail with + // the specified error number. + ErrorCode Error(int err); + + // Trap returns an ErrorCode to indicate the system call should + // instead invoke a trap handler. + ErrorCode Trap(TrapRegistry::TrapFnc fnc, const void* aux, bool safe); + + // UnsafeTraps require some syscalls to always be allowed. + // This helper function returns true for these calls. + static bool IsRequiredForUnsafeTrap(int sysno); + + // We can also use ErrorCode to request evaluation of a conditional + // statement based on inspection of system call parameters. + // This method wrap an ErrorCode object around the conditional statement. + // Argument "argno" (1..6) will be bitwise-AND'd with "mask" and compared + // to "value"; if equal, then "passed" will be returned, otherwise "failed". + // If "is32bit" is set, the argument must in the range of 0x0..(1u << 32 - 1) + // If it is outside this range, the sandbox treats the system call just + // the same as any other ABI violation (i.e. it aborts with an error + // message). + ErrorCode CondMaskedEqual(int argno, + ErrorCode::ArgType is_32bit, + uint64_t mask, + uint64_t value, + const ErrorCode& passed, + const ErrorCode& failed); + + // Returns the fatal ErrorCode that is used to indicate that somebody + // attempted to pass a 64bit value in a 32bit system call argument. + // This method is primarily needed for testing purposes. + ErrorCode Unexpected64bitArgument(); + + private: + struct Range; + typedef std::vector<Range> Ranges; + typedef std::set<ErrorCode, struct ErrorCode::LessThan> Conds; + + // Used by CondExpressionHalf to track which half of the argument it's + // emitting instructions for. + enum ArgHalf { + LowerHalf, + UpperHalf, + }; + + // Compile the configured policy into a complete instruction sequence. + CodeGen::Node AssemblePolicy(); + + // Return an instruction sequence that checks the + // arch_seccomp_data's "arch" field is valid, and then passes + // control to |passed| if so. + CodeGen::Node CheckArch(CodeGen::Node passed); + + // If |has_unsafe_traps_| is true, returns an instruction sequence + // that allows all system calls from |escapepc_|, and otherwise + // passes control to |rest|. Otherwise, simply returns |rest|. + CodeGen::Node MaybeAddEscapeHatch(CodeGen::Node rest); + + // Return an instruction sequence that loads and checks the system + // call number, performs a binary search, and then dispatches to an + // appropriate instruction sequence compiled from the current + // policy. + CodeGen::Node DispatchSyscall(); + + // Return an instruction sequence that checks the system call number + // (expected to be loaded in register A) and if valid, passes + // control to |passed| (with register A still valid). + CodeGen::Node CheckSyscallNumber(CodeGen::Node passed); + + // Finds all the ranges of system calls that need to be handled. Ranges are + // sorted in ascending order of system call numbers. There are no gaps in the + // ranges. System calls with identical ErrorCodes are coalesced into a single + // range. + void FindRanges(Ranges* ranges); + + // Returns a BPF program snippet that implements a jump table for the + // given range of system call numbers. This function runs recursively. + CodeGen::Node AssembleJumpTable(Ranges::const_iterator start, + Ranges::const_iterator stop); + + // CompileResult compiles an individual result expression into a + // CodeGen node. + CodeGen::Node CompileResult(const ResultExpr& res); + + // Returns a BPF program snippet that makes the BPF filter program exit + // with the given ErrorCode "err". N.B. the ErrorCode may very well be a + // conditional expression; if so, this function will recursively call + // CondExpression() and possibly RetExpression() to build a complex set of + // instructions. + CodeGen::Node RetExpression(const ErrorCode& err); + + // Returns a BPF program that evaluates the conditional expression in + // "cond" and returns the appropriate value from the BPF filter program. + // This function recursively calls RetExpression(); it should only ever be + // called from RetExpression(). + CodeGen::Node CondExpression(const ErrorCode& cond); + + // Returns a BPF program that evaluates half of a conditional expression; + // it should only ever be called from CondExpression(). + CodeGen::Node CondExpressionHalf(const ErrorCode& cond, + ArgHalf half, + CodeGen::Node passed, + CodeGen::Node failed); + + const Policy* policy_; + TrapRegistry* registry_; + uint64_t escapepc_; + + Conds conds_; + CodeGen gen_; + bool has_unsafe_traps_; + + DISALLOW_COPY_AND_ASSIGN(PolicyCompiler); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ diff --git a/sandbox/linux/bpf_dsl/syscall_set.cc b/sandbox/linux/bpf_dsl/syscall_set.cc new file mode 100644 index 0000000000..47810e99ac --- /dev/null +++ b/sandbox/linux/bpf_dsl/syscall_set.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/bpf_dsl/syscall_set.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/linux_syscall_ranges.h" + +namespace sandbox { + +namespace { + +#if defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) +// This is true for Mips O32 ABI. +static_assert(MIN_SYSCALL == __NR_Linux, "min syscall number should be 4000"); +#else +// This true for supported architectures (Intel and ARM EABI). +static_assert(MIN_SYSCALL == 0u, + "min syscall should always be zero"); +#endif + +// SyscallRange represents an inclusive range of system call numbers. +struct SyscallRange { + uint32_t first; + uint32_t last; +}; + +const SyscallRange kValidSyscallRanges[] = { + // First we iterate up to MAX_PUBLIC_SYSCALL, which is equal to MAX_SYSCALL + // on Intel architectures, but leaves room for private syscalls on ARM. + {MIN_SYSCALL, MAX_PUBLIC_SYSCALL}, +#if defined(__arm__) + // ARM EABI includes "ARM private" system calls starting at + // MIN_PRIVATE_SYSCALL, and a "ghost syscall private to the kernel" at + // MIN_GHOST_SYSCALL. + {MIN_PRIVATE_SYSCALL, MAX_PRIVATE_SYSCALL}, + {MIN_GHOST_SYSCALL, MAX_SYSCALL}, +#endif +}; + +} // namespace + +SyscallSet::Iterator SyscallSet::begin() const { + return Iterator(set_, false); +} + +SyscallSet::Iterator SyscallSet::end() const { + return Iterator(set_, true); +} + +bool SyscallSet::IsValid(uint32_t num) { + for (const SyscallRange& range : kValidSyscallRanges) { + if (num >= range.first && num <= range.last) { + return true; + } + } + return false; +} + +bool operator==(const SyscallSet& lhs, const SyscallSet& rhs) { + return (lhs.set_ == rhs.set_); +} + +SyscallSet::Iterator::Iterator(Set set, bool done) + : set_(set), done_(done), num_(0) { + // If the set doesn't contain 0, we need to skip to the next element. + if (!done && set_ == (IsValid(num_) ? Set::INVALID_ONLY : Set::VALID_ONLY)) { + ++*this; + } +} + +uint32_t SyscallSet::Iterator::operator*() const { + DCHECK(!done_); + return num_; +} + +SyscallSet::Iterator& SyscallSet::Iterator::operator++() { + DCHECK(!done_); + + num_ = NextSyscall(); + if (num_ == 0) { + done_ = true; + } + + return *this; +} + +// NextSyscall returns the next system call in the iterated system +// call set after |num_|, or 0 if no such system call exists. +uint32_t SyscallSet::Iterator::NextSyscall() const { + const bool want_valid = (set_ != Set::INVALID_ONLY); + const bool want_invalid = (set_ != Set::VALID_ONLY); + + for (const SyscallRange& range : kValidSyscallRanges) { + if (want_invalid && range.first > 0 && num_ < range.first - 1) { + // Even when iterating invalid syscalls, we only include the end points; + // so skip directly to just before the next (valid) range. + return range.first - 1; + } + if (want_valid && num_ < range.first) { + return range.first; + } + if (want_valid && num_ < range.last) { + return num_ + 1; + } + if (want_invalid && num_ <= range.last) { + return range.last + 1; + } + } + + if (want_invalid) { + // BPF programs only ever operate on unsigned quantities. So, + // that's how we iterate; we return values from + // 0..0xFFFFFFFFu. But there are places, where the kernel might + // interpret system call numbers as signed quantities, so the + // boundaries between signed and unsigned values are potential + // problem cases. We want to explicitly return these values from + // our iterator. + if (num_ < 0x7FFFFFFFu) + return 0x7FFFFFFFu; + if (num_ < 0x80000000u) + return 0x80000000u; + + if (num_ < 0xFFFFFFFFu) + return 0xFFFFFFFFu; + } + + return 0; +} + +bool operator==(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs) { + DCHECK(lhs.set_ == rhs.set_); + return (lhs.done_ == rhs.done_) && (lhs.num_ == rhs.num_); +} + +bool operator!=(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs) { + return !(lhs == rhs); +} + +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/syscall_set.h b/sandbox/linux/bpf_dsl/syscall_set.h new file mode 100644 index 0000000000..b9f076d932 --- /dev/null +++ b/sandbox/linux/bpf_dsl/syscall_set.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ +#define SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ + +#include <stdint.h> + +#include <iterator> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Iterates over the entire system call range from 0..0xFFFFFFFFu. This +// iterator is aware of how system calls look like and will skip quickly +// over ranges that can't contain system calls. It iterates more slowly +// whenever it reaches a range that is potentially problematic, returning +// the last invalid value before a valid range of system calls, and the +// first invalid value after a valid range of syscalls. It iterates over +// individual values whenever it is in the normal range for system calls +// (typically MIN_SYSCALL..MAX_SYSCALL). +// +// Example usage: +// for (uint32_t sysnum : SyscallSet::All()) { +// // Do something with sysnum. +// } +class SANDBOX_EXPORT SyscallSet { + public: + class Iterator; + + SyscallSet(const SyscallSet& ss) : set_(ss.set_) {} + ~SyscallSet() {} + + Iterator begin() const; + Iterator end() const; + + // All returns a SyscallSet that contains both valid and invalid + // system call numbers. + static SyscallSet All() { return SyscallSet(Set::ALL); } + + // ValidOnly returns a SyscallSet that contains only valid system + // call numbers. + static SyscallSet ValidOnly() { return SyscallSet(Set::VALID_ONLY); } + + // InvalidOnly returns a SyscallSet that contains only invalid + // system call numbers, but still omits numbers in the middle of a + // range of invalid system call numbers. + static SyscallSet InvalidOnly() { return SyscallSet(Set::INVALID_ONLY); } + + // IsValid returns whether |num| specifies a valid system call + // number. + static bool IsValid(uint32_t num); + + private: + enum class Set { ALL, VALID_ONLY, INVALID_ONLY }; + + explicit SyscallSet(Set set) : set_(set) {} + + Set set_; + + friend bool operator==(const SyscallSet&, const SyscallSet&); + DISALLOW_ASSIGN(SyscallSet); +}; + +SANDBOX_EXPORT bool operator==(const SyscallSet& lhs, const SyscallSet& rhs); + +// Iterator provides C++ input iterator semantics for traversing a +// SyscallSet. +class SyscallSet::Iterator + : public std::iterator<std::input_iterator_tag, uint32_t> { + public: + Iterator(const Iterator& it) + : set_(it.set_), done_(it.done_), num_(it.num_) {} + ~Iterator() {} + + uint32_t operator*() const; + Iterator& operator++(); + + private: + Iterator(Set set, bool done); + + uint32_t NextSyscall() const; + + Set set_; + bool done_; + uint32_t num_; + + friend SyscallSet; + friend bool operator==(const Iterator&, const Iterator&); + DISALLOW_ASSIGN(Iterator); +}; + +SANDBOX_EXPORT bool operator==(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs); +SANDBOX_EXPORT bool operator!=(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ diff --git a/sandbox/linux/bpf_dsl/syscall_set_unittest.cc b/sandbox/linux/bpf_dsl/syscall_set_unittest.cc new file mode 100644 index 0000000000..fafb6f6f73 --- /dev/null +++ b/sandbox/linux/bpf_dsl/syscall_set_unittest.cc @@ -0,0 +1,124 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/bpf_dsl/syscall_set.h" + +#include <stdint.h> + +#include "sandbox/linux/bpf_dsl/linux_syscall_ranges.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +const SyscallSet kSyscallSets[] = { + SyscallSet::All(), + SyscallSet::InvalidOnly(), +}; + +SANDBOX_TEST(SyscallSet, Monotonous) { + for (const SyscallSet& set : kSyscallSets) { + uint32_t prev = 0; + bool have_prev = false; + for (uint32_t sysnum : set) { + if (have_prev) { + SANDBOX_ASSERT(sysnum > prev); + } else if (set == SyscallSet::All()) { + // The iterator should start at 0. + SANDBOX_ASSERT(sysnum == 0); + } + + prev = sysnum; + have_prev = true; + } + + // The iterator should always return 0xFFFFFFFFu as the last value. + SANDBOX_ASSERT(have_prev); + SANDBOX_ASSERT(prev == 0xFFFFFFFFu); + } +} + +// AssertRange checks that SyscallIterator produces all system call +// numbers in the inclusive range [min, max]. +void AssertRange(uint32_t min, uint32_t max) { + SANDBOX_ASSERT(min < max); + uint32_t prev = min - 1; + for (uint32_t sysnum : SyscallSet::All()) { + if (sysnum >= min && sysnum <= max) { + SANDBOX_ASSERT(prev == sysnum - 1); + prev = sysnum; + } + } + SANDBOX_ASSERT(prev == max); +} + +SANDBOX_TEST(SyscallSet, ValidSyscallRanges) { + AssertRange(MIN_SYSCALL, MAX_PUBLIC_SYSCALL); +#if defined(__arm__) + AssertRange(MIN_PRIVATE_SYSCALL, MAX_PRIVATE_SYSCALL); + AssertRange(MIN_GHOST_SYSCALL, MAX_SYSCALL); +#endif +} + +SANDBOX_TEST(SyscallSet, InvalidSyscalls) { + static const uint32_t kExpected[] = { +#if defined(__mips__) + 0, + MIN_SYSCALL - 1, +#endif + MAX_PUBLIC_SYSCALL + 1, +#if defined(__arm__) + MIN_PRIVATE_SYSCALL - 1, + MAX_PRIVATE_SYSCALL + 1, + MIN_GHOST_SYSCALL - 1, + MAX_SYSCALL + 1, +#endif + 0x7FFFFFFFu, + 0x80000000u, + 0xFFFFFFFFu, + }; + + for (const SyscallSet& set : kSyscallSets) { + size_t i = 0; + for (uint32_t sysnum : set) { + if (!SyscallSet::IsValid(sysnum)) { + SANDBOX_ASSERT(i < arraysize(kExpected)); + SANDBOX_ASSERT(kExpected[i] == sysnum); + ++i; + } + } + SANDBOX_ASSERT(i == arraysize(kExpected)); + } +} + +SANDBOX_TEST(SyscallSet, ValidOnlyIsOnlyValid) { + for (uint32_t sysnum : SyscallSet::ValidOnly()) { + SANDBOX_ASSERT(SyscallSet::IsValid(sysnum)); + } +} + +SANDBOX_TEST(SyscallSet, InvalidOnlyIsOnlyInvalid) { + for (uint32_t sysnum : SyscallSet::InvalidOnly()) { + SANDBOX_ASSERT(!SyscallSet::IsValid(sysnum)); + } +} + +SANDBOX_TEST(SyscallSet, AllIsValidOnlyPlusInvalidOnly) { + std::vector<uint32_t> merged; + const SyscallSet valid_only = SyscallSet::ValidOnly(); + const SyscallSet invalid_only = SyscallSet::InvalidOnly(); + std::merge(valid_only.begin(), + valid_only.end(), + invalid_only.begin(), + invalid_only.end(), + std::back_inserter(merged)); + + const SyscallSet all = SyscallSet::All(); + SANDBOX_ASSERT(merged == std::vector<uint32_t>(all.begin(), all.end())); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/verifier.cc b/sandbox/linux/bpf_dsl/verifier.cc new file mode 100644 index 0000000000..417c663e30 --- /dev/null +++ b/sandbox/linux/bpf_dsl/verifier.cc @@ -0,0 +1,396 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/bpf_dsl/verifier.h" + +#include <string.h> + +#include <limits> + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +const uint64_t kLower32Bits = std::numeric_limits<uint32_t>::max(); +const uint64_t kUpper32Bits = static_cast<uint64_t>(kLower32Bits) << 32; + +struct State { + State(const std::vector<struct sock_filter>& p, + const struct arch_seccomp_data& d) + : program(p), data(d), ip(0), accumulator(0), acc_is_valid(false) {} + const std::vector<struct sock_filter>& program; + const struct arch_seccomp_data& data; + unsigned int ip; + uint32_t accumulator; + bool acc_is_valid; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(State); +}; + +uint32_t EvaluateErrorCode(bpf_dsl::PolicyCompiler* compiler, + const ErrorCode& code, + const struct arch_seccomp_data& data) { + if (code.error_type() == ErrorCode::ET_SIMPLE || + code.error_type() == ErrorCode::ET_TRAP) { + return code.err(); + } else if (code.error_type() == ErrorCode::ET_COND) { + if (code.width() == ErrorCode::TP_32BIT && + (data.args[code.argno()] >> 32) && + (data.args[code.argno()] & 0xFFFFFFFF80000000ull) != + 0xFFFFFFFF80000000ull) { + return compiler->Unexpected64bitArgument().err(); + } + bool equal = (data.args[code.argno()] & code.mask()) == code.value(); + return EvaluateErrorCode(compiler, equal ? *code.passed() : *code.failed(), + data); + } else { + return SECCOMP_RET_INVALID; + } +} + +bool VerifyErrorCode(bpf_dsl::PolicyCompiler* compiler, + const std::vector<struct sock_filter>& program, + struct arch_seccomp_data* data, + const ErrorCode& root_code, + const ErrorCode& code, + const char** err) { + if (code.error_type() == ErrorCode::ET_SIMPLE || + code.error_type() == ErrorCode::ET_TRAP) { + const uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err); + if (*err) { + return false; + } + const uint32_t policy_ret = EvaluateErrorCode(compiler, root_code, *data); + if (computed_ret != policy_ret) { + // For efficiency's sake, we'd much rather compare "computed_ret" + // against "code.err()". This works most of the time, but it doesn't + // always work for nested conditional expressions. The test values + // that we generate on the fly to probe expressions can trigger + // code flow decisions in multiple nodes of the decision tree, and the + // only way to compute the correct error code in that situation is by + // calling EvaluateErrorCode(). + *err = "Exit code from BPF program doesn't match"; + return false; + } + } else if (code.error_type() == ErrorCode::ET_COND) { + if (code.argno() < 0 || code.argno() >= 6) { + *err = "Invalid argument number in error code"; + return false; + } + + // TODO(mdempsky): The test values generated here try to provide good + // coverage for generated BPF instructions while avoiding combinatorial + // explosion on large policies. Ideally we would instead take a fuzzing-like + // approach and generate a bounded number of test cases regardless of policy + // size. + + // Verify that we can check a value for simple equality. + data->args[code.argno()] = code.value(); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), + err)) { + return false; + } + + // If mask ignores any bits, verify that setting those bits is still + // detected as equality. + uint64_t ignored_bits = ~code.mask(); + if (code.width() == ErrorCode::TP_32BIT) { + ignored_bits = static_cast<uint32_t>(ignored_bits); + } + if ((ignored_bits & kLower32Bits) != 0) { + data->args[code.argno()] = code.value() | (ignored_bits & kLower32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), + err)) { + return false; + } + } + if ((ignored_bits & kUpper32Bits) != 0) { + data->args[code.argno()] = code.value() | (ignored_bits & kUpper32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), + err)) { + return false; + } + } + + // Verify that changing bits included in the mask is detected as inequality. + if ((code.mask() & kLower32Bits) != 0) { + data->args[code.argno()] = code.value() ^ (code.mask() & kLower32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.failed(), + err)) { + return false; + } + } + if ((code.mask() & kUpper32Bits) != 0) { + data->args[code.argno()] = code.value() ^ (code.mask() & kUpper32Bits); + if (!VerifyErrorCode(compiler, program, data, root_code, *code.failed(), + err)) { + return false; + } + } + + if (code.width() == ErrorCode::TP_32BIT) { + // For 32-bit system call arguments, we emit additional instructions to + // validate the upper 32-bits. Here we test that validation. + + // Arbitrary 64-bit values should be rejected. + data->args[code.argno()] = 1ULL << 32; + if (!VerifyErrorCode(compiler, program, data, root_code, + compiler->Unexpected64bitArgument(), err)) { + return false; + } + + // Upper 32-bits set without the MSB of the lower 32-bits set should be + // rejected too. + data->args[code.argno()] = kUpper32Bits; + if (!VerifyErrorCode(compiler, program, data, root_code, + compiler->Unexpected64bitArgument(), err)) { + return false; + } + } + } else { + *err = "Attempting to return invalid error code from BPF program"; + return false; + } + return true; +} + +void Ld(State* state, const struct sock_filter& insn, const char** err) { + if (BPF_SIZE(insn.code) != BPF_W || BPF_MODE(insn.code) != BPF_ABS || + insn.jt != 0 || insn.jf != 0) { + *err = "Invalid BPF_LD instruction"; + return; + } + if (insn.k < sizeof(struct arch_seccomp_data) && (insn.k & 3) == 0) { + // We only allow loading of properly aligned 32bit quantities. + memcpy(&state->accumulator, + reinterpret_cast<const char*>(&state->data) + insn.k, 4); + } else { + *err = "Invalid operand in BPF_LD instruction"; + return; + } + state->acc_is_valid = true; + return; +} + +void Jmp(State* state, const struct sock_filter& insn, const char** err) { + if (BPF_OP(insn.code) == BPF_JA) { + if (state->ip + insn.k + 1 >= state->program.size() || + state->ip + insn.k + 1 <= state->ip) { + compilation_failure: + *err = "Invalid BPF_JMP instruction"; + return; + } + state->ip += insn.k; + } else { + if (BPF_SRC(insn.code) != BPF_K || !state->acc_is_valid || + state->ip + insn.jt + 1 >= state->program.size() || + state->ip + insn.jf + 1 >= state->program.size()) { + goto compilation_failure; + } + switch (BPF_OP(insn.code)) { + case BPF_JEQ: + if (state->accumulator == insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + case BPF_JGT: + if (state->accumulator > insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + case BPF_JGE: + if (state->accumulator >= insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + case BPF_JSET: + if (state->accumulator & insn.k) { + state->ip += insn.jt; + } else { + state->ip += insn.jf; + } + break; + default: + goto compilation_failure; + } + } +} + +uint32_t Ret(State*, const struct sock_filter& insn, const char** err) { + if (BPF_SRC(insn.code) != BPF_K) { + *err = "Invalid BPF_RET instruction"; + return 0; + } + return insn.k; +} + +void Alu(State* state, const struct sock_filter& insn, const char** err) { + if (BPF_OP(insn.code) == BPF_NEG) { + state->accumulator = -state->accumulator; + return; + } else { + if (BPF_SRC(insn.code) != BPF_K) { + *err = "Unexpected source operand in arithmetic operation"; + return; + } + switch (BPF_OP(insn.code)) { + case BPF_ADD: + state->accumulator += insn.k; + break; + case BPF_SUB: + state->accumulator -= insn.k; + break; + case BPF_MUL: + state->accumulator *= insn.k; + break; + case BPF_DIV: + if (!insn.k) { + *err = "Illegal division by zero"; + break; + } + state->accumulator /= insn.k; + break; + case BPF_MOD: + if (!insn.k) { + *err = "Illegal division by zero"; + break; + } + state->accumulator %= insn.k; + break; + case BPF_OR: + state->accumulator |= insn.k; + break; + case BPF_XOR: + state->accumulator ^= insn.k; + break; + case BPF_AND: + state->accumulator &= insn.k; + break; + case BPF_LSH: + if (insn.k > 32) { + *err = "Illegal shift operation"; + break; + } + state->accumulator <<= insn.k; + break; + case BPF_RSH: + if (insn.k > 32) { + *err = "Illegal shift operation"; + break; + } + state->accumulator >>= insn.k; + break; + default: + *err = "Invalid operator in arithmetic operation"; + break; + } + } +} + +} // namespace + +bool Verifier::VerifyBPF(bpf_dsl::PolicyCompiler* compiler, + const std::vector<struct sock_filter>& program, + const bpf_dsl::Policy& policy, + const char** err) { + *err = NULL; + for (uint32_t sysnum : SyscallSet::All()) { + // We ideally want to iterate over the full system call range and values + // just above and just below this range. This gives us the full result set + // of the "evaluators". + // On Intel systems, this can fail in a surprising way, as a cleared bit 30 + // indicates either i386 or x86-64; and a set bit 30 indicates x32. And + // unless we pay attention to setting this bit correctly, an early check in + // our BPF program will make us fail with a misleading error code. + struct arch_seccomp_data data = {static_cast<int>(sysnum), + static_cast<uint32_t>(SECCOMP_ARCH)}; +#if defined(__i386__) || defined(__x86_64__) +#if defined(__x86_64__) && defined(__ILP32__) + if (!(sysnum & 0x40000000u)) { + continue; + } +#else + if (sysnum & 0x40000000u) { + continue; + } +#endif +#endif + ErrorCode code = SyscallSet::IsValid(sysnum) + ? policy.EvaluateSyscall(sysnum)->Compile(compiler) + : policy.InvalidSyscall()->Compile(compiler); + if (!VerifyErrorCode(compiler, program, &data, code, code, err)) { + return false; + } + } + return true; +} + +uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program, + const struct arch_seccomp_data& data, + const char** err) { + *err = NULL; + if (program.size() < 1 || program.size() >= SECCOMP_MAX_PROGRAM_SIZE) { + *err = "Invalid program length"; + return 0; + } + for (State state(program, data); !*err; ++state.ip) { + if (state.ip >= program.size()) { + *err = "Invalid instruction pointer in BPF program"; + break; + } + const struct sock_filter& insn = program[state.ip]; + switch (BPF_CLASS(insn.code)) { + case BPF_LD: + Ld(&state, insn, err); + break; + case BPF_JMP: + Jmp(&state, insn, err); + break; + case BPF_RET: { + uint32_t r = Ret(&state, insn, err); + switch (r & SECCOMP_RET_ACTION) { + case SECCOMP_RET_TRAP: + case SECCOMP_RET_ERRNO: + case SECCOMP_RET_TRACE: + case SECCOMP_RET_ALLOW: + break; + case SECCOMP_RET_KILL: // We don't ever generate this + case SECCOMP_RET_INVALID: // Should never show up in BPF program + default: + *err = "Unexpected return code found in BPF program"; + return 0; + } + return r; + } + case BPF_ALU: + Alu(&state, insn, err); + break; + default: + *err = "Unexpected instruction in BPF program"; + break; + } + } + return 0; +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/sandbox/linux/bpf_dsl/verifier.h b/sandbox/linux/bpf_dsl/verifier.h new file mode 100644 index 0000000000..b0435d1aa1 --- /dev/null +++ b/sandbox/linux/bpf_dsl/verifier.h @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_VERIFIER_H__ +#define SANDBOX_LINUX_BPF_DSL_VERIFIER_H__ + +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +struct sock_filter; + +namespace sandbox { +struct arch_seccomp_data; + +namespace bpf_dsl { +class Policy; +class PolicyCompiler; + +class SANDBOX_EXPORT Verifier { + public: + // Evaluate the BPF program for all possible inputs and verify that it + // computes the correct result. We use the "evaluators" to determine + // the full set of possible inputs that we have to iterate over. + // Returns success, if the BPF filter accurately reflects the rules + // set by the "evaluators". + // Upon success, "err" is set to NULL. Upon failure, it contains a static + // error message that does not need to be free()'d. + static bool VerifyBPF(bpf_dsl::PolicyCompiler* compiler, + const std::vector<struct sock_filter>& program, + const bpf_dsl::Policy& policy, + const char** err); + + // Evaluate a given BPF program for a particular set of system call + // parameters. If evaluation failed for any reason, "err" will be set to + // a non-NULL error string. Otherwise, the BPF program's result will be + // returned by the function and "err" is NULL. + // We do not actually implement the full BPF state machine, but only the + // parts that can actually be generated by our BPF compiler. If this code + // is used for purposes other than verifying the output of the sandbox's + // BPF compiler, we might have to extend this BPF interpreter. + static uint32_t EvaluateBPF(const std::vector<struct sock_filter>& program, + const struct arch_seccomp_data& data, + const char** err); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Verifier); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_VERIFIER_H__ diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc new file mode 100644 index 0000000000..8c679a3d41 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.cc @@ -0,0 +1,270 @@ +// Copyright (c) 2013 The Chromium 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 "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h" + +#include <errno.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" +#include "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +// Changing this implementation will have an effect on *all* policies. +// Currently this means: Renderer/Worker, GPU, Flash and NaCl. + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Arg; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::If; +using sandbox::bpf_dsl::ResultExpr; + +namespace sandbox { + +namespace { + +bool IsBaselinePolicyAllowed(int sysno) { + return SyscallSets::IsAllowedAddressSpaceAccess(sysno) || + SyscallSets::IsAllowedBasicScheduler(sysno) || + SyscallSets::IsAllowedEpoll(sysno) || + SyscallSets::IsAllowedFileSystemAccessViaFd(sysno) || + SyscallSets::IsAllowedFutex(sysno) || + SyscallSets::IsAllowedGeneralIo(sysno) || + SyscallSets::IsAllowedGetOrModifySocket(sysno) || + SyscallSets::IsAllowedGettime(sysno) || + SyscallSets::IsAllowedProcessStartOrDeath(sysno) || + SyscallSets::IsAllowedSignalHandling(sysno) || + SyscallSets::IsGetSimpleId(sysno) || + SyscallSets::IsKernelInternalApi(sysno) || +#if defined(__arm__) + SyscallSets::IsArmPrivate(sysno) || +#endif +#if defined(__mips__) + SyscallSets::IsMipsPrivate(sysno) || +#endif + SyscallSets::IsAllowedOperationOnFd(sysno); +} + +// System calls that will trigger the crashing SIGSYS handler. +bool IsBaselinePolicyWatched(int sysno) { + return SyscallSets::IsAdminOperation(sysno) || + SyscallSets::IsAdvancedScheduler(sysno) || + SyscallSets::IsAdvancedTimer(sysno) || + SyscallSets::IsAsyncIo(sysno) || + SyscallSets::IsDebug(sysno) || + SyscallSets::IsEventFd(sysno) || + SyscallSets::IsExtendedAttributes(sysno) || + SyscallSets::IsFaNotify(sysno) || + SyscallSets::IsFsControl(sysno) || + SyscallSets::IsGlobalFSViewChange(sysno) || + SyscallSets::IsGlobalProcessEnvironment(sysno) || + SyscallSets::IsGlobalSystemStatus(sysno) || + SyscallSets::IsInotify(sysno) || + SyscallSets::IsKernelModule(sysno) || + SyscallSets::IsKeyManagement(sysno) || + SyscallSets::IsKill(sysno) || + SyscallSets::IsMessageQueue(sysno) || + SyscallSets::IsMisc(sysno) || +#if defined(__x86_64__) + SyscallSets::IsNetworkSocketInformation(sysno) || +#endif + SyscallSets::IsNuma(sysno) || + SyscallSets::IsPrctl(sysno) || + SyscallSets::IsProcessGroupOrSession(sysno) || +#if defined(__i386__) || defined(__mips__) + SyscallSets::IsSocketCall(sysno) || +#endif +#if defined(__arm__) + SyscallSets::IsArmPciConfig(sysno) || +#endif +#if defined(__mips__) + SyscallSets::IsMipsMisc(sysno) || +#endif + SyscallSets::IsTimer(sysno); +} + +// |fs_denied_errno| is the errno return for denied filesystem access. +ResultExpr EvaluateSyscallImpl(int fs_denied_errno, + pid_t current_pid, + int sysno) { +#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \ + defined(MEMORY_SANITIZER) + // TCGETS is required by the sanitizers on failure. + if (sysno == __NR_ioctl) { + return RestrictIoctl(); + } + + if (sysno == __NR_sched_getaffinity) { + return Allow(); + } + + // Used when RSS limiting is enabled in sanitizers. + if (sysno == __NR_getrusage) { + return RestrictGetrusage(); + } + + if (sysno == __NR_sigaltstack) { + // Required for better stack overflow detection in ASan. Disallowed in + // non-ASan builds. + return Allow(); + } +#endif // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || + // defined(MEMORY_SANITIZER) + + if (IsBaselinePolicyAllowed(sysno)) { + return Allow(); + } + +#if defined(OS_ANDROID) + // Needed for thread creation. + if (sysno == __NR_sigaltstack) + return Allow(); +#endif + + if (sysno == __NR_clock_gettime) { + return RestrictClockID(); + } + + if (sysno == __NR_clone) { + return RestrictCloneToThreadsAndEPERMFork(); + } + + if (sysno == __NR_fcntl) + return RestrictFcntlCommands(); + +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + if (sysno == __NR_fcntl64) + return RestrictFcntlCommands(); +#endif + +#if !defined(__aarch64__) + // fork() is never used as a system call (clone() is used instead), but we + // have seen it in fallback code on Android. + if (sysno == __NR_fork) { + return Error(EPERM); + } +#endif + + if (sysno == __NR_futex) + return RestrictFutex(); + + if (sysno == __NR_set_robust_list) + return Error(EPERM); + + if (sysno == __NR_getpriority || sysno ==__NR_setpriority) + return RestrictGetSetpriority(current_pid); + + if (sysno == __NR_madvise) { + // Only allow MADV_DONTNEED (aka MADV_FREE). + const Arg<int> advice(2); + return If(advice == MADV_DONTNEED, Allow()).Else(Error(EPERM)); + } + +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + if (sysno == __NR_mmap) + return RestrictMmapFlags(); +#endif + +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + if (sysno == __NR_mmap2) + return RestrictMmapFlags(); +#endif + + if (sysno == __NR_mprotect) + return RestrictMprotectFlags(); + + if (sysno == __NR_prctl) + return RestrictPrctl(); + +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + if (sysno == __NR_socketpair) { + // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen. + static_assert(AF_UNIX == PF_UNIX, + "af_unix and pf_unix should not be different"); + const Arg<int> domain(0); + return If(domain == AF_UNIX, Allow()).Else(CrashSIGSYS()); + } +#endif + + if (SyscallSets::IsKill(sysno)) { + return RestrictKillTarget(current_pid, sysno); + } + + if (SyscallSets::IsFileSystem(sysno) || + SyscallSets::IsCurrentDirectory(sysno)) { + return Error(fs_denied_errno); + } + + if (SyscallSets::IsSeccomp(sysno)) + return Error(EPERM); + + if (SyscallSets::IsAnySystemV(sysno)) { + return Error(EPERM); + } + + if (SyscallSets::IsUmask(sysno) || + SyscallSets::IsDeniedFileSystemAccessViaFd(sysno) || + SyscallSets::IsDeniedGetOrModifySocket(sysno) || + SyscallSets::IsProcessPrivilegeChange(sysno)) { + return Error(EPERM); + } + +#if defined(__i386__) || defined(__mips__) + if (SyscallSets::IsSocketCall(sysno)) + return RestrictSocketcallCommand(); +#endif + + if (IsBaselinePolicyWatched(sysno)) { + // Previously unseen syscalls. TODO(jln): some of these should + // be denied gracefully right away. + return CrashSIGSYS(); + } + + // In any other case crash the program with our SIGSYS handler. + return CrashSIGSYS(); +} + +} // namespace. + +// Unfortunately C++03 doesn't allow delegated constructors. +// Call other constructor when C++11 lands. +BaselinePolicy::BaselinePolicy() : BaselinePolicy(EPERM) {} + +BaselinePolicy::BaselinePolicy(int fs_denied_errno) + : fs_denied_errno_(fs_denied_errno), policy_pid_(sys_getpid()) { +} + +BaselinePolicy::~BaselinePolicy() { + // Make sure that this policy is created, used and destroyed by a single + // process. + DCHECK_EQ(sys_getpid(), policy_pid_); +} + +ResultExpr BaselinePolicy::EvaluateSyscall(int sysno) const { + // Sanity check that we're only called with valid syscall numbers. + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + // Make sure that this policy is used in the creating process. + if (1 == sysno) { + DCHECK_EQ(sys_getpid(), policy_pid_); + } + return EvaluateSyscallImpl(fs_denied_errno_, policy_pid_, sysno); +} + +ResultExpr BaselinePolicy::InvalidSyscall() const { + return CrashSIGSYS(); +} + +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h new file mode 100644 index 0000000000..4169d9c3e2 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy.h @@ -0,0 +1,48 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_BASELINE_POLICY_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_BASELINE_POLICY_H_ + +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This is a helper to build seccomp-bpf policies, i.e. policies for a sandbox +// that reduces the Linux kernel's attack surface. Given its nature, it doesn't +// have a clear semantics and is mostly "implementation-defined". +// +// This class implements the Policy interface with a "baseline" +// policy for use within Chromium. +// The "baseline" policy is somewhat arbitrary. All Chromium policies are an +// alteration of it, and it represents a reasonable common ground to run most +// code in a sandboxed environment. +// A baseline policy is only valid for the process for which this object was +// instantiated (so do not fork() and use it in a child). +class SANDBOX_EXPORT BaselinePolicy : public bpf_dsl::Policy { + public: + BaselinePolicy(); + // |fs_denied_errno| is the errno returned when a filesystem access system + // call is denied. + explicit BaselinePolicy(int fs_denied_errno); + ~BaselinePolicy() override; + + bpf_dsl::ResultExpr EvaluateSyscall(int system_call_number) const override; + bpf_dsl::ResultExpr InvalidSyscall() const override; + pid_t policy_pid() const { return policy_pid_; } + + private: + int fs_denied_errno_; + + // The PID that the policy applies to (should be equal to the current pid). + pid_t policy_pid_; + + DISALLOW_COPY_AND_ASSIGN(BaselinePolicy); +}; + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_BASELINE_POLICY_H_ diff --git a/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc new file mode 100644 index 0000000000..614849f61c --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/baseline_policy_unittest.cc @@ -0,0 +1,334 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/seccomp-bpf-helpers/baseline_policy.h" + +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <signal.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread.h" +#include "build/build_config.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/linux_futex.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +namespace { + +// This also tests that read(), write() and fstat() are allowed. +void TestPipeOrSocketPair(base::ScopedFD read_end, base::ScopedFD write_end) { + BPF_ASSERT_LE(0, read_end.get()); + BPF_ASSERT_LE(0, write_end.get()); + struct stat stat_buf; + int sys_ret = fstat(read_end.get(), &stat_buf); + BPF_ASSERT_EQ(0, sys_ret); + BPF_ASSERT(S_ISFIFO(stat_buf.st_mode) || S_ISSOCK(stat_buf.st_mode)); + + const ssize_t kTestTransferSize = 4; + static const char kTestString[kTestTransferSize] = {'T', 'E', 'S', 'T'}; + ssize_t transfered = 0; + + transfered = + HANDLE_EINTR(write(write_end.get(), kTestString, kTestTransferSize)); + BPF_ASSERT_EQ(kTestTransferSize, transfered); + char read_buf[kTestTransferSize + 1] = {0}; + transfered = HANDLE_EINTR(read(read_end.get(), read_buf, sizeof(read_buf))); + BPF_ASSERT_EQ(kTestTransferSize, transfered); + BPF_ASSERT_EQ(0, memcmp(kTestString, read_buf, kTestTransferSize)); +} + +// Test that a few easy-to-test system calls are allowed. +BPF_TEST_C(BaselinePolicy, BaselinePolicyBasicAllowed, BaselinePolicy) { + BPF_ASSERT_EQ(0, sched_yield()); + + int pipefd[2]; + int sys_ret = pipe(pipefd); + BPF_ASSERT_EQ(0, sys_ret); + TestPipeOrSocketPair(base::ScopedFD(pipefd[0]), base::ScopedFD(pipefd[1])); + + BPF_ASSERT_LE(1, getpid()); + BPF_ASSERT_LE(0, getuid()); +} + +BPF_TEST_C(BaselinePolicy, FchmodErrno, BaselinePolicy) { + int ret = fchmod(-1, 07777); + BPF_ASSERT_EQ(-1, ret); + // Without the sandbox, this would EBADF instead. + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_TEST_C(BaselinePolicy, ForkErrno, BaselinePolicy) { + errno = 0; + pid_t pid = fork(); + const int fork_errno = errno; + TestUtils::HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +pid_t ForkX86Glibc() { + static pid_t ptid; + return sys_clone(CLONE_PARENT_SETTID | SIGCHLD, nullptr, &ptid, nullptr, + nullptr); +} + +BPF_TEST_C(BaselinePolicy, ForkX86Eperm, BaselinePolicy) { + errno = 0; + pid_t pid = ForkX86Glibc(); + const int fork_errno = errno; + TestUtils::HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +pid_t ForkARMGlibc() { + static pid_t ctid; + return sys_clone(CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, nullptr, + nullptr, &ctid, nullptr); +} + +BPF_TEST_C(BaselinePolicy, ForkArmEperm, BaselinePolicy) { + errno = 0; + pid_t pid = ForkARMGlibc(); + const int fork_errno = errno; + TestUtils::HandlePostForkReturn(pid); + + BPF_ASSERT_EQ(-1, pid); + BPF_ASSERT_EQ(EPERM, fork_errno); +} + +BPF_TEST_C(BaselinePolicy, CreateThread, BaselinePolicy) { + base::Thread thread("sandbox_tests"); + BPF_ASSERT(thread.Start()); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + DisallowedCloneFlagCrashes, + DEATH_SEGV_MESSAGE(GetCloneErrorMessageContentForTests()), + BaselinePolicy) { + pid_t pid = sys_clone(CLONE_THREAD | SIGCHLD); + TestUtils::HandlePostForkReturn(pid); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + DisallowedKillCrashes, + DEATH_SEGV_MESSAGE(GetKillErrorMessageContentForTests()), + BaselinePolicy) { + BPF_ASSERT_NE(1, getpid()); + kill(1, 0); + _exit(0); +} + +BPF_TEST_C(BaselinePolicy, CanKillSelf, BaselinePolicy) { + int sys_ret = kill(getpid(), 0); + BPF_ASSERT_EQ(0, sys_ret); +} + +BPF_TEST_C(BaselinePolicy, Socketpair, BaselinePolicy) { + int sv[2]; + int sys_ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, sv); + BPF_ASSERT_EQ(0, sys_ret); + TestPipeOrSocketPair(base::ScopedFD(sv[0]), base::ScopedFD(sv[1])); + + sys_ret = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv); + BPF_ASSERT_EQ(0, sys_ret); + TestPipeOrSocketPair(base::ScopedFD(sv[0]), base::ScopedFD(sv[1])); +} + +// Not all architectures can restrict the domain for socketpair(). +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +BPF_DEATH_TEST_C(BaselinePolicy, + SocketpairWrongDomain, + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + int sv[2]; + ignore_result(socketpair(AF_INET, SOCK_STREAM, 0, sv)); + _exit(1); +} +#endif // defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + +BPF_TEST_C(BaselinePolicy, EPERM_open, BaselinePolicy) { + errno = 0; + int sys_ret = open("/proc/cpuinfo", O_RDONLY); + BPF_ASSERT_EQ(-1, sys_ret); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_TEST_C(BaselinePolicy, EPERM_access, BaselinePolicy) { + errno = 0; + int sys_ret = access("/proc/cpuinfo", R_OK); + BPF_ASSERT_EQ(-1, sys_ret); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_TEST_C(BaselinePolicy, EPERM_getcwd, BaselinePolicy) { + errno = 0; + char buf[1024]; + char* cwd = getcwd(buf, sizeof(buf)); + BPF_ASSERT_EQ(NULL, cwd); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + SIGSYS_InvalidSyscall, + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + Syscall::InvalidCall(); +} + +// A failing test using this macro could be problematic since we perform +// system calls by passing "0" as every argument. +// The kernel could SIGSEGV the process or the system call itself could reboot +// the machine. Some thoughts have been given when hand-picking the system +// calls below to limit any potential side effects outside of the current +// process. +#define TEST_BASELINE_SIGSYS(sysno) \ + BPF_DEATH_TEST_C(BaselinePolicy, \ + SIGSYS_##sysno, \ + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), \ + BaselinePolicy) { \ + syscall(sysno, 0, 0, 0, 0, 0, 0); \ + _exit(1); \ + } + +TEST_BASELINE_SIGSYS(__NR_acct); +TEST_BASELINE_SIGSYS(__NR_chroot); +TEST_BASELINE_SIGSYS(__NR_fanotify_init); +TEST_BASELINE_SIGSYS(__NR_fgetxattr); +TEST_BASELINE_SIGSYS(__NR_getcpu); +TEST_BASELINE_SIGSYS(__NR_getitimer); +TEST_BASELINE_SIGSYS(__NR_init_module); +TEST_BASELINE_SIGSYS(__NR_io_cancel); +TEST_BASELINE_SIGSYS(__NR_keyctl); +TEST_BASELINE_SIGSYS(__NR_mq_open); +TEST_BASELINE_SIGSYS(__NR_ptrace); +TEST_BASELINE_SIGSYS(__NR_sched_setaffinity); +TEST_BASELINE_SIGSYS(__NR_setpgid); +TEST_BASELINE_SIGSYS(__NR_swapon); +TEST_BASELINE_SIGSYS(__NR_sysinfo); +TEST_BASELINE_SIGSYS(__NR_syslog); +TEST_BASELINE_SIGSYS(__NR_timer_create); + +#if !defined(__aarch64__) +TEST_BASELINE_SIGSYS(__NR_eventfd); +TEST_BASELINE_SIGSYS(__NR_inotify_init); +TEST_BASELINE_SIGSYS(__NR_vserver); +#endif + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithRequeuePriorityInheritence, + DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI, 0, NULL, NULL, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithRequeuePriorityInheritencePrivate, + DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_CMP_REQUEUE_PI_PRIVATE, 0, NULL, NULL, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + FutexWithUnlockPIPrivate, + DEATH_SEGV_MESSAGE(GetFutexErrorMessageContentForTests()), + BaselinePolicy) { + syscall(__NR_futex, NULL, FUTEX_UNLOCK_PI_PRIVATE, 0, NULL, NULL, 0); + _exit(1); +} + +BPF_TEST_C(BaselinePolicy, PrctlDumpable, BaselinePolicy) { + const int is_dumpable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0); + BPF_ASSERT(is_dumpable == 1 || is_dumpable == 0); + const int prctl_ret = prctl(PR_SET_DUMPABLE, is_dumpable, 0, 0, 0, 0); + BPF_ASSERT_EQ(0, prctl_ret); +} + +// Workaround incomplete Android headers. +#if !defined(PR_CAPBSET_READ) +#define PR_CAPBSET_READ 23 +#endif + +BPF_DEATH_TEST_C(BaselinePolicy, + PrctlSigsys, + DEATH_SEGV_MESSAGE(GetPrctlErrorMessageContentForTests()), + BaselinePolicy) { + prctl(PR_CAPBSET_READ, 0, 0, 0, 0); + _exit(1); +} + +BPF_TEST_C(BaselinePolicy, GetOrSetPriority, BaselinePolicy) { + errno = 0; + const int original_prio = getpriority(PRIO_PROCESS, 0); + // Check errno instead of the return value since this system call can return + // -1 as a valid value. + BPF_ASSERT_EQ(0, errno); + + errno = 0; + int rc = getpriority(PRIO_PROCESS, getpid()); + BPF_ASSERT_EQ(0, errno); + + rc = getpriority(PRIO_PROCESS, getpid() + 1); + BPF_ASSERT_EQ(-1, rc); + BPF_ASSERT_EQ(EPERM, errno); + + rc = setpriority(PRIO_PROCESS, 0, original_prio); + BPF_ASSERT_EQ(0, rc); + + rc = setpriority(PRIO_PROCESS, getpid(), original_prio); + BPF_ASSERT_EQ(0, rc); + + errno = 0; + rc = setpriority(PRIO_PROCESS, getpid() + 1, original_prio); + BPF_ASSERT_EQ(-1, rc); + BPF_ASSERT_EQ(EPERM, errno); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + GetPrioritySigsys, + DEATH_SEGV_MESSAGE(GetErrorMessageContentForTests()), + BaselinePolicy) { + getpriority(PRIO_USER, 0); + _exit(1); +} + +BPF_DEATH_TEST_C(BaselinePolicy, + ClockGettimeWithDisallowedClockCrashes, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + BaselinePolicy) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc new file mode 100644 index 0000000000..05250d147f --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Note: any code in this file MUST be async-signal safe. + +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" + +#include <sys/syscall.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +#if defined(__mips__) +// __NR_Linux, is defined in <asm/unistd.h>. +#include <asm/unistd.h> +#endif + +#define SECCOMP_MESSAGE_COMMON_CONTENT "seccomp-bpf failure" +#define SECCOMP_MESSAGE_CLONE_CONTENT "clone() failure" +#define SECCOMP_MESSAGE_PRCTL_CONTENT "prctl() failure" +#define SECCOMP_MESSAGE_IOCTL_CONTENT "ioctl() failure" +#define SECCOMP_MESSAGE_KILL_CONTENT "(tg)kill() failure" +#define SECCOMP_MESSAGE_FUTEX_CONTENT "futex() failure" + +namespace { + +inline bool IsArchitectureX86_64() { +#if defined(__x86_64__) + return true; +#else + return false; +#endif +} + +// Write |error_message| to stderr. Similar to RawLog(), but a bit more careful +// about async-signal safety. |size| is the size to write and should typically +// not include a terminating \0. +void WriteToStdErr(const char* error_message, size_t size) { + while (size > 0) { + // TODO(jln): query the current policy to check if send() is available and + // use it to perform a non-blocking write. + const int ret = HANDLE_EINTR(write(STDERR_FILENO, error_message, size)); + // We can't handle any type of error here. + if (ret <= 0 || static_cast<size_t>(ret) > size) break; + size -= ret; + error_message += ret; + } +} + +// Invalid syscall values are truncated to zero. +// On architectures where base value is zero (Intel and Arm), +// syscall number is the same as offset from base. +// This function returns values between 0 and 1023 on all architectures. +// On architectures where base value is different than zero (currently only +// Mips), we are truncating valid syscall values to offset from base. +uint32_t SyscallNumberToOffsetFromBase(uint32_t sysno) { +#if defined(__mips__) + // On MIPS syscall numbers are in different range than on x86 and ARM. + // Valid MIPS O32 ABI syscall __NR_syscall will be truncated to zero for + // simplicity. + sysno = sysno - __NR_Linux; +#endif + + if (sysno >= 1024) + sysno = 0; + + return sysno; +} + +// Print a seccomp-bpf failure to handle |sysno| to stderr in an +// async-signal safe way. +void PrintSyscallError(uint32_t sysno) { + if (sysno >= 1024) + sysno = 0; + // TODO(markus): replace with async-signal safe snprintf when available. + const size_t kNumDigits = 4; + char sysno_base10[kNumDigits]; + uint32_t rem = sysno; + uint32_t mod = 0; + for (int i = kNumDigits - 1; i >= 0; i--) { + mod = rem % 10; + rem /= 10; + sysno_base10[i] = '0' + mod; + } +#if defined(__mips__) && (_MIPS_SIM == _MIPS_SIM_ABI32) + static const char kSeccompErrorPrefix[] = __FILE__ + ":**CRASHING**:" SECCOMP_MESSAGE_COMMON_CONTENT " in syscall 4000 + "; +#else + static const char kSeccompErrorPrefix[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_COMMON_CONTENT " in syscall "; +#endif + static const char kSeccompErrorPostfix[] = "\n"; + WriteToStdErr(kSeccompErrorPrefix, sizeof(kSeccompErrorPrefix) - 1); + WriteToStdErr(sysno_base10, sizeof(sysno_base10)); + WriteToStdErr(kSeccompErrorPostfix, sizeof(kSeccompErrorPostfix) - 1); +} + +} // namespace. + +namespace sandbox { + +intptr_t CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux) { + uint32_t syscall = SyscallNumberToOffsetFromBase(args.nr); + + PrintSyscallError(syscall); + + // Encode 8-bits of the 1st two arguments too, so we can discern which socket + // type, which fcntl, ... etc., without being likely to hit a mapped + // address. + // Do not encode more bits here without thinking about increasing the + // likelihood of collision with mapped pages. + syscall |= ((args.args[0] & 0xffUL) << 12); + syscall |= ((args.args[1] & 0xffUL) << 20); + // Purposefully dereference the syscall as an address so it'll show up very + // clearly and easily in crash dumps. + volatile char* addr = reinterpret_cast<volatile char*>(syscall); + *addr = '\0'; + // In case we hit a mapped address, hit the null page with just the syscall, + // for paranoia. + syscall &= 0xfffUL; + addr = reinterpret_cast<volatile char*>(syscall); + *addr = '\0'; + for (;;) + _exit(1); +} + +// TODO(jln): refactor the reporting functions. + +intptr_t SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux) { + static const char kSeccompCloneError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_CLONE_CONTENT "\n"; + WriteToStdErr(kSeccompCloneError, sizeof(kSeccompCloneError) - 1); + // "flags" is the first argument in the kernel's clone(). + // Mark as volatile to be able to find the value on the stack in a minidump. + volatile uint64_t clone_flags = args.args[0]; + volatile char* addr; + if (IsArchitectureX86_64()) { + addr = reinterpret_cast<volatile char*>(clone_flags & 0xFFFFFF); + *addr = '\0'; + } + // Hit the NULL page if this fails to fault. + addr = reinterpret_cast<volatile char*>(clone_flags & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +intptr_t SIGSYSPrctlFailure(const struct arch_seccomp_data& args, + void* /* aux */) { + static const char kSeccompPrctlError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_PRCTL_CONTENT "\n"; + WriteToStdErr(kSeccompPrctlError, sizeof(kSeccompPrctlError) - 1); + // Mark as volatile to be able to find the value on the stack in a minidump. + volatile uint64_t option = args.args[0]; + volatile char* addr = + reinterpret_cast<volatile char*>(option & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +intptr_t SIGSYSIoctlFailure(const struct arch_seccomp_data& args, + void* /* aux */) { + static const char kSeccompIoctlError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_IOCTL_CONTENT "\n"; + WriteToStdErr(kSeccompIoctlError, sizeof(kSeccompIoctlError) - 1); + // Make "request" volatile so that we can see it on the stack in a minidump. + volatile uint64_t request = args.args[1]; + volatile char* addr = reinterpret_cast<volatile char*>(request & 0xFFFF); + *addr = '\0'; + // Hit the NULL page if this fails. + addr = reinterpret_cast<volatile char*>(request & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +intptr_t SIGSYSKillFailure(const struct arch_seccomp_data& args, + void* /* aux */) { + static const char kSeccompKillError[] = + __FILE__":**CRASHING**:" SECCOMP_MESSAGE_KILL_CONTENT "\n"; + WriteToStdErr(kSeccompKillError, sizeof(kSeccompKillError) - 1); + // Make "pid" volatile so that we can see it on the stack in a minidump. + volatile uint64_t my_pid = sys_getpid(); + volatile char* addr = reinterpret_cast<volatile char*>(my_pid & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +intptr_t SIGSYSFutexFailure(const struct arch_seccomp_data& args, + void* /* aux */) { + static const char kSeccompFutexError[] = + __FILE__ ":**CRASHING**:" SECCOMP_MESSAGE_FUTEX_CONTENT "\n"; + WriteToStdErr(kSeccompFutexError, sizeof(kSeccompFutexError) - 1); + volatile int futex_op = args.args[1]; + volatile char* addr = reinterpret_cast<volatile char*>(futex_op & 0xFFF); + *addr = '\0'; + for (;;) + _exit(1); +} + +intptr_t SIGSYSSchedHandler(const struct arch_seccomp_data& args, + void* aux) { + switch (args.nr) { + case __NR_sched_getaffinity: + case __NR_sched_getattr: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_rr_get_interval: + case __NR_sched_setaffinity: + case __NR_sched_setattr: + case __NR_sched_setparam: + case __NR_sched_setscheduler: + const pid_t tid = sys_gettid(); + // The first argument is the pid. If is our thread id, then replace it + // with 0, which is equivalent and allowed by the policy. + if (args.args[0] == static_cast<uint64_t>(tid)) { + return Syscall::Call(args.nr, + 0, + static_cast<intptr_t>(args.args[1]), + static_cast<intptr_t>(args.args[2]), + static_cast<intptr_t>(args.args[3]), + static_cast<intptr_t>(args.args[4]), + static_cast<intptr_t>(args.args[5])); + } + break; + } + + CrashSIGSYS_Handler(args, aux); + + // Should never be reached. + RAW_CHECK(false); + return -ENOSYS; +} + +bpf_dsl::ResultExpr CrashSIGSYS() { + return bpf_dsl::Trap(CrashSIGSYS_Handler, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSClone() { + return bpf_dsl::Trap(SIGSYSCloneFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSPrctl() { + return bpf_dsl::Trap(SIGSYSPrctlFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSIoctl() { + return bpf_dsl::Trap(SIGSYSIoctlFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSKill() { + return bpf_dsl::Trap(SIGSYSKillFailure, NULL); +} + +bpf_dsl::ResultExpr CrashSIGSYSFutex() { + return bpf_dsl::Trap(SIGSYSFutexFailure, NULL); +} + +bpf_dsl::ResultExpr RewriteSchedSIGSYS() { + return bpf_dsl::Trap(SIGSYSSchedHandler, NULL); +} + +const char* GetErrorMessageContentForTests() { + return SECCOMP_MESSAGE_COMMON_CONTENT; +} + +const char* GetCloneErrorMessageContentForTests() { + return SECCOMP_MESSAGE_CLONE_CONTENT; +} + +const char* GetPrctlErrorMessageContentForTests() { + return SECCOMP_MESSAGE_PRCTL_CONTENT; +} + +const char* GetIoctlErrorMessageContentForTests() { + return SECCOMP_MESSAGE_IOCTL_CONTENT; +} + +const char* GetKillErrorMessageContentForTests() { + return SECCOMP_MESSAGE_KILL_CONTENT; +} + +const char* GetFutexErrorMessageContentForTests() { + return SECCOMP_MESSAGE_FUTEX_CONTENT; +} + +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h new file mode 100644 index 0000000000..c64e994172 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h @@ -0,0 +1,82 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SIGSYS_HANDLERS_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SIGSYS_HANDLERS_H_ + +#include <stdint.h> + +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/sandbox_export.h" + +// The handlers are suitable for use in Trap() error codes. They are +// guaranteed to be async-signal safe. +// See sandbox/linux/seccomp-bpf/trap.h to see how they work. + +namespace sandbox { + +struct arch_seccomp_data; + +// This handler will crash the currently running process. The crashing address +// will be the number of the current system call, extracted from |args|. +// This handler will also print to stderr the number of the crashing syscall. +SANDBOX_EXPORT intptr_t + CrashSIGSYS_Handler(const struct arch_seccomp_data& args, void* aux); + +// The following three handlers are suitable to report failures with the +// clone(), prctl() and ioctl() system calls respectively. + +// The crashing address will be (clone_flags & 0xFFFFFF), where clone_flags is +// the clone(2) argument, extracted from |args|. +SANDBOX_EXPORT intptr_t + SIGSYSCloneFailure(const struct arch_seccomp_data& args, void* aux); +// The crashing address will be (option & 0xFFF), where option is the prctl(2) +// argument. +SANDBOX_EXPORT intptr_t + SIGSYSPrctlFailure(const struct arch_seccomp_data& args, void* aux); +// The crashing address will be request & 0xFFFF, where request is the ioctl(2) +// argument. +SANDBOX_EXPORT intptr_t + SIGSYSIoctlFailure(const struct arch_seccomp_data& args, void* aux); +// The crashing address will be (pid & 0xFFF), where pid is the first +// argument (and can be a tid). +SANDBOX_EXPORT intptr_t + SIGSYSKillFailure(const struct arch_seccomp_data& args, void* aux); +// The crashing address will be (op & 0xFFF), where op is the second +// argument. +SANDBOX_EXPORT intptr_t + SIGSYSFutexFailure(const struct arch_seccomp_data& args, void* aux); +// If the syscall is not being called on the current tid, crashes in the same +// way as CrashSIGSYS_Handler. Otherwise, returns the result of calling the +// syscall with the pid argument set to 0 (which for these calls means the +// current thread). The following syscalls are supported: +// +// sched_getaffinity(), sched_getattr(), sched_getparam(), sched_getscheduler(), +// sched_rr_get_interval(), sched_setaffinity(), sched_setattr(), +// sched_setparam(), sched_setscheduler() +SANDBOX_EXPORT intptr_t + SIGSYSSchedHandler(const struct arch_seccomp_data& args, void* aux); + +// Variants of the above functions for use with bpf_dsl. +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYS(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSClone(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSPrctl(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSIoctl(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSKill(); +SANDBOX_EXPORT bpf_dsl::ResultExpr CrashSIGSYSFutex(); +SANDBOX_EXPORT bpf_dsl::ResultExpr RewriteSchedSIGSYS(); + +// Following four functions return substrings of error messages used +// in the above four functions. They are useful in death tests. +SANDBOX_EXPORT const char* GetErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetCloneErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetPrctlErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetIoctlErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetKillErrorMessageContentForTests(); +SANDBOX_EXPORT const char* GetFutexErrorMessageContentForTests(); + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SIGSYS_HANDLERS_H_ diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc new file mode 100644 index 0000000000..58ffb843a8 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2013 The Chromium 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 "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" + +#include <errno.h> +#include <fcntl.h> +#include <fcntl.h> +#include <linux/net.h> +#include <sched.h> +#include <signal.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/system_headers/linux_futex.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/system_headers/linux_time.h" + +// PNaCl toolchain does not provide sys/ioctl.h header. +#if !defined(OS_NACL_NONSFI) +#include <sys/ioctl.h> +#endif + +#if defined(OS_ANDROID) + +#if !defined(F_DUPFD_CLOEXEC) +#define F_DUPFD_CLOEXEC (F_LINUX_SPECIFIC_BASE + 6) +#endif + +// https://android.googlesource.com/platform/bionic/+/lollipop-release/libc/private/bionic_prctl.h +#if !defined(PR_SET_VMA) +#define PR_SET_VMA 0x53564d41 +#endif + +// https://android.googlesource.com/platform/system/core/+/lollipop-release/libcutils/sched_policy.c +#if !defined(PR_SET_TIMERSLACK_PID) +#define PR_SET_TIMERSLACK_PID 41 +#endif + +#endif // defined(OS_ANDROID) + +#if defined(__arm__) && !defined(MAP_STACK) +#define MAP_STACK 0x20000 // Daisy build environment has old headers. +#endif + +#if defined(__mips__) && !defined(MAP_STACK) +#define MAP_STACK 0x40000 +#endif +namespace { + +inline bool IsArchitectureX86_64() { +#if defined(__x86_64__) + return true; +#else + return false; +#endif +} + +inline bool IsArchitectureI386() { +#if defined(__i386__) + return true; +#else + return false; +#endif +} + +inline bool IsAndroid() { +#if defined(OS_ANDROID) + return true; +#else + return false; +#endif +} + +inline bool IsArchitectureMips() { +#if defined(__mips__) + return true; +#else + return false; +#endif +} + +} // namespace. + +#define CASES SANDBOX_BPF_DSL_CASES + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Arg; +using sandbox::bpf_dsl::BoolExpr; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::If; +using sandbox::bpf_dsl::ResultExpr; + +namespace sandbox { + +#if !defined(OS_NACL_NONSFI) +// Allow Glibc's and Android pthread creation flags, crash on any other +// thread creation attempts and EPERM attempts to use neither +// CLONE_VM, nor CLONE_THREAD, which includes all fork() implementations. +ResultExpr RestrictCloneToThreadsAndEPERMFork() { + const Arg<unsigned long> flags(0); + + // TODO(mdempsky): Extend DSL to support (flags & ~mask1) == mask2. + const uint64_t kAndroidCloneMask = CLONE_VM | CLONE_FS | CLONE_FILES | + CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM; + const uint64_t kObsoleteAndroidCloneMask = kAndroidCloneMask | CLONE_DETACHED; + + const uint64_t kGlibcPthreadFlags = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | + CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID; + const BoolExpr glibc_test = flags == kGlibcPthreadFlags; + + const BoolExpr android_test = flags == kAndroidCloneMask || + flags == kObsoleteAndroidCloneMask || + flags == kGlibcPthreadFlags; + + return If(IsAndroid() ? android_test : glibc_test, Allow()) + .ElseIf((flags & (CLONE_VM | CLONE_THREAD)) == 0, Error(EPERM)) + .Else(CrashSIGSYSClone()); +} + +ResultExpr RestrictPrctl() { + // Will need to add seccomp compositing in the future. PR_SET_PTRACER is + // used by breakpad but not needed anymore. + const Arg<int> option(0); + return Switch(option) + .CASES((PR_GET_NAME, PR_SET_NAME, PR_GET_DUMPABLE, PR_SET_DUMPABLE), + Allow()) +#if defined(OS_ANDROID) + .CASES((PR_SET_VMA, PR_SET_TIMERSLACK_PID), Allow()) +#endif + .Default(CrashSIGSYSPrctl()); +} + +ResultExpr RestrictIoctl() { + const Arg<int> request(1); + return Switch(request).CASES((TCGETS, FIONREAD), Allow()).Default( + CrashSIGSYSIoctl()); +} + +ResultExpr RestrictMmapFlags() { + // The flags you see are actually the allowed ones, and the variable is a + // "denied" mask because of the negation operator. + // Significantly, we don't permit MAP_HUGETLB, or the newer flags such as + // MAP_POPULATE. + // TODO(davidung), remove MAP_DENYWRITE with updated Tegra libraries. + const uint64_t kAllowedMask = MAP_SHARED | MAP_PRIVATE | MAP_ANONYMOUS | + MAP_STACK | MAP_NORESERVE | MAP_FIXED | + MAP_DENYWRITE; + const Arg<int> flags(3); + return If((flags & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS()); +} + +ResultExpr RestrictMprotectFlags() { + // The flags you see are actually the allowed ones, and the variable is a + // "denied" mask because of the negation operator. + // Significantly, we don't permit weird undocumented flags such as + // PROT_GROWSDOWN. + const uint64_t kAllowedMask = PROT_READ | PROT_WRITE | PROT_EXEC; + const Arg<int> prot(2); + return If((prot & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS()); +} + +ResultExpr RestrictFcntlCommands() { + // We also restrict the flags in F_SETFL. We don't want to permit flags with + // a history of trouble such as O_DIRECT. The flags you see are actually the + // allowed ones, and the variable is a "denied" mask because of the negation + // operator. + // Glibc overrides the kernel's O_LARGEFILE value. Account for this. + uint64_t kOLargeFileFlag = O_LARGEFILE; + if (IsArchitectureX86_64() || IsArchitectureI386() || IsArchitectureMips()) + kOLargeFileFlag = 0100000; + + const Arg<int> cmd(1); + const Arg<long> long_arg(2); + + const uint64_t kAllowedMask = O_ACCMODE | O_APPEND | O_NONBLOCK | O_SYNC | + kOLargeFileFlag | O_CLOEXEC | O_NOATIME; + return Switch(cmd) + .CASES((F_GETFL, + F_GETFD, + F_SETFD, + F_SETLK, + F_SETLKW, + F_GETLK, + F_DUPFD, + F_DUPFD_CLOEXEC), + Allow()) + .Case(F_SETFL, + If((long_arg & ~kAllowedMask) == 0, Allow()).Else(CrashSIGSYS())) + .Default(CrashSIGSYS()); +} + +#if defined(__i386__) || defined(__mips__) +ResultExpr RestrictSocketcallCommand() { + // Unfortunately, we are unable to restrict the first parameter to + // socketpair(2). Whilst initially sounding bad, it's noteworthy that very + // few protocols actually support socketpair(2). The scary call that we're + // worried about, socket(2), remains blocked. + const Arg<int> call(0); + return Switch(call) + .CASES((SYS_SOCKETPAIR, + SYS_SHUTDOWN, + SYS_RECV, + SYS_SEND, + SYS_RECVFROM, + SYS_SENDTO, + SYS_RECVMSG, + SYS_SENDMSG), + Allow()) + .Default(Error(EPERM)); +} +#endif + +ResultExpr RestrictKillTarget(pid_t target_pid, int sysno) { + switch (sysno) { + case __NR_kill: + case __NR_tgkill: { + const Arg<pid_t> pid(0); + return If(pid == target_pid, Allow()).Else(CrashSIGSYSKill()); + } + case __NR_tkill: + return CrashSIGSYSKill(); + default: + NOTREACHED(); + return CrashSIGSYS(); + } +} + +ResultExpr RestrictFutex() { + const uint64_t kAllowedFutexFlags = FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME; + const Arg<int> op(1); + return Switch(op & ~kAllowedFutexFlags) + .CASES((FUTEX_WAIT, + FUTEX_WAKE, + FUTEX_REQUEUE, + FUTEX_CMP_REQUEUE, + FUTEX_WAKE_OP, + FUTEX_WAIT_BITSET, + FUTEX_WAKE_BITSET), + Allow()) + .Default(CrashSIGSYSFutex()); +} + +ResultExpr RestrictGetSetpriority(pid_t target_pid) { + const Arg<int> which(0); + const Arg<int> who(1); + return If(which == PRIO_PROCESS, + If(who == 0 || who == target_pid, Allow()).Else(Error(EPERM))) + .Else(CrashSIGSYS()); +} + +ResultExpr RestrictSchedTarget(pid_t target_pid, int sysno) { + switch (sysno) { + case __NR_sched_getaffinity: + case __NR_sched_getattr: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_rr_get_interval: + case __NR_sched_setaffinity: + case __NR_sched_setattr: + case __NR_sched_setparam: + case __NR_sched_setscheduler: { + const Arg<pid_t> pid(0); + return If(pid == 0 || pid == target_pid, Allow()) + .Else(RewriteSchedSIGSYS()); + } + default: + NOTREACHED(); + return CrashSIGSYS(); + } +} + +ResultExpr RestrictPrlimit64(pid_t target_pid) { + const Arg<pid_t> pid(0); + return If(pid == 0 || pid == target_pid, Allow()).Else(CrashSIGSYS()); +} + +ResultExpr RestrictGetrusage() { + const Arg<int> who(0); + return If(who == RUSAGE_SELF, Allow()).Else(CrashSIGSYS()); +} +#endif // !defined(OS_NACL_NONSFI) + +ResultExpr RestrictClockID() { + static_assert(4 == sizeof(clockid_t), "clockid_t is not 32bit"); + const Arg<clockid_t> clockid(0); + return If( +#if defined(OS_CHROMEOS) + // Allow the special clock for Chrome OS used by Chrome tracing. + clockid == base::TraceTicks::kClockSystemTrace || +#endif + clockid == CLOCK_MONOTONIC || + clockid == CLOCK_MONOTONIC_COARSE || + clockid == CLOCK_PROCESS_CPUTIME_ID || + clockid == CLOCK_REALTIME || + clockid == CLOCK_REALTIME_COARSE || + clockid == CLOCK_THREAD_CPUTIME_ID, + Allow()).Else(CrashSIGSYS()); +} + +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h new file mode 100644 index 0000000000..9eb35d10e0 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h @@ -0,0 +1,100 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ + +#include <unistd.h> + +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/sandbox_export.h" + +// These are helpers to build seccomp-bpf policies, i.e. policies for a +// sandbox that reduces the Linux kernel's attack surface. They return a +// bpf_dsl::ResultExpr suitable to restrict certain system call parameters. + +namespace sandbox { + +// Allow clone(2) for threads. +// Reject fork(2) attempts with EPERM. +// Don't restrict on ASAN. +// Crash if anything else is attempted. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictCloneToThreadsAndEPERMFork(); + +// Allow PR_SET_NAME, PR_SET_DUMPABLE, PR_GET_DUMPABLE. +// Crash if anything else is attempted. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictPrctl(); + +// Allow TCGETS and FIONREAD. +// Crash if anything else is attempted. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictIoctl(); + +// Restrict the flags argument in mmap(2). +// Only allow: MAP_SHARED | MAP_PRIVATE | MAP_ANONYMOUS | +// MAP_STACK | MAP_NORESERVE | MAP_FIXED | MAP_DENYWRITE. +// Crash if any other flag is used. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictMmapFlags(); + +// Restrict the prot argument in mprotect(2). +// Only allow: PROT_READ | PROT_WRITE | PROT_EXEC. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictMprotectFlags(); + +// Restrict fcntl(2) cmd argument to: +// We allow F_GETFL, F_SETFL, F_GETFD, F_SETFD, F_DUPFD, F_DUPFD_CLOEXEC, +// F_SETLK, F_SETLKW and F_GETLK. +// Also, in F_SETFL, restrict the allowed flags to: O_ACCMODE | O_APPEND | +// O_NONBLOCK | O_SYNC | O_LARGEFILE | O_CLOEXEC | O_NOATIME. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictFcntlCommands(); + +#if defined(__i386__) || defined(__mips__) +// Restrict socketcall(2) to only allow socketpair(2), send(2), recv(2), +// sendto(2), recvfrom(2), shutdown(2), sendmsg(2) and recvmsg(2). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictSocketcallCommand(); +#endif + +// Restrict |sysno| (which must be kill, tkill or tgkill) by allowing tgkill or +// kill iff the first parameter is |target_pid|, crashing otherwise or if +// |sysno| is tkill. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictKillTarget(pid_t target_pid, + int sysno); + +// Crash if FUTEX_CMP_REQUEUE_PI is used in the second argument of futex(2). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictFutex(); + +// Crash if |which| is not PRIO_PROCESS. EPERM if |who| is not 0, neither +// |target_pid| while calling setpriority(2) / getpriority(2). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictGetSetpriority(pid_t target_pid); + +// Restricts |pid| for sched_* syscalls which take a pid as the first argument. +// We only allow calling these syscalls if the pid argument is equal to the pid +// of the sandboxed process or 0 (indicating the current thread). The following +// syscalls are supported: +// +// sched_getaffinity(), sched_getattr(), sched_getparam(), sched_getscheduler(), +// sched_rr_get_interval(), sched_setaffinity(), sched_setattr(), +// sched_setparam(), sched_setscheduler() +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictSchedTarget(pid_t target_pid, + int sysno); + +// Restricts the |pid| argument of prlimit64 to 0 (meaning the calling process) +// or target_pid. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictPrlimit64(pid_t target_pid); + +// Restricts the |who| argument of getrusage to RUSAGE_SELF (meaning the calling +// process). +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictGetrusage(); + +// Restrict |clk_id| for clock_getres(), clock_gettime() and clock_settime(). +// We allow accessing only CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, +// CLOCK_REALTIME, and CLOCK_THREAD_CPUTIME_ID. In particular, this disallows +// access to arbitrary per-{process,thread} CPU-time clock IDs (such as those +// returned by {clock,pthread}_getcpuclockid), which can leak information +// about the state of the host OS. +// On Chrome OS, base::TraceTicks::kClockSystemTrace is also allowed. +SANDBOX_EXPORT bpf_dsl::ResultExpr RestrictClockID(); + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_PARAMETERS_RESTRICTIONS_H_ diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc new file mode 100644 index 0000000000..aaed480d69 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc @@ -0,0 +1,282 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" + +#include <errno.h> +#include <sched.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/synchronization/waitable_event.h" +#include "base/sys_info.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/system_headers/linux_time.h" +#include "sandbox/linux/tests/unit_tests.h" + +#if !defined(OS_ANDROID) +#include "third_party/lss/linux_syscall_support.h" // for MAKE_PROCESS_CPUCLOCK +#endif + +namespace sandbox { + +namespace { + +// NOTE: most of the parameter restrictions are tested in +// baseline_policy_unittest.cc as a more end-to-end test. + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::ResultExpr; + +class RestrictClockIdPolicy : public bpf_dsl::Policy { + public: + RestrictClockIdPolicy() {} + ~RestrictClockIdPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_clock_gettime: + case __NR_clock_getres: + return RestrictClockID(); + default: + return Allow(); + } + } +}; + +void CheckClock(clockid_t clockid) { + struct timespec ts; + ts.tv_sec = -1; + ts.tv_nsec = -1; + BPF_ASSERT_EQ(0, clock_getres(clockid, &ts)); + BPF_ASSERT_EQ(0, ts.tv_sec); + BPF_ASSERT_LE(0, ts.tv_nsec); + ts.tv_sec = -1; + ts.tv_nsec = -1; + BPF_ASSERT_EQ(0, clock_gettime(clockid, &ts)); + BPF_ASSERT_LE(0, ts.tv_sec); + BPF_ASSERT_LE(0, ts.tv_nsec); +} + +BPF_TEST_C(ParameterRestrictions, + clock_gettime_allowed, + RestrictClockIdPolicy) { + CheckClock(CLOCK_MONOTONIC); + CheckClock(CLOCK_MONOTONIC_COARSE); + CheckClock(CLOCK_PROCESS_CPUTIME_ID); + CheckClock(CLOCK_REALTIME); + CheckClock(CLOCK_REALTIME_COARSE); + CheckClock(CLOCK_THREAD_CPUTIME_ID); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + clock_gettime_crash_monotonic_raw, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictClockIdPolicy) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); +} + +#if defined(OS_CHROMEOS) + +// A custom BPF tester delegate to run IsRunningOnChromeOS() before +// the sandbox is enabled because we cannot run it with non-SFI BPF +// sandbox enabled. +class ClockSystemTesterDelegate : public sandbox::BPFTesterDelegate { + public: + ClockSystemTesterDelegate() + : is_running_on_chromeos_(base::SysInfo::IsRunningOnChromeOS()) {} + ~ClockSystemTesterDelegate() override {} + + scoped_ptr<sandbox::bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return scoped_ptr<sandbox::bpf_dsl::Policy>(new RestrictClockIdPolicy()); + } + void RunTestFunction() override { + if (is_running_on_chromeos_) { + CheckClock(base::TraceTicks::kClockSystemTrace); + } else { + struct timespec ts; + // kClockSystemTrace is 11, which is CLOCK_THREAD_CPUTIME_ID of + // the init process (pid=1). If kernel supports this feature, + // this may succeed even if this is not running on Chrome OS. We + // just check this clock_gettime call does not crash. + clock_gettime(base::TraceTicks::kClockSystemTrace, &ts); + } + } + + private: + const bool is_running_on_chromeos_; + DISALLOW_COPY_AND_ASSIGN(ClockSystemTesterDelegate); +}; + +BPF_TEST_D(BPFTest, BPFTestWithDelegateClass, ClockSystemTesterDelegate); + +#elif defined(OS_LINUX) + +BPF_DEATH_TEST_C(ParameterRestrictions, + clock_gettime_crash_system_trace, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictClockIdPolicy) { + struct timespec ts; + clock_gettime(base::TraceTicks::kClockSystemTrace, &ts); +} + +#endif // defined(OS_CHROMEOS) + +#if !defined(OS_ANDROID) +BPF_DEATH_TEST_C(ParameterRestrictions, + clock_gettime_crash_cpu_clock, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictClockIdPolicy) { + // We can't use clock_getcpuclockid() because it's not implemented in newlib, + // and it might not work inside the sandbox anyway. + const pid_t kInitPID = 1; + const clockid_t kInitCPUClockID = + MAKE_PROCESS_CPUCLOCK(kInitPID, CPUCLOCK_SCHED); + + struct timespec ts; + clock_gettime(kInitCPUClockID, &ts); +} +#endif // !defined(OS_ANDROID) + +class RestrictSchedPolicy : public bpf_dsl::Policy { + public: + RestrictSchedPolicy() {} + ~RestrictSchedPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_sched_getparam: + return RestrictSchedTarget(getpid(), sysno); + default: + return Allow(); + } + } +}; + +void CheckSchedGetParam(pid_t pid, struct sched_param* param) { + BPF_ASSERT_EQ(0, sched_getparam(pid, param)); +} + +void SchedGetParamThread(base::WaitableEvent* thread_run) { + const pid_t pid = getpid(); + const pid_t tid = sys_gettid(); + BPF_ASSERT_NE(pid, tid); + + struct sched_param current_pid_param; + CheckSchedGetParam(pid, ¤t_pid_param); + + struct sched_param zero_param; + CheckSchedGetParam(0, &zero_param); + + struct sched_param tid_param; + CheckSchedGetParam(tid, &tid_param); + + BPF_ASSERT_EQ(zero_param.sched_priority, tid_param.sched_priority); + + // Verify that the SIGSYS handler sets errno properly. + errno = 0; + BPF_ASSERT_EQ(-1, sched_getparam(tid, NULL)); + BPF_ASSERT_EQ(EINVAL, errno); + + thread_run->Signal(); +} + +BPF_TEST_C(ParameterRestrictions, + sched_getparam_allowed, + RestrictSchedPolicy) { + base::WaitableEvent thread_run(true, false); + // Run the actual test in a new thread so that the current pid and tid are + // different. + base::Thread getparam_thread("sched_getparam_thread"); + BPF_ASSERT(getparam_thread.Start()); + getparam_thread.message_loop()->PostTask( + FROM_HERE, base::Bind(&SchedGetParamThread, &thread_run)); + BPF_ASSERT(thread_run.TimedWait(base::TimeDelta::FromMilliseconds(5000))); + getparam_thread.Stop(); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + sched_getparam_crash_non_zero, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictSchedPolicy) { + const pid_t kInitPID = 1; + struct sched_param param; + sched_getparam(kInitPID, ¶m); +} + +class RestrictPrlimit64Policy : public bpf_dsl::Policy { + public: + RestrictPrlimit64Policy() {} + ~RestrictPrlimit64Policy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_prlimit64: + return RestrictPrlimit64(getpid()); + default: + return Allow(); + } + } +}; + +BPF_TEST_C(ParameterRestrictions, prlimit64_allowed, RestrictPrlimit64Policy) { + BPF_ASSERT_EQ(0, sys_prlimit64(0, RLIMIT_AS, NULL, NULL)); + BPF_ASSERT_EQ(0, sys_prlimit64(getpid(), RLIMIT_AS, NULL, NULL)); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + prlimit64_crash_not_self, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictPrlimit64Policy) { + const pid_t kInitPID = 1; + BPF_ASSERT_NE(kInitPID, getpid()); + sys_prlimit64(kInitPID, RLIMIT_AS, NULL, NULL); +} + +class RestrictGetrusagePolicy : public bpf_dsl::Policy { + public: + RestrictGetrusagePolicy() {} + ~RestrictGetrusagePolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + switch (sysno) { + case __NR_getrusage: + return RestrictGetrusage(); + default: + return Allow(); + } + } +}; + +BPF_TEST_C(ParameterRestrictions, getrusage_allowed, RestrictGetrusagePolicy) { + struct rusage usage; + BPF_ASSERT_EQ(0, getrusage(RUSAGE_SELF, &usage)); +} + +BPF_DEATH_TEST_C(ParameterRestrictions, + getrusage_crash_not_self, + DEATH_SEGV_MESSAGE(sandbox::GetErrorMessageContentForTests()), + RestrictGetrusagePolicy) { + struct rusage usage; + getrusage(RUSAGE_CHILDREN, &usage); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc new file mode 100644 index 0000000000..c217d47e2d --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.cc @@ -0,0 +1,1060 @@ +// Copyright (c) 2013 The Chromium 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 "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h" + +#include "build/build_config.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +// The functions below cover all existing i386, x86_64, and ARM system calls; +// excluding syscalls made obsolete in ARM EABI. +// The implicitly defined sets form a partition of the sets of +// system calls. + +bool SyscallSets::IsKill(int sysno) { + switch (sysno) { + case __NR_kill: + case __NR_tgkill: + case __NR_tkill: // Deprecated. + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedGettime(int sysno) { + switch (sysno) { + case __NR_gettimeofday: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_time: +#endif + return true; + case __NR_adjtimex: // Privileged. + case __NR_clock_adjtime: // Privileged. + case __NR_clock_getres: // Could be allowed. + case __NR_clock_gettime: + case __NR_clock_nanosleep: // Could be allowed. + case __NR_clock_settime: // Privileged. +#if defined(__i386__) || defined(__mips__) + case __NR_ftime: // Obsolete. +#endif + case __NR_settimeofday: // Privileged. +#if defined(__i386__) || defined(__mips__) + case __NR_stime: +#endif + default: + return false; + } +} + +bool SyscallSets::IsCurrentDirectory(int sysno) { + switch (sysno) { + case __NR_getcwd: + case __NR_chdir: + case __NR_fchdir: + return true; + default: + return false; + } +} + +bool SyscallSets::IsUmask(int sysno) { + switch (sysno) { + case __NR_umask: + return true; + default: + return false; + } +} + +// System calls that directly access the file system. They might acquire +// a new file descriptor or otherwise perform an operation directly +// via a path. +// Both EPERM and ENOENT are valid errno unless otherwise noted in comment. +bool SyscallSets::IsFileSystem(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_access: // EPERM not a valid errno. + case __NR_chmod: + case __NR_chown: +#if defined(__i386__) || defined(__arm__) + case __NR_chown32: +#endif + case __NR_creat: + case __NR_futimesat: // Should be called utimesat ? + case __NR_lchown: + case __NR_link: + case __NR_lstat: // EPERM not a valid errno. + case __NR_mkdir: + case __NR_mknod: + case __NR_open: + case __NR_readlink: // EPERM not a valid errno. + case __NR_rename: + case __NR_rmdir: + case __NR_stat: // EPERM not a valid errno. + case __NR_symlink: + case __NR_unlink: + case __NR_uselib: // Neither EPERM, nor ENOENT are valid errno. + case __NR_ustat: // Same as above. Deprecated. + case __NR_utimes: +#endif // !defined(__aarch64__) + + case __NR_execve: + case __NR_faccessat: // EPERM not a valid errno. + case __NR_fchmodat: + case __NR_fchownat: // Should be called chownat ? +#if defined(__x86_64__) || defined(__aarch64__) + case __NR_newfstatat: // fstatat(). EPERM not a valid errno. +#elif defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fstatat64: +#endif +#if defined(__i386__) || defined(__arm__) + case __NR_lchown32: +#endif + case __NR_linkat: + case __NR_lookup_dcookie: // ENOENT not a valid errno. + +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_lstat64: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__x86_64__) + case __NR_memfd_create: +#endif + case __NR_mkdirat: + case __NR_mknodat: +#if defined(__i386__) + case __NR_oldlstat: + case __NR_oldstat: +#endif + case __NR_openat: + case __NR_readlinkat: + case __NR_renameat: + case __NR_renameat2: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_stat64: +#endif + case __NR_statfs: // EPERM not a valid errno. +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_statfs64: +#endif + case __NR_symlinkat: + case __NR_truncate: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_truncate64: +#endif + case __NR_unlinkat: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_utime: +#endif + case __NR_utimensat: // New. + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedFileSystemAccessViaFd(int sysno) { + switch (sysno) { + case __NR_fstat: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fstat64: +#endif + return true; +// TODO(jln): these should be denied gracefully as well (moved below). +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_fadvise64: // EPERM not a valid errno. +#endif +#if defined(__i386__) + case __NR_fadvise64_64: +#endif +#if defined(__arm__) + case __NR_arm_fadvise64_64: +#endif + case __NR_fdatasync: // EPERM not a valid errno. + case __NR_flock: // EPERM not a valid errno. + case __NR_fstatfs: // Give information about the whole filesystem. +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fstatfs64: +#endif + case __NR_fsync: // EPERM not a valid errno. +#if defined(__i386__) + case __NR_oldfstat: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_sync_file_range: // EPERM not a valid errno. +#elif defined(__arm__) + case __NR_arm_sync_file_range: // EPERM not a valid errno. +#endif + default: + return false; + } +} + +// EPERM is a good errno for any of these. +bool SyscallSets::IsDeniedFileSystemAccessViaFd(int sysno) { + switch (sysno) { + case __NR_fallocate: + case __NR_fchmod: + case __NR_fchown: + case __NR_ftruncate: +#if defined(__i386__) || defined(__arm__) + case __NR_fchown32: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_ftruncate64: +#endif +#if !defined(__aarch64__) + case __NR_getdents: // EPERM not a valid errno. +#endif + case __NR_getdents64: // EPERM not a valid errno. +#if defined(__i386__) || defined(__mips__) + case __NR_readdir: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsGetSimpleId(int sysno) { + switch (sysno) { + case __NR_capget: + case __NR_getegid: + case __NR_geteuid: + case __NR_getgid: + case __NR_getgroups: + case __NR_getpid: + case __NR_getppid: + case __NR_getresgid: + case __NR_getsid: + case __NR_gettid: + case __NR_getuid: + case __NR_getresuid: +#if defined(__i386__) || defined(__arm__) + case __NR_getegid32: + case __NR_geteuid32: + case __NR_getgid32: + case __NR_getgroups32: + case __NR_getresgid32: + case __NR_getresuid32: + case __NR_getuid32: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsProcessPrivilegeChange(int sysno) { + switch (sysno) { + case __NR_capset: +#if defined(__i386__) || defined(__x86_64__) + case __NR_ioperm: // Intel privilege. + case __NR_iopl: // Intel privilege. +#endif + case __NR_setfsgid: + case __NR_setfsuid: + case __NR_setgid: + case __NR_setgroups: + case __NR_setregid: + case __NR_setresgid: + case __NR_setresuid: + case __NR_setreuid: + case __NR_setuid: +#if defined(__i386__) || defined(__arm__) + case __NR_setfsgid32: + case __NR_setfsuid32: + case __NR_setgid32: + case __NR_setgroups32: + case __NR_setregid32: + case __NR_setresgid32: + case __NR_setresuid32: + case __NR_setreuid32: + case __NR_setuid32: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsProcessGroupOrSession(int sysno) { + switch (sysno) { + case __NR_setpgid: +#if !defined(__aarch64__) + case __NR_getpgrp: +#endif + case __NR_setsid: + case __NR_getpgid: + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedSignalHandling(int sysno) { + switch (sysno) { + case __NR_rt_sigaction: + case __NR_rt_sigprocmask: + case __NR_rt_sigreturn: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_sigaction: + case __NR_sigprocmask: + case __NR_sigreturn: +#endif + return true; + case __NR_rt_sigpending: + case __NR_rt_sigqueueinfo: + case __NR_rt_sigsuspend: + case __NR_rt_sigtimedwait: + case __NR_rt_tgsigqueueinfo: + case __NR_sigaltstack: +#if !defined(__aarch64__) + case __NR_signalfd: +#endif + case __NR_signalfd4: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_sigpending: + case __NR_sigsuspend: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_signal: + case __NR_sgetmask: // Obsolete. + case __NR_ssetmask: +#endif + default: + return false; + } +} + +bool SyscallSets::IsAllowedOperationOnFd(int sysno) { + switch (sysno) { + case __NR_close: + case __NR_dup: +#if !defined(__aarch64__) + case __NR_dup2: +#endif + case __NR_dup3: +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_shutdown: +#endif + return true; + case __NR_fcntl: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_fcntl64: +#endif + default: + return false; + } +} + +bool SyscallSets::IsKernelInternalApi(int sysno) { + switch (sysno) { + case __NR_restart_syscall: +#if defined(__arm__) + case __ARM_NR_cmpxchg: +#endif + return true; + default: + return false; + } +} + +// This should be thought through in conjunction with IsFutex(). +bool SyscallSets::IsAllowedProcessStartOrDeath(int sysno) { + switch (sysno) { + case __NR_exit: + case __NR_exit_group: + case __NR_wait4: + case __NR_waitid: +#if defined(__i386__) + case __NR_waitpid: +#endif + return true; + case __NR_clone: // Should be parameter-restricted. + case __NR_setns: // Privileged. +#if !defined(__aarch64__) + case __NR_fork: +#endif +#if defined(__i386__) || defined(__x86_64__) + case __NR_get_thread_area: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_set_thread_area: +#endif + case __NR_set_tid_address: + case __NR_unshare: +#if !defined(__mips__) && !defined(__aarch64__) + case __NR_vfork: +#endif + default: + return false; + } +} + +// It's difficult to restrict those, but there is attack surface here. +bool SyscallSets::IsAllowedFutex(int sysno) { + switch (sysno) { + case __NR_get_robust_list: + case __NR_set_robust_list: + case __NR_futex: + default: + return false; + } +} + +bool SyscallSets::IsAllowedEpoll(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_epoll_create: + case __NR_epoll_wait: +#endif + case __NR_epoll_create1: + case __NR_epoll_ctl: + return true; + default: +#if defined(__x86_64__) + case __NR_epoll_ctl_old: +#endif + case __NR_epoll_pwait: +#if defined(__x86_64__) + case __NR_epoll_wait_old: +#endif + return false; + } +} + +bool SyscallSets::IsAllowedGetOrModifySocket(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_pipe: +#endif + case __NR_pipe2: + return true; + default: +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_socketpair: // We will want to inspect its argument. +#endif + return false; + } +} + +bool SyscallSets::IsDeniedGetOrModifySocket(int sysno) { + switch (sysno) { +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_accept: + case __NR_accept4: + case __NR_bind: + case __NR_connect: + case __NR_socket: + case __NR_listen: + return true; +#endif + default: + return false; + } +} + +#if defined(__i386__) || defined(__mips__) +// Big multiplexing system call for sockets. +bool SyscallSets::IsSocketCall(int sysno) { + switch (sysno) { + case __NR_socketcall: + return true; + default: + return false; + } +} +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) +bool SyscallSets::IsNetworkSocketInformation(int sysno) { + switch (sysno) { + case __NR_getpeername: + case __NR_getsockname: + case __NR_getsockopt: + case __NR_setsockopt: + return true; + default: + return false; + } +} +#endif + +bool SyscallSets::IsAllowedAddressSpaceAccess(int sysno) { + switch (sysno) { + case __NR_brk: + case __NR_mlock: + case __NR_munlock: + case __NR_munmap: + return true; + case __NR_madvise: + case __NR_mincore: + case __NR_mlockall: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_mmap: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_mmap2: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_modify_ldt: +#endif + case __NR_mprotect: + case __NR_mremap: + case __NR_msync: + case __NR_munlockall: + case __NR_readahead: + case __NR_remap_file_pages: +#if defined(__i386__) + case __NR_vm86: + case __NR_vm86old: +#endif + default: + return false; + } +} + +bool SyscallSets::IsAllowedGeneralIo(int sysno) { + switch (sysno) { + case __NR_lseek: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR__llseek: +#endif +#if !defined(__aarch64__) + case __NR_poll: +#endif + case __NR_ppoll: + case __NR_pselect6: + case __NR_read: + case __NR_readv: +#if defined(__arm__) || defined(__mips__) + case __NR_recv: +#endif +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_recvfrom: // Could specify source. + case __NR_recvmsg: // Could specify source. +#endif +#if defined(__i386__) || defined(__x86_64__) + case __NR_select: +#endif +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR__newselect: +#endif +#if defined(__arm__) + case __NR_send: +#endif +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_sendmsg: // Could specify destination. + case __NR_sendto: // Could specify destination. +#endif + case __NR_write: + case __NR_writev: + return true; + case __NR_ioctl: // Can be very powerful. + case __NR_pread64: + case __NR_preadv: + case __NR_pwrite64: + case __NR_pwritev: + case __NR_recvmmsg: // Could specify source. + case __NR_sendfile: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_sendfile64: +#endif + case __NR_sendmmsg: // Could specify destination. + case __NR_splice: + case __NR_tee: + case __NR_vmsplice: + default: + return false; + } +} + +bool SyscallSets::IsPrctl(int sysno) { + switch (sysno) { +#if defined(__x86_64__) + case __NR_arch_prctl: +#endif + case __NR_prctl: + return true; + default: + return false; + } +} + +bool SyscallSets::IsSeccomp(int sysno) { + switch (sysno) { + case __NR_seccomp: + return true; + default: + return false; + } +} + +bool SyscallSets::IsAllowedBasicScheduler(int sysno) { + switch (sysno) { + case __NR_sched_yield: +#if !defined(__aarch64__) + case __NR_pause: +#endif + case __NR_nanosleep: + return true; + case __NR_getpriority: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_nice: +#endif + case __NR_setpriority: + default: + return false; + } +} + +bool SyscallSets::IsAdminOperation(int sysno) { + switch (sysno) { +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_bdflush: +#endif + case __NR_kexec_load: + case __NR_reboot: + case __NR_setdomainname: + case __NR_sethostname: + case __NR_syslog: + return true; + default: + return false; + } +} + +bool SyscallSets::IsKernelModule(int sysno) { + switch (sysno) { +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_create_module: + case __NR_get_kernel_syms: // Should ENOSYS. + case __NR_query_module: +#endif + case __NR_delete_module: + case __NR_init_module: + case __NR_finit_module: + return true; + default: + return false; + } +} + +bool SyscallSets::IsGlobalFSViewChange(int sysno) { + switch (sysno) { + case __NR_pivot_root: + case __NR_chroot: + case __NR_sync: + return true; + default: + return false; + } +} + +bool SyscallSets::IsFsControl(int sysno) { + switch (sysno) { + case __NR_mount: + case __NR_nfsservctl: + case __NR_quotactl: + case __NR_swapoff: + case __NR_swapon: +#if defined(__i386__) || defined(__mips__) + case __NR_umount: +#endif + case __NR_umount2: + return true; + default: + return false; + } +} + +bool SyscallSets::IsNuma(int sysno) { + switch (sysno) { + case __NR_get_mempolicy: + case __NR_getcpu: + case __NR_mbind: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_migrate_pages: +#endif + case __NR_move_pages: + case __NR_set_mempolicy: + return true; + default: + return false; + } +} + +bool SyscallSets::IsMessageQueue(int sysno) { + switch (sysno) { + case __NR_mq_getsetattr: + case __NR_mq_notify: + case __NR_mq_open: + case __NR_mq_timedreceive: + case __NR_mq_timedsend: + case __NR_mq_unlink: + return true; + default: + return false; + } +} + +bool SyscallSets::IsGlobalProcessEnvironment(int sysno) { + switch (sysno) { + case __NR_acct: // Privileged. +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_getrlimit: +#endif +#if defined(__i386__) || defined(__arm__) + case __NR_ugetrlimit: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_ulimit: +#endif + case __NR_getrusage: + case __NR_personality: // Can change its personality as well. + case __NR_prlimit64: // Like setrlimit / getrlimit. + case __NR_setrlimit: + case __NR_times: + return true; + default: + return false; + } +} + +bool SyscallSets::IsDebug(int sysno) { + switch (sysno) { + case __NR_ptrace: + case __NR_process_vm_readv: + case __NR_process_vm_writev: + case __NR_kcmp: + return true; + default: + return false; + } +} + +bool SyscallSets::IsGlobalSystemStatus(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR__sysctl: + case __NR_sysfs: +#endif + case __NR_sysinfo: + case __NR_uname: +#if defined(__i386__) + case __NR_olduname: + case __NR_oldolduname: +#endif + return true; + default: + return false; + } +} + +bool SyscallSets::IsEventFd(int sysno) { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_eventfd: +#endif + case __NR_eventfd2: + return true; + default: + return false; + } +} + +// Asynchronous I/O API. +bool SyscallSets::IsAsyncIo(int sysno) { + switch (sysno) { + case __NR_io_cancel: + case __NR_io_destroy: + case __NR_io_getevents: + case __NR_io_setup: + case __NR_io_submit: + return true; + default: + return false; + } +} + +bool SyscallSets::IsKeyManagement(int sysno) { + switch (sysno) { + case __NR_add_key: + case __NR_keyctl: + case __NR_request_key: + return true; + default: + return false; + } +} + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +bool SyscallSets::IsSystemVSemaphores(int sysno) { + switch (sysno) { + case __NR_semctl: + case __NR_semget: + case __NR_semop: + case __NR_semtimedop: + return true; + default: + return false; + } +} +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +// These give a lot of ambient authority and bypass the setuid sandbox. +bool SyscallSets::IsSystemVSharedMemory(int sysno) { + switch (sysno) { + case __NR_shmat: + case __NR_shmctl: + case __NR_shmdt: + case __NR_shmget: + return true; + default: + return false; + } +} +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +bool SyscallSets::IsSystemVMessageQueue(int sysno) { + switch (sysno) { + case __NR_msgctl: + case __NR_msgget: + case __NR_msgrcv: + case __NR_msgsnd: + return true; + default: + return false; + } +} +#endif + +#if defined(__i386__) || defined(__mips__) +// Big system V multiplexing system call. +bool SyscallSets::IsSystemVIpc(int sysno) { + switch (sysno) { + case __NR_ipc: + return true; + default: + return false; + } +} +#endif + +bool SyscallSets::IsAnySystemV(int sysno) { +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + return IsSystemVMessageQueue(sysno) || IsSystemVSemaphores(sysno) || + IsSystemVSharedMemory(sysno); +#elif defined(__i386__) || defined(__mips__) + return IsSystemVIpc(sysno); +#endif +} + +bool SyscallSets::IsAdvancedScheduler(int sysno) { + switch (sysno) { + case __NR_ioprio_get: // IO scheduler. + case __NR_ioprio_set: + case __NR_sched_get_priority_max: + case __NR_sched_get_priority_min: + case __NR_sched_getaffinity: + case __NR_sched_getattr: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_rr_get_interval: + case __NR_sched_setaffinity: + case __NR_sched_setattr: + case __NR_sched_setparam: + case __NR_sched_setscheduler: + return true; + default: + return false; + } +} + +bool SyscallSets::IsInotify(int sysno) { + switch (sysno) { + case __NR_inotify_add_watch: +#if !defined(__aarch64__) + case __NR_inotify_init: +#endif + case __NR_inotify_init1: + case __NR_inotify_rm_watch: + return true; + default: + return false; + } +} + +bool SyscallSets::IsFaNotify(int sysno) { + switch (sysno) { + case __NR_fanotify_init: + case __NR_fanotify_mark: + return true; + default: + return false; + } +} + +bool SyscallSets::IsTimer(int sysno) { + switch (sysno) { + case __NR_getitimer: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_alarm: +#endif + case __NR_setitimer: + return true; + default: + return false; + } +} + +bool SyscallSets::IsAdvancedTimer(int sysno) { + switch (sysno) { + case __NR_timer_create: + case __NR_timer_delete: + case __NR_timer_getoverrun: + case __NR_timer_gettime: + case __NR_timer_settime: + case __NR_timerfd_create: + case __NR_timerfd_gettime: + case __NR_timerfd_settime: + return true; + default: + return false; + } +} + +bool SyscallSets::IsExtendedAttributes(int sysno) { + switch (sysno) { + case __NR_fgetxattr: + case __NR_flistxattr: + case __NR_fremovexattr: + case __NR_fsetxattr: + case __NR_getxattr: + case __NR_lgetxattr: + case __NR_listxattr: + case __NR_llistxattr: + case __NR_lremovexattr: + case __NR_lsetxattr: + case __NR_removexattr: + case __NR_setxattr: + return true; + default: + return false; + } +} + +// Various system calls that need to be researched. +// TODO(jln): classify this better. +bool SyscallSets::IsMisc(int sysno) { + switch (sysno) { +#if !defined(__mips__) + case __NR_getrandom: +#endif + case __NR_name_to_handle_at: + case __NR_open_by_handle_at: + case __NR_perf_event_open: + case __NR_syncfs: + case __NR_vhangup: +// The system calls below are not implemented. +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_afs_syscall: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_break: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_getpmsg: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_gtty: + case __NR_idle: + case __NR_lock: + case __NR_mpx: + case __NR_prof: + case __NR_profil: +#endif +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_putpmsg: +#endif +#if defined(__x86_64__) + case __NR_security: +#endif +#if defined(__i386__) || defined(__mips__) + case __NR_stty: +#endif +#if defined(__x86_64__) + case __NR_tuxcall: +#endif +#if !defined(__aarch64__) + case __NR_vserver: +#endif + return true; + default: + return false; + } +} + +#if defined(__arm__) +bool SyscallSets::IsArmPciConfig(int sysno) { + switch (sysno) { + case __NR_pciconfig_iobase: + case __NR_pciconfig_read: + case __NR_pciconfig_write: + return true; + default: + return false; + } +} + +bool SyscallSets::IsArmPrivate(int sysno) { + switch (sysno) { + case __ARM_NR_breakpoint: + case __ARM_NR_cacheflush: + case __ARM_NR_set_tls: + case __ARM_NR_usr26: + case __ARM_NR_usr32: + return true; + default: + return false; + } +} +#endif // defined(__arm__) + +#if defined(__mips__) +bool SyscallSets::IsMipsPrivate(int sysno) { + switch (sysno) { + case __NR_cacheflush: + case __NR_cachectl: + return true; + default: + return false; + } +} + +bool SyscallSets::IsMipsMisc(int sysno) { + switch (sysno) { + case __NR_sysmips: + case __NR_unused150: + return true; + default: + return false; + } +} +#endif // defined(__mips__) +} // namespace sandbox. diff --git a/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h new file mode 100644 index 0000000000..5ba6335a95 --- /dev/null +++ b/sandbox/linux/seccomp-bpf-helpers/syscall_sets.h @@ -0,0 +1,112 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_SETS_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_SETS_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/sandbox_export.h" + +// These are helpers to build seccomp-bpf policies, i.e. policies for a +// sandbox that reduces the Linux kernel's attack surface. Given their +// nature, they don't have any clear semantics and are completely +// "implementation-defined". + +namespace sandbox { + +class SANDBOX_EXPORT SyscallSets { + public: + static bool IsKill(int sysno); + static bool IsAllowedGettime(int sysno); + static bool IsCurrentDirectory(int sysno); + static bool IsUmask(int sysno); + // System calls that directly access the file system. They might acquire + // a new file descriptor or otherwise perform an operation directly + // via a path. + static bool IsFileSystem(int sysno); + static bool IsAllowedFileSystemAccessViaFd(int sysno); + static bool IsDeniedFileSystemAccessViaFd(int sysno); + static bool IsGetSimpleId(int sysno); + static bool IsProcessPrivilegeChange(int sysno); + static bool IsProcessGroupOrSession(int sysno); + static bool IsAllowedSignalHandling(int sysno); + static bool IsAllowedOperationOnFd(int sysno); + static bool IsKernelInternalApi(int sysno); + // This should be thought through in conjunction with IsFutex(). + static bool IsAllowedProcessStartOrDeath(int sysno); + // It's difficult to restrict those, but there is attack surface here. + static bool IsAllowedFutex(int sysno); + static bool IsAllowedEpoll(int sysno); + static bool IsAllowedGetOrModifySocket(int sysno); + static bool IsDeniedGetOrModifySocket(int sysno); + +#if defined(__i386__) || defined(__mips__) + // Big multiplexing system call for sockets. + static bool IsSocketCall(int sysno); +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + static bool IsNetworkSocketInformation(int sysno); +#endif + + static bool IsAllowedAddressSpaceAccess(int sysno); + static bool IsAllowedGeneralIo(int sysno); + static bool IsPrctl(int sysno); + static bool IsSeccomp(int sysno); + static bool IsAllowedBasicScheduler(int sysno); + static bool IsAdminOperation(int sysno); + static bool IsKernelModule(int sysno); + static bool IsGlobalFSViewChange(int sysno); + static bool IsFsControl(int sysno); + static bool IsNuma(int sysno); + static bool IsMessageQueue(int sysno); + static bool IsGlobalProcessEnvironment(int sysno); + static bool IsDebug(int sysno); + static bool IsGlobalSystemStatus(int sysno); + static bool IsEventFd(int sysno); + // Asynchronous I/O API. + static bool IsAsyncIo(int sysno); + static bool IsKeyManagement(int sysno); +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + static bool IsSystemVSemaphores(int sysno); +#endif +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + // These give a lot of ambient authority and bypass the setuid sandbox. + static bool IsSystemVSharedMemory(int sysno); +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + static bool IsSystemVMessageQueue(int sysno); +#endif + +#if defined(__i386__) || defined(__mips__) + // Big system V multiplexing system call. + static bool IsSystemVIpc(int sysno); +#endif + + static bool IsAnySystemV(int sysno); + static bool IsAdvancedScheduler(int sysno); + static bool IsInotify(int sysno); + static bool IsFaNotify(int sysno); + static bool IsTimer(int sysno); + static bool IsAdvancedTimer(int sysno); + static bool IsExtendedAttributes(int sysno); + static bool IsMisc(int sysno); +#if defined(__arm__) + static bool IsArmPciConfig(int sysno); + static bool IsArmPrivate(int sysno); +#endif // defined(__arm__) +#if defined(__mips__) + static bool IsMipsPrivate(int sysno); + static bool IsMipsMisc(int sysno); +#endif // defined(__mips__) + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SyscallSets); +}; + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SECCOMP_BPF_HELPERS_SYSCALL_SETS_H_ diff --git a/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc b/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc new file mode 100644 index 0000000000..63e1814c90 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc @@ -0,0 +1,153 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/seccomp-bpf/bpf_tests.h" + +#include <errno.h> +#include <sys/ptrace.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::ResultExpr; + +namespace sandbox { + +namespace { + +class FourtyTwo { + public: + static const int kMagicValue = 42; + FourtyTwo() : value_(kMagicValue) {} + int value() { return value_; } + + private: + int value_; + DISALLOW_COPY_AND_ASSIGN(FourtyTwo); +}; + +class EmptyClassTakingPolicy : public bpf_dsl::Policy { + public: + explicit EmptyClassTakingPolicy(FourtyTwo* fourty_two) { + BPF_ASSERT(fourty_two); + BPF_ASSERT(FourtyTwo::kMagicValue == fourty_two->value()); + } + ~EmptyClassTakingPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + return Allow(); + } +}; + +BPF_TEST(BPFTest, + BPFAUXPointsToClass, + EmptyClassTakingPolicy, + FourtyTwo /* *BPF_AUX */) { + // BPF_AUX should point to an instance of FourtyTwo. + BPF_ASSERT(BPF_AUX); + BPF_ASSERT(FourtyTwo::kMagicValue == BPF_AUX->value()); +} + +void DummyTestFunction(FourtyTwo *fourty_two) { +} + +TEST(BPFTest, BPFTesterCompatibilityDelegateLeakTest) { + // Don't do anything, simply gives dynamic tools an opportunity to detect + // leaks. + { + BPFTesterCompatibilityDelegate<EmptyClassTakingPolicy, FourtyTwo> + simple_delegate(DummyTestFunction); + } + { + // Test polymorphism. + scoped_ptr<BPFTesterDelegate> simple_delegate( + new BPFTesterCompatibilityDelegate<EmptyClassTakingPolicy, FourtyTwo>( + DummyTestFunction)); + } +} + +class EnosysPtracePolicy : public bpf_dsl::Policy { + public: + EnosysPtracePolicy() { my_pid_ = sys_getpid(); } + ~EnosysPtracePolicy() override { + // Policies should be able to bind with the process on which they are + // created. They should never be created in a parent process. + BPF_ASSERT_EQ(my_pid_, sys_getpid()); + } + + ResultExpr EvaluateSyscall(int system_call_number) const override { + CHECK(SandboxBPF::IsValidSyscallNumber(system_call_number)); + if (system_call_number == __NR_ptrace) { + // The EvaluateSyscall function should run in the process that created + // the current object. + BPF_ASSERT_EQ(my_pid_, sys_getpid()); + return Error(ENOSYS); + } else { + return Allow(); + } + } + + private: + pid_t my_pid_; + DISALLOW_COPY_AND_ASSIGN(EnosysPtracePolicy); +}; + +class BasicBPFTesterDelegate : public BPFTesterDelegate { + public: + BasicBPFTesterDelegate() {} + ~BasicBPFTesterDelegate() override {} + + scoped_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return scoped_ptr<bpf_dsl::Policy>(new EnosysPtracePolicy()); + } + void RunTestFunction() override { + errno = 0; + int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL); + BPF_ASSERT(-1 == ret); + BPF_ASSERT(ENOSYS == errno); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicBPFTesterDelegate); +}; + +// This is the most powerful and complex way to create a BPF test, but it +// requires a full class definition (BasicBPFTesterDelegate). +BPF_TEST_D(BPFTest, BPFTestWithDelegateClass, BasicBPFTesterDelegate); + +// This is the simplest form of BPF tests. +BPF_TEST_C(BPFTest, BPFTestWithInlineTest, EnosysPtracePolicy) { + errno = 0; + int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL); + BPF_ASSERT(-1 == ret); + BPF_ASSERT(ENOSYS == errno); +} + +const char kHelloMessage[] = "Hello"; + +BPF_DEATH_TEST_C(BPFTest, + BPFDeathTestWithInlineTest, + DEATH_MESSAGE(kHelloMessage), + EnosysPtracePolicy) { + LOG(ERROR) << kHelloMessage; + _exit(1); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/die.cc b/sandbox/linux/seccomp-bpf/die.cc new file mode 100644 index 0000000000..3baf1f13d9 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/die.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/seccomp-bpf/die.h" + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <string> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace sandbox { + +void Die::ExitGroup() { + // exit_group() should exit our program. After all, it is defined as a + // function that doesn't return. But things can theoretically go wrong. + // Especially, since we are dealing with system call filters. Continuing + // execution would be very bad in most cases where ExitGroup() gets called. + // So, we'll try a few other strategies too. + Syscall::Call(__NR_exit_group, 1); + + // We have no idea what our run-time environment looks like. So, signal + // handlers might or might not do the right thing. Try to reset settings + // to a defined state; but we have not way to verify whether we actually + // succeeded in doing so. Nonetheless, triggering a fatal signal could help + // us terminate. + struct sigaction sa = {}; + sa.sa_handler = LINUX_SIG_DFL; + sa.sa_flags = LINUX_SA_RESTART; + sys_sigaction(LINUX_SIGSEGV, &sa, nullptr); + Syscall::Call(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0); + if (*(volatile char*)0) { + } + + // If there is no way for us to ask for the program to exit, the next + // best thing we can do is to loop indefinitely. Maybe, somebody will notice + // and file a bug... + // We in fact retry the system call inside of our loop so that it will + // stand out when somebody tries to diagnose the problem by using "strace". + for (;;) { + Syscall::Call(__NR_exit_group, 1); + } +} + +void Die::SandboxDie(const char* msg, const char* file, int line) { + if (simple_exit_) { + LogToStderr(msg, file, line); + } else { + logging::LogMessage(file, line, logging::LOG_FATAL).stream() << msg; + } + ExitGroup(); +} + +void Die::RawSandboxDie(const char* msg) { + if (!msg) + msg = ""; + RAW_LOG(FATAL, msg); + ExitGroup(); +} + +void Die::SandboxInfo(const char* msg, const char* file, int line) { + if (!suppress_info_) { + logging::LogMessage(file, line, logging::LOG_INFO).stream() << msg; + } +} + +void Die::LogToStderr(const char* msg, const char* file, int line) { + if (msg) { + char buf[40]; + snprintf(buf, sizeof(buf), "%d", line); + std::string s = std::string(file) + ":" + buf + ":" + msg + "\n"; + + // No need to loop. Short write()s are unlikely and if they happen we + // probably prefer them over a loop that blocks. + ignore_result( + HANDLE_EINTR(Syscall::Call(__NR_write, 2, s.c_str(), s.length()))); + } +} + +bool Die::simple_exit_ = false; +bool Die::suppress_info_ = false; + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/die.h b/sandbox/linux/seccomp-bpf/die.h new file mode 100644 index 0000000000..b3f3f72c2f --- /dev/null +++ b/sandbox/linux/seccomp-bpf/die.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This is the main API for using this file. Prints a error message and +// exits with a fatal error. This is not async-signal safe. +#define SANDBOX_DIE(m) sandbox::Die::SandboxDie(m, __FILE__, __LINE__) + +// An async signal safe version of the same API. Won't print the filename +// and line numbers. +#define RAW_SANDBOX_DIE(m) sandbox::Die::RawSandboxDie(m) + +// Adds an informational message to the log file or stderr as appropriate. +#define SANDBOX_INFO(m) sandbox::Die::SandboxInfo(m, __FILE__, __LINE__) + +class SANDBOX_EXPORT Die { + public: + // Terminate the program, even if the current sandbox policy prevents some + // of the more commonly used functions used for exiting. + // Most users would want to call SANDBOX_DIE() instead, as it logs extra + // information. But calling ExitGroup() is correct and in some rare cases + // preferable. So, we make it part of the public API. + static void ExitGroup() __attribute__((noreturn)); + + // This method gets called by SANDBOX_DIE(). There is normally no reason + // to call it directly unless you are defining your own exiting macro. + static void SandboxDie(const char* msg, const char* file, int line) + __attribute__((noreturn)); + + static void RawSandboxDie(const char* msg) __attribute__((noreturn)); + + // This method gets called by SANDBOX_INFO(). There is normally no reason + // to call it directly unless you are defining your own logging macro. + static void SandboxInfo(const char* msg, const char* file, int line); + + // Writes a message to stderr. Used as a fall-back choice, if we don't have + // any other way to report an error. + static void LogToStderr(const char* msg, const char* file, int line); + + // We generally want to run all exit handlers. This means, on SANDBOX_DIE() + // we should be calling LOG(FATAL). But there are some situations where + // we just need to print a message and then terminate. This would typically + // happen in cases where we consume the error message internally (e.g. in + // unit tests or in the supportsSeccompSandbox() method). + static void EnableSimpleExit() { simple_exit_ = true; } + + // Sometimes we need to disable all informational messages (e.g. from within + // unittests). + static void SuppressInfoMessages(bool flag) { suppress_info_ = flag; } + + private: + static bool simple_exit_; + static bool suppress_info_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Die); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc new file mode 100644 index 0000000000..3e4f0adf53 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -0,0 +1,279 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/seccomp-bpf/sandbox_bpf.h" + +// Some headers on Android are missing cdefs: crbug.com/172337. +// (We can't use OS_ANDROID here since build_config.h is not included). +#if defined(ANDROID) +#include <sys/cdefs.h> +#endif + +#include <errno.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/compiler_specific.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "third_party/valgrind/valgrind.h" + +namespace sandbox { + +namespace { + +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +bool IsSingleThreaded(int proc_fd) { + return ThreadHelpers::IsSingleThreaded(proc_fd); +} + +// Check if the kernel supports seccomp-filter (a.k.a. seccomp mode 2) via +// prctl(). +bool KernelSupportsSeccompBPF() { + errno = 0; + const int rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr); + + if (rv == -1 && EFAULT == errno) { + return true; + } + return false; +} + +// LG introduced a buggy syscall, sys_set_media_ext, with the same number as +// seccomp. Return true if the current kernel has this buggy syscall. +// +// We want this to work with upcoming versions of seccomp, so we pass bogus +// flags that are unlikely to ever be used by the kernel. A normal kernel would +// return -EINVAL, but a buggy LG kernel would return 1. +bool KernelHasLGBug() { +#if defined(OS_ANDROID) + // sys_set_media will see this as NULL, which should be a safe (non-crashing) + // way to invoke it. A genuine seccomp syscall will see it as + // SECCOMP_SET_MODE_STRICT. + const unsigned int operation = 0; + // Chosen by fair dice roll. Guaranteed to be random. + const unsigned int flags = 0xf7a46a5c; + const int rv = sys_seccomp(operation, flags, nullptr); + // A genuine kernel would return -EINVAL (which would set rv to -1 and errno + // to EINVAL), or at the very least return some kind of error (which would + // set rv to -1). Any other behavior indicates that whatever code received + // our syscall was not the real seccomp. + if (rv != -1) { + return true; + } +#endif // defined(OS_ANDROID) + + return false; +} + +// Check if the kernel supports seccomp-filter via the seccomp system call +// and the TSYNC feature to enable seccomp on all threads. +bool KernelSupportsSeccompTsync() { + if (KernelHasLGBug()) { + return false; + } + + errno = 0; + const int rv = + sys_seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, nullptr); + + if (rv == -1 && errno == EFAULT) { + return true; + } else { + // TODO(jln): turn these into DCHECK after 417888 is considered fixed. + CHECK_EQ(-1, rv); + CHECK(ENOSYS == errno || EINVAL == errno); + return false; + } +} + +uint64_t EscapePC() { + intptr_t rv = Syscall::Call(-1); + if (rv == -1 && errno == ENOSYS) { + return 0; + } + return static_cast<uint64_t>(static_cast<uintptr_t>(rv)); +} + +} // namespace + +SandboxBPF::SandboxBPF(bpf_dsl::Policy* policy) + : proc_fd_(), sandbox_has_started_(false), policy_(policy) { +} + +SandboxBPF::~SandboxBPF() { +} + +// static +bool SandboxBPF::SupportsSeccompSandbox(SeccompLevel level) { + // Never pretend to support seccomp with Valgrind, as it + // throws the tool off. + if (IsRunningOnValgrind()) { + return false; + } + + switch (level) { + case SeccompLevel::SINGLE_THREADED: + return KernelSupportsSeccompBPF(); + case SeccompLevel::MULTI_THREADED: + return KernelSupportsSeccompTsync(); + } + NOTREACHED(); + return false; +} + +bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level) { + DCHECK(policy_); + CHECK(seccomp_level == SeccompLevel::SINGLE_THREADED || + seccomp_level == SeccompLevel::MULTI_THREADED); + + if (sandbox_has_started_) { + SANDBOX_DIE( + "Cannot repeatedly start sandbox. Create a separate Sandbox " + "object instead."); + return false; + } + + if (!proc_fd_.is_valid()) { + SetProcFd(ProcUtil::OpenProc()); + } + + const bool supports_tsync = KernelSupportsSeccompTsync(); + + if (seccomp_level == SeccompLevel::SINGLE_THREADED) { + // Wait for /proc/self/task/ to update if needed and assert the + // process is single threaded. + ThreadHelpers::AssertSingleThreaded(proc_fd_.get()); + } else if (seccomp_level == SeccompLevel::MULTI_THREADED) { + if (IsSingleThreaded(proc_fd_.get())) { + SANDBOX_DIE("Cannot start sandbox; " + "process may be single-threaded when reported as not"); + return false; + } + if (!supports_tsync) { + SANDBOX_DIE("Cannot start sandbox; kernel does not support synchronizing " + "filters for a threadgroup"); + return false; + } + } + + // We no longer need access to any files in /proc. We want to do this + // before installing the filters, just in case that our policy denies + // close(). + if (proc_fd_.is_valid()) { + proc_fd_.reset(); + } + + // Install the filters. + InstallFilter(supports_tsync || + seccomp_level == SeccompLevel::MULTI_THREADED); + + return true; +} + +void SandboxBPF::SetProcFd(base::ScopedFD proc_fd) { + proc_fd_.swap(proc_fd); +} + +// static +bool SandboxBPF::IsValidSyscallNumber(int sysnum) { + return SyscallSet::IsValid(sysnum); +} + +// static +bool SandboxBPF::IsRequiredForUnsafeTrap(int sysno) { + return bpf_dsl::PolicyCompiler::IsRequiredForUnsafeTrap(sysno); +} + +// static +intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) { + return Syscall::Call( + args.nr, static_cast<intptr_t>(args.args[0]), + static_cast<intptr_t>(args.args[1]), static_cast<intptr_t>(args.args[2]), + static_cast<intptr_t>(args.args[3]), static_cast<intptr_t>(args.args[4]), + static_cast<intptr_t>(args.args[5])); +} + +scoped_ptr<CodeGen::Program> SandboxBPF::AssembleFilter( + bool force_verification) { +#if !defined(NDEBUG) + force_verification = true; +#endif + DCHECK(policy_); + + bpf_dsl::PolicyCompiler compiler(policy_.get(), Trap::Registry()); + if (Trap::SandboxDebuggingAllowedByUser()) { + compiler.DangerousSetEscapePC(EscapePC()); + } + return compiler.Compile(force_verification); +} + +void SandboxBPF::InstallFilter(bool must_sync_threads) { + // We want to be very careful in not imposing any requirements on the + // policies that are set with SetSandboxPolicy(). This means, as soon as + // the sandbox is active, we shouldn't be relying on libraries that could + // be making system calls. This, for example, means we should avoid + // using the heap and we should avoid using STL functions. + // Temporarily copy the contents of the "program" vector into a + // stack-allocated array; and then explicitly destroy that object. + // This makes sure we don't ex- or implicitly call new/delete after we + // installed the BPF filter program in the kernel. Depending on the + // system memory allocator that is in effect, these operators can result + // in system calls to things like munmap() or brk(). + CodeGen::Program* program = AssembleFilter(false).release(); + + struct sock_filter bpf[program->size()]; + const struct sock_fprog prog = {static_cast<unsigned short>(program->size()), + bpf}; + memcpy(bpf, &(*program)[0], sizeof(bpf)); + delete program; + + // Make an attempt to release memory that is no longer needed here, rather + // than in the destructor. Try to avoid as much as possible to presume of + // what will be possible to do in the new (sandboxed) execution environment. + policy_.reset(); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + SANDBOX_DIE("Kernel refuses to enable no-new-privs"); + } + + // Install BPF filter program. If the thread state indicates multi-threading + // support, then the kernel hass the seccomp system call. Otherwise, fall + // back on prctl, which requires the process to be single-threaded. + if (must_sync_threads) { + int rv = + sys_seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog); + if (rv) { + SANDBOX_DIE( + "Kernel refuses to turn on and synchronize threads for BPF filters"); + } + } else { + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + SANDBOX_DIE("Kernel refuses to turn on BPF filters"); + } + } + + sandbox_has_started_ = true; +} + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h new file mode 100644 index 0000000000..96cceb5648 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ + +#include <stdint.h> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +struct arch_seccomp_data; +namespace bpf_dsl { +class Policy; +} + +// This class can be used to apply a syscall sandboxing policy expressed in a +// bpf_dsl::Policy object to the current process. +// Syscall sandboxing policies get inherited by subprocesses and, once applied, +// can never be removed for the lifetime of the process. +class SANDBOX_EXPORT SandboxBPF { + public: + enum class SeccompLevel { + SINGLE_THREADED, + MULTI_THREADED, + }; + + // Ownership of |policy| is transfered here to the sandbox object. + // nullptr is allowed for unit tests. + explicit SandboxBPF(bpf_dsl::Policy* policy); + // NOTE: Setting a policy and starting the sandbox is a one-way operation. + // The kernel does not provide any option for unloading a loaded sandbox. The + // sandbox remains engaged even when the object is destructed. + ~SandboxBPF(); + + // Detect if the kernel supports the specified seccomp level. + // See StartSandbox() for a description of these. + static bool SupportsSeccompSandbox(SeccompLevel level); + + // This is the main public entry point. It sets up the resources needed by + // the sandbox, and enters Seccomp mode. + // The calling process must provide a |level| to tell the sandbox which type + // of kernel support it should engage. + // SINGLE_THREADED will only sandbox the calling thread. Since it would be a + // security risk, the sandbox will also check that the current process is + // single threaded and crash if it isn't the case. + // MULTI_THREADED requires more recent kernel support and allows to sandbox + // all the threads of the current process. Be mindful of potential races, + // with other threads using disallowed system calls either before or after + // the sandbox is engaged. + // + // It is possible to stack multiple sandboxes by creating separate "Sandbox" + // objects and calling "StartSandbox()" on each of them. Please note, that + // this requires special care, though, as newly stacked sandboxes can never + // relax restrictions imposed by earlier sandboxes. Furthermore, installing + // a new policy requires making system calls, that might already be + // disallowed. + // Finally, stacking does add more kernel overhead than having a single + // combined policy. So, it should only be used if there are no alternatives. + bool StartSandbox(SeccompLevel level) WARN_UNUSED_RESULT; + + // The sandbox needs to be able to access files in "/proc/self/". If + // this directory is not accessible when "StartSandbox()" gets called, the + // caller must provide an already opened file descriptor by calling + // "SetProcFd()". + // The sandbox becomes the new owner of this file descriptor and will + // close it when "StartSandbox()" executes or when the sandbox object + // disappears. + void SetProcFd(base::ScopedFD proc_fd); + + // Checks whether a particular system call number is valid on the current + // architecture. + static bool IsValidSyscallNumber(int sysnum); + + // UnsafeTraps require some syscalls to always be allowed. + // This helper function returns true for these calls. + static bool IsRequiredForUnsafeTrap(int sysno); + + // From within an UnsafeTrap() it is often useful to be able to execute + // the system call that triggered the trap. The ForwardSyscall() method + // makes this easy. It is more efficient than calling glibc's syscall() + // function, as it avoid the extra round-trip to the signal handler. And + // it automatically does the correct thing to report kernel-style error + // conditions, rather than setting errno. See the comments for TrapFnc for + // details. In other words, the return value from ForwardSyscall() is + // directly suitable as a return value for a trap handler. + static intptr_t ForwardSyscall(const struct arch_seccomp_data& args); + + // Assembles a BPF filter program from the current policy. After calling this + // function, you must not call any other sandboxing function. + // Typically, AssembleFilter() is only used by unit tests and by sandbox + // internals. It should not be used by production code. + // For performance reasons, we normally only run the assembled BPF program + // through the verifier, iff the program was built in debug mode. + // But by setting "force_verification", the caller can request that the + // verifier is run unconditionally. This is useful for unittests. + scoped_ptr<CodeGen::Program> AssembleFilter(bool force_verification); + + private: + // Assembles and installs a filter based on the policy that has previously + // been configured with SetSandboxPolicy(). + void InstallFilter(bool must_sync_threads); + + base::ScopedFD proc_fd_; + bool sandbox_has_started_; + scoped_ptr<bpf_dsl::Policy> policy_; + + DISALLOW_COPY_AND_ASSIGN(SandboxBPF); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ diff --git a/sandbox/linux/seccomp-bpf/syscall.cc b/sandbox/linux/seccomp-bpf/syscall.cc new file mode 100644 index 0000000000..bc6461f117 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall.cc @@ -0,0 +1,421 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/seccomp-bpf/syscall.h" + +#include <errno.h> +#include <stdint.h> + +#include "base/logging.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" + +namespace sandbox { + +namespace { + +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) +// Number that's not currently used by any Linux kernel ABIs. +const int kInvalidSyscallNumber = 0x351d3; +#else +#error Unrecognized architecture +#endif + +asm(// We need to be able to tell the kernel exactly where we made a + // system call. The C++ compiler likes to sometimes clone or + // inline code, which would inadvertently end up duplicating + // the entry point. + // "gcc" can suppress code duplication with suitable function + // attributes, but "clang" doesn't have this ability. + // The "clang" developer mailing list suggested that the correct + // and portable solution is a file-scope assembly block. + // N.B. We do mark our code as a proper function so that backtraces + // work correctly. But we make absolutely no attempt to use the + // ABI's calling conventions for passing arguments. We will only + // ever be called from assembly code and thus can pick more + // suitable calling conventions. +#if defined(__i386__) + ".text\n" + ".align 16, 0x90\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.cfi_startproc\n" + // Check if "%eax" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "int $0x80". This address can be + // used as a marker that BPF code inspects. + "test %eax, %eax\n" + "jge 1f\n" + // Always, make sure that our code is position-independent, or + // address space randomization might not work on i386. This means, + // we can't use "lea", but instead have to rely on "call/pop". + "call 0f; .cfi_adjust_cfa_offset 4\n" + "0:pop %eax; .cfi_adjust_cfa_offset -4\n" + "addl $2f-0b, %eax\n" + "ret\n" + // Save register that we don't want to clobber. On i386, we need to + // save relatively aggressively, as there are a couple or registers + // that are used internally (e.g. %ebx for position-independent + // code, and %ebp for the frame pointer), and as we need to keep at + // least a few registers available for the register allocator. + "1:push %esi; .cfi_adjust_cfa_offset 4; .cfi_rel_offset esi, 0\n" + "push %edi; .cfi_adjust_cfa_offset 4; .cfi_rel_offset edi, 0\n" + "push %ebx; .cfi_adjust_cfa_offset 4; .cfi_rel_offset ebx, 0\n" + "push %ebp; .cfi_adjust_cfa_offset 4; .cfi_rel_offset ebp, 0\n" + // Copy entries from the array holding the arguments into the + // correct CPU registers. + "movl 0(%edi), %ebx\n" + "movl 4(%edi), %ecx\n" + "movl 8(%edi), %edx\n" + "movl 12(%edi), %esi\n" + "movl 20(%edi), %ebp\n" + "movl 16(%edi), %edi\n" + // Enter the kernel. + "int $0x80\n" + // This is our "magic" return address that the BPF filter sees. + "2:" + // Restore any clobbered registers that we didn't declare to the + // compiler. + "pop %ebp; .cfi_restore ebp; .cfi_adjust_cfa_offset -4\n" + "pop %ebx; .cfi_restore ebx; .cfi_adjust_cfa_offset -4\n" + "pop %edi; .cfi_restore edi; .cfi_adjust_cfa_offset -4\n" + "pop %esi; .cfi_restore esi; .cfi_adjust_cfa_offset -4\n" + "ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__x86_64__) + ".text\n" + ".align 16, 0x90\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.cfi_startproc\n" + // Check if "%rdi" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "syscall". This address can be + // used as a marker that BPF code inspects. + "test %rdi, %rdi\n" + "jge 1f\n" + // Always make sure that our code is position-independent, or the + // linker will throw a hissy fit on x86-64. + "lea 2f(%rip), %rax\n" + "ret\n" + // Now we load the registers used to pass arguments to the system + // call: system call number in %rax, and arguments in %rdi, %rsi, + // %rdx, %r10, %r8, %r9. Note: These are all caller-save registers + // (only %rbx, %rbp, %rsp, and %r12-%r15 are callee-save), so no + // need to worry here about spilling registers or CFI directives. + "1:movq %rdi, %rax\n" + "movq 0(%rsi), %rdi\n" + "movq 16(%rsi), %rdx\n" + "movq 24(%rsi), %r10\n" + "movq 32(%rsi), %r8\n" + "movq 40(%rsi), %r9\n" + "movq 8(%rsi), %rsi\n" + // Enter the kernel. + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + "2:ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__arm__) + // Throughout this file, we use the same mode (ARM vs. thumb) + // that the C++ compiler uses. This means, when transfering control + // from C++ to assembly code, we do not need to switch modes (e.g. + // by using the "bx" instruction). It also means that our assembly + // code should not be invoked directly from code that lives in + // other compilation units, as we don't bother implementing thumb + // interworking. That's OK, as we don't make any of the assembly + // symbols public. They are all local to this file. + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" +#if defined(__thumb__) + ".thumb_func\n" +#else + ".arm\n" +#endif + "SyscallAsm:\n" +#if !defined(__native_client_nonsfi__) + // .fnstart and .fnend pseudo operations creates unwind table. + // It also creates a reference to the symbol __aeabi_unwind_cpp_pr0, which + // is not provided by PNaCl toolchain. Disable it. + ".fnstart\n" +#endif + "@ args = 0, pretend = 0, frame = 8\n" + "@ frame_needed = 1, uses_anonymous_args = 0\n" +#if defined(__thumb__) + ".cfi_startproc\n" + "push {r7, lr}\n" + ".save {r7, lr}\n" + ".cfi_offset 14, -4\n" + ".cfi_offset 7, -8\n" + ".cfi_def_cfa_offset 8\n" +#else + "stmfd sp!, {fp, lr}\n" + "add fp, sp, #4\n" +#endif + // Check if "r0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "swi 0". This address can be + // used as a marker that BPF code inspects. + "cmp r0, #0\n" + "bge 1f\n" + "adr r0, 2f\n" + "b 2f\n" + // We declared (almost) all clobbered registers to the compiler. On + // ARM there is no particular register pressure. So, we can go + // ahead and directly copy the entries from the arguments array + // into the appropriate CPU registers. + "1:ldr r5, [r6, #20]\n" + "ldr r4, [r6, #16]\n" + "ldr r3, [r6, #12]\n" + "ldr r2, [r6, #8]\n" + "ldr r1, [r6, #4]\n" + "mov r7, r0\n" + "ldr r0, [r6, #0]\n" + // Enter the kernel + "swi 0\n" +// Restore the frame pointer. Also restore the program counter from +// the link register; this makes us return to the caller. +#if defined(__thumb__) + "2:pop {r7, pc}\n" + ".cfi_endproc\n" +#else + "2:ldmfd sp!, {fp, pc}\n" +#endif +#if !defined(__native_client_nonsfi__) + // Do not use .fnstart and .fnend for PNaCl toolchain. See above comment, + // for more details. + ".fnend\n" +#endif + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__mips__) + ".text\n" + ".align 4\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.ent SyscallAsm\n" + ".frame $sp, 40, $ra\n" + ".set push\n" + ".set noreorder\n" + "addiu $sp, $sp, -40\n" + "sw $ra, 36($sp)\n" + // Check if "v0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "syscall". This address can be + // used as a marker that BPF code inspects. + "bgez $v0, 1f\n" + " nop\n" + "la $v0, 2f\n" + "b 2f\n" + " nop\n" + // On MIPS first four arguments go to registers a0 - a3 and any + // argument after that goes to stack. We can go ahead and directly + // copy the entries from the arguments array into the appropriate + // CPU registers and on the stack. + "1:lw $a3, 28($a0)\n" + "lw $a2, 24($a0)\n" + "lw $a1, 20($a0)\n" + "lw $t0, 16($a0)\n" + "sw $a3, 28($sp)\n" + "sw $a2, 24($sp)\n" + "sw $a1, 20($sp)\n" + "sw $t0, 16($sp)\n" + "lw $a3, 12($a0)\n" + "lw $a2, 8($a0)\n" + "lw $a1, 4($a0)\n" + "lw $a0, 0($a0)\n" + // Enter the kernel + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + // Restore the return address from the stack. + "2:lw $ra, 36($sp)\n" + "jr $ra\n" + " addiu $sp, $sp, 40\n" + ".set pop\n" + ".end SyscallAsm\n" + ".size SyscallAsm,.-SyscallAsm\n" +#elif defined(__aarch64__) + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" + "SyscallAsm:\n" + ".cfi_startproc\n" + "cmp x0, #0\n" + "b.ge 1f\n" + "adr x0,2f\n" + "b 2f\n" + "1:ldr x5, [x6, #40]\n" + "ldr x4, [x6, #32]\n" + "ldr x3, [x6, #24]\n" + "ldr x2, [x6, #16]\n" + "ldr x1, [x6, #8]\n" + "mov x8, x0\n" + "ldr x0, [x6, #0]\n" + // Enter the kernel + "svc 0\n" + "2:ret\n" + ".cfi_endproc\n" + ".size SyscallAsm, .-SyscallAsm\n" +#endif + ); // asm + +#if defined(__x86_64__) +extern "C" { +intptr_t SyscallAsm(intptr_t nr, const intptr_t args[6]); +} +#endif + +} // namespace + +intptr_t Syscall::InvalidCall() { + // Explicitly pass eight zero arguments just in case. + return Call(kInvalidSyscallNumber, 0, 0, 0, 0, 0, 0, 0, 0); +} + +intptr_t Syscall::Call(int nr, + intptr_t p0, + intptr_t p1, + intptr_t p2, + intptr_t p3, + intptr_t p4, + intptr_t p5, + intptr_t p6, + intptr_t p7) { + // We rely on "intptr_t" to be the exact size as a "void *". This is + // typically true, but just in case, we add a check. The language + // specification allows platforms some leeway in cases, where + // "sizeof(void *)" is not the same as "sizeof(void (*)())". We expect + // that this would only be an issue for IA64, which we are currently not + // planning on supporting. And it is even possible that this would work + // on IA64, but for lack of actual hardware, I cannot test. + static_assert(sizeof(void*) == sizeof(intptr_t), + "pointer types and intptr_t must be exactly the same size"); + + // TODO(nedeljko): Enable use of more than six parameters on architectures + // where that makes sense. +#if defined(__mips__) + const intptr_t args[8] = {p0, p1, p2, p3, p4, p5, p6, p7}; +#else + DCHECK_EQ(p6, 0) << " Support for syscalls with more than six arguments not " + "added for this architecture"; + DCHECK_EQ(p7, 0) << " Support for syscalls with more than six arguments not " + "added for this architecture"; + const intptr_t args[6] = {p0, p1, p2, p3, p4, p5}; +#endif // defined(__mips__) + +// Invoke our file-scope assembly code. The constraints have been picked +// carefully to match what the rest of the assembly code expects in input, +// output, and clobbered registers. +#if defined(__i386__) + intptr_t ret = nr; + asm volatile( + "call SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=a"(ret) + : "0"(ret), "D"(args) + : "cc", "esp", "memory", "ecx", "edx"); +#elif defined(__x86_64__) + intptr_t ret = SyscallAsm(nr, args); +#elif defined(__arm__) + intptr_t ret; + { + register intptr_t inout __asm__("r0") = nr; + register const intptr_t* data __asm__("r6") = args; + asm volatile( + "bl SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=r"(inout) + : "0"(inout), "r"(data) + : "cc", + "lr", + "memory", + "r1", + "r2", + "r3", + "r4", + "r5" +#if !defined(__thumb__) + // In thumb mode, we cannot use "r7" as a general purpose register, as + // it is our frame pointer. We have to manually manage and preserve + // it. + // In ARM mode, we have a dedicated frame pointer register and "r7" is + // thus available as a general purpose register. We don't preserve it, + // but instead mark it as clobbered. + , + "r7" +#endif // !defined(__thumb__) + ); + ret = inout; + } +#elif defined(__mips__) + int err_status; + intptr_t ret = Syscall::SandboxSyscallRaw(nr, args, &err_status); + + if (err_status) { + // On error, MIPS returns errno from syscall instead of -errno. + // The purpose of this negation is for SandboxSyscall() to behave + // more like it would on other architectures. + ret = -ret; + } +#elif defined(__aarch64__) + intptr_t ret; + { + register intptr_t inout __asm__("x0") = nr; + register const intptr_t* data __asm__("x6") = args; + asm volatile("bl SyscallAsm\n" + : "=r"(inout) + : "0"(inout), "r"(data) + : "memory", "x1", "x2", "x3", "x4", "x5", "x8", "x30"); + ret = inout; + } + +#else +#error "Unimplemented architecture" +#endif + return ret; +} + +void Syscall::PutValueInUcontext(intptr_t ret_val, ucontext_t* ctx) { +#if defined(__mips__) + // Mips ABI states that on error a3 CPU register has non zero value and if + // there is no error, it should be zero. + if (ret_val <= -1 && ret_val >= -4095) { + // |ret_val| followes the Syscall::Call() convention of being -errno on + // errors. In order to write correct value to return register this sign + // needs to be changed back. + ret_val = -ret_val; + SECCOMP_PARM4(ctx) = 1; + } else + SECCOMP_PARM4(ctx) = 0; +#endif + SECCOMP_RESULT(ctx) = static_cast<greg_t>(ret_val); +} + +#if defined(__mips__) +intptr_t Syscall::SandboxSyscallRaw(int nr, + const intptr_t* args, + intptr_t* err_ret) { + register intptr_t ret __asm__("v0") = nr; + // a3 register becomes non zero on error. + register intptr_t err_stat __asm__("a3") = 0; + { + register const intptr_t* data __asm__("a0") = args; + asm volatile( + "la $t9, SyscallAsm\n" + "jalr $t9\n" + " nop\n" + : "=r"(ret), "=r"(err_stat) + : "0"(ret), + "r"(data) + // a2 is in the clober list so inline assembly can not change its + // value. + : "memory", "ra", "t9", "a2"); + } + + // Set an error status so it can be used outside of this function + *err_ret = err_stat; + + return ret; +} +#endif // defined(__mips__) + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/syscall.h b/sandbox/linux/seccomp-bpf/syscall.h new file mode 100644 index 0000000000..ccfc88dcb3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall.h @@ -0,0 +1,166 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ + +#include <signal.h> +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This purely static class can be used to perform system calls with some +// low-level control. +class SANDBOX_EXPORT Syscall { + public: + // InvalidCall() invokes Call() with a platform-appropriate syscall + // number that is guaranteed to not be implemented (i.e., normally + // returns -ENOSYS). + // This is primarily meant to be useful for writing sandbox policy + // unit tests. + static intptr_t InvalidCall(); + + // System calls can take up to six parameters (up to eight on some + // architectures). Traditionally, glibc + // implements this property by using variadic argument lists. This works, but + // confuses modern tools such as valgrind, because we are nominally passing + // uninitialized data whenever we call through this function and pass less + // than the full six arguments. + // So, instead, we use C++'s template system to achieve a very similar + // effect. C++ automatically sets the unused parameters to zero for us, and + // it also does the correct type expansion (e.g. from 32bit to 64bit) where + // necessary. + // We have to use C-style cast operators as we want to be able to accept both + // integer and pointer types. + template <class T0, + class T1, + class T2, + class T3, + class T4, + class T5, + class T6, + class T7> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + (intptr_t)p6, + (intptr_t)p7); + } + + template <class T0, + class T1, + class T2, + class T3, + class T4, + class T5, + class T6> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + (intptr_t)p6, + 0); + } + + template <class T0, class T1, class T2, class T3, class T4, class T5> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + 0, + 0); + } + + template <class T0, class T1, class T2, class T3, class T4> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4) { + return Call(nr, p0, p1, p2, p3, p4, 0, 0, 0); + } + + template <class T0, class T1, class T2, class T3> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3) { + return Call(nr, p0, p1, p2, p3, 0, 0, 0, 0); + } + + template <class T0, class T1, class T2> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2) { + return Call(nr, p0, p1, p2, 0, 0, 0, 0, 0); + } + + template <class T0, class T1> + static inline intptr_t Call(int nr, T0 p0, T1 p1) { + return Call(nr, p0, p1, 0, 0, 0, 0, 0, 0); + } + + template <class T0> + static inline intptr_t Call(int nr, T0 p0) { + return Call(nr, p0, 0, 0, 0, 0, 0, 0, 0); + } + + static inline intptr_t Call(int nr) { + return Call(nr, 0, 0, 0, 0, 0, 0, 0, 0); + } + + // Set the registers in |ctx| to match what they would be after a system call + // returning |ret_val|. |ret_val| must follow the Syscall::Call() convention + // of being -errno on errors. + static void PutValueInUcontext(intptr_t ret_val, ucontext_t* ctx); + + private: + // This performs system call |nr| with the arguments p0 to p7 from a constant + // userland address, which is for instance observable by seccomp-bpf filters. + // The constant userland address from which these system calls are made will + // be returned if |nr| is passed as -1. + // On error, this function will return a value between -1 and -4095 which + // should be interpreted as -errno. + static intptr_t Call(int nr, + intptr_t p0, + intptr_t p1, + intptr_t p2, + intptr_t p3, + intptr_t p4, + intptr_t p5, + intptr_t p6, + intptr_t p7); + +#if defined(__mips__) + // This function basically does on MIPS what SandboxSyscall() is doing on + // other architectures. However, because of specificity of MIPS regarding + // handling syscall errors, SandboxSyscall() is made as a wrapper for this + // function in order for SandboxSyscall() to behave more like on other + // architectures on places where return value from SandboxSyscall() is used + // directly (like in most tests). + // The syscall "nr" is called with arguments that are set in an array on which + // pointer "args" points to and an information weather there is an error or no + // is returned to SandboxSyscall() by err_stat. + static intptr_t SandboxSyscallRaw(int nr, + const intptr_t* args, + intptr_t* err_stat); +#endif // defined(__mips__) + + DISALLOW_IMPLICIT_CONSTRUCTORS(Syscall); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ diff --git a/sandbox/linux/seccomp-bpf/syscall_unittest.cc b/sandbox/linux/seccomp-bpf/syscall_unittest.cc new file mode 100644 index 0000000000..5fdee6c495 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -0,0 +1,240 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/seccomp-bpf/syscall.h" + +#include <asm/unistd.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::ResultExpr; +using sandbox::bpf_dsl::Trap; + +namespace sandbox { + +namespace { + +// Different platforms use different symbols for the six-argument version +// of the mmap() system call. Test for the correct symbol at compile time. +#ifdef __NR_mmap2 +const int kMMapNr = __NR_mmap2; +#else +const int kMMapNr = __NR_mmap; +#endif + +TEST(Syscall, InvalidCallReturnsENOSYS) { + EXPECT_EQ(-ENOSYS, Syscall::InvalidCall()); +} + +TEST(Syscall, WellKnownEntryPoint) { +// Test that Syscall::Call(-1) is handled specially. Don't do this on ARM, +// where syscall(-1) crashes with SIGILL. Not running the test is fine, as we +// are still testing ARM code in the next set of tests. +#if !defined(__arm__) && !defined(__aarch64__) + EXPECT_NE(Syscall::Call(-1), syscall(-1)); +#endif + +// If possible, test that Syscall::Call(-1) returns the address right +// after +// a kernel entry point. +#if defined(__i386__) + EXPECT_EQ(0x80CDu, ((uint16_t*)Syscall::Call(-1))[-1]); // INT 0x80 +#elif defined(__x86_64__) + EXPECT_EQ(0x050Fu, ((uint16_t*)Syscall::Call(-1))[-1]); // SYSCALL +#elif defined(__arm__) +#if defined(__thumb__) + EXPECT_EQ(0xDF00u, ((uint16_t*)Syscall::Call(-1))[-1]); // SWI 0 +#else + EXPECT_EQ(0xEF000000u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 +#endif +#elif defined(__mips__) + // Opcode for MIPS sycall is in the lower 16-bits + EXPECT_EQ(0x0cu, (((uint32_t*)Syscall::Call(-1))[-1]) & 0x0000FFFF); +#elif defined(__aarch64__) + EXPECT_EQ(0xD4000001u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 +#else +#warning Incomplete test case; need port for target platform +#endif +} + +TEST(Syscall, TrivialSyscallNoArgs) { + // Test that we can do basic system calls + EXPECT_EQ(Syscall::Call(__NR_getpid), syscall(__NR_getpid)); +} + +TEST(Syscall, TrivialSyscallOneArg) { + int new_fd; + // Duplicate standard error and close it. + ASSERT_GE(new_fd = Syscall::Call(__NR_dup, 2), 0); + int close_return_value = IGNORE_EINTR(Syscall::Call(__NR_close, new_fd)); + ASSERT_EQ(close_return_value, 0); +} + +TEST(Syscall, TrivialFailingSyscall) { + errno = -42; + int ret = Syscall::Call(__NR_dup, -1); + ASSERT_EQ(-EBADF, ret); + // Verify that Syscall::Call does not touch errno. + ASSERT_EQ(-42, errno); +} + +// SIGSYS trap handler that will be called on __NR_uname. +intptr_t CopySyscallArgsToAux(const struct arch_seccomp_data& args, void* aux) { + // |aux| is our BPF_AUX pointer. + std::vector<uint64_t>* const seen_syscall_args = + static_cast<std::vector<uint64_t>*>(aux); + BPF_ASSERT(arraysize(args.args) == 6); + seen_syscall_args->assign(args.args, args.args + arraysize(args.args)); + return -ENOMEM; +} + +class CopyAllArgsOnUnamePolicy : public bpf_dsl::Policy { + public: + explicit CopyAllArgsOnUnamePolicy(std::vector<uint64_t>* aux) : aux_(aux) {} + ~CopyAllArgsOnUnamePolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + return Trap(CopySyscallArgsToAux, aux_); + } else { + return Allow(); + } + } + + private: + std::vector<uint64_t>* aux_; + + DISALLOW_COPY_AND_ASSIGN(CopyAllArgsOnUnamePolicy); +}; + +// We are testing Syscall::Call() by making use of a BPF filter that +// allows us +// to inspect the system call arguments that the kernel saw. +BPF_TEST(Syscall, + SyntheticSixArgs, + CopyAllArgsOnUnamePolicy, + std::vector<uint64_t> /* (*BPF_AUX) */) { + const int kExpectedValue = 42; + // In this test we only pass integers to the kernel. We might want to make + // additional tests to try other types. What we will see depends on + // implementation details of kernel BPF filters and we will need to document + // the expected behavior very clearly. + int syscall_args[6]; + for (size_t i = 0; i < arraysize(syscall_args); ++i) { + syscall_args[i] = kExpectedValue + i; + } + + // We could use pretty much any system call we don't need here. uname() is + // nice because it doesn't have any dangerous side effects. + BPF_ASSERT(Syscall::Call(__NR_uname, + syscall_args[0], + syscall_args[1], + syscall_args[2], + syscall_args[3], + syscall_args[4], + syscall_args[5]) == -ENOMEM); + + // We expect the trap handler to have copied the 6 arguments. + BPF_ASSERT(BPF_AUX->size() == 6); + + // Don't loop here so that we can see which argument does cause the failure + // easily from the failing line. + // uint64_t is the type passed to our SIGSYS handler. + BPF_ASSERT((*BPF_AUX)[0] == static_cast<uint64_t>(syscall_args[0])); + BPF_ASSERT((*BPF_AUX)[1] == static_cast<uint64_t>(syscall_args[1])); + BPF_ASSERT((*BPF_AUX)[2] == static_cast<uint64_t>(syscall_args[2])); + BPF_ASSERT((*BPF_AUX)[3] == static_cast<uint64_t>(syscall_args[3])); + BPF_ASSERT((*BPF_AUX)[4] == static_cast<uint64_t>(syscall_args[4])); + BPF_ASSERT((*BPF_AUX)[5] == static_cast<uint64_t>(syscall_args[5])); +} + +TEST(Syscall, ComplexSyscallSixArgs) { + int fd; + ASSERT_LE(0, + fd = Syscall::Call(__NR_openat, AT_FDCWD, "/dev/null", O_RDWR, 0L)); + + // Use mmap() to allocate some read-only memory + char* addr0; + ASSERT_NE( + (char*)NULL, + addr0 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + 4096, + PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, + fd, + 0L))); + + // Try to replace the existing mapping with a read-write mapping + char* addr1; + ASSERT_EQ(addr0, + addr1 = reinterpret_cast<char*>( + Syscall::Call(kMMapNr, + addr0, + 4096L, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + fd, + 0L))); + ++*addr1; // This should not seg fault + + // Clean up + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr1, 4096L)); + EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); + + // Check that the offset argument (i.e. the sixth argument) is processed + // correctly. + ASSERT_GE( + fd = Syscall::Call(__NR_openat, AT_FDCWD, "/proc/self/exe", O_RDONLY, 0L), + 0); + char* addr2, *addr3; + ASSERT_NE((char*)NULL, + addr2 = reinterpret_cast<char*>(Syscall::Call( + kMMapNr, (void*)NULL, 8192L, PROT_READ, MAP_PRIVATE, fd, 0L))); + ASSERT_NE((char*)NULL, + addr3 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + 4096L, + PROT_READ, + MAP_PRIVATE, + fd, +#if defined(__NR_mmap2) + 1L +#else + 4096L +#endif + ))); + EXPECT_EQ(0, memcmp(addr2 + 4096, addr3, 4096)); + + // Just to be absolutely on the safe side, also verify that the file + // contents matches what we are getting from a read() operation. + char buf[8192]; + EXPECT_EQ(8192, Syscall::Call(__NR_read, fd, buf, 8192L)); + EXPECT_EQ(0, memcmp(addr2, buf, 8192)); + + // Clean up + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr2, 8192L)); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr3, 4096L)); + EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/trap.cc b/sandbox/linux/seccomp-bpf/trap.cc new file mode 100644 index 0000000000..8f559e53b1 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.cc @@ -0,0 +1,390 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/seccomp-bpf/trap.h" + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <sys/syscall.h> + +#include <algorithm> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace { + +struct arch_sigsys { + void* ip; + int nr; + unsigned int arch; +}; + +const int kCapacityIncrement = 20; + +// Unsafe traps can only be turned on, if the user explicitly allowed them +// by setting the CHROME_SANDBOX_DEBUGGING environment variable. +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +// We need to tell whether we are performing a "normal" callback, or +// whether we were called recursively from within a UnsafeTrap() callback. +// This is a little tricky to do, because we need to somehow get access to +// per-thread data from within a signal context. Normal TLS storage is not +// safely accessible at this time. We could roll our own, but that involves +// a lot of complexity. Instead, we co-opt one bit in the signal mask. +// If BUS is blocked, we assume that we have been called recursively. +// There is a possibility for collision with other code that needs to do +// this, but in practice the risks are low. +// If SIGBUS turns out to be a problem, we could instead co-opt one of the +// realtime signals. There are plenty of them. Unfortunately, there is no +// way to mark a signal as allocated. So, the potential for collision is +// possibly even worse. +bool GetIsInSigHandler(const ucontext_t* ctx) { + // Note: on Android, sigismember does not take a pointer to const. + return sigismember(const_cast<sigset_t*>(&ctx->uc_sigmask), LINUX_SIGBUS); +} + +void SetIsInSigHandler() { + sigset_t mask; + if (sigemptyset(&mask) || sigaddset(&mask, LINUX_SIGBUS) || + sandbox::sys_sigprocmask(LINUX_SIG_BLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to block SIGBUS"); + } +} + +bool IsDefaultSignalAction(const struct sigaction& sa) { + if (sa.sa_flags & SA_SIGINFO || sa.sa_handler != SIG_DFL) { + return false; + } + return true; +} + +} // namespace + +namespace sandbox { + +Trap::Trap() + : trap_array_(NULL), + trap_array_size_(0), + trap_array_capacity_(0), + has_unsafe_traps_(false) { + // Set new SIGSYS handler + struct sigaction sa = {}; + // In some toolchain, sa_sigaction is not declared in struct sigaction. + // So, here cast the pointer to the sa_handler's type. This works because + // |sa_handler| and |sa_sigaction| shares the same memory. + sa.sa_handler = reinterpret_cast<void (*)(int)>(SigSysAction); + sa.sa_flags = LINUX_SA_SIGINFO | LINUX_SA_NODEFER; + struct sigaction old_sa = {}; + if (sys_sigaction(LINUX_SIGSYS, &sa, &old_sa) < 0) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } + + if (!IsDefaultSignalAction(old_sa)) { + static const char kExistingSIGSYSMsg[] = + "Existing signal handler when trying to install SIGSYS. SIGSYS needs " + "to be reserved for seccomp-bpf."; + DLOG(FATAL) << kExistingSIGSYSMsg; + LOG(ERROR) << kExistingSIGSYSMsg; + } + + // Unmask SIGSYS + sigset_t mask; + if (sigemptyset(&mask) || sigaddset(&mask, LINUX_SIGSYS) || + sys_sigprocmask(LINUX_SIG_UNBLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } +} + +bpf_dsl::TrapRegistry* Trap::Registry() { + // Note: This class is not thread safe. It is the caller's responsibility + // to avoid race conditions. Normally, this is a non-issue as the sandbox + // can only be initialized if there are no other threads present. + // Also, this is not a normal singleton. Once created, the global trap + // object must never be destroyed again. + if (!global_trap_) { + global_trap_ = new Trap(); + if (!global_trap_) { + SANDBOX_DIE("Failed to allocate global trap handler"); + } + } + return global_trap_; +} + +void Trap::SigSysAction(int nr, LinuxSigInfo* info, void* void_context) { + if (info) { + MSAN_UNPOISON(info, sizeof(*info)); + } + + // Obtain the signal context. This, most notably, gives us access to + // all CPU registers at the time of the signal. + ucontext_t* ctx = reinterpret_cast<ucontext_t*>(void_context); + if (ctx) { + MSAN_UNPOISON(ctx, sizeof(*ctx)); + } + + if (!global_trap_) { + RAW_SANDBOX_DIE( + "This can't happen. Found no global singleton instance " + "for Trap() handling."); + } + global_trap_->SigSys(nr, info, ctx); +} + +void Trap::SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx) { + // Signal handlers should always preserve "errno". Otherwise, we could + // trigger really subtle bugs. + const int old_errno = errno; + + // Various sanity checks to make sure we actually received a signal + // triggered by a BPF filter. If something else triggered SIGSYS + // (e.g. kill()), there is really nothing we can do with this signal. + if (nr != LINUX_SIGSYS || info->si_code != SYS_SECCOMP || !ctx || + info->si_errno <= 0 || + static_cast<size_t>(info->si_errno) > trap_array_size_) { + // ATI drivers seem to send SIGSYS, so this cannot be FATAL. + // See crbug.com/178166. + // TODO(jln): add a DCHECK or move back to FATAL. + RAW_LOG(ERROR, "Unexpected SIGSYS received."); + errno = old_errno; + return; + } + + + // Obtain the siginfo information that is specific to SIGSYS. Unfortunately, + // most versions of glibc don't include this information in siginfo_t. So, + // we need to explicitly copy it into a arch_sigsys structure. + struct arch_sigsys sigsys; + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); + +#if defined(__mips__) + // When indirect syscall (syscall(__NR_foo, ...)) is made on Mips, the + // number in register SECCOMP_SYSCALL(ctx) is always __NR_syscall and the + // real number of a syscall (__NR_foo) is in SECCOMP_PARM1(ctx) + bool sigsys_nr_is_bad = sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) && + sigsys.nr != static_cast<int>(SECCOMP_PARM1(ctx)); +#else + bool sigsys_nr_is_bad = sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)); +#endif + + // Some more sanity checks. + if (sigsys.ip != reinterpret_cast<void*>(SECCOMP_IP(ctx)) || + sigsys_nr_is_bad || sigsys.arch != SECCOMP_ARCH) { + // TODO(markus): + // SANDBOX_DIE() can call LOG(FATAL). This is not normally async-signal + // safe and can lead to bugs. We should eventually implement a different + // logging and reporting mechanism that is safe to be called from + // the sigSys() handler. + RAW_SANDBOX_DIE("Sanity checks are failing after receiving SIGSYS."); + } + + intptr_t rc; + if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) { + errno = old_errno; + if (sigsys.nr == __NR_clone) { + RAW_SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler."); + } +#if defined(__mips__) + // Mips supports up to eight arguments for syscall. + // However, seccomp bpf can filter only up to six arguments, so using eight + // arguments has sense only when using UnsafeTrap() handler. + rc = Syscall::Call(SECCOMP_SYSCALL(ctx), + SECCOMP_PARM1(ctx), + SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), + SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), + SECCOMP_PARM6(ctx), + SECCOMP_PARM7(ctx), + SECCOMP_PARM8(ctx)); +#else + rc = Syscall::Call(SECCOMP_SYSCALL(ctx), + SECCOMP_PARM1(ctx), + SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), + SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), + SECCOMP_PARM6(ctx)); +#endif // defined(__mips__) + } else { + const TrapKey& trap = trap_array_[info->si_errno - 1]; + if (!trap.safe) { + SetIsInSigHandler(); + } + + // Copy the seccomp-specific data into a arch_seccomp_data structure. This + // is what we are showing to TrapFnc callbacks that the system call + // evaluator registered with the sandbox. + struct arch_seccomp_data data = { + static_cast<int>(SECCOMP_SYSCALL(ctx)), + SECCOMP_ARCH, + reinterpret_cast<uint64_t>(sigsys.ip), + {static_cast<uint64_t>(SECCOMP_PARM1(ctx)), + static_cast<uint64_t>(SECCOMP_PARM2(ctx)), + static_cast<uint64_t>(SECCOMP_PARM3(ctx)), + static_cast<uint64_t>(SECCOMP_PARM4(ctx)), + static_cast<uint64_t>(SECCOMP_PARM5(ctx)), + static_cast<uint64_t>(SECCOMP_PARM6(ctx))}}; + + // Now call the TrapFnc callback associated with this particular instance + // of SECCOMP_RET_TRAP. + rc = trap.fnc(data, const_cast<void*>(trap.aux)); + } + + // Update the CPU register that stores the return code of the system call + // that we just handled, and restore "errno" to the value that it had + // before entering the signal handler. + Syscall::PutValueInUcontext(rc, ctx); + errno = old_errno; + + return; +} + +bool Trap::TrapKey::operator<(const TrapKey& o) const { + if (fnc != o.fnc) { + return fnc < o.fnc; + } else if (aux != o.aux) { + return aux < o.aux; + } else { + return safe < o.safe; + } +} + +uint16_t Trap::Add(TrapFnc fnc, const void* aux, bool safe) { + if (!safe && !SandboxDebuggingAllowedByUser()) { + // Unless the user set the CHROME_SANDBOX_DEBUGGING environment variable, + // we never return an ErrorCode that is marked as "unsafe". This also + // means, the BPF compiler will never emit code that allow unsafe system + // calls to by-pass the filter (because they use the magic return address + // from Syscall::Call(-1)). + + // This SANDBOX_DIE() can optionally be removed. It won't break security, + // but it might make error messages from the BPF compiler a little harder + // to understand. Removing the SANDBOX_DIE() allows callers to easily check + // whether unsafe traps are supported (by checking whether the returned + // ErrorCode is ET_INVALID). + SANDBOX_DIE( + "Cannot use unsafe traps unless CHROME_SANDBOX_DEBUGGING " + "is enabled"); + + return 0; + } + + // Each unique pair of TrapFnc and auxiliary data make up a distinct instance + // of a SECCOMP_RET_TRAP. + TrapKey key(fnc, aux, safe); + + // We return unique identifiers together with SECCOMP_RET_TRAP. This allows + // us to associate trap with the appropriate handler. The kernel allows us + // identifiers in the range from 0 to SECCOMP_RET_DATA (0xFFFF). We want to + // avoid 0, as it could be confused for a trap without any specific id. + // The nice thing about sequentially numbered identifiers is that we can also + // trivially look them up from our signal handler without making any system + // calls that might be async-signal-unsafe. + // In order to do so, we store all of our traps in a C-style trap_array_. + + TrapIds::const_iterator iter = trap_ids_.find(key); + if (iter != trap_ids_.end()) { + // We have seen this pair before. Return the same id that we assigned + // earlier. + return iter->second; + } + + // This is a new pair. Remember it and assign a new id. + if (trap_array_size_ >= SECCOMP_RET_DATA /* 0xFFFF */ || + trap_array_size_ >= std::numeric_limits<uint16_t>::max()) { + // In practice, this is pretty much impossible to trigger, as there + // are other kernel limitations that restrict overall BPF program sizes. + SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); + } + + // Our callers ensure that there are no other threads accessing trap_array_ + // concurrently (typically this is done by ensuring that we are single- + // threaded while the sandbox is being set up). But we nonetheless are + // modifying a live data structure that could be accessed any time a + // system call is made; as system calls could be triggering SIGSYS. + // So, we have to be extra careful that we update trap_array_ atomically. + // In particular, this means we shouldn't be using realloc() to resize it. + // Instead, we allocate a new array, copy the values, and then switch the + // pointer. We only really care about the pointer being updated atomically + // and the data that is pointed to being valid, as these are the only + // values accessed from the signal handler. It is OK if trap_array_size_ + // is inconsistent with the pointer, as it is monotonously increasing. + // Also, we only care about compiler barriers, as the signal handler is + // triggered synchronously from a system call. We don't have to protect + // against issues with the memory model or with completely asynchronous + // events. + if (trap_array_size_ >= trap_array_capacity_) { + trap_array_capacity_ += kCapacityIncrement; + TrapKey* old_trap_array = trap_array_; + TrapKey* new_trap_array = new TrapKey[trap_array_capacity_]; + std::copy_n(old_trap_array, trap_array_size_, new_trap_array); + + // Language specs are unclear on whether the compiler is allowed to move + // the "delete[]" above our preceding assignments and/or memory moves, + // iff the compiler believes that "delete[]" doesn't have any other + // global side-effects. + // We insert optimization barriers to prevent this from happening. + // The first barrier is probably not needed, but better be explicit in + // what we want to tell the compiler. + // The clang developer mailing list couldn't answer whether this is a + // legitimate worry; but they at least thought that the barrier is + // sufficient to prevent the (so far hypothetical) problem of re-ordering + // of instructions by the compiler. + // + // TODO(mdempsky): Try to clean this up using base/atomicops or C++11 + // atomics; see crbug.com/414363. + asm volatile("" : "=r"(new_trap_array) : "0"(new_trap_array) : "memory"); + trap_array_ = new_trap_array; + asm volatile("" : "=r"(trap_array_) : "0"(trap_array_) : "memory"); + + delete[] old_trap_array; + } + + uint16_t id = trap_array_size_ + 1; + trap_ids_[key] = id; + trap_array_[trap_array_size_] = key; + trap_array_size_++; + return id; +} + +bool Trap::SandboxDebuggingAllowedByUser() { + const char* debug_flag = getenv(kSandboxDebuggingEnv); + return debug_flag && *debug_flag; +} + +bool Trap::EnableUnsafeTraps() { + if (!has_unsafe_traps_) { + // Unsafe traps are a one-way fuse. Once enabled, they can never be turned + // off again. + // We only allow enabling unsafe traps, if the user explicitly set an + // appropriate environment variable. This prevents bugs that accidentally + // disable all sandboxing for all users. + if (SandboxDebuggingAllowedByUser()) { + // We only ever print this message once, when we enable unsafe traps the + // first time. + SANDBOX_INFO("WARNING! Disabling sandbox for debugging purposes"); + has_unsafe_traps_ = true; + } else { + SANDBOX_INFO( + "Cannot disable sandbox and use unsafe traps unless " + "CHROME_SANDBOX_DEBUGGING is turned on first"); + } + } + // Returns the, possibly updated, value of has_unsafe_traps_. + return has_unsafe_traps_; +} + +Trap* Trap::global_trap_; + +} // namespace sandbox diff --git a/sandbox/linux/seccomp-bpf/trap.h b/sandbox/linux/seccomp-bpf/trap.h new file mode 100644 index 0000000000..50ac3fd1c3 --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ + +#include <stdint.h> + +#include <map> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// The Trap class allows a BPF filter program to branch out to user space by +// raising a SIGSYS signal. +// N.B.: This class does not perform any synchronization operations. If +// modifications are made to any of the traps, it is the caller's +// responsibility to ensure that this happens in a thread-safe fashion. +// Preferably, that means that no other threads should be running at that +// time. For the purposes of our sandbox, this assertion should always be +// true. Threads are incompatible with the seccomp sandbox anyway. +class SANDBOX_EXPORT Trap : public bpf_dsl::TrapRegistry { + public: + uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override; + + bool EnableUnsafeTraps() override; + + // Registry returns the trap registry used by Trap's SIGSYS handler, + // creating it if necessary. + static bpf_dsl::TrapRegistry* Registry(); + + // SandboxDebuggingAllowedByUser returns whether the + // "CHROME_SANDBOX_DEBUGGING" environment variable is set. + static bool SandboxDebuggingAllowedByUser(); + + private: + struct TrapKey { + TrapKey() : fnc(NULL), aux(NULL), safe(false) {} + TrapKey(TrapFnc f, const void* a, bool s) : fnc(f), aux(a), safe(s) {} + TrapFnc fnc; + const void* aux; + bool safe; + bool operator<(const TrapKey&) const; + }; + typedef std::map<TrapKey, uint16_t> TrapIds; + + // Our constructor is private. A shared global instance is created + // automatically as needed. + Trap(); + + // The destructor is unimplemented as destroying this object would + // break subsequent system calls that trigger a SIGSYS. + ~Trap() = delete; + + static void SigSysAction(int nr, LinuxSigInfo* info, void* void_context); + + // Make sure that SigSys is not inlined in order to get slightly better crash + // dumps. + void SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx) + __attribute__((noinline)); + // We have a global singleton that handles all of our SIGSYS traps. This + // variable must never be deallocated after it has been set up initially, as + // there is no way to reset in-kernel BPF filters that generate SIGSYS + // events. + static Trap* global_trap_; + + TrapIds trap_ids_; // Maps from TrapKeys to numeric ids + TrapKey* trap_array_; // Array of TrapKeys indexed by ids + size_t trap_array_size_; // Currently used size of array + size_t trap_array_capacity_; // Currently allocated capacity of array + bool has_unsafe_traps_; // Whether unsafe traps have been enabled + + // Copying and assigning is unimplemented. It doesn't make sense for a + // singleton. + DISALLOW_COPY_AND_ASSIGN(Trap); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ diff --git a/sandbox/linux/seccomp-bpf/trap_unittest.cc b/sandbox/linux/seccomp-bpf/trap_unittest.cc new file mode 100644 index 0000000000..99f94bfb3a --- /dev/null +++ b/sandbox/linux/seccomp-bpf/trap_unittest.cc @@ -0,0 +1,28 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/seccomp-bpf/trap.h" + +#include <signal.h> + +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { +namespace { + +SANDBOX_TEST_ALLOW_NOISE(Trap, SigSysAction) { + // This creates a global Trap instance, and registers the signal handler + // (Trap::SigSysAction). + Trap::Registry(); + + // Send SIGSYS to self. If signal handler (SigSysAction) is not registered, + // the process will be terminated with status code -SIGSYS. + // Note that, SigSysAction handler would output an error message + // "Unexpected SIGSYS received." so it is necessary to allow the noise. + raise(SIGSYS); +} + +} // namespace +} // namespace sandbox diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc new file mode 100644 index 0000000000..c77fd9efde --- /dev/null +++ b/sandbox/linux/services/credentials.cc @@ -0,0 +1,299 @@ +// Copyright (c) 2013 The Chromium 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 "sandbox/linux/services/credentials.h" + +#include <errno.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/template_util.h" +#include "build/build_config.h" +#include "sandbox/linux/services/namespace_utils.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "third_party/valgrind/valgrind.h" + +namespace sandbox { + +namespace { + +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +// Checks that the set of RES-uids and the set of RES-gids have +// one element each and return that element in |resuid| and |resgid| +// respectively. It's ok to pass NULL as one or both of the ids. +bool GetRESIds(uid_t* resuid, gid_t* resgid) { + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + PCHECK(sys_getresuid(&ruid, &euid, &suid) == 0); + PCHECK(sys_getresgid(&rgid, &egid, &sgid) == 0); + const bool uids_are_equal = (ruid == euid) && (ruid == suid); + const bool gids_are_equal = (rgid == egid) && (rgid == sgid); + if (!uids_are_equal || !gids_are_equal) return false; + if (resuid) *resuid = euid; + if (resgid) *resgid = egid; + return true; +} + +const int kExitSuccess = 0; + +int ChrootToSelfFdinfo(void*) { + RAW_CHECK(sys_chroot("/proc/self/fdinfo/") == 0); + + // CWD is essentially an implicit file descriptor, so be careful to not + // leave it behind. + RAW_CHECK(chdir("/") == 0); + _exit(kExitSuccess); +} + +// chroot() to an empty dir that is "safe". To be safe, it must not contain +// any subdirectory (chroot-ing there would allow a chroot escape) and it must +// be impossible to create an empty directory there. +// We achieve this by doing the following: +// 1. We create a new process sharing file system information. +// 2. In the child, we chroot to /proc/self/fdinfo/ +// This is already "safe", since fdinfo/ does not contain another directory and +// one cannot create another directory there. +// 3. The process dies +// After (3) happens, the directory is not available anymore in /proc. +bool ChrootToSafeEmptyDir() { + // We need to chroot to a fdinfo that is unique to a process and have that + // process die. + // 1. We don't want to simply fork() because duplicating the page tables is + // slow with a big address space. + // 2. We do not use a regular thread (that would unshare CLONE_FILES) because + // when we are in a PID namespace, we cannot easily get a handle to the + // /proc/tid directory for the thread (since /proc may not be aware of the + // PID namespace). With a process, we can just use /proc/self. + pid_t pid = -1; + char stack_buf[PTHREAD_STACK_MIN]; +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS64_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) + // The stack grows downward. + void* stack = stack_buf + sizeof(stack_buf); +#else +#error "Unsupported architecture" +#endif + + pid = clone(ChrootToSelfFdinfo, stack, + CLONE_VM | CLONE_VFORK | CLONE_FS | LINUX_SIGCHLD, nullptr, + nullptr, nullptr, nullptr); + PCHECK(pid != -1); + + int status = -1; + PCHECK(HANDLE_EINTR(waitpid(pid, &status, 0)) == pid); + + return WIFEXITED(status) && WEXITSTATUS(status) == kExitSuccess; +} + +// CHECK() that an attempt to move to a new user namespace raised an expected +// errno. +void CheckCloneNewUserErrno(int error) { + // EPERM can happen if already in a chroot. EUSERS if too many nested + // namespaces are used. EINVAL for kernels that don't support the feature. + // Valgrind will ENOSYS unshare(). + PCHECK(error == EPERM || error == EUSERS || error == EINVAL || + error == ENOSYS); +} + +// Converts a Capability to the corresponding Linux CAP_XXX value. +int CapabilityToKernelValue(Credentials::Capability cap) { + switch (cap) { + case Credentials::Capability::SYS_CHROOT: + return CAP_SYS_CHROOT; + case Credentials::Capability::SYS_ADMIN: + return CAP_SYS_ADMIN; + } + + LOG(FATAL) << "Invalid Capability: " << static_cast<int>(cap); + return 0; +} + +} // namespace. + +// static +bool Credentials::DropAllCapabilities(int proc_fd) { + if (!SetCapabilities(proc_fd, std::vector<Capability>())) { + return false; + } + + CHECK(!HasAnyCapability()); + return true; +} + +// static +bool Credentials::DropAllCapabilities() { + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + return Credentials::DropAllCapabilities(proc_fd.get()); +} + +// static +bool Credentials::DropAllCapabilitiesOnCurrentThread() { + return SetCapabilitiesOnCurrentThread(std::vector<Capability>()); +} + +// static +bool Credentials::SetCapabilitiesOnCurrentThread( + const std::vector<Capability>& caps) { + struct cap_hdr hdr = {}; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}}; + + // Initially, cap has no capability flags set. Enable the effective and + // permitted flags only for the requested capabilities. + for (const Capability cap : caps) { + const int cap_num = CapabilityToKernelValue(cap); + const size_t index = CAP_TO_INDEX(cap_num); + const uint32_t mask = CAP_TO_MASK(cap_num); + data[index].effective |= mask; + data[index].permitted |= mask; + } + + return sys_capset(&hdr, data) == 0; +} + +// static +bool Credentials::SetCapabilities(int proc_fd, + const std::vector<Capability>& caps) { + DCHECK_LE(0, proc_fd); + +#if !defined(THREAD_SANITIZER) + // With TSAN, accept to break the security model as it is a testing + // configuration. + CHECK(ThreadHelpers::IsSingleThreaded(proc_fd)); +#endif + + return SetCapabilitiesOnCurrentThread(caps); +} + +bool Credentials::HasAnyCapability() { + struct cap_hdr hdr = {}; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}}; + + PCHECK(sys_capget(&hdr, data) == 0); + + for (size_t i = 0; i < arraysize(data); ++i) { + if (data[i].effective || data[i].permitted || data[i].inheritable) { + return true; + } + } + + return false; +} + +bool Credentials::HasCapability(Capability cap) { + struct cap_hdr hdr = {}; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}}; + + PCHECK(sys_capget(&hdr, data) == 0); + + const int cap_num = CapabilityToKernelValue(cap); + const size_t index = CAP_TO_INDEX(cap_num); + const uint32_t mask = CAP_TO_MASK(cap_num); + + return (data[index].effective | data[index].permitted | + data[index].inheritable) & + mask; +} + +// static +bool Credentials::CanCreateProcessInNewUserNS() { + // Valgrind will let clone(2) pass-through, but doesn't support unshare(), + // so always consider UserNS unsupported there. + if (IsRunningOnValgrind()) { + return false; + } + +#if defined(THREAD_SANITIZER) + // With TSAN, processes will always have threads running and can never + // enter a new user namespace with MoveToNewUserNS(). + return false; +#endif + + // This is roughly a fork(). + const pid_t pid = sys_clone(CLONE_NEWUSER | SIGCHLD, 0, 0, 0, 0); + + if (pid == -1) { + CheckCloneNewUserErrno(errno); + return false; + } + + // The parent process could have had threads. In the child, these threads + // have disappeared. Make sure to not do anything in the child, as this is a + // fragile execution environment. + if (pid == 0) { + _exit(kExitSuccess); + } + + // Always reap the child. + int status = -1; + PCHECK(HANDLE_EINTR(waitpid(pid, &status, 0)) == pid); + CHECK(WIFEXITED(status)); + CHECK_EQ(kExitSuccess, WEXITSTATUS(status)); + + // clone(2) succeeded, we can use CLONE_NEWUSER. + return true; +} + +bool Credentials::MoveToNewUserNS() { + uid_t uid; + gid_t gid; + if (!GetRESIds(&uid, &gid)) { + // If all the uids (or gids) are not equal to each other, the security + // model will most likely confuse the caller, abort. + DVLOG(1) << "uids or gids differ!"; + return false; + } + int ret = sys_unshare(CLONE_NEWUSER); + if (ret) { + const int unshare_errno = errno; + VLOG(1) << "Looks like unprivileged CLONE_NEWUSER may not be available " + << "on this kernel."; + CheckCloneNewUserErrno(unshare_errno); + return false; + } + + if (NamespaceUtils::KernelSupportsDenySetgroups()) { + PCHECK(NamespaceUtils::DenySetgroups()); + } + + // The current {r,e,s}{u,g}id is now an overflow id (c.f. + // /proc/sys/kernel/overflowuid). Setup the uid and gid maps. + DCHECK(GetRESIds(NULL, NULL)); + const char kGidMapFile[] = "/proc/self/gid_map"; + const char kUidMapFile[] = "/proc/self/uid_map"; + PCHECK(NamespaceUtils::WriteToIdMapFile(kGidMapFile, gid)); + PCHECK(NamespaceUtils::WriteToIdMapFile(kUidMapFile, uid)); + DCHECK(GetRESIds(NULL, NULL)); + return true; +} + +bool Credentials::DropFileSystemAccess(int proc_fd) { + CHECK_LE(0, proc_fd); + + CHECK(ChrootToSafeEmptyDir()); + CHECK(!base::DirectoryExists(base::FilePath("/proc"))); + CHECK(!ProcUtil::HasOpenDirectory(proc_fd)); + // We never let this function fail. + return true; +} + +} // namespace sandbox. diff --git a/sandbox/linux/services/credentials.h b/sandbox/linux/services/credentials.h new file mode 100644 index 0000000000..0001dc7328 --- /dev/null +++ b/sandbox/linux/services/credentials.h @@ -0,0 +1,104 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_CREDENTIALS_H_ +#define SANDBOX_LINUX_SERVICES_CREDENTIALS_H_ + +#include "build/build_config.h" +// Link errors are tedious to track, raise a compile-time error instead. +#if defined(OS_ANDROID) +#error "Android is not supported." +#endif // defined(OS_ANDROID). + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This class should be used to manipulate the current process' credentials. +// It is currently a stub used to manipulate POSIX.1e capabilities as +// implemented by the Linux kernel. +class SANDBOX_EXPORT Credentials { + public: + // For brevity, we only expose enums for the subset of capabilities we use. + // This can be expanded as the need arises. + enum class Capability { + SYS_CHROOT, + SYS_ADMIN, + }; + + // Drop all capabilities in the effective, inheritable and permitted sets for + // the current thread. For security reasons, since capabilities are + // per-thread, the caller is responsible for ensuring it is single-threaded + // when calling this API. + // |proc_fd| must be a file descriptor to /proc/ and remains owned by + // the caller. + static bool DropAllCapabilities(int proc_fd) WARN_UNUSED_RESULT; + // A similar API which assumes that it can open /proc/self/ by itself. + static bool DropAllCapabilities() WARN_UNUSED_RESULT; + // Sets the effective and permitted capability sets for the current thread to + // the list of capabiltiies in |caps|. All other capability flags are cleared. + static bool SetCapabilities(int proc_fd, + const std::vector<Capability>& caps) + WARN_UNUSED_RESULT; + + // Versions of the above functions which do not check that the process is + // single-threaded. After calling these functions, capabilities of other + // threads will not be changed. This is dangerous, do not use unless you nkow + // what you are doing. + static bool DropAllCapabilitiesOnCurrentThread() WARN_UNUSED_RESULT; + static bool SetCapabilitiesOnCurrentThread( + const std::vector<Capability>& caps) WARN_UNUSED_RESULT; + + // Returns true if the current thread has either the effective, permitted, or + // inheritable flag set for the given capability. + static bool HasCapability(Capability cap); + + // Return true iff there is any capability in any of the capabilities sets + // of the current thread. + static bool HasAnyCapability(); + + // Returns whether the kernel supports CLONE_NEWUSER and whether it would be + // possible to immediately move to a new user namespace. There is no point + // in using this method right before calling MoveToNewUserNS(), simply call + // MoveToNewUserNS() immediately. This method is only useful to test the + // ability to move to a user namespace ahead of time. + static bool CanCreateProcessInNewUserNS(); + + // Move the current process to a new "user namespace" as supported by Linux + // 3.8+ (CLONE_NEWUSER). + // The uid map will be set-up so that the perceived uid and gid will not + // change. + // If this call succeeds, the current process will be granted a full set of + // capabilities in the new namespace. + // This will fail if the process is not mono-threaded. + static bool MoveToNewUserNS() WARN_UNUSED_RESULT; + + // Remove the ability of the process to access the file system. File + // descriptors which are already open prior to calling this API remain + // available. + // The implementation currently uses chroot(2) and requires CAP_SYS_CHROOT. + // CAP_SYS_CHROOT can be acquired by using the MoveToNewUserNS() API. + // |proc_fd| must be a file descriptor to /proc/ and must be the only open + // directory file descriptor of the process. + // + // CRITICAL: + // - the caller must close |proc_fd| eventually or access to the file + // system can be recovered. + // - DropAllCapabilities() must be called to prevent escapes. + static bool DropFileSystemAccess(int proc_fd) WARN_UNUSED_RESULT; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Credentials); +}; + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SERVICES_CREDENTIALS_H_ diff --git a/sandbox/linux/services/credentials_unittest.cc b/sandbox/linux/services/credentials_unittest.cc new file mode 100644 index 0000000000..6b93c86c3e --- /dev/null +++ b/sandbox/linux/services/credentials_unittest.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/services/credentials.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/capability.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +struct CapFreeDeleter { + inline void operator()(cap_t cap) const { + int ret = cap_free(cap); + CHECK_EQ(0, ret); + } +}; + +// Wrapper to manage libcap2's cap_t type. +typedef scoped_ptr<typeof(*((cap_t)0)), CapFreeDeleter> ScopedCap; + +bool WorkingDirectoryIsRoot() { + char current_dir[PATH_MAX]; + char* cwd = getcwd(current_dir, sizeof(current_dir)); + PCHECK(cwd); + if (strcmp("/", cwd)) return false; + + // The current directory is the root. Add a few paranoid checks. + struct stat current; + CHECK_EQ(0, stat(".", ¤t)); + struct stat parrent; + CHECK_EQ(0, stat("..", &parrent)); + CHECK_EQ(current.st_dev, parrent.st_dev); + CHECK_EQ(current.st_ino, parrent.st_ino); + CHECK_EQ(current.st_mode, parrent.st_mode); + CHECK_EQ(current.st_uid, parrent.st_uid); + CHECK_EQ(current.st_gid, parrent.st_gid); + return true; +} + +SANDBOX_TEST(Credentials, DropAllCaps) { + CHECK(Credentials::DropAllCapabilities()); + CHECK(!Credentials::HasAnyCapability()); +} + +SANDBOX_TEST(Credentials, MoveToNewUserNS) { + CHECK(Credentials::DropAllCapabilities()); + bool moved_to_new_ns = Credentials::MoveToNewUserNS(); + fprintf(stdout, + "Unprivileged CLONE_NEWUSER supported: %s\n", + moved_to_new_ns ? "true." : "false."); + fflush(stdout); + if (!moved_to_new_ns) { + fprintf(stdout, "This kernel does not support unprivileged namespaces. " + "USERNS tests will succeed without running.\n"); + fflush(stdout); + return; + } + CHECK(Credentials::HasAnyCapability()); + CHECK(Credentials::DropAllCapabilities()); + CHECK(!Credentials::HasAnyCapability()); +} + +SANDBOX_TEST(Credentials, CanCreateProcessInNewUserNS) { + CHECK(Credentials::DropAllCapabilities()); + bool user_ns_supported = Credentials::CanCreateProcessInNewUserNS(); + bool moved_to_new_ns = Credentials::MoveToNewUserNS(); + CHECK_EQ(user_ns_supported, moved_to_new_ns); +} + +SANDBOX_TEST(Credentials, UidIsPreserved) { + CHECK(Credentials::DropAllCapabilities()); + uid_t old_ruid, old_euid, old_suid; + gid_t old_rgid, old_egid, old_sgid; + PCHECK(0 == getresuid(&old_ruid, &old_euid, &old_suid)); + PCHECK(0 == getresgid(&old_rgid, &old_egid, &old_sgid)); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + uid_t new_ruid, new_euid, new_suid; + PCHECK(0 == getresuid(&new_ruid, &new_euid, &new_suid)); + CHECK(old_ruid == new_ruid); + CHECK(old_euid == new_euid); + CHECK(old_suid == new_suid); + + gid_t new_rgid, new_egid, new_sgid; + PCHECK(0 == getresgid(&new_rgid, &new_egid, &new_sgid)); + CHECK(old_rgid == new_rgid); + CHECK(old_egid == new_egid); + CHECK(old_sgid == new_sgid); +} + +bool NewUserNSCycle() { + if (!Credentials::MoveToNewUserNS() || + !Credentials::HasAnyCapability() || + !Credentials::DropAllCapabilities() || + Credentials::HasAnyCapability()) { + return false; + } + return true; +} + +SANDBOX_TEST(Credentials, NestedUserNS) { + CHECK(Credentials::DropAllCapabilities()); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + CHECK(Credentials::DropAllCapabilities()); + // As of 3.12, the kernel has a limit of 32. See create_user_ns(). + const int kNestLevel = 10; + for (int i = 0; i < kNestLevel; ++i) { + CHECK(NewUserNSCycle()) << "Creating new user NS failed at iteration " + << i << "."; + } +} + +// Test the WorkingDirectoryIsRoot() helper. +SANDBOX_TEST(Credentials, CanDetectRoot) { + PCHECK(0 == chdir("/proc/")); + CHECK(!WorkingDirectoryIsRoot()); + PCHECK(0 == chdir("/")); + CHECK(WorkingDirectoryIsRoot()); +} + +// Disabled on ASAN because of crbug.com/451603. +SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessIsSafe)) { + CHECK(Credentials::DropAllCapabilities()); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); + CHECK(!base::DirectoryExists(base::FilePath("/proc"))); + CHECK(WorkingDirectoryIsRoot()); + CHECK(base::IsDirectoryEmpty(base::FilePath("/"))); + // We want the chroot to never have a subdirectory. A subdirectory + // could allow a chroot escape. + CHECK_NE(0, mkdir("/test", 0700)); +} + +// Check that after dropping filesystem access and dropping privileges +// it is not possible to regain capabilities. +SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(CannotRegainPrivileges)) { + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + CHECK(Credentials::DropAllCapabilities(proc_fd.get())); + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) return; + CHECK(Credentials::DropFileSystemAccess(proc_fd.get())); + CHECK(Credentials::DropAllCapabilities(proc_fd.get())); + + // The kernel should now prevent us from regaining capabilities because we + // are in a chroot. + CHECK(!Credentials::CanCreateProcessInNewUserNS()); + CHECK(!Credentials::MoveToNewUserNS()); +} + +SANDBOX_TEST(Credentials, SetCapabilities) { + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) + return; + + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_ADMIN)); + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); + + std::vector<Credentials::Capability> caps; + caps.push_back(Credentials::Capability::SYS_CHROOT); + CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); + + CHECK(!Credentials::HasCapability(Credentials::Capability::SYS_ADMIN)); + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); + + const std::vector<Credentials::Capability> no_caps; + CHECK(Credentials::SetCapabilities(proc_fd.get(), no_caps)); + CHECK(!Credentials::HasAnyCapability()); +} + +SANDBOX_TEST(Credentials, SetCapabilitiesAndChroot) { + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) + return; + + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + + CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); + PCHECK(chroot("/") == 0); + + std::vector<Credentials::Capability> caps; + caps.push_back(Credentials::Capability::SYS_CHROOT); + CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); + PCHECK(chroot("/") == 0); + + CHECK(Credentials::DropAllCapabilities()); + PCHECK(chroot("/") == -1 && errno == EPERM); +} + +SANDBOX_TEST(Credentials, SetCapabilitiesMatchesLibCap2) { + // Probably missing kernel support. + if (!Credentials::MoveToNewUserNS()) + return; + + base::ScopedFD proc_fd(ProcUtil::OpenProc()); + + std::vector<Credentials::Capability> caps; + caps.push_back(Credentials::Capability::SYS_CHROOT); + CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); + + ScopedCap actual_cap(cap_get_proc()); + PCHECK(actual_cap != nullptr); + + ScopedCap expected_cap(cap_init()); + PCHECK(expected_cap != nullptr); + + const cap_value_t allowed_cap = CAP_SYS_CHROOT; + for (const cap_flag_t flag : {CAP_EFFECTIVE, CAP_PERMITTED}) { + PCHECK(cap_set_flag(expected_cap.get(), flag, 1, &allowed_cap, CAP_SET) == + 0); + } + + CHECK_EQ(0, cap_compare(expected_cap.get(), actual_cap.get())); +} + +} // namespace. + +} // namespace sandbox. diff --git a/sandbox/linux/services/init_process_reaper.cc b/sandbox/linux/services/init_process_reaper.cc new file mode 100644 index 0000000000..2e0b90b7b5 --- /dev/null +++ b/sandbox/linux/services/init_process_reaper.cc @@ -0,0 +1,101 @@ +// Copyright 2013 The Chromium 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 "sandbox/linux/services/init_process_reaper.h" + +#include <signal.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace sandbox { + +namespace { + +void DoNothingSignalHandler(int signal) {} + +} // namespace + +bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback) { + int sync_fds[2]; + // We want to use send, so we can't use a pipe + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_fds)) { + PLOG(ERROR) << "Failed to create socketpair"; + return false; + } + pid_t child_pid = fork(); + if (child_pid == -1) { + int close_ret; + close_ret = IGNORE_EINTR(close(sync_fds[0])); + DPCHECK(!close_ret); + close_ret = IGNORE_EINTR(close(sync_fds[1])); + DPCHECK(!close_ret); + return false; + } + if (child_pid) { + // In the parent, assuming the role of an init process. + // The disposition for SIGCHLD cannot be SIG_IGN or wait() will only return + // once all of our childs are dead. Since we're init we need to reap childs + // as they come. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = &DoNothingSignalHandler; + CHECK(sigaction(SIGCHLD, &action, NULL) == 0); + + int close_ret; + close_ret = IGNORE_EINTR(close(sync_fds[0])); + DPCHECK(!close_ret); + close_ret = shutdown(sync_fds[1], SHUT_RD); + DPCHECK(!close_ret); + if (post_fork_parent_callback) + post_fork_parent_callback->Run(); + // Tell the child to continue + CHECK(HANDLE_EINTR(send(sync_fds[1], "C", 1, MSG_NOSIGNAL)) == 1); + close_ret = IGNORE_EINTR(close(sync_fds[1])); + DPCHECK(!close_ret); + + for (;;) { + // Loop until we have reaped our one natural child + siginfo_t reaped_child_info; + int wait_ret = + HANDLE_EINTR(waitid(P_ALL, 0, &reaped_child_info, WEXITED)); + if (wait_ret) + _exit(1); + if (reaped_child_info.si_pid == child_pid) { + int exit_code = 0; + // We're done waiting + if (reaped_child_info.si_code == CLD_EXITED) { + exit_code = reaped_child_info.si_status; + } + // Exit with the same exit code as our parent. Exit with 0 if we got + // signaled. + _exit(exit_code); + } + } + } else { + // The child needs to wait for the parent to run the callback to avoid a + // race condition. + int close_ret; + close_ret = IGNORE_EINTR(close(sync_fds[1])); + DPCHECK(!close_ret); + close_ret = shutdown(sync_fds[0], SHUT_WR); + DPCHECK(!close_ret); + char should_continue; + int read_ret = HANDLE_EINTR(read(sync_fds[0], &should_continue, 1)); + close_ret = IGNORE_EINTR(close(sync_fds[0])); + DPCHECK(!close_ret); + if (read_ret == 1) + return true; + else + return false; + } +} + +} // namespace sandbox. diff --git a/sandbox/linux/services/init_process_reaper.h b/sandbox/linux/services/init_process_reaper.h new file mode 100644 index 0000000000..840f6fcda7 --- /dev/null +++ b/sandbox/linux/services/init_process_reaper.h @@ -0,0 +1,25 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ +#define SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ + +#include "base/callback_forward.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// The current process will fork(). The parent will become a process reaper +// like init(1). The child will continue normally (after this function +// returns). +// If not NULL, |post_fork_parent_callback| will run in the parent almost +// immediately after fork(). +// Since this function calls fork(), it's very important that the caller has +// only one thread running. +SANDBOX_EXPORT bool CreateInitProcessReaper( + base::Closure* post_fork_parent_callback); + +} // namespace sandbox. + +#endif // SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ diff --git a/sandbox/linux/services/namespace_sandbox.cc b/sandbox/linux/services/namespace_sandbox.cc new file mode 100644 index 0000000000..23796446f3 --- /dev/null +++ b/sandbox/linux/services/namespace_sandbox.cc @@ -0,0 +1,208 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/services/namespace_sandbox.h" + +#include <sched.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <utility> +#include <vector> + +#include "base/command_line.h" +#include "base/environment.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/namespace_utils.h" + +namespace sandbox { + +namespace { + +const char kSandboxUSERNSEnvironmentVarName[] = "SBX_USER_NS"; +const char kSandboxPIDNSEnvironmentVarName[] = "SBX_PID_NS"; +const char kSandboxNETNSEnvironmentVarName[] = "SBX_NET_NS"; + +#if !defined(OS_NACL_NONSFI) +class WriteUidGidMapDelegate : public base::LaunchOptions::PreExecDelegate { + public: + WriteUidGidMapDelegate() + : uid_(getuid()), + gid_(getgid()), + supports_deny_setgroups_( + NamespaceUtils::KernelSupportsDenySetgroups()) {} + + ~WriteUidGidMapDelegate() override {} + + void RunAsyncSafe() override { + if (supports_deny_setgroups_) { + RAW_CHECK(NamespaceUtils::DenySetgroups()); + } + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid_)); + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid_)); + } + + private: + const uid_t uid_; + const gid_t gid_; + const bool supports_deny_setgroups_; + DISALLOW_COPY_AND_ASSIGN(WriteUidGidMapDelegate); +}; + +void SetEnvironForNamespaceType(base::EnvironmentMap* environ, + base::NativeEnvironmentString env_var, + bool value) { + // An empty string causes the env var to be unset in the child process. + (*environ)[env_var] = value ? "1" : ""; +} + +// Linux supports up to 64 signals. This should be updated if that ever changes. +int g_signal_exit_codes[64]; + +void TerminationSignalHandler(int sig) { + // Return a special exit code so that the process is detected as terminated by + // a signal. + const size_t sig_idx = static_cast<size_t>(sig); + if (sig_idx < arraysize(g_signal_exit_codes)) { + _exit(g_signal_exit_codes[sig_idx]); + } + + _exit(NamespaceSandbox::kDefaultExitCode); +} +#endif // !defined(OS_NACL_NONSFI) + +} // namespace + +#if !defined(OS_NACL_NONSFI) +// static +base::Process NamespaceSandbox::LaunchProcess( + const base::CommandLine& cmdline, + const base::LaunchOptions& options) { + return LaunchProcess(cmdline.argv(), options); +} + +// static +base::Process NamespaceSandbox::LaunchProcess( + const std::vector<std::string>& argv, + const base::LaunchOptions& options) { + int clone_flags = 0; + int ns_types[] = {CLONE_NEWUSER, CLONE_NEWPID, CLONE_NEWNET}; + for (const int ns_type : ns_types) { + if (NamespaceUtils::KernelSupportsUnprivilegedNamespace(ns_type)) { + clone_flags |= ns_type; + } + } + CHECK(clone_flags & CLONE_NEWUSER); + + // These fields may not be set by the caller. + CHECK(options.pre_exec_delegate == nullptr); + CHECK_EQ(0, options.clone_flags); + + WriteUidGidMapDelegate write_uid_gid_map_delegate; + + base::LaunchOptions launch_options = options; + launch_options.pre_exec_delegate = &write_uid_gid_map_delegate; + launch_options.clone_flags = clone_flags; + + const std::pair<int, const char*> clone_flag_environ[] = { + std::make_pair(CLONE_NEWUSER, kSandboxUSERNSEnvironmentVarName), + std::make_pair(CLONE_NEWPID, kSandboxPIDNSEnvironmentVarName), + std::make_pair(CLONE_NEWNET, kSandboxNETNSEnvironmentVarName), + }; + + base::EnvironmentMap* environ = &launch_options.environ; + for (const auto& entry : clone_flag_environ) { + const int flag = entry.first; + const char* environ_name = entry.second; + SetEnvironForNamespaceType(environ, environ_name, clone_flags & flag); + } + + return base::LaunchProcess(argv, launch_options); +} + +// static +pid_t NamespaceSandbox::ForkInNewPidNamespace(bool drop_capabilities_in_child) { + const pid_t pid = + base::ForkWithFlags(CLONE_NEWPID | SIGCHLD, nullptr, nullptr); + if (pid < 0) { + return pid; + } + + if (pid == 0) { + DCHECK_EQ(1, getpid()); + if (drop_capabilities_in_child) { + // Since we just forked, we are single-threaded, so this should be safe. + CHECK(Credentials::DropAllCapabilitiesOnCurrentThread()); + } + return 0; + } + + return pid; +} + +// static +void NamespaceSandbox::InstallDefaultTerminationSignalHandlers() { + static const int kDefaultTermSignals[] = { + SIGHUP, SIGINT, SIGABRT, SIGQUIT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2, + }; + + for (const int sig : kDefaultTermSignals) { + InstallTerminationSignalHandler(sig, kDefaultExitCode); + } +} + +// static +bool NamespaceSandbox::InstallTerminationSignalHandler( + int sig, + int exit_code) { + struct sigaction old_action; + PCHECK(sigaction(sig, nullptr, &old_action) == 0); + + if (old_action.sa_flags & SA_SIGINFO && + old_action.sa_sigaction != nullptr) { + return false; + } else if (old_action.sa_handler != SIG_DFL) { + return false; + } + + const size_t sig_idx = static_cast<size_t>(sig); + CHECK_LT(sig_idx, arraysize(g_signal_exit_codes)); + + DCHECK_GE(exit_code, 0); + DCHECK_LT(exit_code, 256); + + g_signal_exit_codes[sig_idx] = exit_code; + + struct sigaction action = {}; + action.sa_handler = &TerminationSignalHandler; + PCHECK(sigaction(sig, &action, nullptr) == 0); + return true; +} +#endif // !defined(OS_NACL_NONSFI) + +// static +bool NamespaceSandbox::InNewUserNamespace() { + return getenv(kSandboxUSERNSEnvironmentVarName) != nullptr; +} + +// static +bool NamespaceSandbox::InNewPidNamespace() { + return getenv(kSandboxPIDNSEnvironmentVarName) != nullptr; +} + +// static +bool NamespaceSandbox::InNewNetNamespace() { + return getenv(kSandboxNETNSEnvironmentVarName) != nullptr; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/namespace_sandbox.h b/sandbox/linux/services/namespace_sandbox.h new file mode 100644 index 0000000000..80097fb16a --- /dev/null +++ b/sandbox/linux/services/namespace_sandbox.h @@ -0,0 +1,101 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_ +#define SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_ + +#include <sys/types.h> + +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/macros.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Helper class for starting a process inside a new user, PID, and network +// namespace. Before using a namespace sandbox, check for namespaces support +// using Credentials::CanCreateProcessInNewUserNS. +// +// A typical use for "A" launching a sandboxed process "B" would be: +// 1. A sets up a command line and launch options for process B. +// 2. A launches B with LaunchProcess. +// 3. B should be prepared to assume the role of init(1). In particular, apart +// from SIGKILL and SIGSTOP, B cannot receive any signal for which it does +// not have an explicit signal handler registered. +// If B dies, all the processes in the namespace will die. +// B can fork() and the parent can assume the role of init(1), by using +// CreateInitProcessReaper(). +// 4. B chroots using Credentials::MoveToNewUserNS() and +// Credentials::DropFileSystemAccess() +// 5. B drops capabilities gained by entering the new user namespace with +// Credentials::DropAllCapabilities(). +class SANDBOX_EXPORT NamespaceSandbox { + public: +#if !defined(OS_NACL_NONSFI) + static const int kDefaultExitCode = 1; + + // Launch a new process inside its own user/PID/network namespaces (depending + // on kernel support). Requires at a minimum that user namespaces are + // supported (use Credentials::CanCreateProcessInNewUserNS to check this). + // + // pre_exec_delegate and clone_flags fields of LaunchOptions should be nullptr + // and 0, respectively, since this function makes a copy of options and + // overrides them. + static base::Process LaunchProcess(const base::CommandLine& cmdline, + const base::LaunchOptions& options); + static base::Process LaunchProcess(const std::vector<std::string>& argv, + const base::LaunchOptions& options); + + // Forks a process in its own PID namespace. The child process is the init + // process inside of the PID namespace, so if the child needs to fork further, + // it should call CreateInitProcessReaper, which turns the init process into a + // reaper process. + // + // Otherwise, the child should setup handlers for signals which should + // terminate the process using InstallDefaultTerminationSignalHandlers or + // InstallTerminationSignalHandler. This works around the fact that init + // processes ignore such signals unless they have an explicit handler set. + // + // This function requries CAP_SYS_ADMIN. If |drop_capabilities_in_child| is + // true, then capabilities are dropped in the child. + static pid_t ForkInNewPidNamespace(bool drop_capabilities_in_child); + + // Installs a signal handler for: + // + // SIGHUP, SIGINT, SIGABRT, SIGQUIT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2 + // + // that exits with kDefaultExitCode. These are signals whose default action is + // to terminate the program (apart from SIGILL, SIGFPE, and SIGSEGV, which + // will still terminate the process if e.g. an illegal instruction is + // encountered, etc.). + // + // If any of these already had a signal handler installed, this function will + // not override them. + static void InstallDefaultTerminationSignalHandlers(); + + // Installs a signal handler for |sig| which exits with |exit_code|. If a + // signal handler was already present for |sig|, does nothing and returns + // false. + static bool InstallTerminationSignalHandler(int sig, int exit_code); +#endif // !defined(OS_NACL_NONSFI) + + // Returns whether the namespace sandbox created a new user, PID, and network + // namespace. In particular, InNewUserNamespace should return true iff the + // process was started via this class. + static bool InNewUserNamespace(); + static bool InNewPidNamespace(); + static bool InNewNetNamespace(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceSandbox); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_ diff --git a/sandbox/linux/services/namespace_sandbox_unittest.cc b/sandbox/linux/services/namespace_sandbox_unittest.cc new file mode 100644 index 0000000000..547ef6728c --- /dev/null +++ b/sandbox/linux/services/namespace_sandbox_unittest.cc @@ -0,0 +1,217 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/services/namespace_sandbox.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> +#include <utility> + +#include "base/command_line.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/launch.h" +#include "base/process/process.h" +#include "base/test/multiprocess_test.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/services/namespace_utils.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace sandbox { + +namespace { + +bool RootDirectoryIsEmpty() { + base::FilePath root("/"); + int file_type = + base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES; + base::FileEnumerator enumerator_before(root, false, file_type); + return enumerator_before.Next().empty(); +} + +class NamespaceSandboxTest : public base::MultiProcessTest { + public: + void TestProc(const std::string& procname) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + base::FileHandleMappingVector fds_to_remap = { + std::make_pair(STDOUT_FILENO, STDOUT_FILENO), + std::make_pair(STDERR_FILENO, STDERR_FILENO), + }; + base::LaunchOptions launch_options; + launch_options.fds_to_remap = &fds_to_remap; + + base::Process process = + NamespaceSandbox::LaunchProcess(MakeCmdLine(procname), launch_options); + ASSERT_TRUE(process.IsValid()); + + const int kDummyExitCode = 42; + int exit_code = kDummyExitCode; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_EQ(0, exit_code); + } +}; + +MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + bool in_user_ns = NamespaceSandbox::InNewUserNamespace(); + bool in_pid_ns = NamespaceSandbox::InNewPidNamespace(); + bool in_net_ns = NamespaceSandbox::InNewNetNamespace(); + CHECK(in_user_ns); + CHECK_EQ(in_pid_ns, + NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWPID)); + CHECK_EQ(in_net_ns, + NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWNET)); + if (in_pid_ns) { + CHECK_EQ(1, getpid()); + } + return 0; +} + +TEST_F(NamespaceSandboxTest, BasicUsage) { + TestProc("SimpleChildProcess"); +} + +MULTIPROCESS_TEST_MAIN(ChrootMe) { + CHECK(!RootDirectoryIsEmpty()); + CHECK(sandbox::Credentials::MoveToNewUserNS()); + CHECK(sandbox::Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); + CHECK(RootDirectoryIsEmpty()); + return 0; +} + +// Temporarily disabled on ASAN due to crbug.com/451603. +TEST_F(NamespaceSandboxTest, DISABLE_ON_ASAN(ChrootAndDropCapabilities)) { + TestProc("ChrootMe"); +} + +MULTIPROCESS_TEST_MAIN(NestedNamespaceSandbox) { + base::FileHandleMappingVector fds_to_remap = { + std::make_pair(STDOUT_FILENO, STDOUT_FILENO), + std::make_pair(STDERR_FILENO, STDERR_FILENO), + }; + base::LaunchOptions launch_options; + launch_options.fds_to_remap = &fds_to_remap; + base::Process process = NamespaceSandbox::LaunchProcess( + base::CommandLine(base::FilePath("/bin/true")), launch_options); + CHECK(process.IsValid()); + + const int kDummyExitCode = 42; + int exit_code = kDummyExitCode; + CHECK(process.WaitForExit(&exit_code)); + CHECK_EQ(0, exit_code); + return 0; +} + +TEST_F(NamespaceSandboxTest, NestedNamespaceSandbox) { + TestProc("NestedNamespaceSandbox"); +} + +const int kNormalExitCode = 0; +const int kSignalTerminationExitCode = 255; + +// Ensure that CHECK(false) is distinguishable from _exit(kNormalExitCode). +// Allowing noise since CHECK(false) will write a stack trace to stderr. +SANDBOX_TEST_ALLOW_NOISE(ForkInNewPidNamespace, CheckDoesNotReturnZero) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + CHECK(sandbox::Credentials::MoveToNewUserNS()); + const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + CHECK_GE(pid, 0); + + if (pid == 0) { + CHECK(false); + _exit(kNormalExitCode); + } + + int status; + PCHECK(waitpid(pid, &status, 0) == pid); + if (WIFEXITED(status)) { + CHECK_NE(kNormalExitCode, WEXITSTATUS(status)); + } +} + +SANDBOX_TEST(ForkInNewPidNamespace, BasicUsage) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + CHECK(sandbox::Credentials::MoveToNewUserNS()); + const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + CHECK_GE(pid, 0); + + if (pid == 0) { + CHECK_EQ(1, getpid()); + CHECK(!Credentials::HasAnyCapability()); + _exit(kNormalExitCode); + } + + int status; + PCHECK(waitpid(pid, &status, 0) == pid); + CHECK(WIFEXITED(status)); + CHECK_EQ(kNormalExitCode, WEXITSTATUS(status)); +} + +SANDBOX_TEST(ForkInNewPidNamespace, ExitWithSignal) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + CHECK(sandbox::Credentials::MoveToNewUserNS()); + const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( + /*drop_capabilities_in_child=*/true); + CHECK_GE(pid, 0); + + if (pid == 0) { + CHECK_EQ(1, getpid()); + CHECK(!Credentials::HasAnyCapability()); + CHECK(NamespaceSandbox::InstallTerminationSignalHandler( + SIGTERM, kSignalTerminationExitCode)); + while (true) { + raise(SIGTERM); + } + } + + int status; + PCHECK(waitpid(pid, &status, 0) == pid); + CHECK(WIFEXITED(status)); + CHECK_EQ(kSignalTerminationExitCode, WEXITSTATUS(status)); +} + +volatile sig_atomic_t signal_handler_called; +void ExitSuccessfully(int sig) { + signal_handler_called = 1; +} + +SANDBOX_TEST(InstallTerminationSignalHandler, DoesNotOverrideExistingHandlers) { + struct sigaction action = {}; + action.sa_handler = &ExitSuccessfully; + PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0); + + NamespaceSandbox::InstallDefaultTerminationSignalHandlers(); + CHECK(!NamespaceSandbox::InstallTerminationSignalHandler( + SIGUSR1, kSignalTerminationExitCode)); + + raise(SIGUSR1); + CHECK_EQ(1, signal_handler_called); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/namespace_utils.cc b/sandbox/linux/services/namespace_utils.cc new file mode 100644 index 0000000000..29b649c078 --- /dev/null +++ b/sandbox/linux/services/namespace_utils.cc @@ -0,0 +1,117 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/services/namespace_utils.h" + +#include <fcntl.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "base/strings/safe_sprintf.h" +#include "third_party/valgrind/valgrind.h" + +namespace sandbox { + +namespace { +bool IsRunningOnValgrind() { + return RUNNING_ON_VALGRIND; +} + +const char kProcSelfSetgroups[] = "/proc/self/setgroups"; +} // namespace + +// static +bool NamespaceUtils::WriteToIdMapFile(const char* map_file, generic_id_t id) { + // This function needs to be async-signal-safe, as it may be called in between + // fork and exec. + + int fd = HANDLE_EINTR(open(map_file, O_WRONLY)); + if (fd == -1) { + return false; + } + + const generic_id_t inside_id = id; + const generic_id_t outside_id = id; + + char mapping[64]; + const ssize_t len = + base::strings::SafeSPrintf(mapping, "%d %d 1\n", inside_id, outside_id); + const ssize_t rc = HANDLE_EINTR(write(fd, mapping, len)); + RAW_CHECK(IGNORE_EINTR(close(fd)) == 0); + return rc == len; +} + +// static +bool NamespaceUtils::KernelSupportsUnprivilegedNamespace(int type) { + // Valgrind will let clone(2) pass-through, but doesn't support unshare(), + // so always consider namespaces unsupported there. + if (IsRunningOnValgrind()) { + return false; + } + + // As of Linux 3.8, /proc/self/ns/* files exist for all namespace types. Since + // user namespaces were added in 3.8, it is OK to rely on the existence of + // /proc/self/ns/*. + if (!base::PathExists(base::FilePath("/proc/self/ns/user"))) { + return false; + } + + const char* path; + switch (type) { + case CLONE_NEWUSER: + return true; + case CLONE_NEWIPC: + path = "/proc/self/ns/ipc"; + break; + case CLONE_NEWNET: + path = "/proc/self/ns/net"; + break; + case CLONE_NEWNS: + path = "/proc/self/ns/mnt"; + break; + case CLONE_NEWPID: + path = "/proc/self/ns/pid"; + break; + case CLONE_NEWUTS: + path = "/proc/self/ns/uts"; + break; + default: + NOTREACHED(); + return false; + } + + return base::PathExists(base::FilePath(path)); +} + +// static +bool NamespaceUtils::KernelSupportsDenySetgroups() { + return base::PathExists(base::FilePath(kProcSelfSetgroups)); +} + +// static +bool NamespaceUtils::DenySetgroups() { + // This function needs to be async-signal-safe. + int fd = HANDLE_EINTR(open(kProcSelfSetgroups, O_WRONLY)); + if (fd == -1) { + return false; + } + + static const char kDeny[] = "deny"; + const ssize_t len = sizeof(kDeny) - 1; + const ssize_t rc = HANDLE_EINTR(write(fd, kDeny, len)); + RAW_CHECK(IGNORE_EINTR(close(fd)) == 0); + return rc == len; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/namespace_utils.h b/sandbox/linux/services/namespace_utils.h new file mode 100644 index 0000000000..f3c88a9452 --- /dev/null +++ b/sandbox/linux/services/namespace_utils.h @@ -0,0 +1,53 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_NAMESPACE_UTILS_H_ +#define SANDBOX_LINUX_SERVICES_NAMESPACE_UTILS_H_ + +#include <sys/types.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/template_util.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Utility functions for using Linux namepaces. +class SANDBOX_EXPORT NamespaceUtils { + public: + COMPILE_ASSERT((base::is_same<uid_t, gid_t>::value), UidAndGidAreSameType); + // generic_id_t can be used for either uid_t or gid_t. + typedef uid_t generic_id_t; + + // Write a uid or gid mapping from |id| to |id| in |map_file|. This function + // is async-signal-safe. + static bool WriteToIdMapFile(const char* map_file, + generic_id_t id) WARN_UNUSED_RESULT; + + // Returns true if unprivileged namespaces of type |type| is supported + // (meaning that both CLONE_NEWUSER and type are are supported). |type| must + // be one of CLONE_NEWIPC, CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWPID, + // CLONE_NEWUSER, or CLONE_NEWUTS. This relies on access to /proc, so it will + // not work from within a sandbox. + static bool KernelSupportsUnprivilegedNamespace(int type); + + // Returns true if the kernel supports denying setgroups in a user namespace. + // On kernels where this is supported, DenySetgroups must be called before a + // gid mapping can be added. + static bool KernelSupportsDenySetgroups(); + + // Disables setgroups() within the current user namespace. On Linux 3.18.2 and + // later, this is required in order to write to /proc/self/gid_map without + // having CAP_SETGID. Callers can determine whether is this needed with + // KernelSupportsDenySetgroups. This function is async-signal-safe. + static bool DenySetgroups() WARN_UNUSED_RESULT; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NamespaceUtils); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_NAMESPACE_UTILS_H_ diff --git a/sandbox/linux/services/namespace_utils_unittest.cc b/sandbox/linux/services/namespace_utils_unittest.cc new file mode 100644 index 0000000000..41ed7e89a6 --- /dev/null +++ b/sandbox/linux/services/namespace_utils_unittest.cc @@ -0,0 +1,72 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/services/namespace_utils.h" + +#include <errno.h> +#include <sched.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/launch.h" +#include "sandbox/linux/services/credentials.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +SANDBOX_TEST(NamespaceUtils, KernelSupportsUnprivilegedNamespace) { + const bool can_create_user_ns = Credentials::CanCreateProcessInNewUserNS(); + const bool supports_user_ns = + NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWUSER); + // can_create_user_ns implies supports_user_ns, but the converse is not + // necessarily true, as creating a user namespace can fail for various + // reasons. + if (can_create_user_ns) { + SANDBOX_ASSERT(supports_user_ns); + } +} + +SANDBOX_TEST(NamespaceUtils, WriteToIdMapFile) { + if (!Credentials::CanCreateProcessInNewUserNS()) { + return; + } + + const uid_t uid = getuid(); + const gid_t gid = getgid(); + + const bool supports_deny_setgroups = + NamespaceUtils::KernelSupportsDenySetgroups(); + + const pid_t pid = + base::ForkWithFlags(CLONE_NEWUSER | SIGCHLD, nullptr, nullptr); + ASSERT_NE(-1, pid); + if (pid == 0) { + if (supports_deny_setgroups) { + RAW_CHECK(NamespaceUtils::DenySetgroups()); + } + + RAW_CHECK(getuid() != uid); + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/uid_map", uid)); + RAW_CHECK(getuid() == uid); + + RAW_CHECK(getgid() != gid); + RAW_CHECK(NamespaceUtils::WriteToIdMapFile("/proc/self/gid_map", gid)); + RAW_CHECK(getgid() == gid); + + _exit(0); + } + + int status = 42; + SANDBOX_ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + SANDBOX_ASSERT_EQ(0, status); +} + +} // namespace. + +} // namespace sandbox. diff --git a/sandbox/linux/services/proc_util.cc b/sandbox/linux/services/proc_util.cc new file mode 100644 index 0000000000..d3f755f9a1 --- /dev/null +++ b/sandbox/linux/services/proc_util.cc @@ -0,0 +1,119 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/proc_util.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" + +namespace sandbox { +namespace { + +struct DIRCloser { + void operator()(DIR* d) const { + DCHECK(d); + PCHECK(0 == closedir(d)); + } +}; + +typedef scoped_ptr<DIR, DIRCloser> ScopedDIR; + +base::ScopedFD OpenDirectory(const char* path) { + DCHECK(path); + base::ScopedFD directory_fd( + HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC))); + PCHECK(directory_fd.is_valid()); + return directory_fd.Pass(); +} + +} // namespace + +int ProcUtil::CountOpenFds(int proc_fd) { + DCHECK_LE(0, proc_fd); + int proc_self_fd = HANDLE_EINTR( + openat(proc_fd, "self/fd/", O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + PCHECK(0 <= proc_self_fd); + + // Ownership of proc_self_fd is transferred here, it must not be closed + // or modified afterwards except via dir. + ScopedDIR dir(fdopendir(proc_self_fd)); + CHECK(dir); + + int count = 0; + struct dirent e; + struct dirent* de; + while (!readdir_r(dir.get(), &e, &de) && de) { + if (strcmp(e.d_name, ".") == 0 || strcmp(e.d_name, "..") == 0) { + continue; + } + + int fd_num; + CHECK(base::StringToInt(e.d_name, &fd_num)); + if (fd_num == proc_fd || fd_num == proc_self_fd) { + continue; + } + + ++count; + } + return count; +} + +bool ProcUtil::HasOpenDirectory(int proc_fd) { + DCHECK_LE(0, proc_fd); + int proc_self_fd = + openat(proc_fd, "self/fd/", O_DIRECTORY | O_RDONLY | O_CLOEXEC); + + PCHECK(0 <= proc_self_fd); + + // Ownership of proc_self_fd is transferred here, it must not be closed + // or modified afterwards except via dir. + ScopedDIR dir(fdopendir(proc_self_fd)); + CHECK(dir); + + struct dirent e; + struct dirent* de; + while (!readdir_r(dir.get(), &e, &de) && de) { + if (strcmp(e.d_name, ".") == 0 || strcmp(e.d_name, "..") == 0) { + continue; + } + + int fd_num; + CHECK(base::StringToInt(e.d_name, &fd_num)); + if (fd_num == proc_fd || fd_num == proc_self_fd) { + continue; + } + + struct stat s; + // It's OK to use proc_self_fd here, fstatat won't modify it. + CHECK(fstatat(proc_self_fd, e.d_name, &s, 0) == 0); + if (S_ISDIR(s.st_mode)) { + return true; + } + } + + // No open unmanaged directories found. + return false; +} + +bool ProcUtil::HasOpenDirectory() { + base::ScopedFD proc_fd( + HANDLE_EINTR(open("/proc/", O_DIRECTORY | O_RDONLY | O_CLOEXEC))); + return HasOpenDirectory(proc_fd.get()); +} + +//static +base::ScopedFD ProcUtil::OpenProc() { + return OpenDirectory("/proc/"); +} + +} // namespace sandbox diff --git a/sandbox/linux/services/proc_util.h b/sandbox/linux/services/proc_util.h new file mode 100644 index 0000000000..bc14c5ef2a --- /dev/null +++ b/sandbox/linux/services/proc_util.h @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_PROC_UTIL_H_ +#define SANDBOX_LINUX_SERVICES_PROC_UTIL_H_ + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +class SANDBOX_EXPORT ProcUtil { + public: + // Returns the number of file descriptors in the current process's FD + // table, excluding |proc_fd|, which should be a file descriptor for + // /proc/. + static int CountOpenFds(int proc_fd); + + // Checks whether the current process has any directory file descriptor open. + // Directory file descriptors are "capabilities" that would let a process use + // system calls such as openat() to bypass restrictions such as + // DropFileSystemAccess(). + // Sometimes it's useful to call HasOpenDirectory() after file system access + // has been dropped. In this case, |proc_fd| should be a file descriptor to + // /proc/. The file descriptor in |proc_fd| will be ignored by + // HasOpenDirectory() and remains owned by the caller. It is very important + // for the caller to close it. + static bool HasOpenDirectory(int proc_fd) WARN_UNUSED_RESULT; + static bool HasOpenDirectory() WARN_UNUSED_RESULT; + + // Open /proc/ or crash if not possible. + static base::ScopedFD OpenProc(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ProcUtil); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_PROC_UTIL_H_ diff --git a/sandbox/linux/services/proc_util_unittest.cc b/sandbox/linux/services/proc_util_unittest.cc new file mode 100644 index 0000000000..bf25151956 --- /dev/null +++ b/sandbox/linux/services/proc_util_unittest.cc @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/proc_util.h" + +#include <fcntl.h> +#include <unistd.h> + +#include "base/files/scoped_file.h" +#include "base/posix/eintr_wrapper.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +TEST(ProcUtil, CountOpenFds) { + base::ScopedFD proc_fd(open("/proc/", O_RDONLY | O_DIRECTORY)); + ASSERT_TRUE(proc_fd.is_valid()); + int fd_count = ProcUtil::CountOpenFds(proc_fd.get()); + int fd = open("/dev/null", O_RDONLY); + ASSERT_LE(0, fd); + EXPECT_EQ(fd_count + 1, ProcUtil::CountOpenFds(proc_fd.get())); + ASSERT_EQ(0, IGNORE_EINTR(close(fd))); + EXPECT_EQ(fd_count, ProcUtil::CountOpenFds(proc_fd.get())); +} + +TEST(ProcUtil, HasOpenDirectory) { + // No open directory should exist at startup. + EXPECT_FALSE(ProcUtil::HasOpenDirectory()); + { + // Have a "/proc" file descriptor around. + int proc_fd = open("/proc/", O_RDONLY | O_DIRECTORY); + base::ScopedFD proc_fd_closer(proc_fd); + EXPECT_TRUE(ProcUtil::HasOpenDirectory()); + } + EXPECT_FALSE(ProcUtil::HasOpenDirectory()); +} + +TEST(ProcUtil, HasOpenDirectoryWithFD) { + int proc_fd = open("/proc/", O_RDONLY | O_DIRECTORY); + base::ScopedFD proc_fd_closer(proc_fd); + ASSERT_LE(0, proc_fd); + + // Don't pass |proc_fd|, an open directory (proc_fd) should + // be detected. + EXPECT_TRUE(ProcUtil::HasOpenDirectory()); + // Pass |proc_fd| and no open directory should be detected. + EXPECT_FALSE(ProcUtil::HasOpenDirectory(proc_fd)); + + { + // Have a directory file descriptor around. + int open_directory_fd = open("/proc/self/", O_RDONLY | O_DIRECTORY); + base::ScopedFD open_directory_fd_closer(open_directory_fd); + EXPECT_TRUE(ProcUtil::HasOpenDirectory(proc_fd)); + } + + // The "/proc/" file descriptor should now be closed, |proc_fd| is the + // only directory file descriptor open. + EXPECT_FALSE(ProcUtil::HasOpenDirectory(proc_fd)); +} + +} // namespace sandbox diff --git a/sandbox/linux/services/resource_limits.cc b/sandbox/linux/services/resource_limits.cc new file mode 100644 index 0000000000..1ec11295d1 --- /dev/null +++ b/sandbox/linux/services/resource_limits.cc @@ -0,0 +1,26 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/services/resource_limits.h" + +#include <sys/resource.h> +#include <sys/time.h> + +#include <algorithm> + +namespace sandbox { + +// static +bool ResourceLimits::Lower(int resource, rlim_t limit) { + struct rlimit old_rlimit; + if (getrlimit(resource, &old_rlimit)) + return false; + // Make sure we don't raise the existing limit. + const struct rlimit new_rlimit = {std::min(old_rlimit.rlim_cur, limit), + std::min(old_rlimit.rlim_max, limit)}; + int rc = setrlimit(resource, &new_rlimit); + return rc == 0; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/resource_limits.h b/sandbox/linux/services/resource_limits.h new file mode 100644 index 0000000000..3464dab679 --- /dev/null +++ b/sandbox/linux/services/resource_limits.h @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_RESOURCE_LIMITS_H_ +#define SANDBOX_LINUX_SERVICES_RESOURCE_LIMITS_H_ + +#include <sys/resource.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This class provides a small wrapper around setrlimit(). +class SANDBOX_EXPORT ResourceLimits { + public: + // Lower the soft and hard limit of |resource| to |limit|. If the current + // limit is lower than |limit|, keep it. + static bool Lower(int resource, rlim_t limit) WARN_UNUSED_RESULT; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ResourceLimits); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_RESOURCE_LIMITS_H_ diff --git a/sandbox/linux/services/resource_limits_unittests.cc b/sandbox/linux/services/resource_limits_unittests.cc new file mode 100644 index 0000000000..910c740f7b --- /dev/null +++ b/sandbox/linux/services/resource_limits_unittests.cc @@ -0,0 +1,43 @@ +// Copyright 2015 The Chromium 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 "sandbox/linux/services/resource_limits.h" + +#include <errno.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <unistd.h> + +#include "base/logging.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +// Fails on Android: crbug.com/459158 +#if !defined(OS_ANDROID) +#define MAYBE_NoFork DISABLE_ON_ASAN(NoFork) +#else +#define MAYBE_NoFork DISABLED_NoFork +#endif // OS_ANDROID + +// Not being able to fork breaks LeakSanitizer, so disable on +// all ASAN builds. +SANDBOX_TEST(ResourceLimits, MAYBE_NoFork) { + // Make sure that fork will fail with EAGAIN. + SANDBOX_ASSERT(ResourceLimits::Lower(RLIMIT_NPROC, 0)); + errno = 0; + pid_t pid = fork(); + // Reap any child if fork succeeded. + TestUtils::HandlePostForkReturn(pid); + SANDBOX_ASSERT_EQ(-1, pid); + CHECK_EQ(EAGAIN, errno); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/scoped_process.cc b/sandbox/linux/services/scoped_process.cc new file mode 100644 index 0000000000..65af4873a4 --- /dev/null +++ b/sandbox/linux/services/scoped_process.cc @@ -0,0 +1,119 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/scoped_process.h" + +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" + +namespace sandbox { + +namespace { + +const char kSynchronisationChar[] = "D"; + +void WaitForever() { + while(true) { + pause(); + } +} + +} // namespace + +ScopedProcess::ScopedProcess(const base::Closure& child_callback) + : child_process_id_(-1), process_id_(getpid()) { + PCHECK(0 == pipe(pipe_fds_)); +#if !defined(THREAD_SANITIZER) + // Make sure that we can safely fork(). + CHECK(ThreadHelpers::IsSingleThreaded()); +#endif + child_process_id_ = fork(); + PCHECK(0 <= child_process_id_); + + if (0 == child_process_id_) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[0]))); + pipe_fds_[0] = -1; + child_callback.Run(); + // Notify the parent that the closure has run. + CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds_[1], kSynchronisationChar, 1))); + WaitForever(); + NOTREACHED(); + _exit(1); + } + + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[1]))); + pipe_fds_[1] = -1; +} + +ScopedProcess::~ScopedProcess() { + CHECK(IsOriginalProcess()); + if (child_process_id_ >= 0) { + PCHECK(0 == kill(child_process_id_, SIGKILL)); + siginfo_t process_info; + + PCHECK(0 == HANDLE_EINTR( + waitid(P_PID, child_process_id_, &process_info, WEXITED))); + } + if (pipe_fds_[0] >= 0) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[0]))); + } + if (pipe_fds_[1] >= 0) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[1]))); + } +} + +int ScopedProcess::WaitForExit(bool* got_signaled) { + DCHECK(got_signaled); + CHECK(IsOriginalProcess()); + siginfo_t process_info; + // WNOWAIT to make sure that the destructor can wait on the child. + int ret = HANDLE_EINTR( + waitid(P_PID, child_process_id_, &process_info, WEXITED | WNOWAIT)); + PCHECK(0 == ret) << "Did something else wait on the child?"; + + if (process_info.si_code == CLD_EXITED) { + *got_signaled = false; + } else if (process_info.si_code == CLD_KILLED || + process_info.si_code == CLD_DUMPED) { + *got_signaled = true; + } else { + CHECK(false) << "ScopedProcess needs to be extended for si_code " + << process_info.si_code; + } + return process_info.si_status; +} + +bool ScopedProcess::WaitForClosureToRun() { + char c = 0; + int ret = HANDLE_EINTR(read(pipe_fds_[0], &c, 1)); + PCHECK(ret >= 0); + if (0 == ret) + return false; + + CHECK_EQ(c, kSynchronisationChar[0]); + return true; +} + +// It would be problematic if after a fork(), another process would start using +// this object. +// This method allows to assert it is not happening. +bool ScopedProcess::IsOriginalProcess() { + // Make a direct syscall to bypass glibc caching of PIDs. + pid_t pid = sys_getpid(); + return pid == process_id_; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/scoped_process.h b/sandbox/linux/services/scoped_process.h new file mode 100644 index 0000000000..bddbd5529b --- /dev/null +++ b/sandbox/linux/services/scoped_process.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/process/process_handle.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// fork() a child process that will run a Closure. +// After the Closure has run, the child will pause forever. If this object +// is detroyed, the child will be destroyed, even if the closure did not +// finish running. It's ok to signal the child from outside of this class to +// destroy it. +// This class cannot be instanciated from a multi-threaded process, as it needs +// to fork(). +class SANDBOX_EXPORT ScopedProcess { + public: + // A new process will be created and |child_callback| will run in the child + // process. This callback is allowed to terminate the process or to simply + // return. If the callback returns, the process will wait forever. + explicit ScopedProcess(const base::Closure& child_callback); + ~ScopedProcess(); + + // Wait for the process to exit. + // |got_signaled| tells how to interpret the return value: either as an exit + // code, or as a signal number. + // When this returns, the process will still not have been reaped and will + // survive as a zombie for the lifetime of this object. This method can be + // called multiple times. + int WaitForExit(bool* got_signaled); + + // Wait for the |child_callback| passed at construction to run. Return false + // if |child_callback| did not finish running and we know it never will (for + // instance the child crashed or used _exit()). + bool WaitForClosureToRun(); + base::ProcessId GetPid() { return child_process_id_; } + + private: + bool IsOriginalProcess(); + + base::ProcessId child_process_id_; + base::ProcessId process_id_; + int pipe_fds_[2]; + DISALLOW_COPY_AND_ASSIGN(ScopedProcess); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ diff --git a/sandbox/linux/services/scoped_process_unittest.cc b/sandbox/linux/services/scoped_process_unittest.cc new file mode 100644 index 0000000000..8bd2847997 --- /dev/null +++ b/sandbox/linux/services/scoped_process_unittest.cc @@ -0,0 +1,130 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/scoped_process.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +void DoExit() { _exit(0); } + +void ExitWithCode(int exit_code) { _exit(exit_code); } + +void RaiseAndExit(int signal) { + PCHECK(0 == raise(signal)); + _exit(0); +} + +void DoNothing() {} + +TEST(ScopedProcess, ScopedProcessNormalExit) { + const int kCustomExitCode = 12; + ScopedProcess process(base::Bind(&ExitWithCode, kCustomExitCode)); + bool got_signaled = true; + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_FALSE(got_signaled); + EXPECT_EQ(kCustomExitCode, exit_code); + + // Verify that WaitForExit() can be called multiple times on the same + // process. + bool got_signaled2 = true; + int exit_code2 = process.WaitForExit(&got_signaled2); + EXPECT_FALSE(got_signaled2); + EXPECT_EQ(kCustomExitCode, exit_code2); +} + +// Disable this test on Android, SIGABRT is funky there. +TEST(ScopedProcess, DISABLE_ON_ANDROID(ScopedProcessAbort)) { + PCHECK(SIG_ERR != signal(SIGABRT, SIG_DFL)); + ScopedProcess process(base::Bind(&RaiseAndExit, SIGABRT)); + bool got_signaled = false; + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_TRUE(got_signaled); + EXPECT_EQ(SIGABRT, exit_code); +} + +TEST(ScopedProcess, ScopedProcessSignaled) { + ScopedProcess process(base::Bind(&DoNothing)); + bool got_signaled = false; + ASSERT_EQ(0, kill(process.GetPid(), SIGKILL)); + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_TRUE(got_signaled); + EXPECT_EQ(SIGKILL, exit_code); +} + +TEST(ScopedProcess, DiesForReal) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end_closer(pipe_fds[0]); + base::ScopedFD write_end_closer(pipe_fds[1]); + + { ScopedProcess process(base::Bind(&DoExit)); } + + // Close writing end of the pipe. + write_end_closer.reset(); + pipe_fds[1] = -1; + + ASSERT_EQ(0, fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK)); + char c; + // If the child process is dead for real, there will be no writing end + // for this pipe left and read will EOF instead of returning EWOULDBLOCK. + ASSERT_EQ(0, read(pipe_fds[0], &c, 1)); +} + +TEST(ScopedProcess, SynchronizationBasic) { + ScopedProcess process1(base::Bind(&DoNothing)); + EXPECT_TRUE(process1.WaitForClosureToRun()); + + ScopedProcess process2(base::Bind(&DoExit)); + // The closure didn't finish running normally. This case is simple enough + // that process.WaitForClosureToRun() should return false, even though the + // API does not guarantees that it will return at all. + EXPECT_FALSE(process2.WaitForClosureToRun()); +} + +void SleepInMsAndWriteOneByte(int time_to_sleep, int fd) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(time_to_sleep)); + CHECK(1 == write(fd, "1", 1)); +} + +TEST(ScopedProcess, SynchronizationWorks) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end_closer(pipe_fds[0]); + base::ScopedFD write_end_closer(pipe_fds[1]); + + // Start a process with a closure that takes a little bit to run. + ScopedProcess process( + base::Bind(&SleepInMsAndWriteOneByte, 100, pipe_fds[1])); + EXPECT_TRUE(process.WaitForClosureToRun()); + + // Verify that the closure did, indeed, run. + ASSERT_EQ(0, fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK)); + char c = 0; + EXPECT_EQ(1, read(pipe_fds[0], &c, 1)); + EXPECT_EQ('1', c); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/syscall_wrappers.cc b/sandbox/linux/services/syscall_wrappers.cc new file mode 100644 index 0000000000..b6e87655a3 --- /dev/null +++ b/sandbox/linux/services/syscall_wrappers.cc @@ -0,0 +1,246 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/syscall_wrappers.h" + +#include <pthread.h> +#include <sched.h> +#include <setjmp.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <cstring> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "third_party/valgrind/valgrind.h" + +namespace sandbox { + +pid_t sys_getpid(void) { + return syscall(__NR_getpid); +} + +pid_t sys_gettid(void) { + return syscall(__NR_gettid); +} + +long sys_clone(unsigned long flags, + decltype(nullptr) child_stack, + pid_t* ptid, + pid_t* ctid, + decltype(nullptr) tls) { + const bool clone_tls_used = flags & CLONE_SETTLS; + const bool invalid_ctid = + (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && !ctid; + const bool invalid_ptid = (flags & CLONE_PARENT_SETTID) && !ptid; + + // We do not support CLONE_VM. + const bool clone_vm_used = flags & CLONE_VM; + if (clone_tls_used || invalid_ctid || invalid_ptid || clone_vm_used) { + RAW_LOG(FATAL, "Invalid usage of sys_clone"); + } + + if (ptid) MSAN_UNPOISON(ptid, sizeof(*ptid)); + if (ctid) MSAN_UNPOISON(ctid, sizeof(*ctid)); + // See kernel/fork.c in Linux. There is different ordering of sys_clone + // parameters depending on CONFIG_CLONE_BACKWARDS* configuration options. +#if defined(ARCH_CPU_X86_64) + return syscall(__NR_clone, flags, child_stack, ptid, ctid, tls); +#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_MIPS64_FAMILY) + // CONFIG_CLONE_BACKWARDS defined. + return syscall(__NR_clone, flags, child_stack, ptid, tls, ctid); +#endif +} + +long sys_clone(unsigned long flags) { + return sys_clone(flags, nullptr, nullptr, nullptr, nullptr); +} + +void sys_exit_group(int status) { + syscall(__NR_exit_group, status); +} + +int sys_seccomp(unsigned int operation, + unsigned int flags, + const struct sock_fprog* args) { + return syscall(__NR_seccomp, operation, flags, args); +} + +int sys_prlimit64(pid_t pid, + int resource, + const struct rlimit64* new_limit, + struct rlimit64* old_limit) { + int res = syscall(__NR_prlimit64, pid, resource, new_limit, old_limit); + if (res == 0 && old_limit) MSAN_UNPOISON(old_limit, sizeof(*old_limit)); + return res; +} + +int sys_capget(cap_hdr* hdrp, cap_data* datap) { + int res = syscall(__NR_capget, hdrp, datap); + if (res == 0) { + if (hdrp) MSAN_UNPOISON(hdrp, sizeof(*hdrp)); + if (datap) MSAN_UNPOISON(datap, sizeof(*datap)); + } + return res; +} + +int sys_capset(cap_hdr* hdrp, const cap_data* datap) { + return syscall(__NR_capset, hdrp, datap); +} + +int sys_getresuid(uid_t* ruid, uid_t* euid, uid_t* suid) { + int res; +#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARMEL) + // On 32-bit x86 or 32-bit arm, getresuid supports 16bit values only. + // Use getresuid32 instead. + res = syscall(__NR_getresuid32, ruid, euid, suid); +#else + res = syscall(__NR_getresuid, ruid, euid, suid); +#endif + if (res == 0) { + if (ruid) MSAN_UNPOISON(ruid, sizeof(*ruid)); + if (euid) MSAN_UNPOISON(euid, sizeof(*euid)); + if (suid) MSAN_UNPOISON(suid, sizeof(*suid)); + } + return res; +} + +int sys_getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid) { + int res; +#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARMEL) + // On 32-bit x86 or 32-bit arm, getresgid supports 16bit values only. + // Use getresgid32 instead. + res = syscall(__NR_getresgid32, rgid, egid, sgid); +#else + res = syscall(__NR_getresgid, rgid, egid, sgid); +#endif + if (res == 0) { + if (rgid) MSAN_UNPOISON(rgid, sizeof(*rgid)); + if (egid) MSAN_UNPOISON(egid, sizeof(*egid)); + if (sgid) MSAN_UNPOISON(sgid, sizeof(*sgid)); + } + return res; +} + +int sys_chroot(const char* path) { + return syscall(__NR_chroot, path); +} + +int sys_unshare(int flags) { + return syscall(__NR_unshare, flags); +} + +int sys_sigprocmask(int how, const sigset_t* set, decltype(nullptr) oldset) { + // In some toolchain (in particular Android and PNaCl toolchain), + // sigset_t is 32 bits, but Linux ABI requires 64 bits. + uint64_t linux_value = 0; + std::memcpy(&linux_value, set, std::min(sizeof(sigset_t), sizeof(uint64_t))); + return syscall(__NR_rt_sigprocmask, how, &linux_value, nullptr, + sizeof(linux_value)); +} + +#if (defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ + (defined(ARCH_CPU_X86_64) && !defined(__clang__))) && \ + !defined(OS_NACL_NONSFI) +// If MEMORY_SANITIZER or THREAD_SANITIZER is enabled, it is necessary to call +// sigaction() here, rather than the direct syscall (sys_sigaction() defined +// by ourselves). +// It is because, if MEMORY_SANITIZER or THREAD_SANITIZER is enabled, sigaction +// is wrapped, and |act->sa_handler| is injected in order to unpoisonize the +// memory passed via callback's arguments for MEMORY_SANITIZER, or handle +// signals to check thread consistency for THREAD_SANITIZER. Please see +// msan_interceptors.cc and tsan_interceptors.cc for more details. +// So, specifically, if MEMORY_SANITIZER is enabled while the direct syscall is +// used, as MEMORY_SANITIZER does not know about it, sigaction() invocation in +// other places would be broken (in more precise, returned |oldact| would have +// a broken |sa_handler| callback). +// Practically, it would break NaCl's signal handler installation. +// cf) native_client/src/trusted/service_runtime/linux/nacl_signal.c. +// As for THREAD_SANITIZER, the intercepted signal handlers are processed more +// in other libc functions' interceptors (such as for raise()), so that it +// would not work properly. +// +// Also on x86_64 architecture, we need naked function for rt_sigreturn. +// However, there is no simple way to define it with GCC. Note that the body +// of function is actually very small (only two instructions), but we need to +// define much debug information in addition, otherwise backtrace() used by +// base::StackTrace would not work so that some tests would fail. +// +// When this is built with PNaCl toolchain, we should always use sys_sigaction +// below, because sigaction() provided by the toolchain is incompatible with +// Linux's ABI. So, otherwise, it would just fail. Note that it is not +// necessary to think about sigaction() invocation in other places even with +// MEMORY_SANITIZER or THREAD_SANITIZER, because it would just fail there. +int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact) { + return sigaction(signum, act, oldact); +} +#else +// struct sigaction is different ABI from the Linux's. +struct KernelSigAction { + void (*kernel_handler)(int); + uint32_t sa_flags; + void (*sa_restorer)(void); + uint64_t sa_mask; +}; + +// On X86_64 arch, it is necessary to set sa_restorer always. +#if defined(ARCH_CPU_X86_64) +#if !defined(SA_RESTORER) +#define SA_RESTORER 0x04000000 +#endif + +// rt_sigreturn is a special system call that interacts with the user land +// stack. Thus, here prologue must not be created, which implies syscall() +// does not work properly, too. Note that rt_sigreturn will never return. +static __attribute__((naked)) void sys_rt_sigreturn() { + // Just invoke rt_sigreturn system call. + asm volatile ("syscall\n" + :: "a"(__NR_rt_sigreturn)); +} +#endif + +int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact) { + KernelSigAction kernel_act = {}; + if (act) { + kernel_act.kernel_handler = act->sa_handler; + std::memcpy(&kernel_act.sa_mask, &act->sa_mask, + std::min(sizeof(kernel_act.sa_mask), sizeof(act->sa_mask))); + kernel_act.sa_flags = act->sa_flags; + +#if defined(ARCH_CPU_X86_64) + if (!(kernel_act.sa_flags & SA_RESTORER)) { + kernel_act.sa_flags |= SA_RESTORER; + kernel_act.sa_restorer = sys_rt_sigreturn; + } +#endif + } + + KernelSigAction kernel_oldact = {}; + int result = syscall(__NR_rt_sigaction, signum, act ? &kernel_act : nullptr, + oldact ? &kernel_oldact : nullptr, sizeof(uint64_t)); + if (result == 0 && oldact) { + oldact->sa_handler = kernel_oldact.kernel_handler; + sigemptyset(&oldact->sa_mask); + std::memcpy(&oldact->sa_mask, &kernel_oldact.sa_mask, + std::min(sizeof(kernel_act.sa_mask), sizeof(act->sa_mask))); + oldact->sa_flags = kernel_oldact.sa_flags; + } + return result; +} + +#endif // defined(MEMORY_SANITIZER) + +} // namespace sandbox diff --git a/sandbox/linux/services/syscall_wrappers.h b/sandbox/linux/services/syscall_wrappers.h new file mode 100644 index 0000000000..581425a367 --- /dev/null +++ b/sandbox/linux/services/syscall_wrappers.h @@ -0,0 +1,83 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ +#define SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ + +#include <signal.h> +#include <stdint.h> +#include <sys/types.h> + +#include "sandbox/sandbox_export.h" + +struct sock_fprog; +struct rlimit64; +struct cap_hdr; +struct cap_data; + +namespace sandbox { + +// Provide direct system call wrappers for a few common system calls. +// These are guaranteed to perform a system call and do not rely on things such +// as caching the current pid (c.f. getpid()) unless otherwise specified. + +SANDBOX_EXPORT pid_t sys_getpid(void); + +SANDBOX_EXPORT pid_t sys_gettid(void); + +SANDBOX_EXPORT long sys_clone(unsigned long flags); + +// |regs| is not supported and must be passed as nullptr. |child_stack| must be +// nullptr, since otherwise this function cannot safely return. As a +// consequence, this function does not support CLONE_VM. +SANDBOX_EXPORT long sys_clone(unsigned long flags, + decltype(nullptr) child_stack, + pid_t* ptid, + pid_t* ctid, + decltype(nullptr) regs); + +SANDBOX_EXPORT void sys_exit_group(int status); + +// The official system call takes |args| as void* (in order to be extensible), +// but add more typing for the cases that are currently used. +SANDBOX_EXPORT int sys_seccomp(unsigned int operation, + unsigned int flags, + const struct sock_fprog* args); + +// Some libcs do not expose a prlimit64 wrapper. +SANDBOX_EXPORT int sys_prlimit64(pid_t pid, + int resource, + const struct rlimit64* new_limit, + struct rlimit64* old_limit); + +// Some libcs do not expose capget/capset wrappers. We want to use these +// directly in order to avoid pulling in libcap2. +SANDBOX_EXPORT int sys_capget(struct cap_hdr* hdrp, struct cap_data* datap); +SANDBOX_EXPORT int sys_capset(struct cap_hdr* hdrp, + const struct cap_data* datap); + +// Some libcs do not expose getresuid/getresgid wrappers. +SANDBOX_EXPORT int sys_getresuid(uid_t* ruid, uid_t* euid, uid_t* suid); +SANDBOX_EXPORT int sys_getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid); + +// Some libcs do not expose a chroot wrapper. +SANDBOX_EXPORT int sys_chroot(const char* path); + +// Some libcs do not expose a unshare wrapper. +SANDBOX_EXPORT int sys_unshare(int flags); + +// Some libcs do not expose a sigprocmask. Note that oldset must be a nullptr, +// because of some ABI gap between toolchain's and Linux's. +SANDBOX_EXPORT int sys_sigprocmask(int how, + const sigset_t* set, + decltype(nullptr) oldset); + +// Some libcs do not expose a sigaction(). +SANDBOX_EXPORT int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ diff --git a/sandbox/linux/services/syscall_wrappers_unittest.cc b/sandbox/linux/services/syscall_wrappers_unittest.cc new file mode 100644 index 0000000000..249d9ae1da --- /dev/null +++ b/sandbox/linux/services/syscall_wrappers_unittest.cc @@ -0,0 +1,99 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/syscall_wrappers.h" + +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <cstring> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/valgrind/valgrind.h" + +namespace sandbox { + +namespace { + +TEST(SyscallWrappers, BasicSyscalls) { + EXPECT_EQ(getpid(), sys_getpid()); +} + +TEST(SyscallWrappers, CloneBasic) { + pid_t child = sys_clone(SIGCHLD); + TestUtils::HandlePostForkReturn(child); + EXPECT_LT(0, child); +} + +TEST(SyscallWrappers, CloneParentSettid) { + pid_t ptid = 0; + pid_t child = sys_clone(CLONE_PARENT_SETTID | SIGCHLD, nullptr, &ptid, + nullptr, nullptr); + TestUtils::HandlePostForkReturn(child); + EXPECT_LT(0, child); + EXPECT_EQ(child, ptid); +} + +TEST(SyscallWrappers, CloneChildSettid) { + pid_t ctid = 0; + pid_t pid = + sys_clone(CLONE_CHILD_SETTID | SIGCHLD, nullptr, nullptr, &ctid, nullptr); + + const int kSuccessExit = 0; + if (0 == pid) { + // In child. + if (sys_getpid() == ctid) + _exit(kSuccessExit); + _exit(1); + } + + ASSERT_NE(-1, pid); + int status = 0; + ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(kSuccessExit, WEXITSTATUS(status)); +} + +TEST(SyscallWrappers, GetRESUid) { + uid_t ruid, euid, suid; + uid_t sys_ruid, sys_euid, sys_suid; + ASSERT_EQ(0, getresuid(&ruid, &euid, &suid)); + ASSERT_EQ(0, sys_getresuid(&sys_ruid, &sys_euid, &sys_suid)); + EXPECT_EQ(ruid, sys_ruid); + EXPECT_EQ(euid, sys_euid); + EXPECT_EQ(suid, sys_suid); +} + +TEST(SyscallWrappers, GetRESGid) { + gid_t rgid, egid, sgid; + gid_t sys_rgid, sys_egid, sys_sgid; + ASSERT_EQ(0, getresgid(&rgid, &egid, &sgid)); + ASSERT_EQ(0, sys_getresgid(&sys_rgid, &sys_egid, &sys_sgid)); + EXPECT_EQ(rgid, sys_rgid); + EXPECT_EQ(egid, sys_egid); + EXPECT_EQ(sgid, sys_sgid); +} + +TEST(SyscallWrappers, LinuxSigSet) { + sigset_t sigset; + ASSERT_EQ(0, sigemptyset(&sigset)); + ASSERT_EQ(0, sigaddset(&sigset, LINUX_SIGSEGV)); + ASSERT_EQ(0, sigaddset(&sigset, LINUX_SIGBUS)); + uint64_t linux_sigset = 0; + std::memcpy(&linux_sigset, &sigset, + std::min(sizeof(sigset), sizeof(linux_sigset))); + EXPECT_EQ((1ULL << (LINUX_SIGSEGV - 1)) | (1ULL << (LINUX_SIGBUS - 1)), + linux_sigset); +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/thread_helpers.cc b/sandbox/linux/services/thread_helpers.cc new file mode 100644 index 0000000000..80766a9bc5 --- /dev/null +++ b/sandbox/linux/services/thread_helpers.cc @@ -0,0 +1,157 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/thread_helpers.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "sandbox/linux/services/proc_util.h" + +namespace sandbox { + +namespace { + +const char kAssertSingleThreadedError[] = + "Current process is not mono-threaded!"; + +bool IsSingleThreadedImpl(int proc_fd) { + CHECK_LE(0, proc_fd); + struct stat task_stat; + int fstat_ret = fstatat(proc_fd, "self/task/", &task_stat, 0); + PCHECK(0 == fstat_ret); + + // At least "..", "." and the current thread should be present. + CHECK_LE(3UL, task_stat.st_nlink); + // Counting threads via /proc/self/task could be racy. For the purpose of + // determining if the current proces is monothreaded it works: if at any + // time it becomes monothreaded, it'll stay so. + return task_stat.st_nlink == 3; +} + +bool IsThreadPresentInProcFS(int proc_fd, + const std::string& thread_id_dir_str) { + struct stat task_stat; + const int fstat_ret = + fstatat(proc_fd, thread_id_dir_str.c_str(), &task_stat, 0); + if (fstat_ret < 0) { + PCHECK(ENOENT == errno); + return false; + } + return true; +} + +// Run |cb| in a loop until it returns false. Every time |cb| runs, sleep +// for an exponentially increasing amount of time. |cb| is expected to return +// false very quickly and this will crash if it doesn't happen within ~64ms on +// Debug builds (2s on Release builds). +// This is guaranteed to not sleep more than twice as much as the bare minimum +// amount of time. +void RunWhileTrue(const base::Callback<bool(void)>& cb) { +#if defined(NDEBUG) + // In Release mode, crash after 30 iterations, which means having spent + // roughly 2s in + // nanosleep(2) cumulatively. + const unsigned int kMaxIterations = 30U; +#else + // In practice, this never goes through more than a couple iterations. In + // debug mode, crash after 64ms (+ eventually 25 times the granularity of + // the clock) in nanosleep(2). This ensures that this is not becoming too + // slow. + const unsigned int kMaxIterations = 25U; +#endif + + // Run |cb| with an exponential back-off, sleeping 2^iterations nanoseconds + // in nanosleep(2). + // Note: the clock may not allow for nanosecond granularity, in this case the + // first iterations would sleep a tiny bit more instead, which would not + // change the calculations significantly. + for (unsigned int i = 0; i < kMaxIterations; ++i) { + if (!cb.Run()) { + return; + } + + // Increase the waiting time exponentially. + struct timespec ts = {0, 1L << i /* nanoseconds */}; + PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts))); + } + + LOG(FATAL) << kAssertSingleThreadedError << " (iterations: " << kMaxIterations + << ")"; + + NOTREACHED(); +} + +bool IsMultiThreaded(int proc_fd) { + return !ThreadHelpers::IsSingleThreaded(proc_fd); +} + +} // namespace + +// static +bool ThreadHelpers::IsSingleThreaded(int proc_fd) { + DCHECK_LE(0, proc_fd); + return IsSingleThreadedImpl(proc_fd); +} + +// static +bool ThreadHelpers::IsSingleThreaded() { + base::ScopedFD task_fd(ProcUtil::OpenProc()); + return IsSingleThreaded(task_fd.get()); +} + +// static +void ThreadHelpers::AssertSingleThreaded(int proc_fd) { + DCHECK_LE(0, proc_fd); + const base::Callback<bool(void)> cb = base::Bind(&IsMultiThreaded, proc_fd); + RunWhileTrue(cb); +} + +void ThreadHelpers::AssertSingleThreaded() { + base::ScopedFD task_fd(ProcUtil::OpenProc()); + AssertSingleThreaded(task_fd.get()); +} + +// static +bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_fd, + base::Thread* thread) { + DCHECK_LE(0, proc_fd); + DCHECK(thread); + const base::PlatformThreadId thread_id = thread->thread_id(); + const std::string thread_id_dir_str = + "self/task/" + base::IntToString(thread_id) + "/"; + + // The kernel is at liberty to wake the thread id futex before updating + // /proc. Following Stop(), the thread is joined, but entries in /proc may + // not have been updated. + thread->Stop(); + + const base::Callback<bool(void)> cb = + base::Bind(&IsThreadPresentInProcFS, proc_fd, thread_id_dir_str); + + RunWhileTrue(cb); + + return true; +} + +// static +const char* ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests() { + return kAssertSingleThreadedError; +} + +} // namespace sandbox diff --git a/sandbox/linux/services/thread_helpers.h b/sandbox/linux/services/thread_helpers.h new file mode 100644 index 0000000000..f4abdffd03 --- /dev/null +++ b/sandbox/linux/services/thread_helpers.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ +#define SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace base { class Thread; } + +namespace sandbox { + +class SANDBOX_EXPORT ThreadHelpers { + public: + // Check whether the current process is single threaded. |proc_fd| + // must be a file descriptor to /proc/ and remains owned by the + // caller. + static bool IsSingleThreaded(int proc_fd); + static bool IsSingleThreaded(); + + // Crash if the current process is not single threaded. This will wait + // on /proc to be updated. In the case where this doesn't crash, this will + // return promptly. In the case where this does crash, this will first wait + // for a few ms in Debug mode, a few seconds in Release mode. + static void AssertSingleThreaded(int proc_fd); + static void AssertSingleThreaded(); + + // Stop |thread| and ensure that it does not have an entry in + // /proc/self/task/ from the point of view of the current thread. This is + // the way to stop threads before calling IsSingleThreaded(). + static bool StopThreadAndWatchProcFS(int proc_fd, base::Thread* thread); + + static const char* GetAssertSingleThreadedErrorMessageForTests(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ diff --git a/sandbox/linux/services/thread_helpers_unittests.cc b/sandbox/linux/services/thread_helpers_unittests.cc new file mode 100644 index 0000000000..7357a0cfa7 --- /dev/null +++ b/sandbox/linux/services/thread_helpers_unittests.cc @@ -0,0 +1,147 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/thread_helpers.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_metrics.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "build/build_config.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::PlatformThread; + +namespace sandbox { + +namespace { + +// These tests fail under ThreadSanitizer, see http://crbug.com/342305 +#if !defined(THREAD_SANITIZER) + +int GetRaceTestIterations() { + if (IsRunningOnValgrind()) { + return 2; + } else { + return 1000; + } +} + +class ScopedProc { + public: + ScopedProc() : fd_(-1) { + fd_ = open("/proc/", O_RDONLY | O_DIRECTORY); + CHECK_LE(0, fd_); + } + + ~ScopedProc() { PCHECK(0 == IGNORE_EINTR(close(fd_))); } + + int fd() { return fd_; } + + private: + int fd_; + DISALLOW_COPY_AND_ASSIGN(ScopedProc); +}; + +TEST(ThreadHelpers, IsSingleThreadedBasic) { + ScopedProc proc_fd; + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded()); + + base::Thread thread("sandbox_tests"); + ASSERT_TRUE(thread.Start()); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded()); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.fd(), &thread)); +} + +SANDBOX_TEST(ThreadHelpers, AssertSingleThreaded) { + ScopedProc proc_fd; + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded()); + + ThreadHelpers::AssertSingleThreaded(proc_fd.fd()); + ThreadHelpers::AssertSingleThreaded(); +} + +TEST(ThreadHelpers, IsSingleThreadedIterated) { + ScopedProc proc_fd; + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + + // Iterate to check for race conditions. + for (int i = 0; i < GetRaceTestIterations(); ++i) { + base::Thread thread("sandbox_tests"); + ASSERT_TRUE(thread.Start()); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.fd(), &thread)); + } +} + +TEST(ThreadHelpers, IsSingleThreadedStartAndStop) { + ScopedProc proc_fd; + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + + base::Thread thread("sandbox_tests"); + // This is testing for a race condition, so iterate. + // Manually, this has been tested with more that 1M iterations. + for (int i = 0; i < GetRaceTestIterations(); ++i) { + ASSERT_TRUE(thread.Start()); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(proc_fd.fd(), &thread)); + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(proc_fd.fd())); + ASSERT_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); + } +} + +SANDBOX_TEST(ThreadHelpers, AssertSingleThreadedAfterThreadStopped) { + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded()); + + base::Thread thread1("sandbox_tests"); + base::Thread thread2("sandbox_tests"); + + for (int i = 0; i < GetRaceTestIterations(); ++i) { + SANDBOX_ASSERT(thread1.Start()); + SANDBOX_ASSERT(thread2.Start()); + SANDBOX_ASSERT(!ThreadHelpers::IsSingleThreaded()); + + thread1.Stop(); + thread2.Stop(); + // This will wait on /proc/ to reflect the state of threads in the + // process. + ThreadHelpers::AssertSingleThreaded(); + SANDBOX_ASSERT(ThreadHelpers::IsSingleThreaded()); + } +} + +// Only run this test in Debug mode, where AssertSingleThreaded() will return +// in less than 64ms. +#if !defined(NDEBUG) +SANDBOX_DEATH_TEST( + ThreadHelpers, + AssertSingleThreadedDies, + DEATH_MESSAGE( + ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests())) { + base::Thread thread1("sandbox_tests"); + SANDBOX_ASSERT(thread1.Start()); + ThreadHelpers::AssertSingleThreaded(); +} +#endif // !defined(NDEBUG) + +#endif // !defined(THREAD_SANITIZER) + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/services/yama.cc b/sandbox/linux/services/yama.cc new file mode 100644 index 0000000000..151f4bd340 --- /dev/null +++ b/sandbox/linux/services/yama.cc @@ -0,0 +1,115 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/services/yama.h" + +#include <fcntl.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +#if !defined(PR_SET_PTRACER_ANY) +#define PR_SET_PTRACER_ANY ((unsigned long)-1) +#endif + +#if !defined(PR_SET_PTRACER) +#define PR_SET_PTRACER 0x59616d61 +#endif + +namespace sandbox { + +namespace { + +// Enable or disable the Yama ptracers restrictions. +// Return false if Yama is not present on this kernel. +bool SetYamaPtracersRestriction(bool enable_restrictions) { + unsigned long set_ptracer_arg; + if (enable_restrictions) { + set_ptracer_arg = 0; + } else { + set_ptracer_arg = PR_SET_PTRACER_ANY; + } + + const int ret = prctl(PR_SET_PTRACER, set_ptracer_arg); + const int prctl_errno = errno; + + if (0 == ret) { + return true; + } else { + // ENOSYS or EINVAL means Yama is not in the current kernel. + CHECK(ENOSYS == prctl_errno || EINVAL == prctl_errno); + return false; + } +} + +bool CanAccessProcFS() { + static const char kProcfsKernelSysPath[] = "/proc/sys/kernel/"; + int ret = access(kProcfsKernelSysPath, F_OK); + if (ret) { + return false; + } + return true; +} + +} // namespace + +// static +bool Yama::RestrictPtracersToAncestors() { + return SetYamaPtracersRestriction(true /* enable_restrictions */); +} + +// static +bool Yama::DisableYamaRestrictions() { + return SetYamaPtracersRestriction(false /* enable_restrictions */); +} + +// static +int Yama::GetStatus() { + if (!CanAccessProcFS()) { + return 0; + } + + static const char kPtraceScopePath[] = "/proc/sys/kernel/yama/ptrace_scope"; + + base::ScopedFD yama_scope(HANDLE_EINTR(open(kPtraceScopePath, O_RDONLY))); + + if (!yama_scope.is_valid()) { + const int open_errno = errno; + DCHECK(ENOENT == open_errno); + // The status is known, yama is not present. + return STATUS_KNOWN; + } + + char yama_scope_value = 0; + ssize_t num_read = HANDLE_EINTR(read(yama_scope.get(), &yama_scope_value, 1)); + PCHECK(1 == num_read); + + switch (yama_scope_value) { + case '0': + return STATUS_KNOWN | STATUS_PRESENT; + case '1': + return STATUS_KNOWN | STATUS_PRESENT | STATUS_ENFORCING; + case '2': + case '3': + return STATUS_KNOWN | STATUS_PRESENT | STATUS_ENFORCING | + STATUS_STRICT_ENFORCING; + default: + NOTREACHED(); + return 0; + } +} + +// static +bool Yama::IsPresent() { return GetStatus() & STATUS_PRESENT; } + +// static +bool Yama::IsEnforcing() { return GetStatus() & STATUS_ENFORCING; } + +} // namespace sandbox diff --git a/sandbox/linux/services/yama.h b/sandbox/linux/services/yama.h new file mode 100644 index 0000000000..e6c5c45b2a --- /dev/null +++ b/sandbox/linux/services/yama.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_YAMA_H_ +#define SANDBOX_LINUX_SERVICES_YAMA_H_ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Yama is a LSM kernel module which can restrict ptrace(). +// This class provides ways to detect if Yama is present and enabled +// and to restrict which processes can ptrace the current process. +class SANDBOX_EXPORT Yama { + public: + // This enum should be used to set or check a bitmask. + // A value of 0 would indicate that the status is not known. + enum GlobalStatus { + STATUS_KNOWN = 1 << 0, + STATUS_PRESENT = 1 << 1, + STATUS_ENFORCING = 1 << 2, + // STATUS_STRICT_ENFORCING corresponds to either mode 2 or mode 3 of Yama. + // Ptrace could be entirely denied, or restricted to CAP_SYS_PTRACE + // and PTRACE_TRACEME. + STATUS_STRICT_ENFORCING = 1 << 3 + }; + + // Restrict who can ptrace() the current process to its ancestors. + // If this succeeds, then Yama is available on this kernel. + // However, Yama may not be enforcing at this time. + static bool RestrictPtracersToAncestors(); + + // Disable Yama restrictions for the current process. + // This will fail if Yama is not available on this kernel. + // This is meant for testing only. If you need this, implement + // a per-pid authorization instead. + static bool DisableYamaRestrictions(); + + // Checks if Yama is currently in enforcing mode for the machine (not the + // current process). This requires access to the filesystem and will use + // /proc/sys/kernel/yama/ptrace_scope. + static int GetStatus(); + + // Helper for checking for STATUS_PRESENT in GetStatus(). + static bool IsPresent(); + // Helper for checkking for STATUS_ENFORCING in GetStatus(). + static bool IsEnforcing(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Yama); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_YAMA_H_ diff --git a/sandbox/linux/services/yama_unittests.cc b/sandbox/linux/services/yama_unittests.cc new file mode 100644 index 0000000000..204cfd6a44 --- /dev/null +++ b/sandbox/linux/services/yama_unittests.cc @@ -0,0 +1,172 @@ +// Copyright 2014 The Chromium 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 <errno.h> +#include <fcntl.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/string_util.h" +#include "base/sys_info.h" +#include "sandbox/linux/services/scoped_process.h" +#include "sandbox/linux/services/yama.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool HasLinux32Bug() { +#if defined(__i386__) + // On 3.2 kernels, yama doesn't work for 32-bit binaries on 64-bit kernels. + // This is fixed in 3.4. + bool is_kernel_64bit = + base::SysInfo::OperatingSystemArchitecture() == "x86_64"; + bool is_linux = base::SysInfo::OperatingSystemName() == "Linux"; + bool is_3_dot_2 = base::StartsWithASCII( + base::SysInfo::OperatingSystemVersion(), "3.2", /*case_sensitive=*/false); + if (is_kernel_64bit && is_linux && is_3_dot_2) + return true; +#endif // defined(__i386__) + return false; +} + +bool CanPtrace(pid_t pid) { + int ret; + ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL); + if (ret == -1) { + CHECK_EQ(EPERM, errno); + return false; + } + // Wait for the process to be stopped so that it can be detached. + siginfo_t process_info; + int wait_ret = HANDLE_EINTR(waitid(P_PID, pid, &process_info, WSTOPPED)); + PCHECK(0 == wait_ret); + PCHECK(0 == ptrace(PTRACE_DETACH, pid, NULL, NULL)); + return true; +} + +// _exit(0) if pid can be ptraced by the current process. +// _exit(1) otherwise. +void ExitZeroIfCanPtrace(pid_t pid) { + if (CanPtrace(pid)) { + _exit(0); + } else { + _exit(1); + } +} + +bool CanSubProcessPtrace(pid_t pid) { + ScopedProcess process(base::Bind(&ExitZeroIfCanPtrace, pid)); + bool signaled; + int exit_code = process.WaitForExit(&signaled); + CHECK(!signaled); + return 0 == exit_code; +} + +// The tests below assume that the system-level configuration will not change +// while they run. + +TEST(Yama, GetStatus) { + int status1 = Yama::GetStatus(); + + // Check that the value is a possible bitmask. + ASSERT_LE(0, status1); + ASSERT_GE(Yama::STATUS_KNOWN | Yama::STATUS_PRESENT | Yama::STATUS_ENFORCING | + Yama::STATUS_STRICT_ENFORCING, + status1); + + // The status should not just be a random value. + int status2 = Yama::GetStatus(); + EXPECT_EQ(status1, status2); + + // This test is not running sandboxed, there is no reason to not know the + // status. + EXPECT_NE(0, Yama::STATUS_KNOWN & status1); + + if (status1 & Yama::STATUS_STRICT_ENFORCING) { + // If Yama is strictly enforcing, it is also enforcing. + EXPECT_TRUE(status1 & Yama::STATUS_ENFORCING); + } + + if (status1 & Yama::STATUS_ENFORCING) { + // If Yama is enforcing, Yama is present. + EXPECT_NE(0, status1 & Yama::STATUS_PRESENT); + } + + // Verify that the helper functions work as intended. + EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_ENFORCING), + Yama::IsEnforcing()); + EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_PRESENT), + Yama::IsPresent()); + + fprintf(stdout, + "Yama present: %s - enforcing: %s\n", + Yama::IsPresent() ? "Y" : "N", + Yama::IsEnforcing() ? "Y" : "N"); +} + +SANDBOX_TEST(Yama, RestrictPtraceSucceedsWhenYamaPresent) { + // This call will succeed iff Yama is present. + bool restricted = Yama::RestrictPtracersToAncestors(); + CHECK_EQ(restricted, Yama::IsPresent()); +} + +// Attempts to enable or disable Yama restrictions. +void SetYamaRestrictions(bool enable_restriction) { + if (enable_restriction) { + Yama::RestrictPtracersToAncestors(); + } else { + Yama::DisableYamaRestrictions(); + } +} + +TEST(Yama, RestrictPtraceWorks) { + if (HasLinux32Bug()) + return; + + ScopedProcess process1(base::Bind(&SetYamaRestrictions, true)); + ASSERT_TRUE(process1.WaitForClosureToRun()); + + if (Yama::IsEnforcing()) { + // A sibling process cannot ptrace process1. + ASSERT_FALSE(CanSubProcessPtrace(process1.GetPid())); + } + + if (!(Yama::GetStatus() & Yama::STATUS_STRICT_ENFORCING)) { + // However, parent can ptrace process1. + ASSERT_TRUE(CanPtrace(process1.GetPid())); + + // A sibling can ptrace process2 which disables any Yama protection. + ScopedProcess process2(base::Bind(&SetYamaRestrictions, false)); + ASSERT_TRUE(process2.WaitForClosureToRun()); + ASSERT_TRUE(CanSubProcessPtrace(process2.GetPid())); + } +} + +void DoNothing() {} + +SANDBOX_TEST(Yama, RestrictPtraceIsDefault) { + if (!Yama::IsPresent() || HasLinux32Bug()) + return; + + CHECK(Yama::DisableYamaRestrictions()); + ScopedProcess process1(base::Bind(&DoNothing)); + + if (Yama::IsEnforcing()) { + // Check that process1 is protected by Yama, even though it has + // been created from a process that disabled Yama. + CHECK(!CanSubProcessPtrace(process1.GetPid())); + } +} + +} // namespace + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_channel.cc b/sandbox/linux/syscall_broker/broker_channel.cc new file mode 100644 index 0000000000..fa0f7615fc --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_channel.cc @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/syscall_broker/broker_channel.h" + +#include <sys/socket.h> +#include <sys/types.h> + +#include "base/logging.h" + +namespace sandbox { + +namespace syscall_broker { + +// static +void BrokerChannel::CreatePair(EndPoint* reader, EndPoint* writer) { + DCHECK(reader); + DCHECK(writer); + int socket_pair[2]; + // Use SOCK_SEQPACKET, to preserve message boundaries but we also want to be + // notified (recvmsg should return and not block) when the connection has + // been broken which could mean that the other end has been closed. + PCHECK(0 == socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socket_pair)); + + reader->reset(socket_pair[0]); + PCHECK(0 == shutdown(reader->get(), SHUT_WR)); + + writer->reset(socket_pair[1]); + PCHECK(0 == shutdown(writer->get(), SHUT_RD)); +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_channel.h b/sandbox/linux/syscall_broker/broker_channel.h new file mode 100644 index 0000000000..2abdba413a --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_channel.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CHANNEL_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CHANNEL_H_ + +#include "base/files/scoped_file.h" +#include "base/macros.h" + +namespace sandbox { + +namespace syscall_broker { + +// A small class to create a pipe-like communication channel. It is based on a +// SOCK_SEQPACKET unix socket, which is connection-based and guaranteed to +// preserve message boundaries. +class BrokerChannel { + public: + typedef base::ScopedFD EndPoint; + static void CreatePair(EndPoint* reader, EndPoint* writer); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(BrokerChannel); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CHANNEL_H_ diff --git a/sandbox/linux/syscall_broker/broker_client.cc b/sandbox/linux/syscall_broker/broker_client.cc new file mode 100644 index 0000000000..760cf59b3c --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_client.cc @@ -0,0 +1,144 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/syscall_broker/broker_client.h" + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include "build/build_config.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_policy.h" + +#if defined(OS_ANDROID) && !defined(MSG_CMSG_CLOEXEC) +#define MSG_CMSG_CLOEXEC 0x40000000 +#endif + +namespace sandbox { + +namespace syscall_broker { + +// Make a remote system call over IPC for syscalls that take a path and flags +// as arguments, currently open() and access(). +// Will return -errno like a real system call. +// This function needs to be async signal safe. +int BrokerClient::PathAndFlagsSyscall(IPCCommand syscall_type, + const char* pathname, + int flags) const { + int recvmsg_flags = 0; + RAW_CHECK(syscall_type == COMMAND_OPEN || syscall_type == COMMAND_ACCESS); + if (!pathname) + return -EFAULT; + + // For this "remote system call" to work, we need to handle any flag that + // cannot be sent over a Unix socket in a special way. + // See the comments around kCurrentProcessOpenFlagsMask. + if (syscall_type == COMMAND_OPEN && (flags & kCurrentProcessOpenFlagsMask)) { + // This implementation only knows about O_CLOEXEC, someone needs to look at + // this code if other flags are added. + RAW_CHECK(kCurrentProcessOpenFlagsMask == O_CLOEXEC); + recvmsg_flags |= MSG_CMSG_CLOEXEC; + flags &= ~O_CLOEXEC; + } + + // There is no point in forwarding a request that we know will be denied. + // Of course, the real security check needs to be on the other side of the + // IPC. + if (fast_check_in_client_) { + if (syscall_type == COMMAND_OPEN && + !broker_policy_.GetFileNameIfAllowedToOpen( + pathname, flags, NULL /* file_to_open */, + NULL /* unlink_after_open */)) { + return -broker_policy_.denied_errno(); + } + if (syscall_type == COMMAND_ACCESS && + !broker_policy_.GetFileNameIfAllowedToAccess(pathname, flags, NULL)) { + return -broker_policy_.denied_errno(); + } + } + + base::Pickle write_pickle; + write_pickle.WriteInt(syscall_type); + write_pickle.WriteString(pathname); + write_pickle.WriteInt(flags); + RAW_CHECK(write_pickle.size() <= kMaxMessageLength); + + int returned_fd = -1; + uint8_t reply_buf[kMaxMessageLength]; + + // Send a request (in write_pickle) as well that will include a new + // temporary socketpair (created internally by SendRecvMsg()). + // Then read the reply on this new socketpair in reply_buf and put an + // eventual attached file descriptor in |returned_fd|. + ssize_t msg_len = base::UnixDomainSocket::SendRecvMsgWithFlags( + ipc_channel_.get(), reply_buf, sizeof(reply_buf), recvmsg_flags, + &returned_fd, write_pickle); + if (msg_len <= 0) { + if (!quiet_failures_for_tests_) + RAW_LOG(ERROR, "Could not make request to broker process"); + return -ENOMEM; + } + + base::Pickle read_pickle(reinterpret_cast<char*>(reply_buf), msg_len); + base::PickleIterator iter(read_pickle); + int return_value = -1; + // Now deserialize the return value and eventually return the file + // descriptor. + if (iter.ReadInt(&return_value)) { + switch (syscall_type) { + case COMMAND_ACCESS: + // We should never have a fd to return. + RAW_CHECK(returned_fd == -1); + return return_value; + case COMMAND_OPEN: + if (return_value < 0) { + RAW_CHECK(returned_fd == -1); + return return_value; + } else { + // We have a real file descriptor to return. + RAW_CHECK(returned_fd >= 0); + return returned_fd; + } + default: + RAW_LOG(ERROR, "Unsupported command"); + return -ENOSYS; + } + } else { + RAW_LOG(ERROR, "Could not read pickle"); + NOTREACHED(); + return -ENOMEM; + } +} + +BrokerClient::BrokerClient(const BrokerPolicy& broker_policy, + BrokerChannel::EndPoint ipc_channel, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : broker_policy_(broker_policy), + ipc_channel_(ipc_channel.Pass()), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests) { +} + +BrokerClient::~BrokerClient() { +} + +int BrokerClient::Access(const char* pathname, int mode) const { + return PathAndFlagsSyscall(COMMAND_ACCESS, pathname, mode); +} + +int BrokerClient::Open(const char* pathname, int flags) const { + return PathAndFlagsSyscall(COMMAND_OPEN, pathname, flags); +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_client.h b/sandbox/linux/syscall_broker/broker_client.h new file mode 100644 index 0000000000..2dfef8150c --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_client.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ + +#include "base/macros.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_common.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerPolicy; + +// This class can be embedded in a sandboxed process and can be +// used to perform certain system calls in another, presumably +// non-sandboxed process (which embeds BrokerHost). +// A key feature of this class is the ability to use some of its methods in a +// thread-safe and async-signal safe way. The goal is to be able to use it to +// replace the open() or access() system calls happening anywhere in a process +// (as allowed for instance by seccomp-bpf's SIGSYS mechanism). +class BrokerClient { + public: + // |policy| needs to match the policy used by BrokerHost. This + // allows to predict some of the requests which will be denied + // and save an IPC round trip. + // |ipc_channel| needs to be a suitable SOCK_SEQPACKET unix socket. + // |fast_check_in_client| should be set to true and + // |quiet_failures_for_tests| to false unless you are writing tests. + BrokerClient(const BrokerPolicy& policy, + BrokerChannel::EndPoint ipc_channel, + bool fast_check_in_client, + bool quiet_failures_for_tests); + ~BrokerClient(); + + // Can be used in place of access(). + // X_OK will always return an error in practice since the broker process + // doesn't support execute permissions. + // It's similar to the access() system call and will return -errno on errors. + // This is async signal safe. + int Access(const char* pathname, int mode) const; + // Can be used in place of open(). + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + // It's similar to the open() system call and will return -errno on errors. + // This is async signal safe. + int Open(const char* pathname, int flags) const; + + // Get the file descriptor used for IPC. This is used for tests. + int GetIPCDescriptor() const { return ipc_channel_.get(); } + + private: + const BrokerPolicy& broker_policy_; + const BrokerChannel::EndPoint ipc_channel_; + const bool fast_check_in_client_; // Whether to forward a request that we + // know will be denied to the broker. (Used + // for tests). + const bool quiet_failures_for_tests_; // Disable certain error message when + // testing for failures. + + int PathAndFlagsSyscall(IPCCommand syscall_type, + const char* pathname, + int flags) const; + + DISALLOW_COPY_AND_ASSIGN(BrokerClient); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_CLIENT_H_ diff --git a/sandbox/linux/syscall_broker/broker_file_permission.cc b/sandbox/linux/syscall_broker/broker_file_permission.cc new file mode 100644 index 0000000000..beceda93f5 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_file_permission.cc @@ -0,0 +1,243 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/syscall_broker/broker_file_permission.h" + +#include <fcntl.h> +#include <string.h> + +#include <string> + +#include "base/logging.h" +#include "sandbox/linux/syscall_broker/broker_common.h" + +namespace sandbox { + +namespace syscall_broker { + +// Async signal safe +bool BrokerFilePermission::ValidatePath(const char* path) { + if (!path) + return false; + + const size_t len = strlen(path); + // No empty paths + if (len == 0) + return false; + // Paths must be absolute and not relative + if (path[0] != '/') + return false; + // No trailing / (but "/" is valid) + if (len > 1 && path[len - 1] == '/') + return false; + // No trailing /.. + if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' && + path[len - 1] == '.') + return false; + // No /../ anywhere + for (size_t i = 0; i < len; i++) { + if (path[i] == '/' && (len - i) > 3) { + if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') { + return false; + } + } + } + return true; +} + +// Async signal safe +// Calls std::string::c_str(), strncmp and strlen. All these +// methods are async signal safe in common standard libs. +// TODO(leecam): remove dependency on std::string +bool BrokerFilePermission::MatchPath(const char* requested_filename) const { + const char* path = path_.c_str(); + if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) { + // Note: This prefix match will allow any path under the whitelisted + // path, for any number of directory levels. E.g. if the whitelisted + // path is /good/ then the following will be permitted by the policy. + // /good/file1 + // /good/folder/file2 + // /good/folder/folder2/file3 + // If an attacker could make 'folder' a symlink to ../../ they would have + // access to the entire filesystem. + // Whitelisting with multiple depths is useful, e.g /proc/ but + // the system needs to ensure symlinks can not be created! + // That said if an attacker can convert any of the absolute paths + // to a symlink they can control any file on the system also. + return true; + } else if (strcmp(requested_filename, path) == 0) { + return true; + } + return false; +} + +// Async signal safe. +// External call to std::string::c_str() is +// called in MatchPath. +// TODO(leecam): remove dependency on std::string +bool BrokerFilePermission::CheckAccess(const char* requested_filename, + int mode, + const char** file_to_access) const { + // First, check if |mode| is existence, ability to read or ability + // to write. We do not support X_OK. + if (mode != F_OK && mode & ~(R_OK | W_OK)) { + return false; + } + + if (!ValidatePath(requested_filename)) + return false; + + if (!MatchPath(requested_filename)) { + return false; + } + bool allowed = false; + switch (mode) { + case F_OK: + if (allow_read_ || allow_write_) + allowed = true; + break; + case R_OK: + if (allow_read_) + allowed = true; + break; + case W_OK: + if (allow_write_) + allowed = true; + break; + case R_OK | W_OK: + if (allow_read_ && allow_write_) + allowed = true; + break; + default: + return false; + } + + if (allowed && file_to_access) { + if (!recursive_) + *file_to_access = path_.c_str(); + else + *file_to_access = requested_filename; + } + return allowed; +} + +// Async signal safe. +// External call to std::string::c_str() is +// called in MatchPath. +// TODO(leecam): remove dependency on std::string +bool BrokerFilePermission::CheckOpen(const char* requested_filename, + int flags, + const char** file_to_open, + bool* unlink_after_open) const { + if (!ValidatePath(requested_filename)) + return false; + + if (!MatchPath(requested_filename)) { + return false; + } + + // First, check the access mode is valid. + const int access_mode = flags & O_ACCMODE; + if (access_mode != O_RDONLY && access_mode != O_WRONLY && + access_mode != O_RDWR) { + return false; + } + + // Check if read is allowed + if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) { + return false; + } + + // Check if write is allowed + if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) { + return false; + } + + // Check if file creation is allowed. + if (!allow_create_ && (flags & O_CREAT)) { + return false; + } + + // If O_CREAT is present, ensure O_EXCL + if ((flags & O_CREAT) && !(flags & O_EXCL)) { + return false; + } + + // If this file is to be unlinked, ensure it's created. + if (unlink_ && !(flags & O_CREAT)) { + return false; + } + + // Some flags affect the behavior of the current process. We don't support + // them and don't allow them for now. + if (flags & kCurrentProcessOpenFlagsMask) { + return false; + } + + // Now check that all the flags are known to us. + const int creation_and_status_flags = flags & ~O_ACCMODE; + + const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT | + O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | + O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | + O_SYNC | O_TRUNC; + + const int unknown_flags = ~known_flags; + const bool has_unknown_flags = creation_and_status_flags & unknown_flags; + + if (has_unknown_flags) + return false; + + if (file_to_open) { + if (!recursive_) + *file_to_open = path_.c_str(); + else + *file_to_open = requested_filename; + } + if (unlink_after_open) + *unlink_after_open = unlink_; + + return true; +} + +const char* BrokerFilePermission::GetErrorMessageForTests() { + static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission"; + return kInvalidBrokerFileString; +} + +BrokerFilePermission::BrokerFilePermission(const std::string& path, + bool recursive, + bool unlink, + bool allow_read, + bool allow_write, + bool allow_create) + : path_(path), + recursive_(recursive), + unlink_(unlink), + allow_read_(allow_read), + allow_write_(allow_write), + allow_create_(allow_create) { + // Validate this permission and die if invalid! + + // Must have enough length for a '/' + CHECK(path_.length() > 0) << GetErrorMessageForTests(); + // Whitelisted paths must be absolute. + CHECK(path_[0] == '/') << GetErrorMessageForTests(); + + // Don't allow unlinking on creation without create permission + if (unlink_) { + CHECK(allow_create) << GetErrorMessageForTests(); + } + const char last_char = *(path_.rbegin()); + // Recursive paths must have a trailing slash + if (recursive_) { + CHECK(last_char == '/') << GetErrorMessageForTests(); + } else { + CHECK(last_char != '/') << GetErrorMessageForTests(); + } +} + +} // namespace syscall_broker + +} // namespace sandbox
\ No newline at end of file diff --git a/sandbox/linux/syscall_broker/broker_file_permission.h b/sandbox/linux/syscall_broker/broker_file_permission.h new file mode 100644 index 0000000000..03300d1d74 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_file_permission.h @@ -0,0 +1,119 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ + +#include <string> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +namespace syscall_broker { + +// BrokerFilePermission defines a path for whitelisting. +// Pick the correct static factory method to create a permission. +// CheckOpen and CheckAccess are async signal safe. +// Constuction and Destruction are not async signal safe. +// |path| is the path to be whitelisted. +class SANDBOX_EXPORT BrokerFilePermission { + public: + ~BrokerFilePermission() {} + BrokerFilePermission(const BrokerFilePermission&) = default; + BrokerFilePermission& operator=(const BrokerFilePermission&) = default; + + static BrokerFilePermission ReadOnly(const std::string& path) { + return BrokerFilePermission(path, false, false, true, false, false); + } + + static BrokerFilePermission ReadOnlyRecursive(const std::string& path) { + return BrokerFilePermission(path, true, false, true, false, false); + } + + static BrokerFilePermission WriteOnly(const std::string& path) { + return BrokerFilePermission(path, false, false, false, true, false); + } + + static BrokerFilePermission ReadWrite(const std::string& path) { + return BrokerFilePermission(path, false, false, true, true, false); + } + + static BrokerFilePermission ReadWriteCreate(const std::string& path) { + return BrokerFilePermission(path, false, false, true, true, true); + } + + static BrokerFilePermission ReadWriteCreateUnlink(const std::string& path) { + return BrokerFilePermission(path, false, true, true, true, true); + } + + static BrokerFilePermission ReadWriteCreateUnlinkRecursive( + const std::string& path) { + return BrokerFilePermission(path, true, true, true, true, true); + } + + // Returns true if |requested_filename| is allowed to be opened + // by this permission. + // If |file_to_open| is not NULL it is set to point to either + // the |requested_filename| in the case of a recursive match, + // or a pointer the matched path in the whitelist if an absolute + // match. + // If not NULL |unlink_after_open| is set to point to true if the + // caller should unlink the path after openning. + // Async signal safe if |file_to_open| is NULL. + bool CheckOpen(const char* requested_filename, + int flags, + const char** file_to_open, + bool* unlink_after_open) const; + // Returns true if |requested_filename| is allowed to be accessed + // by this permission as per access(2). + // If |file_to_open| is not NULL it is set to point to either + // the |requested_filename| in the case of a recursive match, + // or a pointer to the matched path in the whitelist if an absolute + // match. + // |mode| is per mode argument of access(2). + // Async signal safe if |file_to_access| is NULL + bool CheckAccess(const char* requested_filename, + int mode, + const char** file_to_access) const; + + private: + friend class BrokerFilePermissionTester; + BrokerFilePermission(const std::string& path, + bool recursive, + bool unlink, + bool allow_read, + bool allow_write, + bool allow_create); + + // ValidatePath checks |path| and returns true if these conditions are met + // * Greater than 0 length + // * Is an absolute path + // * No trailing slash + // * No /../ path traversal + static bool ValidatePath(const char* path); + + // MatchPath returns true if |requested_filename| is covered by this instance + bool MatchPath(const char* requested_filename) const; + + // Used in by BrokerFilePermissionTester for tests. + static const char* GetErrorMessageForTests(); + + // These are not const as std::vector requires copy-assignment and this class + // is stored in vectors. All methods are marked const so + // the compiler will still enforce no changes outside of the constructor. + std::string path_; + bool recursive_; // Allow everything under this path. |path| must be a dir. + bool unlink_; // unlink after opening. + bool allow_read_; + bool allow_write_; + bool allow_create_; +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_
\ No newline at end of file diff --git a/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc new file mode 100644 index 0000000000..b58a901cde --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_file_permission_unittest.cc @@ -0,0 +1,262 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/syscall_broker/broker_file_permission.h" + +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerFilePermissionTester { + public: + static bool ValidatePath(const char* path) { + return BrokerFilePermission::ValidatePath(path); + } + static const char* GetErrorMessage() { + return BrokerFilePermission::GetErrorMessageForTests(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BrokerFilePermissionTester); +}; + +namespace { + +// Creation tests are DEATH tests as a bad permission causes termination. +SANDBOX_TEST(BrokerFilePermission, CreateGood) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +SANDBOX_TEST(BrokerFilePermission, CreateGoodRecursive) { + const char kPath[] = "/tmp/good/"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBad, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = "/tmp/bad/"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBadRecursive, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = "/tmp/bad"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBadNotAbs, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = "tmp/bad"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +SANDBOX_DEATH_TEST( + BrokerFilePermission, + CreateBadEmpty, + DEATH_MESSAGE(BrokerFilePermissionTester::GetErrorMessage())) { + const char kPath[] = ""; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); +} + +// CheckPerm tests |path| against |perm| given |access_flags|. +// If |create| is true then file creation is tested for success. +void CheckPerm(const BrokerFilePermission& perm, + const char* path, + int access_flags, + bool create) { + const char* file_to_open = NULL; + + ASSERT_FALSE(perm.CheckAccess(path, X_OK, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, F_OK, NULL)); + // check bad perms + switch (access_flags) { + case O_RDONLY: + ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL)); + ASSERT_FALSE(perm.CheckAccess(path, W_OK, NULL)); + break; + case O_WRONLY: + ASSERT_FALSE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL)); + ASSERT_FALSE(perm.CheckAccess(path, R_OK, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL)); + break; + case O_RDWR: + ASSERT_TRUE(perm.CheckOpen(path, O_RDONLY, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckOpen(path, O_WRONLY, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckOpen(path, O_RDWR, &file_to_open, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, R_OK, NULL)); + ASSERT_TRUE(perm.CheckAccess(path, W_OK, NULL)); + break; + default: + // Bad test case + NOTREACHED(); + } + +// O_SYNC can be defined as (__O_SYNC|O_DSYNC) +#ifdef O_DSYNC + const int kSyncFlag = O_SYNC & ~O_DSYNC; +#else + const int kSyncFlag = O_SYNC; +#endif + + const int kNumberOfBitsInOAccMode = 2; + static_assert(O_ACCMODE == ((1 << kNumberOfBitsInOAccMode) - 1), + "incorrect number of bits"); + // check every possible flag and act accordingly. + // Skipping AccMode bits as they are present in every case. + for (int i = kNumberOfBitsInOAccMode; i < 32; i++) { + int flag = 1 << i; + switch (flag) { + case O_APPEND: + case O_ASYNC: + case O_DIRECT: + case O_DIRECTORY: +#ifdef O_DSYNC + case O_DSYNC: +#endif + case O_EXCL: + case O_LARGEFILE: + case O_NOATIME: + case O_NOCTTY: + case O_NOFOLLOW: + case O_NONBLOCK: +#if (O_NONBLOCK != O_NDELAY) + case O_NDELAY: +#endif + case kSyncFlag: + case O_TRUNC: + ASSERT_TRUE( + perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL)); + break; + case O_CLOEXEC: + case O_CREAT: + default: + ASSERT_FALSE( + perm.CheckOpen(path, access_flags | flag, &file_to_open, NULL)); + } + } + if (create) { + bool unlink; + ASSERT_TRUE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags, + &file_to_open, &unlink)); + ASSERT_FALSE(unlink); + } else { + ASSERT_FALSE(perm.CheckOpen(path, O_CREAT | O_EXCL | access_flags, + &file_to_open, NULL)); + } +} + +TEST(BrokerFilePermission, ReadOnly) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnly(kPath); + CheckPerm(perm, kPath, O_RDONLY, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadOnlyRecursive) { + const char kPath[] = "/tmp/good/"; + const char kPathFile[] = "/tmp/good/file"; + BrokerFilePermission perm = BrokerFilePermission::ReadOnlyRecursive(kPath); + CheckPerm(perm, kPathFile, O_RDONLY, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, WriteOnly) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::WriteOnly(kPath); + CheckPerm(perm, kPath, O_WRONLY, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadWrite) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadWrite(kPath); + CheckPerm(perm, kPath, O_RDWR, false); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadWriteCreate) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = BrokerFilePermission::ReadWriteCreate(kPath); + CheckPerm(perm, kPath, O_RDWR, true); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +void CheckUnlink(BrokerFilePermission& perm, + const char* path, + int access_flags) { + bool unlink; + ASSERT_FALSE(perm.CheckOpen(path, access_flags, NULL, &unlink)); + ASSERT_FALSE(perm.CheckOpen(path, access_flags | O_CREAT, NULL, &unlink)); + ASSERT_TRUE( + perm.CheckOpen(path, access_flags | O_CREAT | O_EXCL, NULL, &unlink)); + ASSERT_TRUE(unlink); +} + +TEST(BrokerFilePermission, ReadWriteCreateUnlink) { + const char kPath[] = "/tmp/good"; + BrokerFilePermission perm = + BrokerFilePermission::ReadWriteCreateUnlink(kPath); + CheckUnlink(perm, kPath, O_RDWR); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ReadWriteCreateUnlinkRecursive) { + const char kPath[] = "/tmp/good/"; + const char kPathFile[] = "/tmp/good/file"; + BrokerFilePermission perm = + BrokerFilePermission::ReadWriteCreateUnlinkRecursive(kPath); + CheckUnlink(perm, kPathFile, O_RDWR); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerFilePermission, ValidatePath) { + EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/path")); + EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/")); + EXPECT_TRUE(BrokerFilePermissionTester::ValidatePath("/..path")); + + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("bad/")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/..")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/bad/../bad")); + EXPECT_FALSE(BrokerFilePermissionTester::ValidatePath("/../bad")); +} + +} // namespace + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_host.cc b/sandbox/linux/syscall_broker/broker_host.cc new file mode 100644 index 0000000000..830b98bf93 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_host.cc @@ -0,0 +1,231 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/syscall_broker/broker_host.h" + +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "sandbox/linux/syscall_broker/broker_common.h" +#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "third_party/valgrind/valgrind.h" + +namespace sandbox { + +namespace syscall_broker { + +namespace { + +bool IsRunningOnValgrind() { + return RUNNING_ON_VALGRIND; +} + +// A little open(2) wrapper to handle some oddities for us. In the general case +// make a direct system call since we want to keep in control of the broker +// process' system calls profile to be able to loosely sandbox it. +int sys_open(const char* pathname, int flags) { + // Hardcode mode to rw------- when creating files. + int mode; + if (flags & O_CREAT) { + mode = 0600; + } else { + mode = 0; + } + if (IsRunningOnValgrind()) { + // Valgrind does not support AT_FDCWD, just use libc's open() in this case. + return open(pathname, flags, mode); + } else { + return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); + } +} + +// Open |requested_filename| with |flags| if allowed by our policy. +// Write the syscall return value (-errno) to |write_pickle| and append +// a file descriptor to |opened_files| if relevant. +void OpenFileForIPC(const BrokerPolicy& policy, + const std::string& requested_filename, + int flags, + base::Pickle* write_pickle, + std::vector<int>* opened_files) { + DCHECK(write_pickle); + DCHECK(opened_files); + const char* file_to_open = NULL; + bool unlink_after_open = false; + const bool safe_to_open_file = policy.GetFileNameIfAllowedToOpen( + requested_filename.c_str(), flags, &file_to_open, &unlink_after_open); + + if (safe_to_open_file) { + CHECK(file_to_open); + int opened_fd = sys_open(file_to_open, flags); + if (opened_fd < 0) { + write_pickle->WriteInt(-errno); + } else { + // Success. + if (unlink_after_open) { + unlink(file_to_open); + } + opened_files->push_back(opened_fd); + write_pickle->WriteInt(0); + } + } else { + write_pickle->WriteInt(-policy.denied_errno()); + } +} + +// Perform access(2) on |requested_filename| with mode |mode| if allowed by our +// policy. Write the syscall return value (-errno) to |write_pickle|. +void AccessFileForIPC(const BrokerPolicy& policy, + const std::string& requested_filename, + int mode, + base::Pickle* write_pickle) { + DCHECK(write_pickle); + const char* file_to_access = NULL; + const bool safe_to_access_file = policy.GetFileNameIfAllowedToAccess( + requested_filename.c_str(), mode, &file_to_access); + + if (safe_to_access_file) { + CHECK(file_to_access); + int access_ret = access(file_to_access, mode); + int access_errno = errno; + if (!access_ret) + write_pickle->WriteInt(0); + else + write_pickle->WriteInt(-access_errno); + } else { + write_pickle->WriteInt(-policy.denied_errno()); + } +} + +// Handle a |command_type| request contained in |iter| and send the reply +// on |reply_ipc|. +// Currently COMMAND_OPEN and COMMAND_ACCESS are supported. +bool HandleRemoteCommand(const BrokerPolicy& policy, + IPCCommand command_type, + int reply_ipc, + base::PickleIterator iter) { + // Currently all commands have two arguments: filename and flags. + std::string requested_filename; + int flags = 0; + if (!iter.ReadString(&requested_filename) || !iter.ReadInt(&flags)) + return false; + + base::Pickle write_pickle; + std::vector<int> opened_files; + + switch (command_type) { + case COMMAND_ACCESS: + AccessFileForIPC(policy, requested_filename, flags, &write_pickle); + break; + case COMMAND_OPEN: + OpenFileForIPC( + policy, requested_filename, flags, &write_pickle, &opened_files); + break; + default: + LOG(ERROR) << "Invalid IPC command"; + break; + } + + CHECK_LE(write_pickle.size(), kMaxMessageLength); + ssize_t sent = base::UnixDomainSocket::SendMsg( + reply_ipc, write_pickle.data(), write_pickle.size(), opened_files); + + // Close anything we have opened in this process. + for (std::vector<int>::iterator it = opened_files.begin(); + it != opened_files.end(); + ++it) { + int ret = IGNORE_EINTR(close(*it)); + DCHECK(!ret) << "Could not close file descriptor"; + } + + if (sent <= 0) { + LOG(ERROR) << "Could not send IPC reply"; + return false; + } + return true; +} + +} // namespace + +BrokerHost::BrokerHost(const BrokerPolicy& broker_policy, + BrokerChannel::EndPoint ipc_channel) + : broker_policy_(broker_policy), ipc_channel_(ipc_channel.Pass()) { +} + +BrokerHost::~BrokerHost() { +} + +// Handle a request on the IPC channel ipc_channel_. +// A request should have a file descriptor attached on which we will reply and +// that we will then close. +// A request should start with an int that will be used as the command type. +BrokerHost::RequestStatus BrokerHost::HandleRequest() const { + ScopedVector<base::ScopedFD> fds; + char buf[kMaxMessageLength]; + errno = 0; + const ssize_t msg_len = base::UnixDomainSocket::RecvMsg( + ipc_channel_.get(), buf, sizeof(buf), &fds); + + if (msg_len == 0 || (msg_len == -1 && errno == ECONNRESET)) { + // EOF from the client, or the client died, we should die. + return RequestStatus::LOST_CLIENT; + } + + // The client should send exactly one file descriptor, on which we + // will write the reply. + // TODO(mdempsky): ScopedVector doesn't have 'at()', only 'operator[]'. + if (msg_len < 0 || fds.size() != 1 || fds[0]->get() < 0) { + PLOG(ERROR) << "Error reading message from the client"; + return RequestStatus::FAILURE; + } + + base::ScopedFD temporary_ipc(fds[0]->Pass()); + + base::Pickle pickle(buf, msg_len); + base::PickleIterator iter(pickle); + int command_type; + if (iter.ReadInt(&command_type)) { + bool command_handled = false; + // Go through all the possible IPC messages. + switch (command_type) { + case COMMAND_ACCESS: + case COMMAND_OPEN: + // We reply on the file descriptor sent to us via the IPC channel. + command_handled = HandleRemoteCommand( + broker_policy_, static_cast<IPCCommand>(command_type), + temporary_ipc.get(), iter); + break; + default: + NOTREACHED(); + break; + } + + if (command_handled) { + return RequestStatus::SUCCESS; + } else { + return RequestStatus::FAILURE; + } + + NOTREACHED(); + } + + LOG(ERROR) << "Error parsing IPC request"; + return RequestStatus::FAILURE; +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_host.h b/sandbox/linux/syscall_broker/broker_host.h new file mode 100644 index 0000000000..9866507d1c --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_host.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_HOST_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_HOST_H_ + +#include "base/macros.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerPolicy; + +// The BrokerHost class should be embedded in a (presumably not sandboxed) +// process. It will honor IPC requests from a BrokerClient sent over +// |ipc_channel| according to |broker_policy|. +class BrokerHost { + public: + enum class RequestStatus { LOST_CLIENT = 0, SUCCESS, FAILURE }; + + BrokerHost(const BrokerPolicy& broker_policy, + BrokerChannel::EndPoint ipc_channel); + ~BrokerHost(); + + RequestStatus HandleRequest() const; + + private: + const BrokerPolicy& broker_policy_; + const BrokerChannel::EndPoint ipc_channel_; + + DISALLOW_COPY_AND_ASSIGN(BrokerHost); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_HOST_H_ diff --git a/sandbox/linux/syscall_broker/broker_policy.cc b/sandbox/linux/syscall_broker/broker_policy.cc new file mode 100644 index 0000000000..d9f69e3b81 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_policy.cc @@ -0,0 +1,99 @@ +// Copyright 2014 The Chromium 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 "sandbox/linux/syscall_broker/broker_policy.h" + +#include <fcntl.h> +#include <stdint.h> +#include <string.h> + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "sandbox/linux/syscall_broker/broker_common.h" + +namespace sandbox { +namespace syscall_broker { + +BrokerPolicy::BrokerPolicy(int denied_errno, + const std::vector<BrokerFilePermission>& permissions) + : denied_errno_(denied_errno), + permissions_(permissions), + num_of_permissions_(permissions.size()) { + // The spec guarantees vectors store their elements contiguously + // so set up a pointer to array of element so it can be used + // in async signal safe code instead of vector operations. + if (num_of_permissions_ > 0) { + permissions_array_ = &permissions_[0]; + } else { + permissions_array_ = NULL; + } +} + +BrokerPolicy::~BrokerPolicy() { +} + +// Check if calling access() should be allowed on |requested_filename| with +// mode |requested_mode|. +// Note: access() being a system call to check permissions, this can get a bit +// confusing. We're checking if calling access() should even be allowed with +// the same policy we would use for open(). +// If |file_to_access| is not NULL, we will return the matching pointer from +// the whitelist. For paranoia a caller should then use |file_to_access|. See +// GetFileNameIfAllowedToOpen() for more explanation. +// return true if calling access() on this file should be allowed, false +// otherwise. +// Async signal safe if and only if |file_to_access| is NULL. +bool BrokerPolicy::GetFileNameIfAllowedToAccess( + const char* requested_filename, + int requested_mode, + const char** file_to_access) const { + if (file_to_access && *file_to_access) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_access should be NULL"); + return false; + } + for (size_t i = 0; i < num_of_permissions_; i++) { + if (permissions_array_[i].CheckAccess(requested_filename, requested_mode, + file_to_access)) { + return true; + } + } + return false; +} + +// Check if |requested_filename| can be opened with flags |requested_flags|. +// If |file_to_open| is not NULL, we will return the matching pointer from the +// whitelist. For paranoia, a caller should then use |file_to_open| rather +// than |requested_filename|, so that it never attempts to open an +// attacker-controlled file name, even if an attacker managed to fool the +// string comparison mechanism. +// Return true if opening should be allowed, false otherwise. +// Async signal safe if and only if |file_to_open| is NULL. +bool BrokerPolicy::GetFileNameIfAllowedToOpen(const char* requested_filename, + int requested_flags, + const char** file_to_open, + bool* unlink_after_open) const { + if (file_to_open && *file_to_open) { + // Make sure that callers never pass a non-empty string. In case callers + // wrongly forget to check the return value and look at the string + // instead, this could catch bugs. + RAW_LOG(FATAL, "*file_to_open should be NULL"); + return false; + } + for (size_t i = 0; i < num_of_permissions_; i++) { + if (permissions_array_[i].CheckOpen(requested_filename, requested_flags, + file_to_open, unlink_after_open)) { + return true; + } + } + return false; +} + +} // namespace syscall_broker + +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_policy.h b/sandbox/linux/syscall_broker/broker_policy.h new file mode 100644 index 0000000000..d5146edc06 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_policy.h @@ -0,0 +1,87 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ +#define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ + +#include <string> +#include <vector> + +#include "base/macros.h" + +#include "sandbox/linux/syscall_broker/broker_file_permission.h" + +namespace sandbox { +namespace syscall_broker { + +// BrokerPolicy allows to define the security policy enforced by a +// BrokerHost. The BrokerHost will evaluate requests sent over its +// IPC channel according to the BrokerPolicy. +// Some of the methods of this class can be used in an async-signal safe +// way. +class BrokerPolicy { + public: + // |denied_errno| is the error code returned when IPC requests for system + // calls such as open() or access() are denied because a file is not in the + // whitelist. EACCESS would be a typical value. + // |permissions| is a list of BrokerPermission objects that define + // what the broker will allow. + BrokerPolicy(int denied_errno, + const std::vector<BrokerFilePermission>& permissions); + + ~BrokerPolicy(); + + // Check if calling access() should be allowed on |requested_filename| with + // mode |requested_mode|. + // Note: access() being a system call to check permissions, this can get a bit + // confusing. We're checking if calling access() should even be allowed with + // If |file_to_open| is not NULL, a pointer to the path will be returned. + // In the case of a recursive match, this will be the requested_filename, + // otherwise it will return the matching pointer from the + // whitelist. For paranoia a caller should then use |file_to_access|. See + // GetFileNameIfAllowedToOpen() for more explanation. + // return true if calling access() on this file should be allowed, false + // otherwise. + // Async signal safe if and only if |file_to_access| is NULL. + bool GetFileNameIfAllowedToAccess(const char* requested_filename, + int requested_mode, + const char** file_to_access) const; + + // Check if |requested_filename| can be opened with flags |requested_flags|. + // If |file_to_open| is not NULL, a pointer to the path will be returned. + // In the case of a recursive match, this will be the requested_filename, + // otherwise it will return the matching pointer from the + // whitelist. For paranoia, a caller should then use |file_to_open| rather + // than |requested_filename|, so that it never attempts to open an + // attacker-controlled file name, even if an attacker managed to fool the + // string comparison mechanism. + // |unlink_after_open| if not NULL will be set to point to true if the + // policy requests the caller unlink the path after opening. + // Return true if opening should be allowed, false otherwise. + // Async signal safe if and only if |file_to_open| is NULL. + bool GetFileNameIfAllowedToOpen(const char* requested_filename, + int requested_flags, + const char** file_to_open, + bool* unlink_after_open) const; + int denied_errno() const { return denied_errno_; } + + private: + const int denied_errno_; + // The permissions_ vector is used as storage for the BrokerFilePermission + // objects but is not referenced outside of the constructor as + // vectors are unfriendly in async signal safe code. + const std::vector<BrokerFilePermission> permissions_; + // permissions_array_ is set up to point to the backing store of + // permissions_ and is used in async signal safe methods. + const BrokerFilePermission* permissions_array_; + const size_t num_of_permissions_; + + DISALLOW_COPY_AND_ASSIGN(BrokerPolicy); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_POLICY_H_ diff --git a/sandbox/linux/syscall_broker/broker_process.cc b/sandbox/linux/syscall_broker/broker_process.cc new file mode 100644 index 0000000000..81131cc4e0 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_process.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/syscall_broker/broker_process.h" + +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_metrics.h" +#include "build/build_config.h" +#include "sandbox/linux/syscall_broker/broker_channel.h" +#include "sandbox/linux/syscall_broker/broker_client.h" +#include "sandbox/linux/syscall_broker/broker_host.h" + +namespace sandbox { + +namespace syscall_broker { + +BrokerProcess::BrokerProcess( + int denied_errno, + const std::vector<syscall_broker::BrokerFilePermission>& permissions, + bool fast_check_in_client, + bool quiet_failures_for_tests) + : initialized_(false), + fast_check_in_client_(fast_check_in_client), + quiet_failures_for_tests_(quiet_failures_for_tests), + broker_pid_(-1), + policy_(denied_errno, permissions) { +} + +BrokerProcess::~BrokerProcess() { + if (initialized_) { + if (broker_client_.get()) { + // Closing the socket should be enough to notify the child to die, + // unless it has been duplicated. + CloseChannel(); + } + PCHECK(0 == kill(broker_pid_, SIGKILL)); + siginfo_t process_info; + // Reap the child. + int ret = HANDLE_EINTR(waitid(P_PID, broker_pid_, &process_info, WEXITED)); + PCHECK(0 == ret); + } +} + +bool BrokerProcess::Init( + const base::Callback<bool(void)>& broker_process_init_callback) { + CHECK(!initialized_); + BrokerChannel::EndPoint ipc_reader; + BrokerChannel::EndPoint ipc_writer; + BrokerChannel::CreatePair(&ipc_reader, &ipc_writer); + +#if !defined(THREAD_SANITIZER) + DCHECK_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); +#endif + int child_pid = fork(); + if (child_pid == -1) { + return false; + } + if (child_pid) { + // We are the parent and we have just forked our broker process. + ipc_reader.reset(); + broker_pid_ = child_pid; + broker_client_.reset(new BrokerClient(policy_, ipc_writer.Pass(), + fast_check_in_client_, + quiet_failures_for_tests_)); + initialized_ = true; + return true; + } else { + // We are the broker process. Make sure to close the writer's end so that + // we get notified if the client disappears. + ipc_writer.reset(); + CHECK(broker_process_init_callback.Run()); + BrokerHost broker_host(policy_, ipc_reader.Pass()); + for (;;) { + switch (broker_host.HandleRequest()) { + case BrokerHost::RequestStatus::LOST_CLIENT: + _exit(1); + case BrokerHost::RequestStatus::SUCCESS: + case BrokerHost::RequestStatus::FAILURE: + continue; + } + } + _exit(1); + } + NOTREACHED(); + return false; +} + +void BrokerProcess::CloseChannel() { + broker_client_.reset(); +} + +int BrokerProcess::Access(const char* pathname, int mode) const { + RAW_CHECK(initialized_); + return broker_client_->Access(pathname, mode); +} + +int BrokerProcess::Open(const char* pathname, int flags) const { + RAW_CHECK(initialized_); + return broker_client_->Open(pathname, flags); +} + +} // namespace syscall_broker + +} // namespace sandbox. diff --git a/sandbox/linux/syscall_broker/broker_process.h b/sandbox/linux/syscall_broker/broker_process.h new file mode 100644 index 0000000000..8a512a0c12 --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_process.h @@ -0,0 +1,94 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/process/process.h" +#include "sandbox/linux/syscall_broker/broker_policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerClient; +class BrokerFilePermission; + +// Create a new "broker" process to which we can send requests via an IPC +// channel by forking the current process. +// This is a low level IPC mechanism that is suitable to be called from a +// signal handler. +// A process would typically create a broker process before entering +// sandboxing. +// 1. BrokerProcess open_broker(read_whitelist, write_whitelist); +// 2. CHECK(open_broker.Init(NULL)); +// 3. Enable sandbox. +// 4. Use open_broker.Open() to open files. +class SANDBOX_EXPORT BrokerProcess { + public: + // |denied_errno| is the error code returned when methods such as Open() + // or Access() are invoked on a file which is not in the whitelist. EACCESS + // would be a typical value. + // |allowed_r_files| and |allowed_w_files| are white lists of files that can + // be opened later via the Open() API, respectively for reading and writing. + // A file available read-write should be listed in both. + // |fast_check_in_client| and |quiet_failures_for_tests| are reserved for + // unit tests, don't use it. + + BrokerProcess( + int denied_errno, + const std::vector<syscall_broker::BrokerFilePermission>& permissions, + bool fast_check_in_client = true, + bool quiet_failures_for_tests = false); + + ~BrokerProcess(); + // Will initialize the broker process. There should be no threads at this + // point, since we need to fork(). + // broker_process_init_callback will be called in the new broker process, + // after fork() returns. + bool Init(const base::Callback<bool(void)>& broker_process_init_callback); + + // Can be used in place of access(). Will be async signal safe. + // X_OK will always return an error in practice since the broker process + // doesn't support execute permissions. + // It's similar to the access() system call and will return -errno on errors. + int Access(const char* pathname, int mode) const; + // Can be used in place of open(). Will be async signal safe. + // The implementation only supports certain white listed flags and will + // return -EPERM on other flags. + // It's similar to the open() system call and will return -errno on errors. + int Open(const char* pathname, int flags) const; + + int broker_pid() const { return broker_pid_; } + + private: + friend class BrokerProcessTestHelper; + + // Close the IPC channel with the other party. This should only be used + // by tests an none of the class methods should be used afterwards. + void CloseChannel(); + + bool initialized_; // Whether we've been through Init() yet. + const bool fast_check_in_client_; + const bool quiet_failures_for_tests_; + pid_t broker_pid_; // The PID of the broker (child). + syscall_broker::BrokerPolicy policy_; // The sandboxing policy. + scoped_ptr<syscall_broker::BrokerClient> broker_client_; + + DISALLOW_COPY_AND_ASSIGN(BrokerProcess); +}; + +} // namespace syscall_broker + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_BROKER_PROCESS_H_ diff --git a/sandbox/linux/syscall_broker/broker_process_unittest.cc b/sandbox/linux/syscall_broker/broker_process_unittest.cc new file mode 100644 index 0000000000..9ad0e719de --- /dev/null +++ b/sandbox/linux/syscall_broker/broker_process_unittest.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2012 The Chromium 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 "sandbox/linux/syscall_broker/broker_process.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "sandbox/linux/syscall_broker/broker_client.h" +#include "sandbox/linux/tests/scoped_temporary_file.h" +#include "sandbox/linux/tests/test_utils.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace syscall_broker { + +class BrokerProcessTestHelper { + public: + static void CloseChannel(BrokerProcess* broker) { broker->CloseChannel(); } + // Get the client's IPC descriptor to send IPC requests directly. + // TODO(jln): refator tests to get rid of this. + static int GetIPCDescriptor(const BrokerProcess* broker) { + return broker->broker_client_->GetIPCDescriptor(); + } +}; + +namespace { + +bool NoOpCallback() { + return true; +} + +} // namespace + +TEST(BrokerProcess, CreateAndDestroy) { + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly("/proc/cpuinfo")); + + scoped_ptr<BrokerProcess> open_broker(new BrokerProcess(EPERM, permissions)); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + // Destroy the broker and check it has exited properly. + open_broker.reset(); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); +} + +TEST(BrokerProcess, TestOpenAccessNull) { + std::vector<BrokerFilePermission> empty; + BrokerProcess open_broker(EPERM, empty); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + int fd = open_broker.Open(NULL, O_RDONLY); + ASSERT_EQ(fd, -EFAULT); + + int ret = open_broker.Access(NULL, F_OK); + ASSERT_EQ(ret, -EFAULT); +} + +void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { + const char kR_WhiteListed[] = "/proc/DOESNOTEXIST1"; + // We can't debug the init process, and shouldn't be able to access + // its auxv file. + const char kR_WhiteListedButDenied[] = "/proc/1/auxv"; + const char kW_WhiteListed[] = "/proc/DOESNOTEXIST2"; + const char kRW_WhiteListed[] = "/proc/DOESNOTEXIST3"; + const char k_NotWhitelisted[] = "/proc/DOESNOTEXIST4"; + + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kR_WhiteListed)); + permissions.push_back( + BrokerFilePermission::ReadOnly(kR_WhiteListedButDenied)); + permissions.push_back(BrokerFilePermission::WriteOnly(kW_WhiteListed)); + permissions.push_back(BrokerFilePermission::ReadWrite(kRW_WhiteListed)); + + BrokerProcess open_broker(denied_errno, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + int fd = -1; + fd = open_broker.Open(kR_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kR_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(kR_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + int ret = -1; + ret = open_broker.Access(kR_WhiteListed, F_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kR_WhiteListed, R_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kR_WhiteListed, W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListed, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListed, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListed, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + // Android sometimes runs tests as root. + // This part of the test requires a process that doesn't have + // CAP_DAC_OVERRIDE. We check against a root euid as a proxy for that. + if (geteuid()) { + fd = open_broker.Open(kR_WhiteListedButDenied, O_RDONLY); + // The broker process will allow this, but the normal permission system + // won't. + ASSERT_EQ(fd, -EACCES); + fd = open_broker.Open(kR_WhiteListedButDenied, O_WRONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(kR_WhiteListedButDenied, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, F_OK); + // The normal permission system will let us check that the file exists. + ASSERT_EQ(ret, 0); + ret = open_broker.Access(kR_WhiteListedButDenied, R_OK); + ASSERT_EQ(ret, -EACCES); + ret = open_broker.Access(kR_WhiteListedButDenied, W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kR_WhiteListedButDenied, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + } + + fd = open_broker.Open(kW_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(kW_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kW_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, F_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kW_WhiteListed, R_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, W_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kW_WhiteListed, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kW_WhiteListed, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_WRONLY); + ASSERT_EQ(fd, -ENOENT); + fd = open_broker.Open(kRW_WhiteListed, O_RDWR); + ASSERT_EQ(fd, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, F_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, R_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, W_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, R_OK | W_OK); + ASSERT_EQ(ret, -ENOENT); + ret = open_broker.Access(kRW_WhiteListed, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(kRW_WhiteListed, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + fd = open_broker.Open(k_NotWhitelisted, O_RDONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(k_NotWhitelisted, O_WRONLY); + ASSERT_EQ(fd, -denied_errno); + fd = open_broker.Open(k_NotWhitelisted, O_RDWR); + ASSERT_EQ(fd, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, F_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, R_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, R_OK | W_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, X_OK); + ASSERT_EQ(ret, -denied_errno); + ret = open_broker.Access(k_NotWhitelisted, R_OK | X_OK); + ASSERT_EQ(ret, -denied_errno); + + // We have some extra sanity check for clearly wrong values. + fd = open_broker.Open(kRW_WhiteListed, O_RDONLY | O_WRONLY | O_RDWR); + ASSERT_EQ(fd, -denied_errno); + + // It makes no sense to allow O_CREAT in a 2-parameters open. Ensure this + // is denied. + fd = open_broker.Open(kRW_WhiteListed, O_RDWR | O_CREAT); + ASSERT_EQ(fd, -denied_errno); +} + +// Run the same thing twice. The second time, we make sure that no security +// check is performed on the client. +TEST(BrokerProcess, OpenFilePermsWithClientCheck) { + TestOpenFilePerms(true /* fast_check_in_client */, EPERM); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenOpenFilePermsNoClientCheck) { + TestOpenFilePerms(false /* fast_check_in_client */, EPERM); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +// Run the same twice again, but with ENOENT instead of EPERM. +TEST(BrokerProcess, OpenFilePermsWithClientCheckNoEnt) { + TestOpenFilePerms(true /* fast_check_in_client */, ENOENT); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenOpenFilePermsNoClientCheckNoEnt) { + TestOpenFilePerms(false /* fast_check_in_client */, ENOENT); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +void TestBadPaths(bool fast_check_in_client) { + const char kFileCpuInfo[] = "/proc/cpuinfo"; + const char kNotAbsPath[] = "proc/cpuinfo"; + const char kDotDotStart[] = "/../proc/cpuinfo"; + const char kDotDotMiddle[] = "/proc/self/../cpuinfo"; + const char kDotDotEnd[] = "/proc/.."; + const char kTrailingSlash[] = "/proc/"; + + std::vector<BrokerFilePermission> permissions; + + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive("/proc/")); + scoped_ptr<BrokerProcess> open_broker( + new BrokerProcess(EPERM, permissions, fast_check_in_client)); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + ASSERT_GE(cpuinfo_fd, 0); + + int fd = -1; + int can_access; + + can_access = open_broker->Access(kNotAbsPath, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kNotAbsPath, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kDotDotStart, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kDotDotStart, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kDotDotMiddle, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kDotDotMiddle, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kDotDotEnd, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kDotDotEnd, O_RDONLY); + ASSERT_EQ(fd, -EPERM); + + can_access = open_broker->Access(kTrailingSlash, R_OK); + ASSERT_EQ(can_access, -EPERM); + fd = open_broker->Open(kTrailingSlash, O_RDONLY); + ASSERT_EQ(fd, -EPERM); +} + +TEST(BrokerProcess, BadPathsClientCheck) { + TestBadPaths(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, BadPathsNoClientCheck) { + TestBadPaths(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +void TestOpenCpuinfo(bool fast_check_in_client, bool recursive) { + const char kFileCpuInfo[] = "/proc/cpuinfo"; + const char kDirProc[] = "/proc/"; + + std::vector<BrokerFilePermission> permissions; + if (recursive) + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive(kDirProc)); + else + permissions.push_back(BrokerFilePermission::ReadOnly(kFileCpuInfo)); + + scoped_ptr<BrokerProcess> open_broker( + new BrokerProcess(EPERM, permissions, fast_check_in_client)); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + + int fd = -1; + fd = open_broker->Open(kFileCpuInfo, O_RDWR); + base::ScopedFD fd_closer(fd); + ASSERT_EQ(fd, -EPERM); + + // Check we can read /proc/cpuinfo. + int can_access = open_broker->Access(kFileCpuInfo, R_OK); + ASSERT_EQ(can_access, 0); + can_access = open_broker->Access(kFileCpuInfo, W_OK); + ASSERT_EQ(can_access, -EPERM); + // Check we can not write /proc/cpuinfo. + + // Open cpuinfo via the broker. + int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); + ASSERT_GE(cpuinfo_fd, 0); + char buf[3]; + memset(buf, 0, sizeof(buf)); + int read_len1 = read(cpuinfo_fd, buf, sizeof(buf)); + ASSERT_GT(read_len1, 0); + + // Open cpuinfo directly. + int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); + base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2); + ASSERT_GE(cpuinfo_fd2, 0); + char buf2[3]; + memset(buf2, 1, sizeof(buf2)); + int read_len2 = read(cpuinfo_fd2, buf2, sizeof(buf2)); + ASSERT_GT(read_len1, 0); + + // The following is not guaranteed true, but will be in practice. + ASSERT_EQ(read_len1, read_len2); + // Compare the cpuinfo as returned by the broker with the one we opened + // ourselves. + ASSERT_EQ(memcmp(buf, buf2, read_len1), 0); + + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); + open_broker.reset(); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); +} + +// Run this test 4 times. With and without the check in client +// and using a recursive path. +TEST(BrokerProcess, OpenCpuinfoWithClientCheck) { + TestOpenCpuinfo(true /* fast_check_in_client */, false /* not recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoNoClientCheck) { + TestOpenCpuinfo(false /* fast_check_in_client */, false /* not recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoWithClientCheckRecursive) { + TestOpenCpuinfo(true /* fast_check_in_client */, true /* recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenCpuinfoNoClientCheckRecursive) { + TestOpenCpuinfo(false /* fast_check_in_client */, true /* recursive */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenFileRW) { + ScopedTemporaryFile tempfile; + const char* tempfile_name = tempfile.full_file_name(); + + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWrite(tempfile_name)); + + BrokerProcess open_broker(EPERM, permissions); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + // Check we can access that file with read or write. + int can_access = open_broker.Access(tempfile_name, R_OK | W_OK); + ASSERT_EQ(can_access, 0); + + int tempfile2 = -1; + tempfile2 = open_broker.Open(tempfile_name, O_RDWR); + ASSERT_GE(tempfile2, 0); + + // Write to the descriptor opened by the broker. + char test_text[] = "TESTTESTTEST"; + ssize_t len = write(tempfile2, test_text, sizeof(test_text)); + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + + // Read back from the original file descriptor what we wrote through + // the descriptor provided by the broker. + char buf[1024]; + len = read(tempfile.fd(), buf, sizeof(buf)); + + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(test_text))); + ASSERT_EQ(memcmp(test_text, buf, sizeof(test_text)), 0); + + ASSERT_EQ(close(tempfile2), 0); +} + +// SANDBOX_TEST because the process could die with a SIGPIPE +// and we want this to happen in a subprocess. +SANDBOX_TEST(BrokerProcess, BrokerDied) { + const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); + + BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, + true /* quiet_failures_for_tests */); + SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + const pid_t broker_pid = open_broker.broker_pid(); + SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0); + + // Now we check that the broker has been signaled, but do not reap it. + siginfo_t process_info; + SANDBOX_ASSERT(HANDLE_EINTR(waitid( + P_PID, broker_pid, &process_info, WEXITED | WNOWAIT)) == + 0); + SANDBOX_ASSERT(broker_pid == process_info.si_pid); + SANDBOX_ASSERT(CLD_KILLED == process_info.si_code); + SANDBOX_ASSERT(SIGKILL == process_info.si_status); + + // Check that doing Open with a dead broker won't SIGPIPE us. + SANDBOX_ASSERT(open_broker.Open(kCpuInfo, O_RDONLY) == -ENOMEM); + SANDBOX_ASSERT(open_broker.Access(kCpuInfo, O_RDONLY) == -ENOMEM); +} + +void TestOpenComplexFlags(bool fast_check_in_client) { + const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); + + BrokerProcess open_broker(EPERM, permissions, fast_check_in_client); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. + int fd = -1; + int ret = 0; + fd = open_broker.Open(kCpuInfo, O_RDONLY); + ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFL); + ASSERT_NE(-1, ret); + // The descriptor shouldn't have the O_CLOEXEC attribute, nor O_NONBLOCK. + ASSERT_EQ(0, ret & (O_CLOEXEC | O_NONBLOCK)); + ASSERT_EQ(0, close(fd)); + + fd = open_broker.Open(kCpuInfo, O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFD); + ASSERT_NE(-1, ret); + // Important: use F_GETFD, not F_GETFL. The O_CLOEXEC flag in F_GETFL + // is actually not used by the kernel. + ASSERT_TRUE(FD_CLOEXEC & ret); + ASSERT_EQ(0, close(fd)); + + fd = open_broker.Open(kCpuInfo, O_RDONLY | O_NONBLOCK); + ASSERT_GE(fd, 0); + ret = fcntl(fd, F_GETFL); + ASSERT_NE(-1, ret); + ASSERT_TRUE(O_NONBLOCK & ret); + ASSERT_EQ(0, close(fd)); +} + +TEST(BrokerProcess, OpenComplexFlagsWithClientCheck) { + TestOpenComplexFlags(true /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +TEST(BrokerProcess, OpenComplexFlagsNoClientCheck) { + TestOpenComplexFlags(false /* fast_check_in_client */); + // Don't do anything here, so that ASSERT works in the subfunction as + // expected. +} + +// We need to allow noise because the broker will log when it receives our +// bogus IPCs. +SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, RecvMsgDescriptorLeak) { + // Android creates a socket on first use of the LOG call. + // We need to ensure this socket is open before we + // begin the test. + LOG(INFO) << "Ensure Android LOG socket is allocated"; + + // Find the four lowest available file descriptors. + int available_fds[4]; + SANDBOX_ASSERT(0 == pipe(available_fds)); + SANDBOX_ASSERT(0 == pipe(available_fds + 2)); + + // Save one FD to send to the broker later, and close the others. + base::ScopedFD message_fd(available_fds[0]); + for (size_t i = 1; i < arraysize(available_fds); i++) { + SANDBOX_ASSERT(0 == IGNORE_EINTR(close(available_fds[i]))); + } + + // Lower our file descriptor limit to just allow three more file descriptors + // to be allocated. (N.B., RLIMIT_NOFILE doesn't limit the number of file + // descriptors a process can have: it only limits the highest value that can + // be assigned to newly-created descriptors allocated by the process.) + const rlim_t fd_limit = + 1 + + *std::max_element(available_fds, + available_fds + arraysize(available_fds)); + + // Valgrind doesn't allow changing the hard descriptor limit, so we only + // change the soft descriptor limit here. + struct rlimit rlim; + SANDBOX_ASSERT(0 == getrlimit(RLIMIT_NOFILE, &rlim)); + SANDBOX_ASSERT(fd_limit <= rlim.rlim_cur); + rlim.rlim_cur = fd_limit; + SANDBOX_ASSERT(0 == setrlimit(RLIMIT_NOFILE, &rlim)); + + static const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly(kCpuInfo)); + + BrokerProcess open_broker(EPERM, permissions); + SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + + const int ipc_fd = BrokerProcessTestHelper::GetIPCDescriptor(&open_broker); + SANDBOX_ASSERT(ipc_fd >= 0); + + static const char kBogus[] = "not a pickle"; + std::vector<int> fds; + fds.push_back(message_fd.get()); + + // The broker process should only have a couple spare file descriptors + // available, but for good measure we send it fd_limit bogus IPCs anyway. + for (rlim_t i = 0; i < fd_limit; ++i) { + SANDBOX_ASSERT( + base::UnixDomainSocket::SendMsg(ipc_fd, kBogus, sizeof(kBogus), fds)); + } + + const int fd = open_broker.Open(kCpuInfo, O_RDONLY); + SANDBOX_ASSERT(fd >= 0); + SANDBOX_ASSERT(0 == IGNORE_EINTR(close(fd))); +} + +bool CloseFD(int fd) { + PCHECK(0 == IGNORE_EINTR(close(fd))); + return true; +} + +// Return true if the other end of the |reader| pipe was closed, +// false if |timeout_in_seconds| was reached or another event +// or error occured. +bool WaitForClosedPipeWriter(int reader, int timeout_in_ms) { + struct pollfd poll_fd = {reader, POLLIN | POLLRDHUP, 0}; + const int num_events = HANDLE_EINTR(poll(&poll_fd, 1, timeout_in_ms)); + if (1 == num_events && poll_fd.revents | POLLHUP) + return true; + return false; +} + +// Closing the broker client's IPC channel should terminate the broker +// process. +TEST(BrokerProcess, BrokerDiesOnClosedChannel) { + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadOnly("/proc/cpuinfo")); + + // Get the writing end of a pipe into the broker (child) process so + // that we can reliably detect when it dies. + int lifeline_fds[2]; + PCHECK(0 == pipe(lifeline_fds)); + + BrokerProcess open_broker(EPERM, permissions, true /* fast_check_in_client */, + false /* quiet_failures_for_tests */); + ASSERT_TRUE(open_broker.Init(base::Bind(&CloseFD, lifeline_fds[0]))); + // Make sure the writing end only exists in the broker process. + CloseFD(lifeline_fds[1]); + base::ScopedFD reader(lifeline_fds[0]); + + const pid_t broker_pid = open_broker.broker_pid(); + + // This should cause the broker process to exit. + BrokerProcessTestHelper::CloseChannel(&open_broker); + + const int kTimeoutInMilliseconds = 5000; + const bool broker_lifeline_closed = + WaitForClosedPipeWriter(reader.get(), kTimeoutInMilliseconds); + // If the broker exited, its lifeline fd should be closed. + ASSERT_TRUE(broker_lifeline_closed); + // Now check that the broker has exited, but do not reap it. + siginfo_t process_info; + ASSERT_EQ(0, HANDLE_EINTR(waitid(P_PID, broker_pid, &process_info, + WEXITED | WNOWAIT))); + EXPECT_EQ(broker_pid, process_info.si_pid); + EXPECT_EQ(CLD_EXITED, process_info.si_code); + EXPECT_EQ(1, process_info.si_status); +} + +TEST(BrokerProcess, CreateFile) { + std::string temp_str; + { + ScopedTemporaryFile tmp_file; + temp_str = tmp_file.full_file_name(); + } + const char* tempfile_name = temp_str.c_str(); + + std::vector<BrokerFilePermission> permissions; + permissions.push_back(BrokerFilePermission::ReadWriteCreate(tempfile_name)); + + BrokerProcess open_broker(EPERM, permissions); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); + + int fd = -1; + + // Try without O_EXCL + fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT); + ASSERT_EQ(fd, -EPERM); + + const char kTestText[] = "TESTTESTTEST"; + // Create a file + fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + ASSERT_GE(fd, 0); + { + base::ScopedFD scoped_fd(fd); + + // Confirm fail if file exists + int bad_fd = open_broker.Open(tempfile_name, O_RDWR | O_CREAT | O_EXCL); + ASSERT_EQ(bad_fd, -EEXIST); + + // Write to the descriptor opened by the broker. + + ssize_t len = HANDLE_EINTR(write(fd, kTestText, sizeof(kTestText))); + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); + } + + int fd_check = open(tempfile_name, O_RDONLY); + ASSERT_GE(fd_check, 0); + { + base::ScopedFD scoped_fd(fd_check); + char buf[1024]; + ssize_t len = HANDLE_EINTR(read(fd_check, buf, sizeof(buf))); + + ASSERT_EQ(len, static_cast<ssize_t>(sizeof(kTestText))); + ASSERT_EQ(memcmp(kTestText, buf, sizeof(kTestText)), 0); + } +} + +} // namespace syscall_broker + +} // namespace sandbox |