[libcxx-commits] [libcxx] [libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[] (PR #91798)
Konstantin Varlamov via libcxx-commits
libcxx-commits at lists.llvm.org
Sat Oct 5 19:11:13 PDT 2024
================
@@ -0,0 +1,166 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 {
----------------
var-const wrote:
Optional: can we `static_assert` these properties? (it's already a libc++-specific test, so I think it's acceptable to access internal types)
https://github.com/llvm/llvm-project/pull/91798
More information about the libcxx-commits
mailing list