[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