HomeLog InSource


[View] [Short] [Hash] [Raw]


I’ve been exploring the idea of processes, users, containers and VMs as various expressions of an underlying isolation mechanism.

I also recently wrote about the idea of a file system that implemented directories as recursive mounts[#].

Combining these ideas, together with the idea of unikernels, I’ve come to the idea of “ProcOS.”

A unikernel is the minimum possible operating system that gets you from bare metal to a single running process. Unlike a traditional operating system, it doesn’t have any multiplexing features (scheduler, etc.), so it can only run one program at a time.

“ProcOS” is the unikernel’s complement: it is just a process that runs other processes. It’s a pure multiplexer, with no hardware support. It’s an OS that runs as a regular process on a traditional OS, a unikernel, or–get this–itself.

All ProcOS can do is run processes. Users and containers are just recursive instances of ProcOS.

So for example, let’s say you’re running ProcOS on a unikernel. You turn on the computer, and the unikernel loads. It starts running the root ProcOS. It is configured to spawn a login process, which lets users log into the system.

When a user logs in, the login process uses a special message to tell the root process to spawn a user process for that user. The user process contains the desktop environment or whatever. Anything the user does runs within their process.

One cool thing about the ProcOS idea is that it can be quite portable. You can run it in a VM with a unikernel, or you can run it directly on top of another OS as a regular process. This makes it very clear the overhead that is being removed: the VM and the unikernel (including all drivers) just evaporate.

Ultimately, how practical is this idea? Well, if you were to build a secure multiplexer, this is probably the way you’d want to build it, so that it could be as simple (meaning reliable) and versatile as possible.

[View] [Short] [Hash] [Raw]


What’s up with fashion?

Things you want to get in on before they become fashionable:

Things you want to get in on that will remain (more or less) unfashionable:

Things you want to get in on when they’re fashionable:

Things you want to get in on after they’ve stopped being fashionable:

It seems to me like following fashion is going to get more and more dangerous, because if there is a bubble, that means there is a large incentive to pop it. To be fair, there’s also an enormous incentive to prop it up.

On the other hand, if you’re just doing your own thing, even if you’re completely vulnerable, you’re a small target and it might not be worth anyone’s effort to eat your lunch. But, then again, no one is going to defend you or help you out either.

Personally, I welcome any moderating forces on fashion, so that the highs may be a little less stratospheric and the lows may be a little less bottomless.

Keywords: fashion, herd behavior, hype

[View] [Short] [Hash] [Raw]


Science is hard to do right and not always the best tool in the toolbox

You’ve heard about how people lost in the desert tend to unintentionally walk in circles, right? Either one leg is slightly longer than the other, or their gait is slightly uneven, but for whatever reason, people have trouble walking in a straight line without a frame of reference.

A while back I read about a study to confirm this finding. It took a bunch of people and blindfolded them and had them try to walk straight. And of course they were constantly getting turned around. The conclusion was that it is impossible for people to walk in a straight line without a visual frame of reference.

But if you conducted a similar study to determine whether people could play the violin, and took a few dozen people off the street and gave them a violin to see if they could play it, you would probably conclude that it is impossible for people to play the violin.

Rather than normal scientific experiments, we have ways to determine whether and how well people can play an instrument. They are called auditions and recitals. More generally, if you want to find the limit of human capability, the appropriate structure is competitive, such as a tournament.

If you’re concerned about being able to find your way out of a desert, here are a few ideas. Obviously the simplest way is to use the sun and stars to navigate. If you happen to be blindfolded, perhaps use the heat of the sun on your skin, or the direction of the prevailing winds. Figure out in advance the diameter of the circle you tend to walk in, and adjust for it every 1,000 paces. I’m sure that someone who practiced walking in a straight line competitively would come up with even better tricks.

Similarly, if you want to figure out how to lose weight or get in shape, don’t ask a nutritionist or dietitian. Ask a successful bodybuilder or athlete. Of course they won’t know everything about health, and there will be some “bro science” mixed in, but at least their results show they’re doing something right. (Realistically, the answer is probably, “invest more time and effort than you are willing to invest.”)

If you want to understand people, emotions, beauty, etc., don’t ask science. Ask art. Of course art is less certain than physics, but it’s probably more certain than psychology. The successes in art are often remarkably self-evident.

If you want to learn the best study tricks, look at the most successful students, not studies comparing averages across all the students. Even if the study is right, the benefit will only be marginal, because the best students use a lot of different tricks in concert.

If you want to design a scientific study to show that people are incompetent and don’t know anything, that’s very easy to do. Negative reproductions are hard to do right, because it’s so easy to fail for the wrong reason.

I also don’t give scientists a pass for trying to prove the obvious, because “at least now we know for sure.” No, either we knew before with heaps of real-world experience, or we still don’t know despite what this one random study says.

Using something means you’re also testing it. For example, if one of our assumptions about the laws of physics with regard to the internal combustion engine were wrong, we’d have found out quickly because there are billions of ICEs being used all over the world. That isn’t to say that we’d find all the edge cases (which by definition don’t happen in normal operating conditions), but at least the basic ideas are solid.

Conversely, this implies that any scientific hypothesis that is difficult to test must also be difficult to use. If the higgs boson had any implications for happy meal toys, we could just make some and check whether they work. But since it is so expensive and difficult to even detect, it simply can’t have any practical applications for the foreseeable future.

That’s not to say the Large Hadron Collider is bad or a waste of time. But instead of testing the standard model, we’re really just testing ourselves.

In conclusion, science is hard to do right and not always the best tool in the toolbox. It is far from the only way of knowing things, unless you want to stretch the definition beyond reason.

[View] [Short] [Hash] [Raw]


I’ve been thinking about a couple of ideas for a modern filesystem.

The first idea is to use log structuring to improve application correctness and performance. Common filesystems today reorder writes typically using what is known as an elevator algorithm. Basically of all of the pending blocks to write, instead of writing them in chronological order, it writes them from lowest to highest, and then from highest to lowest. On a mechanical drive, that minimizes seek times, and on SSDs, it’s still not completely worthless.

The problem with write reordering is that it is that it makes fast applications incorrect, and correct applications potentially slower than before. The reason for that is that by reordering writes, you can unintentionally change application semantics, in particular during a kernel panic or power failure. If an application writes A and then B, you’d expect a hard crash to leave the disk in one of three states: nothing, A, or AB. However, write reordering means than the disk can contain B but not A. Hard crashes are rare, but when they happen this can easily cause data corruption.

In order to mitigate this problem, applications have to insert write barriers. The only easy and reliable write barrier is fsync(2) (or fdatasync(2)) (you can also use hashes/checksums, but that requires changes to the file format and extra recovery logic). fsync requires flushing all the way to disk, so now in order to speed things up, you’re actually doing more work and making them slower.

Log structured filesystems can solve this problem. They can guarantee ordering of all writes without write barriers, while simultaneously being even more efficient than the elevator algorithm, because they put all writes together into a contiguous log. Then you need some sort of compaction, but that seems like a worthwhile tradeoff (for reasons I won’t bother explaining again). The side benefit of this design is that it makes LSM-trees in userspace (such as LevelDB) redundant. Any ordinary b-tree stored on this filesystem, including any off-the-shelf database, effectively becomes an LSM-tree with no modification.

I see log-structured filesystems as a generational leap ahead of copy-on-write filesystems like ZFS and btrfs.

The second idea is implementing directories as recursive mounts for simplicity, reliability and security. Microkernels always had great promise but one of their major limitations is that their services (such as filesystems) are still typically monolithic. For example you have a user process that sends messages through the kernel to a filesystem process. All applications that share the same filesystem talk to the same process and can trigger bugs or exploits that affect all other applications.

Also, if you want to do sandboxing right, you need to put any filesystem that contains untrusted data inside the sandbox. The main way to do that is recursive mounts, so you have an outer (trusted) filesystem containing images of inner (untrusted) filesystems. However that’s needlessly complicated and more of a hassle to deal with since none of the standard tools understand filesystem images as opposed to regular directories.

Instead, you could build a purely flat filesystem (no hierarchy) that didn’t support directories at all, and run copies of it within its own files to act as directories. First of all, this would vastly simplify your filesystem design, making it more robust. Second, you could easily run the inner filesystems in different processes/sandboxes, giving strong isolation between different branches of the hierarchy.

This requires several finer points, but no dealbreakers that I’m aware of. First, you need to carefully design the API so that deeply nested filesystem operations don’t lose much efficiency. In particular, you need to ensure the log (as above) is only kept once, at the top layer, instead of repeatedly within each filesystem. You also need to ensure that data deleted within a filesystem is reported to the parent (similar to TRIM for SSDs). These are all problems that affect loop mounts and VMs today, and are mitigated to some extent already.

The other potential pitfall is opening the same directory within multiple processes/sandboxes at once. But serverless databases like LMDB and SQLite can already handle that.

As an example of the security benefit, say that the user downloads a malicious file that exploits a Unicode filename or metadata bug in the filesystem (although really, the filesystem probably shouldn’t be parsing Unicode in the first place). In this design, the damage is contained to the download directory, rather than being able to spread throughout the filesystem regardless of permissions.

Of course in a monolithic kernel, or for efficiency, you can run multiple instances in the same process (or directly in the kernel), although you lose most of the security benefits.

In conclusion there’s plenty of room for improvement.

[View] [Short] [Hash] [Raw]


Interfaces versus Inheritance

It’s a common pattern in object-oriented languages to use subclassing, inheritance and method overriding to define and implement interfaces. For example, one of my old projects, EasyCapViewer, gradually grew five or six drivers for different version of the video capture hardware it supported. A similar project, Macam, had hundreds of drivers for almost every possible USB webcam. Both of these programs defined an semi-abstract base class with methods intended to be overridden by concrete driver implementations, and also convenience implementations that drivers could call back up to for shared behavior.

While working on EasyCapViewer, I learned what a bad idea that design is. The problem is that you are mixing the interface with a shoddy differential compression scheme. The more implementations you have, the more redundancy you will have, and the more that redundancy hurts when you find a bug or have to change something. As you add back-ends, you frequently discover code that is common amongst two or more of them. The obvious thing to do is push that code up into the common base class. That might mean adding a new method, or changing the behavior of an existing one.

However, the biggest piece of redundancy should be the interface itself, which every back-end is tied to (somehow) because every back-end needs to implement it. It can’t be factored out, and every time it changes, most or all of the implementations need to be updated. So it’s crucial that this interface be ironed out and hammered down as early as possible.

So there is a fundamental conflict between trying to optimize the interface to “compress” the implementations, because the appropriate compression depends on the implementations themselves, and they will change and grow over time. On the other hand the interface must not change, because that requires changing all of the implementations. (In lower circles of hell, changes to the interface will cause changes to the appropriate compressions, and vice versa. You can get caught in a loop until things finally stabilize at a new local optimum.)

This is, in a word, bad.

What I recommend, and what I did in my more recent project libkvstore (which supports “drivers” for several different storage engines), is to separate the interface from the code deduplication system. The interface is defined mostly up front, based on the nature of the thing being abstracted and what it needs to do. (This is extremely difficult and vastly under-appreciated, and it’s a topic I’m still not qualified to write much about.)

Then, as implementations are added and redundancy is noticed, “implementation helper” functions are added. These helpers are not publicly exposed, and might not have any formal basis or rationale for existing. They simply soak up whatever duplication is found. As new implementations are added and the apparent duplications change, new helpers can be added that are subsets or supersets of old helpers (and helpers might call each other behind the scenes). Helper functions are a lot like a compression dictionary. They just represent duplication, not necessarily any real structure or meaning.

There is one class of helper that I’m still on the fence about. In a language like C with textual (rather than semantic) macros, you can abstract out even the duplication of function declarations. That lets you standardize argument names and even make certain changes to function signatures easily. On the other hand, it makes the code ugly, harder to read, and harder for some tools (like syntax highlighters) to parse. It’s easy to dismiss this idea out of hand, but when you have hundreds of back-ends, every little bit of deduplication might be worth it. That said, automated refactoring tools can help with these problems in a way that might be more socially acceptable. (I optimistically used this technique for file type converters in StrongLink, even though it currently only has two.)

You might have noticed that I’ve recommended against using most of the standard features of object orientation here. It turns out that inheritance and overriding conflate different things in a way that causes problems. In particular, avoid about using differential compression to guide your interface designs. That said, they are appropriate tools in some cases, especially when you have a strong theoretical basis for what each class’s responsibilities and inheritance relationships are.

Happy coding!

Keywords: programming, interfaces, APIs

[View] [Short] [Hash] [Raw]


This Mess of a Certificate Authority System

I recently had to switch all of my SSL certificates from StartCom to Let’s Encrypt, so let me take this opportunity to expound on the public key infrastructure used on the web.

First, to establish a secure connection over an untrusted channel using symmetric encryption, you need a shared secret. The conventional solution is to start a handshake using asymmetric encryption, through which each party can be identified by their public key. Then the problem becomes, how do you verify the other person’s public key?

There are basically two solutions in practice: SSL/TLS, which uses a hierarchy of certificate authorities to delegate validation to; and SSH, which presents the public key fingerprint to the users and lets them work it out themselves.

Incidentally, any system which doesn’t use either of these techniques, and which doesn’t rely on a pre-shared key, probably has a fundamental weakness. So for example, I think it should be pretty trivial to steal WiFi passwords just by masquerading as someone else’s access point and waiting for them to connect. The only way it could be secure is if the access point provided a public key for clients to validate. (If you Google phrases like “wifi mitm” you get a lot of application man-in-the-middle attacks like Firesheep, which are completely unrelated, so I’m not sure whether this exploit is known or practical.)

Anyway, public keys must be validated by the client, on way or another. Assuming the user is in control of his or her own computer, that means the responsibility ultimately falls to the user. However, since all of us are lazy and most of us are clueless, and browsers do certificate checking by default, we tend to let browsers do it for us.

Responsibility and power must be two sides of the same coin, because browsers have turned around and created a large industry of certificate authorities. The profits of these companies come directly from users’ disinterest in (and to some extent, ignorance and fear of) verifying certificates themselves.

Okay, so I may sound a little bit anti-capitalist but none of that is bad. The bad part is this:

Before you can verify a website’s certificate, before you can even connect to it, you need to know whether to expect the site to be encrypted or not. After all, there are still lots of unencrypted sites out there. If a site supports encryption, you don’t want to connect to it insecurely, but if it isn’t, you have to or it won’t work. (But note that both “upgrade” and “fallback” strategies are insecure. So you really do have to know in advance.)

The original solution was an “s” in the URL’s protocol. If you go to https://www.mybank.com, your browser knows that the connection must be encrypted. It won’t allow the connection to be downgraded by an attacker.

However, we follow URLs from all sorts of sources: other web pages, emails, chat, etc. We also type mybank.com in the address bar, which still assumes plain “http:”. Either way, if you end up at http://www.mybank.com (without “s”), then someone can intercept your unencrypted connection. So the “s” in the protocol is almost worthless.

The next solution was HSTS (Hypertext Strict Transport Security), which simply lets sites indicate that all future connections should be secure. However, that only helps once the user has connected (potentially insecurely) the first time, similar to TOFU (Trust On First Use).

The final solution was HSTS preloading, which is just a way for browsers to bundle in a list of sites which require encrypted connections. It was started by the Chrome developers but these days basically all browsers use their site list.

If a site isn’t on the big list, your connections to it are vulnerable: if the site uses HSTS, then the first time you visit it (on a new computer or after a fresh install); or otherwise, every time you visit the site without using a trusted link or manually typing “https:”.

In other words, the free HSTS preload list is a browser-run, semi-manual certificate authority, like they were trying to avoid by creating the CA ecosystem in the first place. If it were enhanced to record the fingerprint of each site’s root certificate, then all of the current flexibility could be maintained, but additionally free certificates could be more accessible and secure (because each level of delegation just increases the attack surface).

The problem of verifying certificates and the problem of knowing whether to expect a certificate really are the same problem, so it makes no sense for them to be completely disconnected (even handled by separate entities) the way they are currently.

If you want to get into really crazy territory, browser vendors could bring EV (Extended Validation) certificates in-house, which would give companies like Mozilla another revenue stream, instead of (bizarrely) outsourcing the profits like they do today. As EV gets increasingly automated (from what I understand, some CAs already have a completely automated EV process), this becomes more practical for software companies like browser vendors.

Unfortunately, the biggest problem with this plan is probably social. Once an industry is created, it’s nearly impossible to get rid of, no matter how unnecessary it becomes. Thus, as a final warning for aspiring protocol designers, be very careful where you insert opportunities for profit, because they’ll be impossible to remove later.

[View] [Short] [Hash] [Raw]



JIT can be faster than static compilation.

The argument goes that just-in-time (JIT) compilation can be faster than ahead-of-time (AOT) compilation because JIT can specialize based on the common codepaths and runtime data. For the sake of argument, let’s ignore profile-guided optimization (PGO), which nobody likes.

The counter-argument goes that if JIT is so great, why can’t you JIT C code?

It took me a while, but I finally found the answer. It turns out that there is something lighter than JIT, and more adaptive than AOT, and it’s built into every modern CPU: branch prediction!

The argument that CPUs are optimized for C code is both true and false. It’s true that CPUs are optimized for the most common code they run, which historically has been C/C++. However, the deeper truth is that CPUs (especially X86) are optimized as much as possible, period. Those optimizations are just limited by reality (hardware constraints, generality, etc.).

For the most part, C generates logic that is static enough that the CPU’s branch predictor is enough. But for dynamically typed languages, absolutely everything requires a branch prediction, which overwhelms the hardware.

The only dynamically typed language that is both commonly AOT compiled and heavily optimized that I’m aware of is Objective-C, which makes it an interesting case study. Of course, ObjC is not commonly considered that fast, even compared to C++ vtables.

The idea here is that, if you had a fast execution model for dynamically typed languages, you could make JavaScript as fast as V8 without 90% of the overhead.

First, imagine an AOT-compiled JavaScript that just used libobjc (the ObjC runtime which handles dynamic dispatch, amongst other things). It’d be almost as fast as Objective-C (but slower due to the lack of inline C and different idioms) and almost as lightweight (but still more bloated, assuming you need a garbage collector).

Next, the real question: is there an even faster execution model? Based on the idea of working with the CPU branch predictor (rather than duplicating its effort) and doing a bit of extra “branch prediction in software,” I think that there is.

What if you wrote an AOT compiler that generated instructions that were designed to be changed at runtime? Basically, in psuedocode:

var add(var a, var b) {
	Class type = NULL; // Constant modified at runtime
	if(unlikely(get_type(a) != type || get_type(b) != type)) {
		change_specialization(add, a, b);
	Op op = NULL; // Constant modified at runtime
	return a op b;

In this case, Class is probably a class pointer. Op is inline assembly that gets overwritten. The arguments and return value are carefully arranged so that op can be a single CPU instruction (like an add) or a function call (for complicated/custom types).

From this basic setup, you can do a lot of additional optimizations. For example, you can keep two versions of hot functions: one specialized and one generic. That might be useful because change_specialization() is fairly expensive (requiring two calls to mprotect(2) under W^X, although JITs have that same overhead). You can also do more sophisticated runtime profiling to decide exactly when to optimize/deoptimize like JITs do (but that has its own overhead/complexity, so it might not be worth it).

Assuming all goes well, at this point you have a compiled, dynamically typed language that’s basically slightly slower than Golang (or maybe faster when you’re doing lots of dynamic dispatch). If you can replace the garbage collector with reference counting (ARC), you can eliminate the memory bloat too (but you have to deal with cycles… how much overhead does Python’s cycle collector have?).

A statically compiled JavaScript would not be much use in web browsers (unless you target WebAssembly, but that’s pretty ballsy and I don’t know if it lets you do self-modifying code), but it’d be great for Node.js and Electron.

And of course this execution model would work for any dynamic language, including Python, Objective-C, Lisp, etc.

[View] [Short] [Hash] [Raw]


List of headlines for articles I haven’t written



[View] [Short] [Hash] [Raw]


I’ve gone back and forth on my opinion of slow, high level scripting languages like Python. Note that I’m considering all such languages together, although Python is the iconic poster snake.

Let me divide languages into four categories:

Note that I am intentionally conflating the language itself with how it is executed. There’s no reason you can’t have a slow C interpreter or a statically compiled, if not fast, version of Python. You can also make JITs for anything (and JITs for Python, Ruby and others exist).

I’ve pretty much conclusively dismissed JITs at this point, since with current technology they are always enormous, complicated resource hogs. I would take a slow interpreter over a bloated JIT any day.

I’d also rather rather use a mix of high and low level languages (like C and Lua) over a “middle level” language like C++ that tends to do everything badly.

Anyway, using one high level and one low level language seems like a pretty sane and reasonable position. So the argument against it is a little bit dubious, but still worth considering.

Basically, with the proper libraries and abstractions build on C, it can approach the convenience of Python (Cello is a somewhat ridiculous existence proof). If you take the time to develop that scaffolding, and your own skill and experience to wield C as efficiently as someone writes Python, won’t that leave you in a generally much stronger and more capable position?

This is an argument against using the right tool for the job. If you make a conscious effort to use the most powerful tool, even when it’s wildly inappropriate for the task at hand, won’t that leave you at an advantage in the long term?

Conversely, whenever you bang something out in Python, sure you can get paid and go home, but aren’t you also eroding your engineering sense and judgment?

It’s true that ultimately, economics trumps engineering. However, I wonder if as engineers, that isn’t a really horrible mindset to have.

When you argue that software can be slow because human reaction times are slow, isn’t that an anti-engineering argument? When you argue that computers are fast, or that developer time is more valuable than CPU time (user time), isn’t that anti-engineering?

If you’re just trying to make a buck on joke apps, it’s true that no engineering is necessary. But where does that leave your career, and you as a person? Even if you’re writing joke apps to make a buck, wouldn’t it be better to be practicing skills that will one day let you make something “good,” that will stand the test of time?

That which doesn’t make you stronger is killing you.

[View] [Short] [Hash] [Raw]

Originally written 2016-12-08

Significantly advanced technology is indistinguishable from a deceiving god

One conclusion to draw from Ken Thompson’s historic speech, Reflections on Trusting Trust, states that we inherently must rely on tools to control machine code and data, and that in order to build secure systems, we need secure tools. However, the tools themselves cannot be verified by the naked eye, and so we need to trust more tools. Even an extremely simple tool, like a plain LED, could have a tiny computer hidden inside of it, preventing it from lighting up and concealing a larger attack. (With smart light bulbs, this fear is gradually becoming a reality.)

Paranoia over this sort of threat seems to be becoming more common, and probably for good reason. If miniaturization of computer hardware continues apace, we will eventually reach a point where it’s feasible to hide tiny, malicious computers in everything. One might call it an “internet of things.” There are already tiny keyloggers that fit inside the plugs of USB cords.

This concept, of a world full of undetectable forces intent on deceiving us, is surprisingly similar to the one imagined by Rene Descartes. I tried reading him and it was impenetrable, but hopefully we can trust Wikipedia:

Cogito ergo sum

At the beginning of the second meditation, having reached what he considers to be the ultimate level of doubt—his argument from the existence of a deceiving god—Descartes examines his beliefs to see if any have survived the doubt. In his belief in his own existence, he finds that it is impossible to doubt that he exists. Even if there were a deceiving god (or an evil demon), one’s belief in their own existence would be secure, for there is no way one could be deceived unless one existed in order to be deceived.

While reasonable people would agree that a deceiving god doesn’t actually exist, it seems like some people are intent on creating one.

Even under a deceiving technological god, let us assume that our skulls, brains and minds are intact (since biology is hard). Under this scenario, let’s say we can still trust our sense of logic and our own sight and touch. How much more can we know?

A few years ago I saw a tech demo for a “magic mirror”: a video camera with facial recognition software, hooked up to a display showing a computer-generated person. The idea was it would “reflect” the head position and facial expression detected by the camera back, in terms of the CG person’s head and face. It could have applications for animated movies, video conferencing, and who knows what else. (It was slightly different from a newer commercial tech called FaceRig, since it really did try to “mirror” its input.)

The reason I bring this up is because there was an interesting effect at the edges of the “mirror.” When a face is cut off at the camera’s edge, facial recognition fails. That means there is a dead zone around the edges.

With a real mirror, photons are reflected individually. Even if something is at the very edge of the mirror, a single photon can be accurately reflected. But because these “magic mirrors” reflect expressions, they can only interpret photons in aggregate. No matter how advanced or capable they become, they can’t accurately reflect an arbitrary portion of a face.

This concept of “cutting off” also applies to deceptive technology trying to disguise itself. Imagine you have a malicious multimeter, and you’re trying to inspect a malicious circuit. If they are continuously connected, the multimeter can detect a signature in the circuit’s signal, and begin displaying benign (fake) data. But if you can suddenly connect and disconnect the multimeter at any time, and if it must begin displaying data as soon as it is connected, it fundamentally can’t know what data to display until it is too late.

This was one of the design principles of the hardware isolated laptop[#]. Under the assumption of a bunch of compromised hardware modules, perhaps you could keep them in check by limiting their communication to low-bandwidth, inspectable channels. Even if you weren’t inspecting them at a given point in time, the threat that you could inspect them without warning would prevent malicious behavior. And because of the edge effect, other malicious tools couldn’t hide their behavior either.

Now, this requires being able to detect, observe and control these “edges,” wherever they are. That brings us to the problem of energy gapping:


Clive Robinson on Schneier’s blog came up with a thorough notion years ago: energy leaks plus “energy gapping” systems. He said if any form of energy or matter could transfer from one system toward another device then it should be considered a potential leak. So, you have to physically isolate them then block the whole spectrum practically.

Without some method of full isolation, you can’t create an “edge.” So despite all the other problems, this one tempered my enthusiasm for the hardware isolated laptop project.