Skip to content

Commit 4a453cf

Browse files
committed
[libc++] Harden unique_ptr<T[]>::operator[] when we can
This patch adds an ABI configuration that allows bounds-checking in unique_ptr<T[]>::operator[] when it has been constructed with bounds information in the API. The patch also adds support for bounds-checking when an array cookie is known to exist, which allows validating bounds without even changing the ABI. Drive-by changes: - Improve the tests for `operator[]` - Improve the tests for `.get()` - Add a test for incomplete types support
1 parent 85561dd commit 4a453cf

File tree

10 files changed

+597
-59
lines changed

10 files changed

+597
-59
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/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ set(files
528528
__memory/allocator_arg_t.h
529529
__memory/allocator_destructor.h
530530
__memory/allocator_traits.h
531+
__memory/array_cookie.h
531532
__memory/assume_aligned.h
532533
__memory/auto_ptr.h
533534
__memory/builtin_new_allocator.h

libcxx/include/__configuration/abi.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,13 @@
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+
// Note that limited bounds checking is also available outside of this ABI configuration, but only some categories
155+
// of types can be checked.
156+
//
157+
// ABI impact: This causes the layout of std::unique_ptr<T[]> to change and its size to increase.
158+
// #define _LIBCPP_ABI_BOUNDED_UNIQUE_PTR
159+
153160
#if defined(_LIBCPP_COMPILER_CLANG_BASED)
154161
# if defined(__APPLE__)
155162
# if defined(__i386__) || defined(__x86_64__)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP___MEMORY_ARRAY_COOKIE_H
11+
#define _LIBCPP___MEMORY_ARRAY_COOKIE_H
12+
13+
#include <__config>
14+
15+
#include <__configuration/abi.h>
16+
#include <__type_traits/integral_constant.h>
17+
#include <__type_traits/is_trivially_destructible.h>
18+
#include <__type_traits/negation.h>
19+
#include <cstddef>
20+
21+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
22+
# pragma GCC system_header
23+
#endif
24+
25+
_LIBCPP_BEGIN_NAMESPACE_STD
26+
27+
// Trait representing whether a type requires an array cookie at the start of its allocation when
28+
// allocated as `new T[n]` and deallocated as `delete array`.
29+
//
30+
// Under the Itanium C++ ABI [1], we know that an array cookie is available unless `T` is trivially
31+
// destructible and the call to `operator delete[]` is not a sized operator delete. Under ABIs other
32+
// than the Itanium ABI, we assume there are no array cookies.
33+
//
34+
// [1]: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#array-cookies
35+
#ifdef _LIBCPP_ABI_ITANIUM
36+
// TODO: Use a builtin instead
37+
// TODO: We should factor in the choice of the usual deallocation function in this determination.
38+
template <class _Tp>
39+
struct __has_array_cookie : _Not<is_trivially_destructible<_Tp> > {};
40+
#else
41+
template <class _Tp>
42+
struct __has_array_cookie : false_type {};
43+
#endif
44+
45+
template <class _Tp>
46+
_LIBCPP_HIDE_FROM_ABI size_t __get_array_cookie(_Tp const* __ptr) {
47+
static_assert(
48+
__has_array_cookie<_Tp>::value, "Trying to access the array cookie of a type that is not guaranteed to have one");
49+
size_t const* __cookie = reinterpret_cast<size_t const*>(__ptr) - 1; // TODO: Use a builtin instead
50+
return *__cookie;
51+
}
52+
53+
_LIBCPP_END_NAMESPACE_STD
54+
55+
#endif // _LIBCPP___MEMORY_ARRAY_COOKIE_H

libcxx/include/__memory/unique_ptr.h

Lines changed: 117 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
#include <__functional/hash.h>
1818
#include <__functional/operations.h>
1919
#include <__memory/allocator_traits.h> // __pointer
20+
#include <__memory/array_cookie.h>
2021
#include <__memory/auto_ptr.h>
2122
#include <__memory/compressed_pair.h>
23+
#include <__memory/pointer_traits.h>
2224
#include <__type_traits/add_lvalue_reference.h>
2325
#include <__type_traits/common_type.h>
2426
#include <__type_traits/conditional.h>
@@ -40,6 +42,8 @@
4042
#include <__utility/declval.h>
4143
#include <__utility/forward.h>
4244
#include <__utility/move.h>
45+
#include <__utility/private_constructor_tag.h>
46+
#include <climits>
4347
#include <cstddef>
4448

4549
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -283,6 +287,85 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
283287
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT { __ptr_.swap(__u.__ptr_); }
284288
};
285289

