21    Attackers

In the first two parts of the book, we considered how computer systems work. Then, in the third part, we considered how computer systems fail. Knowing that failures are inevitable, we then looked at ways to tolerate failures by incorporating redundant resources. With appropriate arrangements of those resources, we can ensure that the system continues working despite occasional failures.

In this last part of the book, we take up problems and solutions related to deliberate attacks. Whereas before we were concerned with “natural” failures—the impossibility of creating perfect components—now we are concerned with “artificial” failures, caused by attackers seeking to take down otherwise healthy systems. This chapter looks at the nature of deliberate attacks. The remaining chapters elaborate on some of the issues identified—the profound vulnerability of software and the crucial role of cryptography. Then we bring several threads together into an explanation of how the Bitcoin system operates.

Defending: An Overview

Defending against attacks is much less satisfying than the previous topics, in both economic and philosophical terms. When we are considering either normal operation of a system or operation of a system within an expected failure model, we can make useful statements about the system’s expected costs and behavior. However, our analysis is subject to a kind of implicit footnote, saying roughly “as long as there’s no terrible unanticipated development.” When we are focusing on normal operation or tolerating ordinary failures, we can still count it as a success (of sorts) if the system behaves well except in some unusual, completely unanticipated, strange circumstances. In contrast, when we shift to a concern with attacks and deliberate bad behavior, we are essentially taking on that disclaiming footnote as our primary concern. The problem then is twofold:

  1. 1. We can’t really anticipate every kind of attack that could be mounted;
  2. 2. Even if we could somehow anticipate every attack, we don’t typically have the resources to defend against every single one of them.

In some ways we can see the situation as comparable to being a gladiator in a Roman circus. We can choose weapons and armor, and we can train ourselves, but we cannot be invulnerable. Too many weapons or too much armor makes us heavy and slow-moving; but speed and agility require giving up protections (whether weapons or armor) that we may dearly wish we had chosen instead.

We are necessarily in the uncomfortable position of choosing which attacks we can deflect, and accepting that we will fail if attacked in other ways. We also have to accept that the end goal is not perfect security. Most houses are vulnerable if a burglar is determined; the usual goal of a homeowner is not to have zero risk of burglary, but rather to have an acceptably low risk. Mechanisms like locks, lighting, alert neighbors, and police patrols all help to convince burglars to look for easier pickings elsewhere. As the old joke goes, you don’t have to outrun the bear—you only have to outrun the other potential victims in the vicinity.

Viruses

Why are there misbehaving programs? Sometimes the misbehavior is an inadvertent flaw: as we’ve considered previously, it’s hard to get programs right (see chapter 6). However, sometimes a program is intentionally written to misbehave. Such a program might be “correct” in achieving its author’s goals, even though it is not being a well-behaved program from the perspective of an ordinary user.

One example of an intentionally misbehaving program is a virus. A virus acts primarily to propagate itself, and secondarily to achieve other effects. Some viruses can be very destructive, wiping out data or rendering computers unusable; others have essentially no effect other than persisting and spreading in ways that ordinary programs don’t. One cause for concern is that even an apparently benign virus could do something destructive at a later point, when some triggering condition occurs or some triggering signal is received. (We might think of the oddly unpredictable “exploding program” we considered in chapter 6.)

As with biological viruses, hygiene is an important defense against computer viruses. Here, hygiene takes the form of avoiding exposure to unknown programs, and particularly the execution of an unknown program. That in turn requires knowing what actions allow a program to execute, and avoiding those unless you actually want to execute a particular program. The challenge for a non-expert is that so many innocent-seeming actions—clicking a link, opening an email—can initiate program execution, sometimes without any visible sign or warning of that effect.

A number of common viruses recruit the infected computer into service as a part-time slave, executing computational and network tasks on demand. A collection of such recruited machines is called a botnet and is often used by criminals and vandals to carry out large-scale attacks in ways that are hard to counter and hard to trace. That means that antivirus hygiene has benefits even apart from the obvious protection it offers your own machine: even if some virus lurking on your machine doesn’t cause problems for you, it could cause problems for others. Your fellow users benefit if you practice appropriate antivirus hygiene, and keep yourself from being an unwitting participant in stealthy criminal activity.

Viruses and their “ecology”—hygiene practices, antivirus tools, and the like—seem like a strange aspect of using personal computers (PCs). We have many other kinds of electronic devices in our lives, many of them with some amount of software involved—and yet we don’t typically worry about viruses or protection against viruses for those other devices. Where does the virus problem come from, and can it be eliminated?

Single-Purpose vs. Multipurpose

