Skip to content

Commit 2b9433b

Browse files
committed
Harden CT barrier and secret string handling
1 parent 7e70e13 commit 2b9433b

8 files changed

Lines changed: 273 additions & 36 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,10 @@ is unavoidable, call
558558
`into_exposed_unprotected_vec_caller_must_zeroize`; that method name is
559559
intentionally loud because cleanup becomes the caller's responsibility.
560560
`try_into_exposed_string` provides an explicit escape hatch for UTF-8 text and
561-
returns the redacted wrapper unchanged when the bytes are not valid UTF-8.
561+
returns an `ExposedSecretString`, which keeps redacted formatting and
562+
best-effort drop-time cleanup. If a raw `String` is unavoidable, call
563+
`into_exposed_unprotected_string_caller_must_zeroize`; cleanup then becomes the
564+
caller responsibility. Invalid UTF-8 returns the redacted wrapper unchanged.
562565

563566
`SecretBuffer` also implements `From<Vec<u8>>` and `From<String>` for callers
564567
that already own sensitive bytes or text and want to move them into the

docs/API_AUDIT.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ Decision rationale:
158158
redacted formatting and drop-time cleanup. The raw-vector escape hatch is the
159159
deliberately loud
160160
`ExposedSecretVec::into_exposed_unprotected_vec_caller_must_zeroize`.
161+
- `SecretBuffer::try_into_exposed_string` returns `ExposedSecretString`, which
162+
keeps redacted formatting and drop-time cleanup. The raw-string escape hatch
163+
is the deliberately loud
164+
`ExposedSecretString::into_exposed_unprotected_string_caller_must_zeroize`.
161165
- Drop-time cleanup uses the crate's volatile best-effort wipe helper for
162166
initialized bytes and vector spare capacity.
163167
- Equality is intentionally not exposed through `PartialEq`/`==`. Callers must
@@ -172,7 +176,8 @@ Stable boundary:
172176
- Keep redaction as the default formatting behavior.
173177
- Keep `expose_secret`, `into_exposed_vec`,
174178
`into_exposed_unprotected_vec_caller_must_zeroize`, and
175-
`try_into_exposed_string` explicit.
179+
`try_into_exposed_string`,
180+
`into_exposed_unprotected_string_caller_must_zeroize` explicit.
176181
- Do not claim formal zeroization or allocator-wide cleanup.
177182
- Do not add broad conversions that hide profile, alphabet, padding, or
178183
wrapping policy.

docs/CONSTANT_TIME.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,13 @@ Before reporting the opaque malformed-input result, the ct decoder passes the
264264
accumulated error mask through a non-inlined `ct_error_gate_barrier` that uses
265265
`core::hint::black_box`, a compiler fence, and architecture-specific hardware
266266
speculation barriers where available (`lfence` on x86/x86_64, `isb sy` on ARM,
267-
and `isb sy; hint #20` on AArch64). This is defense in depth around the final
268-
public success/failure gate; it does not make the ct decoder a formally
269-
verified hardware side-channel resistant primitive and does not change the
270-
transient-output window described above.
267+
and `isb sy; hint #20` on AArch64). On RISCV the gate uses `fence rw, rw`,
268+
which is an ordering fence rather than a canonical speculation barrier in the
269+
base ISA. The runtime backend report exposes this separately through
270+
`ct_gate_posture`. This is defense in depth around the final public
271+
success/failure gate; it does not make the ct decoder a formally verified
272+
hardware side-channel resistant primitive and does not change the transient
273+
output window described above.
271274

272275
For shared-memory or in-process sandbox threat models where even that transient
273276
output window is unacceptable, use
@@ -306,9 +309,12 @@ type, and the final equality result as public. The helper scans every byte for
306309
equal-length inputs before returning. The per-byte difference is passed through
307310
`core::hint::black_box`; the accumulator is also passed through `black_box`
308311
after each OR reduction to reduce the risk of release-mode optimizer rewrites
309-
into early-exit equality checks. The helper is marked `#[inline(never)]` and
310-
the release evidence script checks that `constant_time_eq_public_len` remains
311-
visible as a separate text symbol in the LTO artifact.
312+
into early-exit equality checks. Before returning the public equality result,
313+
the helper also passes the accumulated difference through the same non-inlined
314+
CT gate barrier used by malformed-input reporting. The helper is marked
315+
`#[inline(never)]` and the release evidence script checks that
316+
`constant_time_eq_public_len` remains visible as a separate text symbol in the
317+
LTO artifact.
312318

313319
This remains a best-effort API and does not upgrade `base64-ng` to a formally
314320
verified cryptographic constant-time comparison crate. Do not use this helper

docs/SECURITY_CONTROLS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ The caller still owns:
4646
- treating `ExposedSecretVec::into_exposed_unprotected_vec_caller_must_zeroize`
4747
as a boundary where redacted formatting and crate-owned drop-time cleanup
4848
intentionally stop
49+
- treating
50+
`ExposedSecretString::into_exposed_unprotected_string_caller_must_zeroize` as
51+
a boundary where redacted formatting and crate-owned drop-time cleanup
52+
intentionally stop
4953
- process-wide memory hygiene such as core-dump policy, swap policy, crash
5054
handling, allocator behavior, and log retention
5155
- deciding whether the constant-time-oriented API is sufficient for the local

docs/UNSAFE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ Preconditions:
161161
Unsafe operation:
162162

163163
- `core::arch::asm!` emits `lfence` on non-Miri `x86`/`x86_64`, `isb sy` on
164-
non-Miri `arm`, and `isb sy; hint #20` on non-Miri `aarch64`.
164+
non-Miri `arm`, `isb sy; hint #20` on non-Miri `aarch64`, and
165+
`fence rw, rw` on non-Miri `riscv32`/`riscv64`.
165166

166167
Safety argument:
167168

@@ -176,6 +177,9 @@ Limitations:
176177
- This is defense in depth against speculation around the final public
177178
malformed-input result. It does not make the ct decoder a formally verified
178179
hardware side-channel resistant primitive.
180+
- RISC-V base ISA has no canonical speculation barrier. The crate reports its
181+
CT gate posture as `ordering-fence` on RISCV rather than
182+
`hardware-speculation-barrier`.
179183
- Unsupported architectures fall back to the compiler fence only.
180184

181185
### `wipe_vec_spare_capacity`

scripts/check_targets.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env sh
22
set -eu
33

4-
targets="${*:-x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-unknown-freebsd wasm32-unknown-unknown thumbv7em-none-eabihf}"
4+
targets="${*:-x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-unknown-freebsd riscv64gc-unknown-linux-gnu riscv32imac-unknown-none-elf wasm32-unknown-unknown thumbv7em-none-eabihf}"
55
installed="$(rustup target list --installed)"
66

77
for target in $targets; do

0 commit comments

Comments
 (0)