Skip to content

Commit 8a0d762

Browse files
committed
Harden backend boundary follow-up
1 parent ca89336 commit 8a0d762

14 files changed

Lines changed: 88 additions & 15 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@
6565
clear-tail, wrapped, alloc, and in-place encode paths through it, giving
6666
future SIMD encode admission one audited integration point while preserving
6767
scalar-only runtime behavior.
68+
- Added the matching scalar-forced decode backend boundary for future decode
69+
admission symmetry, changed the scalar in-place encode invariant to return an
70+
error instead of panicking in release builds, tightened panic-policy scanning
71+
for production equality assertions, and clarified that in-place clear-tail
72+
errors intentionally clear original caller input.
6873

6974
## 1.1.0 - 2026-06-20
7075

SECURITY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ write buffers, allocator behavior, core dumps, swap, hibernation images,
9090
cold-boot remanence, hardware observation, or other process memory disclosure
9191
bugs. Callers that require a platform-specific formal zeroization policy should
9292
apply that policy to their own buffers in addition to using crate cleanup APIs.
93+
For in-place clear-tail methods, "whole buffer on error" includes any original
94+
plaintext or encoded input already present in the caller's buffer. Preserve a
95+
separate audit/retry copy before calling an in-place clear-tail API if recovery
96+
of the original input is required after a validation or sizing failure.
9397

9498
High-assurance deployments handling classified or long-lived key material
9599
should pair `base64-ng` with OS and platform memory controls: locked memory

docs/PLAN.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,9 @@ Current `1.1.x` checkpoint state:
671671
`encode_slice_clear_tail`, alloc encode helpers, wrapped encode helpers, and
672672
in-place encode now route through one audited encode backend boundary, with
673673
scalar-equivalence tests covering slice and in-place encode behavior.
674+
Follow-up review also added the matching scalar-forced decode backend
675+
boundary so future decode acceleration has a symmetric admission point, and
676+
tightened panic-policy checks for production `assert_eq!`/`assert_ne!` sites.
674677

675678
Planned checkpoints to reach `1.2.0` fully working encode acceleration:
676679

docs/SECURITY_CONTROLS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ certification claim.
9898
`ct::CtEngine::decode_slice_staged_clear_tail` when the caller-owned output
9999
buffer could be observed during the decode call, such as shared memory,
100100
sandbox boundaries, enclave-adjacent code, or multi-principal processes.
101+
- Treat in-place clear-tail APIs as destructive cleanup boundaries. On error,
102+
they clear the full caller-provided buffer, including original plaintext or
103+
encoded input. Preserve a separate audit/retry copy before calling them if
104+
recovering the original input is part of the protocol.
101105
- Prefer fixed-size secret types generated by the `base64-ng-derive`
102106
`#[derive(Base64Secret)]` companion crate when protocol key sizes are known.
103107
The generated decode path uses staged CT decode, redacted `Debug`, and

docs/SIMD.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ The SIMD roadmap separates implementation evidence from active acceleration:
2929
- `1.1.5` adds the public encode backend boundary while still forcing scalar
3030
execution. This gives future accelerated encode admission one reviewed
3131
integration point for `encode_slice`, clear-tail helpers, alloc helpers,
32-
wrapped helpers, and in-place encode.
32+
wrapped helpers, and in-place encode. The same checkpoint also adds a
33+
scalar-forced decode backend boundary for symmetry; decode acceleration
34+
remains out of scope until the later decode line.
3335
- `1.2.0` is the release where encode acceleration must be fully working for
3436
the admitted encode scope. Public encode APIs must dispatch to admitted
3537
encode backends when runtime policy and CPU features allow it, and must fall

