I still don't get djb's distinction between untrusted and minimal privilege code. What he calls "not violating security requirements" is effectively a successful least privilege approach. Very few elements can become hacked without breaking security requirements. If you can't gain anything from hacking a piece of software, then why is it even executed? - it obviously didn't deal with anything the user wants.
In his example, yes, you could change the DNS responses, but you still could not escalate to a higher lever where you can potentially modify stored user data. That is a success in practice.
Something that can respond to a DNS request can put whatever it wants in the response. If there's a bug in that program, then whoever controls that bug can put whatever they want in the response.
The only protection from this is to make the code that does this as small as possible so that us human beings can convince ourselves that it is correct and that the risk of a bug that someone can control is zero (or as close to zero as to make no odds).
When Jim Reid wants to pat himself on the back because "at least they didn't get root on my nameserver box", he misses the point: gethostbyname()'s spec doesn't say "it may or may not return. if it returns it could return anything. don't trust it, don't even use it!" They say gethostbyname() return a structure describing the address of the named Internet host, so people expect that and depend on that. Something that "suddenly" violates that gets in the news[1]. Fortunately, nobody remembers what Jim said so the BBC doesn't ask him for a comment.
Anyway.
"Minimizing privilege" doesn't solve that problem because the DNS server needs the privilege to respond to DNS requests.
It might be easier to think about a better example. Let's talk about zlib.
A program that needs to decompress some text is not concerned with the contents of the compressed text, only the uncompressed text. Resource limits on our program exist to keep some things from getting out of control[2], but what about bugs?
If we could run zlib's decompress() with the permission only to decompress text, then the worst-case impact would either spin the cpu or be equivalent to "getting out of control". What do we need to do that?
• No creating file descriptors can be done with setrlimit() except for the dynamic linker is going to open a shittonne of files. We need to know what the minimum number of files are, and decompress can't ever change that without changing our program anyway.
• No accessing files or the network could be done with a setuid wrapper and iptables. At least on Linux. Most programmers don't do this, and most sysadmins only do what they're told, so in practice this doesn't happen.
• Sandboxing! Google published some clever user-level sandboxing that works on Linux to whitelist each syscall. This "verifier" could do it as long as it's smaller than decompress()!
That sandboxing one is tricky: A tiny inflate routine takes around 500 lines of C done the normal way, but how big is our sandbox? Probably a lot bigger.
• Ask the operating system for help! This is what DJB suggests. Ask for a disablefiles and a disablenetwork system call. OpenBSD is implementing this with their pledge[3] system call.
There's not a portable and satisfying solution here yet, but you can see they all cluster around reducing the privileges of the untrusted program.
Now, what's to prevent decompress from lying? What if someone can produce a content stream that causes a future decompress run from producing invalid results. Maybe something really sneaky[4]. What possible protection could we have?
As you can see, in this case so long as decompress is supposed to produce "text", there's nothing we can do to make sure it produces the "correct text".
That's why DJB doesn't want to focus on the "untrusted" aspect, and instead on trying to solve the problem that we have to solve anyway: How do we write software that is correct?
That's a great explanation, but I still don't understand why he says that the principle of least privilege is _fundamentally_ wrong. I fully agree that POLP could lead to an illusion of security or doesn't ensure user's security requirements, but that doesn't make it fundamentally wrong. The correct point is, that you shouldn't over prioritize POLP over code correctness. Maybe he is just arguing against the very strict implementation of POLP I could also agree, but in general, I would argue that POLP is fundamentally true and necessary, but that doesn't mean you should implement complex fine-grained solution with a lot of administrative overhead.
As soon as you build non-trivial systems, you have to contain error propagation with POLP, although you are striving to build simple and secure systems.
DJB is drawing a distinction between two designs in his paper.
1. Netscape had a "dns helper" -- which ostensibly could only do DNS lookups, is designed in the principle of least privilege.
2. Ariel Berkman's xloadimage implementation -- which implements every image loader as a separate filter in a separate process who can do nothing but input image data and output image data (in the "common" format), is designed around eliminating trusted code.
The former could (and did) suffer a bug that affected DNS lookups, and was convinced to perform all sorts of network traffic since, it by definition needed to perform network activity to do it's function, and it could access files like resolv.conf because again, it needed to do that to perform it's function. That it couldn't be exploited to "yield root" wasn't really relevant, since most people didn't run Netscape as root. It could read user files and ship them over the Internet which is frankly bad enough.
I would argue that both are designed following the principle of least privilege. Netscape haven't had the luck of having correct code. So what would have helped in Netscapes case? How would eliminating trusted code work in this case? Netscape has to do DNS lookups. I'm not sure if there was much more left to do as writing secure correct code. And of course you should prioritize writing secure correct code over implementation of least privilege. That doesn't make the principle of least privilege fundamentally wrong.
My opinion is that if you design your software securely threat modeling should result in the decision of implementing the least privilege principle and whether it makes sense and benefits (complexity vs benefit) or not. Of course you better eliminate trusted code so that there are less case where you have to get to these decisions.
I assume that soon or later, there are situation, where you can't eliminate trusted code and it makes sense to implement least privilege.
> I would argue that both are designed following the principle of least privilege.
Okay, but that's not what DJB means, and attempting to read his words with the definitions in your head, instead of the definitions in his head won't help you understand him.
I'm not going to humour an argument about mere semantics: For the purposes of this discussion they are not both the "principle of least privilege".
> So what would have helped in Netscapes case?
Writing the DNS client correctly.
DJB's point is that absolutely nothing else would help: You can't realistically put a box around buggy code as long as the code needs privileges.
And all that effort in writing that sandbox? A waste of time; fundamentally the wrong thing to focus on. Writing a DNS client is far less work.
> I assume that soon or later, there are situation, where you can't eliminate trusted code and it makes sense to implement least privilege.
That was what DJB assumed when he wrote Qmail, however he is now convinced that was wrong. His paper gives some explanation why.
If you can't eliminate trusted code, and it's still big enough you think there might be bugs hiding inside, you should rethink your design.
Right. I think I see the difference he intends. I see this more of a practice -vs- theory issue. (Or in isolation/in deployment) In theory he can work on designing the correct version of gzip and there's a chance he'll succeed. But in practice, I'm still putting a seccomp/pledge-equivalent on it, because if he fails, I'm stopping local root escalation and potential lateral movement, which he doesn't seem to think are interesting consequences.
That's definitely not how I understand this DJB quote:
> I have become convinced that this “principle of least privilege” is fundamentally wrong. Minimizing privilege might
reduce the damage done by some security holes but almost
never fixes the holes. Minimizing privilege is not the same
as minimizing the amount of trusted code, does not have the
same benefits as minimizing the amount of trusted code, and
does not move us any closer to a secure computer system.
By "does not move us any closer" I don't believe he wants us to do it at all.
> By "does not move us any closer" I don't believe he wants us to do it at all.
Then take a look at § 5.1 of the paper which gives a clearer example with which to draw the distinction.
Eliminating trusted code is what you're doing by decorating uncompress with pledge() with any capability to acquire resources; anything beyond stdio (or seccomp)
Minimizing privilege means focusing on finding some other argument for pledge().
I think he intends “privilege” to refer only to filesystem and other OS-level privileges, not more generally to the capabilities of code, and I think he uses “untrusted” to mean minimally-trusted—more restricted than the OS can enforce.
Taking the DNS Helper example, one could imagine a function-like DNS Helper which has the capability only to return a value. This would make libresolv just a bug, not a security hole, because the attacker would only pervert their own request.
In his example, yes, you could change the DNS responses, but you still could not escalate to a higher lever where you can potentially modify stored user data. That is a success in practice.