Skip to content

Commit 253485d

Browse files
committed
[libc++] Add an ABI setting to harden unique_ptr<T[]>::operator[]
1 parent c21a7c0 commit 253485d

File tree

5 files changed

+183
-7
lines changed

5 files changed

+183
-7
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
set(LIBCXX_HARDENING_MODE "fast" CACHE STRING "")
2-
set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR" CACHE STRING "")
2+
set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR;_LIBCPP_ABI_BOUNDED_UNIQUE_PTR" CACHE STRING "")

libcxx/include/__configuration/abi.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@
150150
// ABI impact: changes the iterator type of `vector` (except `vector<bool>`).
151151
// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
152152

153+
// Tracks the bounds of the array owned by std::unique_ptr<T[]>, allowing it to trap when accessed out-of-bounds.
154+
// #define _LIBCPP_ABI_BOUNDED_UNIQUE_PTR
155+
153156
#if defined(_LIBCPP_COMPILER_CLANG_BASED)
154157
# if defined(__APPLE__)
155158
# if defined(__i386__) || defined(__x86_64__)

libcxx/include/__memory/unique_ptr.h

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <__utility/declval.h>
4141
#include <__utility/forward.h>
4242
#include <__utility/move.h>
43+
#include <__utility/private_constructor_tag.h>
4344
#include <cstddef>
4445

4546
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -283,6 +284,78 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
283284
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT { __ptr_.swap(__u.__ptr_); }
284285
};
285286

287+
// These classes are used to implement bounds checking for unique_ptr<T[]>.
288+
//
289+
// Under the "bounded unique_ptr" ABI, we store the size of the allocation when it is known
290+
// so that we can check it when indexing into the unique_ptr later on. The size of the
291+
// allocation can be known when unique_ptr is created via make_unique or a similar API,
292+
// however it can't be known when constructed from an arbitrary pointer, e.g.
293+
//
294+
// unique_ptr<T[], MyDeleter> ptr(new T[3]);
295+
//
296+
// In that case, we don't know the size of the allocation from within the unique_ptr.
297+
// Semantically, we'd need to store `optional<size_t>`. However, since that is really
298+
// heavy weight, we instead store a size_t and use 0 as a magic value meaning that we
299+
// don't know the size. This means that we can't catch OOB accesses inside a unique_ptr
300+
// with a 0-sized allocation, however since this is a degenerate case, it doesn't matter
301+
// in practice.
302+
//
303+
// As a nice simplification, we don't need to store the size inside the unique_ptr when
304+
// the deleter is std::default_delete<T[]>. Indeed, the compiler already inserts the size
305+
// of the array before the allocation (this is called an array cookie) since that is required
306+
// for the language runtime to implement operator delete[]. In that (very common) case, we
307+
// use the array cookie and avoid storing the size, which makes it possible to perform bounds
308+
// checking without any ABI change.
309+
310+
// Implementation where we don't store any bounds information and we don't attempt to perform
311+
// any bounds checking.
312+
template <class _Pointer>
313+
struct __unique_ptr_array_bounds_none {
314+
_LIBCPP_HIDE_FROM_ABI __unique_ptr_array_bounds_none() = default;
315+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_none(size_t) {}
316+
317+
// Pretend the index is always in-bounds.
318+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool __in_bounds(_Pointer, size_t) const { return true; }
319+
};
320+
321+
// Implementation where we store the size information in the class whenever we have it.
322+
// The special value `0` is used to represent that we don't have any bounds information.
323+
template <class _Pointer>
324+
struct __unique_ptr_array_bounds_local {
325+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __unique_ptr_array_bounds_local() : __size_(0) {}
326+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_local(size_t __size) : __size_(__size) {}
327+
328+
// Check against the size we stored (if any)
329+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR bool __in_bounds(_Pointer, size_t __index) const {
330+
return __size_ == 0 || __index < __size_;
331+
}
332+
333+
private:
334+
size_t __size_;
335+
};
336+
337+
// Implementation where we rely on the array cookie to know the size of the allocation.
338+
// When the deleter is std::default_delete, we never need to store the size inside the unique_ptr
339+
// because the compiler already inserts an array cookie before the array allocation (since that is
340+
// required for operator delete[]).
341+
template <class _Pointer>
342+
struct __unique_ptr_array_bounds_cookie {
343+
_LIBCPP_HIDE_FROM_ABI __unique_ptr_array_bounds_cookie() = default;
344+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_cookie(size_t) {}
345+
346+
// Check against the array cookie
347+
_LIBCPP_HIDE_FROM_ABI bool __in_bounds(_Pointer __ptr, size_t __index) const {
348+
size_t* __cookie = reinterpret_cast<size_t*>(std::__to_address(__ptr)) - 1; // TODO: Use a builtin instead?
349+
return __index < *__cookie;
350+
}
351+
};
352+
353+
template <class _Deleter>
354+
inline constexpr bool __has_array_cookie = false;
355+
356+
template <class _Tp>
357+
inline constexpr bool __has_array_cookie<default_delete<_Tp[]>> = true;
358+
286359
template <class _Tp, class _Dp>
287360
class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> {
288361
public:
@@ -291,8 +364,9 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
291364
typedef typename __pointer<_Tp, deleter_type>::type pointer;
292365

293366
// A unique_ptr contains the following members which may be trivially relocatable:
294-
// - pointer : this may be trivially relocatable, so it's checked
367+
// - pointer: this may be trivially relocatable, so it's checked
295368
// - deleter_type: this may be trivially relocatable, so it's checked
369+
// - (optionally) size: this is trivially relocatable
296370
//
297371
// This unique_ptr implementation only contains a pointer to the unique object and a deleter, so there are no
298372
// references to itself. This means that the entire structure is trivially relocatable if its members are.
@@ -303,6 +377,16 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
303377

304378
private:
305379
__compressed_pair<pointer, deleter_type> __ptr_;
380+
using _BoundsChecker =
381+
__conditional_t<__has_array_cookie<deleter_type>,
382+
__unique_ptr_array_bounds_cookie<pointer>,
383+
#ifdef _LIBCPP_ABI_BOUNDED_UNIQUE_PTR
384+
__unique_ptr_array_bounds_local<pointer>
385+
#else
386+
__unique_ptr_array_bounds_none<pointer>
387+
#endif
388+
>;
389+
_LIBCPP_NO_UNIQUE_ADDRESS _BoundsChecker __checker_;
306390

307391
template <class _From>
308392
struct _CheckArrayPointerConversion : is_same<_From, pointer> {};
@@ -364,6 +448,12 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
364448
_LIBCPP_HIDE_FROM_ABI
365449
_LIBCPP_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(_Pp __p) _NOEXCEPT : __ptr_(__p, __value_init_tag()) {}
366450

451+
// Private constructor used by make_unique & friends to pass the size that was allocated
452+
template <class _Tag, class _Ptr, __enable_if_t<is_same<_Tag, __private_constructor_tag>::value, int> = 0>
453+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(_Tag, _Ptr __ptr, size_t __size) _NOEXCEPT
454+
: __ptr_(__ptr, __value_init_tag()),
455+
__checker_(__size) {}
456+
367457
template <class _Pp,
368458
bool _Dummy = true,
369459
class = _EnableIfDeleterConstructible<_LValRefType<_Dummy> >,
@@ -397,11 +487,13 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
397487
_LIBCPP_HIDE_FROM_ABI unique_ptr(_Pp __p, _BadRValRefType<_Dummy> __d) = delete;
398488

399489
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& __u) _NOEXCEPT
400-
: __ptr_(__u.release(), std::forward<deleter_type>(__u.get_deleter())) {}
490+
: __ptr_(__u.release(), std::forward<deleter_type>(__u.get_deleter())),
491+
__checker_(std::move(__u.__checker_)) {}
401492

