9

In view of the Hafnium and Solarwind hacks, where multiple zero-day vulnerabilities were used to ultimately stage the hack and data exfiltration, would the use of memory safe programming languages such as Rust to build software help to reduce or end all these zero-day vulnerabilities/exploits through a programming paradigm?

More to the point, is there anything (an architecture, a programming paradigm) we can do to reduce/stop zero-day vulnerabilities/exploits so data exfiltration can become a thing of the past?

There are so many security tools out there, so many sophisticated cybersecurity providers, but none seem to be able to stop these zero-day vulnerabilities/exploits -- perhaps the solution is something easier and within our grasp? Instead of a patchwork of tools, would something as fundamental as the language one uses to develop software be the key to ending all zero-day vulnerabilities/exploits?

12
  • 3
    Given the variety of reasons for problems (from low level buffer overflows to high level logic errors and on top of this human errors) I don't see a single and simple approach to address these problems - even rust addresses only a small aspect of the possible errors. More requirements lead to more complexity which lead to more errors. Use in more critical environments lead to higher impact of errors. Mar 9, 2021 at 6:49
  • 13
    To poke a huge hole in your hypothesis, that a memory safe language will patch up vast swaths of vulnerabilities: PHP (my language of choice) is completely memory safe. It is mocked for having many applications that are trivial to exploit. So: No. Limiting development to a single known platform won't reduce the number of exploits in any meaningful way.
    – Ghedipunk
    Mar 9, 2021 at 6:53
  • 2
    For those two specific cases you mentioned, no. Neither were due to memory corruption vulnerabilities.
    – Xander
    Mar 9, 2021 at 16:06
  • 2
    It should be noted that the Solarwinds hack happened because hackers managed to get the password to log into Solarwinds' network itself. They then used the credentials to inject malicious code into their product which got distributed as a trusted update to all their customers. The problem wasn't the way their code managed memory.
    – Seth R
    Mar 9, 2021 at 16:56
  • 2
    0-day exploits are simply new exploits that were discovered by malicious actors and used before it is patched. For all intent and purpose, the Heartbleed exploit was "0-day" for probably over 10 years, because nobody knew about it.
    – Nelson
    Mar 10, 2021 at 2:32

6 Answers 6

44

0-days are gonna be a thing as long as humans are making software.

A single exec/eval is often enough for an exploit. Using memory-safe languages like Rust or high-level languages mostly remove memory issues but introduce entire categories of attack vectors, e.g. Code Injection for interpreted languages like Python. There's also logic errors, such as TOCTOU errors, off-by-1 errors, and forgetting to check certain parameters.

Putting malicious code in your software because you copy-pasted some code from a stranger on the internet without checking the code or accepting a pull request made by a malicious or newbie programmer is another way you can introduce bugs to your code without knowing.

Humans are prone to mistakes and will continue making dumb mistakes, whether you like it or not. 0-days aren't just memory issues, they're often logic bugs that can't be caught by software and need to be checked by people.

13
  • 21
    This is right, but still misleading. Even if all languages will allow exploits, that doesn't mean you get the same or even a similar density of them. Replace C with PHP then maybe every memory issues will be replaced by an injection etc. issue, but with something like Rust you really remove a lot of easy ways to go wrong without adding many new ones. This won't avoid all vulnerabilities, but it may well reduce their number by an order of magnitude. Mar 9, 2021 at 15:17
  • 4
    @sevensevens the problem is hackers "breaking" languages, which is rare, but what kind of vunerabilities it allows the programmer to write. C let's you write vunerabilities because it's low-level: it's the programmers job not to write them.
    – PyRulez
    Mar 9, 2021 at 22:58
  • 1
    @sevensevens “security bugs are most often dumb mistakes by humans” – exactly: dumb mistakes that the compiler should catch, and that indeed modern compilers of modern languages can catch, whereas this is not possible in C or PHP because in those languages an awful lot of really bad code is actually perfectly supported and could also be used in legitimate programs (and indeed, is sometimes used by bit-tweaking ninjas to squeeze out some extra performance). Mar 9, 2021 at 23:50
  • 1
    The answer answeres whether or not zero-day bugs can be removed... but not whether or not they can be reduced.
    – NPSF3000
    Mar 10, 2021 at 1:39
  • 1
    @NPSF3000 It's hard to say if a certain language will reduce bugs overall. Compared to C, Python effectively removed memory issues but introduced new issues not in C, such as import path hijacking, Rust is currently considered memory-safe compared to C, but Rust is still new and we haven't had much time to crack it yet. Every new language is trying to solve some problems, but can also introduce new problems not in already popular languages. Security often depends more on usage of the language than the language implementation. Mar 10, 2021 at 7:17
