-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc++][hardening] Classify assertions related to leaks and syscalls. #77164
[libc++][hardening] Classify assertions related to leaks and syscalls. #77164
Conversation
Introduce two new categories: - `_LIBCPP_ASSERT_VALID_DEALLOCATION`; - `_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL`.
@llvm/pr-subscribers-libcxx Author: Konstantin Varlamov (var-const) ChangesIntroduce two new categories:
Full diff: https://github.com/llvm/llvm-project/pull/77164.diff 6 Files Affected:
diff --git a/libcxx/include/__config b/libcxx/include/__config
index 082c73e672c749..4d74b564864272 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -280,6 +280,14 @@
// - `_LIBCPP_ASSERT_NON_OVERLAPPING_RANGES` -- for functions that take several ranges as arguments, checks that the
// given ranges do not overlap.
//
+// - `_LIBCPP_ASSERT_VALID_DEALLOCATION` -- checks that an attempt to deallocate memory is valid (e.g. the given object
+// was allocated by the given allocator). Violating this category typically results in a memory leak.
+//
+// - `_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL` -- checks that a call to an external API (e.g. a syscall) doesn't fail in
+// an unexpected manner. This includes triggering documented cases of undefined behavior in an external library (like
+// attempting to unlock an unlocked mutex in pthreads). We generally don't expect these failures to compromize memory
+// safety or otherwise create an immediate security issue.
+//
// - `_LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR` -- checks any operations that exchange nodes between containers to make sure
// the containers have compatible allocators.
//
@@ -327,6 +335,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
// Overlapping ranges will make algorithms produce incorrect results but don't directly lead to a security
// vulnerability.
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSUME(expression)
+# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSUME(expression)
+# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSUME(expression)
@@ -341,6 +351,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
# define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSERT(expression, message)
+# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSERT(expression, message)
+# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSERT(expression, message)
@@ -356,6 +368,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
# define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSERT(expression, message)
+# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSERT(expression, message)
+# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSERT(expression, message)
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSERT(expression, message)
@@ -370,6 +384,8 @@ _LIBCPP_HARDENING_MODE_DEBUG
# define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_NON_NULL(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message) _LIBCPP_ASSUME(expression)
+# define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message) _LIBCPP_ASSUME(expression)
+# define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_PEDANTIC(expression, message) _LIBCPP_ASSUME(expression)
# define _LIBCPP_ASSERT_INTERNAL(expression, message) _LIBCPP_ASSUME(expression)
diff --git a/libcxx/include/__coroutine/coroutine_handle.h b/libcxx/include/__coroutine/coroutine_handle.h
index 54bfe5b44f4c6a..4557a6643c2393 100644
--- a/libcxx/include/__coroutine/coroutine_handle.h
+++ b/libcxx/include/__coroutine/coroutine_handle.h
@@ -55,7 +55,7 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle<void> {
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; }
_LIBCPP_HIDE_FROM_ABI bool done() const {
- _LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines");
return __builtin_coro_done(__handle_);
}
@@ -63,13 +63,13 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle<void> {
_LIBCPP_HIDE_FROM_ABI void operator()() const { resume(); }
_LIBCPP_HIDE_FROM_ABI void resume() const {
- _LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "resume() can be called only on suspended coroutines");
- _LIBCPP_ASSERT_UNCATEGORIZED(!done(), "resume() has undefined behavior when the coroutine is done");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "resume() can be called only on suspended coroutines");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(!done(), "resume() has undefined behavior when the coroutine is done");
__builtin_coro_resume(__handle_);
}
_LIBCPP_HIDE_FROM_ABI void destroy() const {
- _LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "destroy() can be called only on suspended coroutines");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "destroy() can be called only on suspended coroutines");
__builtin_coro_destroy(__handle_);
}
@@ -130,7 +130,7 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle {
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; }
_LIBCPP_HIDE_FROM_ABI bool done() const {
- _LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines");
return __builtin_coro_done(__handle_);
}
@@ -138,13 +138,13 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle {
_LIBCPP_HIDE_FROM_ABI void operator()() const { resume(); }
_LIBCPP_HIDE_FROM_ABI void resume() const {
- _LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "resume() can be called only on suspended coroutines");
- _LIBCPP_ASSERT_UNCATEGORIZED(!done(), "resume() has undefined behavior when the coroutine is done");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "resume() can be called only on suspended coroutines");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(!done(), "resume() has undefined behavior when the coroutine is done");
__builtin_coro_resume(__handle_);
}
_LIBCPP_HIDE_FROM_ABI void destroy() const {
- _LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "destroy() can be called only on suspended coroutines");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "destroy() can be called only on suspended coroutines");
__builtin_coro_destroy(__handle_);
}
diff --git a/libcxx/include/__memory_resource/polymorphic_allocator.h b/libcxx/include/__memory_resource/polymorphic_allocator.h
index 3a2794931767b2..a145b0f0371693 100644
--- a/libcxx/include/__memory_resource/polymorphic_allocator.h
+++ b/libcxx/include/__memory_resource/polymorphic_allocator.h
@@ -68,7 +68,8 @@ class _LIBCPP_AVAILABILITY_PMR _LIBCPP_TEMPLATE_VIS polymorphic_allocator {
}
_LIBCPP_HIDE_FROM_ABI void deallocate(_ValueType* __p, size_t __n) {
- _LIBCPP_ASSERT_UNCATEGORIZED(__n <= __max_size(), "deallocate called for size which exceeds max_size()");
+ // This would cause an integer overflow, resulting in too few objects being deleted.
+ _LIBCPP_ASSERT_VALID_DEALLOCATION(__n <= __max_size(), "deallocate called for size which exceeds max_size()");
__res_->deallocate(__p, __n * sizeof(_ValueType), alignof(_ValueType));
}
diff --git a/libcxx/src/filesystem/operations.cpp b/libcxx/src/filesystem/operations.cpp
index 6bee340e0d15c8..0febb0c68bacee 100644
--- a/libcxx/src/filesystem/operations.cpp
+++ b/libcxx/src/filesystem/operations.cpp
@@ -461,7 +461,7 @@ path __current_path(error_code* ec) {
Deleter deleter = &::free;
#else
auto size = ::pathconf(".", _PC_PATH_MAX);
- _LIBCPP_ASSERT_UNCATEGORIZED(size >= 0, "pathconf returned a 0 as max size");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(size >= 0, "pathconf returned a 0 as max size");
auto buff = unique_ptr<path::value_type[]>(new path::value_type[size + 1]);
path::value_type* ptr = buff.get();
@@ -621,7 +621,7 @@ void __permissions(const path& p, perms prms, perm_options opts, error_code* ec)
set_sym_perms = is_symlink(st);
if (m_ec)
return err.report(m_ec);
- _LIBCPP_ASSERT_UNCATEGORIZED(st.permissions() != perms::unknown, "Permissions unexpectedly unknown");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(st.permissions() != perms::unknown, "Permissions unexpectedly unknown");
if (add_perms)
prms |= st.permissions();
else if (remove_perms)
@@ -668,7 +668,9 @@ path __read_symlink(const path& p, error_code* ec) {
detail::SSizeT ret;
if ((ret = detail::readlink(p.c_str(), buff.get(), size)) == -1)
return err.report(capture_errno());
- _LIBCPP_ASSERT_UNCATEGORIZED(ret > 0, "TODO");
+ // `ret` indicates the number of bytes written to the buffer, `0` means that the attempt to read the symlink produced
+ // an empty string.
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(ret > 0, "TODO");
if (static_cast<size_t>(ret) >= size)
return err.report(errc::value_too_large);
buff[ret] = 0;
diff --git a/libcxx/src/memory_resource.cpp b/libcxx/src/memory_resource.cpp
index 42c366893f736b..2117238e63487e 100644
--- a/libcxx/src/memory_resource.cpp
+++ b/libcxx/src/memory_resource.cpp
@@ -189,7 +189,8 @@ void unsynchronized_pool_resource::__adhoc_pool::__do_deallocate(
return;
}
}
- _LIBCPP_ASSERT_UNCATEGORIZED(false, "deallocating a block that was not allocated with this allocator");
+ // The request to deallocate memory ends up being a no-op, likely resulting in a memory leak.
+ _LIBCPP_ASSERT_VALID_DEALLOCATION(false, "deallocating a block that was not allocated with this allocator");
}
}
diff --git a/libcxx/src/mutex.cpp b/libcxx/src/mutex.cpp
index ce854757ac08da..2f8504d602dc9f 100644
--- a/libcxx/src/mutex.cpp
+++ b/libcxx/src/mutex.cpp
@@ -36,7 +36,8 @@ bool mutex::try_lock() noexcept { return __libcpp_mutex_trylock(&__m_); }
void mutex::unlock() noexcept {
int ec = __libcpp_mutex_unlock(&__m_);
(void)ec;
- _LIBCPP_ASSERT_UNCATEGORIZED(ec == 0, "call to mutex::unlock failed");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(
+ ec == 0, "call to mutex::unlock failed. A possible reason is that the mutex wasn't locked");
}
// recursive_mutex
@@ -50,7 +51,7 @@ recursive_mutex::recursive_mutex() {
recursive_mutex::~recursive_mutex() {
int e = __libcpp_recursive_mutex_destroy(&__m_);
(void)e;
- _LIBCPP_ASSERT_UNCATEGORIZED(e == 0, "call to ~recursive_mutex() failed");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(e == 0, "call to ~recursive_mutex() failed");
}
void recursive_mutex::lock() {
@@ -62,7 +63,8 @@ void recursive_mutex::lock() {
void recursive_mutex::unlock() noexcept {
int e = __libcpp_recursive_mutex_unlock(&__m_);
(void)e;
- _LIBCPP_ASSERT_UNCATEGORIZED(e == 0, "call to recursive_mutex::unlock() failed");
+ _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(
+ e == 0, "call to recursive_mutex::unlock() failed. A possible reason is that the mutex wasn't locked");
}
bool recursive_mutex::try_lock() noexcept { return __libcpp_recursive_mutex_trylock(&__m_); }
|
@@ -55,21 +55,21 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle<void> { | |||
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; } | |||
|
|||
_LIBCPP_HIDE_FROM_ABI bool done() const { | |||
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines"); | |||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a little weird, but to me calling these coroutine compiler built-ins does seem like doing a syscall, even though technically both the library and the compiler are part of the "implementation". It's essentially a black box that we don't control and I don't think we can easily say what happens if this condition is violated (without reading Clang sources, but from our perspective it's no different from reading e.g. pthread
sources). I'd prefer not to multiply the number of categories too much and would certainly like to avoid making a separate category just for coroutines. Let me know what you think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it might look a bit odd, but you could call it an external API. I don't mind to keep it in the same category, but I would like to specifically mention that compiler intrinsics are part of this category.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I think I would mention that we consider anything external to the library (including compiler builtins) as an "external API" for the purpose of this category.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points, done.
@@ -621,7 +621,7 @@ void __permissions(const path& p, perms prms, perm_options opts, error_code* ec) | |||
set_sym_perms = is_symlink(st); | |||
if (m_ec) | |||
return err.report(m_ec); | |||
_LIBCPP_ASSERT_UNCATEGORIZED(st.permissions() != perms::unknown, "Permissions unexpectedly unknown"); | |||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(st.permissions() != perms::unknown, "Permissions unexpectedly unknown"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why we treat this as an error -- isn't it valid (if weird) for a user to set permissions to "unknown"? Or is there some context here that I'm missing that should guarantee that the permissions cannot be unknown?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems possible -- you could have SELinux enabled that allows the owner of the process to read
the file but denies it getattr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a TODO to revisit this later.
libcxx/src/filesystem/operations.cpp
Outdated
@@ -461,7 +461,7 @@ path __current_path(error_code* ec) { | |||
Deleter deleter = &::free; | |||
#else | |||
auto size = ::pathconf(".", _PC_PATH_MAX); | |||
_LIBCPP_ASSERT_UNCATEGORIZED(size >= 0, "pathconf returned a 0 as max size"); | |||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(size >= 0, "pathconf returned a 0 as max size"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is suspicious -- the comment seems to indicate it should be size > 0
. Also, doesn't 0
indicate "no limit"? (I don't know if any platform can actually report that, though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is something interesting that I found from the man
page:
• If name corresponds to a maximum or minimum limit, and that limit is
indeterminate, -1 is returned and errno is not changed. (To distin‐
guish an indeterminate limit from an error, set errno to zero before
the call, and then check whether errno is nonzero when -1 is re‐
turned.)
So, perhaps, there could be a platform out there that has an indeterminate maximum path length that could return -1
and mean that it was not an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reading of pathconf
's manpage is that it returns -1
if there's an error or if there's no limit associated to _PC_PATH_MAX
. So I think checking for >= 0
here is correct (although in reality I don't think any system would return 0
).
After consideration, it sounds like we should instead be reporting the error if we encounter one? So instead of the assertion, something like
errno = 0; // POSIX requires this to be thread-safe
auto size = ::pathconf(".", _PC_PATH_MAX);
if (size == -1 && errno != 0)
return err.report(capture_errno(), "call to pathconf failed");
If size == -1
but there was no error, the implementation doesn't have a limit for the path length. We should probably set size = PATH_MAX
(defined in <limits.h>
) in that case. That's a bit academic but it seems reasonable.
CC @EricWF since he most likely wrote that code in the original filesystem implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ldionne Your reading seems to match with the reading that I had from the man
page and the section that I quoted! I am glad that we are seeing the same thing!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the assertion in favor of error reporting. There's an additional quirk that apparently PATH_MAX
might not be available on z/OS which I presume false under the #else
branch of the #if defined(_LIBCPP_WIN32API) || defined(__GLIBC__) || defined(__APPLE__)
condition (it really comes down to the __GLIBC__
define -- I don't know if lack of glibc
can be used as a reliable indicator of z/OS).
libcxx/src/filesystem/operations.cpp
Outdated
_LIBCPP_ASSERT_UNCATEGORIZED(ret > 0, "TODO"); | ||
// `ret` indicates the number of bytes written to the buffer, `0` means that the attempt to read the symlink produced | ||
// an empty string. | ||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(ret > 0, "TODO"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something I wanted to point out about the new valid-api-call
category is that this is (so far, at least) the only category that also covers spurious failures. All other categories are (or are supposed to be) completely deterministic -- they are direct consequences of the user giving us invalid arguments. System calls, however, can (at least in theory) fail sporadically, with no fault on the user's part.
I'd like to make sure we think it's reasonable. Alternatively, we could:
- only leave checks that we only expect to fail due to bad user input that is hard to check. A failure to lock a mutex might be a good example -- it's most likely to happen if the user didn't lock the mutex in the first place (but we don't have an easy way to check for that, so we have to settle for checking the consequences). However, even this category still might cover spurious errors;
- separate the user-triggered failures and the spurious failures. For the latter, either create a new category or mark them
internal
. The downsides are a) arguably too fine a granularity; b) the separation between what is triggered by a user and what is spurious isn't completely deterministic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder how often these can trigger when an end-user deletes a file the application was using. AFAIK we don't enable these assertions in the dylib so at the moment they are unchecked. Right?
I also how many of these can be solved by throwing an exception. Obviously that requires validating the functions against the WP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only case to consider here is if ret == 0
, and I think in that case the code should "work fine". We'll return a buffer of size at least 1, and where buf[0] == '\0'
. That's weird but valid. In that line of thought, I think this assertion is wrong (or at least it's in the wrong place).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the assertion.
// an unexpected manner. This includes triggering documented cases of undefined behavior in an external library (like | ||
// attempting to unlock an unlocked mutex in pthreads). We generally don't expect these failures to compromize memory | ||
// safety or otherwise create an immediate security issue. | ||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not specific to this commit, but I feel this list is getting a bit long in here. How about putting it in an .rst
file. I also would like a better overview what the modi do. For example, what does _LIBCPP_HARDENING_MODE_FAST
do.
Maybe a summary lke
* _LIBCPP_HARDENING_MODE_FAST
* foo
* bar
* _LIBCPP_HARDENING_MODE_EXTENSIVE
* all of _LIBCPP_HARDENING_MODE_FAST
* do
* re
* mi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I completely agree, and I plan to improve the documentation after this round of categorization patches lands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That probably happens after branching LLVM 18. After branching we can always backport documentation changes. So it would be great if we can get better documentation in LLVM 18.
@@ -55,21 +55,21 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle<void> { | |||
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; } | |||
|
|||
_LIBCPP_HIDE_FROM_ABI bool done() const { | |||
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines"); | |||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it might look a bit odd, but you could call it an external API. I don't mind to keep it in the same category, but I would like to specifically mention that compiler intrinsics are part of this category.
libcxx/src/filesystem/operations.cpp
Outdated
_LIBCPP_ASSERT_UNCATEGORIZED(ret > 0, "TODO"); | ||
// `ret` indicates the number of bytes written to the buffer, `0` means that the attempt to read the symlink produced | ||
// an empty string. | ||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(ret > 0, "TODO"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder how often these can trigger when an end-user deletes a file the application was using. AFAIK we don't enable these assertions in the dylib so at the moment they are unchecked. Right?
I also how many of these can be solved by throwing an exception. Obviously that requires validating the functions against the WP.
libcxx/src/filesystem/operations.cpp
Outdated
@@ -461,7 +461,7 @@ path __current_path(error_code* ec) { | |||
Deleter deleter = &::free; | |||
#else | |||
auto size = ::pathconf(".", _PC_PATH_MAX); | |||
_LIBCPP_ASSERT_UNCATEGORIZED(size >= 0, "pathconf returned a 0 as max size"); | |||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(size >= 0, "pathconf returned a 0 as max size"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reading of pathconf
's manpage is that it returns -1
if there's an error or if there's no limit associated to _PC_PATH_MAX
. So I think checking for >= 0
here is correct (although in reality I don't think any system would return 0
).
After consideration, it sounds like we should instead be reporting the error if we encounter one? So instead of the assertion, something like
errno = 0; // POSIX requires this to be thread-safe
auto size = ::pathconf(".", _PC_PATH_MAX);
if (size == -1 && errno != 0)
return err.report(capture_errno(), "call to pathconf failed");
If size == -1
but there was no error, the implementation doesn't have a limit for the path length. We should probably set size = PATH_MAX
(defined in <limits.h>
) in that case. That's a bit academic but it seems reasonable.
CC @EricWF since he most likely wrote that code in the original filesystem implementation.
libcxx/src/filesystem/operations.cpp
Outdated
_LIBCPP_ASSERT_UNCATEGORIZED(ret > 0, "TODO"); | ||
// `ret` indicates the number of bytes written to the buffer, `0` means that the attempt to read the symlink produced | ||
// an empty string. | ||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(ret > 0, "TODO"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only case to consider here is if ret == 0
, and I think in that case the code should "work fine". We'll return a buffer of size at least 1, and where buf[0] == '\0'
. That's weird but valid. In that line of thought, I think this assertion is wrong (or at least it's in the wrong place).
@@ -55,21 +55,21 @@ struct _LIBCPP_TEMPLATE_VIS coroutine_handle<void> { | |||
_LIBCPP_HIDE_FROM_ABI constexpr explicit operator bool() const noexcept { return __handle_ != nullptr; } | |||
|
|||
_LIBCPP_HIDE_FROM_ABI bool done() const { | |||
_LIBCPP_ASSERT_UNCATEGORIZED(__is_suspended(), "done() can be called only on suspended coroutines"); | |||
_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(__is_suspended(), "done() can be called only on suspended coroutines"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I think I would mention that we consider anything external to the library (including compiler builtins) as an "external API" for the purpose of this category.
✅ With the latest revision this PR passed the C/C++ code formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM modulo one nit.
// an unexpected manner. This includes triggering documented cases of undefined behavior in an external library (like | ||
// attempting to unlock an unlocked mutex in pthreads). We generally don't expect these failures to compromize memory | ||
// safety or otherwise create an immediate security issue. | ||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That probably happens after branching LLVM 18. After branching we can always backport documentation changes. So it would be great if we can get better documentation in LLVM 18.
...s/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/assert.deallocate.pass.cpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Introduce two new categories:
_LIBCPP_ASSERT_VALID_DEALLOCATION
;_LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL
.