From d7f82726c0e7470c9d05fd35f0b849ed5b5ed216 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 13 Feb 2015 14:36:42 -0800 Subject: [PATCH 1/7] RFC: Never allow reads from uninitialized memory in safe Rust --- text/0000-uninit-memory-policy.md | 72 +++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 text/0000-uninit-memory-policy.md diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md new file mode 100644 index 00000000000..faa2f609a97 --- /dev/null +++ b/text/0000-uninit-memory-policy.md @@ -0,0 +1,72 @@ +- Feature Name: (fill me in with a unique ident, my_awesome_feature) +- Start Date: 2015-02-13 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Set an explicit policy that uninitialized memory can ever be exposed +in safe Rust, even when it would not lead to undefined behavior. + +# Motivation + +Exactly what is guaranteed by safe Rust code is not entirely +clear. There are some clear baseline guarantees: data-race freedom, +memory safety, type safety. But what about cases like reading from an +uninitialized, but allocated slice of scalars? These cases can be made +memory and typesafe, but they carry security risks. + +In particular, it may be possible to exploit a bug in safe Rust code +that leads that code to reveal the contents of memory. + +Consider the `std::io::Read` trait: + +```rust +pub trait Read { + fn read(&mut self, buf: &mut [u8]) -> Result; + + fn read_to_end(&mut self, buf: &mut Vec) -> Result<()> { ... } +} +``` + +The `read_to_end` convenience function will extend the given vector's capacity, +then pass the resulting (allocated but uninitialized) memory to the +underlying `read` method. + +While the `read` method may be implemented in pure safe code, it is +nonetheless given read access to uninitialized memory. The +implementation guarantees that no UB will arise as a result. But +nevertheless, an incorrect implementation of `read` -- for example, +one that returned an incorrect number of bytes read -- could result in +that memory being exposed (and then potentially sent over the wire). + +# Detailed design + +While we do not have a formal spec/contract for unsafe code, this RFC +will serve to set an explicit policy that: + +**Uninitialized memory can ever be exposed in safe Rust, even when it +would not lead to undefined behavior**. + +# Drawbacks + +In some cases, this policy may incur a performance overhead due to +having to initialize memory that will just be overwritten +later. However, these situations would be better served by improved +implementation techniques and/or introducing something like a `&out` +pointer expressing this idiom. + +In addition, in most cases `unsafe` variants of APIs can always be +provided for maximal performance. + +# Alternatives + +The main alternative is to limit safety in Rust to e.g. having defined +behavior (which generally entails memory and type safety and data-race +freedom). While this is a good baseline, it seems worthwhile to aspire +to greater guarantees where they come at relatively low cost. + +# Unresolved questions + +Are there APIs in `std` besides the convenience functions in IO that +this policy would affect? From b2c7cb11f70df1d024f0672a49bc242f61658e23 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 13 Feb 2015 14:40:50 -0800 Subject: [PATCH 2/7] Clarifications --- text/0000-uninit-memory-policy.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md index faa2f609a97..b32d661fcbe 100644 --- a/text/0000-uninit-memory-policy.md +++ b/text/0000-uninit-memory-policy.md @@ -35,10 +35,11 @@ underlying `read` method. While the `read` method may be implemented in pure safe code, it is nonetheless given read access to uninitialized memory. The -implementation guarantees that no UB will arise as a result. But -nevertheless, an incorrect implementation of `read` -- for example, -one that returned an incorrect number of bytes read -- could result in -that memory being exposed (and then potentially sent over the wire). +implementation of `read_to_end` guarantees that no UB will arise as a +result. But nevertheless, an incorrect implementation of `read` -- for +example, one that returned an incorrect number of bytes read -- could +result in that memory being exposed (and then potentially sent over +the wire). # Detailed design From 22c5996aad3e57c7e1b023120f32363691c4e1b4 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 13 Feb 2015 15:01:29 -0800 Subject: [PATCH 3/7] Clarifications --- text/0000-uninit-memory-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md index b32d661fcbe..958000021ee 100644 --- a/text/0000-uninit-memory-policy.md +++ b/text/0000-uninit-memory-policy.md @@ -46,7 +46,7 @@ the wire). While we do not have a formal spec/contract for unsafe code, this RFC will serve to set an explicit policy that: -**Uninitialized memory can ever be exposed in safe Rust, even when it +**Uninitialized memory can never be exposed in safe Rust, even when it would not lead to undefined behavior**. # Drawbacks From adf97bdbfd9b79f454216d928139aa5490cc2a64 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 13 Feb 2015 15:02:15 -0800 Subject: [PATCH 4/7] Clarifications --- text/0000-uninit-memory-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md index 958000021ee..3a54045692d 100644 --- a/text/0000-uninit-memory-policy.md +++ b/text/0000-uninit-memory-policy.md @@ -5,7 +5,7 @@ # Summary -Set an explicit policy that uninitialized memory can ever be exposed +Set an explicit policy that uninitialized memory can never be exposed in safe Rust, even when it would not lead to undefined behavior. # Motivation From d419cee598295a5464ba34cec42ea27fc07a4641 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 13 Feb 2015 15:03:55 -0800 Subject: [PATCH 5/7] Clarifications --- text/0000-uninit-memory-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md index 3a54045692d..c012c5a9f28 100644 --- a/text/0000-uninit-memory-policy.md +++ b/text/0000-uninit-memory-policy.md @@ -17,7 +17,7 @@ uninitialized, but allocated slice of scalars? These cases can be made memory and typesafe, but they carry security risks. In particular, it may be possible to exploit a bug in safe Rust code -that leads that code to reveal the contents of memory. +that causes that code to reveal the contents of memory. Consider the `std::io::Read` trait: From 229bca6e3e31c4ba8eeceb7c23fbd85da64a2318 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 13 Feb 2015 16:05:57 -0800 Subject: [PATCH 6/7] Clarify practical consequences --- text/0000-uninit-memory-policy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md index c012c5a9f28..ec962788085 100644 --- a/text/0000-uninit-memory-policy.md +++ b/text/0000-uninit-memory-policy.md @@ -49,6 +49,10 @@ will serve to set an explicit policy that: **Uninitialized memory can never be exposed in safe Rust, even when it would not lead to undefined behavior**. +In practical terms, this will require methods like `read_to_end` to +internally zero out (or otherwise ensure initialization of) memory +before they pass it to unknown safe code like the `read` method. + # Drawbacks In some cases, this policy may incur a performance overhead due to From a066ed5238764228f9a39125e0ccb13627b6069c Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Tue, 17 Feb 2015 15:35:00 -0800 Subject: [PATCH 7/7] Clarify applicability --- text/0000-uninit-memory-policy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-uninit-memory-policy.md b/text/0000-uninit-memory-policy.md index ec962788085..3307fd8868e 100644 --- a/text/0000-uninit-memory-policy.md +++ b/text/0000-uninit-memory-policy.md @@ -49,6 +49,10 @@ will serve to set an explicit policy that: **Uninitialized memory can never be exposed in safe Rust, even when it would not lead to undefined behavior**. +Like other aspects of the definition of "safe Rust", this is part of +the contract that *all* unsafe code must abide by, whether part of +`std` or external libraries or applications. + In practical terms, this will require methods like `read_to_end` to internally zero out (or otherwise ensure initialization of) memory before they pass it to unknown safe code like the `read` method.