Skip to content

Commit c5078ca

Browse files
committed
Remove inline assembly.
This is broken on nightly, since asm has been changed to llvm_asm. But rather than fixing it, we can simply drop it, because we now have an optimization barrier that works on stable Rust with no platform dependencies. Also, update the README and docs to reflect this change.
1 parent ae6aa12 commit c5078ca

File tree

2 files changed

+47
-54
lines changed

2 files changed

+47
-54
lines changed

README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,34 @@ It consists of a `Choice` type, and a collection of traits using `Choice`
66
instead of `bool` which are intended to execute in constant-time. The `Choice`
77
type is a wrapper around a `u8` that holds a `0` or `1`.
88

9+
```toml
10+
subtle = "2.2"
11+
```
12+
913
This crate represents a “best-effort” attempt, since side-channels
1014
are ultimately a property of a deployed cryptographic system
1115
including the hardware it runs on, not just of software.
1216

1317
The traits are implemented using bitwise operations, and should execute in
14-
constant time provided that a) the bitwise operations are constant-time and b)
15-
the operations are not optimized into a branch.
18+
constant time provided that a) the bitwise operations are constant-time and
19+
b) the bitwise operations are not recognized as a conditional assignment and
20+
optimized back into a branch.
1621

17-
To prevent the latter possibility, the crate attempts to hide the value of a
18-
`Choice`'s inner `u8` from the optimizer, by passing it through either an
19-
inline assembly block or a volatile read. For more information, see the
20-
_About_ section below.
21-
22-
```toml
23-
[dependencies.subtle]
24-
version = "2.2"
25-
```
22+
For a compiler to recognize that bitwise operations represent a conditional
23+
assignment, it needs to know that the value used to generate the bitmasks is
24+
really a boolean `i1` rather than an `i8` byte value. In an attempt to
25+
prevent this refinement, the crate tries to hide the value of a `Choice`'s
26+
inner `u8` by passing it through a volatile read. For more information, see
27+
the _About_ section below.
2628

2729
Versions prior to `2.2` recommended use of the `nightly` feature to enable an
2830
optimization barrier; this is not required in versions `2.2` and above.
2931

32+
Note: the `subtle` crate contains `debug_assert`s to check invariants during
33+
debug builds. These invariant checks involve secret-dependent branches, and
34+
are not present when compiled in release mode. This crate is intended to be
35+
used in release mode.
36+
3037
## Documentation
3138

3239
Documentation is available [here][docs].

src/lib.rs

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
// - Henry de Valence <hdevalence@hdevalence.ca>
1010

1111
#![no_std]
12-
#![cfg_attr(feature = "nightly", feature(asm))]
1312
#![cfg_attr(feature = "nightly", feature(external_doc))]
1413
#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
1514
#![cfg_attr(feature = "nightly", deny(missing_docs))]
@@ -24,25 +23,23 @@ extern crate std;
2423

2524
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not};
2625

27-
/// The `Choice` struct represents a choice for use in conditional
28-
/// assignment.
29-
///
30-
/// It is a wrapper around a `u8`, which should have the value either
31-
/// `1` (true) or `0` (false).
32-
///
33-
/// With the `nightly` feature enabled, the conversion from `u8` to
34-
/// `Choice` passes the value through an optimization barrier, as a
35-
/// best-effort attempt to prevent the compiler from inferring that the
36-
/// `Choice` value is a boolean. This strategy is based on Tim
37-
/// Maclean's [work on `rust-timing-shield`][rust-timing-shield],
38-
/// which attempts to provide a more comprehensive approach for
39-
/// preventing software side-channels in Rust code.
40-
///
41-
/// The `Choice` struct implements operators for AND, OR, XOR, and
42-
/// NOT, to allow combining `Choice` values.
43-
/// These operations do not short-circuit.
44-
///
45-
/// [rust-timing-shield]: https://www.chosenplaintext.ca/open-source/rust-timing-shield/security
26+
/// The `Choice` struct represents a choice for use in conditional assignment.
27+
///
28+
/// It is a wrapper around a `u8`, which should have the value either `1` (true)
29+
/// or `0` (false).
30+
///
31+
/// The conversion from `u8` to `Choice` passes the value through an optimization
32+
/// barrier, as a best-effort attempt to prevent the compiler from inferring that
33+
/// the `Choice` value is a boolean. This strategy is based on Tim Maclean's
34+
/// [work on `rust-timing-shield`][rust-timing-shield], which attempts to provide
35+
/// a more comprehensive approach for preventing software side-channels in Rust
36+
/// code.
37+
///
38+
/// The `Choice` struct implements operators for AND, OR, XOR, and NOT, to allow
39+
/// combining `Choice` values. These operations do not short-circuit.
40+
///
41+
/// [rust-timing-shield]:
42+
/// https://www.chosenplaintext.ca/open-source/rust-timing-shield/security
4643
#[derive(Copy, Clone, Debug)]
4744
pub struct Choice(u8);
4845

@@ -51,11 +48,11 @@ impl Choice {
5148
///
5249
/// # Note
5350
///
54-
/// This function only exists as an escape hatch for the rare case
51+
/// This function only exists as an **escape hatch** for the rare case
5552
/// where it's not possible to use one of the `subtle`-provided
5653
/// trait impls.
5754
///
58-
/// To convert a `Choice` to a `bool`, use the `From` implementation instead.
55+
/// **To convert a `Choice` to a `bool`, use the `From` implementation instead.**
5956
#[inline]
6057
pub fn unwrap_u8(&self) -> u8 {
6158
self.0
@@ -136,30 +133,19 @@ impl Not for Choice {
136133
}
137134
}
138135

139-
/// This function is a best-effort attempt to prevent the compiler
140-
/// from knowing anything about the value of the returned `u8`, other
141-
/// than its type.
142-
///
143-
/// Uses inline asm when available, otherwise it's a no-op.
144-
#[cfg(all(feature = "nightly", not(any(target_arch = "asmjs", target_arch = "wasm32"))))]
145-
#[inline(always)]
146-
fn black_box(mut input: u8) -> u8 {
147-
debug_assert!((input == 0u8) | (input == 1u8));
148-
149-
// Move value through assembler, which is opaque to the compiler, even though we don't do anything.
150-
unsafe { asm!("" : "=r"(input) : "0"(input) ) }
151-
152-
input
153-
}
154-
#[cfg(any(target_arch = "asmjs", target_arch = "wasm32", not(feature = "nightly")))]
136+
/// This function is a best-effort attempt to prevent the compiler from knowing
137+
/// anything about the value of the returned `u8`, other than its type.
138+
///
139+
/// Because we want to support stable Rust, we don't have access to inline
140+
/// assembly or test::black_box, so we use the fact that volatile values will
141+
/// never be elided to register values.
142+
///
143+
/// Note: Rust's notion of "volatile" is subject to change over time. While this
144+
/// code may break in a non-destructive way in the future, “constant-time” code
145+
/// is a continually moving target, and this is better than doing nothing.
155146
#[inline(never)]
156147
fn black_box(input: u8) -> u8 {
157148
debug_assert!((input == 0u8) | (input == 1u8));
158-
// We don't have access to inline assembly or test::black_box, so we use the fact that
159-
// volatile values will never be elided to register values.
160-
//
161-
// Note: Rust's notion of "volatile" is subject to change over time. While this code may break
162-
// in a non-destructive way in the future, it is better than doing nothing.
163149

164150
unsafe {
165151
// Optimization barrier

0 commit comments

Comments
 (0)