Comment by tptacek

Comment by tptacek 9 days ago

15 replies

It has essentially the same security properties of all the modern non-C-languages (ie, C, C++, ObjC), with the added bonus of largely being designed after the deserialization pandemic that especially hit Java, Python, and Ruby. ~All these modern languages are fine for security (though: be careful with serialization formats in anything but Go and Rust).

Arguably, Rust and Go are the two "most secure" mainstream languages, but in reality I don't think it much matters and that you're likely to have approximately the same issues shipping in Python as in Rust (ie: logic and systems programming issues, not language-level issues).

Be wary of anyone trying to claim that there are significant security differences between any of the "modern" or "high-level" languages. These threads inexorably trend towards language-warring.

pants2 9 days ago

I'd point out that one advantage Go has over Rust in terms of security are the coverage of standard libraries. Go has great support for HTTP clients/servers, cryptography primitives, SSH, SQL, JSON, secure RNG, etc. all in officially maintained standard libraries. The Rust ecosystem has some standards here but the most widely used HTTP client, just as an example, is mostly maintained by one guy[1]. I think that adds considerable security risk vs Go's net/http.

1. https://github.com/hyperium/hyper/graphs/contributors

  • TheDong 9 days ago

    My own experience is that the Go stdlib has resulted in worse security than, for example, rust.

    The reason for that is that both the Rust and Go stdlib have a stability promise, so anything built into them can't change if it's insecure.

    For example, the 'tar' package in go by default returns unsanitized paths, and has led to a bunch of CVEs: https://github.com/golang/go/issues/55356

    The go stdlib can't change the tar package to make it secure by default because it would be a breaking change to do so.

    Rust, on the other hand, has a tar package outside of the stdlib, and so it can evolve to be more secure and over time find a better interface.

    We've seen that with various other packages, where the Go stdlib HTTP implementation defaults to no timeouts, and thus makes it easy to DoS yourself. Ditto for tcp. The tls package has similar backwards compatibility warts that make it less secure by default.

    Forcing backwards compatibility with network protocols by baking them into the stdlib has largely not been a security win in my experience.

    You can argue that people can build packages outside of the Go stdlib too, like if the stdlib "image/draw" package is so bad it can't be used, they can make "golang.org/x/image/draw", or if the stdlib crypto package is bad, they can make "golang.org/x/crypto"... and they did, but people still reach for the stdlib because it's easier to, which makes it an active security trap.

    • tptacek 8 days ago

      No, I'm not going to give Rust security credit for vulnerabilities it avoided in library functionality that it simply didn't provide.

      • TheDong 8 days ago

        I'm not giving rust credit, I'm giving Go a demerit for having a large stdlib which it does not have a good path to evolve around security problems.

        We do have stuff like `golang.org/x/<etc>` and `rand/v2`, both of which people don't really use, which are I think clear indications that the go team screwed up here.

        Things like tls and http should have been separately versioned packages from the beginning, allowing infrequent breaking changes, and for users to update at their own pace independently of the compiler version.

        As-is, every time I update the go compiler, I also have to worry about setting a bunch of new GODEBUG flags (like 'x509sha1=1') to perform the compiler update without breaking stuff, and then separately deal with the breakages associated with those flags. Practically every go version in recent memory has had a breaking http or tls change which has caused issues for me.

        But of course they're all tied together, so to get a CVE fix in one package, I have to update the entire stdlib at once, so I have to accept some broken http change in order to fix a tls CVE or whatever.

        If tls were a separate package, I could update it separately from the compiler and http package and consume security updates more quickly, and also actually update my go compiler version without worrying about how much of my code will break.

        As I said, I'm not giving rust extra-credit, it did the reasonable normal thing of saying "the stdlib is for stuff we're pretty sure is actually stable", while go instead said "idk, will net.Dial ever need a timeout? Who knows, let's promise it's stable forever anyways" and "the default zero value for tls version should be 1.0 forever right", which I think deserves an obvious demerit.

  • tptacek 9 days ago

    For what it's worth, I don't believe there's any meaningful security difference between Rust and Go.

  • silverliver 9 days ago

    Good point. If you consider the size of your dependency graph as a risk, especially for languages that encourage large dependency graphs like JS and Rust, then Go has a very clear advantage.

kibwen 9 days ago

> Be wary of anyone trying to claim that there are significant security differences between any of the "modern" or "high-level" languages. These threads inexorably trend towards language-warring.