290+
// Bounds checking in unique_ptr<T[]>
291+
// ==================================
292+
//
293+
// We provide some helper classes that allow bounds checking when accessing a unique_ptr<T[]>.
294+
// There are a few cases where bounds checking can be implemented:
295+
//
296+
// 1. When an array cookie (see [1]) exists at the beginning of the array allocation, we are
297+
// able to reuse that cookie to extract the size of the array and perform bounds checking.
298+
// An array cookie is a size inserted at the beginning of the allocation by the compiler.
299+
// That size is inserted implicitly when doing `new T[n]` in some cases, and its purpose
300+
// is to allow the runtime to destroy the `n` array elements when doing `delete array`.
301+
// When we are able to use array cookies, we reuse information already available in the
302+
// current runtime, so bounds checking does not require changing libc++'s ABI.
303+
//
304+
// 2. When the "bounded unique_ptr" ABI configuration (controlled by `_LIBCPP_ABI_BOUNDED_UNIQUE_PTR`)
305+
// is enabled, we store the size of the allocation (when it is known) so we can check it when
306+
// indexing into the `unique_ptr`. That changes the layout of `std::unique_ptr<T[]>`, which is
307+
// an ABI break from the default configuration.
308+
//
309+
// Note that even under this ABI configuration, we can't always know the size of the unique_ptr.
310+
// Indeed, the size of the allocation can only be known when the unique_ptr is created via
311+
// make_unique or a similar API. For example, it can't be known when constructed from an arbitrary
312+
// pointer, in which case we are not able to check the bounds on access:
313+
//
314+
// unique_ptr<T[], MyDeleter> ptr(new T[3]);
315+
//
316+
// When we don't know the size of the allocation via the API used to create the unique_ptr, we
317+
// try to fall back to using an array cookie when available.
318+
//
319+
// Finally, note that when this ABI configuration is enabled, we have no choice but to always
320+
// make space for a size to be stored in the unique_ptr. Indeed, while we might want to avoid
321+
// storing the size when an array cookie is available, knowing whether an array cookie is available
322+
// requires the type stored in the unique_ptr to be complete, while unique_ptr can normally
323+
// accommodate incomplete types.
324+
//
325+
// (1) Implementation where we rely on the array cookie to know the size of the allocation, if
326+
// an array cookie exists.
327+
struct __unique_ptr_array_bounds_stateless {
328+
_LIBCPP_HIDE_FROM_ABI __unique_ptr_array_bounds_stateless() = default;
329+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_stateless(size_t) {}
330+
331+
template <class _Tp, __enable_if_t<__has_array_cookie<_Tp>::value, int> = 0>
332+
_LIBCPP_HIDE_FROM_ABI bool __in_bounds(_Tp* __ptr, size_t __index) const {
333+
size_t __cookie = std::__get_array_cookie(__ptr);
334+
return __index < __cookie;
335+
}
336+
337+
template <class _Tp, __enable_if_t<!__has_array_cookie<_Tp>::value, int> = 0>
338+
_LIBCPP_HIDE_FROM_ABI bool __in_bounds(_Tp*, size_t) const {
339+
return true; // If we don't have an array cookie, we assume the access is in-bounds
340+
}
341+
};
342+
343+
// (2) Implementation where we store the size in the class whenever we have it.
344+
//
345+
// Semantically, we'd need to store the size as an optional<size_t>. However, since that
346+
// is really heavy weight, we instead store a size_t and use SIZE_MAX as a magic value
347+
// meaning that we don't know the size.
348+
struct __unique_ptr_array_bounds_stored {
349+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __unique_ptr_array_bounds_stored() : __size_(SIZE_MAX) {}
350+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR explicit __unique_ptr_array_bounds_stored(size_t __size) : __size_(__size) {}
351+
352+
// Use the array cookie if there's one
353+
template <class _Tp, __enable_if_t<__has_array_cookie<_Tp>::value, int> = 0>
354+
_LIBCPP_HIDE_FROM_ABI bool __in_bounds(_Tp* __ptr, size_t __index) const {
355+
size_t __cookie = std::__get_array_cookie(__ptr);
356+
return __index < __cookie;
357+
}
358+
359+
// Otherwise, fall back on the stored size (if any)
360+
template <class _Tp, __enable_if_t<!__has_array_cookie<_Tp>::value, int> = 0>
361+
_LIBCPP_HIDE_FROM_ABI bool __in_bounds(_Tp*, size_t __index) const {
362+
return __index < __size_;
363+
}
364+
365+
private:
366+
size_t __size_;
367+
};
368+
286369
template <class _Tp, class _Dp>
287370
class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp> {
288371
public:
@@ -291,8 +374,9 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
291374
typedef typename __pointer<_Tp, deleter_type>::type pointer;
292375

293376
// A unique_ptr contains the following members which may be trivially relocatable:
294-
// - pointer : this may be trivially relocatable, so it's checked
377+
// - pointer: this may be trivially relocatable, so it's checked
295378
// - deleter_type: this may be trivially relocatable, so it's checked
379+
// - (optionally) size: this is trivially relocatable
296380
//
297381
// This unique_ptr implementation only contains a pointer to the unique object and a deleter, so there are no
298382
// references to itself. This means that the entire structure is trivially relocatable if its members are.
@@ -303,6 +387,15 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
303387

304388
private:
305389
__compressed_pair<pointer, deleter_type> __ptr_;
390+
#ifdef _LIBCPP_ABI_BOUNDED_UNIQUE_PTR
391+
using _BoundsChecker = __unique_ptr_array_bounds_stored;
392+
#else
393+
using _BoundsChecker = __unique_ptr_array_bounds_stateless;
394+
#endif
395+
_LIBCPP_NO_UNIQUE_ADDRESS _BoundsChecker __checker_;
396+
397+
template <class, class>
398+
friend class unique_ptr; // access __checker_ in other unique_ptrs
306399

307400
template <class _From>
308401
struct _CheckArrayPointerConversion : is_same<_From, pointer> {};
@@ -364,6 +457,12 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
364457
_LIBCPP_HIDE_FROM_ABI
365458
_LIBCPP_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(_Pp __p) _NOEXCEPT : __ptr_(__p, __value_init_tag()) {}
366459

460+
// Private constructor used by make_unique & friends to pass the size that was allocated
461+
template <class _Tag, class _Ptr, __enable_if_t<is_same<_Tag, __private_constructor_tag>::value, int> = 0>
462+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 explicit unique_ptr(_Tag, _Ptr __ptr, size_t __size) _NOEXCEPT
463+
: __ptr_(__ptr, __value_init_tag()),
464+
__checker_(__size) {}
465+
367466
template <class _Pp,
368467
bool _Dummy = true,
369468
class = _EnableIfDeleterConstructible<_LValRefType<_Dummy> >,
@@ -397,11 +496,13 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
397496
_LIBCPP_HIDE_FROM_ABI unique_ptr(_Pp __p, _BadRValRefType<_Dummy> __d) = delete;
398497

399498
_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())) {}
499+
: __ptr_(__u.release(), std::forward<deleter_type>(__u.get_deleter())),
500+
__checker_(std::move(__u.__checker_)) {}
401501