402493
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& __u) _NOEXCEPT {
403494
reset(__u.release());
404495
__ptr_.second() = std::forward<deleter_type>(__u.get_deleter());
496+
__checker_ = std::move(__u.__checker_);
405497
return *this;
406498
}
407499

@@ -410,7 +502,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
410502
class = _EnableIfMoveConvertible<unique_ptr<_Up, _Ep>, _Up>,
411503
class = _EnableIfDeleterConvertible<_Ep> >
412504
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT
413-
: __ptr_(__u.release(), std::forward<_Ep>(__u.get_deleter())) {}
505+
: __ptr_(__u.release(), std::forward<_Ep>(__u.get_deleter())),
506+
__checker_(std::move(__u.__checker_)) {}
414507

415508
template <class _Up,
416509
class _Ep,
@@ -419,6 +512,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
419512
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT {
420513
reset(__u.release());
421514
__ptr_.second() = std::forward<_Ep>(__u.get_deleter());
515+
__checker_ = std::move(__u.__checker_);
422516
return *this;
423517
}
424518

@@ -436,6 +530,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
436530
}
437531

438532
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator[](size_t __i) const {
533+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
534+
__checker_.__in_bounds(__ptr_.first(), __i), "unique_ptr<T[]>::operator[](index): index out of range");
439535
return __ptr_.first()[__i];
440536
}
441537
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer get() const _NOEXCEPT { return __ptr_.first(); }
@@ -452,25 +548,31 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
452548
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer release() _NOEXCEPT {
453549
pointer __t = __ptr_.first();
454550
__ptr_.first() = pointer();
551+
__checker_ = _BoundsChecker();
455552
return __t;
456553
}
457554

