Skip to content

Commit 92c81e7

Browse files
authored
hashcrypt: minimize panic code paths (#528)
This pull request enhances error handling in the `hashcrypt` hasher implementation by introducing a unified `Error` type and updating relevant methods to return `Result` instead of panicking. This makes the code more robust, especially for invalid input scenarios, and improves its safety and reliability in both blocking and asynchronous hashing modes. **Error handling improvements:** * Introduced a new `Error` enum in `src/hashcrypt/mod.rs` to represent unsupported configurations and updated code to use this error type instead of panicking. * Updated internal helper methods in `Hasher` (such as `init_final_data`, `init_final_block`, and `init_final_len`) to return `Result<(), Error>` and propagate errors instead of panicking on invalid buffer access. **Blocking hasher API changes:** * Changed public and internal methods in the blocking `Hasher` implementation (`submit_blocks`, `finalize`, `hash`) to return `Result` and handle errors gracefully, replacing panics with error returns for invalid input lengths. **Async hasher API changes:** * Updated async `Hasher` methods (`transfer`, `submit_blocks`, `finalize`, `hash`) to return `Result` and propagate errors, ensuring safe handling of invalid conditions such as missing DMA channels or incorrect data lengths. [[1]](diffhunk://#diff-2b7655c2223a12761dadfbbd782cba8cf016f31e8b298aa46e9519bd81ff2158L130-R150) [[2]](diffhunk://#diff-2b7655c2223a12761dadfbbd782cba8cf016f31e8b298aa46e9519bd81ff2158L141-R159) [[3]](diffhunk://#diff-2b7655c2223a12761dadfbbd782cba8cf016f31e8b298aa46e9519bd81ff2158R191-R232) **General code safety improvements:** * Added assertions and explicit error returns to ensure buffer sizes and data lengths are always valid, replacing unchecked unwraps and panics with error handling. [[1]](diffhunk://#diff-2b7655c2223a12761dadfbbd782cba8cf016f31e8b298aa46e9519bd81ff2158R78-R138) [[2]](diffhunk://#diff-2b7655c2223a12761dadfbbd782cba8cf016f31e8b298aa46e9519bd81ff2158L37-R56)
1 parent 644bf24 commit 92c81e7

File tree

4 files changed

+94
-56
lines changed

4 files changed

+94
-56
lines changed

examples/rt685s-evk/src/bin/sha256-async.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async fn main(_spawner: Spawner) {
2222
info!("Starting hashes");
2323
// Data that fits into a single block
2424
info!("Single hash block");
25-
hashcrypt.new_sha256().hash(b"abc", &mut hash).await;
25+
hashcrypt.new_sha256().hash(b"abc", &mut hash).await.unwrap();
2626
defmt::assert_eq!(
2727
&hash,
2828
&[
@@ -39,7 +39,8 @@ async fn main(_spawner: Spawner) {
3939
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()",
4040
&mut hash,
4141
)
42-
.await;
42+
.await
43+
.unwrap();
4344
defmt::assert_eq!(
4445
&hash,
4546
&[
@@ -56,7 +57,8 @@ async fn main(_spawner: Spawner) {
5657
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@",
5758
&mut hash,
5859
)
59-
.await;
60+
.await
61+
.unwrap();
6062
defmt::assert_eq!(
6163
&hash,
6264
&[
@@ -70,7 +72,7 @@ async fn main(_spawner: Spawner) {
7072
hashcrypt.new_sha256().hash(
7173
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345678",
7274
&mut hash,
73-
).await;
75+
).await.unwrap();
7476
defmt::assert_eq!(
7577
&hash,
7678
&[

examples/rt685s-evk/src/bin/sha256.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) {
1717
info!("Starting hashes");
1818
// Data that fits into a single block
1919
info!("Single hash block");
20-
hashcrypt.new_sha256().hash(b"abc", &mut hash);
20+
hashcrypt.new_sha256().hash(b"abc", &mut hash).unwrap();
2121
defmt::assert_eq!(
2222
&hash,
2323
&[
@@ -28,10 +28,13 @@ async fn main(_spawner: Spawner) {
2828

2929
// Data that fits into two blocks
3030
info!("Two hash blocks");
31-
hashcrypt.new_sha256().hash(
32-
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()",
33-
&mut hash,
34-
);
31+
hashcrypt
32+
.new_sha256()
33+
.hash(
34+
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()",
35+
&mut hash,
36+
)
37+
.unwrap();
3538
defmt::assert_eq!(
3639
&hash,
3740
&[
@@ -42,10 +45,13 @@ async fn main(_spawner: Spawner) {
4245

4346
// Data that is exactly one block
4447
info!("One block exactly");
45-
hashcrypt.new_sha256().hash(
46-
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@",
47-
&mut hash,
48-
);
48+
hashcrypt
49+
.new_sha256()
50+
.hash(
51+
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@",
52+
&mut hash,
53+
)
54+
.unwrap();
4955
defmt::assert_eq!(
5056
&hash,
5157
&[
@@ -59,7 +65,7 @@ async fn main(_spawner: Spawner) {
5965
hashcrypt.new_sha256().hash(
6066
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345678",
6167
&mut hash,
62-
);
68+
).unwrap();
6369
defmt::assert_eq!(
6470
&hash,
6571
&[

src/hashcrypt/hasher.rs

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::task::Poll;
55

66
use embassy_futures::select::select;
77

8-
use super::{Async, Blocking, Hashcrypt, Mode};
8+
use super::{Async, Blocking, Error, Hashcrypt, Mode};
99
use crate::dma;
1010
use crate::dma::transfer::{Transfer, Width};
1111

@@ -34,18 +34,26 @@ impl<'d, 'a, M: Mode> Hasher<'d, 'a, M> {
3434
}
3535
}
3636

37-
fn init_final_data(&self, data: &[u8], buffer: &mut [u8; BLOCK_LEN]) {
38-
buffer[..data.len()].copy_from_slice(data);
39-
buffer[data.len()] = END_BYTE;
37+
fn init_final_data(&self, data: &[u8], buffer: &mut [u8; BLOCK_LEN]) -> Result<(), Error> {
38+
buffer
39+
.get_mut(..data.len())
40+
.ok_or(Error::UnsupportedConfiguration)?
41+
.copy_from_slice(data);
42+
*buffer.get_mut(data.len()).ok_or(Error::UnsupportedConfiguration)? = END_BYTE;
43+
Ok(())
4044
}
4145

42-
fn init_final_block(&self, data: &[u8], buffer: &mut [u8; BLOCK_LEN]) {
43-
self.init_final_data(data, buffer);
44-
self.init_final_len(buffer);
46+
fn init_final_block(&self, data: &[u8], buffer: &mut [u8; BLOCK_LEN]) -> Result<(), Error> {
47+
self.init_final_data(data, buffer)?;
48+
self.init_final_len(buffer)
4549
}
4650

47-
fn init_final_len(&self, buffer: &mut [u8; BLOCK_LEN]) {
48-
buffer[BLOCK_LEN - 8..BLOCK_LEN].copy_from_slice(&(8 * self.written as u64).to_be_bytes());
51+
fn init_final_len(&self, buffer: &mut [u8; BLOCK_LEN]) -> Result<(), Error> {
52+
buffer
53+
.get_mut(BLOCK_LEN - 8..BLOCK_LEN)
54+
.ok_or(Error::UnsupportedConfiguration)?
55+
.copy_from_slice(&(8 * self.written as u64).to_be_bytes());
56+
Ok(())
4957
}
5058

5159
fn wait_for_digest(&self) {
@@ -67,57 +75,67 @@ impl<'d, 'a> Hasher<'d, 'a, Blocking> {
6775
}
6876

6977
fn transfer_block(&mut self, data: &[u8; BLOCK_LEN]) {
78+
const _: () = core::assert!(BLOCK_LEN.is_multiple_of(4), "BLOCK_LEN must be divisible by 4");
79+
7080
for word in data.chunks(4) {
71-
self.hashcrypt
72-
.hashcrypt
73-
.indata()
74-
.write(|w| unsafe { w.data().bits(u32::from_le_bytes([word[0], word[1], word[2], word[3]])) });
81+
self.hashcrypt.hashcrypt.indata().write(|w| unsafe {
82+
#[allow(clippy::unwrap_used)]
83+
// panic safety: word is always 4 bytes and BLOCK_LEN is multiple of 4
84+
w.data().bits(u32::from_le_bytes(word.try_into().unwrap()))
85+
});
7586
}
7687
self.wait_for_digest();
7788
}
7889

7990
/// Submit one or more blocks of data to the hasher, data must be a multiple of the block length
80-
pub fn submit_blocks(&mut self, data: &[u8]) {
91+
pub fn submit_blocks(&mut self, data: &[u8]) -> Result<(), Error> {
8192
if data.is_empty() || !data.len().is_multiple_of(BLOCK_LEN) {
82-
panic!("Invalid data length");
93+
return Err(Error::UnsupportedConfiguration);
8394
}
8495

8596
for block in data.chunks(BLOCK_LEN) {
97+
#[allow(clippy::unwrap_used)] // panic safety: block is always BLOCK_LEN bytes with check above
8698
self.transfer_block(block.try_into().unwrap());
8799
}
88100
self.written += data.len();
101+
Ok(())
89102
}
90103

91104
/// Submits the final data for hashing
92-
pub fn finalize(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) {
105+
pub fn finalize(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) -> Result<(), Error> {
93106
let mut buffer = [0u8; BLOCK_LEN];
94107

95108
self.written += data.len();
96109
if data.len() <= LAST_BLOCK_MAX_DATA {
97110
// Only have one final block
98-
self.init_final_block(data, &mut buffer);
111+
self.init_final_block(data, &mut buffer)?;
99112
self.transfer_block(&buffer);
100113
} else {
101114
//End byte and padding won't fit in this block, submit this block and an extra one
102-
self.init_final_data(data, &mut buffer);
115+
self.init_final_data(data, &mut buffer)?;
103116
self.transfer_block(&buffer);
104117

105118
buffer.fill(0);
106-
self.init_final_len(&mut buffer);
119+
self.init_final_len(&mut buffer)?;
107120
self.transfer_block(&buffer);
108121
}
109122

110123
self.read_hash(hash);
124+
125+
Ok(())
111126
}
112127

113128
/// Computes the hash of the given data
114-
pub fn hash(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) {
115-
let full_blocks = data.len() / BLOCK_LEN;
129+
pub fn hash(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) -> Result<(), Error> {
130+
let mut iter = data.chunks_exact(BLOCK_LEN);
116131

117-
if full_blocks > 0 {
118-
self.submit_blocks(&data[0..full_blocks * BLOCK_LEN]);
132+
for block in &mut iter {
133+
self.submit_blocks(block)?;
119134
}
120-
self.finalize(&data[full_blocks * BLOCK_LEN..], hash);
135+
136+
self.finalize(iter.remainder(), hash)?;
137+
138+
Ok(())
121139
}
122140
}
123141

@@ -127,9 +145,9 @@ impl<'d, 'a> Hasher<'d, 'a, Async> {
127145
Self::new_inner(hashcrypt)
128146
}
129147

130-
async fn transfer(&mut self, data: &[u8]) {
148+
async fn transfer(&mut self, data: &[u8]) -> Result<(), Error> {
131149
if data.is_empty() || !data.len().is_multiple_of(BLOCK_LEN) {
132-
panic!("Invalid data length");
150+
return Err(Error::UnsupportedConfiguration);
133151
}
134152

135153
let options = dma::transfer::TransferOptions {
@@ -138,7 +156,7 @@ impl<'d, 'a> Hasher<'d, 'a, Async> {
138156
};
139157

140158
let transfer = Transfer::new_write(
141-
self.hashcrypt.dma_ch.as_ref().unwrap(),
159+
self.hashcrypt.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?,
142160
data,
143161
self.hashcrypt.hashcrypt.indata().as_ptr() as *mut u8,
144162
options,
@@ -170,43 +188,47 @@ impl<'d, 'a> Hasher<'d, 'a, Async> {
170188
Poll::Pending
171189
})
172190
.await;
191+
192+
Ok(())
173193
}
174194

175195
/// Submit one or more blocks of data to the hasher, data must be a multiple of the block length
176-
pub async fn submit_blocks(&mut self, data: &[u8]) {
177-
self.transfer(data).await;
196+
pub async fn submit_blocks(&mut self, data: &[u8]) -> Result<(), Error> {
197+
self.transfer(data).await?;
178198
self.written += data.len();
199+
Ok(())
179200
}
180201

181202
/// Submits the final data for hashing
182-
pub async fn finalize(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) {
203+
pub async fn finalize(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) -> Result<(), Error> {
183204
let mut buffer = [0u8; BLOCK_LEN];
184205

185206
self.written += data.len();
186207
if data.len() <= LAST_BLOCK_MAX_DATA {
187208
// Only have one final block
188-
self.init_final_block(data, &mut buffer);
189-
self.transfer(&buffer).await;
209+
self.init_final_block(data, &mut buffer)?;
210+
self.transfer(&buffer).await?;
190211
} else {
191212
//End byte and padding won't fit in this block, submit this block and an extra one
192-
self.init_final_data(data, &mut buffer);
193-
self.transfer(&buffer).await;
194-
213+
self.init_final_data(data, &mut buffer)?;
214+
self.transfer(&buffer).await?;
195215
buffer.fill(0);
196-
self.init_final_len(&mut buffer);
197-
self.transfer(&buffer).await;
216+
self.init_final_len(&mut buffer)?;
217+
self.transfer(&buffer).await?;
198218
}
199219

200220
self.read_hash(hash);
221+
Ok(())
201222
}
202223

203224
/// Computes the hash of the given data
204-
pub async fn hash(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) {
205-
let full_blocks = data.len() / BLOCK_LEN;
225+
pub async fn hash(mut self, data: &[u8], hash: &mut [u8; HASH_LEN]) -> Result<(), Error> {
226+
let mut iter = data.chunks_exact(BLOCK_LEN);
206227

207-
if full_blocks > 0 {
208-
self.submit_blocks(&data[0..full_blocks * BLOCK_LEN]).await;
228+
for block in &mut iter {
229+
self.submit_blocks(block).await?;
209230
}
210-
self.finalize(&data[full_blocks * BLOCK_LEN..], hash).await;
231+
232+
self.finalize(iter.remainder(), hash).await
211233
}
212234
}

src/hashcrypt/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ use crate::{Peri, dma, interrupt, pac};
1212
/// Hasher module
1313
pub mod hasher;
1414

15+
/// Error information type
16+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
17+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18+
pub enum Error {
19+
/// configuration requested is not supported
20+
UnsupportedConfiguration,
21+
}
22+
1523
trait Sealed {}
1624

1725
/// Asynchronous or blocking mode

0 commit comments

Comments
 (0)