32

TLDR: Rust is probably an improvement, but it is not a panacea.

The Rust programming language aims to reduce vulnerabilities caused by undefined behaviour.

Rust has undefined behaviour

First of all: using rust does not reduce the chance of vulnerabilities caused by undefined behaviour to zero because rust has unsafe and misuse of unsafe can lead to UB. unsafe exists for pragmatic reasons. Most applications will depend on a small amount of unsafe code (possibly included as a dependency rather than written by the developers of the app). The promise of rust is that it can make finding and fixing UB more scalable compared to C and C++. Only bugs in unsafe code can cause UB. Fewer opportunities to make a mistake -> fewer mistakes.

UB is just a drop in a bucket

A much bigger issue is that UB is just one of the many sources of vulnerabilities. Here are some examples of vulnerabilities that Rust would not protect you from:

Configuration can be vulnerable too

Build a system that even a fool can use, and only a fool will want to use it. - Shaw's Principle

As Adam Barnes has pointed out in his answer data exfiltration is possible even without exploiting software bugs. Most software has some configuration mechanism in order to be able to adapt to many different environments and use cases or to be able to adapt to a changing environment. Due to Shaw's Principle it is not uncommon for this configuration mechanism to allow insecure configurations. Moreover: authentication usually relies on keeping a piece of information (a password, a token, a cryptographic key etc.) secret and breaks if that piece of information is not secret.

Here is a number of examples of insecure configuration:

4
  • 9
    Latest numbers published by Chrome/Edge/Firefox are that about 70% of critical security bugs were due to memory safety issues. Even assuming that they used only safe Rust, the unsafe portions of Rust's std were bug-free, and the compiler was bug-free -- this means that 30% would remain. 30% is much better, of course, but it's still 30%... Mar 9, 2021 at 18:27
  • 4
    Don't let perfection get in the way of progress... Mar 9, 2021 at 22:36
  • The problem is not so much the undefined behavior, but the actual (non-specified) definition of that behavior by the combination of compiler, runtime, processor. But that would also be a problem if it were specified this way. Mar 9, 2021 at 23:34
  • 3
    Re: "Most applications will depend on a small amount of unsafe code (possibly included as a dependency rather than written by the developers of the app)." -- I am pretty confident that the Rust standard lib uses unsafe a good bit, so my guess is that all Rust devs use unsafe transitively. Mar 9, 2021 at 23:58
11

First of all, very quick history lesson: memory-safe languages have been around for decades. Java, for example, is aggressively memory-safe (aside from the risk of null reference exceptions, which can cause crashes but not memory corruption); it exposes no pointers (addresses) and allows no manual memory management. Yet the mountains of code written in Java still have vulnerabilities all the time.

Why?

Well, some of it is that, in the end, the features of the language that make it memory-safe have to be implemented in actual machine logic, which is decidedly not memory-safe. It doesn't do any good to have a language where all java.util.ArrayList accesses are guaranteed bounds-checked if ArrayList is implemented using a native memory buffer which, due to some pointer arithmetic resulting in integer overflows on certain platforms, occasionally thinks an index is safe when it isn't. Any language implemented on an actual, real-world, widely-used instruction set will face this issue, because all that the CPU understands are addresses and values.

But even aside from bugs in the compiler or runtime, there's no lack of logic bugs. Shell injections, like SQL injections and XSS (which really ought to be termed "script injection" or "html injection"), lead to arbitrary code execution without any memory corruption. Missing authorization checks, where some object that should only allow certain users access allows everybody instead or where something is R/W for a group where it should be RO, are common and can give access to all sorts of things. Cryptographic mistakes, like reusing the same IV/nonce and key for multiple operations or failing to include integrity checks on encrypted data, can break any system that depends on them. Spoofing attacks, where some message is assumed to be trustworthy but is attacker-controlled (often due to one of the errors above) can also lead to code execution.

It is impossible to design a language, even in theory, that is immune to such issues while still being Turing-complete.