Hm, I think this is a reasonable take but taken too far. Presumably this out of a desire to avoid people arguing about this-language-feature vs. that-language-feature, but in practice "the language" also gets conflated with the tooling and the ecosystem for that language, and having good tooling and a good ecosystem actually does matter when it comes to security vulns in practice. Indeed, anyone can write SQL injection in any language, but having a culture of finding, reporting, and disseminating those vulnerabilities when they happen, and then having mature tooling to detect where those vulnerable packages are being used, and then having a responsive ecosystem where vulnerable packages get swiftly updated, those are all things that make for more secure languages in practice, even among languages with near-identical feature sets.

quietbritishjim 9 days ago

What is the "deserialisation pandemic"? It doesn't have obvious web search results, and I'm struggling to imagine what about deserialisation what be common between Java and Python (except that, in both cases, I'd surely just use protobuf if I wanted binary serialisation).

  • plorkyeran 9 days ago

    In the early 2000/2010s there was a popular idea that it'd be neat to have (de)serialization functionality that could perfectly roundtrip your language's native objects, without requiring that the objects be whatever the language uses as plain old data storage. In the happy case it worked super well and basically every language sufficiently dynamic to support it got a library which let you take some in memory objects, write them to disk, then restore them exactly as they were at some later time.

    This had the obvious-in-retrospect major problem that it meant that your deserialization was functionally equivalent to eval(), and if an attacker could ever control what you deserialized they could execute arbitrary code. Many programmers did not realize this and just plain called deserialization functions on untrusted data, and even when people did become aware that was bad it still turned lots of minor bugs into RCE bugs. It was often a long and painful migration away from insecure deserialization methods because of how darn convenient they were, so it continued to be a problem long after it was well understood that things like pickle were a bad idea.

  • kibwen 9 days ago

    See https://en.wikipedia.org/wiki/Log4Shell , but also historically the mess that is pickling/unpickling in Python (see the big scary warning at the top of https://docs.python.org/3/library/pickle.html#pickle-python-... ), and more broadly any dynamic language that exposes `eval` in any capacity.

    • tptacek 9 days ago

      For many years, these were the most widespread serverside RCE vulnerabilities; Rails YAML might be the best-known, but there were a bunch of different variants in Java serialization, and a whole cottage subfield of vulnerability research deriving different sequences of objects/methods to bounce deserializations through. It was a huge problem, and my perception is that it sort of bled into SSRF (now the scariest vulnerability you're likely to have serverside) via XML deserialization.

      • sn9 8 days ago

        You said that Go and Rust managed to avoid these issues. Is there anywhere I can read about how they avoided it? And why other popular modern languages can't?

innocentoldguy 9 days ago

Elixir is "more secure" than Go due to its isolated processes, functional processing, and immutable data.

  • tptacek 9 days ago

    Given the enormity of Elixir's runtime, that seems extremely unlikely. The kinds of bugs you expect to see in interpreted/VM code are different than those in compiled languages like Rust; someone is going to find memory corruption, for instance, when you index exactly the right weird offset off a binary, or do something weird with an auto-promoted bignum. We still find those kinds of bugs in mainstream interpreted languages built on memory-unsafe virtual machines and interpreters.

    I'm not saying Elixir is insecure; far from it. It's a memory-safe language. Just, it would be a weird language slapfight to pick with a compiled language.

    • innocentoldguy 8 days ago

      My comment isn't about compiled vs. bytecode languages. It's about memory management. For example:

      • In Elixir, each process runs in isolation, has its own heap, and prevents one process from directly accessing or corrupting the memory of another process. In contrast, Goroutines share the same address space, which means that a bug in one goroutine can potentially corrupt the shared memory and affect other code.

      • Elixir uses immutable data structures by default, so nothing can be changed in place. Go, on the other hand, allows mutable state, which can lead to race conditions if not managed correctly. In other words, Elixir is inherently thread safe and Go is not.

      • Elixir uses a generational garbage collector with per-process heaps, meaning that the garbage collection of one process can't impact another process. In contrast, Go uses a mark-sweep garbage collector across its entire memory space. This can cause global pauses that can open a window for denial-of-service attacks.

      • Elixir uses supervisor processes to monitor operational processes and restart them if they crash. Go's error handling can lead to memory leaks and other undefined behavior if not carefully managed.

      • Elixir inherently protects against race conditions, whereas Go relies on tools like the race detector and developer onus to avoid them.