Skip to content

Commit

Permalink
backend/bitbox02bootloader: allow fixing broken install
Browse files Browse the repository at this point in the history
Problem: a new device ships with the latest firmware monotonic version. User
sees 'Install' because the device is erased (no firmware on it), but during
first install of firmware, unplugs. Upon replug, the device will show 'Invalid
firmware' and boot into bootloader again. Since the monotonic version of the
firmware to be installed is the same as the version stored on the device, and
the device is not erased (half of the firmware is on it), the user sees no
button to upgrade/instll anymore.

This commit fixes this by still showing the upgrade button if the versions are
the same, but the device firmware hash does not match the expected firmware
hash.
  • Loading branch information
benma committed May 13, 2024
1 parent 3483ab9 commit 6876115
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 1 deletion.
24 changes: 23 additions & 1 deletion backend/devices/bitbox02bootloader/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package bitbox02bootloader

import (
"bytes"
"encoding/hex"
"fmt"
"sync"

Expand Down Expand Up @@ -240,6 +242,11 @@ func (device *Device) VersionInfo() (*VersionInfo, error) {
if err != nil {
return nil, err
}
currentFirmwareHash, _, err := device.Device.GetHashes(false, false)
if err != nil {
return nil, err
}

latestFw, err := bundledFirmware(device.Device.Product())
if err != nil {
return nil, err
Expand All @@ -249,12 +256,27 @@ func (device *Device) VersionInfo() (*VersionInfo, error) {
if err != nil {
return nil, err
}
canUpgrade := erased || latestFirmwareVersion > currentFirmwareVersion

latestFirmwareHash, err := latestFw.firmwareHash()
if err != nil {
return nil, err
}

// If the device firmware version is at the latest version but the installed firmware is
// different, we assume it's a broken/interrupted install. This can happen for example when a
// new device is shipped with the latest monotonic version pre-set, and the user interrupts
// their first install.
brokenInstall := latestFirmwareVersion == currentFirmwareVersion &&
!bytes.Equal(currentFirmwareHash, latestFirmwareHash)

canUpgrade := erased || latestFirmwareVersion > currentFirmwareVersion || brokenInstall
additionalUpgradeFollows := nextFw.monotonicVersion < latestFirmwareVersion
device.log.
WithField("latestFirmwareVersion", latestFirmwareVersion).
WithField("currentFirmwareVersion", currentFirmwareVersion).
WithField("currentFirmwareHash", hex.EncodeToString(currentFirmwareHash)).
WithField("erased", erased).
WithField("brokenInstall", brokenInstall).
WithField("canUpgrade", canUpgrade).
WithField("additionalUpgradeFollows", additionalUpgradeFollows).
Info("VersionInfo")
Expand Down
14 changes: 14 additions & 0 deletions backend/devices/bitbox02bootloader/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"

"github.com/digitalbitbox/bitbox-wallet-app/util/errp"
"github.com/digitalbitbox/bitbox02-api-go/api/bootloader"
bitbox02common "github.com/digitalbitbox/bitbox02-api-go/api/common"
"github.com/digitalbitbox/bitbox02-api-go/util/semver"
)
Expand Down Expand Up @@ -53,6 +54,19 @@ func (fi firmwareInfo) signedBinary() ([]byte, error) {
return io.ReadAll(gz)
}

func (fi firmwareInfo) firmwareHash() ([]byte, error) {
signedBinary, err := fi.signedBinary()
if err != nil {
return nil, err
}

_, _, binary, err := bootloader.ParseSignedFirmware(signedBinary)
if err != nil {
return nil, err
}
return bootloader.HashFirmware(fi.monotonicVersion, binary), nil
}

// The last entry in the slice is the latest firmware update to which one can upgrade.
// The other entries are intermediate upgrades that are required before upgrading to the latest one.
// Each one has to be flashed and booted before being able to continue upgrading.
Expand Down

0 comments on commit 6876115

Please sign in to comment.