Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
586 changes: 586 additions & 0 deletions meta-balena-common/classes/balena-firmware-exclusion.bbclass

Large diffs are not rendered by default.

94 changes: 94 additions & 0 deletions meta-balena-common/classes/image-balena.bbclass
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,97 @@ python __anonymous() {
rootfs_postprocess_command = d.getVar('ROOTFS_POSTPROCESS_COMMAND')
d.setVar('ROOTFS_POSTPROCESS_COMMAND', re.sub(r'zap_empty_root_password ?;?', '', rootfs_postprocess_command))
}

# Add excluded firmware to BAD_RECOMMENDATIONS. This includes GPU, Audio, Video and Misc categories.
python do_apply_firmware_exclusion_policy() {
import os

# Allow including specific packages from device repositories
# if they are really necessary
raw_whitelist = d.getVar('BALENA_ALLOWED_FIRMWARE_PACKAGES') or ""
whitelist = [pkg.strip() for pkg in raw_whitelist.split()]
bb.note(f"Allowed firmware whitelist: {repr(whitelist)}")

deploy_dir = d.getVar('DEPLOY_DIR_IMAGE')
if not deploy_dir:
bb.fatal("Could not determine DEPLOY_DIR_IMAGE")

nonessential_file = os.path.join(deploy_dir, 'nonessential_firmware.txt')

if os.path.exists(nonessential_file):
try:
with open(nonessential_file, 'r') as f:
extra_bad = []
for line in f:
# Extract the package name without category
pkg = line.split(':')[0].strip()

if pkg and pkg not in whitelist:
extra_bad.append(pkg)
elif pkg in whitelist:
bb.note(f"Firmware Policy: Whitelisting '{pkg}', allowing installation.")
if extra_bad:
bad_str = " ".join(extra_bad)
# BAD_RECOMMENDATIONS is used to remove packages from RRECOMMENDS
d.appendVar('BAD_RECOMMENDATIONS', " " + bad_str)
bb.note(f"Policy applied: Excluded {len(extra_bad)} firmware packages.")

except Exception as e:
bb.fatal(f"Failed to enforce firmware exclusion policy: {str(e)}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you confirm that this is making any firmware present in nonessential_firmware.txt, that would have ended up in the image, caught and added to BAD_RECOMMENDATIONS, and hence not installed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ycardaillac adding files to BAD_RECOMMENDATIONS only removes packages from RRECOMENDS, packages installed through other methods still end up in the image, but they are included in the nonessential_firmware.txt and when the files in this file are found in the image manifest, the build fails.

The alternative would be adding them to PACKAGES_EXCLUDE, but that may trigger build failures which are reportedly more difficult to debug, so we compare the non-essential firmware list to the final manifest and report the packages which shouldn't have been installed, so we can either add them to the WHITELIST or find what installs them and do the removal

}

do_rootfs[depends] += "linux-firmware:do_exclude_firmware"
addtask do_apply_firmware_exclusion_policy before do_rootfs


# Fail the build if any of the excluded packages have been found in the image manifest
#
python do_nonessential_firmware_check() {
import os

# During do_image_complete, this variable points to the manifest in WORKDIR
manifest_path = d.getVar('IMAGE_MANIFEST')

if not manifest_path or not os.path.exists(manifest_path):
# Fallback to check the deploy directory manually
deploy_dir = d.getVar('DEPLOY_DIR_IMAGE')
link_name = d.getVar('IMAGE_LINK_NAME')
manifest_path = os.path.join(deploy_dir, f"{link_name}.manifest")

if not os.path.exists(manifest_path):
bb.fatal(f"Firmware policy check failed: Manifest file not found in {manifest_path}")

nonessential_path = os.path.join(d.getVar('DEPLOY_DIR_IMAGE'), 'nonessential_firmware.txt')
if not os.path.exists(nonessential_path):
bb.fatal("nonessential_firmware.txt not found, cannot perform firmware policy check.")

whitelist_raw = d.getVar('BALENA_ALLOWED_FIRMWARE_PACKAGES') or ""
whitelist = [p.strip() for p in whitelist_raw.split()]

# Parse non-essential packages list
with open(nonessential_path, 'r') as f:
nonessential_packages = [line.split(':')[0].strip() for line in f if line.strip()]

# Parse image manifest
installed = []
with open(manifest_path, 'r') as f:
for line in f:
parts = line.split()
if parts:
installed.append(parts[0])

# Check if non-essential packages exist in image manifest AND are not whitelisted
matched_packages = [p for p in nonessential_packages if p in installed and p not in whitelist]

if matched_packages:
bb.fatal(f"Non-essential firmware found in manifest: {', '.join(matched_packages)}. "
f"Please check which categories these packages belong to in linux-firmware/temp/log.do_exclude_firmware "
f"or add them to BALENA_ALLOWED_FIRMWARE_PACKAGES")
else:
bb.plain("Firmware Policy Check: PASSED")
}

# Manifest is generated in do_rootfs,
# we thus need to perform the check after
# it becomes available.
addtask nonessential_firmware_check after do_rootfs before do_image_complete
114 changes: 114 additions & 0 deletions meta-balena-common/conf/distro/include/balena-os.inc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,120 @@ DISTRO_NAME = "balenaOS"
DISTRO_VERSION = "6.11.0"
HOSTOS_VERSION = "${DISTRO_VERSION}"

# This list of packages is common for all
# meta-balena-<yocto_release> layers
COMMON_CONNECTIVITY_FIRMWARES = " \
linux-firmware-bcm43143 \
linux-firmware-iwlwifi-135-6 \
linux-firmware-iwlwifi-3160 \
linux-firmware-iwlwifi-6000-4 \
linux-firmware-iwlwifi-6000g2a-6 \
linux-firmware-iwlwifi-6000g2b-6 \
linux-firmware-iwlwifi-6050-5 \
linux-firmware-iwlwifi-7260 \
linux-firmware-iwlwifi-7265 \
linux-firmware-iwlwifi-7265d \
linux-firmware-iwlwifi-8000c \
linux-firmware-iwlwifi-8265 \
linux-firmware-rtl8188eu \
linux-firmware-wl12xx \
"

# This list is set by meta-balena-common
CORE_CONNECTIVITY_FIRMWARES = " \
linux-firmware-ath9k \
linux-firmware-mt7601u \
linux-firmware-ralink \
linux-firmware-rtl8192cu \
linux-firmware-rtl8192su \
linux-firmware-rtl8723 \
${COMMON_CONNECTIVITY_FIRMWARES} \
"

# Firmware packages which are used by
# wifi modules with an M.2 interface
LINUX_FIRMWARE_PACKAGES[pci] ?= " \
linux-firmware-iwlwifi-9260 \
linux-firmware-iwlwifi-3160 \
linux-firmware-iwlwifi-7260 \
linux-firmware-iwlwifi-7265d \
linux-firmware-iwlwifi-7265 \
linux-firmware-iwlwifi-8000c \
linux-firmware-iwlwifi-8265 \
linux-firmware-rtl8723 \
linux-firmware-iwlwifi-7260 \
linux-firmware-iwlwifi-7265d \
linux-firmware-iwlwifi-7265 \
linux-firmware-iwlwifi-8000c \
linux-firmware-iwlwifi-8265 \
linux-firmware-rtl8723 \
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TODO: Remove duplicates, must be a typo

"

# Firmware packages used by wifi modules
# that use SDIO or UART. The wifi chip
# usually comes soldered in this case.
LINUX_FIRMWARE_PACKAGES[sdio] ?= " \
linux-firmware-iwlwifi-8265 \
linux-firmware-rtl8723 \
"

# Firmware packages for modules which use a
# Half MINI PCIE interface
LINUX_FIRMWARE_PACKAGES[pci] ?= " \
linux-firmware-iwlwifi-6000-4 \
linux-firmware-iwlwifi-6000g2a-6 \
linux-firmware-iwlwifi-6000g2b-6 \
linux-firmware-iwlwifi-6050-5 \
linux-firmware-iwlwifi-7260 \
linux-firmware-iwlwifi-135-6 \
linux-firmware-rtl8723 \
"

# Firmware packages for USB wifi dongles
LINUX_FIRMWARE_PACKAGES[usbhost] ?= " \
linux-firmware-ath9k \
linux-firmware-rtl8723 \
linux-firmware-ralink \
linux-firmware-bcm43143 \
linux-firmware-rtl8192cu \
linux-firmware-mt7601u \
linux-firmware-rtl8188eu \
"

# Firmware packages for PCIE Wifi adapters
LINUX_FIRMWARE_PACKAGES[pci] ?= " \
linux-firmware-ath9k \
"

# Firmware packages for mini PCIE Wifi adapters
LINUX_FIRMWARE_PACKAGES[pci] ?= " \
linux-firmware-ath9k \
linux-firmware-ralink \
"

# Firmware packages used by the TI WiLink8 WL1835MOD / 1837MOD
# wifi chipsets. These are not detachable
# from the device, they are soldered.
# Commonly used by Beaglebone boards
LINUX_FIRMWARE_PACKAGES[features_TI_WiLink8] ?= " \
linux-firmware-wl18xx \
linux-firmware-wlcommon \
"

# Firmware for TI wilink 6.0 / 7.0 - WL1271 / WL1273
# wifi chipsets, used on the VAR-SOM-MX6 and BeagleXM
LINUX_FIRMWARE_PACKAGES[features_TI_WiLink_6_7] ?= " \
linux-firmware-wl12xx \
"

# Firmware for the Realtek RTL8192SU chipset,
# found in USB dongles. Has been discontinued
# since 2012 and can only be purchased from
# pulled parts.
LINUX_FIRMWARE_PACKAGES[rtl8192su] ?= " \
linux-firmware-rtl8192su \
"

# Allowed kernel cmdline arguments, used by balena-bootloader and the bootloader configuration scripts
ALLOWED_BOOTARGS_LIST=" \
firmware_class.path=/var/lib/docker/volumes/extra-firmware/_data \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ NETWORK_MANAGER_PACKAGES ?= "networkmanager"

CONNECTIVITY_MODULES = ""

CONNECTIVITY_FIRMWARES ?= " \
linux-firmware-ath9k \
linux-firmware-mt7601u \
linux-firmware-ralink \
linux-firmware-rtl8192cu \
linux-firmware-rtl8192su \
linux-firmware-rtl8723 \
# We no longer ship bluetooth
# firmware by default

BLUETOOTH_FIRMWARE = " \
linux-firmware-rtl8723b-bt \
"
"

# Defined in meta-balena-common/conf/distro/include/balena-os.inc
CONNECTIVITY_FIRMWARES ?= " \
${CORE_CONNECTIVITY_FIRMWARES} \
"

CONNECTIVITY_PACKAGES = " \
${NETWORK_MANAGER_PACKAGES} \
Expand All @@ -38,3 +40,64 @@ RDEPENDS:${PN} = " \
${CONNECTIVITY_FIRMWARES} \
${CONNECTIVITY_PACKAGES} \
"

# Filter-out linux-firmware packages
# based on the interfaces available for
# the device-type built.
# The device-type repository specifies
# the available interfaces using MACHINE_FEATURES.
# Connectivity packages - interfaces mapping
# is set by conf/distro/include/balena-os.inc
python () {
# All connectivity firmwares that can be installed by meta-balena
all_connectivity_firmwares = d.getVar('CONNECTIVITY_FIRMWARES')
bb.note(f"Initial list of CONNECTIVITY_FIRMWARES: {all_connectivity_firmwares}")
if not all_connectivity_firmwares:
return

# Split into an iterateable list
current_firmware_list = all_connectivity_firmwares.split()
machine_features = (d.getVar('MACHINE_FEATURES') or "").split()

# Mapping of firmware packages to hardware features (M.2, USB, etc)
fw_feature_map = d.getVarFlags('LINUX_FIRMWARE_PACKAGES') or {}

keep_list = []

for pkg in current_firmware_list:
# Hardware interfaces associated with this package
# i.e 'ath9k' is in features_USB', 'features_PCIE' and in 'features_MINI_PCIE'
associated_features = []
for feature_name, pkg_list_str in fw_feature_map.items():
if pkg in pkg_list_str.split():
associated_features.append(feature_name)

# If the package does not belong to any interface, we keep it to avoid
# accidentaly discarding any essential packages that are installed
# by the device repository. If the device repository installs
# a linux-firmware package which does not belong to the Connectivity or Storage
# categories, it will be blacklisted by the firmware exclusion and surfaced
# by the final audit.
if not associated_features:
keep_list.append(pkg)
continue

# Check if MACHINE_FEATURES for this DT includes at least one of the required features
has_hardware_support = False
for feat in associated_features:
if feat in machine_features:
has_hardware_support = True
break

if has_hardware_support:
# DT has the physical interface (i.e USB, M.2, etc) needed for this firmware
# sp we keep the current package
keep_list.append(pkg)
else:
# None the package's supported interfaces is present in MACHINE_FEATURES
bb.note(f"Policy: Stripping {pkg} - Machine lacks any of: {', '.join(associated_features)}")

updated_connectivity_firmwares = " ".join(keep_list)
d.setVar('CONNECTIVITY_FIRMWARES', updated_connectivity_firmwares)
bb.note(f"Updated list of CONNECTIVITY_FIRMWARES: {updated_connectivity_firmwares}")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--------------------------------------------------------------------------

Driver: iwlwifi - Intel Wireless Wifi

File: iwlwifi-3160-12.ucode
File: iwlwifi-3160-7.ucode
File: iwlwifi-3160-8.ucode
File: iwlwifi-3160-9.ucode
File: iwlwifi-3160-10.ucode
File: iwlwifi-3160-12.ucode
File: iwlwifi-3160-13.ucode
File: iwlwifi-3160-16.ucode
File: iwlwifi-6000g2a-5.ucode
File: iwlwifi-6000g2b-5.ucode
File: iwlwifi-6050-4.ucode

--------------------------------------------------------------------------

Driver: qcom_q6v5_pas - Qualcomm remoteproc firmware

File: qcom/LENOVO/21BX

--------------------------------------------------------------------------
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
FILESEXTRAPATHS:append := ":${THISDIR}/${PN}"

inherit balena-firmware-exclusion
inherit balena-linux-firmware

SRC_URI:append = " \
file://extra_WHENCE \
"

# Cleanup iwlwifi firmware files
IWLWIFI_PATH = "${D}${nonarch_base_libdir}/firmware"
IWLWIFI_REGEX = "^iwlwifi-([0-9a-zA-Z-]+)-([0-9]+).ucode$"
Expand Down Expand Up @@ -110,3 +117,48 @@ PACKAGES =+ "${PN}-iwlwifi-quz-a0-jf-b0"
FILES:${PN}-iwlwifi-quz-a0-jf-b0 = " \
${nonarch_base_libdir}/firmware/iwlwifi-QuZ-a0-jf-b0-*.ucode* \
"

FILES:${PN}-moxa = "${nonarch_base_libdir}/firmware/moxa/moxa-*.fw*"

FILES:${PN}-qca = " \
${nonarch_base_libdir}/firmware/qca/nvm* \
${nonarch_base_libdir}/firmware/qca/rampatch* \
${nonarch_base_libdir}/firmware/qca/htbtfw* \
${nonarch_base_libdir}/firmware/qca/htnv* \
${nonarch_base_libdir}/firmware/qca/hpnv* \
${nonarch_base_libdir}/firmware/qca/hpbtfw* \
${nonarch_base_libdir}/firmware/qca/hspnv* \
${nonarch_base_libdir}/firmware/qca/crnv* \
${nonarch_base_libdir}/firmware/qca/crbtfw* \
${nonarch_base_libdir}/firmware/qca/apbtfw* \
${nonarch_base_libdir}/firmware/qca/apnv* \
${nonarch_base_libdir}/firmware/qca/msbtfw* \
${nonarch_base_libdir}/firmware/qca/msnv* \
"

PACKAGES += "${PN}-rtl-bt"

# Capture Ralink Bluetooth firmware specifically
FILES:${PN}-rtl-bt = " \
${nonarch_base_libdir}/firmware/rtl_bt/rt*.bin* \
"

# Do not capture rtl*
FILES:${PN}-ralink = " \
${nonarch_base_libdir}/firmware/rt2*.bin* \
${nonarch_base_libdir}/firmware/rt3*.bin* \
${nonarch_base_libdir}/firmware/rt7*.bin* \
"

PACKAGES =+ " ${PN}-wlcommon-bt "
FILES:${PN}-wlcommon-bt = " \
${nonarch_base_libdir}/firmware/ti-connectivity/TIInit*bts* \
"

# We override the upstream wlcommon packaging
# to avoid including Bluetooth files, which
# we pack separately above
FILES:${PN}-wlcommon = " \
${nonarch_base_libdir}/firmware/ti-connectivity/wl127x-nvs.bin* \
${nonarch_base_libdir}/firmware/ti-connectivity/wl1271-nvs.bin* \
"
Loading
Loading