this makes i2c accessible without having to rely explicitly
on the server itself, making the library callable in more contexts.
necessitated avoiding a name space collision and moving the
I2C hardware drivers to a stucture named I2cDriver; the I2C
object now unambiguously refers to the userspace interface
to I2C.
This routine pares down the clock setting routine to hopefully
a core minimum set of side-effects while allowing the system
to boot.
It also takes the register bank offsets as arguments, as well
as various functions to accomplish different tasks as argument,
so that it can be used in different contexts (such as in `std`).
Finally, the BIO clock rate can be set to slow or fast. The power
cost of the fast setting is about 12mW. If you're not needing
the high speed features of the BIO, slow setting is probably what
you want.
Currently, the system is configured such that after boot1, the
BIO is running in slow mode (to save power for battery powered
configurations); and then the clock set calls in baremetal and
dabao bump this up to the fast speed since these two configurations
are assumed to be running off of a USB wire with ample power.
40 bytes gives 320 bits which fits nicely into a version 3 code,
which allows 4 pixels per module rendering.
if we have to move to 3 pixels per module, the next code up that optimally
uses the full screen is version 6. This would give 102 bytes of transfer
in a single scan.
The equation for capacity is:
`binary_bytes = floor((alphanumeric_capacity / 3) × 2)`
where `alphameric_capacity` is the capacity of the QR code version
per spec lookup table.
a 40-byte message can be encoded in a version 3 code with
medium ECC. This is pretty optimal because it allows 4 pixels
per module in the QR code, which is a nice size for viewing.
base45 is added as a dependency to the project. Base45 is weird but
apparently it's the most efficient way to code binary data into a
QR code.
40 bytes (320 bits) is enough to exchange a 256-bit key plus
64 bits of sideband data. I think this is going to be enough for
basic device-to-device handshaking.
Previous implementation sent the QR code as draw commands.
This is inefficient as it would require hundreds of draw
commands to be sent per QR code rendered.
This implementation codes the QR code as a bool and sends
it in a single memory message, and implements a QR code renderer
on the receiver side.
The QR code rendering is also cleaned up so that the QR code is
always centered and takes the full screen, to give the best
conditions for someone scanning the code from another device.
non-paranoid mode responses happen through IRQs (recall that
paranoid mode is a hair-trigger hardware reset with no
exceptions). This patch installs the IRQ handlers in boot0
and boot1.
- boot0 in particular now even has an IRQ handler. Previously,
it didn't have the IRQ facility configured, because there was no need
for one.
- boot0 sets up the sensors, and the IRQs
- boot1 merely continues with boot0's settings, but re-installing
the trap handler to be its own and double-checking some
register settings.
An old testing IRQ handler stub for keyboard presses was also removed,
as it can never be triggered as that IRQ is never set up.
glitches can trigger interrupts from several sources:
- ECC errors on multiple blocks
- consistency check errors on hardened logic
- light sensor
- voltage sensor
- glue cells
These primitives respond to these glitches in an interrupt
context. At the moment, by default a fairly lax approach is
taken to responding to the glitches: partially, we want to make
sure that the sensors aren't too sensitive and trigger by accident.
The final thresholds can be adjusted fairly readily.
In addition, the mesh can trigger an interrupt, and typically,
that interrupt is a false reading (because the mesh takes a
long time to settle, and we boot the system while the mesh
settles). Thus, we take an approach of polling the mesh
effectively, and when interrupts happen due to the mesh we
just clear it and ignore it and allow the polling routine
to manage the mesh. The mesh is theorized to be a static
attack, anyways.
this is the final part of the developer mode bit hardening.
If an adversary manages to increment the developer mode
bit, the next reboot they have to contend with active counter-
measures that would attempt to erase the secrets due to
the developer mode bit being set.
As noted in the top of the commit set, previously, without
this, the attacker would have the luxury of being able to
flip the bit and try many times to bypass the developer
mode check.
this is used by the key erase proof routine, but will be
useful in general for storing data in the buram
also, the ranges of slices are currently defined at
the top of the file but might be moved somewhere more
maintainable later.
RRAM writing got bollards added to it, as RRAM write could
be a glitch target: if a glitch could manipulate the
address somehow to stick stuff at an arbitrary location,
during write, that would be bad. However, it's probably
also reasonably dangerous to try and glitch RRAM writes
because there's a chance you spray over off-target items
and thus trigger a signature check failure on your next
boot.
A problem was discovered while implementing the secure boot
hardening. Namely, an attacker could trick the loader into
loading a devkey-signed kernel by incrementing the
developer mode counter without actually triggering the
secure erase of the keys, and finding a way to glitch
over the check of the developer mode counter check.
Two countermeasuers are implemented:
1. if the developer mode flag is set, trigger a secure erase
even if the image signatures aren't developer mode
2. leak one of the chaff keys into the loader, so there are
now at least two indpendent checks of being in developer
mode in the loader. One is based on the counter, which
is only weakly correlated with the erased state. The
other is checking for the erase value in the leaked key,
which is a strong indicator of being in developer mode.
The leaked key is, of course, erased before entering the
kernel (and the key is leaked into a region of memory
that is, in theory, securely claimed by the keystore, so
it's not like anyone can just read it; however, it is
in a less secure location than the protected key area).
The impact of an adversary obtaining the key is only
a slight weakening of security because the chaff keys
are numerous, they still have thousands of bits to
guess even if they got a couple hundred of them.
The next series of patches implements the scheme outlined
above, starting with defining the key to be leaked. The
leaked key is intentionally the last key in the slice
of keys used in the erasure algorithm, so that presumably
if the leaked key is showing an erased value, all other
keys have been erased.
this correctly gives the loader 256k of space, which is what
its link file specifies. The loader was previously under 128k
by chance, but with glitch hardening it's grown past that
boundary.
At this point, some of the keys are partially locked out
which makes reacting to an attack slightly less effective
(keys are now locked into a particular process space configuration
which is not available in the loader's default config).
The reaction at this point sums up to seeing if the image is
a developer image, and if so, ensuring that the developer mode
bit is set (which would indicate that the keys have been
erased). I suppose an attacker could get around this by glitching
to some code that sets developer mode without also erasing
the keys. This weakness needs to be explored and hardened against.
this patch uses the previously modified APIs and implements
a multi-pass check on signatures in the case that paranoid
mode is turned on. This may effectively double the boot time but
it also reduces the chance of a successful glitch attack
on signature validation by a multiple of the glitch
success probability.
this also updates other API calls and implements the
XOR-on-target-address code.
Signature checking parameters are now consolidated into
a single "SecurityConfiguration" record. This allows the
sum total of security state transitions to be summarized in
the bao1x-api/src/pubkeys/mod.rs file, easing maintenance
and audit.
Reaction to the signature check is split into a `hardened_erase_policy`
API call which can take the results of the signature check and
react to it if necessary.
This allows the signature check routine to be used for both
audit (where reaction is not necessary) and for live testing
of the code (where reaction is required). It also simplifies
the implementation of double-checking signatures in the case
of paranoid mode.
Another important improvement is that the signature checking
API outputs the target address for the jump, but masked with
an XOR of the function code of the signed block. The purpose
of the XOR mask isn't to obfuscate the value; rather, it's
to prevent there being a single, simple, fixed-constant
jump instruction laying around that a glitcher can use as
a target: instead, a pair of registers must be loaded
with the target and its mask. This is an example of what
the final jump looks like:
```
60003a8e: ffffffff .word 0xffffffff
60003a92: 0440006f j 60003ad6 <bao1x_hal::hardening::die>
60003a96: ffffffff .word 0xffffffff
60003a9a: 167d addi a2,a2,-1
60003a9c: fe71 bnez a2,60003a78 <rust_entry+0x2f74>
60003a9e: a00d j 60003ac0 <rust_entry+0x2fbc>
60003aa0: 0360006f j 60003ad6 <bao1x_hal::hardening::die>
60003aa4: ffffffff .word 0xffffffff
60003aa8: 02e0006f j 60003ad6 <bao1x_hal::hardening::die>
60003aac: ffffffff .word 0xffffffff
60003ab0: 0260006f j 60003ad6 <bao1x_hal::hardening::die>
60003ab4: ffffffff .word 0xffffffff
60003ab8: 01e0006f j 60003ad6 <bao1x_hal::hardening::die>
60003abc: ffffffff .word 0xffffffff
60003ac0: 0149c533 xor a0,s3,s4
60003ac4: 600805b7 lui a1,0x60080
60003ac8: d0058593 addi a1,a1,-768 # 6007fd00 <_etext+0x6e9dc>
60003acc: 82aa mv t0,a0
60003ace: 852e mv a0,a1
60003ad0: 4581 li a1,0
60003ad2: 8282 jr t0
```
So here you can see the alternating bollards in the region that
will cause any glitches that don't land exactly right to
either halt the machine or cause a reaction.
Line 0x6003ac0 is where the xor happens to compute the target
address. Should a glitcher land on that address, `s3` and `s4`
will need to be loaded with the target address and a value when
XOR'd with it that yields a meaningful result. While there is
a non-zero chance that this can be arranged, it's definitely
not as easy as targeting a jump-to-fixed address exit sequence.
The management of different security configurations was getting
a bit out of hand, so all the security configurations used by
the system and their transitions are now consolidated into a single
file, This will make auditing and checking the transitions a lot
easier.