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?

Nathan Aw
  • 1
  • 7
  • 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. – Steffen Ullrich Mar 09 '21 at 06: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 09 '21 at 06:53
  • 2
    For those two specific cases you mentioned, no. Neither were due to memory corruption vulnerabilities. – Xander Mar 09 '21 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 09 '21 at 16:56
  • 1
    How would a programming paradigm stop [this](https://xkcd.com/538/)? – OrangeDog Mar 09 '21 at 17:29
  • We won't be perfect, but measures can be taken to improve things. I also think that developers need to fight for longer development times and better QA. I love this talk by Uncle Bob about software quality in general: https://youtu.be/ecIWPzGEbFc – ljrk Mar 09 '21 at 18:58
  • So we are completely helpless against zero-day exploits? Only software vendors can help us? Also, those folks who want to close this question are uncomfortable with truth. – Nathan Aw Mar 10 '21 at 01:13
  • 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 '21 at 02:32
  • With strong type systems that allow writing very precise types (i.e. specifications) for your programs, you would have a chance at avoiding all or most of those. There is still potential for human mistakes when writing the specification but writing a correct specification is an order of magnitude easier than writing a correct and efficient program. An example of very expressive type system is the dependent types of Coq. – xavierm02 Mar 10 '21 at 07:48
  • @OrangeDog [`import antigravity`](https://xkcd.com/353/) should be enough to take you out of the range of any wrenches! – leftaroundabout Mar 10 '21 at 16:43
  • @Ghedipunk PHP was originally not even intended to be a programming language _at all_, let alone a safe one, so expecting _this_ to be a security improvement over C is really a bit hilarious. And using it as an argument to disprove that platform choice can “reduce the number of exploits in any meaningful way” is like using a long-haul truck vs a moped as an argument to “disprove” that four-stroke engines can be more environmentally friendly than two-stroke ones. – leftaroundabout Mar 10 '21 at 16:57
  • 1
    @leftaroundabout, I didn't mean to come across like I was saying that PHP is a security improvement over C (or Rust, as OP discussed). I am well aware of the history of PHP, including how its first three years of development had a vastly different set of priorities than the decades that followed. The language and core library of PHP does currently take security very seriously, but my point is that the language itself will not prevent my fellow developers from doing some incredibly stupid things. – Ghedipunk Mar 10 '21 at 17:12

6 Answers6

43

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.

ChocolateOverflow
  • 3,452
  • 4
  • 17
  • 34
  • 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. – leftaroundabout Mar 09 '21 at 15:17
  • @leftaroundabout - This answer is not misleading at all. Rust is MUCH newer than C/PHP so hackers haven't had time to break it. If Rust ever becomes as popular as C or PHP then it'll have tons of known security issues, just like them. There is no panacea programming language for security, speed, or anything else. As soon as one vulnerability is fixed hackers just find another. As JohnZhau said, security bugs are most often dumb mistakes by humans, no language can fix that. – sevensevens Mar 09 '21 at 22:30
  • 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 09 '21 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). – leftaroundabout Mar 09 '21 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 '21 at 01: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. – ChocolateOverflow Mar 10 '21 at 07:17
  • @sevensevens Your argument is flawed. Many of the most common, serious, and exploitable vulnerabilities are buffer overflows; something that isn't possible in many commonly used languages. The idea that all languages are the same in this aspect is just not true. – JimmyJames Mar 10 '21 at 21:50
  • "Security often depends more on usage of the language than the language implementation." I think this is highly debatable. It's kind of like arguing that good drivers don't get into accidents as much as bad drivers and therefore seat belts aren't relevant to safety. Proper design principles require making it harder to do something wrong than to to do it right e.g. asymmetrical power outlets. You are also comparing a flaw in design/implementation to language 'feature' which is apples to oranges. – JimmyJames Mar 10 '21 at 22:13
  • @JimmyJames Certainly, Rust makes memory issues much harder to happen with its memory-safe designs. However, AFAIK, Rust still allows memory-unsafe operations with certain features and libraries, and it'll be up to programmers to abuse such features. Safeguards a provided, but new issues can also be introduced. Comparing C to Python is like comparing apples to oranges; they both have their own design flaws, and programmers will eventually misuse features for some "hacky" solutions to their problems. Design safeguards can reduce some issues, but human stupidity will eventually overcome them. – ChocolateOverflow Mar 11 '21 at 02:23
  • @JohnZhau The way you are putting this makes it seem kind of like a binary but the reality is that language design affects both the quantity of vulnerabilities and the magnitude of their impact. This is not only theoretical; it's observable and measurable. – JimmyJames Mar 11 '21 at 14:36
  • I didn't mean anything binary. I'm saying that while language design can help reduce certain errors, but new errors can also pop up. I'm not an expert on language design so I can't say much about measuring errors but AFAIK, general bad practices and logic errors are prevalent across all languages, and those are more often the cause for bugs. It's much harder to detect logic bugs in source code analysis, but it's often easier to abuse than memory issues in my opinion. – ChocolateOverflow Mar 11 '21 at 15:11
  • @JohnZhau I get that the question is posed in a sort of all or nothing way but I think it's dangerous for people to get the idea that language design has no impact on security. I've found it surprising that memory errors can be so often exploited. The tragedy is that it is so easily resolved. And just because C allows buffer overflows doesn't make it immune to any other flaws. – JimmyJames Mar 12 '21 at 16:16
  • @JohnZhau To your point, remote code execution is a comparable problem and in my experience it is introduced often though features that don't consider security. These features are often added by 'experts'. It's not just skill, it's attitude. For example, custom XML entity definition is an extremely dangerous feature (as defined) that is implemented across many languages. What needs to change is the attitude that leads to the kinds of decisions to add inherently insecure features because they are (or might be) useful. Rust seems like a decent step in that direction. – JimmyJames Mar 12 '21 at 16:23
31

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:

  • 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%... – Matthieu M. Mar 09 '21 at 18:27
  • 4
    Don't let perfection get in the way of progress... – Tracy Cramer Mar 09 '21 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. – Paŭlo Ebermann Mar 09 '21 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. – Captain Man Mar 09 '21 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.

CBHacking
  • 40,303
  • 3
  • 74
  • 98
  • 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. – leftaroundabout Mar 09 '21 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. – Peter - Reinstate Monica Mar 09 '21 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). – Paŭlo Ebermann Mar 09 '21 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 '21 at 03: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 '21 at 07:55
  • I strongly disagree with the classification of Java reference variables as pointers. Yes, under the covers both are just addresses, but they don't have any of the properties that make pointers unsafe (except, yes, unexpected nulls leading to DoS). You can't cast them to incompatible types, can't instantiate them to arbitrary values, can't do math on them, can't (double)-free them, can't use-after-free them, etc. To the programmer, a *pointer* is a typed address, which can also be dereferenced. Java doesn't expose the address or let you do anything but assign or dereference the variable. – CBHacking Mar 10 '21 at 09:32
  • "The important thing is that it doesn't allow pointer arithmetic, or treating pointers of one type as pointers of another type (without checks). " and it also doesn't allow manual freeing of objects, so (assuming the VM isn't broken) the pointers can never be stale. – Peter Green Mar 18 '21 at 07:21
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.

Michael
  • 2,391
  • 2
  • 19
  • 36
Serge Ballesta
  • 25,636
  • 4
  • 42
  • 84
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.

Adam Barnes
  • 403
  • 2
  • 8
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.

Nosajimiki
  • 1,799
  • 6
  • 13