Bugpocalypse, or reporting bugs in an AI age
“…perfect software doesn’t exist. No one in the brief history of computing has ever written a piece of perfect software.”
- Andrew Hunt, The Pragmatic Programmer, Chapter 4
In June this year we updated our security process to make the reporting of security bugs broadly the same procedure as reporting other bugs. The only difference is asking reporters to set GitLab’s confidential flag to limit its visibility to project members. The previous email address routed through a volunteer had become unsustainable as the rate of reports rose. While more people can see the reports now, there is at least a chance to distribute the triage work across more of the projects volunteers.
The state of the bug tracker
If you look at the project’s bug stats over the last year, you can see an inflection point around about March. This seems to coincide with the point where LLMs reached a new level of capability in their ability to diagnose security issues in code.
Dramatic as this graph is, it doesn’t even count the multiple times we have had to report otherwise inactive GitLab accounts suddenly spamming the bug tracker with AI slop bugs. The record was one reporter who raised 120 seemingly valid issues in the space of a few minutes.
As time has gone on it has become harder to identify slop, and the reports have become more plausible. We have had to debate how much effort we should spend analysing those reports out of concern we could be missing genuine issues in the flood.
Verbosity has a cost
As a project, we’ve tweaked our issue templates a number of times to encourage users to include as much relevant information as possible when reporting bugs. From a developer point of view we want to know the precise versions and command lines used to help us replicate the issues. An excellent bug report is one that comes with a simple reproducer test case that is easily run.
It turns out as LLM agents abilities have improved so has their ability and willingness to fill out the relevant details. Often they go much further than the template by including a long-form root-cause analysis of why we are seeing the failure. The quality of this text can vary a lot, but we usually get at least a test case and an example command line to run it.
However, that long text does come at a cost, and it will eventually need to have a human sit down and read it to understand what’s going on.
For example consider this iommu migration bug which is nearly 4000 words long and includes a from scratch test harness to modify a migration stream and trigger the issue. Once you spend awhile reading it you can work out that what is at issue is if you mess with a migration stream you can trigger the receiving end to abort() when it gets junk. Is this really a critical issue? It’s hard to tell, but the text does seem persuasive - it will probably need a migration expert to look at it and give their opinion.
Humans, unlike machines, get tired after spending many hours reading through walls of plausible text from a seemingly never-ending stream of incoming reports.
It is with some sense of irony that we’ve been experimenting with using LLMs to do the initial triage of bugs so we can better distribute the load by routing bugs to the appropriate developer. But still eventually a human has to assess and decide what to do about it.
We should also consider another fact which is….
Not All Code is Created Equal
QEMU’s git history goes back to 2003 and has grown a lot over the years as it has gained additional capabilities. Originally intended to help run Windows applications on other architectures via Wine, it has grown system emulation facilities for numerous architectures as well as support for a number of different hypervisors.
In that time, it has also gained support for numerous architectures, boards, and features totalling between 5 million to 11.5 million lines of code (depending on how you count). To keep track of that large codebase, we rely on our hardworking maintainers. This is all documented in the top-level MAINTAINERS file.
Compared to a lot of Free, Libre, and Open Source (FLOSS) projects, QEMU is quite lucky in that quite a high proportion of maintainers have jobs that allow them to actively look after the code. Around two-thirds of the code has maintainers who are able to review and queue patches for the upstream tree as well as triage bugs and undertake the architectural clean-up all projects need to go through as codebases mature.
In general, those paid to look after QEMU worry about the virtualisation use case, as this is where the risk of untrusted guests trying to exploit the hypervisor and VMM is the greatest. The whole TCG accelerator is excluded from this security boundary specifically because it was never written with security requirements in mind.
Generally, we expect people running images on the numerous boutique platforms QEMU can emulate to have a fairly good idea of the provenance of the code they are running. Those using QEMU for security research are expected to take extra steps to contain potential exploits of QEMU itself (for example by using the security features of libvirt).
Nearly half of the currently open confidential issues are in undermaintained areas of the codebase. This means that, although they have been triaged, they are unlikely to be looked at by anyone. It’s also not particularly surprising that if you go looking around in dusty areas of the codebase you are likely to find warts that have been removed from code that is used in real production environment.
We still want help
Ultimately, the QEMU project is still a community of humans collaborating on a key component of the FLOSS virtualisation stack. People participate for many reasons, but no developer has signed up to spend their days wading through reams of generated slop.
The ability to submit drive-by patches to fix that annoying behaviour in something you use has long been an feature of FLOSS, and we want to take advantage of that. Some of those passing by end up hanging around the project and taking on bigger jobs and more responsibilities, and that is the way we try and sustain QEMU’s developer community.
With that in mind, I thought I would mention some things to keep in mind if you want to raise a bug in our bug tracker.
Focus on areas you use
Anyone can aim an LLM at some of the dustier corners of the QEMU code base and find issues. That’s fine if you’re looking for candidates for your first patch submission to the project. However, if you’re just looking to raise bugs for the sake of it, please reconsider what your motivation is. If you truly want to help the project, maybe consider looking at some of the existing bugs and helping out there. We have plenty.
Review the output of AI tools
We don’t ban the use of LLMs in the bug tracker, because they obviously have their uses in identifying problems with the code. However, they are not infallible, so please be upfront about their use so people reading the report are fully informed of the origin of the report. Also, please review the text and see if you can edit down some of the more verbose passages that don’t add useful information or are engaging in speculation as to the underlying failure reason. If your agent has written an elaborate harness to exercise the bug, then tell it to rewrite the test using QEMU’s existing testing framework.
Propose a patch
If you’ve automated the process of finding bugs, you might as well go a step further and propose a patch to fix the issue. Often, reading a patch will give a clearer idea of the issue than wading through all the descriptive text. And if the patch turns into a massive rearchitecting of the codebase, then maybe consider that the LLM’s model of how things might work could be flawed.
We have been here before
Modern LLMs are certainly proving to be disruptive, but this is not the first time a new technology has caused ripple effects throughout the FLOSS world. The introduction of static analysers, improved compiler diagnostics, sanitisers, and fuzzers have all been accompanied by code churn as the issues they find have been addressed.
I expect the same will eventually be true of this wave even though it feels larger than previous ones. We have to hope that at the end of this disruptive phase, we will have better, cleaner, and more reliable software. And to achieve that, we will still need motivated human engineers who understand the history and architecture of the codebase and can shepherd it to the next release.