1818//!
1919//! # Security
2020//!
21- //! The read-all helpers use temporary `Vec<u8>` allocations and the normal
22- //! strict decode path. They wipe those temporary vectors before returning, but
23- //! they are not constant-time-oriented token validators or high-assurance
24- //! secret decoders. For secret-bearing async frames, collect a bounded frame
25- //! under the application's approved memory policy and decode through
21+ //! The read-all helpers use RAII-guarded temporary `Vec<u8>` allocations and
22+ //! the normal strict decode path. The guards wipe initialized bytes and spare
23+ //! capacity on ordinary return, I/O error, or future cancellation. They are
24+ //! not constant-time-oriented token validators or high-assurance secret
25+ //! decoders. For secret-bearing async frames, collect a bounded frame under
26+ //! the application's approved memory policy and decode through
2627//! `base64_ng::ct`, staged CT decode, `base64-ng-derive`, or
2728//! `base64-ng-sanitization`.
2829
@@ -56,20 +57,15 @@ where
5657 R : AsyncRead + Unpin + ?Sized ,
5758 W : AsyncWrite + Unpin + ?Sized ,
5859{
59- let mut input = Vec :: new ( ) ;
60- reader. read_to_end ( & mut input) . await ?;
61- let mut output = match engine. encode_vec ( & input) . map_err ( encode_io_error) {
62- Ok ( output) => output,
63- Err ( error) => {
64- wipe_bytes ( & mut input) ;
65- return Err ( error) ;
66- }
67- } ;
60+ let mut input = WipingVec :: new ( ) ;
61+ reader. read_to_end ( input. as_mut_vec ( ) ) . await ?;
62+ let output = WipingVec :: from_vec (
63+ engine
64+ . encode_vec ( input. as_slice ( ) )
65+ . map_err ( encode_io_error) ?,
66+ ) ;
6867 let written = output. len ( ) as u64 ;
69- let result = writer. write_all ( & output) . await ;
70- wipe_bytes ( & mut input) ;
71- wipe_bytes ( & mut output) ;
72- result?;
68+ writer. write_all ( output. as_slice ( ) ) . await ?;
7369 Ok ( written)
7470}
7571
@@ -95,19 +91,14 @@ where
9591 R : AsyncRead + Unpin + ?Sized ,
9692 W : AsyncWrite + Unpin + ?Sized ,
9793{
98- let mut input = read_to_end_limited ( reader, max_input_len) . await ?;
99- let mut output = match engine. encode_vec ( & input) . map_err ( encode_io_error) {
100- Ok ( output) => output,
101- Err ( error) => {
102- wipe_bytes ( & mut input) ;
103- return Err ( error) ;
104- }
105- } ;
94+ let input = read_to_end_limited ( reader, max_input_len) . await ?;
95+ let output = WipingVec :: from_vec (
96+ engine
97+ . encode_vec ( input. as_slice ( ) )
98+ . map_err ( encode_io_error) ?,
99+ ) ;
106100 let written = output. len ( ) as u64 ;
107- let result = writer. write_all ( & output) . await ;
108- wipe_bytes ( & mut input) ;
109- wipe_bytes ( & mut output) ;
110- result?;
101+ writer. write_all ( output. as_slice ( ) ) . await ?;
111102 Ok ( written)
112103}
113104
@@ -130,20 +121,15 @@ where
130121 R : AsyncRead + Unpin + ?Sized ,
131122 W : AsyncWrite + Unpin + ?Sized ,
132123{
133- let mut input = Vec :: new ( ) ;
134- reader. read_to_end ( & mut input) . await ?;
135- let mut output = match engine. decode_vec ( & input) . map_err ( decode_io_error) {
136- Ok ( output) => output,
137- Err ( error) => {
138- wipe_bytes ( & mut input) ;
139- return Err ( error) ;
140- }
141- } ;
124+ let mut input = WipingVec :: new ( ) ;
125+ reader. read_to_end ( input. as_mut_vec ( ) ) . await ?;
126+ let output = WipingVec :: from_vec (
127+ engine
128+ . decode_vec ( input. as_slice ( ) )
129+ . map_err ( decode_io_error) ?,
130+ ) ;
142131 let written = output. len ( ) as u64 ;
143- let result = writer. write_all ( & output) . await ;
144- wipe_bytes ( & mut input) ;
145- wipe_bytes ( & mut output) ;
146- result?;
132+ writer. write_all ( output. as_slice ( ) ) . await ?;
147133 Ok ( written)
148134}
149135
@@ -169,19 +155,14 @@ where
169155 R : AsyncRead + Unpin + ?Sized ,
170156 W : AsyncWrite + Unpin + ?Sized ,
171157{
172- let mut input = read_to_end_limited ( reader, max_input_len) . await ?;
173- let mut output = match engine. decode_vec ( & input) . map_err ( decode_io_error) {
174- Ok ( output) => output,
175- Err ( error) => {
176- wipe_bytes ( & mut input) ;
177- return Err ( error) ;
178- }
179- } ;
158+ let input = read_to_end_limited ( reader, max_input_len) . await ?;
159+ let output = WipingVec :: from_vec (
160+ engine
161+ . decode_vec ( input. as_slice ( ) )
162+ . map_err ( decode_io_error) ?,
163+ ) ;
180164 let written = output. len ( ) as u64 ;
181- let result = writer. write_all ( & output) . await ;
182- wipe_bytes ( & mut input) ;
183- wipe_bytes ( & mut output) ;
184- result?;
165+ writer. write_all ( output. as_slice ( ) ) . await ?;
185166 Ok ( written)
186167}
187168
@@ -227,37 +208,85 @@ fn wipe_bytes(bytes: &mut [u8]) {
227208 base64_ng:: secure_wipe ( bytes) ;
228209}
229210
230- async fn read_to_end_limited < R > ( reader : & mut R , max_input_len : usize ) -> io:: Result < Vec < u8 > >
211+ struct WipingVec ( Vec < u8 > ) ;
212+
213+ impl WipingVec {
214+ fn new ( ) -> Self {
215+ Self ( Vec :: new ( ) )
216+ }
217+
218+ fn with_capacity ( capacity : usize ) -> Self {
219+ Self ( Vec :: with_capacity ( capacity) )
220+ }
221+
222+ fn from_vec ( bytes : Vec < u8 > ) -> Self {
223+ Self ( bytes)
224+ }
225+
226+ fn as_slice ( & self ) -> & [ u8 ] {
227+ & self . 0
228+ }
229+
230+ fn as_mut_vec ( & mut self ) -> & mut Vec < u8 > {
231+ & mut self . 0
232+ }
233+
234+ fn len ( & self ) -> usize {
235+ self . 0 . len ( )
236+ }
237+ }
238+
239+ impl Drop for WipingVec {
240+ fn drop ( & mut self ) {
241+ // Initialize the existing spare capacity without reallocating so the
242+ // reviewed wipe primitive covers the complete allocation.
243+ self . 0 . resize ( self . 0 . capacity ( ) , 0 ) ;
244+ wipe_bytes ( & mut self . 0 ) ;
245+ self . 0 . clear ( ) ;
246+ }
247+ }
248+
249+ struct WipingArray < const N : usize > ( [ u8 ; N ] ) ;
250+
251+ impl < const N : usize > WipingArray < N > {
252+ const fn new ( ) -> Self {
253+ Self ( [ 0 ; N ] )
254+ }
255+ }
256+
257+ impl < const N : usize > Drop for WipingArray < N > {
258+ fn drop ( & mut self ) {
259+ wipe_bytes ( & mut self . 0 ) ;
260+ }
261+ }
262+
263+ async fn read_to_end_limited < R > ( reader : & mut R , max_input_len : usize ) -> io:: Result < WipingVec >
231264where
232265 R : AsyncRead + Unpin + ?Sized ,
233266{
234- let mut input = Vec :: with_capacity ( max_input_len. min ( READ_ALL_EAGER_CAP ) ) ;
235- let mut chunk = [ 0u8 ; 8192 ] ;
267+ let mut input = WipingVec :: with_capacity ( max_input_len. min ( READ_ALL_EAGER_CAP ) ) ;
268+ let mut chunk = WipingArray :: < 8192 > :: new ( ) ;
236269
237270 loop {
238- let read = match reader. read ( & mut chunk) . await {
239- Ok ( read) => read,
240- Err ( error) => {
241- wipe_bytes ( & mut chunk) ;
242- wipe_bytes ( & mut input) ;
243- return Err ( error) ;
244- }
271+ let remaining = max_input_len - input. len ( ) ;
272+ let read_cap = if remaining < chunk. 0 . len ( ) {
273+ remaining + 1
274+ } else {
275+ chunk. 0 . len ( )
245276 } ;
277+ let read = reader. read ( & mut chunk. 0 [ ..read_cap] ) . await ?;
246278 if read == 0 {
247- wipe_bytes ( & mut chunk) ;
248279 return Ok ( input) ;
249280 }
250281
251- if read > max_input_len. saturating_sub ( input. len ( ) ) {
252- wipe_bytes ( & mut chunk) ;
253- wipe_bytes ( & mut input) ;
282+ if read > remaining {
254283 return Err ( io:: Error :: new (
255284 io:: ErrorKind :: InvalidData ,
256285 "base64-ng-tokio input exceeds configured limit" ,
257286 ) ) ;
258287 }
259288
260- input. extend_from_slice ( & chunk[ ..read] ) ;
261- wipe_bytes ( & mut chunk) ;
289+ input. 0 . extend_from_slice ( & chunk. 0 [ ..read] ) ;
290+ wipe_bytes ( & mut chunk. 0 ) ;
262291 }
263292}
0 commit comments