It’s crucial to distinguish between a single-purpose device and a multipurpose device, because much of the vulnerability comes from the nature of being multipurpose. A PC is intended to be a kind of universal machine. Each of the different applications makes the computer behave differently. By changing among different programs, we are able to achieve the effect of dozens of special-purpose devices. Indeed, we are not limited to merely having multiple programs: we can also have complex ways in which programs affect other programs.

There are programs that create other programs, such as the programming-language translators called compilers (we will say much more about compilers later; see chapter 23). There are programs that modify other programs: one such program, called a linker, combines different components into a new larger program, fixing up the connections as needed. Another such program, called a debugger, inserts “hooks” into a program so that it’s possible to follow the flow of a program’s execution to understand what that program is doing. Finally, we already learned (chapters 8, 11, and 12) that operating systems are programs that control or switch among other programs. All of these program/program interactions are easy to take for granted, but they would actually be quite difficult to achieve if the machinery accepted only a single program or a small fixed set of programs.

A multipurpose device has an additional vulnerability if it supports multiple operating systems. In normal operation, the operating system is in charge of deciding what program runs next—but the operating system itself is a kind of program. The operating system can’t be the mechanism that starts up the operating system, because the operating system isn’t yet running. So how does an operating system start? There is usually a specialized program called a boot loader whose role is a little like a starter motor in an internal combustion engine. Just as a starter motor gets the engine running and then gets out of the way, a boot loader gets an operating system running and then does nothing more. The boot loader only understands how to load the operating system into the computer, and then hands control to that operating system. The operating system then starts its own activities of loading additional services and applications.

In terms of fighting viruses, part of the problem is that the operating system can’t be in charge of trust and security until it’s running. If a virus is able to affect the boot loader, or the data that the boot loader uses, then it won’t matter how well the operating system defends itself against viruses—because the virus will have control before the operating system is even on the scene.

Computer scientists often use the term code as shorthand for “data that is intended to be a program.” The power of a universal machine partly comes from the ability to read or construct ordinary, passive data that is then interpreted as active code. In particular, a program can grab a chunk of “data” produced by someone else and execute it. That’s exactly what the boot loader does, and that’s exactly what the operating system does. The newly executing program might transform our computer to do something far beyond what we could build or even envision ourselves. Converting passive data to active code is a marvelous capability, but it’s two-edged. Being open to all the great and wonderful programs that can be built by other people also means that the system is open to all the awful and horrible programs that can be built by other people.

Extensibility and Viruses

In an earlier time, we didn’t have to worry about viruses in our TV or other electronics. The distinction is not as simple as saying that devices now use more software. Even if the older devices were running software to control their functions, they weren’t very extensible. Each electronic device was designed for a relatively narrow use case, and was not attempting to be anything like a universal computer. Any software involved was just an implementation “material,” like the silicon chips or copper wires. Accordingly, in those older times there were no “apps” to download and run for a TV or stereo.

Because of the risks inherent in a multipurpose device, it is often wise to stick with a single-purpose device whenever that is workable. For example, some vendors sell various kinds of network “appliances” for corporate computing: each such appliance is marketed as a single-purpose device. One kind provides a particular kind of file storage, another kind provides a particular kind of filtering of network traffic, and yet a third kind provides a particular kind of “spraying” of traffic among multiple servers (as we saw previously in chapter 20). In spite of the name “appliance,” these devices are not really appliances like your refrigerator or stove—built from the ground up for a single purpose. Instead, these network “appliances” are actually built as software running on ordinary PC—the same kind of hardware that anyone could go buy, not something exotic and unusual. As we have mentioned previously, a PC is very much a multipurpose device. It has no intrinsic function, but it can do a wide variety of tasks depending on how it’s programmed.

In principle, a network appliance that is built on PC hardware could operate in much the same way if it were packaged as software to be loaded onto a PC. All of the interesting capabilities are in the software. Nevertheless, there is value to selling hardware and software together. When the software and hardware are bundled together as an appliance, the manufacturer can also modify the operating system on the PC to match the appliance use case, and thereby reduce vulnerabilities. For example, the manufacturer can remove the capability to load additional programs onto the PC. Such a restriction protects the primary function of the appliance: misbehaving “neighbor” programs running on the same hardware can no longer interfere with the primary program, because there simply are no such neighbors! Likewise, the manufacturer can also disable general-purpose communication mechanisms that might be avenues for virus attacks but are not actually needed for the appliance’s primary function.

Packaging software into an “appliance” is an example of shrinking vulnerability by removing generality and programmability. We can also see an opposing trend of making devices “smart”—more flexible and more programmable, with more customization, downloadable apps, and the like. As a device becomes more programmable, it becomes more vulnerable to viruses and other kinds of deliberate misbehavior. There is no sure way of eliminating this risk except for eliminating the benefit. One possibility is to simply remove or disallow programmability. Another possibility is to allow programmability, but only via a trusted gatekeeper. In either case, an improvement in safety comes at the cost of reducing flexibility.

