
This allows catching OOB accesses inside `unique_ptr<T[]>` when the size of the allocation is known. The size of the allocation can be known when the unique_ptr has been created with make_unique & friends or when the type necessitates an array cookie before the allocation. This is a re-aplpication of 45a09d181 which had been reverted in f11abac6 due to unrelated CI failures.
167 lines
5.5 KiB
C++
167 lines
5.5 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// REQUIRES: has-unix-headers
|
|
// UNSUPPORTED: c++03, c++11, c++14, c++17
|
|
// UNSUPPORTED: libcpp-hardening-mode=none
|
|
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
|
|
|
|
// <memory>
|
|
//
|
|
// unique_ptr<T[]>
|
|
//
|
|
// T& operator[](std::size_t);
|
|
|
|
// This test ensures that we catch an out-of-bounds access in std::unique_ptr<T[]>::operator[]
|
|
// when unique_ptr has the appropriate ABI configuration.
|
|
|
|
#include <memory>
|
|
#include <cstddef>
|
|
#include <string>
|
|
|
|
#include "check_assertion.h"
|
|
#include "type_algorithms.h"
|
|
|
|
struct MyDeleter {
|
|
MyDeleter() = default;
|
|
|
|
// required to exercise converting move-constructor
|
|
template <class T>
|
|
MyDeleter(std::default_delete<T> const&) {}
|
|
|
|
// required to exercise converting move-assignment
|
|
template <class T>
|
|
MyDeleter& operator=(std::default_delete<T> const&) {
|
|
return *this;
|
|
}
|
|
|
|
template <class T>
|
|
void operator()(T* ptr) const {
|
|
delete[] ptr;
|
|
}
|
|
};
|
|
|
|
template <class WithCookie, class NoCookie>
|
|
void test() {
|
|
// For types with an array cookie, we can always detect OOB accesses.
|
|
{
|
|
// Check with the default deleter
|
|
{
|
|
{
|
|
std::unique_ptr<WithCookie[]> ptr(new WithCookie[5]);
|
|
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
{
|
|
std::unique_ptr<WithCookie[]> ptr = std::make_unique<WithCookie[]>(5);
|
|
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
#if TEST_STD_VER >= 20
|
|
{
|
|
std::unique_ptr<WithCookie[]> ptr = std::make_unique_for_overwrite<WithCookie[]>(5);
|
|
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Check with a custom deleter
|
|
{
|
|
std::unique_ptr<WithCookie[], MyDeleter> ptr(new WithCookie[5]);
|
|
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
}
|
|
|
|
// For types that don't have an array cookie, things are a bit more complicated. We can detect OOB accesses
|
|
// only when the unique_ptr is created via an API where the size is passed down to the library so that we
|
|
// can store it inside the unique_ptr. That requires the appropriate ABI configuration to be enabled.
|
|
//
|
|
// Note that APIs that allow the size to be passed down to the library only support the default deleter
|
|
// as of writing this test.
|
|
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
|
|
{
|
|
{
|
|
std::unique_ptr<NoCookie[]> ptr = std::make_unique<NoCookie[]>(5);
|
|
TEST_LIBCPP_ASSERT_FAILURE(ptr[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
# if TEST_STD_VER >= 20
|
|
{
|
|
std::unique_ptr<NoCookie[]> ptr = std::make_unique_for_overwrite<NoCookie[]>(5);
|
|
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = NoCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
// Make sure that we carry the bounds information properly through conversions, assignments, etc.
|
|
// These tests are mostly relevant when the ABI setting is enabled (with a stateful bounds-checker),
|
|
// but we still run them for types with an array cookie either way.
|
|
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
|
|
using Types = types::type_list<NoCookie, WithCookie>;
|
|
#else
|
|
using Types = types::type_list<WithCookie>;
|
|
#endif
|
|
types::for_each(Types(), []<class T> {
|
|
// Bounds carried through move construction
|
|
{
|
|
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
|
|
std::unique_ptr<T[]> other(std::move(ptr));
|
|
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
|
|
// Bounds carried through move assignment
|
|
{
|
|
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
|
|
std::unique_ptr<T[]> other;
|
|
other = std::move(ptr);
|
|
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
|
|
// Bounds carried through converting move-constructor
|
|
{
|
|
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
|
|
std::unique_ptr<T[], MyDeleter> other(std::move(ptr));
|
|
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
|
|
// Bounds carried through converting move-assignment
|
|
{
|
|
std::unique_ptr<T[]> ptr = std::make_unique<T[]>(5);
|
|
std::unique_ptr<T[], MyDeleter> other;
|
|
other = std::move(ptr);
|
|
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
|
|
}
|
|
});
|
|
}
|
|
|
|
template <std::size_t Size>
|
|
struct NoCookie {
|
|
char padding[Size];
|
|
};
|
|
|
|
template <std::size_t Size>
|
|
struct WithCookie {
|
|
WithCookie() = default;
|
|
WithCookie(WithCookie const&) {}
|
|
WithCookie& operator=(WithCookie const&) { return *this; }
|
|
~WithCookie() {}
|
|
char padding[Size];
|
|
};
|
|
|
|
int main(int, char**) {
|
|
test<WithCookie<1>, NoCookie<1>>();
|
|
test<WithCookie<2>, NoCookie<2>>();
|
|
test<WithCookie<3>, NoCookie<3>>();
|
|
test<WithCookie<4>, NoCookie<4>>();
|
|
test<WithCookie<8>, NoCookie<8>>();
|
|
test<WithCookie<16>, NoCookie<16>>();
|
|
test<WithCookie<32>, NoCookie<32>>();
|
|
test<WithCookie<256>, NoCookie<256>>();
|
|
test<std::string, int>();
|
|
|
|
return 0;
|
|
}
|