Skip to content

Commit 9f02eb9

Browse files
committed
Address low severity pentest hardening
1 parent b12ccb7 commit 9f02eb9

9 files changed

Lines changed: 482 additions & 41 deletions

File tree

crates/base64-ng-bytes/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,17 @@ assert_eq!(&encoded[..], b"aGVsbG8=");
1515
let decoded = STANDARD.decode_bytes(encoded).unwrap();
1616
assert_eq!(&decoded[..], b"hello");
1717
```
18+
19+
For peer-controlled `Buf` values, prefer the bounded helpers so a custom
20+
`Buf::remaining()` value cannot drive an unbounded temporary allocation:
21+
22+
```rust
23+
use base64_ng::STANDARD;
24+
use base64_ng_bytes::EngineBytesExt;
25+
use bytes::Bytes;
26+
27+
let encoded = STANDARD
28+
.encode_buf_limited(Bytes::from_static(b"hello"), 5)
29+
.unwrap();
30+
assert_eq!(&encoded[..], b"aGVsbG8=");
31+
```

crates/base64-ng-bytes/src/lib.rs

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,96 @@ use alloc::vec::Vec;
1212
use base64_ng::{Alphabet, DecodeError, EncodeError, Engine};
1313
use bytes::{Buf, BufMut, Bytes};
1414

15+
/// Encoding error for bounded `bytes` integration helpers.
16+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17+
pub enum BytesEncodeError {
18+
/// The input buffer reports more remaining bytes than the caller permits.
19+
InputTooLarge {
20+
/// Remaining input bytes reported by [`Buf::remaining`].
21+
input_len: usize,
22+
/// Caller-provided maximum input bytes.
23+
max_input_len: usize,
24+
},
25+
/// Base64 encoding failed after the input-size check passed.
26+
Encode(EncodeError),
27+
}
28+
29+
impl core::fmt::Display for BytesEncodeError {
30+
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31+
match self {
32+
Self::InputTooLarge {
33+
input_len,
34+
max_input_len,
35+
} => write!(
36+
formatter,
37+
"bytes input length {input_len} exceeds limit {max_input_len}"
38+
),
39+
Self::Encode(error) => core::fmt::Display::fmt(error, formatter),
40+
}
41+
}
42+
}
43+
44+
#[cfg(feature = "std")]
45+
impl std::error::Error for BytesEncodeError {
46+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
47+
match self {
48+
Self::InputTooLarge { .. } => None,
49+
Self::Encode(error) => Some(error),
50+
}
51+
}
52+
}
53+
54+
impl From<EncodeError> for BytesEncodeError {
55+
fn from(error: EncodeError) -> Self {
56+
Self::Encode(error)
57+
}
58+
}
59+
60+
/// Decoding error for bounded `bytes` integration helpers.
61+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62+
pub enum BytesDecodeError {
63+
/// The input buffer reports more remaining bytes than the caller permits.
64+
InputTooLarge {
65+
/// Remaining input bytes reported by [`Buf::remaining`].
66+
input_len: usize,
67+
/// Caller-provided maximum input bytes.
68+
max_input_len: usize,
69+
},
70+
/// Base64 decoding failed after the input-size check passed.
71+
Decode(DecodeError),
72+
}
73+
74+
impl core::fmt::Display for BytesDecodeError {
75+
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
76+
match self {
77+
Self::InputTooLarge {
78+
input_len,
79+
max_input_len,
80+
} => write!(
81+
formatter,
82+
"bytes input length {input_len} exceeds limit {max_input_len}"
83+
),
84+
Self::Decode(error) => core::fmt::Display::fmt(error, formatter),
85+
}
86+
}
87+
}
88+
89+
#[cfg(feature = "std")]
90+
impl std::error::Error for BytesDecodeError {
91+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
92+
match self {
93+
Self::InputTooLarge { .. } => None,
94+
Self::Decode(error) => Some(error),
95+
}
96+
}
97+
}
98+
99+
impl From<DecodeError> for BytesDecodeError {
100+
fn from(error: DecodeError) -> Self {
101+
Self::Decode(error)
102+
}
103+
}
104+
15105
/// Extension helpers for [`base64_ng::Engine`] and `bytes` buffers.
16106
pub trait EngineBytesExt<A, const PAD: bool>
17107
where
@@ -43,6 +133,26 @@ where
43133
where
44134
B: Buf;
45135

136+
/// Encodes at most `max_input_len` remaining bytes from `input` into a
137+
/// [`Bytes`] value.
138+
///
139+
/// Use this variant for peer-controlled or metadata-declared frame sizes.
140+
/// The input buffer is not collected if [`Buf::remaining`] exceeds
141+
/// `max_input_len`.
142+
///
143+
/// # Errors
144+
///
145+
/// Returns [`BytesEncodeError::InputTooLarge`] if `input.remaining()`
146+
/// exceeds `max_input_len`, or [`BytesEncodeError::Encode`] if Base64
147+
/// encoding fails.
148+
fn encode_buf_limited<B>(
149+
&self,
150+
input: B,
151+
max_input_len: usize,
152+
) -> Result<Bytes, BytesEncodeError>
153+
where
154+
B: Buf;
155+
46156
/// Decodes all remaining bytes from `input` into a [`Bytes`] value.
47157
///
48158
/// # Errors
@@ -52,6 +162,26 @@ where
52162
where
53163
B: Buf;
54164

165+
/// Decodes at most `max_input_len` remaining Base64 bytes from `input`
166+
/// into a [`Bytes`] value.
167+
///
168+
/// Use this variant for peer-controlled or metadata-declared frame sizes.
169+
/// The input buffer is not collected if [`Buf::remaining`] exceeds
170+
/// `max_input_len`.
171+
///
172+
/// # Errors
173+
///
174+
/// Returns [`BytesDecodeError::InputTooLarge`] if `input.remaining()`
175+
/// exceeds `max_input_len`, or [`BytesDecodeError::Decode`] if Base64
176+
/// decoding fails.
177+
fn decode_buf_limited<B>(
178+
&self,
179+
input: B,
180+
max_input_len: usize,
181+
) -> Result<Bytes, BytesDecodeError>
182+
where
183+
B: Buf;
184+
55185
/// Encodes all remaining bytes from `input` into `output`.
56186
///
57187
/// # Errors
@@ -63,6 +193,25 @@ where
63193
B: Buf,
64194
M: BufMut;
65195

196+
/// Encodes at most `max_input_len` remaining bytes from `input` into
197+
/// `output`.
198+
///
199+
/// # Errors
200+
///
201+
/// Returns [`BytesEncodeError::InputTooLarge`] if `input.remaining()`
202+
/// exceeds `max_input_len`, [`BytesEncodeError::Encode`] with
203+
/// [`EncodeError::OutputTooSmall`] if `output` is too small, or another
204+
/// wrapped [`EncodeError`] if Base64 encoding fails.
205+
fn encode_buf_to_mut_limited<B, M>(
206+
&self,
207+
input: B,
208+
output: &mut M,
209+
max_input_len: usize,
210+
) -> Result<usize, BytesEncodeError>
211+
where
212+
B: Buf,
213+
M: BufMut;
214+
66215
/// Decodes all remaining Base64 bytes from `input` into `output`.
67216
///
68217
/// # Errors
@@ -74,6 +223,25 @@ where
74223
where
75224
B: Buf,
76225
M: BufMut;
226+
227+
/// Decodes at most `max_input_len` remaining Base64 bytes from `input`
228+
/// into `output`.
229+
///
230+
/// # Errors
231+
///
232+
/// Returns [`BytesDecodeError::InputTooLarge`] if `input.remaining()`
233+
/// exceeds `max_input_len`, [`BytesDecodeError::Decode`] with
234+
/// [`DecodeError::OutputTooSmall`] if `output` is too small, or another
235+
/// wrapped [`DecodeError`] if Base64 decoding fails.
236+
fn decode_buf_to_mut_limited<B, M>(
237+
&self,
238+
input: B,
239+
output: &mut M,
240+
max_input_len: usize,
241+
) -> Result<usize, BytesDecodeError>
242+
where
243+
B: Buf,
244+
M: BufMut;
77245
}
78246

79247
impl<A, const PAD: bool> EngineBytesExt<A, PAD> for Engine<A, PAD>
@@ -95,13 +263,49 @@ where
95263
self.encode_bytes(collect_buf(input))
96264
}
97265

266+
fn encode_buf_limited<B>(
267+
&self,
268+
input: B,
269+
max_input_len: usize,
270+
) -> Result<Bytes, BytesEncodeError>
271+
where
272+
B: Buf,
273+
{
274+
let input =
275+
collect_buf_limited(input, max_input_len).map_err(|(input_len, max_input_len)| {
276+
BytesEncodeError::InputTooLarge {
277+
input_len,
278+
max_input_len,
279+
}
280+
})?;
281+
self.encode_bytes(input).map_err(BytesEncodeError::Encode)
282+
}
283+
98284
fn decode_buf<B>(&self, input: B) -> Result<Bytes, DecodeError>
99285
where
100286
B: Buf,
101287
{
102288
self.decode_bytes(collect_buf(input))
103289
}
104290

291+
fn decode_buf_limited<B>(
292+
&self,
293+
input: B,
294+
max_input_len: usize,
295+
) -> Result<Bytes, BytesDecodeError>
296+
where
297+
B: Buf,
298+
{
299+
let input =
300+
collect_buf_limited(input, max_input_len).map_err(|(input_len, max_input_len)| {
301+
BytesDecodeError::InputTooLarge {
302+
input_len,
303+
max_input_len,
304+
}
305+
})?;
306+
self.decode_bytes(input).map_err(BytesDecodeError::Decode)
307+
}
308+
105309
fn encode_buf_to_mut<B, M>(&self, input: B, output: &mut M) -> Result<usize, EncodeError>
106310
where
107311
B: Buf,
@@ -119,6 +323,28 @@ where
119323
Ok(len)
120324
}
121325

326+
fn encode_buf_to_mut_limited<B, M>(
327+
&self,
328+
input: B,
329+
output: &mut M,
330+
max_input_len: usize,
331+
) -> Result<usize, BytesEncodeError>
332+
where
333+
B: Buf,
334+
M: BufMut,
335+
{
336+
let encoded = self.encode_buf_limited(input, max_input_len)?;
337+
let len = encoded.len();
338+
if output.remaining_mut() < len {
339+
return Err(BytesEncodeError::Encode(EncodeError::OutputTooSmall {
340+
required: len,
341+
available: output.remaining_mut(),
342+
}));
343+
}
344+
output.put_slice(&encoded);
345+
Ok(len)
346+
}
347+
122348
fn decode_buf_to_mut<B, M>(&self, input: B, output: &mut M) -> Result<usize, DecodeError>
123349
where
124350
B: Buf,
@@ -135,6 +361,28 @@ where
135361
output.put_slice(&decoded);
136362
Ok(len)
137363
}
364+
365+
fn decode_buf_to_mut_limited<B, M>(
366+
&self,
367+
input: B,
368+
output: &mut M,
369+
max_input_len: usize,
370+
) -> Result<usize, BytesDecodeError>
371+
where
372+
B: Buf,
373+
M: BufMut,
374+
{
375+
let decoded = self.decode_buf_limited(input, max_input_len)?;
376+
let len = decoded.len();
377+
if output.remaining_mut() < len {
378+
return Err(BytesDecodeError::Decode(DecodeError::OutputTooSmall {
379+
required: len,
380+
available: output.remaining_mut(),
381+
}));
382+
}
383+
output.put_slice(&decoded);
384+
Ok(len)
385+
}
138386
}
139387

140388
fn collect_buf<B>(mut input: B) -> Vec<u8>
@@ -150,3 +398,15 @@ where
150398
}
151399
output
152400
}
401+
402+
fn collect_buf_limited<B>(input: B, max_input_len: usize) -> Result<Vec<u8>, (usize, usize)>
403+
where
404+
B: Buf,
405+
{
406+
let input_len = input.remaining();
407+
if input_len > max_input_len {
408+
return Err((input_len, max_input_len));
409+
}
410+
411+
Ok(collect_buf(input))
412+
}

0 commit comments

Comments
 (0)