14 ms·
A capability-safe language would have minimized the Log4j vulnerability
- josephcsible 5y agoThis approach reminds me of Haskell's effect systems.
- gianone 5y ago
- eska 5y agoIt seems more practical to use BSD’s approach of pledging once in main that the process won’t access the network. Parts of the program that need different capabilities are isolated in their own processes and communicated with using IPC. I don’t think people want to pass all kinds of capabilities around in every function call.
- MattPalmer1086 5y agoSounds better in general, but probably wouldn't help with something like logging which would probably be used in all the processes. Unless you want to make IPC calls for every logging call.
- vlovich123 5y agoI don’t know. It’s not necessarily a bad idea because then you have a single audit point for all logs and can see the cost centralized in measurement tools vs it looking to be in the noise and never popping up unless you have particularly egregious hot spots.
- vladvasiliu 5y ago> Unless you want to make IPC calls for every logging call Isn't this more or less what ends up happening anyway? Sure, from the application's perspective it's just a function call. But usually, in the end, the logs are shipped to some central location one way or another.
- MattPalmer1086 5y agoHmmm, good point!
- dkysang 5y agoDoes this interact with or preclude operating system level capabilities?
- Ericson2314 5y agoIt interacts with well. OS-level ones call the shots, but maintaining the same discipline within a process is a good way to write better code, (especially better libraries).
- vsareto 5y agoWhy should a programming language be limiting network access? Why wouldn't we do this via the operating system?
- jayd16 5y agoIt might be useful that some code in a single process have access while other parts do not. How would you propose an OS handle those cases?
- vsareto 5y agoI don't think that's particularly useful though. A program with requirements like that seems more likely to be split up into two independent pieces.
- Jweb_Guru 5y agoBut that's not at all the case in practice. Logging is intertwined with other program functionality.
- samus 5y agoMost logging libraries are facades over a concrete implementation. And there exist adapters for those that aren't. One could easily provide one that pipes the log stream to a child process that drops privileges and is restricted down by the OS. This will probably entail a performance penalty, but it could be worth it. It would be nice to have such a capability in-process though.
- alerighi 5y agoAnd how do you do that, without operating system support? I mean, you can't of course limit that based on the code, in the JVM there is only a single address space, thus every method or class can be instantiated from anywhere in the code (despite public/private/etc. that are only for programmer convenience, they are easily circumvented with reflection, they don't provide any security at all). You can imagine doing something like adding some bytecode instructions that if encountered by the JVM block the network access to every other request done in the call stack after that instruction. Something that reduces efficiency drastically and still can easily be circumvented if you don't implement different address spaces for different parts of the code (something that only the OS can do).
- the_arun 5y agoIdeally all egress network connections should go via NAT Gateway & filters at NAT Gateway should have policies to say which request can go out. Failures do happen in programming, we need to have better guardrails ensuring security.
- xxpor 5y agoNAT != firewalling. People conflating the two leads to policies like needing NAT for ipv6.
- jrockway 5y agoWhat sort of software are you using for the NAT gateways? (I know that's an AWS term, but not what I'm looking for. I've used that to provide a consistent outbound IP for production traffic so that upstream providers can allowlist an IP to talk to their API. Dumb security model when TLS client certificates exist, but... easy to set up I guess.) I've always wanted to defend against the attack where an application routinely talks to some API hosted on AWS, but an attacker starts using the application to exfiltrate data to another API hosted on AWS. If you just look at outgoing IP addresses, you'll think something like "oh, that API just started another replica" and not "my app is doing something weird". I want to do MITM on my applications so that every outgoing payload can be inspected. (I come at this from a security angle, but I'm really more interested in debugging in production. "Page foo that talks to the bar API has stopped working" "Oh, here's the JSON it started returning instead of text/plain." Bug fixed in 5 minutes.) I know this sort of pattern is common for, say, corporate firewalls, but I haven't seen any good projects for doing it to arbitrary applications running in production. I looked at Apache Traffic Server which might be the right thing, but seems super old and doesn't support any integrations I'd want (OpenTracing, Prometheus). I also tried configuring Envoy to do what I want, but it also didn't work. (Things like Istio's Egress Gateways seem to not intercept TLS, so you just get a list of IP addresses requested, not URLs.) I was thinking of just writing something to do this, but I know that everyone on Earth wants the same thing, so I figure I'm just missing the obvious out of the box solution. I'm 100% OK with fail closed (applications must be configured to only egress through a known-trusted IP of the proxy, all other network connections fail), reconfiguring my applications (happy to use some library for this, or inject TLS certificates for authenting the MITM proxy to the application), and this only working with HTTP. Suggestions?
- monocasa 5y agoExcept the vulnerability was a stack up. The logger wasn't making network calls per se, but passing requests to a component (JNDI) that would obviously need network access. You'd have the same root issue, it'd just manifest more as sort of a confused deputy with a capability based model.
- armchairhacker 5y agoIt still passes. Because JNDI would require network access, so log4j would also have to require network access or "disable" the network capability.
- monocasa 5y agoPractically (and like is being suggested here in other comments), JNDI would probably be a separate component (maybe a separate process) with network access, and log4j would just send those user provided strings over an IPC channel, and you'd be in exactly the same place at the end of the day.
- ryukafalz 5y agoSure, it could be, but in a capability system if the application is starting that JNDI component, granting it network access, and granting the logger access to it: 1) It's pretty dang explicit to the programmer that they're granting the logger network access, because they're the ones writing that logic. 2) Even if you've granted the logger network access, you probably haven't granted it complete access to your filesystem. Network access isn't the only sensitive thing on your system, and a logging library needs very little authority to operate (specific files/directories and its own config?). Getting code execution in the logger shouldn't be game over security-wise, because it shouldn't have that much authority to begin with.
- monocasa 5y agoWhat it would probably look like was they were granting an IPC channel to the JNDI component, just like they would for DNS or what have you. And then separately "ok, yeah, JNDI needs network access, that makes sense". So you wouldn't see network access directly in the capability manifest for the logging component. None of this protects you from "fuck it, load class file from random ldap server" that was apparently coded up in JNDI.
- adfhadfignio 5y agoJava has actually had this built in for a long time now. A SecurityManager allows you to restrict access to things like the filesystem and the network (and whatever else you want). I have never seen it used in a real codebase. https://docs.oracle.com/javase/tutorial/essential/environment/security.html https://docs.oracle.com/javase/tutorial/essential/environmen...
- cesarb 5y agoWe actually use it in a real codebase. When the software is running on "developer mode" (on a developer's machine), we install a SecurityManager which denies all outbound connections except for localhost by default.
- nibix 5y agoElasticsearch is actually using SecurityManager with quite thoroughly locked down policies; and it seems that this actually saved ES from being vulnerable to the RCE. The irony is now that OpenJDK just recently decided to deprecate the SecurityManager in Java 17 and remove it in Java 18. See also this Twitter thread: https://twitter.com/rcmuir/status/1469730949810339843 https://twitter.com/rcmuir/status/1469730949810339843
- catern 5y agoJava's SecurityManager is very different from capability-safety. It's much more like setuid: SecurityManager allows or disallows network accesses depending on what code is running. (Literally by inspecting the stack)
- samus 5y agoThe security manager represents a flawed way to do it. It tries to catch up and restrict an application after it already has access to the releveant APIs. Forget to restrict just one API, and the sandbox can be escaped. Usually, the integration with the security manager requires intimate knowledge of the application. Javascript follows a better approach because it has a small core API. All other APIs have to be explicitly injected and permitted by the host program.
- to11mtm 5y agoSo, IDK what the Java world had that was similar, but in .NET at one time they tried to solve this at the runtime VM level with the concept of Security Permissions and trust levels. In practice, this wound up being: - Somewhere between too confusing/frustrating to developers (especially ASP.NET ones, where often the escape hatch of 'AllowPartiallyTrustedCallersAttribute' would get thrown around) - Hard to manage from a per-app granularity standpoint. Edit, hit the post button too early: In any case, this behavior wound up getting changed to be a lot more forgiving in .NET 4 (although, often in fact requiring the removal of the aforementioned APTCA from your web projects,) and IIRC it's gone in Core.
- gnufx 5y agohttp://joeduffyblog.com/2015/11/03/blogging-about-midori/ http://joeduffyblog.com/2015/11/03/blogging-about-midori/ sounds relevant as what sounds like a contrast in that space.
- xxpor 5y agoThere's what I think is a similar concept in Java called security contexts. I've never encountered it actually being used, and it just tends to get in the way, like you said. These strike me as similar to SELinux in that regard.
- pjmlp 5y agoJava had JAAS, which fulfilled a similar purpose, and it is scheduled for removal for similar reasons CAS was taken out of .NET.
- gnufx 5y agoHear, hear. I've said at work that many, perhaps most, of the security issues I've seen are at least related to ambient authority. I never see these major issues trigger any deeper thinking about the issues beyond the relatively shallow bug causing the vulnerability. (A subtlety here is that you may want authority to write to a network filesystem.)
- DarkmSparks 5y agoPersonally I wonder whether anyone actually used log4j willingly or just because its generally used by all the apache projects and they wont start without it. With a healthy dose of "use the latest versions" mentality. I checked back and all my java code was still running log4j 1.1, and removing it has been on the todo list ever since it needed linking. You should be checking all your 3rd party source code, "capabilities" wont get round that, the number of applications that dont need network access these days is as good as zero.
- fiddlerwoaroof 5y agoI think you’re misunderstanding capabilities: they can apply also at the class/method/module level. If you’re language doesn’t have “ambient authority”, nothing can create a network connection unless it’s explicitly granted permission to do so and, typically, this permission is granted by passing some sort of non-forgeable token around.
- DarkmSparks 5y agoJava used to have exactly this with security manager https://docs.oracle.com/javase/tutorial/essential/environment/security.html https://docs.oracle.com/javase/tutorial/essential/environmen... Its been removed from the language because "it wasnt needed or used".
- fiddlerwoaroof 5y agoThis is more like ACLs, which are a completely different paradigm from capability-based security. An ACL system is based around specifying permissions for actions and such, whereas capability systems are based around reifying authority in an unforgeable way and passing the resulting tokens around.
- DarkmSparks 5y agoI don't see the difference between passing tokens around and inheriting a security manager that can only have permissions and capabilities revoked?
- randombits0 5y agoWhat you let out is just as important as what you let in.
- nickdothutton 5y agoFor at least 25 years we have been telling people to implement egress filtering and to only allow outbound the specific, known, necessary connections from any given server. Rarely do I see it done.
- Ericson2314 5y agoCapabilities are underrated as a generally way to purge bad archictures, make it clearer what code is doing, and generally cut accidental complexity & improve programmer productivity. This is a big deal, because many security practices are neutral or bad for programmer productivity. We need a big project to get CloudABI implemented in all the major kernels to make the theory reality. Whereas before it was unclear what was a good candidate to get this stuff in prod, now it is very clear that socket-activated services are an idea use-case, with very little migration pain. Even if you think we should be going to Fuschia or SEl4 or whatever, I think this is a good stepping stone. Those projects are a big jump alone, and funding is uncertain. (Plus there are issues of single-company dominance with Fuchsia.) I think CloudABI is the sort of "non-reformist reform" not "worse is better" stepping stone that would help those projects not hurt them.
- ryukafalz 5y agoAgreed re: the general idea, but isn't CloudABI in particular superseded somewhat by WASI? Its repo seems to say it is: https://github.com/NuxiNL/cloudabi https://github.com/NuxiNL/cloudabi (WASI is similarly capability-based, as I understand it!)
- Ericson2314 5y agoI don't think so. WASM is changing things on many fronts. CloudABI is just doing one front. I don't have any thing against WASI, and I don't blame them for wanting to point out a like-minded project that was still active. But just as I think CloudABI is a good stepping stone for seL4 or Fuschia, I think it is a good stepping stone for WASM. Also, I guess I don't believe in coupling change on in principle independent axes. If you at least allow the knobs to be turned separately, even if you don't e.g. CI or otherwise support all combinations, you are incentivized to handle things more "parametrically" vs if-def soup (which matches capabilities, incidentally!) and you have a great way to troubleshoot stuff. This is like how NetBSD says they like supporting obscure architectures to catch more bugs in the portable code too, not just make their lives harder.
- 5y ago
- mlinksva 5y agohttps://fossandcrafts.org/episodes/20-hygiene-for-a-computing-pandemic.html https://fossandcrafts.org/episodes/20-hygiene-for-a-computin... does a great job of introducing capabilities in an easy to understand manner.
- kragen 5y agoThanks, Mike. This kind of introductory material is clearly needed in this thread!
- teitoklien 5y agoWhat you need is not a capability safe language, but rather a tool to make application jails and similar systems more accessible. Your os exists for a reason, let it handle the sandboxing for you, and not the language. Otherwise you fall into troubles later with different sandboxing vulnerabilities in different compilers. Have a codebase that compiles using deprecated features which are removed on language version 2.1 ? “Tough luck, security patches only exists in 3.1 and above, guess you’ll have to stay unsafe” Not to mention the pain it would bring on languages to backport safety features to older LTS releases of the compiler. Just why?, let the os handle these things for you. It is called an operating system for a reason. Log4j vulnerability was minimised in any organisation that restricts its applications network capability to whitelisted machines only, with the help of its OS features. (Yes your application might need network access, so run it through a reverse proxy and filter the traffic based on strict set of rules)
- dccoolgai 5y agoCapabilities model is not about "features" which might be deprecated, but more about "access to standard resources". Capabilities e.g. doesn't care that you are running version 3.1.73 of a certain dependency, it cares that you want to open a network connection to 123.76.34 or that you want /usr/bin/secret file.txt.
- samus 5y agoThe version [of a dependency] starts to matter if it blocks upgrade to a version of the platform that would allow to control a specific new capability.
- justinpombrio 5y agoThis is a good point. I talked about capabilities at the language level, but they're just as important if not more-so at the operating system level. If you have capabilities in the programming language, then you can make guarantees about what resources (like the network) your program's transitive dependencies can use (in process). If you have capabilities in the OS, then you can make guarantees about what resources a process can use. EDIT: this is how you would use capabilities in the OS to obtain "application jails". But capabilities would be more flexible here, for example by allowing processes to share them. As monocasa points out in a separate thread, log4j is actually a messy example---messier than I realized when I wrote this post---because the way it (is? might be?) set up is that your application runs in one process, and JNDI in another. Which brings up a lot of design questions around capabilities in the OS and in the language and in how they interact that I don't know much about. But the primary point of capabilities is to limit how access control can flow from place to place, using standard data-flow mechanisms. In a programming language, this means tying access to whether you have a pointer to an object. In an OS, I think this looks more like OS-level permissions granted to a process, that processes can share with each other. You don't have some big table somewhere saying who is allowed to do what. Instead, access is granted by a thing, that you can pass around. When you get used to the idea, it just feels very natural and powerful.
- deleted 5y ago[deleted]
- lgrapenthin 5y agoWhile tools can save us from stupid mistakes, some mistakes are so stupid that relying on tools to prevent them seems much more dangerous even. Log4j should not have been used by anyone, and certainly it should not have been extended and "improved" in an endless cascade of irresponsible additions, likely violating every principle of good practice, ever. I hope that this is what industry people take away from it, not that some magic language feature should have prevented it.
- catern 5y agoIf log4j is such a fundamentally bad idea, surely we should expect our tools to stop such ideas from ever being implemented. In the same way that modern languages are memory-safe (disallowing pointer arithmetic, because it's proven to be a terrible idea), a real modern language would disallow log4j by being capability-safe.
- mikewarot 5y agoSo close to getting to the actual root issue (ambient authority), but so far away (blaming the programmer, instead of structural deficits in the OS). We need operating systems that don't hand out authority like candy if we're ever going to have the usability and freedom that we used to have in the 1980s with floppy based IBM PCs running MS-DOS. I'm constantly dismayed at the ongoing failure of imagination that accepts a security model designed for a computer shared by co-workers in a small department in a corporation, as the model to chose in the age of mobile code and persistent networking. It's as if we've designed our infrastructure out of crates of Dynamite and wonder why things keep blowing up.
- catern 5y agoOperating systems are also written by programmers. An operating system written in a capability-safe language would not have such ambient authority problems.
- mikewarot 5y agoIf that were true, you could cross compile Windows 10 to Rust Source code (or whatever language you think its magic enough to be safe), compile it from that language, and then it would never have a security problem again. Obviously that's not true.
- catern 5y agoCross-compiling OpenSSL to Rust and then compiling the Rust would also not get rid of the memory-unsafety vulnerabilities created by writing OpenSSL in C. Nevertheless, if you wrote OpenSSL in Rust, or any memory-safe language, it would not have those memory-unsafety vulnerabilities. The same applies when writing in a capability-safe language. It's fairly deliberately ignorant to call these kind of properties "magic".
- adrianN 5y agoThat's not true because the compiler of the magical safe language would likely reject the Windows 10 source and "cross compiling" is actually a major rewrite.
- reactivenz 5y agoAs was said with the .Net had something like this. This reminds me of Threads vs Tasks in .Net also, where to do async threading well, you used to have use API's that wanted the kitchen sink of options. To one group of user this was a chance to go read lots of stuff and workout what all these options meant and why they where want, security etc etc. Writting solid code, etc etc. To the other group of users, you get a magic screen full of code you cut'n'paste and it "magically works", this bit done, next problem. Then Tasks came alone, and the code Demo's well because it just works and is sleek and only needs "a couple of lines of code". That is until you want to use a pool, and manage this and that, and slowly you learn/add all layers that provide the old API's functionality, as you discover you actually needed more code. Which relates, the log4j code "just worked" so everyone closed that issue, and got another ticket started. There is mostly not idea of craftmanship, it mostly "velocity" based thinking. Thus in the last decade the simpler snippets of code that just work, allow more cheaper/mindless monkeys. And we just grab packages, tools , and leverage "free and awesome" but don't understand anything of what is actually happening. And it's not going to improve, because there is a drive for more code, code is already too expensive, so it's just going to be done cheaper and cheaper. So whatever neat "solutions" to the "nobody will ever read the manual to do it correctly" problems of this class represent, they better work, for all the ever increasing people, that will never read the manual.
- kortilla 5y agoHere’s the alternate history where “Network” is passed as an argument to the constructor. The year 2006 rolls around and centralized logging is becoming pretty cool. Someone requests that log4j add support to logging to a UDP syslog server. The constructor now requires “Network” and now everyone still thinks it’s reasonable. IMO the elephant in the room is code execution of remotely loaded code. I want to be able to give something access to the network but not be able to execute stuff from network provided sources.
- bo0tzz 5y agoIt's a similar story though, that could be covered by another capability - one to access the Java classloader, or run executables on the filesystem, for example.
- tomjakubowski 5y agoThe last few paragraphs of the post address your post's alternate history.
- gnufx 5y agoThe way I put it was how come a remote process has authority to load arbitrary code into the local process?
- continuational 5y agoI am working on a capability-safe language! I fully agree with this article, and the great thing about object capabilities is that they are just plain values, and require nothing special from the type system. This also means that object capabilities aren't "colored" like async/await, monads, or other effect systems.
- Zababa 5y agoDo you have more to share, like resources about capability-safe languages, or your ongoing work? > This also means that object capabilities aren't "colored" like async/await, monads, or other effect systems. That's interesting. How does that work? Is it by passing around a value from your main function to the functions that need it?
- continuational 5y ago> Do you have more to share, like resources about capability-safe languages, or your ongoing work? I can share an example from the main function of the self hosting Firefly compiler [1]: main(system: System): Unit { ... let fs = system.files() if(fs.exists(tempPath)) { deleteDirectory(fs, tempPath) } ... } deleteDirectory(fs: FileSystem, outputFile: String): Unit { fs.list(outputFile).each { file => if(fs.isDirectory(file)) { deleteDirectory(fs, file) } else { fs.delete(file) } } fs.delete(outputFile) } Main is passed `System`, which is a value with methods to access the network, the file system, etc. It passes on the `FileSystem` value to `deleteDirectory`, which only has methods to access the file system. Since there's no other way for `deleteDirectory` to obtain capabilities than to recieve them as arguments, `deleteDirectory` only has access to the file system. > That's interesting. How does that work? Is it by passing around a value from your main function to the functions that need it? Exactly; such arguments can themselves be seen as "coloring", since they show up in the function signature. However, an important distinction is that you can capture capabilities in the fields of an object or in closures, and thus get rid of the coloring. The Firefly language is quite far along - nearly feature complete, and translating itself. However, there's a lot of work left before it becomes a viable alternative to existing languages, not least in the tooling and documentation department. [1] https://github.com/Ahnfelt/firefly-boot/tree/master/compiler https://github.com/Ahnfelt/firefly-boot/tree/master/compiler
- alasdair_ 5y agoNo, it wouldn’t have, for the simple reason that no one is willing to go to the extra effort to use a capability-safe language, for the same reason we don’t all code in formally-verified languages - it’s just too much work. The claim in the article could be “The log4j vulnerability could have been prevented by hiring someone to punch any developer who tries to use JNDI in a logging library in the face” and would be equally valid.
- kragen 5y agoIt sounds like you haven't tried it, so you're just guessing that it would be hard without any knowledge, because you're used to writing code in such a way that security is inconvenient. Writing code in E is nothing like writing formally verified code. It's a lot less work than writing code in Java or C++.
- PedroBatista 5y agoIs it? I’m referring to real world coding to solve real world problems in order to get real world paychecks.
- kragen 5y agoYou're going to have a hard time getting a job writing E, or for that matter finding E libraries, because nobody uses E. Same story with Lobster, Clean, RScheme, Cobra, or a zillion other languages that don't have much adoption. But that is an entirely separate question from it being a cumbersome programming language, which is what the grandparent comment was alleging of all ocap languages—based on, apparently, total ignorance. There's nothing wrong with total ignorance; it's where we all start. But there is a great deal wrong with pretending that your total ignorance is expertise that justifies dismissing what you're ignorant of. Believe me, I have a lot of experience being that guy. It sucks.
- pdimitar 5y agoI don't disagree but we have to start somewhere. For example: formally specify [the clear parts of] HTTP 1.1 with declarative programming (which will generate proper code) and that would already be a huge jump. My point is: at one point somebody has to contribute.
- thrower123 5y agoIn theory, this always seems like it would be a good idea. In practice, I think it would turn into a ball of mud because your capabilities would be viral and end up infecting all the way up the stack, similar to the way async/await works in dotnet or the IO monad does in Haskell.
- throw149102 5y agoA fun excerpt from "Capabilities: Effects for Free" Capability-safe languages prohibit the ambient authority [18] that is present in noncapability-safe languages. An implementation of a logger in Java, for example, does not need to be initialised with a log file capability, as it can simply import the appropriate file-access library and open the log file for appending by itself. But critically, a malicious implementation could also delete the log, read from another file, or exfiltrate logging information over the network. Other mechanisms such as sandboxing can be used to limit the damage of such malicious components, but recent work has found that Java’s sandbox (for instance) is difficult to use and therefore often misused [2, 12]. Emphasis added my own. [1] https://www.cs.cmu.edu/~aldrich/papers/effects-icfem2018.pdf https://www.cs.cmu.edu/~aldrich/papers/effects-icfem2018.pdf
- axiosgunnar 5y agoHindsight is 20/20
- samus 5y agoSeveral good points, and so much to comment on. I will restrict myself to repeat that Java already has several mechanisms to enforce capabilities, and that this is mainly an exercise in API design. However, the article fails to recognize that Log4j (and probably other libraries) have different capability requirements at different points in time and in code. At startup and when reloading configuration, only access to some severely restricted JNDI lookups should be allowed. Core message formatters and lookups should have minimal privileges. Appenders require access to network and filesystem APIs though! The capability style presented by TA requires the ability to use of Dependency Injection to properly inject handles that permit access to restricted APIs. This is quite fine in the case of Log4j, which allows programmatic configuration, but it would force developers to give more cababilities than strictly required to more monolithic components!