The open source community saw a number of new advances in security processes in the last year. One of those advances was the GNU toolchain community rallying around the idea of adopting security policies, with GCC and binutils already adopting one, guided by the precedent set by the glibc project as early as in 2014. The idea of a security policy in the context of software is not new; many large open source projects understand the need to define policies and procedures for reporting security issues. Some even have dedicated teams to handle security issues. Forges like GitHub actively promote the idea of adding a SECURITY.md file in the code repository that explains how to report security issues.
However, while the idea is not new, the usage of the security policy across open source communities has been sketchy at best. Few communities actually do anything with their SECURITY.md beyond pointing users to procedures used in filing security issues. Most importantly, many projects fail to actually set a context in which their reports may be evaluated for security impact. This is fast becoming a problem as more organizations become increasingly focused on security and governments start making laws that require organizations to provide certain guarantees with respect to software security.
In this post I hope to provide some insight on how we adopted security policies in GNU toolchain projects to help security researchers and users better understand how to use their software securely and how other open source projects could emulate that.
What is a software security issue?
This is the first question that must be answered when discussing processes around security issues. Strictly speaking, a security issue in software is any defect or design flaw that compromises the confidentiality, integrity or availability of the software or the data it manages. Depending on the circles in which this question is asked, this answer for a specific open source project could range from "almost everything" to "almost nothing." This is because the concept of security does not exist in a vacuum, it needs a context. An authentication bypass needs the software to actually implement an interface that requires authentication. A privilege escalation needs the execution environment of the target software to be in a lower privilege state. A Denial of Service needs the software to be, um, a service!
For most software, this context is defined by its use, i.e. the environment in which it is deployed. A good security policy defines model contexts so that users know how to use the software without compromising on security.
Setting security boundaries
One way to identify this context is to build a threat model for the software. It does not have to be a detailed exercise, listing every possible attack vector and weaknesses in the software in the context of those vectors. For the purposes of the security policy, maintainers could at least explicitly list out use cases that they have the resources to support.
The binutils example is probably the most pertinent one in this case. In addition to the assembler and linker, the binutils project also distributes a number of tools to analyze binary objects. There is a legitimate threat vector where an attacker could trick a user into downloading and analyzing a malicious binary and through that, execute arbitrary code on their system. This attack vector can easily be neutralized by making sure that untrusted binaries are only analyzed in a sandbox, such as a container or a virtual machine.
The code for binary object analysis tools in binutils is written in C and has significant technical debt, resulting in many memory safety issues when analyzing binaries without executing them. Further, most binary formats are extremely flexible and at least one study has shown that it's possible to get ELF interpreters to execute arbitrary code. While the study focuses on dynamic loaders, the question of similar issues with static linkers is still open. As a result, the responsible approach was to make it clear in the binutils security policy that analysis of untrusted binaries must be done in a sandbox.
Another model example of a security policy setting security boundaries is the QEMU project, which clearly defines what a trusted context is for various modules within QEMU as well as external software that QEMU interacts with.
Finally, not all projects restrict context in the same way. For example, while glibc does not consider bugs due to untrusted regular expression inputs as security issues in most cases, the Go project accepts untrusted regular expression induced bugs as security issues. Likewise, while the binutils project discourages static analysis of binaries without sandboxing, the GDB community is leaning towards treating memory safety bugs encountered during static analysis of binaries as security issues.
Not all security issues are vulnerabilities
Another aspect of identifying security issues is actually understanding its impact. The CVE program maintains a scoring system called Common Vulnerability Scoring System (CVSS), which helps you do that, but that is explicitly catered towards vulnerabilities. There is another class of security issues that goes unnoticed or worst, incorrectly categorized and those are issues in security hardening.
Security hardening is quite different in nature to vulnerabilities, because of which they need to be treated differently. A vulnerability is a bug that has potential to be used as a starting point to attack a target system. A buffer overflow in an internet facing service is an example of a vulnerability. The CVE program was made to make wide communication of such vulnerabilities possible because these have potential to directly threaten the safety of computer systems across the world.
A hardening bug on the other hand, does not have that property. An example hardening feature is source code fortification, which recently got marked improvements in the GNU toolchain. The correct working of this feature mitigates the impact of buffer overflow bugs, thus reducing the attack surface for applications. However if the compiler fails to add fortification to the application source, it does not open a security hole in the application or systems it runs on. While it may be important to improve the fortification so that it works, there is no urgency to fix the problem.
A security policy should determine which security features implemented by the project fall under the first line of defence (i.e. their failure is a direct threat and should be communicated by the CVE program) and which ones are issues that should be given importance for fixing, but not necessary for urgent communication. Again, like security boundaries, classifying security issues as hardening issues or vulnerabilities can sometimes be subjective, based on what the community and the underlying technology for the hardening can reasonably support.
How does this help everyone?
A security policy can often be quite an effort to write up, evolve and support, but it is a worthwhile effort for everyone. With computer systems becoming more and more central to our existence and security becoming an increasing focus, it is only natural that we think harder about our security posture as open source maintainers. The process of security policy design forced us to think about the security of all of the modules in our projects in the GNU toolchain. It also raised important questions about what can and cannot be reasonably supported by that community of maintainers and in many cases, helped us think beyond the ideal, to what's practically doable so as to give users a more real view of what is good security practice when using our software.
From a user's perspective, the security policy helps calibrate expectations, helping them set up their environments more securely and help them be better prepared to protect their systems from misuse. From the broader perspective of the CVE program, the security policy helps improve the signal to noise ratio in communication of vulnerabilities to users so that they're not inundated with security advisories that are not urgent but merely masquerading as urgent.
Such bogus CVEs are not just a burden for users, they are an even bigger issue for open source maintainers and downstream distribution developers, as they have to scramble to backport fixes to appease the many new security requirements that have been mushrooming in the industry. Cutting down on these, benefits these developers too because they can then focus on improving the security design of their software.
There is, however, a missing link in this whole picture, which is communication of hardening issues. We probably need a mechanism similar to the CVE program to identify and communicate software hardening issues that do not make systems directly vulnerable, but are nevertheless important to make widely known and subsequently addressed.
Conclusion
I hope I've been able to lay out a clearer picture on the motivation of setting up security policies for the GNU toolchain project and also made a case for more open source communities to set up security policies for their projects. A security policy not only improves the precision of CVE advisories, it also sets the stage for net improvement of the security position of open source projects.
Last updated: February 7, 2024