From bda7c9ac79fe841d39084f73730d0b3ffa3b101b Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Mon, 6 Jan 2025 12:50:00 -0800 Subject: [PATCH] [libc++][hardening] Add checks to `forward_list` element access. (#120858) In our implementation, failing these checks would result in a null pointer access rather than an out-of-bounds access. --- libcxx/docs/Hardening.rst | 2 +- libcxx/include/forward_list | 12 ++++- .../sequences/forwardlist/assert.pass.cpp | 47 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 libcxx/test/libcxx/containers/sequences/forwardlist/assert.pass.cpp diff --git a/libcxx/docs/Hardening.rst b/libcxx/docs/Hardening.rst index 4002f40e1dad33..531065afb8e82b 100644 --- a/libcxx/docs/Hardening.rst +++ b/libcxx/docs/Hardening.rst @@ -407,7 +407,7 @@ Hardened containers status - ✅ - ❌ * - ``forward_list`` - - ❌ + - ✅ - ❌ * - ``deque`` - ✅ diff --git a/libcxx/include/forward_list b/libcxx/include/forward_list index c1ab155d5a133e..ea854ea828b3be 100644 --- a/libcxx/include/forward_list +++ b/libcxx/include/forward_list @@ -202,6 +202,7 @@ template # include <__algorithm/lexicographical_compare.h> # include <__algorithm/lexicographical_compare_three_way.h> # include <__algorithm/min.h> +# include <__assert> # include <__config> # include <__cstddef/nullptr_t.h> # include <__iterator/distance.h> @@ -766,8 +767,14 @@ public: return std::min(__node_traits::max_size(this->__alloc_), numeric_limits::max()); } - _LIBCPP_HIDE_FROM_ABI reference front() { return __base::__before_begin()->__next_->__get_value(); } - _LIBCPP_HIDE_FROM_ABI const_reference front() const { return __base::__before_begin()->__next_->__get_value(); } + _LIBCPP_HIDE_FROM_ABI reference front() { + _LIBCPP_ASSERT_NON_NULL(!empty(), "forward_list::front called on an empty list"); + return __base::__before_begin()->__next_->__get_value(); + } + _LIBCPP_HIDE_FROM_ABI const_reference front() const { + _LIBCPP_ASSERT_NON_NULL(!empty(), "forward_list::front called on an empty list"); + return __base::__before_begin()->__next_->__get_value(); + } # ifndef _LIBCPP_CXX03_LANG # if _LIBCPP_STD_VER >= 17 @@ -1085,6 +1092,7 @@ void forward_list<_Tp, _Alloc>::push_front(const value_type& __v) { template void forward_list<_Tp, _Alloc>::pop_front() { + _LIBCPP_ASSERT_NON_NULL(!empty(), "forward_list::pop_front called on an empty list"); __node_pointer __p = __base::__before_begin()->__next_; __base::__before_begin()->__next_ = __p->__next_; this->__delete_node(__p); diff --git a/libcxx/test/libcxx/containers/sequences/forwardlist/assert.pass.cpp b/libcxx/test/libcxx/containers/sequences/forwardlist/assert.pass.cpp new file mode 100644 index 00000000000000..6d1748e6450256 --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/forwardlist/assert.pass.cpp @@ -0,0 +1,47 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// Test hardening assertions for std::forward_list. + +// REQUIRES: has-unix-headers +// REQUIRES: libcpp-hardening-mode={{extensive|debug}} +// UNSUPPORTED: c++03 +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +#include + +#include "check_assertion.h" + +int main(int, char**) { + { // Default-constructed list. + std::forward_list c; + const auto& const_c = c; + TEST_LIBCPP_ASSERT_FAILURE(c.front(), "forward_list::front called on an empty list"); + TEST_LIBCPP_ASSERT_FAILURE(const_c.front(), "forward_list::front called on an empty list"); + TEST_LIBCPP_ASSERT_FAILURE(c.pop_front(), "forward_list::pop_front called on an empty list"); + } + + { // Non-empty list becomes empty. + std::forward_list c; + const auto& const_c = c; + c.push_front(1); + + // Check that there's no assertion on valid access. + (void)c.front(); + (void)const_c.front(); + + c.pop_front(); + TEST_LIBCPP_ASSERT_FAILURE(c.pop_front(), "forward_list::pop_front called on an empty list"); + TEST_LIBCPP_ASSERT_FAILURE(c.front(), "forward_list::front called on an empty list"); + TEST_LIBCPP_ASSERT_FAILURE(const_c.front(), "forward_list::front called on an empty list"); + } + + return 0; +}