Fix 32-bit/64-bit compatibility issue 11/318911/1
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Fri, 27 Sep 2024 08:30:59 +0000 (10:30 +0200)
committerKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Thu, 10 Oct 2024 17:12:01 +0000 (19:12 +0200)
Message serialization uses size_t to represent the size of the complete message
On 32-bit and 64-bit architecture this will produce buffers of different sizes:

MessageSerializer::Writer::Writer(size_t size)
{
    m_buffer.reserve(sizeof(size_t) + size);
    Serialization::Serialize(*this, size);
}

Message serialization is used to pack/unpack the binary data stored in the
database. See Store::unpack and Store::pack in SW and TZ backend.

To support both formats regardless of the architecture we have to detect the
format somehow.

* SW backend case

  32-bit (12 bytes):
  4B size(size_t) | 4B Enc. scheme | 4B data size(int) | ...

  64-bit (16 bytes)
  8B size(size_t)                  | 4B Enc. scheme | 4B data size(int) | ...

  Reading 32-bit data as 64-bit -> may get huge size value if encryption
  scheme > 0 leading to overflows.

  Reading 64-bit data as 32-bit -> will get 0 encryption scheme and 0B or 1B
  data size (depending on the scheme) and there will be at least 1B left in the
  buffer.

  To handle it, the packed buffer size must always be written and read as a
  32-bit number. If there is some data left in the buffer after the read, it
  means it's a 64-bit buffer and we have to re-read it using the 64-bit method.

* TZ backend case

  32-bit:
  4B size(size_t) | 4B Enc. scheme | 4B keyId size  | ...

  64-bit
  8B size(size_t)                  | 4B Enc. scheme | 4B keyId size | ...

  To handle it, the packed buffer size must always be written and read as a
  32-bit number. The keyId size is fixed (64B). If after deserialization the
  size is different, we can assume that it's a 64-bit format and we have to
  re-read it using 64-bit method.

Change-Id: I149bcf0691a56ccdbb8ddbe7106cca48f03d5614

src/manager/common/message-buffer.cpp
src/manager/common/message-buffer.h
src/manager/crypto/sw-backend/store.cpp
src/manager/crypto/tz-backend/obj.h
src/manager/crypto/tz-backend/store.cpp
src/manager/crypto/tz-backend/store.h
src/manager/service/crypto-logic.cpp
src/manager/service/crypto-logic.h
unit-tests/test_serialization.cpp

index 4dc5c16411be2781aff2e09a4b057b0986977a67..3bf155761d8c40fce1310c45de4b41e481d9f0e7 100644 (file)
  */
 
 #include <message-buffer.h>
