5 ms·
I think it could be both a user and an industry issue. Lately I’ve been experimenting with treating many libraries as a starting point in some of my projects.
by rapind 5y ago
I think it could be both a user and an industry issue.
Lately I’ve been experimenting with treating many libraries as a starting point in some of my projects. Meaning I read and use the code, often removing things I don’t need.
So I fork and maintain my own lesser / crippled version (and hope authors don’t take this as passive aggressive criticism!). This helps me lower attack surface and better understand what’s going on.
This doesn’t work for everything obviously. I’m not forking an OS or database, so there are still lots of black boxes, but for some stuff for I’m liking this approach.
Now if another dev inherits my code I doubt they’ll see it my way. The industry wisdom points at simply assembling libraries and only writing your specific business logic. So what if you use a library to do one thing that just happens to do 100 other things (this having a much larger attack surface and bug potential)?
I don’t know yet if I’m being foolish or if I’ve stumbled on some ancient programmer wisdom I simply failed to grasp earlier. At least I’ll probably never run into a leftpad issue.
- FridgeSeal 5y ago> So what if you use a library to do one thing that just happens to do 100 other things (this having a much larger attack surface and bug potential)? I’ve wondered about this for a while, and one idea that’s crossed my mind is whether compiler stages could be introduced to do this. For example, you add a dependency, you use a few methods and structures. You compile it, the compiler goes through your code, looks at what traits, implementations, etc that you do and don’t use, it grabs just the code required to satisfy these, and proceeds as normal. At the end it spits out a little report for you telling you what specific things it included/excluded from your binary/library. Like tree-shaking in JS but better. Maybe this already happens during dead-code-elimination passes, or during some other compiler step, maybe most of our libraries are far too interconnected/non-modular to be able to do this without ending up with the whole dependency anyway, maybe it’s computationally infeasible due to some result in Computer Science, I don’t know-and wouldn’t really know where to look to find out-but if it could be done, and if we could go even further to embed this metadata into the resulting binary itself, we’d at least have a provable way of saying “my application is safe from x because it does not include <vulnerable part of lib y>”. I imagine to do this, you’d need to operate on source code-unless there’s some magic way to do it with precompiled binaries-and runtime dynamism would make things extra difficult, but it’s an interesting idea.
- kelnos 5y agoThis is pretty normal for compiled languages like C, C++, and Rust; the linker will throw out functions and classes that aren't used. In Java it's a bit different, because the compiler doesn't know if some code is using reflection to talk to some other code, so it can't safely throw away stuff that isn't directly referenced. Even then, tools like ProGuard can help you trim out code you don't use, but I don't think they're used all that often outside of mobile. But the log4j thing really isn't in the same class because it's not really "code that wasn't used". It's code that probably users didn't expect was there, and if they knew would probably not want used, but it's there, and the proper functioning of the library included that code path that allowed for JNDI interpolation. Whether or not that code is really "needed" is not something the compiler can really figure out, at least not without teaching the compiler that very very very specific thing (which would be madness). And even then, let's say you bizarrely wanted to be able to do things very much like what the log4j exploits do, there's no way the compiler (or even some kind of specific purpose-built code scanner) can know whether or not some string that might be supplied by a user in the future is going to trigger this JNDI interpolation code.
- kelnos 5y agoI've been thinking about this too. Most of my JVM projects use slf4j and logback, but the same concept applies as for log4j. I probably use less than 10% of the features provided. I log strings at different logging levels, and want to be able to set the level globally at which log lines actually get emitted. My use of interpolation is dirt-simple: I just expect the logging framework to call ".toString()" on the things I pass. I log exceptions, and expect the framework to emit a stack trace in addition to the exception message. I log to stdout, and use pretty much the same log-line format for everything. I like the loggers to be named, and occasionally use the ability to change the log level on a per-logger basis. I could build this set of features in... I dunno, a day? Sure, it would take me a lot longer to build the entirety of slf4j+logback, or log4j, but I don't need 90% of their features. So, yeah, I'll continue to just use slf4j+logback (hell, maybe I should use slf4j-simple); the idea of writing my own simple logging library doesn't really interest me all that much, even if it wouldn't be too hard to do so. But I'm still carrying around all this extra attack surface, and that's unfortunate.
- collinmanderson 5y agoYes I generally use a similar strategy of avoiding dependencies and writing my own small library that does what I need. I also appreciate learning exactly what’s going on. > Now if another dev inherits my code I doubt they’ll see it my way. . . . I don’t know yet if I’m being foolish or if I’ve stumbled on some ancient programmer wisdom I have the same fear here. Lately I’ve been trying to keep a similar api to popular libraries so it would be easy to swap in the real library if ever needed. But yes “at least I’ll probably never run into a leftpad issue.”