Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Matter OTA firmware #815

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

rhilseth
Copy link

@rhilseth rhilseth commented Jan 1, 2025

This PR adds support for detecting Matter OTA firmware (https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/protocols/matter/overview/dfu.html#matter_ota_image_structure_and_transfer) and also extracting the firmware payload itself.

Example output for the file https://prod.ota-updates.bosch-smarthome.com/v1/updates/BTH-RAD/BTH-RAD_Prod_2.0.9.ota found via the DCL UI at https://webui.dcl.csa-iot.org/models

The test file in tests/inputs has been created manually with a dummy payload.

--------------------------------------------------------------------------------------------------------------------------------------------
DECIMAL                            HEXADECIMAL                        DESCRIPTION
--------------------------------------------------------------------------------------------------------------------------------------------
0                                  0x0                                Matter OTA firmware, total size: 786644 bytes, tlv header size: 68
                                                                      bytes, vendor id: 0x1209, product id: 0x3012, version: 2.00.09,
                                                                      payload size: 786560 bytes, digest type: 1, payload digest:
                                                                      9d4aac8bf87b4ce519318952a3736973e0427d867f3612d776dcd36340fdb574
760368                             0xB9A30                            CRC32 polynomial table, little endian
--------------------------------------------------------------------------------------------------------------------------------------------

Copy link
Collaborator

@devttys0 devttys0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, aside from the noted sanity checks that should be performed to prevent crashes/exceptions in the event of malformed/corrupted data. Once those are addressed, I'll merge the PR. Thanks!

let total_header_size =
MAGIC_SIZE + TOTAL_SIZE_SIZE + HEADER_SIZE_SIZE + ota_header.header_size;

result.size = Some(ota_header.total_size);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By convention internal extractors should set result.success = true here, so that if the extractor will still return success if it is invoked as part of a dry-run (i.e., output_directory is None).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result.success is now set to true even when there is a dry run

// Header starts after the magic, total size and header size fields
let header_start = common::size(&ota_structure);
let header_end = header_start + header_size;
let header_data = &ota_data[header_start..header_end];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no guarantee that the values reported in the ota_stucture are sane, thus the calculated header_start / header_end values could cause a crash here. Consider using ota_data.get() and/or sanity checking the header_start and header_end values.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to using ota_data.get()

_ => return Err(StructureError),
};

let field_data = &data[field_offset..];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although field_offset is a small value, there does not appear to be any guarantee that data is at least field_offset bytes in size. Consider using data.get() or sanity checking the size of data.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now use data.get() here as well.

let structure = &vec![("string_length", field_width_type)];
let result = common::parse(field_data, structure, "little")?;
let string_length = result["string_length"] as usize;
let string_data = &field_data[common::size(structure)..];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using field_data.get() or sanity checking the size of field_data.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using field_data.get() here.

let result = common::parse(field_data, structure, "little")?;
let string_length = result["string_length"] as usize;
let string_data = &field_data[common::size(structure)..];
let string = get_cstring(&string_data[..string_length]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using string_data.get() or sanity checking the size of string_data. Alternatively, you can simply pass string_data directly to get_cstring(), as it will terminate at the first null byte or the end of data, whichever comes first.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TLV encoded strings aren't null-terminated in the headers I've seen. But I changed to using string_data.get() here as well

let structure = &vec![("octet_string_length", field_width_type)];
let result = common::parse(field_data, structure, "little")?;
let octet_string_length = result["octet_string_length"] as usize;
let octet_string_data = &field_data[common::size(structure)..];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using field_data.get() or sanity checking the size of field_data.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using field_data.get() now

Ok((
Element {
tag,
value: Value::OctetString(octet_string_data[..octet_string_length].to_vec()),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using octet_string_data.get() or sanity checking the size of octet_string_data.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using octet_string_data.get() here

];
let mut offset = 0;
let mut header = HashMap::new();
while offset < data.len() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider sanity checking the offset value by using while common::is_offset_safe() here to prevent infinite loops, wrap-arounds, and out-of-bounds access.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to using common::is_offset_safe()

Thanks for your review comments!

@rhilseth rhilseth requested a review from devttys0 January 7, 2025 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants