Skip to content

Commit d22c956

Browse files
var-constcopybara-github
authored andcommitted
[libc++][hardening] Classify assertions related to leaks and syscalls. (#77164)
Introduce two new categories: - `_LIBCPP_ASSERT_VALID_DEALLOCATION`; - `_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL`. NOKEYCHECK=True GitOrigin-RevId: 6082478e1a5bde8ac03ac7220b733313a5417a13
1 parent b3f2609 commit d22c956

File tree

7 files changed

+59
-18
lines changed

7 files changed

+59
-18
lines changed

include/__config

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,15 @@
291291
// - `_LIBCPP_ASSERT_NON_OVERLAPPING_RANGES` -- for functions that take several ranges as arguments, checks that the
292292
// given ranges do not overlap.
293293
//
294+
// - `_LIBCPP_ASSERT_VALID_DEALLOCATION` -- checks that an attempt to deallocate memory is valid (e.g. the given object
295+
// was allocated by the given allocator). Violating this category typically results in a memory leak.
296+
//
297+
// - `_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL` -- checks that a call to an external API doesn't fail in
298+
// an unexpected manner. This includes triggering documented cases of undefined behavior in an external library (like
299+
// attempting to unlock an unlocked mutex in pthreads). Any API external to the library falls under this category
300+
// (from system calls to compiler intrinsics). We generally don't expect these failures to compromize memory safety or
301+
// otherwise create an immediate security issue.
302+
//
294303
// - `_LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR` -- checks any operations that exchange nodes between containers to make sure
295304
// the containers have compatible allocators.
296305
//
@@ -345,8 +354,10 @@ _LIBCPP_HARDENING_MODE_DEBUG
345354
// Overlapping ranges will make algorithms produce incorrect results but don't directly lead to a security
346355
// vulnerability.
347356
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSUME(expression)
348-
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSUME(expression)
357+
# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSUME(expression)
358+
# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSUME(expression)
349359
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSUME(expression)
360+
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSUME(expression)
350361
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSUME(expression)
351362
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSUME(expression)
352363
# define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message) _LIBCPP_ASSUME(expression)
@@ -360,6 +371,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
360371
# define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSERT(expression, message)
361372
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSERT(expression, message)
362373
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSERT(expression, message)
374+
# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSERT(expression, message)
375+
# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSERT(expression, message)
363376
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSERT(expression, message)
364377
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSERT(expression, message)
365378
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSERT(expression, message)
@@ -376,6 +389,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
376389
# define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSERT(expression, message)
377390
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSERT(expression, message)
378391
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSERT(expression, message)
392+
# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSERT(expression, message)
393+
# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSERT(expression, message)
379394
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSERT(expression, message)
380395
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSERT(expression, message)
381396
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSERT(expression, message)
@@ -391,6 +406,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
391406
# define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSUME(expression)
392407
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSUME(expression)
393408
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSUME(expression)
409+
# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSUME(expression)
410+
# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSUME(expression)
394411
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSUME(expression)
395412
# define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message) _LIBCPP_ASSUME(expression)
396413
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSUME(expression)

include/__coroutine/coroutine_handle.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,21 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle<void> {
5555
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; }
5656

5757
_LIBCPP_HIDE_FROM_ABI bool done() const {
58-
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines");
58+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines");
5959
return __builtin_coro_done(__handle_);
6060
}
6161

6262
// [coroutine.handle.resumption], resumption
6363
_LIBCPP_HIDE_FROM_ABI void operator()() const { resume(); }
6464

6565
_LIBCPP_HIDE_FROM_ABI void resume() const {
66-
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "resume() can be called only on suspended coroutines");
67-
_LIBCPP_ASSERT_UNCATEGORIZED(!done(), "resume() has undefined behavior when the coroutine is done");
66+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "resume() can be called only on suspended coroutines");
67+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(!done(), "resume() has undefined behavior when the coroutine is done");
6868
__builtin_coro_resume(__handle_);
6969
}
7070

7171
_LIBCPP_HIDE_FROM_ABI void destroy() const {
72-
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "destroy() can be called only on suspended coroutines");
72+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "destroy() can be called only on suspended coroutines");
7373
__builtin_coro_destroy(__handle_);
7474
}
7575

@@ -130,21 +130,21 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle {
130130
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; }
131131

132132
_LIBCPP_HIDE_FROM_ABI bool done() const {
133-
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines");
133+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines");
134134
return __builtin_coro_done(__handle_);
135135
}
136136

137137
// [coroutine.handle.resumption], resumption
138138
_LIBCPP_HIDE_FROM_ABI void operator()() const { resume(); }
139139

140140
_LIBCPP_HIDE_FROM_ABI void resume() const {
141-
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "resume() can be called only on suspended coroutines");
142-
_LIBCPP_ASSERT_UNCATEGORIZED(!done(), "resume() has undefined behavior when the coroutine is done");
141+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "resume() can be called only on suspended coroutines");
142+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(!done(), "resume() has undefined behavior when the coroutine is done");
143143
__builtin_coro_resume(__handle_);
144144
}
145145

146146
_LIBCPP_HIDE_FROM_ABI void destroy() const {
147-
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "destroy() can be called only on suspended coroutines");
147+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "destroy() can be called only on suspended coroutines");
148148
__builtin_coro_destroy(__handle_);
149149
}
150150

include/__memory_resource/polymorphic_allocator.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ class _LIBCPP_AVAILABILITY_PMR _LIBCPP_TEMPLATE_VIS polymorphic_allocator {
6868
}
6969