scripts/validate-panic-policy.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ check_file() {
2727
}
2828
failed = 1
2929
}
30-
/debug_assert!\(/ {
30+
/debug_assert!\(|debug_assert_eq!\(|debug_assert_ne!\(/ {
3131
next
3232
}
3333
FILENAME == "src/engine/encode.rs" && $0 ~ /^[[:space:]]*assert!\($/ {
3434
pending_encode_array_assert = 1
3535
next
3636
}
37-
/panic!\(|unreachable!\(|\.unwrap\(|\.expect\(|assert!\(/ {
37+
/panic!\(|unreachable!\(|\.unwrap\(|\.expect\(|assert!\(|assert_eq!\(|assert_ne!\(/ {
3838
allowed = 0
3939
if ($0 ~ /assert!\(line_len != 0, "base64 line wrap length must be non-zero"\)/) {
4040
allowed = 1
@@ -69,7 +69,7 @@ test -s docs/PANIC_POLICY.md
6969

7070
find src -name '*.rs' | sort | while IFS= read -r source_file; do
7171
case "$source_file" in
72-
src/kani_proofs.rs|src/tests.rs|src/simd/tests.rs)
72+
src/kani_proofs.rs|src/tests.rs|src/simd/tests.rs|src/simd/wasm.rs)
7373
continue
7474
;;
7575
esac

src/buffers/secret.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ impl ExposedSecretString {
183183
let text = match string_from_validated_secret_bytes(bytes) {
184184
Ok(text) => text,
185185
Err(mut bytes) => {
186+
// This branch is unreachable for bytes produced from a valid
187+
// `String`. If unsafe upstream code violates that invariant,
188+
// wipe the bytes and fail closed without introducing a
189+
// release-mode panic in secret cleanup code.
186190
wipe_vec_all(&mut bytes);
187191
alloc::string::String::new()
188192
}

src/decode_backend.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! Decode backend dispatch boundary.
2+
//!
3+
//! This module mirrors the encode backend boundary. Decode acceleration remains
4+
//! inactive; future SIMD decode admission must update this boundary together
5+
//! with canonicality, error-shape, fallback, retention, timing, and release
6+
//! evidence.
7+
8+
use crate::{Alphabet, DecodeError, scalar};
9+
10+
/// Decode backend currently allowed to execute.
11+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12+
pub(crate) enum DecodeBackend {
13+
/// The audited scalar implementation.
14+
Scalar,
15+
}
16+
17+
/// Returns the decode backend selected for this build and target.
18+
#[must_use]
19+
pub(crate) fn active_decode_backend() -> DecodeBackend {
20+
#[cfg(feature = "simd")]
21+
match crate::simd::active_backend() {
22+
crate::simd::ActiveBackend::Scalar => {}
23+
}
24+
25+
DecodeBackend::Scalar
26+
}
27+
28+
/// Decodes `input` into `output` through the admitted decode backend.
29+
pub(crate) fn decode_slice<A, const PAD: bool>(
30+
input: &[u8],
31+
output: &mut [u8],
32+
) -> Result<usize, DecodeError>
33+
where
34+
A: Alphabet,
35+
{
36+
match active_decode_backend() {
37+
DecodeBackend::Scalar => scalar::decode_slice::<A, PAD>(input, output),
38+
}
39+
}

src/engine/decode.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use crate::SecretBuffer;
33
#[cfg(feature = "alloc")]
44
use crate::validate_decode;
55
use crate::{
6-
Alphabet, DecodeError, DecodedBuffer, Engine, LineWrap, decode_legacy_to_slice,
7-
decode_wrapped_to_slice, scalar, validate_legacy_decode, validate_wrapped_decode, wipe_bytes,
6+
Alphabet, DecodeError, DecodedBuffer, Engine, LineWrap, decode_backend, decode_legacy_to_slice,
7+
decode_wrapped_to_slice, validate_legacy_decode, validate_wrapped_decode, wipe_bytes,
88
wipe_tail,
99
};
1010

@@ -33,7 +33,7 @@ where
3333
/// constant-time-oriented secret decoding.
3434
#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3535
pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
36-
scalar::decode_slice::<A, PAD>(input, output)
36+
decode_backend::decode_slice::<A, PAD>(input, output)
3737
}
3838

3939
/// Decodes `input` into `output` and clears all bytes after the decoded

src/engine/encode_in_place.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ where
4040
///
4141
/// If encoding fails because `input_len` is too large, the output buffer is
4242
/// too small, or the encoded length overflows `usize`, the entire buffer is
43-
/// cleared before the error is returned.
43+
/// cleared before the error is returned. This includes the original
44+
/// plaintext in `buffer[..input_len]`; keep another copy before calling
45+
/// this method if recovery of the original input is required after an
46+
/// error.
4447
///
4548
/// # Examples
4649
///

0 commit comments

Comments
 (0)