Skip to content

Commit 40efde2

Browse files
committed
Support flashing in Secure Download Mode
* With secure download enabled, the stub is not utilized so compressed flashing is unavailble. In this situation, use the uncompressed flashing commands. * If secure download is enabled, prevent flashing data to addresses below 0x8000 to prevent overwriting the bootloader and bricking the device
1 parent 5419c6c commit 40efde2

File tree

4 files changed

+135
-54
lines changed

4 files changed

+135
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- esp32p4rc1 support for esp32p4 chips with revision < 3.0 (#922)
1414
- Support for binaries with INIT_ARRAY sections, which is needed for esp32p4 support. (#991)
1515
- Add sha256 calculation to match esptool generated binaries (#991)
16+
- Support flashing in secure download mode (#990)
1617

1718
### Changed
1819

espflash/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ pub enum Error {
383383
/// Tried to use an unsupported eFuse coding scheme
384384
#[error("Tried to use an unsupported eFuse coding scheme: {0}")]
385385
UnsupportedEfuseCodingScheme(String),
386+
387+
/// Tried to write to address < 0x8000 with Secure Download enabled
388+
#[error("Tried to write to address < 0x8000 with Secure Download enabled")]
389+
SecureDownloadBootloaderProtection,
386390
}
387391

388392
#[cfg(feature = "serialport")]

espflash/src/flasher/mod.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ pub(crate) const TRY_SPI_PARAMS: [SpiAttachParams; 2] =
5757
pub(crate) const FLASH_SECTOR_SIZE: usize = 0x1000;
5858
pub(crate) const FLASH_WRITE_SIZE: usize = 0x400;
5959

60+
#[cfg(feature = "serialport")]
61+
pub(crate) const BOOTLOADER_PROTECTION_ADDR: u32 = 0x8000;
62+
6063
/// Supported flash frequencies
6164
///
6265
/// Note that not all frequencies are supported by each target device.
@@ -936,15 +939,41 @@ impl Flasher {
936939
target.finish(&mut self.connection, true).flashing()
937940
}
938941

942+
/// Validate flash arguments when in secure download mode.
943+
/// Prevent a user from accidentally flashing over a secure boot enabled
944+
/// bootloader and bricking their device.
945+
fn validate_secure_download_args(&self, segments: &[Segment<'_>]) -> Result<(), Error> {
946+
for segment in segments {
947+
if segment.addr < BOOTLOADER_PROTECTION_ADDR {
948+
return Err(Error::SecureDownloadBootloaderProtection);
949+
}
950+
}
951+
952+
if self.verify || self.skip {
953+
warn!(
954+
"Secure Download Mode enabled: --verify and --skip options are not available \
955+
(flash read operations are restricted)"
956+
);
957+
}
958+
Ok(())
959+
}
960+
939961
/// Load an ELF image to flash and execute it
940962
pub fn load_image_to_flash<'a>(
941963
&mut self,
942964
progress: &mut dyn ProgressCallbacks,
943965
image_format: ImageFormat<'a>,
944966
) -> Result<(), Error> {
945-
let mut target =
946-
self.chip
947-
.flash_target(self.spi_params, self.use_stub, self.verify, self.skip);
967+
let (mut verify, mut skip) = (self.verify, self.skip);
968+
969+
if self.connection.secure_download_mode {
970+
self.validate_secure_download_args(&image_format.clone().flash_segments())?;
971+
(verify, skip) = (false, false);
972+
}
973+
974+
let mut target = self
975+
.chip
976+
.flash_target(self.spi_params, self.use_stub, verify, skip);
948977
target.begin(&mut self.connection).flashing()?;
949978

950979
// When the `cli` feature is enabled, display the image size information.
@@ -1005,16 +1034,16 @@ impl Flasher {
10051034
segments: &[Segment<'_>],
10061035
progress: &mut dyn ProgressCallbacks,
10071036
) -> Result<(), Error> {
1037+
let (mut verify, mut skip) = (self.verify, self.skip);
1038+
10081039
if self.connection.secure_download_mode {
1009-
return Err(Error::UnsupportedFeature {
1010-
chip: self.chip,
1011-
feature: "writing binaries in Secure Download Mode currently".into(),
1012-
});
1040+
self.validate_secure_download_args(segments)?;
1041+
(verify, skip) = (false, false);
10131042
}
10141043

1015-
let mut target =
1016-
self.chip
1017-
.flash_target(self.spi_params, self.use_stub, self.verify, self.skip);
1044+
let mut target = self
1045+
.chip
1046+
.flash_target(self.spi_params, self.use_stub, verify, skip);
10181047

10191048
target.begin(&mut self.connection).flashing()?;
10201049

espflash/src/target/flash_target/esp32.rs

Lines changed: 91 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub struct Esp32Target {
3434
use_stub: bool,
3535
verify: bool,
3636
skip: bool,
37-
need_deflate_end: bool,
37+
need_flash_end: bool,
3838
}
3939

4040
impl Esp32Target {
@@ -52,7 +52,7 @@ impl Esp32Target {
5252
use_stub,
5353
verify,
5454
skip,
55-
need_deflate_end: false,
55+
need_flash_end: false,
5656
}
5757
}
5858
}
@@ -79,7 +79,10 @@ impl FlashTarget for Esp32Target {
7979
//
8080
// TODO: the stub doesn't appear to disable the watchdog on ESP32-S3, so we
8181
// explicitly disable the watchdog here.
82-
if connection.is_using_usb_serial_jtag() {
82+
//
83+
// NOTE: In Secure Download Mode, WRITE_REG commands are not allowed, so we
84+
// must skip the watchdog disable.
85+
if connection.is_using_usb_serial_jtag() && !connection.secure_download_mode {
8386
if let (Some(wdt_wprotect), Some(wdt_config0)) =
8487
(self.chip.wdt_wprotect(), self.chip.wdt_config0())
8588
{
@@ -116,22 +119,15 @@ impl FlashTarget for Esp32Target {
116119
md5_hasher.update(&segment.data);
117120
let checksum_md5 = md5_hasher.finalize();
118121

119-
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());
120-
encoder.write_all(&segment.data)?;
121-
let compressed = encoder.finish()?;
122+
// use compression only when stub is loaded.
123+
let use_compression = self.use_stub;
122124

123125
let flash_write_size = self.chip.flash_write_size();
124-
let block_count = compressed.len().div_ceil(flash_write_size);
125126
let erase_count = segment.data.len().div_ceil(FLASH_SECTOR_SIZE);
126127

127128
// round up to sector size
128129
let erase_size = (erase_count * FLASH_SECTOR_SIZE) as u32;
129130

130-
let chunks = compressed.chunks(flash_write_size);
131-
let num_chunks = chunks.len();
132-
133-
progress.init(addr, num_chunks);
134-
135131
if self.skip {
136132
let flash_checksum_md5: u128 = connection.with_timeout(
137133
CommandType::FlashMd5.timeout_for_size(segment.data.len() as u32),
@@ -153,43 +149,88 @@ impl FlashTarget for Esp32Target {
153149
}
154150
}
155151

156-
connection.with_timeout(
157-
CommandType::FlashDeflBegin.timeout_for_size(erase_size),
158-
|connection| {
159-
connection.command(Command::FlashDeflBegin {
160-
size: segment.data.len() as u32,
161-
blocks: block_count as u32,
162-
block_size: flash_write_size as u32,
163-
offset: addr,
164-
supports_encryption: self.chip != Chip::Esp32 && !self.use_stub,
165-
})?;
166-
Ok(())
167-
},
168-
)?;
169-
self.need_deflate_end = true;
152+
let data = if use_compression {
153+
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::best());
154+
encoder.write_all(&segment.data)?;
155+
encoder.finish()?
156+
} else {
157+
segment.data.to_vec()
158+
};
170159

171-
// decode the chunks to see how much data the device will have to save
172-
let mut decoder = ZlibDecoder::new(Vec::new());
173-
let mut decoded_size = 0;
160+
let block_count = data.len().div_ceil(flash_write_size);
161+
let chunks = data.chunks(flash_write_size);
162+
let num_chunks = chunks.len();
174163

175-
for (i, block) in chunks.enumerate() {
176-
decoder.write_all(block)?;
177-
decoder.flush()?;
178-
let size = decoder.get_ref().len() - decoded_size;
179-
decoded_size = decoder.get_ref().len();
164+
progress.init(addr, num_chunks);
180165

166+
if use_compression {
167+
connection.with_timeout(
168+
CommandType::FlashDeflBegin.timeout_for_size(erase_size),
169+
|connection| {
170+
connection.command(Command::FlashDeflBegin {
171+
size: segment.data.len() as u32,
172+
blocks: block_count as u32,
173+
block_size: flash_write_size as u32,
174+
offset: addr,
175+
supports_encryption: self.chip != Chip::Esp32 && !self.use_stub,
176+
})?;
177+
Ok(())
178+
},
179+
)?;
180+
} else {
181181
connection.with_timeout(
182-
CommandType::FlashDeflData.timeout_for_size(size as u32),
182+
CommandType::FlashBegin.timeout_for_size(erase_size),
183183
|connection| {
184-
connection.command(Command::FlashDeflData {
185-
sequence: i as u32,
186-
pad_to: 0,
187-
pad_byte: 0xff,
188-
data: block,
184+
connection.command(Command::FlashBegin {
185+
size: erase_size,
186+
blocks: block_count as u32,
187+
block_size: flash_write_size as u32,
188+
offset: addr,
189+
supports_encryption: self.chip != Chip::Esp32,
189190
})?;
190191
Ok(())
191192
},
192193
)?;
194+
}
195+
self.need_flash_end = true;
196+
197+
// decode the chunks to see how much data the device will have to save
198+
let mut decoder = ZlibDecoder::new(Vec::new());
199+
let mut decoded_size = 0;
200+
201+
for (i, block) in chunks.enumerate() {
202+
if use_compression {
203+
decoder.write_all(block)?;
204+
decoder.flush()?;
205+
let size = decoder.get_ref().len() - decoded_size;
206+
decoded_size = decoder.get_ref().len();
207+
208+
connection.with_timeout(
209+
CommandType::FlashDeflData.timeout_for_size(size as u32),
210+
|connection| {
211+
connection.command(Command::FlashDeflData {
212+
sequence: i as u32,
213+
pad_to: 0,
214+
pad_byte: 0xff,
215+
data: block,
216+
})?;
217+
Ok(())
218+
},
219+
)?;
220+
} else {
221+
connection.with_timeout(
222+
CommandType::FlashData.timeout_for_size(block.len() as u32),
223+
|connection| {
224+
connection.command(Command::FlashData {
225+
sequence: i as u32,
226+
pad_to: flash_write_size,
227+
pad_byte: 0xff,
228+
data: block,
229+
})?;
230+
Ok(())
231+
},
232+
)?;
233+
}
193234

194235
progress.update(i + 1)
195236
}
@@ -220,10 +261,16 @@ impl FlashTarget for Esp32Target {
220261
}
221262

222263
fn finish(&mut self, connection: &mut Connection, reboot: bool) -> Result<(), Error> {
223-
if self.need_deflate_end {
224-
connection.with_timeout(CommandType::FlashDeflEnd.timeout(), |connection| {
225-
connection.command(Command::FlashDeflEnd { reboot: false })
226-
})?;
264+
if self.need_flash_end {
265+
if self.use_stub {
266+
connection.with_timeout(CommandType::FlashDeflEnd.timeout(), |connection| {
267+
connection.command(Command::FlashDeflEnd { reboot: false })
268+
})?;
269+
} else {
270+
connection.with_timeout(CommandType::FlashEnd.timeout(), |connection| {
271+
connection.command(Command::FlashEnd { reboot: false })
272+
})?;
273+
}
227274
}
228275

229276
if reboot {

0 commit comments

Comments
 (0)