402502
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr&& __u) _NOEXCEPT {
403503
reset(__u.release());
404504
__ptr_.second() = std::forward<deleter_type>(__u.get_deleter());
505+
__checker_ = std::move(__u.__checker_);
405506
return *this;
406507
}
407508

@@ -410,7 +511,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
410511
class = _EnableIfMoveConvertible<unique_ptr<_Up, _Ep>, _Up>,
411512
class = _EnableIfDeleterConvertible<_Ep> >
412513
_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())) {}
514+
: __ptr_(__u.release(), std::forward<_Ep>(__u.get_deleter())),
515+
__checker_(std::move(__u.__checker_)) {}
414516

415517
template <class _Up,
416518
class _Ep,
@@ -419,6 +521,7 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
419521
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr& operator=(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT {
420522
reset(__u.release());
421523
__ptr_.second() = std::forward<_Ep>(__u.get_deleter());
524+
__checker_ = std::move(__u.__checker_);
422525
return *this;
423526
}
424527

@@ -436,6 +539,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
436539
}
437540

438541
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 __add_lvalue_reference_t<_Tp> operator[](size_t __i) const {
542+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__checker_.__in_bounds(std::__to_address(__ptr_.first()), __i),
543+
"unique_ptr<T[]>::operator[](index): index out of range");
439544
return __ptr_.first()[__i];
440545
}
441546
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer get() const _NOEXCEPT { return __ptr_.first(); }
@@ -452,25 +557,31 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
452557
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 pointer release() _NOEXCEPT {
453558
pointer __t = __ptr_.first();
454559
__ptr_.first() = pointer();
560+
__checker_ = _BoundsChecker();
455561
return __t;
456562
}
457563