458555
template <class _Pp, __enable_if_t<_CheckArrayPointerConversion<_Pp>::value, int> = 0>
459556
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(_Pp __p) _NOEXCEPT {
460557
pointer __tmp = __ptr_.first();
461558
__ptr_.first() = __p;
559+
__checker_ = _BoundsChecker();
462560
if (__tmp)
463561
__ptr_.second()(__tmp);
464562
}
465563

466564
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(nullptr_t = nullptr) _NOEXCEPT {
467565
pointer __tmp = __ptr_.first();
468566
__ptr_.first() = nullptr;
567+
__checker_ = _BoundsChecker();
469568
if (__tmp)
470569
__ptr_.second()(__tmp);
471570
}
472571

473-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT { __ptr_.swap(__u.__ptr_); }
572+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT {
573+
__ptr_.swap(__u.__ptr_);
574+
std::swap(__checker_, __u.__checker_);
575+
}
474576
};
475577

476578
template <class _Tp, class _Dp, __enable_if_t<__is_swappable_v<_Dp>, int> = 0>
@@ -626,7 +728,7 @@ template <class _Tp>
626728
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound
627729
make_unique(size_t __n) {
628730
typedef __remove_extent_t<_Tp> _Up;
629-
return unique_ptr<_Tp>(new _Up[__n]());
731+
return unique_ptr<_Tp>(__private_constructor_tag(), new _Up[__n](), __n);
630732
}
631733

632734
template <class _Tp, class... _Args>
@@ -645,7 +747,7 @@ make_unique_for_overwrite() {
645747
template <class _Tp>
646748
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound
647749
make_unique_for_overwrite(size_t __n) {
648-
return unique_ptr<_Tp>(new __remove_extent_t<_Tp>[__n]);
750+
return unique_ptr<_Tp>(__private_constructor_tag(), new __remove_extent_t<_Tp>[__n], __n);
649751
}
650752

651753
template <class _Tp, class... _Args>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// REQUIRES: has-unix-headers
10+
// UNSUPPORTED: c++03, c++11
11+
// UNSUPPORTED: libcpp-hardening-mode=none
12+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
13+
14+
// <memory>
15+
//
16+
// unique_ptr<T[]>
17+
//
18+
// T& operator[](std::size_t);
19+
20+
// This test ensures that we catch an out-of-bounds access in std::unique_ptr<T[]>::operator[]
21+
// when unique_ptr has the appropriate ABI configuration.
22+
23+
#include <memory>
24+
25+
#include "check_assertion.h"
26+
27+
struct MyDeleter {
28+
void operator()(int* ptr) const { delete[] ptr; }
29+
};
30+
31+
int main(int, char**) {
32+
// Check with a default deleter
33+
{
34+
{
35+
std::unique_ptr<int[]> ptr(new int[5]);
36+
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
37+
}
38+
39+
{
40+
std::unique_ptr<int[]> ptr = std::make_unique<int[]>(5);
41+
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
42+
}
43+
44+
#if TEST_STD_VER >= 20
45+
{
46+
std::unique_ptr<int[]> ptr = std::make_unique_for_overwrite<int[]>(5);
47+
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
48+
}
49+
#endif
50+
}
51+
52+
// Check with a non-default deleter
53+
#if defined(_LIBCPP_ABI_BOUNDED_UNIQUE_PTR)
54+
{
55+
{
56+
std::unique_ptr<int[], MyDeleter> ptr = std::make_unique<int[]>(5);
57+
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
58+
}
59+
60+
# if TEST_STD_VER >= 20
61+
{
62+
std::unique_ptr<int[], MyDeleter> ptr = std::make_unique_for_overwrite<int[], MyDeleter>(5);
63+
TEST_LIBCPP_ASSERT_FAILURE(ptr[6] = 42, "unique_ptr<T[]>::operator[](index): index out of range");
64+
}
65+
# endif
66+
}
67+
#endif
68+
69+
return 0;
70+
}

libcxx/utils/libcxx/test/features.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ def _mingwSupportsModules(cfg):
374374
"_LIBCPP_ABI_BOUNDED_ITERATORS": "libcpp-has-abi-bounded-iterators",
375375
"_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING": "libcpp-has-abi-bounded-iterators-in-string",
376376
"_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR": "libcpp-has-abi-bounded-iterators-in-vector",
377+
"_LIBCPP_ABI_BOUNDED_UNIQUE_PTR": "libcpp-has-abi-bounded-unique_ptr",
377378
"_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor",
378379
"_LIBCPP_HAS_NO_FILESYSTEM": "no-filesystem",
379380
"_LIBCPP_HAS_NO_RANDOM_DEVICE": "no-random-device",

0 commit comments

Comments
 (0)