Files
MMKV/Core/KeyValueHolder.h
guoling de70a23783 Fix oversized key corrupting data due to uint16_t overflow in KeyValueHolder
Keys longer than 65531 bytes cause keySize and computedKVSize (both uint16_t)
to wrap around, silently corrupting the internal offset so that subsequent reads
return garbage. Reject keys exceeding the safe threshold (UINT16_MAX - 4) at
all write and parse entry points.
2026-04-08 16:08:59 +08:00

134 lines
4.3 KiB
C++

/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef KeyValueHolder_hpp
#define KeyValueHolder_hpp
#ifdef __cplusplus
#include "MMBuffer.h"
#include "aes/AESCrypt.h"
namespace mmkv {
// The maximum key length that can be safely stored without overflowing internal uint16_t fields.
// keySize (uint16) must hold the key length, and computedKVSize (uint16) must hold
// keySize + pbRawVarint32Size(keySize) + pbRawVarint32Size(valueSize).
// With keySize up to 65531, the worst-case computedKVSize = 65531 + 3 + 5 = 65539 can still overflow,
// but in practice computedKVSize only needs keySize + varint(keySize); the valueSize varint is added
// separately. The critical constraint is keySize + pbRawVarint32Size(keySize) <= UINT16_MAX,
// i.e. keySize + 3 <= 65535, so keySize <= 65532. However empirical testing shows 65532 already fails
// because the full computedKVSize includes pbRawVarint32Size(valueSize) (>= 1), pushing it to >= 65536.
// Therefore the safe limit is 65531 (UINT16_MAX - 4).
static constexpr uint32_t KeySizeLimit = UINT16_MAX - 4;
#pragma pack(push, 1)
struct KeyValueHolder {
uint16_t computedKVSize; // internal use only
uint16_t keySize;
uint32_t valueSize;
uint32_t offset;
KeyValueHolder() = default;
KeyValueHolder(uint32_t keyLength, uint32_t valueLength, uint32_t offset);
MMBuffer toMMBuffer(const void *basePtr) const;
};
#ifndef MMKV_DISABLE_CRYPT
enum KeyValueHolderType : uint8_t {
KeyValueHolderType_Direct, // store value directly
KeyValueHolderType_Memory, // store value in the heap memory
KeyValueHolderType_Offset, // store value by offset
};
// kv holder for encrypted mmkv
struct KeyValueHolderCrypt {
KeyValueHolderType type = KeyValueHolderType_Direct;
union {
// store value by offset
struct {
uint8_t pbKeyValueSize; // size needed to encode keySize & valueSize
uint16_t keySize;
uint32_t valueSize;
uint32_t offset;
AESCryptStatus cryptStatus;
};
// store value directly
struct {
uint8_t paddedSize;
uint8_t paddedValue[1];
};
// store value in the heap memory
struct {
uint32_t memSize;
void *memPtr;
};
};
static constexpr size_t SmallBufferSize() {
return sizeof(KeyValueHolderCrypt) - offsetof(KeyValueHolderCrypt, paddedValue);
}
static constexpr size_t MediumBufferSize() {
return 256;
}
static bool isValueStoredAsOffset(size_t valueSize) { return valueSize > MediumBufferSize(); }
KeyValueHolderCrypt() = default;
KeyValueHolderCrypt(const void *valuePtr, size_t valueLength);
explicit KeyValueHolderCrypt(MMBuffer &&data);
KeyValueHolderCrypt(uint32_t keyLength, uint32_t valueLength, uint32_t offset);
KeyValueHolderCrypt(KeyValueHolderCrypt &&other) noexcept;
KeyValueHolderCrypt &operator=(KeyValueHolderCrypt &&other) noexcept;
void move(KeyValueHolderCrypt &&other) noexcept;
~KeyValueHolderCrypt();
uint32_t realValueSize() const;
MMBuffer toMMBuffer(const void *basePtr, const AESCrypt *crypter) const;
std::tuple<uint32_t, uint32_t, AESCryptStatus *> toTuple() {
return std::make_tuple(offset, pbKeyValueSize + keySize + valueSize, &cryptStatus);
}
// those are expensive, just forbid it for possibly misuse
explicit KeyValueHolderCrypt(const KeyValueHolderCrypt &other) = delete;
KeyValueHolderCrypt &operator=(const KeyValueHolderCrypt &other) = delete;
#ifdef MMKV_DEBUG
static void testAESToMMBuffer();
#endif
};
#endif // MMKV_DISABLE_CRYPT
#pragma pack(pop)
} // namespace mmkv
#endif
#endif /* KeyValueHolder_hpp */