458564
template <class _Pp, __enable_if_t<_CheckArrayPointerConversion<_Pp>::value, int> = 0>
459565
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(_Pp __p) _NOEXCEPT {
460566
pointer __tmp = __ptr_.first();
461567
__ptr_.first() = __p;
568+
__checker_ = _BoundsChecker();
462569
if (__tmp)
463570
__ptr_.second()(__tmp);
464571
}
465572

466573
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void reset(nullptr_t = nullptr) _NOEXCEPT {
467574
pointer __tmp = __ptr_.first();
468575
__ptr_.first() = nullptr;
576+
__checker_ = _BoundsChecker();
469577
if (__tmp)
470578
__ptr_.second()(__tmp);
471579
}
472580

473-
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT { __ptr_.swap(__u.__ptr_); }
581+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 void swap(unique_ptr& __u) _NOEXCEPT {
582+
__ptr_.swap(__u.__ptr_);
583+
std::swap(__checker_, __u.__checker_);
584+
}
474585
};
475586

476587
template <class _Tp, class _Dp, __enable_if_t<__is_swappable_v<_Dp>, int> = 0>
@@ -626,7 +737,7 @@ template <class _Tp>
626737
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound
627738
make_unique(size_t __n) {
628739
typedef __remove_extent_t<_Tp> _Up;
629-
return unique_ptr<_Tp>(new _Up[__n]());
740+
return unique_ptr<_Tp>(__private_constructor_tag(), new _Up[__n](), __n);
630741
}
631742

632743
template <class _Tp, class... _Args>
@@ -645,7 +756,7 @@ make_unique_for_overwrite() {
645756
template <class _Tp>
646757
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 typename __unique_if<_Tp>::__unique_array_unknown_bound
647758
make_unique_for_overwrite(size_t __n) {
648-
return unique_ptr<_Tp>(new __remove_extent_t<_Tp>[__n]);
759+
return unique_ptr<_Tp>(__private_constructor_tag(), new __remove_extent_t<_Tp>[__n], __n);
649760
}
650761

651762
template <class _Tp, class... _Args>

libcxx/include/module.modulemap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,7 @@ module std_private_memory_allocator [system] { header "__m
15091509
module std_private_memory_allocator_arg_t [system] { header "__memory/allocator_arg_t.h" }
15101510
module std_private_memory_allocator_destructor [system] { header "__memory/allocator_destructor.h" }
15111511
module std_private_memory_allocator_traits [system] { header "__memory/allocator_traits.h" }
1512+
module std_private_memory_array_cookie [system] { header "__memory/array_cookie.h" }
15121513
module std_private_memory_assume_aligned [system] { header "__memory/assume_aligned.h" }
15131514
module std_private_memory_auto_ptr [system] { header "__memory/auto_ptr.h" }
15141515
module std_private_memory_builtin_new_allocator [system] {

0 commit comments

Comments
 (0)