-
-#include <exception.h>
-#include <dpl/log/log.h>
+#include <limits>
 
 namespace CKM {
 
-void MessageBuffer::Push(RawBuffer &&data)
-{
-       m_buffer.Push(std::move(data));
-}
-
-bool MessageBuffer::Ready()
-{
-       CountBytesLeft();
-
-       if (m_bytesLeft == 0)
-               return false;
-
-       if (m_bytesLeft > m_buffer.Size())
-               return false;
-
-       return true;
-}
-
-void MessageBuffer::Read(size_t num, void *bytes)
-{
-       CountBytesLeft();
-
-       if (num > m_bytesLeft) {
-               LogDebug("Protocol broken. OutOfData. Asked for: " << num << " Ready: " <<
-                                m_bytesLeft << " Buffer.size(): " << m_buffer.Size());
-               Throw(Exception::OutOfData);
-       }
-
-       m_buffer.Pop(bytes, num);
-       m_bytesLeft -= num;
-}
-
-void MessageBuffer::Write(size_t, const void *)
-{
-       ThrowErr(Exc::InternalError, "unexpected IStream::Write call in MessageBuffer");
-}
-
 void MessageSerializer::Sizer::Read(size_t, void*)
 {
        ThrowErr(Exc::InternalError, "unexpected IStream::Read call in SerializeMessage");
@@ -79,8 +40,12 @@ void MessageSerializer::Sizer::Write(size_t num, const void*)
 
 MessageSerializer::Writer::Writer(size_t size)
 {
-       m_buffer.reserve(sizeof(size_t) + size);
-       Serialization::Serialize(*this, size);
+       if (size > std::numeric_limits<uint32_t>::max())
+               ThrowErr(Exc::InternalError, "Message too large for serialization");
+
+       uint32_t size32 = size;
+       m_buffer.reserve(sizeof(size32) + size32);
+       Serialization::Serialize(*this, size32);
 }
 
 void MessageSerializer::Writer::Read(size_t, void*)
index 14e4c3fd7a22a35bdb0f3c1b8bf26b4f06c7dd13..3f06bca824eaad9824a67b6e56c6cc7ef30e458d 100644 (file)
 #include <dpl/exception.h>
 #include <dpl/serialization.h>
 #include <dpl/raw-buffer.h>
+#include <dpl/log/log.h>
 #include <symbol-visibility.h>
+#include <cstdint>
+#include <exception.h>
 
 namespace CKM {
 
-class COMMON_API MessageBuffer : public CKM::IStream {
+template <typename SIZE_TYPE>
+class COMMON_API MessageBufferT : public CKM::IStream {
 public:
        class Exception {
        public:
@@ -42,18 +46,43 @@ public:
                DECLARE_EXCEPTION_TYPE(Base, OutOfData)
        };
 
-       MessageBuffer() : m_bytesLeft(0) {}
+       MessageBufferT() : m_bytesLeft(0) {}
 
-       MessageBuffer(MessageBuffer &&) = default;
-       MessageBuffer &operator=(MessageBuffer &&) = default;
+       MessageBufferT(MessageBufferT &&) = default;
+       MessageBufferT &operator=(MessageBufferT &&) = default;
 
-       void Push(RawBuffer &&data);
+       void Push(RawBuffer &&data) {
+               m_buffer.Push(std::move(data));
+       }
+
+       bool Ready() {
+               CountBytesLeft();
+
+               if (m_bytesLeft == 0)
+                       return false;
+
+               if (m_bytesLeft > m_buffer.Size())
+                       return false;
+
+               return true;
+       }
 
-       bool Ready();
+       virtual void Read(size_t num, void *bytes) {
+               CountBytesLeft();
 
-       virtual void Read(size_t num, void *bytes);
+               if (num > m_bytesLeft) {
+                       LogDebug("Protocol broken. OutOfData. Asked for: " << num << " Ready: " <<
+                                        m_bytesLeft << " Buffer.size(): " << m_buffer.Size());
+                       Throw(typename Exception::OutOfData);
+               }
 
-       virtual void Write(size_t num, const void *bytes);
+               m_buffer.Pop(bytes, num);
+               m_bytesLeft -= num;
+       }
+
+       virtual void Write(size_t, const void *) {
+               ThrowErr(Exc::InternalError, "unexpected IStream::Write call in MessageBufferT");
+       }
 
        // generic deserialization
        template <typename... Args>
@@ -62,22 +91,29 @@ public:
                Deserializer<Args...>::Deserialize(*this, args...);
        }
 
+       bool Empty() const {
+               return m_buffer.Size() == 0;
+       }
+
 protected:
        inline void CountBytesLeft()
        {
                if (m_bytesLeft > 0)
                        return;  // we already counted m_bytesLeft nothing to do
 
-               if (m_buffer.Size() < sizeof(size_t))
+               if (m_buffer.Size() < sizeof(SIZE_TYPE))
                        return;  // we cannot count m_bytesLeft because buffer is too small
 
-               m_buffer.Pop(&m_bytesLeft, sizeof(size_t));
+               m_buffer.Pop(&m_bytesLeft, sizeof(SIZE_TYPE));
        }
 
-       size_t m_bytesLeft;
+       SIZE_TYPE m_bytesLeft;
        CKM::BinaryQueue m_buffer;
 };
 
+using MessageBuffer = MessageBufferT<uint32_t>;
+using MessageBuffer64 = MessageBufferT<uint64_t>;
+
 class MessageSerializer final {
        struct COMMON_API Sizer : IStream {
                void Read(size_t num, void* bytes) override;
index 30776f5fc44bece405449eb65b7bead1a63caa45..f26bf4534b88c8dca3a68121b89381be62b0689b 100644 (file)
@@ -172,46 +172,60 @@ RawBuffer Store::pack(const RawBuffer &data, const Password &pass)
 
 RawBuffer Store::unpack(const RawBuffer &packed, const Password &pass)
 {
-       MessageBuffer buffer;
-       buffer.Push(RawBuffer(packed));
        int encryptionScheme = EncryptionScheme::NONE;
        RawBuffer data;
+
+       auto unpackInternal = [&](auto&& internalBuffer){
+               if (encryptionScheme == EncryptionScheme::NONE) {
+                       if (!pass.empty())
+                               ThrowErr(Exc::AuthenticationFailed, "Unexpected custom password.");
+                       return data;
+               }
+
+               internalBuffer.Push(std::move(data));
+               RawBuffer encrypted;
+               RawBuffer iv;
+               RawBuffer tag;
+
+               // serialization exceptions will be catched as CKM::Exception and will cause
+               // CKM_API_ERROR_SERVER_ERROR
+               internalBuffer.Deserialize(encrypted, iv, tag);
+
+               /*
+                * AES GCM will check data integrity and handle cases where:
+                * - wrong password is used
+                * - password is empty when it shouldn't be
+                */
+               RawBuffer key = passwordToKey(pass, iv, Params::DERIVED_KEY_LENGTH);
+
+               RawBuffer ret;
+
+               try {
+                       ret = Crypto::SW::Internals::decryptDataAesGcm(key, encrypted, iv, tag);
+               } catch (const Exc::Crypto::InputParam &e) {
+                       ThrowErr(Exc::AuthenticationFailed, "Decryption with custom password failed, authentication failed");
+               } catch (const Exc::Exception &e) {
+                       ThrowErr(Exc::InternalError, "Decryption with custom password failed, internal error");
+               }
+               return ret;
+       };
+
+       MessageBuffer buffer;
+       buffer.Push(RawBuffer(packed));
        buffer.Deserialize(encryptionScheme, data);
 
-       if (encryptionScheme == EncryptionScheme::NONE) {
-               if (!pass.empty())
-                       ThrowErr(Exc::AuthenticationFailed, "Unexpected custom password.");
-               return data;
-       }
+       // There is some data left -> this must be a buffer encoded using 64-bit size
+       if (!buffer.Empty()) {
+               MessageBuffer64 buffer64;
+               buffer64.Push(RawBuffer(packed));
+               buffer64.Deserialize(encryptionScheme, data);
+               if (!buffer64.Empty())
+                       ThrowErr(Exc::InternalError, "Buffer not fully deserialized");
 
-       MessageBuffer internalBuffer;
-       internalBuffer.Push(std::move(data));
-       RawBuffer encrypted;
-       RawBuffer iv;
-       RawBuffer tag;
-
-       // serialization exceptions will be catched as CKM::Exception and will cause
-       // CKM_API_ERROR_SERVER_ERROR
-       internalBuffer.Deserialize(encrypted, iv, tag);
-
-       /*
-        * AES GCM will check data integrity and handle cases where:
-        * - wrong password is used
-        * - password is empty when it shouldn't be
-        */
-       RawBuffer key = passwordToKey(pass, iv, Params::DERIVED_KEY_LENGTH);
-
-       RawBuffer ret;
-
-       try {
-               ret = Crypto::SW::Internals::decryptDataAesGcm(key, encrypted, iv, tag);
-       } catch (const Exc::Crypto::InputParam &e) {
-               ThrowErr(Exc::AuthenticationFailed, "Decryption with custom password failed, authentication failed");
-       } catch (const Exc::Exception &e) {
-               ThrowErr(Exc::InternalError, "Decryption with custom password failed, internal error");
+               return unpackInternal(MessageBuffer64());
        }
 
-       return ret;
+       return unpackInternal(MessageBuffer());
 }
 
 } // namespace SW
index 790b66d4dbbbf73d894f1114b159cddb9622f7c6..72a4495b2285917bdf9b537bdc0cd39c91a814e8 100644 (file)
@@ -79,10 +79,6 @@ public:
        {
                return m_id;
        }
-       virtual int getScheme() const
-       {
-               return m_scheme;
-       }
        virtual const Pwd& getPassword() const
        {
                return m_password;
index 87360f5c7b9cb87e52f4db3319bc36c2b7d65b0f..5dcecba7c226cbb3c307164b20d012dc94941b38 100644 (file)
@@ -26,6 +26,7 @@
 #include <tz-backend/internals.h>
 
 #include <message-buffer.h>
+#include <crypto-logic.h>
 
 namespace CKM {
 namespace Crypto {
@@ -189,19 +190,30 @@ RawBuffer Store::pack(const RawBuffer &keyId,
 void Store::unpack(const RawBuffer &packed,
                                   const Password& password,
                                   int &scheme,
-                                  RawBuffer &data,
+                                  RawBuffer &keyId,
                                   RawBuffer &iv,
                                   RawBuffer &tag)
 {
        MessageBuffer buffer;
        buffer.Push(RawBuffer(packed));
 
-       buffer.Deserialize(scheme);
+       buffer.Deserialize(scheme, keyId);
 
-       if (scheme == EncryptionScheme::PASSWORD) {
-               buffer.Deserialize(data, iv, tag);
+       // Hash has invalid length -> this must be a buffer encoded using 64-bit size
+       if (keyId.size() != CryptoLogic::HASH_SIZE) {
+               MessageBuffer64 buffer64;
+               buffer64.Push(RawBuffer(packed));
+
+               buffer64.Deserialize(scheme, keyId);
+
+               if (keyId.size() != CryptoLogic::HASH_SIZE)
+                       ThrowErr(Exc::InternalError, "Error during deserialization");
+
+               if (scheme == EncryptionScheme::PASSWORD)
+                       buffer64.Deserialize(iv, tag);
        } else {
-               buffer.Deserialize(data);
+               if (scheme == EncryptionScheme::PASSWORD)
+                       buffer.Deserialize(iv, tag);
        }
 
        if ((scheme & EncryptionScheme::PASSWORD) && password.empty()) {
index 67b480ce5aa3f25c42203e634c692b93f8622fce..bbcbeeb405918c9146b67c51a579805d07293402 100644 (file)
@@ -52,7 +52,7 @@ public:
        static void unpack(const RawBuffer &packed,
                                           const Password& password,
                                           int &scheme,
-                                          RawBuffer &data,
+                                          RawBuffer &keyId,
                                           RawBuffer &iv,
                                           RawBuffer &tag);
        size_t maxChunkSize() const override;
index a0dd9fbe738c8a308efdab7d72454a8cf6cb08ba..263de4a6017497f04e3d6f9666e94f9c78ae6a55 100644 (file)
@@ -45,6 +45,8 @@
 
 namespace CKM {
 
+const size_t CryptoLogic::HASH_SIZE = SHA512_DIGEST_LENGTH;
+
 namespace {
 
 const static int AES_CBC_KEY_SIZE = 32;
@@ -52,7 +54,7 @@ const static int AES_GCM_TAG_SIZE = 16;
 
 RawBuffer makeHashInternal(const std::string& message)
 {
-       RawBuffer digest(SHA512_DIGEST_LENGTH);
+       RawBuffer digest(CryptoLogic::HASH_SIZE);
        auto msg_ptr = reinterpret_cast<const unsigned char*>(message.data());
 
        if (!SHA512(msg_ptr, message.length(), digest.data()))
index 61f5f36600c145efaa02b6571ecc4527e64fab5d..ddaa56d95e25716fc1506eaf5f1097c5c1fc8687 100644 (file)
@@ -73,6 +73,8 @@ public:
         */
        static const int ENCRYPTION_V2 = 1;
 
+       static const size_t HASH_SIZE;
+
 private:
        // Encryption scheme flags (enable/disable specific encryption type, multiple choice)
        static const int ENCR_BASE64   = 1 << 0;
index 2d298b16452f5a15d910b944a98e5a3b771946c3..546e82a81dd5623455d85931e043775606fb9b13 100644 (file)
 #include <ckm/ckm-raw-buffer.h>
 #include <protocols.h>
 #include <message-buffer.h>
+#include <sw-backend/store.h>
 
 using namespace CKM;
 
 namespace {
 std::string IV_STR("1234567890123456");
 std::string AAD_STR("sdfdsgsghrtkghwiuho3irhfoewituhre");
+std::string DATA_STR("test data");
 RawBuffer IV(IV_STR.begin(), IV_STR.end());
 RawBuffer AAD(AAD_STR.begin(), AAD_STR.end());
+RawBuffer DATA(DATA_STR.begin(), DATA_STR.end());
+const Password PASSWORD = "password";
 
 void checkIntParam(const CryptoAlgorithm &algo, ParamName name,
                                   uint64_t expected)
@@ -82,6 +86,39 @@ void setParam(CryptoAlgorithm &algo, ParamName name, const T &value,
                                                  " should " << (success ? "succeed" : "fail"));
 }
 
+// This function mimics the serialization on a 64-bit system prior to the fix
+RawBuffer pack64(const RawBuffer& data)
+{
+       struct Writer64 : public IStream {
+               explicit Writer64(uint64_t size) {
+                       m_buffer.reserve(sizeof(size) + size);
+                       Serialization::Serialize(*this, size);
+               }
+
+               void Read(size_t, void*) override {
+                       BOOST_FAIL("Unexpected Read call in Writer.");
+               }
+
+               void Write(size_t num, const void* bytes) override {
+                       BOOST_REQUIRE(m_buffer.size() + num <= m_buffer.capacity());
+                       m_buffer.resize(m_buffer.size() + num);
+                       memcpy(&m_buffer[m_buffer.size() - num], bytes, num);
+               }
+
+               RawBuffer m_buffer;
+       };
+
+       int scheme = 0;
+       int dataSize = data.size();
+       uint64_t size64 = sizeof(scheme) + sizeof(dataSize) + data.size();
+       Writer64 writer(sizeof(size64) + size64);
+       Serialization::Serialize(writer, scheme);
+       Serialization::Serialize(writer, data);
+       BOOST_REQUIRE(writer.m_buffer.size() == sizeof(size64) + size64);
+
+       return writer.m_buffer;
+}
+
 } // namespace anonymous
 
 BOOST_AUTO_TEST_SUITE(SERIALIZATION_TEST)
@@ -197,4 +234,42 @@ POSITIVE_TEST_CASE(Serialization_AliasInfoVector)
        BOOST_REQUIRE(o[1].backend == v[1].backend);
 }
 
+POSITIVE_TEST_CASE(Serialization_32_vs_64_bit)
+{
+       using Crypto::SW::Store;
+
+       // pack 32 -> unpack
+       auto packed = Store::pack(DATA, PASSWORD);
+       auto unpacked = Store::unpack(packed, PASSWORD);
+       BOOST_REQUIRE(DATA == unpacked);
+
+       packed = Store::pack(DATA, "");
+       unpacked = Store::unpack(packed, "");
+       BOOST_REQUIRE(DATA == unpacked);
+
+       // pack 64 -> unpack
+       packed = pack64(DATA);
+       unpacked = Store::unpack(packed, "");
+       BOOST_REQUIRE(DATA == unpacked);
+}
+
+NEGATIVE_TEST_CASE(Serialization_32_vs_64_bit)
+{
+       using Crypto::SW::Store;
+
+       // pack 32 -> unpack
+       auto packed = Store::pack(DATA, PASSWORD);
+
+       BOOST_REQUIRE_THROW(Store::unpack(packed, "wrong-password"), Exc::AuthenticationFailed);
+       BOOST_REQUIRE_THROW(Store::unpack(packed, ""), Exc::AuthenticationFailed);
+
+       packed = Store::pack(DATA, "");
+
+       BOOST_REQUIRE_THROW(Store::unpack(packed, PASSWORD), Exc::AuthenticationFailed);
+
+       // pack 64 -> unpack
+       packed = pack64(DATA);
+       BOOST_REQUIRE_THROW(Store::unpack(packed, PASSWORD), Exc::AuthenticationFailed);
+}
+
 BOOST_AUTO_TEST_SUITE_END()