blob: 398f7b2640a9eeea5187be05464eebdac23c25df [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef PARTITION_ALLOC_POOL_OFFSET_FREELIST_H_
#define PARTITION_ALLOC_POOL_OFFSET_FREELIST_H_
#include <cstddef>
#include <cstdint>
#include "partition_alloc/build_config.h"
#include "partition_alloc/buildflags.h"
#include "partition_alloc/partition_address_space.h"
#include "partition_alloc/partition_alloc-inl.h"
#include "partition_alloc/partition_alloc_base/compiler_specific.h"
#include "partition_alloc/partition_alloc_check.h"
#include "partition_alloc/partition_alloc_config.h"
#include "partition_alloc/partition_alloc_constants.h"
#include "partition_alloc/tagging.h"
#if !PA_BUILDFLAG(PA_ARCH_CPU_BIG_ENDIAN)
#include "partition_alloc/reverse_bytes.h"
#endif
#if !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
#error Pool-Offset Freelist is supported only on 64-bit system.
#endif // !PA_BUILDFLAG(HAS_64_BIT_POINTERS)
namespace partition_alloc::internal {
// Defined in "partition_freelist_entry.cc".
[[noreturn]] PA_NOINLINE PA_COMPONENT_EXPORT(
PARTITION_ALLOC) void FreelistCorruptionDetected(size_t slot_size);
using PoolInfo = PartitionAddressSpace::PoolInfo;
class FreelistEntry;
// Encoding to store the entries as pool offsets. In a scenario that an attacker
// has a write primitive anywhere within the pool, they would not be able to
// corrupt the freelist in a way that would allow them to break out of the pool.
class EncodedPoolOffset {
#if PA_BUILDFLAG(PA_ARCH_CPU_BIG_ENDIAN)
static constexpr uintptr_t kEncodeedNullptr = ~uintptr_t{0};
#else
static constexpr uintptr_t kEncodeedNullptr = uintptr_t{0};
#endif
PA_ALWAYS_INLINE constexpr explicit EncodedPoolOffset(std::nullptr_t)
: encoded_(kEncodeedNullptr) {}
PA_ALWAYS_INLINE explicit EncodedPoolOffset(void* ptr)
// The encoded pointer stays MTE-tagged.
: encoded_(Encode(ptr)) {}
// Similar to above, but faster with known pool.
PA_ALWAYS_INLINE explicit EncodedPoolOffset(
void* ptr,
const PoolOffsetLookup& offset_lookup)
: encoded_(Encode(ptr, offset_lookup)) {}
PA_ALWAYS_INLINE constexpr uintptr_t Inverted() const { return ~encoded_; }
PA_ALWAYS_INLINE constexpr void Override(uintptr_t encoded) {
encoded_ = encoded;
}
PA_ALWAYS_INLINE constexpr explicit operator bool() const { return encoded_; }
// Transform() works the same in both directions, so can be used for
// encoding and decoding.
PA_ALWAYS_INLINE static constexpr uintptr_t Transform(uintptr_t offset) {
// We use bswap on little endian as a fast transformation for two reasons:
// 1) The offset is a canonical address, possibly pointing to valid memory,
// whereas, on 64 bit, the swapped offset is very unlikely to be a
// canonical address. Therefore, if an object is freed and its vtable is
// used where the attacker doesn't get the chance to run allocations
// between the free and use, the vtable dereference is likely to fault.
// 2) If the attacker has a linear buffer overflow and elects to try and
// corrupt a freelist pointer, partial pointer overwrite attacks are
// thwarted.
// For big endian, similar guarantees are arrived at with a negation.
#if PA_BUILDFLAG(PA_ARCH_CPU_BIG_ENDIAN)
uintptr_t transformed = ~offset;
#else
uintptr_t transformed = ReverseBytes(offset);
#endif
return transformed;
}
// Determines the containing pool of `ptr` and returns `ptr`
// represented as a tagged offset into that pool.
PA_ALWAYS_INLINE static uintptr_t Encode(void* ptr) {
if (!ptr) {
return kEncodeedNullptr;
}
uintptr_t address = SlotStart::Unchecked(ptr).Untag().value();
PoolInfo pool_info = PartitionAddressSpace::GetPoolInfo(address);
// Save a MTE tag as well as an offset.
uintptr_t tagged_offset =
reinterpret_cast<uintptr_t>(ptr) & (kPtrTagMask | ~pool_info.base_mask);
return Transform(tagged_offset);
}
// Similar to above, but faster with known pool.
PA_ALWAYS_INLINE static uintptr_t Encode(
void* ptr,
const PoolOffsetLookup& offset_lookup) {
if (!ptr) {
return kEncodeedNullptr;
}
// Save a MTE tag as well as an offset.
return Transform(offset_lookup.GetTaggedOffset(ptr));
}
// Given `pool_info`, decodes a `tagged_offset` into a tagged pointer.
PA_ALWAYS_INLINE FreelistEntry* Decode(size_t slot_size) const {
PoolInfo pool_info =
GetPoolInfo(SlotStart::Unchecked(this).Untag().value());
uintptr_t tagged_offset = Transform(encoded_);
// `tagged_offset` must not have bits set in the pool base mask, except MTE
// tag.
if (tagged_offset & pool_info.base_mask & ~kPtrTagMask) {
FreelistCorruptionDetected(slot_size);
}
// We assume `tagged_offset` contains a proper MTE tag.
return reinterpret_cast<FreelistEntry*>(pool_info.base | tagged_offset);
}
// Given `pool_info`, decodes a `tagged_offset` into a tagged pointer.
PA_ALWAYS_INLINE FreelistEntry* Decode(
size_t slot_size,
const PoolOffsetLookup& offset_lookup) const {
uintptr_t tagged_offset = Transform(encoded_);
// `tagged_offset` must not have bits set in the pool base mask, except MTE
// tag.
if (!offset_lookup.IsValidTaggedOffset(tagged_offset)) {
FreelistCorruptionDetected(slot_size);
}
// We assume `tagged_offset` contains a proper MTE tag.
return static_cast<FreelistEntry*>(offset_lookup.GetPointer(tagged_offset));
}
uintptr_t encoded_;
friend FreelistEntry;
};
} // namespace partition_alloc::internal
#endif // PARTITION_ALLOC_POOL_OFFSET_FREELIST_H_