USB Protection correction (#90)

This commit is contained in:
devdl11
2021-12-14 20:51:53 +00:00
committed by GitHub
parent 0bd30ed2e6
commit dd48235721
6 changed files with 174 additions and 118 deletions

View File

@@ -13,7 +13,7 @@ using namespace Shared;
namespace Settings {
UsbInfoController::UsbInfoController(Responder *parentResponder):
UsbInfoController::UsbInfoController(Responder *parentResponder):
GenericSubController(parentResponder),
m_usbProtectionLevelController(this),
m_contentView(&m_selectableTableView)
@@ -23,7 +23,7 @@ UsbInfoController::UsbInfoController(Responder *parentResponder):
}
bool UsbInfoController::handleEvent(Ion::Events::Event event) {
if ((Ion::Events::OK == event || Ion::Events::EXE == event) && selectedRow() == 0) {
if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 0) {
if (!GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) {
if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) {
Ion::LED::setColor(KDColorPurple);
@@ -39,15 +39,8 @@ bool UsbInfoController::handleEvent(Ion::Events::Event event) {
m_selectableTableView.reloadCellAtLocation(0, 0);
return true;
}
// We cannot use things like willExitResponderChain because this view can disappear due to an USB connection,
// and in this case we must keep the DFU status.
if ((Ion::Events::Left == event || Ion::Events::Home == event || Ion::Events::Back == event) && GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) {
GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(false);
m_selectableTableView.reloadCellAtLocation(0, 0);
Container::activeApp()->displayWarning(I18n::Message::USBProtectionReactivated);
return true;
}
if ((Ion::Events::OK == event || Ion::Events::EXE == event) && selectedRow() == 1) {
if ((Ion::Events::OK == event || Ion::Events::EXE == event || Ion::Events::Right == event) && selectedRow() == 1) {
GenericSubController *subController = &m_usbProtectionLevelController;
subController->setMessageTreeModel(m_messageTreeModel->childAtIndex(1));
StackViewController *stack = stackController();
@@ -55,6 +48,20 @@ bool UsbInfoController::handleEvent(Ion::Events::Event event) {
stack->push(subController);
return true;
}
// We cannot use things like willExitResponderChain because this view can disappear due to an USB connection,
// and in this case we must keep the DFU status.
if ((event != Ion::Events::USBPlug && event != Ion::Events::USBEnumeration) &&
GlobalPreferences::sharedGlobalPreferences()->dfuUnlocked()) {
GlobalPreferences::sharedGlobalPreferences()->setDfuUnlocked(false);
m_selectableTableView.reloadCellAtLocation(0, 0);
Container::activeApp()->displayWarning(I18n::Message::USBProtectionReactivated);
if (!GlobalPreferences::sharedGlobalPreferences()->isInExamMode()) {
Ion::LED::setColor(KDColorBlack);
}
return true;
}
return GenericSubController::handleEvent(event);
}

View File

@@ -47,6 +47,7 @@ __DFU_GETSTATUS = 3
__DFU_CLRSTATUS = 4
__DFU_GETSTATE = 5
__DFU_ABORT = 6
__DFU_UNLOCK = 11
# DFU status
__DFU_STATE_APP_IDLE = 0x00
@@ -145,6 +146,10 @@ def abort_request():
__dev.ctrl_transfer(0x21, __DFU_ABORT, 0, __DFU_INTERFACE, None, __TIMEOUT)
def unlock_request():
"""Deactivate the protection"""
__dev.ctrl_transfer(0x21, __DFU_UNLOCK, 0, __DFU_INTERFACE, None, __TIMEOUT)
def clr_status():
"""Clears any error status (perhaps left over from a previous session)."""
__dev.ctrl_transfer(0x21, __DFU_CLRSTATUS, 0, __DFU_INTERFACE,
@@ -587,6 +592,7 @@ def main():
elements = read_dfu_file(args.path)
if not elements:
return
unlock_request()
print("Writing memory...")
write_elements(elements, args.mass_erase, progress=cli_progress)

View File

@@ -176,8 +176,8 @@ SECTIONS {
*(.text._ZN20KDPostProcessContext15setClippingRectE6KDRect)
*(.text._ZNK6KDFont17indexForCodePointE9CodePoint)
*(.text._ZNK6KDFont26fetchGrayscaleGlyphAtIndexEhPh)
*(.text.LZ4_decompress_safe)
*(.text.LZ4_wildCopy)
*(.text.LZ4_decompress_safe*)
*(.text.LZ4_wildCopy*)
*(.text.*DFU*)
*(.text.*isEnumerated*)
*(.text._ZN3Ion3USB6enableEv)
@@ -214,6 +214,7 @@ SECTIONS {
*(.text._ZNK10Statistics5Store33sortedElementAtCumulatedFrequencyEidb)
*(.text.round)
*(.text._ZNK10Statistics5Store8minIndexEPdi*)
*(.text.LZ4_decompress_safe*)
/* 'standby' dependencies '*/
*(.text._ZN3Ion6Device5Power20internalFlashStandbyEv)
@@ -277,6 +278,12 @@ SECTIONS {
*(.rodata._ZN8Sequence23TypeParameterController25willDisplayCellAtLocationEP13HighlightCellii*)
*(.rodata._ZN6KDFont16privateSmallFontE)
*(.rodata._ZN4I18nL23CountryPreferencesArrayE)
*(.rodata._ZN3Ion6Device3LED6ConfigL7RGBPinsE*)
*(.rodata._ZN4I18nL23CountryPreferencesArrayE*)
*(.rodata._ZN3Ion6Device3USB6ConfigL7VbusPinE*)
*(.rodata.bp*)
*(.rodata.dp_l*)
*(.rodata.dp_h*)
*(.rodata.abort_sleeping.str1.1)
*(.rodata.abort_core.str1.1)
*(.rodata.dfu_bootloader)

View File

@@ -15,8 +15,6 @@ namespace Ion {
namespace Device {
namespace USB {
constexpr static uint8_t k_externalMagik[9] = {0x64, 0x6c, 0x31, 0x31, 0x23, 0x39, 0x38, 0x33, 0x35};
static inline uint32_t minUint32T(uint32_t x, uint32_t y) { return x < y ? x : y; }
void DFUInterface::StatusData::push(Channel *c) const {
@@ -111,6 +109,9 @@ bool DFUInterface::processSetupInRequest(SetupPacket *request, uint8_t *transfer
return getState(transferBuffer, transferBufferLength, transferBufferMaxLength);
case (uint8_t)DFURequest::Abort:
return dfuAbort(transferBufferLength);
case (uint8_t)DFURequest::Unlock:
m_dfuUnlocked = true;
return true;
}
return false;
}
@@ -140,17 +141,17 @@ bool DFUInterface::processUploadRequest(SetupPacket *request, uint8_t *transferB
}
if (request->wValue() == 0) {
/* The host requests to read the commands supported by the bootloader. After
* receiving this command, the device should returns N bytes representing
* the command codes for :
* Get command / Set Address Pointer / Erase / Read Unprotect
* We no not need it for now. */
* receiving this command, the device should returns N bytes representing
* the command codes for :
* Get command / Set Address Pointer / Erase / Read Unprotect
* We no not need it for now. */
return false;
} else if (request->wValue() == 1) {
m_ep0->stallTransaction();
return false;
} else {
/* We decided to never protect Read operation. Else we would have to check
* here it is not protected before reading. */
* here it is not protected before reading. */
// Compute the reading address
uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer;
@@ -199,7 +200,7 @@ void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBuffer
// Sector erase
assert(transferBufferLength == 5);
uint32_t m_eraseAddress = transferBuffer[1]
m_eraseAddress = transferBuffer[1]
+ (transferBuffer[2] << 8)
+ (transferBuffer[3] << 16)
+ (transferBuffer[4] << 24);
@@ -214,24 +215,21 @@ void DFUInterface::eraseCommand(uint8_t *transferBuffer, uint16_t transferBuffer
void DFUInterface::eraseMemoryIfNeeded() {
if (m_erasePage < 0) {
// There was no erase waiting.
return;
}
willErase();
#if 0 // We don't erase now the flash memory to avoid crash if writing is refused
#if 0 // We don't erase now the flash memory to avoid crash if writing is refused
if (m_erasePage == Flash::TotalNumberOfSectors()) {
Flash::MassErase();
Flash::MassErase();
}
#endif
#endif
if ((m_eraseAddress >= k_externalAppsBorderAddress && m_eraseAddress < ExternalFlash::Config::EndAddress) || m_dfuUnlocked) {
Flash::EraseSector(m_erasePage);
if ((m_eraseAddress >= k_ExternalBorderAddress && m_eraseAddress < ExternalFlash::Config::EndAddress) || m_dfuUnlocked) {
int32_t order = Flash::SectorAtAddress(m_eraseAddress);
Flash::EraseSector(order);
}
/* Put an out of range value in m_erasePage to indicate that no erase is
* waiting. */
m_erasePage = -1;
m_state = State::dfuDNLOADIDLE;
m_status = Status::OK;
m_erasePage = -1;
@@ -242,67 +240,89 @@ void DFUInterface::writeOnMemory() {
// Write on SRAM
// FIXME We should check that we are not overriding the current instructions.
memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength);
resetProtectionVariables(); // We can reset the protection variables because update process is finsihed.
resetFlashParameters(); // We are writing in SRAM, so we can reset flash parameters
} else if (Flash::SectorAtAddress(m_writeAddress) >= 0) {
if (m_dfuLevel == 2) { // If no-update mode, we throw an error
if (m_dfuLevel == 2) { // We don't accept update
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errWRITE;
leaveDFUAndReset(false);
return;
}
if (!(m_isTemporaryUnlocked || m_dfuUnlocked)) {
if (m_writeAddress >= InternalFlash::Config::StartAddress && m_writeAddress <= InternalFlash::Config::EndAddress) {
// We check if the user is autorized to write on the internal flash
if (m_haveAlreadyFlashedExternal) {
for (size_t i = 0; i < 4; i++) {
if (k_internalMagik[i] != m_largeBuffer[k_internalMagikPointer + i]) {
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errWRITE;
// We don't leave DFU to avoid having only external flashed
return;
}
m_largeBuffer[k_internalMagikPointer + i] = 0; // We reset the buffer to its initial value
int currentMemoryType; // Detection of the current memory type (Internal or External)
if (m_writeAddress >= InternalFlash::Config::StartAddress && m_writeAddress <= InternalFlash::Config::EndAddress) {
// We are writing in Internal where live the internal recovery (it's the most sensitive memory type)
if (m_isInternalLocked && !m_dfuUnlocked) {
// We have to check if external was written in order to
// prevent recovery mode loop or the necessity to activate STM bootloader (which is like a superuser mode)
// Nevertheless, unlike NumWorks, we don't forbid its access.
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errTARGET;
leaveDFUAndReset(false);
return;
}
currentMemoryType = 0;
// If the protection is activated,
// we check the internal magic code in order to prevent the NumWorks' Bootloader flash
if (m_isFirstInternalPacket && !m_dfuUnlocked) {
for (int i = 0; i < 4; i++) {
if (k_omegaMagic[i] != m_largeBuffer[k_internalMagicAddress + i]) {
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errVERIFY;
return;
}
}
else { // All people trying to write on the internal flash before external are considered as not authorized
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errTARGET;
leaveDFUAndReset(false);
return;
}
// We only check the first packet because there is some predictable data in there
m_isFirstInternalPacket = false;
}
else if (m_writeAddress < k_externalAppsBorderAddress) { // If we are not installing external apps
if (m_dfuLevel == 0) {
for (size_t i = 0; i < 9; i++) {
if (k_externalMagik[i] != m_largeBuffer[k_externalMagikPointer + i]) {
m_largeBufferLength = 0;
m_state = State::dfuERROR;
m_status = Status::errWRITE;
leaveDFUAndReset(false);
return;
}
m_largeBuffer[k_externalMagikPointer + i] = 0; // We reset the buffer to its initial value
} else {
currentMemoryType = 1;
// We are writing in the external part where live the users apps. It's not a sensitive memory,
// but we check it in Upsilon Mode to ensure compatibility between the internal and the external.
if (m_writeAddress < k_ExternalBorderAddress && m_isFirstExternalPacket && m_dfuLevel == 0 &&
!m_dfuUnlocked) {
// We skip any data verification if the user is writing in the Optionals Applications part in the
// external (Externals Apps)
for (int i = 0; i < 4; i++) {
if (k_externalUpsilonMagic[i] != m_largeBuffer[k_externalMagicAddress + i]) {
m_largeBufferLength = 0;
leaveDFUAndReset(false);
return;
}
m_isTemporaryUnlocked = true; // We can unlock the flash because signature is good
}
else {
m_haveAlreadyFlashedExternal = true;
}
}
// We only check the first packet because there is some predictable data in there,
// and we unlock the internal memory
m_isFirstExternalPacket = false;
m_isInternalLocked = false;
}
int pageToErase = Flash::SectorAtAddress(m_writeAddress);
//On vérifie qu'on a pas déjà effacé le secteur et si ce n'est pas un secteur external déjà effacé
if ((m_lastErasedPage == -1 || pageToErase != m_lastErasedPage) && m_writeAddress < k_externalAppsBorderAddress && !m_dfuUnlocked) {
Flash::EraseSector(pageToErase);
m_lastErasedPage = pageToErase;
// We check if we changed the memory type where we are writing from last time.
if (m_lastMemoryType >= 0 && currentMemoryType != m_lastMemoryType) {
m_lastMemoryType = -1;
}
m_erasePage = Flash::SectorAtAddress(m_writeAddress);
// We check if the Sector where we are writing was not already erased and if not, we erase it.
if ((m_lastMemoryType < 0 || m_erasePage != m_lastPageErased) &&
m_writeAddress < k_ExternalBorderAddress && !m_dfuUnlocked) {
Flash::EraseSector(m_erasePage);
m_lastMemoryType = currentMemoryType;
}
m_lastPageErased = m_erasePage;
m_erasePage = -1;
// We wait a little before writing in order to prevent some memory error.
Ion::Timing::msleep(1);
Flash::WriteMemory(reinterpret_cast<uint8_t *>(m_writeAddress), m_largeBuffer, m_largeBufferLength);
} else {
// Invalid write address
@@ -311,7 +331,6 @@ void DFUInterface::writeOnMemory() {
m_status = Status::errTARGET;
return;
}
// Reset the buffer length
m_largeBufferLength = 0;
// Change the interface state and status
@@ -350,11 +369,14 @@ bool DFUInterface::dfuAbort(uint16_t *transferBufferLength) {
}
void DFUInterface::leaveDFUAndReset(bool do_reset) {
resetProtectionVariables();
resetFlashParameters();
m_isInternalLocked = true;
m_isFirstInternalPacket = true;
m_isFirstExternalPacket = true;
m_device->setResetOnDisconnect(do_reset);
m_device->detach();
}
}
}
}
}

View File

@@ -21,7 +21,7 @@ namespace USB
class DFUInterface : public Interface {
public:
DFUInterface(Device *device, Endpoint0 *ep0, uint8_t bInterfaceAlternateSetting):
DFUInterface(Device *device, Endpoint0 *ep0, uint8_t bInterfaceAlternateSetting) :
Interface(ep0),
m_device(device),
m_status(Status::OK),
@@ -34,16 +34,18 @@ public:
m_writeAddress(0),
m_eraseAddress(0),
m_bInterfaceAlternateSetting(bInterfaceAlternateSetting),
m_lastErasedPage(-1),
m_isErasingAndWriting(false),
m_isTemporaryUnlocked(false),
m_haveAlreadyFlashedExternal(false),
m_isFirstInternalPacket(true),
m_isInternalLocked(true),
m_isFirstExternalPacket(true),
m_lastMemoryType(-1),
m_lastPageErased(-1),
m_dfuUnlocked(false),
m_dfuLevel(0)
{
m_dfuLevel(0) {
}
uint32_t addressPointer() const { return m_addressPointer; }
void wholeDataReceivedCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) override;
void wholeDataSentCallback(SetupPacket *request, uint8_t *transferBuffer, uint16_t *transferBufferLength) override;
bool isErasingAndWriting() const { return m_isErasingAndWriting; }
@@ -68,7 +70,8 @@ private:
GetStatus = 3,
ClearStatus = 4,
GetState = 5,
Abort = 6
Abort = 6,
Unlock = 11
};
// DFU Download Commmand Codes
@@ -99,32 +102,33 @@ private:
};
enum class State : uint8_t {
appIDLE = 0,
appDETACH = 1,
dfuIDLE = 2,
dfuDNLOADSYNC = 3,
dfuDNBUSY = 4,
dfuDNLOADIDLE = 5,
dfuMANIFESTSYNC = 6,
dfuMANIFEST = 7,
appIDLE = 0,
appDETACH = 1,
dfuIDLE = 2,
dfuDNLOADSYNC = 3,
dfuDNBUSY = 4,
dfuDNLOADIDLE = 5,
dfuMANIFESTSYNC = 6,
dfuMANIFEST = 7,
dfuMANIFESTWAITRESET = 8,
dfuUPLOADIDLE = 9,
dfuERROR = 10
dfuUPLOADIDLE = 9,
dfuERROR = 10
};
class StatusData : public Streamable {
public:
StatusData(Status status, State state, uint32_t pollTimeout = 10):
StatusData(Status status, State state, uint32_t pollTimeout = 10) :
/* We put a default pollTimeout value of 1ms: if the device is busy, the
* host has to wait 1ms before sending a getStatus Request. */
m_bStatus((uint8_t)status),
m_bwPollTimeout{uint8_t((pollTimeout >> 16) & 0xFF), uint8_t((pollTimeout >> 8) & 0xFF), uint8_t(pollTimeout & 0xFF)},
m_bState((uint8_t)state),
* host has to wait 1ms before sending a getStatus Request. */
m_bStatus((uint8_t) status),
m_bwPollTimeout{uint8_t((pollTimeout >> 16) & 0xFF), uint8_t((pollTimeout >> 8) & 0xFF),uint8_t(pollTimeout & 0xFF)},
m_bState((uint8_t) state),
m_iString(0)
{
}
{
}
protected:
void push(Channel * c) const override;
void push(Channel *c) const override;
private:
uint8_t m_bStatus; // Status resulting from the execution of the most recent request
uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits
@@ -135,7 +139,7 @@ private:
class StateData : public Streamable
{
public:
StateData(State state) : m_bState((uint8_t)state) {}
StateData(State state) : m_bState((uint8_t) state) {}
protected:
void push(Channel *c) const override;
private:
@@ -143,15 +147,16 @@ private:
};
/* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is
* linked with dfu.ld, so we cannot access the values. */
constexpr static uint32_t k_sramStartAddress = 0x20000000;
constexpr static uint32_t k_sramEndAddress = 0x20040000;
constexpr static uint32_t k_externalAppsBorderAddress = 0x90200000;
* linked with dfu.ld, so we cannot access the values. */
constexpr static uint32_t k_sramStartAddress = 0x20000000;
constexpr static uint32_t k_sramEndAddress = 0x20040000;
constexpr static uint32_t k_ExternalBorderAddress = 0x90200000;
constexpr static int k_internalMagikPointer = 0x1C4;
constexpr static int k_externalMagikPointer = 0x44F;
constexpr static uint8_t k_internalMagik[4] = {0xF0, 0x0D, 0xC0, 0xDE};
constexpr static uint8_t k_externalMagik[4] = {0x32, 0x30, 0x30, 0x36};
const static int k_internalMagicAddress = 0x1C4;
constexpr static int k_externalMagicAddress = 0x44f;
constexpr static uint8_t k_omegaMagic[4] = {0xF0, 0x0D, 0xC0, 0xDE};
// TODO maybe do: add seperated upsilon magic (k_upsilonMagic)
constexpr static uint8_t k_externalUpsilonMagic[4] = {0x32, 0x30, 0x30, 0x36};
// Download and upload
bool processDownloadRequest(uint16_t wLength, uint16_t *transferBufferLength);
@@ -174,14 +179,18 @@ private:
// Abort
bool dfuAbort(uint16_t *transferBufferLength);
// Leave DFU
void leaveDFUAndReset(bool do_reset=true);
void leaveDFUAndReset(bool do_reset = true);
/* Erase and Write state. After starting the erase of flash memory, the user
* can no longer leave DFU mode by pressing the Back key of the keyboard. This
* way, we prevent the user from interrupting a software download. After every
* software download, the calculator resets, which unlocks the "exit on
* pressing back". */
void willErase() { m_isErasingAndWriting = true; }
void resetProtectionVariables() { m_lastErasedPage = -1; m_isTemporaryUnlocked = false; m_haveAlreadyFlashedExternal = false; }
void resetFlashParameters() {
m_lastMemoryType = -1;
m_lastPageErased = -1;
}
Device *m_device;
Status m_status;
@@ -194,10 +203,12 @@ private:
uint32_t m_writeAddress;
uint32_t m_eraseAddress;
uint8_t m_bInterfaceAlternateSetting;
uint8_t m_lastErasedPage;
bool m_isErasingAndWriting;
bool m_isTemporaryUnlocked;
bool m_haveAlreadyFlashedExternal;
bool m_isFirstInternalPacket;
bool m_isInternalLocked;
bool m_isFirstExternalPacket;
uint8_t m_lastMemoryType; // -1: None; 0: internal; 1: external
uint8_t m_lastPageErased; // -1 default value
bool m_dfuUnlocked;
uint8_t m_dfuLevel; // 0: Upsilon only, 1: Omega-forked only, 2: No update
};

View File

@@ -38,6 +38,7 @@ public:
#else
m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"},
#endif
m_osType(UpsilonMagic),
m_ohm_footer(OmegaMagic) { }
const char * version() const {
assert(m_storageAddress != nullptr);
@@ -78,6 +79,7 @@ public:
private:
constexpr static uint32_t Magic = 0xDEC00DF0;
constexpr static uint32_t OmegaMagic = 0xEFBEADDE;
constexpr static uint32_t UpsilonMagic = 0x55707369;
uint32_t m_header;
const char m_version[8];
const char m_patchLevel[8];
@@ -87,6 +89,7 @@ private:
uint32_t m_ohm_header;
const char m_UpsilonVersion[16];
const volatile char m_username[16];
uint32_t m_osType;
uint32_t m_ohm_footer;
};