There are attempts at doing this with specific chunks of code. "Provable correctness", where you attempt to exhaustively specify the input space and map it all to the correct outputs, and then verify (typically through static analysis) that the inputs all produce their correct outputs, is one attempt at this. However, even where the program isn't too complex for such proofs to be feasible, the idea breaks down because "inputs" include the entire environment in which the program runs, and "outputs" include all the detectable effects of the code (not just the actual value it returns). An algorithm that takes a 128-byte string as an input and returns true iff it exactly matches some secret value without ever exposing the secret itself is very easy to prove correct. However, if it uses an early-exit algorithm (where the first byte that doesn't match causes the function to immediately return false) then a timing attack reduces the difficulty of finding the secret from "brute-force it until the heat death of the universe without getting close" to "this might take a few hours, depending on how noisy the timing info is". That's the problem with provable correctness: if you didn't think to consider things like "the time the function takes must be constant", you won't consider "the time the function takes" as an output and therefore won't notice the way wrong inputs with longer matching substrings take longer to produce an output; you'll just see that they still return false and call the code Correct.

7
  • 1
    Java isn't “aggressively” memory safe. It just replaces one unsafe way of handling memory with a garbage collector that's unsafe in other (nondeterministic) ways. — Your point about Turing completeness is relevant but also points at a solution: to avoid using the full Turing-complete language where it isn't needed (which is actually often the case), and instead write those parts in a more restricted eDSL where provable correctness is a much more realistic aim. Mar 9, 2021 at 15:31
  • 6
    You should perhaps qualify your statement that Java "exposes no pointers" -- Java is all pointers and really popularized the null pointer exception. (Hoare must be turning at his desk every time he looks at Java ;-). ) it's just that they aren't raw pointers, which is of course relevant to memory safety. Mar 9, 2021 at 17:15
  • Java has basically only pointers (and primitive values, which is just a performance-optimization on pointers to constant objects). The important thing is that it doesn't allow pointer arithmetic, or treating pointers of one type as pointers of another type (without checks). Mar 9, 2021 at 23:38
  • @PaŭloEbermann Do you know what the native Java keyword does? If not, look it up, and start worrying.
    – alephzero
    Mar 10, 2021 at 3:25
  • Turing-completeness is not necessarily something you want in a programming languages. Being Turing-complete means that you can write programs that you can not prove terminate. For any reasonable axiomatic theory you place yourself in, you can look at all programs that can be proven to terminate within this theory. All programs that you will ever want to write will be among those. (If you want "non-termination" to have an event loop, or a main loop that just repeats something, then what you really want is coinduction and not general non-terminaton)
    – xavierm02
    Mar 10, 2021 at 7:55
6

If you get really smart about it, this question turns into a categorisation problem which is unsolveable, because the categories are ill-defined. Is an SMTP client a data exfiltration zero day exploit? What if it's capable of sending a message containing private keyfiles? What if the recipient of that message is an authorised person? What if the receiving server is compromised?

Whether humans or machines are writing programs in future, the context in which they are used will always be the final determining factor in whether you want them to be running or not.

6

You have a wrong vision on software vulnerabilities. Buffer overflows happen because unwary C or C++ programmers can easily write poor code. And it is still very important to underscore that when teaching those languages, because by definition beginners have little experience and can fall in pitfalls.

But there are tons of other possible vulnerabilities unrelated to such low level details. SQL injections are possible whatever the language, and they have caused a lot of problems in web applications. When a programmer is in a hurry, it is very easy to fail to control a corner case, and in the end the authentication will gladly accept a specific forged password for any username - I have actually seen that...

So of course memory safe languages prevent programmers from falling into the buffer overflow pitfall, but tons of other traps are still there. The only way for secure programming is best practices, tests and reviews, whatever the language. It takes time and—because of that—costs money, but I have never found a better way.

1

Use Mature Code Whenever Possible

Memory Safe Programming Languages only protect against certain kinds of vulnerabilities, but since the OP is asking about programming paradigms that universally reduce all kinds of vulnerabilities I would say that any style of modular programming that allows you to use mature code in place of writing your own solutions from scratch fits this description. (dependencies, plugins, libraries, frameworks, CMSs, etc...)

In a general since, all Zero Day vulnerabilities are the result of poorly tested code, and any code that you've just written is by definition poorly tested. So, the most likely code to have a vulnerability is the stuff you are writing right now. In contrast, code that has been in the wild for years, used on a lot of different projects, and attacked by a lot of different hackers, and is well enough supported to have already been cleaned up is much less likely to contain any vulnerabilities worth exploiting, because those problems have already been Zero-Day'ed and resolved.

The past few decades have seen software become immensely more complex, theoretically giving programs hundreds of times more opportunities for vulnerabilities, yet the reuse of good code has made actually hacking modern software per attack surface much more difficult.

There is no guarantee that mature code will not have its vulnerabilities, and there will always be some new code in every project which risks new vulnerabilities, but code reuse has been a huge factor in improving modern cyber security.

Not the answer you're looking for? Browse other questions tagged or ask your own question.