Find all of the Yocto hardening texts from here!
Last time when we were talking about Yocto hardening we focused on setting up extra users to prevent unauthorized root access and to avoid situations where the users would have too open permissions. Getting these right is a good low-hanging fruit, but there are plenty of other things to consider for hardening the system. One of the most important things to do is to eliminate (or at least minimize) the security vulnerabilities weakening the overall system defences.
What are CVEs?
Despite what one may be inclined to think, open-source code is not always perfect. It may contain bugs, which is a bit annoying, but it may also contain vulnerabilities, which is potentially a bit more dangerous. These can lead to denial of service attacks, data leaks, or even unauthorized accesses & system takeovers. Not good. Very bad. The usual stuff.
CVE (Common Vulnerabilities and Exposures) is a system maintained by The United States National Cybersecurity FFRDC and operated by Mitre Corporation. The system contains information about, well, vulnerabilities and exposures. “CVE” as a noun can also mean vulnerability or exposure. Or at least I often use the acronym with that meaning. Checking your Yocto system against this database of vulnerabilities can help detect security issues lurking in the open-source code components.
Checking vulnerabilities in Yocto
Every CVE doesn’t necessarily lead to unauthorised root access, and there may be vulnerabilities that don’t have a CVE identifier. However, scanning through the CVEs in your system should provide a fairly good overview of how vulnerable your system is against possible CVE exploits. At least at the time of the scan, there are constantly new issues popping up which means you have to keep doing it often. Also, it’s good to keep in mind that this kind of scan does not check for other kind of weaknesses in the system. But at least performing the CVE check in the Yocto system is fairly simple. Something is simple in Yocto, what?
In all simplicity, just add this line to local.conf
to check your packages & images for common vulnerabilities.
INHERIT += "cve-check"
# I had some issues with slow download speeds. If you get weird timeouts
# when fetching issue database, try adding this line as well:
CVE_SOCKET_TIMEOUT = "180"
Yocto has an include file containing some exclusions for CVEs that are deemed impractical to fix from the Yocto project’s point of view. To add this exclusion list to your build, add this to the local.conf
as well:
include conf/distro/include/cve-extra-exclusions.inc
Now when you run the build you’ll most likely end up with more warnings than before. Yocto’s CVE checker prints a warning to the build output for every vulnerability it detects, and depending on the complexity and age of your system your terminal may end up quite yellow. It’s more useful to inspect the CVE reports than to try and remember the build output as it scrolls by. These reports can be found in <build-folder>/tmp/deploy/cve
directory, and we’ll inspect them a bit later. It’s worth knowing that this CVE checker may not be perfect, meaning that there may be some vulnerabilities lurking around that it does not detect. But it’s at least better than going through all of the source code eyeballing it.
Analysing the results
Ok, now you’ve proved to yourself that your system has holes that need to be plugged. How to get over this uneasy feeling of constant doom looming over your system? The first thing to do is listing out all the CVE IDs and checking their status & severity to become even more stressed. Or relaxed, depends a bit on how well your system is maintained.
Further information for the CVEs can be found on MITRE’s CVE site, and some more complementary information can be found on NIST’s (National Institute of Standards and Technology) NVD (National Vulnerability Database). Plenty of acronyms. NVD is a system containing the same CVE IDs as MITRE’s site but with severity scores and usually some extra information. Both are useful, but for practical purposes, NVD tends to be more useful. MITRE’s site can contain some new info not yet present in NVD, but NVD contains the information in a more easily digestible format.
Let’s take CVE-2023-22451 as an example. CVE listing for the vulnerability shows a summary, affected versions, and a bunch of links. NVD entry has the same information, severity score, modification date, and a bit more information about the links so you won’t have to guess which link is the patch and which is advisory (this can be useful on bigger issues where there are a dozen links). For what it’s worth, Yocto’s CVE checker uses NVD’s database.
Now that we’ve gone through some background information, we can investigate the results of the CVE check. The aforementioned <build-folder>/tmp/deploy/cve
contains two summary files: cve-summary
and cve-summary.json
. These contain all the detected issues, both patched and unpatched. The format of the files isn’t all that intuitive for getting an actual summary, so I wrote a Python tool for summarising the results. The design is very human:
# Replace the file path with your path
python3 ./cve-parser.py -f build/tmp/log/cve/cve-summary.json
Very easy to use. By default, the parser will output the unpatched CVEs, and from each CVE it will print the package name, CVE ID, CVSS scores, and the link to the NVD site for more info. There is more information per issue available, the results can be sorted, and the ignored & patched issues can also be printed if needed. To get the complete and current list of options, run:
python3 ./cve-parser.py -h
I’m not going to document the whole program here in case I decide to change it at some point. But for the sake of example, if your boss asks you to get the total amount of unpatched CVEs in the system, you can run the command below to get “extra info” from the parser. Currently, “extra info” just contains the number of displayed issues, but if you’ve got ideas for it feel free to let me know:
# -u display unpatched issues (default behavior, but added just in case)
# -e displays extra info
python3 ./cve-parser.py -u -e -f build/tmp/log/cve/cve-summary.json
Then, if your boss asks how many of them actually need to be fixed, use this to sort the CVEs by severity score and count how many have a score higher than 8.3
# -u display unpatched issues
# -ss2 sort by CVSSv2 score
python3 ./cve-parser.py -u -ss2 -f build/tmp/log/cve/cve-summary.json
Just kidding, please fix them all (if applicable).
Fixing the CVEs
After you’ve justified your fears of getting hacked by realizing that your system has seven CVEs with a CVSS score higher than 9.5 (three of them in the kernel) and a few dozen more with just a slightly lower priority, it’s time to ask the big question: what can I do? What can I possibly do? This didn’t help with the feeling of constant doom at all.
The first step is going through the list of unpatched issues. There tend to be quite a few false positives that do not apply to the current system. For example, a brand new Poky build (at the time of writing) reports an unpatched CVE-2017-6264 that’s been around six years already. This is a vulnerability in the NVIDIA GPU driver, and it applies to Android products. Most certainly it’s a false positive in your Yocto system, but since the bad code is present in the source code, it’s reported as unpatched. You can ignore these false positives by adding the line below somewhere to your build configuration (local.conf
works, but it’s maybe not the best place for it):
# The format is CVE_CHECK_IGNORE="<cve-id>", e.g.
CVE_CHECK_IGNORE="CVE-2017-6264"
# In the older Yocto versions this was called CVE_CHECK_WHITELIST
# so if ignore doesn't work, try the old whitelist variable
After ignoring the false positives fixing the rest of the CVE issues is easy, in theory. Just keep the Yocto at its newest (or relatively new LTS) version. This should keep the packages fresh. Sounds simple, but unfortunately, this process tends to cause a big headache with incompatibilities and such. However, that’s the best advice I can give.
If there is a need to update a recipe to a newer version than officially supported by the version of Yocto you’re using, copy the recipe in question to your meta-layer, update the recipe version in the filename and fix SRCREV
to match. When hacking around like this remember to hope that nothing falls apart in the system even more than during a regular Yocto version update.
Sometimes, updating the packages is not an option. Perhaps updating the package breaks the build, a newer version of a library isn’t compatible with your application, or there is some other Perfectly Good Reason you’re not allowed to do that. That’s when it’s time to do some patching to make the confusing build system even more spaghettified.
Basically, this approach consists of heading to the NVD entry for the CVE, checking if there’s a patch for the vulnerability and if there is, porting the patch to the Yocto build. For example, the aforementioned CVE-2023-22451 mentions this commit as its patch in the NVD entry. Copy the contents of the commit to a patch file, create a bbappend file for the recipe and add the patch to the recipe with that bbappend.
If there’s no patch you can wait. Or if you’re like a kid on Christmas and can’t wait, you can try digging the package’s git repositories for the corrective commit before it’s been released. It isn’t fun, and rarely it’s a productive use of time, but occasionally, some fixes can be found like this before they are made official. In the worst case you can always fix it yourself, most likely causing another vulnerability in the process.
That’s the short explanation of how you can check and fix the CVE vulnerabilities in your system. The theory of the process is fairly simple. However, it tends to get a bit more complicated in the actual world, especially when trying to update older legacy systems where the stale, non-updated packages contain more patches than original code. But I guess that’s the problem with real life, it tends to mess up good theories.
You can find the next part about Yocto hardening here. It’s about firewalls.