Preventing Viruses

Most of our single-purpose devices are not PCs that have been converted into appliances. Instead, they are devices like simple TVs or calculators that are only designed and built to serve one purpose. Such a single-purpose electronic device typically doesn’t allow any conversion of data into processes once it leaves the factory. If it does allow such conversion, it typically has only a very tightly controlled update mechanism. In contrast, the whole point of a PC is to be the engine powering a wide variety of useful programs, most of which could not be anticipated by the people producing and selling the PC. Using a similarly narrow “update” mechanism for a general-purpose PC would reduce its value considerably, even if such a restriction might also reduce that machine’s vulnerability.

Is it impossible to have a virus-free system? No, but it might well be impractical. Programmers know that it’s possible to build a very small program that’s correct. It’s hard to do much better because it’s easy to exceed the programmer’s cognitive limits (see chapter 6). Those cognitive limits seem to be a part of what it means to be human. Techniques exist to build software well despite those cognitive limits, but they are expensive; except for systems whose failure can kill people, it’s generally uneconomical to do better.

In the same way that it’s possible to build a small program that is correct, it’s also possible to have small systems that allow the conversion of data into processes without flaws. The challenge is in keeping such systems small. In principle it might be possible for each user to write the exact applications and operating system they need, with no extraneous features at all. However, that would be a very different world from the one in which we actually live. Everyone would be a programmer, and everyone would construct their own software environment from scratch. There would be no commonality of computing environments and no reason to share programs. As a result, there would be no virus problems. However, we would all have to do a lot of work on building and maintaining our own personal software—which might well be a bigger problem than viruses.

In contrast to the expense of producing the first copy of a correct piece of software, the production of an additional copy is essentially free. Recall that software is so evanescent that sometimes we have trouble deciding if it’s real (chapter 2). The “manufacturing” of software is not at all like the manufacturing of other goods. Indeed, the usual problem for a software vendor is that it’s too easy to produce copies of software that haven’t been paid for. A radical analysis of the software and information business argues that the very notion of property and trade needs to be rethought when applied to software. After all, if I can give you the software that I have without giving up anything myself, that’s quite different from a conventional exchange. In more typical (non-software) settings, selling you my widget means that you now have it—but I don’t.

Since the production of the first copy of correct software is expensive, but its subsequent duplication is extremely cheap, there are strong economic forces working against our hypothetical world in which everyone programs their own systems. Instead, those economic forces steer us into a world where there are only a small number of different implementations for generally useful functionality. In practice, it is much more economical for operating systems and applications to be shared among many users.

Detecting Viruses

At this point we’ve come to understand that if we have a multipurpose (extensible, programmable) system, it’s vulnerable to viruses. So if we can’t prevent all viruses, can we at least detect them? Can we build some kind of guaranteed-reliable virus detector? Not really. Once again, it may be possible to solve the problem on a very small scale, but not on a large scale or for general cases.

The problem of determining whether a given program misbehaves is just a form of the halting problem that we previously examined (chapter 7). The halting problem in its narrowest form says that there are circumstances in which we can’t build a program to predict another program’s behavior. Now, from a philosophical perspective it is important to note that this limitation could be meaningless. The existence of some programs that are unpredictable doesn’t necessarily imply anything about whether the programs we care about can be handled correctly. It’s possible that all interesting programs can be analyzed successfully, and the theoretical limit would then have no impact at all on our ability to find problems. The philosophical argument here is like learning that blueberries might be a carcinogen, but also learning that the lowest dose that might cause cancer requires eating only blueberries continuously for 20 years. The possible carcinogenic impact might still be interesting information, but it’s not likely to have any practical effect on our lives.

But in reality, the halting problem is important guidance. The reason it’s important is not so much because we know for certain about the characteristics of interesting programs; instead, it matters because we’re in an adversarial situation. We are attempting to find viruses, while the attackers (people writing viruses) want to hide them from us. If they’re smart, the virus-writers will exploit whatever opening(s) they can find. The existence of the halting problem says that the virus-writers are pretty much guaranteed to have some avenues open for exploitation. The underlying theory here says that there are some things the good guys will miss, no matter how hard they work.

To review, problems with viruses arise because:

  1. 1. our computers are generally programmable,
  2. 2. we use software that we didn’t write ourselves, and
  3. 3. no trust mechanism is 100 percent reliable.

We can reduce or eliminate virus vulnerabilities if we use devices without general programmability—like single-purpose appliances without upgrade or extension capabilities. We can also completely eliminate virus vulnerabilities if we are prepared to write all of the software ourselves, including all of our programming tools. But as soon as we trust another person’s software, it’s hard to be sure of what’s in it. Even innocent-seeming software tools can be instruments of subversion, as we will explore further in the next chapter.