Skip to content

Commit

Permalink
Merge pull request #746 from particle-iot/feature/sc-129083/when-flas…
Browse files Browse the repository at this point in the history
…hing-device-os-modules-to-a-protected

Feature/sc 129083/when flashing device os modules to a protected
  • Loading branch information
monkbroc authored Jun 27, 2024
2 parents fa277b4 + b48ae06 commit e823810
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/cmd/flash.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const {
createFlashSteps,
filterModulesToFlash,
parseModulesToFlash,
validateModulesForProtection,
flashFiles,
validateDFUSupport,
getFileFlashInfo
Expand Down Expand Up @@ -87,6 +88,7 @@ module.exports = class FlashCommand extends CLICommandBase {
platformId: device.platformId,
platformName
});
await validateModulesForProtection({ modules: modulesToFlash, device });
const flashSteps = await createFlashSteps({
modules: modulesToFlash,
isInDfuMode: device.isInDfuMode,
Expand Down Expand Up @@ -156,6 +158,7 @@ module.exports = class FlashCommand extends CLICommandBase {
let modulesToFlash = [...fileModules, ...deviceOsModules];
modulesToFlash = filterModulesToFlash({ modules: modulesToFlash, platformId });

await validateModulesForProtection({ modules: modulesToFlash, device });
const flashSteps = await createFlashSteps({
modules: modulesToFlash,
isInDfuMode: device.isInDfuMode,
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const semver = require('semver');
const usbUtils = require('./usb-util');
const deviceOsUtils = require('../lib/device-os-version-util');
const CLICommandBase = require('./base');
const { parseModulesToFlash, filterModulesToFlash, createFlashSteps, flashFiles, validateDFUSupport } = require('../lib/flash-helper');
const { parseModulesToFlash, filterModulesToFlash, validateModulesForProtection, createFlashSteps, flashFiles, validateDFUSupport } = require('../lib/flash-helper');
const createApiCache = require('../lib/api-cache');

module.exports = class UpdateCommand extends CLICommandBase {
Expand Down Expand Up @@ -41,6 +41,7 @@ module.exports = class UpdateCommand extends CLICommandBase {
});
const deviceOsModules = await parseModulesToFlash({ files: deviceOsBinaries });
const modulesToFlash = filterModulesToFlash({ modules: deviceOsModules, platformId: device.platformId, allowAll: true });
await validateModulesForProtection({ modules: modulesToFlash, device });
const flashSteps = await createFlashSteps({ modules: modulesToFlash, isInDfuMode: device.isInDfuMode , platformId: device.platformId });
await flashFiles({ device, flashSteps, ui: this.ui });
this.ui.write('Update success!');
Expand Down
34 changes: 34 additions & 0 deletions src/lib/flash-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const ensureError = utilities.ensureError;
// Flashing an NCP firmware can take a few minutes
const FLASH_TIMEOUT = 4 * 60000;

// Minimum version of Device OS that supports protected modules
const PROTECTED_MINIMUM_VERSION = '6.0.0';
const PROTECTED_MINIMUM_SYSTEM_VERSION = 6000;
const PROTECTED_MINIMUM_BOOTLOADER_VERSION = 3000;

async function flashFiles({ device, flashSteps, resetAfterFlash = true, ui, verbose=true }) {
let progress = null;
progress = verbose ? _createFlashProgress({ flashSteps, ui, verbose }) : null;
Expand Down Expand Up @@ -358,6 +363,34 @@ function validateDFUSupport({ device, ui }) {
}
}

async function validateModulesForProtection({ modules, device }) {
try {
const s = await device.getProtectionState();

if (!s.protected && !s.overridden) {
// Device is not protected -> Don't enforce Device OS version
return;
}
} catch (error) {
// Device does not support device protection -> Don't enforce Device OS version
if (error.message === 'Not supported') {
return;
}
throw error;
}

for (const module of modules) {
const { moduleFunction, moduleIndex, moduleVersion } = module.prefixInfo;
const oldSystem = moduleFunction === ModuleInfo.FunctionType.SYSTEM_PART &&
moduleVersion < PROTECTED_MINIMUM_SYSTEM_VERSION;
const oldBootloader = moduleFunction === ModuleInfo.FunctionType.BOOTLOADER &&
moduleIndex === 0 && moduleVersion < PROTECTED_MINIMUM_BOOTLOADER_VERSION;

if (oldSystem || oldBootloader) {
throw new Error(`Cannot downgrade Device OS below version ${PROTECTED_MINIMUM_VERSION} on a Protected Device`);
}
}
}

module.exports = {
flashFiles,
Expand All @@ -366,6 +399,7 @@ module.exports = {
createFlashSteps,
prepareDeviceForFlash,
validateDFUSupport,
validateModulesForProtection,
getFileFlashInfo,
_get256Hash,
_skipAsset
Expand Down
131 changes: 131 additions & 0 deletions src/lib/flash-helper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
filterModulesToFlash,
prepareDeviceForFlash,
validateDFUSupport,
validateModulesForProtection,
getFileFlashInfo,
_get256Hash,
_skipAsset
Expand Down Expand Up @@ -659,4 +660,134 @@ describe('flash-helper', () => {
expect(res).to.equal(false);
});
});

describe('validateModulesForProtection', () => {
let device;
const modulesOldBootloader = [{
prefixInfo: {
moduleFunction: ModuleInfo.FunctionType.BOOTLOADER,
platformId: 12,
moduleIndex: 0,
moduleVersion: 1200
}
}];
const modulesOldSystem = [{
prefixInfo: {
moduleFunction: ModuleInfo.FunctionType.SYSTEM_PART,
platformId: 12,
moduleIndex: 0,
moduleVersion: 5800
}
}];
const modulesNew = [{
prefixInfo: {
moduleFunction: ModuleInfo.FunctionType.BOOTLOADER,
platformId: 12,
moduleIndex: 0,
moduleVersion: 3000
}
}, {
prefixInfo: {
moduleFunction: ModuleInfo.FunctionType.SYSTEM_PART,
platformId: 12,
moduleIndex: 0,
moduleVersion: 6000
}
}];

beforeEach(() => {
device = {
getProtectionState: sinon.stub(),
};
});

describe('device is not protected', () => {
beforeEach(() => {
device.getProtectionState.returns({ protected: false, overridden: false });
});

it('does does not reject old modules', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesOldBootloader });
} catch (_error) {
error = _error;
}
expect(error).to.be.undefined;
});

it('does does not reject new modules', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesNew });
} catch (_error) {
error = _error;
}
expect(error).to.be.undefined;
});
});

describe('device is protected', () => {
beforeEach(() => {
device.getProtectionState.returns({ protected: true, overridden: false });
});

it('throws an exception if the bootloader is too old', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesOldBootloader });
} catch (_error) {
error = _error;
}
expect(error).to.have.property('message').that.eql('Cannot downgrade Device OS below version 6.0.0 on a Protected Device');
});

it('throws an exception if the system part is too old', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesOldSystem });
} catch (_error) {
error = _error;
}
expect(error).to.have.property('message').that.eql('Cannot downgrade Device OS below version 6.0.0 on a Protected Device');
});

it('does does not reject new modules', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesNew });
} catch (_error) {
error = _error;
}
expect(error).to.be.undefined;
});
});

describe('device does not support protection', () => {
beforeEach(() => {
device.getProtectionState.throws(new Error('Not supported'));
});

it('does does not reject old modules', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesOldBootloader });
} catch (_error) {
error = _error;
}
expect(error).to.be.undefined;
});

it('does does not reject new modules', async () => {
let error;
try {
await validateModulesForProtection({ device, modules: modulesNew });
} catch (_error) {
error = _error;
}
expect(error).to.be.undefined;
});
});
});
});

0 comments on commit e823810

Please sign in to comment.