7070
_LIBCPP_HIDE_FROM_ABI void deallocate(_ValueType* __p, size_t __n) {
71-
_LIBCPP_ASSERT_UNCATEGORIZED(__n <= __max_size(), "deallocate called for size which exceeds max_size()");
71+
_LIBCPP_ASSERT_VALID_DEALLOCATION(
72+
__n <= __max_size(),
73+
"deallocate() called for a size which exceeds max_size(), leading to a memory leak "
74+
"(the argument will overflow and result in too few objects being deleted)");
7275
__res_->deallocate(__p, __n * sizeof(_ValueType), alignof(_ValueType));
7376
}
7477

src/filesystem/operations.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,8 +460,21 @@ path __current_path(error_code* ec) {
460460
typedef decltype(&::free) Deleter;
461461
Deleter deleter = &::free;
462462
#else
463+
errno = 0; // Note: POSIX mandates that modifying `errno` is thread-safe.
463464
auto size = ::pathconf(".", _PC_PATH_MAX);
464-
_LIBCPP_ASSERT_UNCATEGORIZED(size >= 0, "pathconf returned a 0 as max size");
465+
if (size == -1) {
466+
if (errno != 0) {
467+
return err.report(capture_errno(), "call to pathconf failed");
468+
469+
// `pathconf` returns `-1` without an error to indicate no limit.
470+
} else {
471+
# if defined(__MVS__) && !defined(PATH_MAX)
472+
size = _XOPEN_PATH_MAX + 1;
473+
# else
474+
size = PATH_MAX + 1;
475+
# endif
476+
}
477+
}
465478

466479
auto buff = unique_ptr<path::value_type[]>(new path::value_type[size + 1]);
467480
path::value_type* ptr = buff.get();
@@ -620,7 +633,9 @@ void __permissions(const path& p, perms prms, perm_options opts, error_code* ec)
620633
set_sym_perms = is_symlink(st);
621634
if (m_ec)
622635
return err.report(m_ec);
623-
_LIBCPP_ASSERT_UNCATEGORIZED(st.permissions() != perms::unknown, "Permissions unexpectedly unknown");
636+
// TODO(hardening): double-check this assertion -- it might be a valid (if rare) case when the permissions are
637+
// unknown.
638+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(st.permissions() != perms::unknown, "Permissions unexpectedly unknown");
624639
if (add_perms)
625640
prms |= st.permissions();
626641
else if (remove_perms)
@@ -667,7 +682,7 @@ path __read_symlink(const path& p, error_code* ec) {
667682
detail::SSizeT ret;
668683
if ((ret = detail::readlink(p.c_str(), buff.get(), size)) == -1)
669684
return err.report(capture_errno());
670-
_LIBCPP_ASSERT_UNCATEGORIZED(ret > 0, "TODO");
685+
// Note that `ret` returning `0` would work, resulting in a valid empty string being returned.
671686
if (static_cast<size_t>(ret) >= size)
672687
return err.report(errc::value_too_large);
673688
buff[ret] = 0;

src/memory_resource.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,8 @@ void unsynchronized_pool_resource::__adhoc_pool::__do_deallocate(
189189
return;
190190
}
191191
}
192-
_LIBCPP_ASSERT_UNCATEGORIZED(false, "deallocating a block that was not allocated with this allocator");
192+
// The request to deallocate memory ends up being a no-op, likely resulting in a memory leak.
193+
_LIBCPP_ASSERT_VALID_DEALLOCATION(false, "deallocating a block that was not allocated with this allocator");
193194
}
194195
}
195196

src/mutex.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ bool mutex::try_lock() noexcept { return __libcpp_mutex_trylock(&__m_); }
3636
void mutex::unlock() noexcept {
3737
int ec = __libcpp_mutex_unlock(&__m_);
3838
(void)ec;
39-
_LIBCPP_ASSERT_UNCATEGORIZED(ec == 0, "call to mutex::unlock failed");
39+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(
40+
ec == 0, "call to mutex::unlock failed. A possible reason is that the mutex wasn't locked");
4041
}
4142

4243
// recursive_mutex
@@ -50,7 +51,7 @@ recursive_mutex::recursive_mutex() {
5051
recursive_mutex::~recursive_mutex() {
5152
int e = __libcpp_recursive_mutex_destroy(&__m_);
5253
(void)e;
53-
_LIBCPP_ASSERT_UNCATEGORIZED(e == 0, "call to ~recursive_mutex() failed");
54+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(e == 0, "call to ~recursive_mutex() failed");
5455
}
5556

5657
void recursive_mutex::lock() {
@@ -62,7 +63,8 @@ void recursive_mutex::lock() {
6263
void recursive_mutex::unlock() noexcept {
6364
int e = __libcpp_recursive_mutex_unlock(&__m_);
6465
(void)e;
65-
_LIBCPP_ASSERT_UNCATEGORIZED(e == 0, "call to recursive_mutex::unlock() failed");
66+
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(
67+
e == 0, "call to recursive_mutex::unlock() failed. A possible reason is that the mutex wasn't locked");
6668
}
6769

6870
bool recursive_mutex::try_lock() noexcept { return __libcpp_recursive_mutex_trylock(&__m_); }
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ int main(int, char**) {
3232
const std::size_t maxSize = Traits::max_size(a);
3333

3434
a.deallocate(nullptr, maxSize); // no assertion
35-
TEST_LIBCPP_ASSERT_FAILURE(a.deallocate(nullptr, maxSize + 1), "deallocate called for size which exceeds max_size()");
35+
TEST_LIBCPP_ASSERT_FAILURE(
36+
a.deallocate(nullptr, maxSize + 1),
37+
"deallocate() called for a size which exceeds max_size(), leading to a memory leak "
38+
"(the argument will overflow and result in too few objects being deleted)");
3639

3740
return 0;
3841
}

0 commit comments

Comments
 (0)