sim: Test software rollback protection
Signed-off-by: Håkon Øye Amundsen <haakon.amundsen@nordicsemi.no>
Signed-off-by: David Brown <david.brown@linaro.org>
diff --git a/.travis.yml b/.travis.yml
index a7587b7..c048e71 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -41,6 +41,8 @@
env: MULTI_FEATURES="sig-ecdsa enc-kw validate-primary-slot" TEST=sim
- os: linux
env: MULTI_FEATURES="sig-rsa validate-primary-slot overwrite-only large-write,sig-ecdsa enc-ec256 validate-primary-slot" TEST=sim
+ - os: linux
+ env: MULTI_FEATURES="sig-rsa validate-primary-slot overwrite-only downgrade-prevention" TEST=sim
- os: linux
language: go
diff --git a/sim/Cargo.toml b/sim/Cargo.toml
index c8e592e..165d01b 100644
--- a/sim/Cargo.toml
+++ b/sim/Cargo.toml
@@ -20,6 +20,7 @@
bootstrap = ["mcuboot-sys/bootstrap"]
multiimage = ["mcuboot-sys/multiimage"]
large-write = []
+downgrade-prevention = ["mcuboot-sys/downgrade-prevention"]
[dependencies]
byteorder = "1.3"
diff --git a/sim/mcuboot-sys/Cargo.toml b/sim/mcuboot-sys/Cargo.toml
index 3466ff0..cbf05e5 100644
--- a/sim/mcuboot-sys/Cargo.toml
+++ b/sim/mcuboot-sys/Cargo.toml
@@ -47,6 +47,9 @@
# Support multiple images (currently 2 instead of 1).
multiimage = []
+# Check (in software) against version downgrades.
+downgrade-prevention = []
+
[build-dependencies]
cc = "1.0.25"
diff --git a/sim/mcuboot-sys/build.rs b/sim/mcuboot-sys/build.rs
index 1c6584f..3bf4409 100644
--- a/sim/mcuboot-sys/build.rs
+++ b/sim/mcuboot-sys/build.rs
@@ -22,6 +22,7 @@
let enc_ec256 = env::var("CARGO_FEATURE_ENC_EC256").is_ok();
let bootstrap = env::var("CARGO_FEATURE_BOOTSTRAP").is_ok();
let multiimage = env::var("CARGO_FEATURE_MULTIIMAGE").is_ok();
+ let downgrade_prevention = env::var("CARGO_FEATURE_DOWNGRADE_PREVENTION").is_ok();
let mut conf = cc::Build::new();
conf.define("__BOOTSIM__", None);
@@ -31,6 +32,10 @@
conf.define("MCUBOOT_MAX_IMG_SECTORS", Some("128"));
conf.define("MCUBOOT_IMAGE_NUMBER", Some(if multiimage { "2" } else { "1" }));
+ if downgrade_prevention && !overwrite_only {
+ panic!("Downgrade prevention requires overwrite only");
+ }
+
if bootstrap {
conf.define("MCUBOOT_BOOTSTRAP", None);
}
@@ -39,6 +44,10 @@
conf.define("MCUBOOT_VALIDATE_PRIMARY_SLOT", None);
}
+ if downgrade_prevention {
+ conf.define("MCUBOOT_DOWNGRADE_PREVENTION", None);
+ }
+
// Currently no more than one sig type can be used simultaneously.
if vec![sig_rsa, sig_rsa3072, sig_ecdsa, sig_ed25519].iter()
.fold(0, |sum, &v| sum + v as i32) > 1 {
diff --git a/sim/src/caps.rs b/sim/src/caps.rs
index a63a343..f823b99 100644
--- a/sim/src/caps.rs
+++ b/sim/src/caps.rs
@@ -22,6 +22,7 @@
Ed25519 = (1 << 9),
EncEc256 = (1 << 10),
SwapUsingMove = (1 << 11),
+ DowngradePrevention = (1 << 12),
}
impl Caps {
diff --git a/sim/src/depends.rs b/sim/src/depends.rs
index 57b4bad..f3bbc29 100644
--- a/sim/src/depends.rs
+++ b/sim/src/depends.rs
@@ -22,11 +22,28 @@
/// A boring image is used when we aren't testing dependencies. There will
/// be meaningful version numbers. The size field is the image number we
/// are.
-pub struct BoringDep(pub usize);
+pub struct BoringDep {
+ number: usize,
+ test: DepTest,
+}
+
+impl BoringDep {
+ pub fn new(number: usize, test: &DepTest) -> BoringDep {
+ BoringDep {
+ number: number,
+ test: test.clone(),
+ }
+ }
+}
impl Depender for BoringDep {
fn my_version(&self, _offset: usize, slot: usize) -> ImageVersion {
- ImageVersion::new_synthetic(self.0 as u8, slot as u8, 0)
+ let slot = if self.test.downgrade {
+ 1 - slot
+ } else {
+ slot
+ };
+ ImageVersion::new_synthetic(self.number as u8, slot as u8, 0)
}
fn my_deps(&self, _offset: usize, _slot: usize) -> Vec<ImageVersion> {
@@ -48,6 +65,10 @@
/// What is the expected outcome of the upgrade.
pub upgrades: [UpgradeInfo; 2],
+
+ /// Should this be considered a downgrade (cause the version number to
+ /// decrease).
+ pub downgrade: bool,
}
/// Describes the various types of dependency information that can be
@@ -81,6 +102,15 @@
pub static NO_DEPS: DepTest = DepTest {
depends: [DepType::Nothing, DepType::Nothing],
upgrades: [UpgradeInfo::Upgraded, UpgradeInfo::Upgraded],
+ downgrade: false,
+};
+
+/// A "test" with no dependency information, and the images marked as a
+/// downgrade.
+pub static REV_DEPS: DepTest = DepTest {
+ depends: [DepType::Nothing, DepType::Nothing],
+ upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
+ downgrade: true,
};
/// A PairDep describes the dependencies between two pairs.
@@ -106,6 +136,11 @@
impl Depender for PairDep {
fn my_version(&self, _offset: usize, slot: usize) -> ImageVersion {
+ let slot = if self.test.downgrade {
+ 1 - slot
+ } else {
+ slot
+ };
ImageVersion::new_synthetic(self.number as u8, slot as u8, 0)
}
diff --git a/sim/src/image.rs b/sim/src/image.rs
index 9c24a3e..2c99694 100644
--- a/sim/src/image.rs
+++ b/sim/src/image.rs
@@ -45,6 +45,7 @@
Depender,
DepTest,
DepType,
+ NO_DEPS,
PairDep,
UpgradeInfo,
};
@@ -177,7 +178,7 @@
let dep: Box<dyn Depender> = if num_images > 1 {
Box::new(PairDep::new(num_images, image_num, deps))
} else {
- Box::new(BoringDep(image_num))
+ Box::new(BoringDep::new(image_num, deps))
};
let primaries = install_image(&mut flash, &slots[0], 42784, &*dep, false);
let upgrades = match deps.depends[image_num] {
@@ -222,7 +223,7 @@
pub fn make_bad_secondary_slot_image(self) -> Images {
let mut bad_flash = self.flash;
let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| {
- let dep = BoringDep(image_num);
+ let dep = BoringDep::new(image_num, &NO_DEPS);
let primaries = install_image(&mut bad_flash, &slots[0], 32784, &dep, false);
let upgrades = install_image(&mut bad_flash, &slots[1], 41928, &dep, true);
OneImage {
@@ -569,6 +570,37 @@
fails > 0
}
+ // Test that an upgrade is rejected. Assumes that the image was build
+ // such that the upgrade is instead a downgrade.
+ pub fn run_nodowngrade(&self) -> bool {
+ if !Caps::DowngradePrevention.present() {
+ return false;
+ }
+
+ let mut flash = self.flash.clone();
+ let mut fails = 0;
+
+ info!("Try no downgrade");
+
+ // First, do a normal upgrade.
+ let (result, _) = c::boot_go(&mut flash, &self.areadesc, None, false);
+ if result != 0 {
+ warn!("Failed first boot");
+ fails += 1;
+ }
+
+ if !self.verify_images(&flash, 0, 0) {
+ warn!("Failed verification after downgrade rejection");
+ fails += 1;
+ }
+
+ if fails > 0 {
+ error!("Error testing downgrade rejection");
+ }
+
+ fails > 0
+ }
+
// Tests a new image written to the primary slot that already has magic and
// image_ok set while there is no image on the secondary slot, so no revert
// should ever happen...
@@ -1450,6 +1482,7 @@
/// The image header
#[repr(C)]
+#[derive(Debug)]
pub struct ImageHeader {
magic: u32,
load_addr: u32,
diff --git a/sim/src/lib.rs b/sim/src/lib.rs
index 4e2f6c3..ec8e5ed 100644
--- a/sim/src/lib.rs
+++ b/sim/src/lib.rs
@@ -23,7 +23,9 @@
DepTest,
DepType,
UpgradeInfo,
- NO_DEPS,},
+ NO_DEPS,
+ REV_DEPS,
+ },
image::{
ImagesBuilder,
Images,
diff --git a/sim/tests/core.rs b/sim/tests/core.rs
index 86736b3..07a8449 100644
--- a/sim/tests/core.rs
+++ b/sim/tests/core.rs
@@ -12,6 +12,7 @@
ImagesBuilder,
Images,
NO_DEPS,
+ REV_DEPS,
testlog,
};
use std::{
@@ -54,6 +55,7 @@
sim_test!(norevert, make_image(&NO_DEPS, true), run_norevert());
sim_test!(status_write_fails_complete, make_image(&NO_DEPS, true), run_with_status_fails_complete());
sim_test!(status_write_fails_with_reset, make_image(&NO_DEPS, true), run_with_status_fails_with_reset());
+sim_test!(downgrade_prevention, make_image(&REV_DEPS, true), run_nodowngrade());
// Test various combinations of incorrect dependencies.
test_shell!(dependency_combos, r, {
@@ -75,18 +77,21 @@
DepTest {
depends: [DepType::Nothing, DepType::Nothing],
upgrades: [UpgradeInfo::Upgraded, UpgradeInfo::Upgraded],
+ downgrade: false,
},
// If all of the dependencies are met, we should also upgrade.
DepTest {
depends: [DepType::Correct, DepType::Correct],
upgrades: [UpgradeInfo::Upgraded, UpgradeInfo::Upgraded],
+ downgrade: false,
},
// If none of the dependencies are met, the images should be held.
DepTest {
depends: [DepType::Newer, DepType::Newer],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
+ downgrade: false,
},
// If the first image is not met, we should hold back on the
@@ -95,12 +100,14 @@
DepTest {
depends: [DepType::Newer, DepType::Correct],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
+ downgrade: false,
},
// Test the variant in the other direction.
DepTest {
depends: [DepType::Correct, DepType::Newer],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
+ downgrade: false,
},
// Test where only the first image is upgraded, and there are no
@@ -108,18 +115,21 @@
DepTest {
depends: [DepType::Nothing, DepType::NoUpgrade],
upgrades: [UpgradeInfo::Upgraded, UpgradeInfo::Held],
+ downgrade: false,
},
// Test one image with a valid dependency on the first image.
DepTest {
depends: [DepType::OldCorrect, DepType::NoUpgrade],
upgrades: [UpgradeInfo::Upgraded, UpgradeInfo::Held],
+ downgrade: false,
},
// Test one image with an invalid dependency on the first image.
DepTest {
depends: [DepType::Newer, DepType::NoUpgrade],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
+ downgrade: false,
},
// Test where only the second image is upgraded, and there are no
@@ -127,18 +137,21 @@
DepTest {
depends: [DepType::NoUpgrade, DepType::Nothing],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Upgraded],
+ downgrade: false,
},
// Test one image with a valid dependency on the second image.
DepTest {
depends: [DepType::NoUpgrade, DepType::OldCorrect],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Upgraded],
+ downgrade: false,
},
// Test one image with an invalid dependency on the second image.
DepTest {
depends: [DepType::NoUpgrade, DepType::Newer],
upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
+ downgrade: false,
},
];