Louis Dionne b3a199469c
[libc++] Properly implement array cookies in the ARM ABI (#160182)
When we implemented array cookie support for hardening std::unique_ptr,
the implementation was only done for the Itanium ABI. I did not
initially realize that ARM was using a different ABI for array cookies,
so unique_ptr should not have been hardened on ARM.

However, we were also incorrectly setting the ABI-detection macro: we
were pretending to be using a vanilla Itanium ABI when in reality the
(similar but different) ARM ABI was in use. As a result, unique_ptr was
using the wrong representation for array cookies on ARM, which
fortunately only mattered in the case of overaligned types.

This patch fixes that.

rdar://160852193
2025-10-16 23:40:58 -04:00

208 lines
7.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"
#include "test_macros.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() {
LIBCPP_STATIC_ASSERT(std::__has_array_cookie<WithCookie>::value);
LIBCPP_STATIC_ASSERT(!std::__has_array_cookie<NoCookie>::value);
// For types with an array cookie, we can always detect OOB accesses. Note that reliance on an array
// cookie is limited to the default deleter, since a unique_ptr with a custom deleter may not have
// been allocated with `new T[n]`.
{
{
std::unique_ptr<WithCookie[]> ptr(new WithCookie[5]);
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
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);
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
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);
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = WithCookie(), "unique_ptr<T[]>::operator[](index): index out of range");
}
#endif
}
// 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);
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
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);
assert(&ptr[1] == ptr.get() + 1); // ensure no assertion
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 only relevant when the ABI setting is enabled (with a stateful bounds-checker).
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
types::for_each(types::type_list<NoCookie, WithCookie>(), []<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));
assert(&other[1] == other.get() + 1); // ensure no assertion
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);
assert(&other[1] == other.get() + 1); // ensure no assertion
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));
assert(&other[1] == other.get() + 1); // ensure no assertion
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);
assert(&other[1] == other.get() + 1); // ensure no assertion
TEST_LIBCPP_ASSERT_FAILURE(other[6], "unique_ptr<T[]>::operator[](index): index out of range");
}
});
#endif
}
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];
};
template <std::size_t Size>
struct alignas(128) OveralignedNoCookie {
char padding[Size];
};
template <std::size_t Size>
struct alignas(128) OveralignedWithCookie {
OveralignedWithCookie() = default;
OveralignedWithCookie(OveralignedWithCookie const&) {}
OveralignedWithCookie& operator=(OveralignedWithCookie const&) { return *this; }
~OveralignedWithCookie() {}
char padding[Size];
};
// These types have a different ABI alignment (alignof) and preferred alignment (__alignof) on some platforms.
// Make sure things work with these types because array cookies can be sensitive to preferred alignment on some
// platforms.
struct WithCookiePreferredAlignment {
WithCookiePreferredAlignment() = default;
WithCookiePreferredAlignment(WithCookiePreferredAlignment const&) {}
WithCookiePreferredAlignment& operator=(WithCookiePreferredAlignment const&) { return *this; }
~WithCookiePreferredAlignment() {}
long double data;
};
struct NoCookiePreferredAlignment {
long double data;
};
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<OveralignedWithCookie<1>, OveralignedNoCookie<1>>();
test<OveralignedWithCookie<2>, OveralignedNoCookie<2>>();
test<OveralignedWithCookie<3>, OveralignedNoCookie<3>>();
test<OveralignedWithCookie<4>, OveralignedNoCookie<4>>();
test<OveralignedWithCookie<8>, OveralignedNoCookie<8>>();
test<OveralignedWithCookie<16>, OveralignedNoCookie<16>>();
test<OveralignedWithCookie<32>, OveralignedNoCookie<32>>();
test<OveralignedWithCookie<256>, OveralignedNoCookie<256>>();
test<std::string, int>();
test<WithCookiePreferredAlignment, NoCookiePreferredAlignment>();
return 0;
}