Auto merge of #117769 - matthiaskrgr:rollup-4efjlg3, r=matthiaskrgr
Rollup of 6 pull requests Successful merges: - #114191 (Update exploit mitigations documentation) - #117039 (Clarify UB in `get_unchecked(_mut)`) - #117730 (Closure-consuming helper functions for `fmt::Debug` helpers) - #117741 (Fix typo in internal.rs) - #117743 (Suggest removing `;` for `;` within let-chains) - #117751 (rustdoc-json: Fix test so it actually checks things) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
d42d73b144
@ -2441,10 +2441,26 @@ fn parse_if_after_cond(&mut self, lo: Span, mut cond: P<Expr>) -> PResult<'a, P<
|
||||
self.error_on_extra_if(&cond)?;
|
||||
// Parse block, which will always fail, but we can add a nice note to the error
|
||||
self.parse_block().map_err(|mut err| {
|
||||
err.span_note(
|
||||
cond_span,
|
||||
"the `if` expression is missing a block after this condition",
|
||||
);
|
||||
if self.prev_token == token::Semi
|
||||
&& self.token == token::AndAnd
|
||||
&& let maybe_let = self.look_ahead(1, |t| t.clone())
|
||||
&& maybe_let.is_keyword(kw::Let)
|
||||
{
|
||||
err.span_suggestion(
|
||||
self.prev_token.span,
|
||||
"consider removing this semicolon to parse the `let` as part of the same chain",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
).span_note(
|
||||
self.token.span.to(maybe_let.span),
|
||||
"you likely meant to continue parsing the let-chain starting here",
|
||||
);
|
||||
} else {
|
||||
err.span_note(
|
||||
cond_span,
|
||||
"the `if` expression is missing a block after this condition",
|
||||
);
|
||||
}
|
||||
err
|
||||
})?
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ fn ty_const<'tcx>(constant: &Const, tables: &mut Tables<'tcx>) -> rustc_ty::Cons
|
||||
match constant.internal(tables) {
|
||||
rustc_middle::mir::Const::Ty(c) => c,
|
||||
cnst => {
|
||||
panic!("Trying to covert constant `{constant:?}` to type constant, but found {cnst:?}")
|
||||
panic!("Trying to convert constant `{constant:?}` to type constant, but found {cnst:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -555,6 +555,8 @@
|
||||
pub use core::fmt::Alignment;
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub use core::fmt::Error;
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub use core::fmt::FormatterFn;
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub use core::fmt::{write, Arguments};
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
|
@ -130,6 +130,18 @@ impl<'a, 'b: 'a> DebugStruct<'a, 'b> {
|
||||
/// ```
|
||||
#[stable(feature = "debug_builders", since = "1.2.0")]
|
||||
pub fn field(&mut self, name: &str, value: &dyn fmt::Debug) -> &mut Self {
|
||||
self.field_with(name, |f| value.fmt(f))
|
||||
}
|
||||
|
||||
/// Adds a new field to the generated struct output.
|
||||
///
|
||||
/// This method is equivalent to [`DebugStruct::field`], but formats the
|
||||
/// value using a provided closure rather than by calling [`Debug::fmt`].
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub fn field_with<F>(&mut self, name: &str, value_fmt: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
if self.is_pretty() {
|
||||
if !self.has_fields {
|
||||
@ -140,14 +152,14 @@ pub fn field(&mut self, name: &str, value: &dyn fmt::Debug) -> &mut Self {
|
||||
let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state);
|
||||
writer.write_str(name)?;
|
||||
writer.write_str(": ")?;
|
||||
value.fmt(&mut writer)?;
|
||||
value_fmt(&mut writer)?;
|
||||
writer.write_str(",\n")
|
||||
} else {
|
||||
let prefix = if self.has_fields { ", " } else { " { " };
|
||||
self.fmt.write_str(prefix)?;
|
||||
self.fmt.write_str(name)?;
|
||||
self.fmt.write_str(": ")?;
|
||||
value.fmt(self.fmt)
|
||||
value_fmt(self.fmt)
|
||||
}
|
||||
});
|
||||
|
||||
@ -315,6 +327,18 @@ impl<'a, 'b: 'a> DebugTuple<'a, 'b> {
|
||||
/// ```
|
||||
#[stable(feature = "debug_builders", since = "1.2.0")]
|
||||
pub fn field(&mut self, value: &dyn fmt::Debug) -> &mut Self {
|
||||
self.field_with(|f| value.fmt(f))
|
||||
}
|
||||
|
||||
/// Adds a new field to the generated tuple struct output.
|
||||
///
|
||||
/// This method is equivalent to [`DebugTuple::field`], but formats the
|
||||
/// value using a provided closure rather than by calling [`Debug::fmt`].
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub fn field_with<F>(&mut self, value_fmt: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
if self.is_pretty() {
|
||||
if self.fields == 0 {
|
||||
@ -323,12 +347,12 @@ pub fn field(&mut self, value: &dyn fmt::Debug) -> &mut Self {
|
||||
let mut slot = None;
|
||||
let mut state = Default::default();
|
||||
let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state);
|
||||
value.fmt(&mut writer)?;
|
||||
value_fmt(&mut writer)?;
|
||||
writer.write_str(",\n")
|
||||
} else {
|
||||
let prefix = if self.fields == 0 { "(" } else { ", " };
|
||||
self.fmt.write_str(prefix)?;
|
||||
value.fmt(self.fmt)
|
||||
value_fmt(self.fmt)
|
||||
}
|
||||
});
|
||||
|
||||
@ -385,7 +409,10 @@ struct DebugInner<'a, 'b: 'a> {
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> DebugInner<'a, 'b> {
|
||||
fn entry(&mut self, entry: &dyn fmt::Debug) {
|
||||
fn entry_with<F>(&mut self, entry_fmt: F)
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
if self.is_pretty() {
|
||||
if !self.has_fields {
|
||||
@ -394,13 +421,13 @@ fn entry(&mut self, entry: &dyn fmt::Debug) {
|
||||
let mut slot = None;
|
||||
let mut state = Default::default();
|
||||
let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut state);
|
||||
entry.fmt(&mut writer)?;
|
||||
entry_fmt(&mut writer)?;
|
||||
writer.write_str(",\n")
|
||||
} else {
|
||||
if self.has_fields {
|
||||
self.fmt.write_str(", ")?
|
||||
}
|
||||
entry.fmt(self.fmt)
|
||||
entry_fmt(self.fmt)
|
||||
}
|
||||
});
|
||||
|
||||
@ -475,7 +502,20 @@ impl<'a, 'b: 'a> DebugSet<'a, 'b> {
|
||||
/// ```
|
||||
#[stable(feature = "debug_builders", since = "1.2.0")]
|
||||
pub fn entry(&mut self, entry: &dyn fmt::Debug) -> &mut Self {
|
||||
self.inner.entry(entry);
|
||||
self.inner.entry_with(|f| entry.fmt(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a new entry to the set output.
|
||||
///
|
||||
/// This method is equivalent to [`DebugSet::entry`], but formats the
|
||||
/// entry using a provided closure rather than by calling [`Debug::fmt`].
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub fn entry_with<F>(&mut self, entry_fmt: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.inner.entry_with(entry_fmt);
|
||||
self
|
||||
}
|
||||
|
||||
@ -605,7 +645,20 @@ impl<'a, 'b: 'a> DebugList<'a, 'b> {
|
||||
/// ```
|
||||
#[stable(feature = "debug_builders", since = "1.2.0")]
|
||||
pub fn entry(&mut self, entry: &dyn fmt::Debug) -> &mut Self {
|
||||
self.inner.entry(entry);
|
||||
self.inner.entry_with(|f| entry.fmt(f));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a new entry to the list output.
|
||||
///
|
||||
/// This method is equivalent to [`DebugList::entry`], but formats the
|
||||
/// entry using a provided closure rather than by calling [`Debug::fmt`].
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub fn entry_with<F>(&mut self, entry_fmt: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.inner.entry_with(entry_fmt);
|
||||
self
|
||||
}
|
||||
|
||||
@ -775,6 +828,18 @@ pub fn entry(&mut self, key: &dyn fmt::Debug, value: &dyn fmt::Debug) -> &mut Se
|
||||
/// ```
|
||||
#[stable(feature = "debug_map_key_value", since = "1.42.0")]
|
||||
pub fn key(&mut self, key: &dyn fmt::Debug) -> &mut Self {
|
||||
self.key_with(|f| key.fmt(f))
|
||||
}
|
||||
|
||||
/// Adds the key part of a new entry to the map output.
|
||||
///
|
||||
/// This method is equivalent to [`DebugMap::key`], but formats the
|
||||
/// key using a provided closure rather than by calling [`Debug::fmt`].
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub fn key_with<F>(&mut self, key_fmt: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
assert!(
|
||||
!self.has_key,
|
||||
@ -789,13 +854,13 @@ pub fn key(&mut self, key: &dyn fmt::Debug) -> &mut Self {
|
||||
let mut slot = None;
|
||||
self.state = Default::default();
|
||||
let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut self.state);
|
||||
key.fmt(&mut writer)?;
|
||||
key_fmt(&mut writer)?;
|
||||
writer.write_str(": ")?;
|
||||
} else {
|
||||
if self.has_fields {
|
||||
self.fmt.write_str(", ")?
|
||||
}
|
||||
key.fmt(self.fmt)?;
|
||||
key_fmt(self.fmt)?;
|
||||
self.fmt.write_str(": ")?;
|
||||
}
|
||||
|
||||
@ -839,16 +904,28 @@ pub fn key(&mut self, key: &dyn fmt::Debug) -> &mut Self {
|
||||
/// ```
|
||||
#[stable(feature = "debug_map_key_value", since = "1.42.0")]
|
||||
pub fn value(&mut self, value: &dyn fmt::Debug) -> &mut Self {
|
||||
self.value_with(|f| value.fmt(f))
|
||||
}
|
||||
|
||||
/// Adds the value part of a new entry to the map output.
|
||||
///
|
||||
/// This method is equivalent to [`DebugMap::value`], but formats the
|
||||
/// value using a provided closure rather than by calling [`Debug::fmt`].
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub fn value_with<F>(&mut self, value_fmt: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
assert!(self.has_key, "attempted to format a map value before its key");
|
||||
|
||||
if self.is_pretty() {
|
||||
let mut slot = None;
|
||||
let mut writer = PadAdapter::wrap(self.fmt, &mut slot, &mut self.state);
|
||||
value.fmt(&mut writer)?;
|
||||
value_fmt(&mut writer)?;
|
||||
writer.write_str(",\n")?;
|
||||
} else {
|
||||
value.fmt(self.fmt)?;
|
||||
value_fmt(self.fmt)?;
|
||||
}
|
||||
|
||||
self.has_key = false;
|
||||
@ -936,3 +1013,44 @@ fn is_pretty(&self) -> bool {
|
||||
self.fmt.alternate()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [`fmt::Debug`] and [`fmt::Display`] using a function.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(debug_closure_helpers)]
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// let value = 'a';
|
||||
/// assert_eq!(format!("{}", value), "a");
|
||||
/// assert_eq!(format!("{:?}", value), "'a'");
|
||||
///
|
||||
/// let wrapped = fmt::FormatterFn(|f| write!(f, "{:?}", &value));
|
||||
/// assert_eq!(format!("{}", wrapped), "'a'");
|
||||
/// assert_eq!(format!("{:?}", wrapped), "'a'");
|
||||
/// ```
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub struct FormatterFn<F>(pub F)
|
||||
where
|
||||
F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
impl<F> fmt::Debug for FormatterFn<F>
|
||||
where
|
||||
F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
impl<F> fmt::Display for FormatterFn<F>
|
||||
where
|
||||
F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
(self.0)(f)
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,9 @@ pub enum Alignment {
|
||||
#[stable(feature = "debug_builders", since = "1.2.0")]
|
||||
pub use self::builders::{DebugList, DebugMap, DebugSet, DebugStruct, DebugTuple};
|
||||
|
||||
#[unstable(feature = "debug_closure_helpers", issue = "117729")]
|
||||
pub use self::builders::FormatterFn;
|
||||
|
||||
/// The type returned by formatter methods.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -640,6 +640,11 @@ pub fn get_mut<I>(&mut self, index: I) -> Option<&mut I::Output>
|
||||
/// Calling this method with an out-of-bounds index is *[undefined behavior]*
|
||||
/// even if the resulting reference is not used.
|
||||
///
|
||||
/// You can think of this like `.get(index).unwrap_unchecked()`. It's UB
|
||||
/// to call `.get_unchecked(len)`, even if you immediately convert to a
|
||||
/// pointer. And it's UB to call `.get_unchecked(..len + 1)`,
|
||||
/// `.get_unchecked(..=len)`, or similar.
|
||||
///
|
||||
/// [`get`]: slice::get
|
||||
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
|
||||
///
|
||||
@ -675,6 +680,11 @@ pub unsafe fn get_unchecked<I>(&self, index: I) -> &I::Output
|
||||
/// Calling this method with an out-of-bounds index is *[undefined behavior]*
|
||||
/// even if the resulting reference is not used.
|
||||
///
|
||||
/// You can think of this like `.get_mut(index).unwrap_unchecked()`. It's
|
||||
/// UB to call `.get_unchecked_mut(len)`, even if you immediately convert
|
||||
/// to a pointer. And it's UB to call `.get_unchecked_mut(..len + 1)`,
|
||||
/// `.get_unchecked_mut(..=len)`, or similar.
|
||||
///
|
||||
/// [`get_mut`]: slice::get_mut
|
||||
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
|
||||
///
|
||||
|
@ -1,12 +1,12 @@
|
||||
# Exploit Mitigations
|
||||
|
||||
This chapter documents the exploit mitigations supported by the Rust
|
||||
compiler, and is by no means an extensive survey of the Rust programming
|
||||
language’s security features.
|
||||
This chapter documents the exploit mitigations supported by the Rust compiler,
|
||||
and is by no means an extensive survey of the Rust programming language’s
|
||||
security features.
|
||||
|
||||
This chapter is for software engineers working with the Rust programming
|
||||
language, and assumes prior knowledge of the Rust programming language and
|
||||
its toolchain.
|
||||
language, and assumes prior knowledge of the Rust programming language and its
|
||||
toolchain.
|
||||
|
||||
|
||||
## Introduction
|
||||
@ -14,8 +14,8 @@ its toolchain.
|
||||
The Rust programming language provides memory[1] and thread[2] safety
|
||||
guarantees via its ownership[3], references and borrowing[4], and slice
|
||||
types[5] features. However, Unsafe Rust[6] introduces unsafe blocks, unsafe
|
||||
functions and methods, unsafe traits, and new types that are not subject to
|
||||
the borrowing rules.
|
||||
functions and methods, unsafe traits, and new types that are not subject to the
|
||||
borrowing rules.
|
||||
|
||||
Parts of the Rust standard library are implemented as safe abstractions over
|
||||
unsafe code (and historically have been vulnerable to memory corruption[7]).
|
||||
@ -23,33 +23,32 @@ Furthermore, the Rust code and documentation encourage creating safe
|
||||
abstractions over unsafe code. This can cause a false sense of security if
|
||||
unsafe code is not properly reviewed and tested.
|
||||
|
||||
Unsafe Rust introduces features that do not provide the same memory and
|
||||
thread safety guarantees. This causes programs or libraries to be
|
||||
susceptible to memory corruption (CWE-119)[8] and concurrency issues
|
||||
(CWE-557)[9]. Modern C and C++ compilers provide exploit mitigations to
|
||||
increase the difficulty to exploit vulnerabilities resulting from these
|
||||
issues. Therefore, the Rust compiler must also support these exploit
|
||||
mitigations in order to mitigate vulnerabilities resulting from the use of
|
||||
Unsafe Rust. This chapter documents these exploit mitigations and how they
|
||||
apply to Rust.
|
||||
Unsafe Rust introduces features that do not provide the same memory and thread
|
||||
safety guarantees. This causes programs or libraries to be susceptible to
|
||||
memory corruption (CWE-119)[8] and concurrency issues (CWE-557)[9]. Modern C
|
||||
and C++ compilers provide exploit mitigations to increase the difficulty to
|
||||
exploit vulnerabilities resulting from these issues. Therefore, the Rust
|
||||
compiler must also support these exploit mitigations in order to mitigate
|
||||
vulnerabilities resulting from the use of Unsafe Rust. This chapter documents
|
||||
these exploit mitigations and how they apply to Rust.
|
||||
|
||||
This chapter does not discuss the effectiveness of these exploit mitigations
|
||||
as they vary greatly depending on several factors besides their design and
|
||||
implementation, but rather describe what they do, so their effectiveness can
|
||||
be understood within a given context.
|
||||
This chapter does not discuss the effectiveness of these exploit mitigations as
|
||||
they vary greatly depending on several factors besides their design and
|
||||
implementation, but rather describe what they do, so their effectiveness can be
|
||||
understood within a given context.
|
||||
|
||||
|
||||
## Exploit mitigations
|
||||
|
||||
This section documents the exploit mitigations applicable to the Rust
|
||||
compiler when building programs for the Linux operating system on the AMD64
|
||||
architecture and equivalent.<sup id="fnref:1" role="doc-noteref"><a
|
||||
href="#fn:1" class="footnote">1</a></sup>
|
||||
This section documents the exploit mitigations applicable to the Rust compiler
|
||||
when building programs for the Linux operating system on the AMD64 architecture
|
||||
and equivalent.<sup id="fnref:1" role="doc-noteref"><a href="#fn:1"
|
||||
class="footnote">1</a></sup> All examples in this section were built using
|
||||
nightly builds of the Rust compiler on Debian testing.
|
||||
|
||||
The Rust Programming Language currently has no specification. The Rust
|
||||
compiler (i.e., rustc) is the language reference implementation. All
|
||||
references to “the Rust compiler” in this chapter refer to the language
|
||||
reference implementation.
|
||||
The Rust Programming Language currently has no specification. The Rust compiler
|
||||
(i.e., rustc) is the language reference implementation. All references to “the
|
||||
Rust compiler” in this chapter refer to the language reference implementation.
|
||||
|
||||
Table I \
|
||||
Summary of exploit mitigations supported by the Rust compiler when building
|
||||
@ -83,8 +82,8 @@ instructing the dynamic linker to load it similarly to a shared object at a
|
||||
random load address, thus also benefiting from address-space layout
|
||||
randomization (ASLR). This is also referred to as “full ASLR”.
|
||||
|
||||
The Rust compiler supports position-independent executable, and enables it
|
||||
by default since version 0.12.0 (2014-10-09)[10]–[13].
|
||||
The Rust compiler supports position-independent executable, and enables it by
|
||||
default since version 0.12.0 (2014-10-09)[10]–[13].
|
||||
|
||||
```text
|
||||
$ readelf -h target/release/hello-rust | grep Type:
|
||||
@ -93,8 +92,7 @@ $ readelf -h target/release/hello-rust | grep Type:
|
||||
Fig. 1. Checking if an executable is a position-independent executable.
|
||||
|
||||
An executable with an object type of `ET_DYN` (i.e., shared object) and not
|
||||
`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig.
|
||||
1).
|
||||
`ET_EXEC` (i.e., executable) is a position-independent executable (see Fig. 1).
|
||||
|
||||
|
||||
### Integer overflow checks
|
||||
@ -104,8 +102,11 @@ behavior (which may cause vulnerabilities) by checking for results of signed
|
||||
and unsigned integer computations that cannot be represented in their type,
|
||||
resulting in an overflow or wraparound.
|
||||
|
||||
The Rust compiler supports integer overflow checks, and enables it when
|
||||
debug assertions are enabled since version 1.1.0 (2015-06-25)[14]–[20].
|
||||
The Rust compiler supports integer overflow checks, and enables it when debug
|
||||
assertions are enabled since version 1.0.0 (2015-05-15)[14]–[17], but support
|
||||
for it was not completed until version 1.1.0 (2015-06-25)[16]. An option to
|
||||
control integer overflow checks was later stabilized in version 1.17.0
|
||||
(2017-04-27)[18]–[20].
|
||||
|
||||
```compile_fail
|
||||
fn main() {
|
||||
@ -136,21 +137,21 @@ u: 0
|
||||
Fig. 4. Build and execution of hello-rust-integer with debug assertions
|
||||
disabled.
|
||||
|
||||
Integer overflow checks are enabled when debug assertions are enabled (see
|
||||
Fig. 3), and disabled when debug assertions are disabled (see Fig. 4). To
|
||||
enable integer overflow checks independently, use the option to control
|
||||
integer overflow checks, scoped attributes, or explicit checking methods
|
||||
such as `checked_add`<sup id="fnref:2" role="doc-noteref"><a href="#fn:2"
|
||||
Integer overflow checks are enabled when debug assertions are enabled (see Fig.
|
||||
3), and disabled when debug assertions are disabled (see Fig. 4). To enable
|
||||
integer overflow checks independently, use the option to control integer
|
||||
overflow checks, scoped attributes, or explicit checking methods such as
|
||||
`checked_add`<sup id="fnref:2" role="doc-noteref"><a href="#fn:2"
|
||||
class="footnote">2</a></sup>.
|
||||
|
||||
It is recommended that explicit wrapping methods such as `wrapping_add` be
|
||||
used when wrapping semantics are intended, and that explicit checking and
|
||||
wrapping methods always be used when using Unsafe Rust.
|
||||
It is recommended that explicit wrapping methods such as `wrapping_add` be used
|
||||
when wrapping semantics are intended, and that explicit checking and wrapping
|
||||
methods always be used when using Unsafe Rust.
|
||||
|
||||
<small id="fn:2">2\. See [the `u32` docs](../std/primitive.u32.html)
|
||||
for more information on the checked, overflowing, saturating, and wrapping
|
||||
methods (using u32 as an example). <a href="#fnref:2"
|
||||
class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
<small id="fn:2">2\. See [the `u32` docs](../std/primitive.u32.html) for more
|
||||
information on the checked, overflowing, saturating, and wrapping methods
|
||||
(using u32 as an example). <a href="#fnref:2" class="reversefootnote"
|
||||
role="doc-backlink">↩</a></small>
|
||||
|
||||
|
||||
### Non-executable memory regions
|
||||
@ -158,17 +159,16 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
Non-executable memory regions increase the difficulty of exploitation by
|
||||
limiting the memory regions that can be used to execute arbitrary code. Most
|
||||
modern processors provide support for the operating system to mark memory
|
||||
regions as non executable, but it was previously emulated by software, such
|
||||
as in grsecurity/PaX's
|
||||
[PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt) and
|
||||
[SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors that
|
||||
did not provide support for it. This is also known as “No Execute (NX) Bit”,
|
||||
“Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
|
||||
regions as non executable, but it was previously emulated by software, such as
|
||||
in grsecurity/PaX’s [PAGEEXEC](https://pax.grsecurity.net/docs/pageexec.txt)
|
||||
and [SEGMEXEC](https://pax.grsecurity.net/docs/segmexec.txt), on processors
|
||||
that did not provide support for it. This is also known as “No Execute (NX)
|
||||
Bit”, “Execute Disable (XD) Bit”, “Execute Never (XN) Bit”, and others.
|
||||
|
||||
The Rust compiler supports non-executable memory regions, and enables it by
|
||||
default since its initial release, version 0.1 (2012-01-20)[21], [22], but
|
||||
has regressed since then[23]–[25], and enforced by default since version
|
||||
1.8.0 (2016-04-14)[25].
|
||||
default since its initial release, version 0.1 (2012-01-20)[21], [22], but has
|
||||
regressed since then[23]–[25], and enforced by default since version 1.8.0
|
||||
(2016-04-14)[25].
|
||||
|
||||
```text
|
||||
$ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
|
||||
@ -178,9 +178,9 @@ $ readelf -l target/release/hello-rust | grep -A 1 GNU_STACK
|
||||
Fig. 5. Checking if non-executable memory regions are enabled for a given
|
||||
binary.
|
||||
|
||||
The presence of an element of type `PT_GNU_STACK` in the program header
|
||||
table with the `PF_X` (i.e., executable) flag unset indicates non-executable
|
||||
memory regions<sup id="fnref:3" role="doc-noteref"><a href="#fn:3"
|
||||
The presence of an element of type `PT_GNU_STACK` in the program header table
|
||||
with the `PF_X` (i.e., executable) flag unset indicates non-executable memory
|
||||
regions<sup id="fnref:3" role="doc-noteref"><a href="#fn:3"
|
||||
class="footnote">3</a></sup> are enabled for a given binary (see Fig. 5).
|
||||
Conversely, the presence of an element of type `PT_GNU_STACK` in the program
|
||||
header table with the `PF_X` flag set or the absence of an element of type
|
||||
@ -196,38 +196,40 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
||||
Stack clashing protection protects the stack from overlapping with another
|
||||
memory region—allowing arbitrary data in both to be overwritten using each
|
||||
other—by reading from the stack pages as the stack grows to cause a page
|
||||
fault when attempting to read from the guard page/region. This is also
|
||||
referred to as “stack probes” or “stack probing”.
|
||||
other—by reading from the stack pages as the stack grows to cause a page fault
|
||||
when attempting to read from the guard page/region. This is also referred to as
|
||||
“stack probes” or “stack probing”.
|
||||
|
||||
The Rust compiler supports stack clashing protection via stack probing, and
|
||||
enables it by default since version 1.20.0 (2017-08-31)[26]–[29].
|
||||
|
||||
![Screenshot of IDA Pro listing cross references to __rust_probestack in hello-rust.](images/image1.png "Cross references to __rust_probestack in hello-rust.")
|
||||
Fig. 6. IDA Pro listing cross references to `__rust_probestack` in
|
||||
hello-rust.
|
||||
|
||||
```rust
|
||||
fn hello() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _: [u64; 1024] = [0; 1024];
|
||||
hello();
|
||||
let v: [u8; 16384] = [1; 16384];
|
||||
let first = &v[0];
|
||||
println!("The first element is: {first}");
|
||||
}
|
||||
```
|
||||
Fig 7. Modified hello-rust.
|
||||
Fig. 6. hello-rust-stack-probe-1 program.
|
||||
|
||||
![Screenshot of IDA Pro listing cross references to __rust_probestack in modified hello-rust.](images/image2.png "Cross references to __rust_probestack in modified hello-rust.")
|
||||
Fig. 8. IDA Pro listing cross references to `__rust_probestack` in modified
|
||||
hello-rust.
|
||||
![Screenshot of IDA Pro listing the "unrolled loop" stack probe variant in modified hello-rust.](images/image1.png "The \"unrolled loop\" stack probe variant in modified hello-rust.")
|
||||
Fig. 7. The "unrolled loop" stack probe variant in modified hello-rust.
|
||||
|
||||
To check if stack clashing protection is enabled for a given binary, search
|
||||
for cross references to `__rust_probestack`. The `__rust_probestack` is
|
||||
called in the prologue of functions whose stack size is larger than a page
|
||||
size (see Fig. 6), and can be forced for illustration purposes by modifying
|
||||
the hello-rust example as seen in Fig. 7 and Fig. 8.
|
||||
```rust
|
||||
fn main() {
|
||||
let v: [u8; 65536] = [1; 65536];
|
||||
let first = &v[0];
|
||||
println!("The first element is: {first}");
|
||||
}
|
||||
```
|
||||
Fig. 8. hello-rust-stack-probe-2 program.
|
||||
|
||||
![Screenshot of IDA Pro listing the "standard loop" stack probe variant in modified hello-rust.](images/image2.png "The \"standard loop\" stack probe variant in modified hello-rust.")
|
||||
Fig. 9. The "standard loop" stack probe variant in modified hello-rust.
|
||||
|
||||
To check if stack clashing protection is enabled for a given binary, look for
|
||||
any of the two stack probe variants in the prologue of functions whose stack
|
||||
size is larger than a page size (see Figs. 6–9).
|
||||
|
||||
|
||||
### Read-only relocations and immediate binding
|
||||
@ -246,21 +248,20 @@ $ readelf -l target/release/hello-rust | grep GNU_RELRO
|
||||
```
|
||||
Fig. 9. Checking if read-only relocations is enabled for a given binary.
|
||||
|
||||
The presence of an element of type `PT_GNU_RELRO` in the program header
|
||||
table indicates read-only relocations are enabled for a given binary (see
|
||||
Fig. 9). Conversely, the absence of an element of type `PT_GNU_RELRO` in the
|
||||
program header table indicates read-only relocations are not enabled for a
|
||||
given binary.
|
||||
The presence of an element of type `PT_GNU_RELRO` in the program header table
|
||||
indicates read-only relocations are enabled for a given binary (see Fig. 9).
|
||||
Conversely, the absence of an element of type `PT_GNU_RELRO` in the program
|
||||
header table indicates read-only relocations are not enabled for a given
|
||||
binary.
|
||||
|
||||
**Immediate binding** protects additional segments containing relocations
|
||||
(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker
|
||||
to perform all relocations before transferring control to the program during
|
||||
startup, so all segments containing relocations can be marked read only
|
||||
(when combined with read-only relocations). This is also referred to as
|
||||
“full RELRO”.
|
||||
(i.e., `.got.plt`) from being overwritten by instructing the dynamic linker to
|
||||
perform all relocations before transferring control to the program during
|
||||
startup, so all segments containing relocations can be marked read only (when
|
||||
combined with read-only relocations). This is also referred to as “full RELRO”.
|
||||
|
||||
The Rust compiler supports immediate binding, and enables it by default
|
||||
since version 1.21.0 (2017-10-12)[30], [31].
|
||||
The Rust compiler supports immediate binding, and enables it by default since
|
||||
version 1.21.0 (2017-10-12)[30], [31].
|
||||
|
||||
```text
|
||||
$ readelf -d target/release/hello-rust | grep BIND_NOW
|
||||
@ -270,16 +271,15 @@ Fig. 10. Checking if immediate binding is enabled for a given binary.
|
||||
|
||||
The presence of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW`
|
||||
flag<sup id="fnref:4" role="doc-noteref"><a href="#fn:4"
|
||||
class="footnote">4</a></sup> in the dynamic section indicates immediate
|
||||
binding is enabled for a given binary (see Fig. 10). Conversely, the absence
|
||||
of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the
|
||||
dynamic section indicates immediate binding is not enabled for a given
|
||||
binary.
|
||||
class="footnote">4</a></sup> in the dynamic section indicates immediate binding
|
||||
is enabled for a given binary (see Fig. 10). Conversely, the absence of an
|
||||
element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag in the dynamic
|
||||
section indicates immediate binding is not enabled for a given binary.
|
||||
|
||||
The presence of both an element of type `PT_GNU_RELRO` in the program header
|
||||
table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW`
|
||||
flag in the dynamic section indicates full RELRO is enabled for a given
|
||||
binary (see Fig. 9 and Fig. 10).
|
||||
table and of an element with the `DT_BIND_NOW` tag and the `DF_BIND_NOW` flag
|
||||
in the dynamic section indicates full RELRO is enabled for a given binary (see
|
||||
Figs. 9–10).
|
||||
|
||||
<small id="fn:4">4\. And the `DF_1_NOW` flag for some link editors. <a
|
||||
href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
@ -287,26 +287,24 @@ href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
||||
### Heap corruption protection
|
||||
|
||||
Heap corruption protection protects memory allocated dynamically by
|
||||
performing several checks, such as checks for corrupted links between list
|
||||
elements, invalid pointers, invalid sizes, double/multiple “frees” of the
|
||||
same memory allocated, and many corner cases of these. These checks are
|
||||
implementation specific, and vary per allocator.
|
||||
Heap corruption protection protects memory allocated dynamically by performing
|
||||
several checks, such as checks for corrupted links between list elements,
|
||||
invalid pointers, invalid sizes, double/multiple “frees” of the same memory
|
||||
allocated, and many corner cases of these. These checks are implementation
|
||||
specific, and vary per allocator.
|
||||
|
||||
[ARM Memory Tagging Extension
|
||||
(MTE)](https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/enhancing-memory-safety),
|
||||
when available, will provide hardware assistance for a probabilistic
|
||||
mitigation to detect memory safety violations by tagging memory allocations,
|
||||
and automatically checking that the correct tag is used on every memory
|
||||
access.
|
||||
when available, will provide hardware assistance for a probabilistic mitigation
|
||||
to detect memory safety violations by tagging memory allocations, and
|
||||
automatically checking that the correct tag is used on every memory access.
|
||||
|
||||
Rust’s default allocator has historically been
|
||||
[jemalloc](http://jemalloc.net/), and it has long been the cause of issues
|
||||
and the subject of much discussion[32]–[38]. Consequently, it has been
|
||||
removed as the default allocator in favor of the operating system’s standard
|
||||
C library default allocator<sup id="fnref:5" role="doc-noteref"><a
|
||||
href="#fn:5" class="footnote">5</a></sup> since version 1.32.0
|
||||
(2019-01-17)[39].
|
||||
[jemalloc](http://jemalloc.net/), and it has long been the cause of issues and
|
||||
the subject of much discussion[32]–[38]. Consequently, it has been removed as
|
||||
the default allocator in favor of the operating system’s standard C library
|
||||
default allocator<sup id="fnref:5" role="doc-noteref"><a href="#fn:5"
|
||||
class="footnote">5</a></sup> since version 1.32.0 (2019-01-17)[39].
|
||||
|
||||
```rust,no_run
|
||||
fn main() {
|
||||
@ -330,8 +328,7 @@ $ cargo run
|
||||
free(): invalid next size (normal)
|
||||
Aborted
|
||||
```
|
||||
Fig. 12. Build and execution of hello-rust-heap with debug assertions
|
||||
enabled.
|
||||
Fig. 12. Build and execution of hello-rust-heap with debug assertions enabled.
|
||||
|
||||
```text
|
||||
$ cargo run --release
|
||||
@ -341,47 +338,41 @@ $ cargo run --release
|
||||
free(): invalid next size (normal)
|
||||
Aborted
|
||||
```
|
||||
Fig. 13. Build and execution of hello-rust-heap with debug assertions
|
||||
disabled.
|
||||
Fig. 13. Build and execution of hello-rust-heap with debug assertions disabled.
|
||||
|
||||
Heap corruption checks are being performed when using the default allocator
|
||||
(i.e., the GNU Allocator) as seen in Fig. 12 and Fig. 13.
|
||||
Heap corruption checks are performed when using the default allocator (i.e.,
|
||||
the GNU Allocator) (see Figs. 12–13).
|
||||
|
||||
<small id="fn:5">5\. Linux's standard C library default allocator is the GNU
|
||||
Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram
|
||||
Gloger, which in turn is derived from dlmalloc (Doug Lea malloc) by Doug
|
||||
Lea. <a href="#fnref:5" class="reversefootnote"
|
||||
role="doc-backlink">↩</a></small>
|
||||
Allocator, which is derived from ptmalloc (pthreads malloc) by Wolfram Gloger,
|
||||
which in turn is derived from dlmalloc (Doug Lea malloc) by Doug Lea. <a
|
||||
href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
||||
|
||||
### Stack smashing protection
|
||||
|
||||
Stack smashing protection protects programs from stack-based buffer
|
||||
overflows by inserting a random guard value between local variables and the
|
||||
saved return instruction pointer, and checking if this value has changed
|
||||
when returning from a function. This is also known as “Stack Protector” or
|
||||
“Stack Smashing Protector (SSP)”.
|
||||
Stack smashing protection protects programs from stack-based buffer overflows
|
||||
by inserting a random guard value between local variables and the saved return
|
||||
instruction pointer, and checking if this value has changed when returning from
|
||||
a function. This is also known as “Stack Protector” or “Stack Smashing
|
||||
Protector (SSP)”.
|
||||
|
||||
The Rust compiler supports stack smashing protection on nightly builds[42].
|
||||
The Rust compiler supports stack smashing protection on nightly builds[40].
|
||||
|
||||
![Screenshot of IDA Pro listing cross references to __stack_chk_fail in hello-rust.](images/image3.png "Cross references to __stack_chk_fail in hello-rust.")
|
||||
Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in
|
||||
hello-rust.
|
||||
Fig. 14. IDA Pro listing cross references to `__stack_chk_fail` in hello-rust.
|
||||
|
||||
To check if stack smashing protection is enabled for a given binary, search
|
||||
for cross references to `__stack_chk_fail`. The presence of these
|
||||
cross-references in Rust-compiled code (e.g., `hello_rust::main`) indicates
|
||||
that the stack smashing protection is enabled (see Fig. 14).
|
||||
To check if stack smashing protection is enabled for a given binary, search for
|
||||
cross references to `__stack_chk_fail` (see Fig. 14).
|
||||
|
||||
|
||||
### Forward-edge control flow protection
|
||||
|
||||
Forward-edge control flow protection protects programs from having its
|
||||
control flow changed/hijacked by performing checks to ensure that
|
||||
destinations of indirect branches are one of their valid destinations in the
|
||||
control flow graph. The comprehensiveness of these checks vary per
|
||||
implementation. This is also known as “forward-edge control flow integrity
|
||||
(CFI)”.
|
||||
Forward-edge control flow protection protects programs from having its control
|
||||
flow changed/hijacked by performing checks to ensure that destinations of
|
||||
indirect branches are one of their valid destinations in the control flow
|
||||
graph. The comprehensiveness of these checks vary per implementation. This is
|
||||
also known as “forward-edge control flow integrity (CFI)”.
|
||||
|
||||
Newer processors provide hardware assistance for forward-edge control flow
|
||||
protection, such as ARM Branch Target Identification (BTI), ARM Pointer
|
||||
@ -394,22 +385,19 @@ commercially available [grsecurity/PaX Reuse Attack Protector
|
||||
(RAP)](https://grsecurity.net/rap_faq).
|
||||
|
||||
The Rust compiler supports forward-edge control flow protection on nightly
|
||||
builds[40]-[41] <sup id="fnref:6" role="doc-noteref"><a href="#fn:6"
|
||||
builds[41]-[42] <sup id="fnref:6" role="doc-noteref"><a href="#fn:6"
|
||||
class="footnote">6</a></sup>.
|
||||
|
||||
```text
|
||||
$ readelf -s -W target/debug/rust-cfi | grep "\.cfi"
|
||||
12: 0000000000005170 46 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_one.cfi
|
||||
15: 00000000000051a0 16 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi7add_two.cfi
|
||||
17: 0000000000005270 396 FUNC LOCAL DEFAULT 14 _RNvCsjaOHoaNjor6_8rust_cfi4main.cfi
|
||||
...
|
||||
$ readelf -s -W target/release/hello-rust | grep "\.cfi"
|
||||
5: 0000000000006480 657 FUNC LOCAL DEFAULT 15 _ZN10hello_rust4main17h4e359f1dcd627c83E.cfi
|
||||
```
|
||||
Fig. 15. Checking if LLVM CFI is enabled for a given binary[41].
|
||||
Fig. 15. Checking if LLVM CFI is enabled for a given binary.
|
||||
|
||||
The presence of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and
|
||||
references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge control
|
||||
flow protection) is enabled for a given binary. Conversely, the absence of
|
||||
symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
|
||||
references to `__cfi_check`) indicates that LLVM CFI (i.e., forward-edge
|
||||
control flow protection) is enabled for a given binary. Conversely, the absence
|
||||
of symbols suffixed with ".cfi" or the `__cfi_init` symbol (and references to
|
||||
`__cfi_check`) indicates that LLVM CFI is not enabled for a given binary (see
|
||||
Fig. 15).
|
||||
|
||||
@ -421,48 +409,47 @@ class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
### Backward-edge control flow protection
|
||||
|
||||
**Shadow stack** protects saved return instruction pointers from being
|
||||
overwritten by storing a copy of them on a separate (shadow) stack, and
|
||||
using these copies as authoritative values when returning from functions.
|
||||
This is also known as “ShadowCallStack” and “Return Flow Guard”, and is
|
||||
considered an implementation of backward-edge control flow protection (or
|
||||
“backward-edge CFI”).
|
||||
overwritten by storing a copy of them on a separate (shadow) stack, and using
|
||||
these copies as authoritative values when returning from functions. This is
|
||||
also known as “ShadowCallStack” and “Return Flow Guard”, and is considered an
|
||||
implementation of backward-edge control flow protection (or “backward-edge
|
||||
CFI”).
|
||||
|
||||
**Safe stack** protects not only the saved return instruction pointers, but
|
||||
also register spills and some local variables from being overwritten by
|
||||
storing unsafe variables, such as large arrays, on a separate (unsafe)
|
||||
stack, and using these unsafe variables on the separate stack instead. This
|
||||
is also known as “SafeStack”, and is also considered an implementation of
|
||||
backward-edge control flow protection.
|
||||
also register spills and some local variables from being overwritten by storing
|
||||
unsafe variables, such as large arrays, on a separate (unsafe) stack, and using
|
||||
these unsafe variables on the separate stack instead. This is also known as
|
||||
“SafeStack”, and is also considered an implementation of backward-edge control
|
||||
flow protection.
|
||||
|
||||
Both shadow and safe stack are intended to be a more comprehensive
|
||||
alternatives to stack smashing protection as they protect the saved return
|
||||
instruction pointers (and other data in the case of safe stack) from
|
||||
arbitrary writes and non-linear out-of-bounds writes.
|
||||
Both shadow and safe stack are intended to be a more comprehensive alternatives
|
||||
to stack smashing protection as they protect the saved return instruction
|
||||
pointers (and other data in the case of safe stack) from arbitrary writes and
|
||||
non-linear out-of-bounds writes.
|
||||
|
||||
Newer processors provide hardware assistance for backward-edge control flow
|
||||
protection, such as ARM Pointer Authentication, and Intel Shadow Stack as
|
||||
part of Intel CET.
|
||||
protection, such as ARM Pointer Authentication, and Intel Shadow Stack as part
|
||||
of Intel CET.
|
||||
|
||||
The Rust compiler supports shadow stack for aarch64 only
|
||||
<sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup>
|
||||
on nightly Rust compilers [43]-[44]. Safe stack is available on nightly
|
||||
Rust compilers [45]-[46].
|
||||
The Rust compiler supports shadow stack for the AArch64 architecture<sup
|
||||
id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote">7</a></sup>on
|
||||
nightly builds[43]-[44], and also supports safe stack on nightly
|
||||
builds[45]-[46].
|
||||
|
||||
```text
|
||||
$ readelf -s target/release/hello-rust | grep __safestack_init
|
||||
1177: 00000000000057b0 444 FUNC GLOBAL DEFAULT 9 __safestack_init
|
||||
678: 0000000000008c80 426 FUNC GLOBAL DEFAULT 15 __safestack_init
|
||||
```
|
||||
Fig. 16. Checking if LLVM SafeStack is enabled for a given binary.
|
||||
|
||||
The presence of the `__safestack_init` symbol indicates that LLVM SafeStack
|
||||
is enabled for a given binary (see Fig. 16). Conversely, the absence of the
|
||||
`__safestack_init` symbol indicates that LLVM SafeStack is not enabled for a
|
||||
given binary.
|
||||
The presence of the `__safestack_init` symbol indicates that LLVM SafeStack is
|
||||
enabled for a given binary. Conversely, the absence of the `__safestack_init`
|
||||
symbol indicates that LLVM SafeStack is not enabled for a given binary (see
|
||||
Fig. 16).
|
||||
|
||||
<small id="fn:7">7\. The shadow stack implementation for the AMD64
|
||||
architecture and equivalent in LLVM was removed due to performance and
|
||||
security issues. <a href="#fnref:7" class="reversefootnote"
|
||||
role="doc-backlink">↩</a></small>
|
||||
<small id="fn:7">7\. The shadow stack implementation for the AMD64 architecture
|
||||
and equivalent in LLVM was removed due to performance and security issues. <a
|
||||
href="#fnref:7" class="reversefootnote" role="doc-backlink">↩</a></small>
|
||||
|
||||
|
||||
## Appendix
|
||||
@ -470,29 +457,28 @@ role="doc-backlink">↩</a></small>
|
||||
As of the latest version of the [Linux Standard Base (LSB) Core
|
||||
Specification](https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/progheader.html),
|
||||
the `PT_GNU_STACK` program header indicates whether the stack should be
|
||||
executable, and the absence of this header indicates that the stack should
|
||||
be executable. However, the Linux kernel currently sets the
|
||||
`READ_IMPLIES_EXEC` personality upon loading any executable with the
|
||||
`PT_GNU_STACK` program header and the `PF_X `flag set or with the absence of
|
||||
this header, resulting in not only the stack, but also all readable virtual
|
||||
memory mappings being executable.
|
||||
executable, and the absence of this header indicates that the stack should be
|
||||
executable. However, the Linux kernel currently sets the `READ_IMPLIES_EXEC`
|
||||
personality upon loading any executable with the `PT_GNU_STACK` program header
|
||||
and the `PF_X` flag set or with the absence of this header, resulting in not
|
||||
only the stack, but also all readable virtual memory mappings being executable.
|
||||
|
||||
An attempt to fix this [was made in
|
||||
2012](https://lore.kernel.org/lkml/f298f914-2239-44e4-8aa1-a51282e7fac0@zmail15.collab.prod.int.phx2.redhat.com/),
|
||||
and another [was made in
|
||||
2020](https://lore.kernel.org/kernel-hardening/20200327064820.12602-1-keescook@chromium.org/).
|
||||
The former never landed, and the latter partially fixed it, but introduced
|
||||
other issues—the absence of the `PT_GNU_STACK` program header still causes
|
||||
not only the stack, but also all readable virtual memory mappings to be
|
||||
executable in some architectures, such as IA-32 and equivalent (or causes
|
||||
the stack to be non-executable in some architectures, such as AMD64 and
|
||||
equivalent, contradicting the LSB).
|
||||
other issues—the absence of the `PT_GNU_STACK` program header still causes not
|
||||
only the stack, but also all readable virtual memory mappings to be executable
|
||||
in some architectures, such as IA-32 and equivalent (or causes the stack to be
|
||||
non-executable in some architectures, such as AMD64 and equivalent,
|
||||
contradicting the LSB).
|
||||
|
||||
The `READ_IMPLIES_EXEC` personality needs to be completely separated from
|
||||
the `PT_GNU_STACK` program header by having a separate option for it (or
|
||||
setarch -X could just be used whenever `READ_IMPLIES_EXEC` is needed), and
|
||||
the absence of the `PT_GNU_STACK` program header needs to have more secure
|
||||
defaults (unrelated to `READ_IMPLIES_EXEC`).
|
||||
The `READ_IMPLIES_EXEC` personality needs to be completely separated from the
|
||||
`PT_GNU_STACK` program header by having a separate option for it (or setarch -X
|
||||
could just be used whenever `READ_IMPLIES_EXEC` is needed), and the absence of
|
||||
the `PT_GNU_STACK` program header needs to have more secure defaults (unrelated
|
||||
to `READ_IMPLIES_EXEC`).
|
||||
|
||||
|
||||
## References
|
||||
@ -576,19 +562,19 @@ defaults (unrelated to `READ_IMPLIES_EXEC`).
|
||||
25. A. Clark. “Explicitly disable stack execution on linux and bsd #30859.”
|
||||
GitHub. <https://github.com/rust-lang/rust/pull/30859>.
|
||||
|
||||
26. “Replace stack overflow checking with stack probes #16012.” GitHub.
|
||||
26. Zoxc. “Replace stack overflow checking with stack probes #16012.” GitHub.
|
||||
<https://github.com/rust-lang/rust/issues/16012>.
|
||||
|
||||
27. B. Striegel. “Extend stack probe support to non-tier-1 platforms, and
|
||||
clarify policy for mitigating LLVM-dependent unsafety #43241.” GitHub.
|
||||
<https://github.com/rust-lang/rust/issues/43241>.
|
||||
|
||||
28. A. Crichton. “rustc: Implement stack probes for x86 #42816.” GitHub.
|
||||
27. A. Crichton. “rustc: Implement stack probes for x86 #42816.” GitHub.
|
||||
<https://github.com/rust-lang/rust/pull/42816>.
|
||||
|
||||
29. A. Crichton. “Add \_\_rust\_probestack intrinsic #175.” GitHub.
|
||||
28. A. Crichton. “Add \_\_rust\_probestack intrinsic #175.” GitHub.
|
||||
<https://github.com/rust-lang/compiler-builtins/pull/175>.
|
||||
|
||||
29. S. Guelton, S. Ledru, J. Stone. “Bringing Stack Clash Protection to Clang /
|
||||
X86 — the Open Source Way.” The LLVM Project Blog.
|
||||
<https://blog.llvm.org/posts/2021-01-05-stack-clash-protection/>.
|
||||
|
||||
30. B. Anderson. “Consider applying -Wl,-z,relro or -Wl,-z,relro,-z,now by
|
||||
default #29877.” GitHub. <https://github.com/rust-lang/rust/issues/29877>.
|
||||
|
||||
@ -621,16 +607,16 @@ defaults (unrelated to `READ_IMPLIES_EXEC`).
|
||||
39. A. Crichton. “Remove the alloc\_jemalloc crate #55238.” GitHub.
|
||||
<https://github.com/rust-lang/rust/pull/55238>.
|
||||
|
||||
40. R. de C Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support
|
||||
for Rust #89653.” GitHub. <https://github.com/rust-lang/rust/issues/89653>.
|
||||
|
||||
41. “ControlFlowIntegrity.” The Rust Unstable Book.
|
||||
[https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#controlflowintegrity](../unstable-book/compiler-flags/sanitizer.html#controlflowintegrity).
|
||||
|
||||
42. bbjornse. “add codegen option for using LLVM stack smash protection #84197.”
|
||||
40. bbjornse. “Add codegen option for using LLVM stack smash protection #84197.”
|
||||
GitHub. <https://github.com/rust-lang/rust/pull/84197>
|
||||
|
||||
43. ivanloz. “Add support for LLVM ShadowCallStack. #98208.” GitHub.
|
||||
41. R. de C. Valle. “Tracking Issue for LLVM Control Flow Integrity (CFI) Support
|
||||
for Rust #89653.” GitHub. <https://github.com/rust-lang/rust/issues/89653>.
|
||||
|
||||
42. “ControlFlowIntegrity.” The Rust Unstable Book.
|
||||
[https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#controlflowintegrity](../unstable-book/compiler-flags/sanitizer.html#controlflowintegrity).
|
||||
|
||||
43. I. Lozano. “Add support for LLVM ShadowCallStack #98208.” GitHub.
|
||||
<https://github.com/rust-lang/rust/pull/98208>.
|
||||
|
||||
44. “ShadowCallStack.” The Rust Unstable Book.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 161 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 152 KiB |
Binary file not shown.
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
@ -2,11 +2,12 @@
|
||||
|
||||
mod repeat_n {
|
||||
#[doc(hidden)]
|
||||
/// not here
|
||||
pub struct RepeatN {}
|
||||
}
|
||||
|
||||
/// not here
|
||||
pub use repeat_n::RepeatN;
|
||||
|
||||
// @count "$.index[*][?(@.name=='pub_use_doc_hidden')].inner.items[*]" 0
|
||||
// @!has "$.index[*][?(@.kind=='struct')]"
|
||||
// @!has "$.index[*][?(@.kind=='import')]"
|
||||
// @!has "$.index[*][?(@.docs == 'not here')]"
|
||||
|
27
tests/ui/parser/semi-in-let-chain.rs
Normal file
27
tests/ui/parser/semi-in-let-chain.rs
Normal file
@ -0,0 +1,27 @@
|
||||
// Issue #117720
|
||||
|
||||
#![feature(let_chains)]
|
||||
|
||||
fn main() {
|
||||
if let () = ()
|
||||
&& let () = (); //~ERROR
|
||||
&& let () = ()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
if let () = ()
|
||||
&& () == (); //~ERROR
|
||||
&& 1 < 0
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
fn bar() {
|
||||
if let () = ()
|
||||
&& () == (); //~ERROR
|
||||
&& let () = ()
|
||||
{
|
||||
}
|
||||
}
|
50
tests/ui/parser/semi-in-let-chain.stderr
Normal file
50
tests/ui/parser/semi-in-let-chain.stderr
Normal file
@ -0,0 +1,50 @@
|
||||
error: expected `{`, found `;`
|
||||
--> $DIR/semi-in-let-chain.rs:7:23
|
||||
|
|
||||
LL | && let () = ();
|
||||
| ^ expected `{`
|
||||
|
|
||||
note: you likely meant to continue parsing the let-chain starting here
|
||||
--> $DIR/semi-in-let-chain.rs:8:9
|
||||
|
|
||||
LL | && let () = ()
|
||||
| ^^^^^^
|
||||
help: consider removing this semicolon to parse the `let` as part of the same chain
|
||||
|
|
||||
LL - && let () = ();
|
||||
LL + && let () = ()
|
||||
|
|
||||
|
||||
error: expected `{`, found `;`
|
||||
--> $DIR/semi-in-let-chain.rs:15:20
|
||||
|
|
||||
LL | && () == ();
|
||||
| ^ expected `{`
|
||||
|
|
||||
note: the `if` expression is missing a block after this condition
|
||||
--> $DIR/semi-in-let-chain.rs:14:8
|
||||
|
|
||||
LL | if let () = ()
|
||||
| ________^
|
||||
LL | | && () == ();
|
||||
| |___________________^
|
||||
|
||||
error: expected `{`, found `;`
|
||||
--> $DIR/semi-in-let-chain.rs:23:20
|
||||
|
|
||||
LL | && () == ();
|
||||
| ^ expected `{`
|
||||
|
|
||||
note: you likely meant to continue parsing the let-chain starting here
|
||||
--> $DIR/semi-in-let-chain.rs:24:9
|
||||
|
|
||||
LL | && let () = ()
|
||||
| ^^^^^^
|
||||
help: consider removing this semicolon to parse the `let` as part of the same chain
|
||||
|
|
||||
LL - && () == ();
|
||||
LL + && () == ()
|
||||
|
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
Loading…
Reference in New Issue
Block a user