 Welcome everybody. My name is David Brown. I'm going to be giving a bit of a presentation on what I've called MCU boot multi-image. I'm going to go over a little bit of background. I know this is a bit more of a diverse audience than I sometimes give this presentation at. A little bit of background on what MCU boot is. The name's kind of obvious. It's broken into MCU and boot. MCU standing for microcontroller. So we're talking about embedded devices, but not like Raspberry Pi. We're talking about little microcontrollers with hundreds of kilobytes or maybe a megabyte of flash and a dozen K to a few hundred K of RAM and it's the bootloader. This is the code that runs first when the system boots up. We'll get into why in just a minute. The idea is to establish a route of trust that there's kind of this conflicting requirement of an MCU. You want to be able to be, you want control over the code that runs on it. You want to be able to know what that code is, but you also need to be able to update it. So you can't really make the code right only or read only right once. So what we do is we create this bootloader that is put in this immutable area of flash that runs, can't be upgraded, and then it manages the rest of the code. Its basic purpose is to validate the image that's in the rest of the flash before it boots and before we do any kind of an upgrade. So there's a lot of diagrams. I hope these are going to be available afterwards. If not, ask me for my email and I can send these slides out. The basic way this memory works is we support what's called Execute in Place. And unlike a typical computer system where you might have a little bit of code in ROM and then most of the code runs out of RAM, on these MCUs, oh, I can't step there. These MCUs typically will run the code directly out of flash. And so what we need to do is have code that lives. I should look here because I can see it. It's going to be interesting. So the bootloader starts generally at memory address zero and there's code typically a few kilobytes beyond that. We're going to call slot zero, although there's a proposal to change these names. And that's the primary code that runs. Then there's another one called slot one, which is an area that's used to do an upgrade of the flash. And then there's this thing called scratch down at the end. It's going to be fun. The scratch area is used as part of the upgrade process, which we'll get to in a minute. So the way this works, this code region here, that's not visible at all, in slot zero starts with a bit of a header. There's some code. There's a manifest and some update state that's maintained as part of that. And executive summary of all of this is when MCU boot starts running, it looks at slot zero. It looks at the header, the code, manifest, signatures, these kinds of things and uses that to make a decision. Do we boot this or is this code been compromised and is not valid? It also looks at slot one to decide, do we have an upgrade available that we want to then put into slot zero for a new version of the firmware? So the important thing to realize from this is the MCU boot isn't responsible for downloading the update, getting it from over the air, whatever process you have. It's simply responsible for installing it once it's been downloaded. So that's typically done as part of whatever application runs on there. We'll go over the network, it will be able to write in slot one and then it does a reboot which causes the update to happen. So current state of MCU boot, we've been about 2017 was our first 1.0 release. And right now we support these what are called execute in place microcontrollers. We have the flash provisioned into the two slots as mentioned. We can validate either an RSA or an ECDSA signature across the code in the slot. And then if the data in slot one is newer than that in slot zero and valid, we can do an upgrade. And based on configuration settings the upgrade can either be an overwrite or we have this thing that we call swap where we exchange the two images to allow a revert if something is wrong and that image is not runnable. So to start with I want to talk a little bit about this execute in place idea. This is the same map we had before where the code lives at in this case 0x8000. And it's actually linked at that address. So if there's any non-relative addresses that you usually find vector tables, that kind of thing, they point as if the code will live at this address 8000. The upgrade which is going to be placed at 42000 in the flash, this is just an example, will still be linked to run at 8000. And so in order to run it the code actually has to be moved into slot zero and doesn't run directly out of slot one. This is by far the most common way these microcontrollers are set up. Generally the flash is actually built onto the chip and there's not that much RAM. There usually is much less RAM than there is flash and I guess that's basically it. We're starting to see some chips that use a more kind of traditional approach which I'll call non-execute in place for lack of a better term. The idea here is the code lives in these slots but in order to run it it's first copied into RAM and then executed from there. This is not that high of a priority for the MCU boot project except that one of our primary development boards for, we'll get to in a minute for this trusted execution works this way. So it kind of ended up having to put some work into doing this because that's the board that I have to run on. Turns out this picture is actually kind of a lie. It isn't quite that simple because the processor still has to start executing from somewhere. And what usually ends up happening is there's a first stage bootloader that's either masked into ROM somewhere inside the chip that then copies the bootloader MCU boot into RAM where it then runs. And then it decides to copy something from slot zero into another part of RAM to run. And it's not even quite that simple. The board that I'm talking about the ARM development board is able to execute from flash but it's a serial flash so it's really slow. It's about 75 clock cycles per instruction for example. So we do actually boot from that but there's just a small piece of code that's responsible for copying the next piece into RAM so that it can run at time. It was interesting about this boot process. Go back to the simple version. The code is linked to run it the RAM address. And what's interesting is that means we don't have to do any of this swap stuff. So this non-executive place is actually a whole lot easier. The bootloader's job is not to swap things around in flash but just to decide which slot is the correct one to run and copy that code into RAM. Interestingly it also means that the flash layout doesn't have to be fixed. That could be a file system. Maybe put a fat file system or some other kind of file system in there and there's some devices that kind of define that for you. So the main topic that I wanted to get into is this multi-image. The non-executive place was kind of forced upon us because of this board. But what we're trying to get into is this idea of multi-image and we'll get in a minute to the idea of why. A typical example would be imagine an MCU that's got a couple of CPUs in it. It may have a Cortex-M4 and a Cortex-M0 in the same chip. Going through an MPU, memory protection unit, it's like a memory manager except there's no address translation. It's just about protection. You give ranges of memory that are allowed for execute, for read and for write. And then we have flash and SRAM and peripherals and these two processes are allowed access to it. This is typically divided into like a secure and a non-secure processor where certain kinds of secure operations be they signature management, management of secrets. These kinds of things are handled by a separate processor that has its own address, its own allowed addresses. So the idea here is that we then chop up the flash into a whole bunch of smaller pieces. So for lack of better terms I've called them slot zero secure, slot zero non-secure, slot one secure, slot one non-secure. Each of these has this same header of code, the manifest, update, state. And what the bootloader now does is it has to decide is the secure and the non-secure image are they both valid before we can boot. And then whatever is necessary to start the two processors running that. Then we also have two slots for upgrades and if there is a new version of the application, say the non-secure code, but we want to keep the same secure code, that could be placed into this slot one non-secure. The bootloader would decide oh the non-secure image needs to be updated, the secure one is fine, it will do the upgrade, check that everything is valid and then start running these two images. So in addition to multiple CPU this exists. An example I can think of is a PSOX6, I believe it's called by Cypress Semiconductor. It's this pretty much exact setup here with two CPUs, one doing secure, one non-secure, shared memory and this MPU. Another really big one that's happening is with this truss zone M or V8M that ARM is coming out with where we're kind of doing the same thing, we have a secure, we have a non-secure, but they're done by the same processor, the barrier of separation is done in the CPU, a single CPU itself. We go through the memory protection or what they call SAU, accesses the same memory, same flash, same peripherals. And in a lot of ways this is very similar to the multiprocessor except that it's done not at the same time. There's a state change in the processor between secure and non-secure. For either of these, both this one with two processors and by the way this is not like SMP, these are not just two processors that are available to run the same code, they have their own separate pieces of code that are run and they're doing different tasks. Whether we have multiple CPUs or a single CPU that kind of acts like two different personalities, the idea is we need to manage multiple of these images and it's very definitely possible in the future we may have three, we may have four, maybe there's the two processors, the secure and the non-secure and we have a wireless offload processor that's using the same memory. Typically these are done with their own address base, with their own flash, but it could definitely be done, this is the kind of thing that would be nice to handle with the bootloader. So I want to get a little further into what makes this a little complicated. I talked about this thing we call a manifest and I'll get to where the term comes from because it's not called a manifest in the MCU boot code, unfortunately we just call it a TLV which is a hint, don't name things after the data structure they're implemented with, give them names so that you can change the data structure and still keep the same name, but regardless that manifest it's the metadata that describes the image that is going to be run. It's broken into two pieces, there's this header that lives, is that invisible at all? The header that lives before the image which is about 32 bytes, it's basically a magic number, a version field and a length and some flags, a couple other things. Following this immediately is the actual executable code, it runs right in place due to some constraints on ARM, sometimes there's a bit of padding because there's usually alignment constraints on where the vector table can live. And then following this which is computed by header plus length jumps to this TLV manifest which has a magic number and a length and then it's a bunch of these triplets of tag, length, value, tag, length and value. The kind of things that are there might be a SHA-256 hash, a key hash which is the hash of a public key so the bootloader knows which key to check this against and then possibly some signatures. So a typical image might have a SHA-256 of the code, a key hash describing an RSA public key and then an RSA 2048 PSS signature and we might also have another key hash and an ECDSA signature. The scheme is set up so there can be as many signatures as are desired and the current code basically says it walks through them and as long as it finds one signature that it has as its list of public keys that are allowed, the image is considered valid and it will boot or upgrade it. If none of them are valid, it considers the image invalid. So how do we expand this to multiple images? Right now this is embedded in the single image, the upgrade image has another one embedded in it. Kind of the most obvious way to expand this is by having multiple manifests. So before we had an image and an upgrade, now I have a secure image, a non-secure image, a secure upgrade and a non-secure upgrade so we can just put this same manifest on each one of them. And what this means we have a separate header, we have a separate signature, we have code and this is actually pretty straightforward to do. The tools aren't that hard to extend for this but there's a lot of questions that have to be answered like what if you change the API between the secure and the non-secure side? You can get into a situation where if you upgrade one without the other, your system now doesn't work or maybe isn't even bootable. But other times maybe you do have the same API, you just fixed a bug in one of the images and it's perfectly reasonable to upgrade that image without the other one. And what it really means is we need to support all of those situations. The best way to do that with this is to describe dependencies. And what this is is, if I can read that, this little blue block there is describing one of these tags. So maybe the secure image, maybe that doesn't need dependencies and maybe the non-secure image says in addition to all the existing tags I now depend upon at least version 1.1 that probably should be a greater than or equal to, but not more than 1.2.6. And this is actually under a lot of discussion right now. How do we represent this? What needs to be represented? Is it just fine to say that 1.x is sufficient, 1.2.x? Does it need to be richer? Does it need to be much more complicated than that? The idea being that then when MCU boot wants to do an update, it will look at the new images available whether it be one or both, and then it will analyze what would I get if I did the upgrade and is that valid. And so if you put in a new non-secure image, but its version constraint won't work with the existing secure image, then it would just not do the upgrade. It will boot up, check it, and then skip it. And maybe later, maybe it was in the process of downloading an update, it downloaded the non-secure image but it hadn't downloaded the new secure image. And so it will just wait, and if we do eventually upgrade both images to where the new versions of them are compatible, it will then actually do the upgrade. Also means that we have to be atomic about the upgrades sometimes. If the version dependencies require both images to be upgraded, it doesn't make any sense then to upgrade one of them. And so this notion of state that we have to maintain that we're in the process of doing an upgrade now covers both images. And if we're in the process of doing an upgrade, even if we've finished one of them, if the second one hasn't finished, we have to complete that before we can successfully boot. Or if we get to a state where we have to revert, maybe we have to revert both of them. So there's a lot of complexity that it adds to the upgrade process, especially when we're in a swap mode where we're trying to keep the old image around in case we need to return to it. So this is kind of complicated. We have another solution that is also kind of complicated. I haven't come up with the one that isn't complicated. But what if we take this manifest and we completely detach it from the two images? So we have our images, the secure image, the non-secure image, and then the manifest is a separate piece of data somewhere that has its extended so it can say, oh, this is the signature of that one. This is the hash of the other image. And then we put all of the dependencies in here. And the idea is then this would probably get attached to whichever image is going to get updated more, which is likely to be the non-secure for this particular case. And if you upgrade just that image with its manifest, that would then give enough information to describe the other image. It solves some of the problems. It makes the atomic upgrade a little easier to decide. It makes it harder to know if there wasn't secure image, where is its signature stored, and that kind of thing. Another interesting point about this is it's also possible to just have a policy that says, we always upgrade both images. The reason you'd usually want to only upgrade one image is because upgrades are kind of expensive with these devices. It may be a low-power wireless interface and we don't want to be transferring data over. And another way to solve that problem is at the protocol level where we are transferring the new images, maybe we extend that to say, well, the new version is actually just the same as the secure version. We don't transfer the new data, we just copy it from the existing image. A more general solution is delta compression, the one I just described is kind of a specific instance of delta compression. But all in all, there's kind of a bit of a problem with what I've just described here. And the question is what is signed? So what gets missed with all of this is right now in MCU boot the signature of this block of metadata consists of some markers that say this is this particular key and then a signature. As soon as we add something that isn't a signature to this, it turns out it's not protected by any kind of signature. So these dependencies, if we just drop them in there as new TLV fields, they're subject to being tampered with. They could be changed and no one would know any better because the signature is just over the code. And what about if we're using one manifest with both, what's our signature? Are we signing both images? Do we have to have multiple then new TLV entries to describe the different signatures? That's actually an error. This should be both slot zero, one should be secure and one should be non-secure. There's a pretty easy solution to this and that's to, instead of signing the code, let's sign the manifest. So the manifest starts with these hashes of the code. Maybe it describes the hash of the secure, the hash of the non-secure image. Describes all the dependencies. And then this last part of it, instead of being a signature of the code, is a signature of the manifest itself. And this works, this solves the problem. It allows us to add pretty much any kind of metadata we want that will then be protected by these signatures, which brings us to suit. And this is an IETF working group. It was originally called FUD for firmware update. And there was actually somebody that reported that if it got called that they would not be allowed to use it at their company. So eventually this thing got renamed for the software update for IOT, hence suit. The working group seeks to basically solve this problem of how do we describe the metadata that describes firmware updates for IOT devices. It's pretty much what we want to do in MCU boot, but it's going to be a very different format, a very different solution. The other thing is it's a working group. If you're familiar with how those work, it's in a lot of flux. You have a lot of voices. A lot of people have their own part they're bringing to the discussion and over time this changes a lot. So as far as MCU boot goes, this is not something we're going to support today. This is something we want to be in part of the discussion so that we make sure that our needs are represented by that. And maybe when this settles and becomes a real RFC, we can then make MCU boot support this if that's appropriate. The big thing is that the suit manifest is likely to be much more complicated than we need. MCU boot currently targets systems where the bootloader has to fit in 16 kilobytes and manage code that's tens or hundreds of kilobytes at most. And these little tiny microcontrollers and if you're having to put code in there to parse a complicated manifest, that's not necessarily going to work. So it's probably always going to be the case that we're going to have to support a simple manifest as well as a richer manifest for larger devices. And somebody put it really well on the MCU boot list. The easiest way to handle this is to make sure that there is a semantic equivalence between the different manifest formats. Whatever we come up with in MCU, in suit, we can describe an equivalence between that manifest and what we're doing now. So that's our goal. That's a future thing. We're not ready for that. But one thing that suit does bring that's pretty much been settled is another RFC that exists called COSA COS. I've heard it pronounced several different ways. It's essentially a way of describing signatures and encryption of something. In this case it would be the manifest. But it fits in exactly what that problem we had before, what is signed. COS breaks things down to this idea of a group of protected headers, a group of unprotected headers, some kind of payload and a signature. For suit, that payload would be the suit manifest itself. It may be reasonable to use this format to describe the signature over a new manifest that we use ourselves. Significant is that this is all encoded in seaborb, which is a... think of it as binary JSON. It's similar to something like message pack, but it was designed with the goal of not requiring a lot of code to parse. So, I want to say it's about 30 or 40 lines of code that I was able to write that can just walk through an arbitrary seaborb data structure. Whereas something like message pack, there's a few hundred lines needed just because the tokens are specialized instead of just bit fields. But the significant thing about this is that they do the same thing that I said we needed to do to our manifest. The suit manifest contains right now, this is as of current. There's going to be a new proposal that comes out and this will all be completely different, I'm fairly certain. But embedded in these conditions and directives or the version dependencies and things like this is the hash of the code. And that way all of this data is represented and then that thing gets signed and we know that it's protected. The important thing is that this manifest only has the hashes and the signature is a separate thing. So, all of this kind of background really. What are we doing with MCU boot in regards to supporting multiple images and this idea of execute in place, suit, these kind of things. As far as suit goes, we kind of don't have a choice but to wait. If we implemented the current draft of the manifest format, it's going to completely be changed. That would just kind of be wasted code. For non-execute place in place, the goal was kind of, it's not really that important. That may still be the goal, it kind of depends on what hardware becomes available but this may have to get done. Just because the main board I have is non-executed in place that supports this dual image. As far as multiple images, the split manifest where there's a manifest on each of the upgrade image, that's what we want to do. There's a proposal in GitHub for the MCU boot project which is describing basically this. It's under discussion. The goal is to get this. Just as a little bit of background, the trusted firmware M project took a branch of MCU boot about six months ago. Took it at that version then basically hacked up the code so that it would work with that architecture with the upgrade image. The goal now is to then merge these changes conceptually back into the mainline. A lot of questions that have to be answered that didn't need to be answered for a prototype. The question of what happens if one image gets reverted and do we need to revert the other. There's a lot of questions that need to be answered but the goal is to do this with the two manifests, adding the version information. As far as the detached manifest, it's kind of a wait and see. It's more complicated in some ways. It's simpler in others. There's a lot of unanswered questions about it. The most important being we even need to do that. Or do we just wait for suit and then at that point decide what needs to be done. So that's all that I have. I guess I could go back to that. As far as MCU boot is concerned, have a couple of minutes. If anybody has any questions, I have a microphone. I've been told that we have to ask this around unless I will be answering silence. So if you're optimistic about suit's efforts, I guess my question would be why were you establishing a semantic equivalence from suit to MCU boot instead of the other way around, especially if MCU boot is kind of the simpler format that should be easier to translate up into a more complex one for a bigger boot loader? Yeah, and I guess that's a question of what do I mean by it when I say that. I mean I think the way you describe it does make more sense. It's really a question of what kind of influence, how much influence will we get over what suit does. The way the proposal was, the discussion that happened in the MCU boot Slack channel was should we care about suit at all. And the kind of the conclusion that came from the people that were concerned about the really small devices was they're okay with suit support as long as the code doesn't do different things when it sees a suit manifest and it sees a traditional existing manifest. That we don't have different behavior depending on which manifest you have. Now obviously if the code sports more features of the manifest it would do something different. But you're right, if there's a semantic equivalent that you could take any existing manifest and represent that in the new format it should do the same thing. Any other questions? Is there any plans to have some kind of interface between the application and MCU boot? What if this compatibility fails? Is there any feedback to the application? There is an open issue that's been there for quite some time to do just that. It's not a solved question. We don't have a way of doing it, but it's important. For obvious reasons there's a lot of different things you could convey. What's the layout of the flash? The application needs to know where to put the upgrade image. To deeper things like we could handle incompatible versions if there was a way of knowing what the versions are. The API version could be communicated to the application and then it would be possible to upgrade them maybe more independently. But it's an open question right now as far as even how that gets implemented. Is it just a data structure that we have in known format or is there some kind of callback mechanism that's much more sophisticated? And we just haven't gotten to it yet. Is MCU boot capable of handling updates of itself? No, so there's a couple parts of that. The basic security requirement is that it can't be upgraded. Every time you think about how do we do a route of trust and allow upgrades, you have to have some piece that you want to make as small as possible that can't be upgraded and then you have the rest that can be. For the most part with this kind of scenario when we sit down and we say, well, what's the absolute minimum that has to be in this immutable bootloader? And that's what MCU boot is right now. If we get more functionality and maybe multiple images is that? It might be better to go to a two-stage boot process where there's this very small core initially and then there's something richer. There's been some debate in the Slack channel as to whether this notion of swapping images is too complicated for an immutable bootloader. It's much more complexity than the rest of the code combined. And so one possibility is you have a simple bootloader that can just run one of the others and then you have the more complicated one that can handle that. But again, that's not yet implemented right now. It's kind of assumed that once you put MCU boot in there, you can't upgrade it. Any other questions? My question is multiple image support is about updating multiple cores. Does the current design current proposal require that they have, for example, shared memory? What if the cores are on separate devices and they only have some transport between them? Right. Yeah, that's something that's important. It's not any hardware that we have right now and therefore it's kind of a lower priority thing. So yeah, it's something that is definitely in the scope of what MCU boot could do but not something that's currently handled. There's a bunch of things that are on the to-do list. Another one that just started going in is what if slot one is an external flash? So the primary code is internal and we have an external flash and that part kind of works if you have the drivers set up correctly. What this thing does is it allows the images that are on the external flash to be encrypted because that's easier to access from somebody trying to attack the device. That patch encrypts the images that are external. That's kind of similar to what if you had to send them over a transport to another device and basically there's a lot of specifics that don't show up until we have one that we wanted to implement for. So I don't know if that's really an answer or just a not yet but hopefully that helps. Okay, thanks. Anything else? We can hand the mic all the way to the back. So my question is once you have Zephyr running on your MCU, how much work is it to get MCU boot working on that system? In theory, you configure it in type make. I haven't done it a lot. I've done a couple of targets that are already supported by Zephyr and it does work. That's provided you have working flash drivers and the Zephyr port supports the notion. I mean, most of them seem to now, at least with ARM, supports the notion of the code not being at zero. It's kind of the main thing that has to be done with the application in Zephyr. And generally they tend to just work at that point. Your flash driver needs to be device-care aware in order to support the application. Yeah. And there's some issues with the flash driver. We tend to configure Zephyr without a scheduler for MCU boot. And if your flash driver needs a scheduler, then that might make Zephyr big and there's some ongoing discussions about that. But it mostly just works. Does that answer the question? I guess so. Anything else? We've got like one minute or less than a minute. Otherwise, well, thank you for coming and we'll see you later.