CVE-2023-45841,CVE-2023-45842,CVE-2023-45838,CVE-2023-45839,CVE-2023-45840
Multiple data integrity vulnerabilities exist in the package hash checking functionality of Buildroot 2023.08.1 and Buildroot dev commit 622698d7847. A specially crafted man-in-the-middle attack can lead to arbitrary command execution in the builder.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Buildroot 2023.08.1
Buildroot dev commit 622698d7847
Buildroot - https://www.buildroot.org/
8.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-494 - Download of Code Without Integrity Check
Buildroot is a tool that automates builds of Linux environments for embedded systems. It supports cross-compiling for multiple target platforms and allows for building a cross-compilation toolchain, Linux kernel image, boot loader, root file system and various utilities.
When building a package, Buildroot executes the corresponding Makefile. Source code is typically downloaded from the internet for most packages, while some are included within Buildroot. Upon downloading the source, Buildroot verifies the integrity of the package using a hash file, extracts the sources, applies any necessary patches and then proceeds with the actual building process.
To describe the logic in detail, let’s use the strace
package as a simple example:
In package/strace/strace.mk
:
STRACE_VERSION = 6.5
STRACE_SOURCE = strace-$(STRACE_VERSION).tar.xz
STRACE_SITE = https://github.com/strace/strace/releases/download/v$(STRACE_VERSION)
...
$(eval $(autotools-package))
In package/strace/strace.hash
:
# Locally calculated after checking signature with RSA key 0xA8041FA839E16E36
# https://strace.io/files/6.5/strace-6.5.tar.xz.asc
sha256 dfb051702389e1979a151892b5901afc9e93bbc1c70d84c906ade3224ca91980 strace-6.5.tar.xz
sha256 d92f973d08c8466993efff1e500453add0c038c20b4d2cbce3297938a296aea9 COPYING
sha256 7c379436436a562834aa7d2f5dcae1f80a25230fa74201046ca1fba4367d39aa LGPL-2.1-or-later
STRACE_SITE
defines the external site to fetch the sources from, and STRACE_SOURCE
defines the actual package name to retrieve. The autotools-package
is then evaluated to interpret the various <PACKAGE_NAME>_<VARIABLE>
definitions in the package .mk
and generate all the Makefiles rules needed to build the package.
In the .hash
file, we can see sha256 hashes for any file that is being downloaded externally so their integrity can be verified after download.
When the strace
package is selected in the config, calling make strace-source
will download the package sources. The download function is defined in package/pkg-download.mk
, which relays the request to the support/download/dl-wrapper
shell script.
In the case of strace
, dl-wrapper
is called like this:
support/download/dl-wrapper
-c 6.4
-d /opt/buildroot/dl/strace
-D /opt/buildroot/dl
-f strace-6.4.tar.xz
-H package/strace//strace.hash
-n strace-6.4
-N strace
-o /opt/buildroot/dl/strace/strace-6.4.tar.xz
-u https+https://github.com/strace/strace/releases/download/v6.4
-u http|urlencode+http://sources.buildroot.net/strace
-u http|urlencode+http://sources.buildroot.net
Interesting parameters to note:
-H
defines the .hash
file for integrity checks-u
can be specified multiple times, to provide a fallback in case the primary URL is not availableThe http://sources.buildroot.net
URLs have been passed as fallback because they correspond to the default value of BR2_BACKUP_SITE
. This behavior is enabled by default. However, it is possible to set BR2_PRIMARY_SITE_ONLY
to disable it and only allow downloads from the primary resource.
Inside dl-wrapper
:
...
download_and_check=0
rc=1
[1] for uri in "${uris[@]}"; do
backend_urlencode="${uri%%+*}"
backend="${backend_urlencode%|*}"
case "${backend}" in
git|svn|cvs|bzr|file|scp|hg|sftp) ;;
*) backend="wget" ;;
esac
uri=${uri#*+}
...
[2] if ! "${OLDPWD}/support/download/${backend}" \
$([ -n "${urlencode}" ] && printf %s '-e') \
-c "${cset}" \
-d "${dl_dir}" \
-n "${raw_base_name}" \
-N "${base_name}" \
-f "${filename}" \
-u "${uri}" \
-o "${tmpf}" \
${quiet} ${large_file} ${recurse} -- "${@}"
then
...
continue
fi
...
# Check if the downloaded file is sane, and matches the stored hashes
# for that file
[3] if support/download/check-hash ${quiet} "${hfile}" "${tmpf}" "${output##*/}"; then
rc=0
else
if [ ${?} -ne 3 ]; then
rm -rf "${tmpd}"
continue
fi
# the hash file exists and there was no hash to check the file
# against
rc=1
fi
[4] download_and_check=1
break
done
# We tried every URI possible, none seems to work or to check against the
# available hash. *ABORT MISSION*
[5] if [ "${download_and_check}" -eq 0 ]; then
rm -rf "${tmpd}"
exit 1
fi
For each URL (specified via -u
) [1], the appropriate backend is used. In the case of strace
the backend is simply wget
, so wget
is going to be called via the support/download/wget
wrapper [2].
After the file is downloaded, the hash is checked (file is specified via -H
) by calling check-hash
[3]. If check-hash
has a 0 exit status, rc
is set to 0, download_and_check
[4, 5] is set to 1 to indicate success and the loop ends.
Let’s see how check-hash
is implemented.
...
# Does the hash-file exist?
[6] if [ ! -f "${h_file}" ]; then
printf "WARNING: no hash file for %s\n" "${base}" >&2
exit 0
fi
# Check one hash for a file
# $1: algo hash
# $2: known hash
# $3: file (full path)
check_one_hash() {
... # exits with error code if the hash doesn't match
}
# Do we know one or more hashes for that file?
nb_checks=0
while read t h f; do
case "${t}" in
''|'#'*)
# Skip comments and empty lines
continue
;;
*)
if [ "${f}" = "${base}" ]; then
[7] check_one_hash "${t}" "${h}" "${file}"
: $((nb_checks++))
fi
;;
esac
done <"${h_file}"
[8] if [ ${nb_checks} -eq 0 ]; then
[9] case " ${BR_NO_CHECK_HASH_FOR} " in
*" ${base} "*)
# File explicitly has no hash
exit 0
;;
esac
printf "ERROR: No hash found for %s\n" "${base}" >&2
exit 3
fi
For each hash line in the .hash
file, check_one_hash
is called [7]. If the hash
doesn’t match check_one_hash
will exit with an error code. Otherwise nb_checks
is incremented to indicate one successful check. If there’s no entry in the .hash
file for the specified input file to check, the check at [8] will return an error, unless BR_NO_CHECK_HASH_FOR
[9] contains this specific file, meaning that the file is excluded from hash checks.
In total, there are 3 ways for check-hash
to return 0 (success):
.hash
file exists for the package [6]$file
’s hash matches the definition in the .hash
file [7]$file
is not present in the .hash
file, and BR_NO_CHECK_HASH_FOR
contains the base name for the package (explicitly skipping checks) [9]Option 2 is what we expect to reach most of the time.
In this advisory, we focus on Option 1. Indeed, we identified 5 packages that miss a .hash
file:
aufs
and aufs-util
) also specify a _SITE
with an http://
schema, meaning that a man-in-the-middle attacker could supply any source package to Buildroot.riscv64-elf-toolchain
, versal-firmware
and mxsldr
) specify an https://
schema. However, because the default BR2_BACKUP_SITE
is set to http://sources.buildroot.net
(note the http://
schema), a man-in-the-middle attacker could supply any source package in this case too. To force Buildroot to use the fallback URL, it’s enough to drop HTTPS requests to the primary site (which is easy to do when we assume a MITM position). This will make the if
condition at [2] fail, and the loop at [1] will perform the download using the next $uri
.Also note, while a warning is printed for the check at [6], this can be very easily overlooked given the verbosity of the output, especially if we consider it may run in a CI (Continuous Integration) pipeline.
Because packages can ship patch files or Makefiles, by supplying a compromised source package an attacker would be able to execute arbitrary commands in the builder. As a direct consequence, an attacker could then also tamper with any file generated for Buildroot’s targets and hosts.
For example, it’s enough to provide a Makefile with the following command:
_ := $(shell id >> /injected)
Or insert such a line in a .patch
file, which would allow modification of any package within Buildroot during the build process.
Below all the affected packages are listed separately.
aufs
fetches its sources from an http
URL and does not include a .hash
file for checking package integrity. An attacker could use a MITM attack to provide compromised packages, which would allow execution of arbitrary commands in the builder.
aufs-util
fetches its sources from an http
URL and does not include a .hash
file for checking package integrity. An attacker could use a MITM attack to provide compromised packages, which would allow execution of arbitrary commands in the builder.
riscv64-elf-toolchain
fetches its sources from an https
URL, however it does not include a .hash
file for checking package integrity. Since Buildroot’s network requests can be downgraded to http
(thanks to the default BR2_BACKUP_SITE
being an http
URL), an attacker could use a MITM attack to provide compromised packages, which would in turn allow execution of arbitrary commands in the builder.
versal-firmware
fetches its sources from an https
URL, however it does not include a .hash
file for checking package integrity. Since Buildroot’s network requests can be downgraded to http
(thanks to the default BR2_BACKUP_SITE
being an http
URL), an attacker could use a MITM attack to provide compromised packages, which would in turn allow execution of arbitrary commands in the builder.
mxsldr
fetches its sources from an https
URL, however it does not include a .hash
file for checking package integrity. Since Buildroot’s network requests can be downgraded to http
(thanks to the default BR2_BACKUP_SITE
being an http
URL), an attacker could use a MITM attack to provide compromised packages, which would in turn allow execution of arbitrary commands in the builder.
This proof-of-concept assumes that an attacker is MITM-ing the network and dropping requests to git.denx.de
on port 443, while at the same time serving any .tar.gz
file requested via port 80 with a malicious version:
$ make source
/usr/bin/make -j1 O=/tmp/builddir HOSTCC="/usr/bin/gcc" HOSTCXX="/usr/bin/g++" syncconfig
mkdir -p /tmp/builddir/build/buildroot-config/lxdialog
PKG_CONFIG_PATH="" /usr/bin/make CC="/usr/bin/gcc" HOSTCC="/usr/bin/gcc" \
obj=/tmp/builddir/build/buildroot-config -C support/kconfig -f Makefile.br conf
/usr/bin/gcc -I/usr/include/ncursesw -DCURSES_LOC="<curses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE -I/tmp/builddir/build/buildroot-config -DCONFIG_=\"\" -MM *.c > /tmp/builddir/build/buildroot-config/.depend 2>/dev/null || :
/usr/bin/gcc -I/usr/include/ncursesw -DCURSES_LOC="<curses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE -I/tmp/builddir/build/buildroot-config -DCONFIG_=\"\" -c conf.c -o /tmp/builddir/build/buildroot-config/conf.o
/usr/bin/gcc -I/usr/include/ncursesw -DCURSES_LOC="<curses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE -I/tmp/builddir/build/buildroot-config -DCONFIG_=\"\" -I. -c /tmp/builddir/build/buildroot-config/zconf.tab.c -o /tmp/builddir/build/buildroot-config/zconf.tab.o
/usr/bin/gcc -I/usr/include/ncursesw -DCURSES_LOC="<curses.h>" -DNCURSES_WIDECHAR=1 -DLOCALE -I/tmp/builddir/build/buildroot-config -DCONFIG_=\"\" /tmp/builddir/build/buildroot-config/conf.o /tmp/builddir/build/buildroot-config/zconf.tab.o -o /tmp/builddir/build/buildroot-config/conf
rm /tmp/builddir/build/buildroot-config/zconf.tab.c
GEN /tmp/builddir/Makefile
gcc-12.3.0.tar.xz: OK (sha512: 8fb799dfa2e5de5284edf8f821e3d40c2781e4c570f5adfdb1ca0671fcae3fb7f794ea783e80f01ec7bfbf912ca508e478bd749b2755c2c14e4055648146c204)
gcc-12.3.0.tar.xz: OK (sha512: 8fb799dfa2e5de5284edf8f821e3d40c2781e4c570f5adfdb1ca0671fcae3fb7f794ea783e80f01ec7bfbf912ca508e478bd749b2755c2c14e4055648146c204)
glibc-2.38-27-g750a45a783906a19591fb8ff6b7841470f1f5701.tar.gz: OK (sha256: fd991e43997ff6e4994264c3cbc23fa87fa28b1b3c446eda8fc2d1d3834a2cfb)
bison-3.8.2.tar.xz: OK (sha256: 9bba0214ccf7f1079c5d59210045227bcf619519840ebfa80cd3849cff5a5bf2)
m4-1.4.19.tar.xz: OK (sha256: 63aede5c6d33b6d9b13511cd0be2cac046f2e70fd0a07aa9573a04a82783af96)
gawk-5.2.2.tar.xz: OK (sha256: 3c1fce1446b4cbee1cd273bd7ec64bc87d89f61537471cd3e05e33a965a250e9)
gcc-12.3.0.tar.xz: OK (sha512: 8fb799dfa2e5de5284edf8f821e3d40c2781e4c570f5adfdb1ca0671fcae3fb7f794ea783e80f01ec7bfbf912ca508e478bd749b2755c2c14e4055648146c204)
binutils-2.40.tar.xz: OK (sha512: a37e042523bc46494d99d5637c3f3d8f9956d9477b748b3b1f6d7dfbb8d968ed52c932e88a4e946c6f77b8f48f1e1b360ca54c3d298f17193f3b4963472f6925)
gmp-6.3.0.tar.xz: OK (sha256: a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898)
mpc-1.2.1.tar.gz: OK (sha256: 17503d2c395dfcf106b622dc142683c1199431d095367c6aacba6eec30340459)
mpfr-4.1.1.tar.xz: OK (sha256: ffd195bd567dbaffc3b98b23fd00aad0537680c9896171e44fe3ff79e28ac33d)
linux-6.5.6.tar.xz: OK (sha256: 78e36d4214547051c24df2140f4ce09428d6c515ad9a71b38b28e8094a95d2f6)
busybox-1.36.1.tar.bz2: OK (sha256: b8cc24c9574d809e7279c3be349795c5d5ceb6fdf19ca709f80cde50e47de314)
>>> host-mxsldr 2793a657ab7a22487d21c1b020957806f8ae8383 Downloading
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git init .
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Initialized empty Git repository in /opt/buildroot/dl/mxsldr/git/.git/
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git remote add origin 'https://git.denx.de/mxsldr.git'
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git remote set-url origin 'https://git.denx.de/mxsldr.git'
Fetching all references
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git fetch origin
fatal: unable to access 'https://git.denx.de/mxsldr.git/': Failed to connect to git.denx.de port 443 after 5 ms: Connection refused
Detected a corrupted git cache.
Removing it and starting afresh.
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git init .
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint: git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint: git branch -m <name>
Initialized empty Git repository in /opt/buildroot/dl/mxsldr/git/.git/
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git remote add origin 'https://git.denx.de/mxsldr.git'
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git remote set-url origin 'https://git.denx.de/mxsldr.git'
Fetching all references
GIT_DIR=/opt/buildroot/dl/mxsldr/git/.git git fetch origin
fatal: unable to access 'https://git.denx.de/mxsldr.git/': Failed to connect to git.denx.de port 443 after 2 ms: Connection refused
Detected a corrupted git cache.
This is the second time in a row; bailing out
wget --passive-ftp -nd -t 3 -O '/tmp/builddir/build/.mxsldr-2793a657ab7a22487d21c1b020957806f8ae8383-br1.tar.gz.HW6f6m/output' 'http://sources.buildroot.net/mxsldr/mxsldr-2793a657ab7a22487d21c1b020957806f8ae8383-br1.tar.gz'
--2023-10-11 13:48:21-- http://sources.buildroot.net/mxsldr/mxsldr-2793a657ab7a22487d21c1b020957806f8ae8383-br1.tar.gz
Resolving sources.buildroot.net (sources.buildroot.net)... 104.26.0.37, 104.26.1.37, 172.67.72.56
Connecting to sources.buildroot.net (sources.buildroot.net)|104.26.0.37|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/x-xz]
Saving to: '/tmp/builddir/build/.mxsldr-2793a657ab7a22487d21c1b020957806f8ae8383-br1.tar.gz.HW6f6m/output'
/tmp/builddir/build/.mxsldr-2793a657ab7a2248 [ <=> ] 160 --.-KB/s in 0s
2023-10-11 13:48:21 (27.1 MB/s) - '/tmp/builddir/build/.mxsldr-2793a657ab7a22487d21c1b020957806f8ae8383-br1.tar.gz.HW6f6m/output' saved [160]
WARNING: no hash file for mxsldr-2793a657ab7a22487d21c1b020957806f8ae8383-br1.tar.gz
libusb-1.0.26.tar.bz2: OK (sha256: 12ce7a61fc9854d1d2a1ffe095f7b5fac19ddba095c259e6067a46500381b5a5)
pkgconf-1.6.3.tar.xz: OK (sha256: 61f0b31b0d5ea0e862b454a80c170f57bad47879c0c42bd8de89200ff62ea210)
patchelf-0.13.tar.bz2: OK (sha256: 4c7ed4bcfc1a114d6286e4a0d3c1a90db147a4c3adda1814ee0eee0f9ee917ed)
2023-10-25 - Vendor Disclosure
2023-12-04 - Vendor Patch Release
2023-12-05 - Public Release
Discovered by Claudio Bozzato and Francesco Benvenuto of Cisco Talos.