Skip to content

STL Hardening

Stephan T. Lavavej edited this page Jan 16, 2025 · 30 revisions

Plan

  • Review:
  • Design a consistent policy for what should be hardened
  • Design how this will be controlled, and its defaults
    • Opt-in means virtually no users will benefit, but has the least performance requirements
    • Implied-by-/sdl means some users will benefit automatically
    • Opt-out means all users will benefit automatically, but has the most stringent performance requirements
  • Design a termination mechanism:
    • Is CDL's termination mechanism ideal?
    • Should violations terminate the program differently?
    • Should it be customizable?
  • With a design, make the code changes (should be the easy part as 90% of the work is already present)
  • Optimizations to mitigate perf impact, once we have examples of where the perf impact will be
    • Loop in the compiler back-end
    • Additional library changes may also be necessary

Audit of CDL checks (as of 2024-12-09)

Sequence containers

  • vector
    • pop_back
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
  • vector<bool>
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
  • deque
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
  • list
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
    • pop_front
    • pop_back
  • forward_list
    • front
      • ✅ P3471R1's design
  • array
    • operator[]
      • ✅ P3471R1's proposed wording
  • array<T, 0>
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design

Strings

  • basic_string
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
    • resize_and_overwrite
  • basic_string_view
    • basic_string_view(const_pointer, size_type)
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
    • remove_prefix
    • remove_suffix

Spans

  • span
    • span(It, size_type)
    • span(It, End)
    • span(R&&)
    • span(const span<OtherElementType, OtherExtent>&)
    • first
    • last
    • subspan
    • size_bytes
    • operator[]
      • ✅ P3471R1's proposed wording
    • front
      • ✅ P3471R1's design
    • back
      • ✅ P3471R1's design
  • mdspan
    • static_extent
    • mdspan(const mdspan<OtherElementType, OtherExtents, OtherLayoutPolicy, OtherAccessor>&)
    • operator[]
      • ✅ P3471R1's design
    • size

optional and expected

  • optional
    • operator*
      • ✅ P3471R1's proposed wording
    • operator->
      • ✅ P3471R1's proposed wording
  • expected<T, E>
    • operator->
      • ✅ P3471R1's proposed wording
    • operator*
      • ✅ P3471R1's proposed wording
    • error
  • expected<void, E>
    • operator*
    • error

Ranges

  • ranges::view_interface
    • front
    • back
    • operator[]
  • generator::iterator
    • operator*
    • operator++
  • generator
    • begin
  • iota_view
    • iota_view(W)
    • iota_view(type_identity_t<W>, type_identity_t<Bound>)
    • iota_view(Iter, Sent)
  • repeat_view::iterator
    • operator++
    • operator--
    • operator+=
    • operator-=
  • repeat_view
    • repeat_view(const T&, Bound)
    • repeat_view(T&&, Bound)
    • repeat_view(piecewise_construct_t, tuple<TArgs...>, tuple<BoundArgs...>)
  • filter_view
    • pred
    • begin
  • take_view
    • take_view(V, range_difference_t<V>)
  • take_while_view
    • pred
    • end
  • drop_view
    • drop_view(V, range_difference_t<V>)
  • drop_while_view
    • pred
    • begin
  • views::counted
    • operator()
  • chunk_view
    • chunk_view(V, range_difference_t<V>)
  • slide_view
    • slide_view(V, range_difference_t<V>)
  • chunk_by_view
    • pred
    • begin
  • stride_view
    • stride_view(V, range_difference_t<V>)
  • cartesian_product_view
    • size

valarray

  • valarray
    • operator*=
    • operator/=
    • operator%=
    • operator+=
    • operator-=
    • operator^=
    • operator|=
    • operator&=
    • operator<<=
    • operator>>=
    • operator[]
    • operator*
    • operator/
    • operator%
    • operator+
    • operator-
    • operator^
    • operator&
    • operator|
    • operator<<
    • operator>>
    • operator&&
    • operator||
    • operator==
    • operator!=
    • operator<
    • operator>
    • operator<=
    • operator>=

mdspan support types

  • extents
    • extents(VARIOUS ARGS)
    • static_extent
    • extent
  • layout_left::mapping
    • mapping(VARIOUS ARGS)
    • stride
    • operator()
  • layout_right::mapping
    • mapping(VARIOUS ARGS)
    • stride
    • operator()
  • layout_stride::mapping
    • mapping(VARIOUS ARGS)
    • stride
    • operator()

Other types

  • condition_variable
    • wait_until(unique_lock&, const time_point&)

Optimization issues

  • VSO-1556181 gsl::span CQ deficiency: predicate inference weakness #1
  • VSO-1556194 gsl::span CQ deficiency: useless multibyte copy
  • VSO-1556195 gsl::span CQ deficiency: predicate inference weakness #2

Reference

  • #5090 "Implement a hardened mode"

P3471R1 Standard Library Hardening

Stephan's highlights:

Hardening needs to be lightweight enough to be used in production environments. In our experience, a debug-only approach is not sufficient to really move the needle security-wise. Thus, the goal is to identify the minimal set of checks that would deliver the most value to users while requiring minimal overhead, with a particular focus on memory safety as a major source of security vulnerabilities. Checking all preconditions in the library is an explicit non-goal, and would be impossible for many semantic requirements anyway.

To reiterate the last point, an important design principle is that hardening needs to be lightweight enough for production use by a wide variety of real-world programs. In our experience in libc++, a small set of checks that is widely used delivers far more value than a more extensive set of checks that is only enabled by select few users. Thankfully, many of the most valuable checks, such as checking for out-of-bounds access in standard containers, also happen to be relatively cheap.

In the initial proposal, we decide to focus on hardened preconditions that prevent out-of-bounds memory access, i.e., compromise the memory safety of the program. These are some of the most valuable for the user since they help prevent potential security vulnerabilities; many of them are also relatively cheap to implement. More hardened preconditions can potentially be added in the future, but the intent is for their number to be limited to keep hardening viable for production use. Specifically, the proposal is to add hardened preconditions to:

  • Accessors of sequence containers, std::span, std::mdspan, std::string, std::string_view and other similar classes that might attempt to access non-existent elements (e.g. back() on an empty container or operator[] with an invalid index).
  • Modifiers of sequence containers and string classes that assume the container to be non-empty (e.g. pop_back()).
  • Accessors of optional and expected that expect the object to be non-empty.

In our experience, hardening all of these operations is trivial to implement and provides significant security value.