[HN Gopher] Writing secure Go code
       ___________________________________________________________________
        
       Writing secure Go code
        
       Author : gus_leonel
       Score  : 162 points
       Date   : 2024-11-04 17:34 UTC (5 hours ago)
        
 (HTM) web link (jarosz.dev)
 (TXT) w3m dump (jarosz.dev)
        
       | gus_leonel wrote:
       | TIL about `gosec`.
        
       | goodlinks wrote:
       | Does go have a bad security reputation?
       | 
       | I get that anything can be insecure and its a constant battle as
       | this article suggests, but i thought it was quite secure and
       | stable generally (say on a par with .net or any other tool you
       | may use to make a web app at least?)
        
         | wbl wrote:
         | You can write SQL injection in any language.
        
         | jiehong wrote:
         | You can use outdated dependencies in any language.
        
         | valbaca wrote:
         | > i thought it was quite secure and stable generally
         | 
         | It is, but security isn't a "given" anywhere. XSS, SQL
         | Injection, Dependency etc can be done by any language,
         | regardless of how "secure" it claims to be.
         | 
         | The headings are all pretty general (versioning, tooling,
         | scanning, testing) but the contents are Go-specific.
         | 
         | It's a pretty good article IMO and could/should be replicated
         | for other languages as well.
        
         | jerf wrote:
         | No.
         | 
         | Ironically, a flip side of the complaints about how Go lacks
         | power is that a lot of the "standard" security vulnerabilities
         | actually become harder to write. The most obvious one is
         | lacking the "eval" that a dynamic language has; more subtle
         | ones include things like, there is _no way_ to take a string
         | and look up a type or a method in the runtime, so things like
         | the Ruby YAML vuln are not assisted by the language level. To
         | write something like that into Go, you 'd have to actually
         | _write it in_. Though you can, if you try hard enoough.
         | 
         | But, as sibling comments point out, nothing stops you from
         | writing an SQL injection. Command injections are inhibited by
         | the command only taking the "array of strings" form of a
         | command, with no "just pass me a string and we'll do shell
         | things to it" provided by the language, but I've dispatched
         | multiple questions about how to run commands correctly in Go by
         | programmers who managed to find []string{"bash", "-c", "my
         | command with user input from the web here"}, so the evidence
         | suggests this is still plenty easy enough to write. Putting the
         | wrong perms or no perms on your resources is as easy as
         | anything else; no special support for internal security
         | (compare with E lang and capabilities languages). And the file
         | access is still based on file names rather than inodes, so
         | file-based TOCTOUs are the default in Go (just like pretty much
         | everywhere else) if you aren't careful. It comes with no
         | special DOS protection or integrated WAF or anything else. You
         | can still store passwords directly in databases, or as their
         | MD5 sums. The default HTML templating system is fairly safe but
         | you can still concatenate strings outside of the template
         | system and ship them out over an HTTP connection in bad ways.
         | Not every race condition is automatically a security
         | vulnerability, but you can certainly write race conditions in
         | Go that could be security vulnerabilities.
         | 
         | I'd say Go largely lacks the footguns some other languages
         | have, but it still provides you plenty of knives you can stab
         | yourself with and it won't stop you.
         | 
         | I've been running govulncheck against my repos for a while, and
         | I have seen some real vulnerabilities go by that could have
         | affected my code, but rather than "get arbitrary execution"
         | they tend to be "didn't correctly escape output in some
         | particular edge case", which in the right circumstances can
         | still be serious, but is still at least _less_ concerning than
         | "gets arbitrary execution".
        
           | Smaug123 wrote:
           | > I'd say Go largely lacks the footguns some other languages
           | have
           | 
           | With the glaring exception of "I forgot to check the error
           | code", which you need a linter (e.g. as provided by golangci-
           | lint) for. It's critically important for security that you
           | know whether the function you just called gave you a
           | meaningful result! Most other languages either have sum types
           | or exceptions.
        
             | tptacek wrote:
             | No it's not. This is what I meant, cross-thread, when I
             | suggested being wary of arguments trying to draw
             | _significant_ distinctions between memory-safe-language X
             | and memory-safe-language Y. Error checking idioms and
             | affordances have profound implications for correctness and
             | for how you build and test code. Programmers have _strong_
             | preferences. But those implications have only incidental
             | connections to security, if any. Nevertheless  "security"
             | is a good claim to throw into a "my language is better"
             | argument.
        
               | Smaug123 wrote:
               | I don't even use Golang, I maybe read two Golang repos a
               | year, I find these errors in almost every repo I look at
               | (probably because of the selection effect: I only look at
               | the code for tools I find bugs in). One of them I
               | remember was a critical vulnerability of exactly this
               | form, so :shrug: Perhaps I'm just grotesquely unlucky in
               | the Golang projects I see, but that makes maybe 10% of
               | the Golang error-handling bugs I've found to be security
               | bugs.
        
               | tptacek wrote:
               | Sounds memorable. Say more about this critical
               | vulnerability?
        
               | Smaug123 wrote:
               | I'll gesture at it. It's not an open source tool, so I
               | can't point at the code (and in fact I just checked and I
               | don't have perms to see the Jira ticket I caused to be
               | raised!), and I am wary of describing security bugs in
               | company-internal code. But in general terms it was a
               | service that attempted to check whether a request was
               | allowed, and it ignored errors from that check. (I just
               | searched history for a bit to find the error in the
               | absence of any actual details about it, but it was a
               | while ago and I failed.) Sorry this is not a very
               | satisfying answer.
        
             | arccy wrote:
             | as if DoS by exception is any better...
        
               | Smaug123 wrote:
               | Depends on the application! There's a reason we have the
               | concept of "failing closed" vs "failing open": sometimes
               | (very often, in fact) it's correct to shut down under
               | attack, rather than to open up under attack.
        
               | tptacek wrote:
               | The subtext of that comment cuts against the argument
               | you're trying to make here: a panic following a missed
               | error check is always fail-closed, but exception recovery
               | is not.
        
             | jerf wrote:
             | Mmm, that's fair. I tend to forget about it because it's
             | not something I personally struggle with but that doesn't
             | mean it's not a problem.
             | 
             | I'd still rate it well below a string eval or a default
             | shell interface that takes strings and treats them like
             | shell does. You assert down below that you've seen this
             | lead to a critical vulnerability and I believe you, but in
             | general what happens if you forget to check errors is that
             | sooner or later you get a panic or something else that goes
             | so far off the rails that your program crashes, not that
             | you get privs you shouldn't. As I say in another comment,
             | any sort of confusing bit of code in any language _could_
             | be the linchpin of some specific security vulnerability,
             | but there are still capabilities that lead to more security
             | issues than some other capabilities. Compared to what I 've
             | seen in languages like Perl this is still only "medium-
             | grade" at best.
             | 
             | And I'm not trying to "defend" Go, which is part of why I
             | gave the laundry list of issues it still has. It's just a
             | matter of perspective; even missing the odd error check
             | here or there is just not the same caliber problem as an
             | environment where people casually blast user-sourced input
             | out to shell because the language makes it easier than
             | doing it right.
             | 
             | (Independent of language I consider code that looks like
             | operation = determineOperation()         if
             | !canIDoOperation(operation) {             // handle
             | failures         }         doOperation(operation)
             | 
             | architecturally broken anyhow. It seems natural code to
             | write, but this is a form of default allow. If you forget
             | to check the operation in one place, or even perhaps forget
             | to write a _return_ in the if clause, the operation
             | proceeds anyhow. You need to write some structure where
             | operations can 't be reached without a positive affirmation
             | that it is allowed. I'd bet the code that was broken due to
             | failing to check an error amounted to this in the end.
             | (Edit: Oh, I see you did say that.) And, like I said, this
             | is independent of Go; other than the capabilities-based
             | languages this code can be written in pretty much
             | anything.)
        
               | tptacek wrote:
               | I think it's a reasonable observation but it isn't a fair
               | comparative security criteria. The subtext behind error
               | checking critiques is that languages with idiomatic sum
               | type returns avoid authz vulnerabilities, in the same way
               | that memory-safety in Go eliminates UAF vulnerabilities.
               | But authz vulnerabilities are endemic to the mainstream
               | sum type languages, too; they're much more complicated as
               | a bug class than just "am I forced to check return codes
               | before using return values".
               | 
               | Sum types are one of the few things I miss when switching
               | from other languages back to Go. I like them a lot. But I
               | think they're wildly overstated as a security feature.
               | Sum type languages have external tooling projects to spot
               | authz vulnerabilities!
        
           | fweimer wrote:
           | One thing to note about data races in Go is that the safe Go
           | subset is only memory-safe if you do not have data races. The
           | original post alludes to that because it mentions the race
           | detector. This situation is different from Java where the
           | expected effect of data races on memory safety is bounded
           | (originally due to the sandbox, now bounded effects are more
           | of QoI aspect). Data races in Java are still bad, and your
           | code may go into infinite loops if you have them (among other
           | things), but they won't turn a long into an object reference.
           | 
           | The good news is that the Go implementation can be changed to
           | handle data races more gracefully, with some additional run-
           | time overhead and some increase in compiler complexity and
           | run-time library complexity, but without language changes. I
           | expect this to happen eventually, once someone manages to get
           | code execution through a data race in a high-profile Go
           | application and publishes the results.
        
             | tptacek wrote:
             | These arguments would be more compelling if they came with
             | actual exploitable vulnerabilities --- in shipped code,
             | with real threat models --- demonstrating them, but of
             | course the lived experience of professional programmers is
             | that non-contrived Go memory safety vulnerabilities are so
             | rare as to be practically nonexistent.
        
           | cyberax wrote:
           | > I'd say Go largely lacks the footguns some other languages
           | have
           | 
           | It does have a couple of its own. Like
           | ((*SomeStruct)(nil)).(SomeInterface) != nil.
           | 
           | And yeah, the error handling is fucked up.
        
             | jerf wrote:
             | I was referring specifically to security footguns like
             | having a string eval. While one can construct code in which
             | that is the critical error that led to a security
             | vulnerability, that can be said about any confusing bit of
             | code in any language, and I would not judge that to
             | _especially_ lead to security issues.
        
               | cyberax wrote:
               | This actually is a security footgun. In Java or C# you
               | can't get security issues by trying to update a reference
               | from multiple threads, because it's always atomic. In Go
               | you can create type confusion because interface pointer
               | updates are not atomic.
        
               | jerf wrote:
               | This sets the bar ludicrously low for "security footgun".
               | If this is a "security footgun" then what is string
               | evaluation in a dynamic scripting language, a "security
               | foot-nuke"?
               | 
               | Granted, there is no sharp line that can be drawn, but
               | given my personal career I'd say _I 've encountered it
               | personally at least once_ is a reasonable bar, if not
               | quite excessively low. (tptacek would have to set the bar
               | somewhere else, given his career.) Concurrency issues
               | causing a security issue because of type confusion on an
               | interface in a Go program is not a "every time I crack
               | open a program, oi, this security vulnerability again"
               | like bad HTML escaping or passing things straight to a
               | shell. I mean, "concurrency issues causing type confusion
               | on an interface" is _already_ not something I 've ever
               | personally witnessed, _let alone_ it actually being a
               | security issue rather than a difficult-to-trace panic
               | issue.
               | 
               | And I will reiterate, I _already_ say that _any_ bug can
               | become a security issue in the right context. That doesn
               | 't make them all "security footguns".
        
               | cyberax wrote:
               | > This sets the bar ludicrously low for "security
               | footgun". If this is a "security footgun" then what is
               | string evaluation in a dynamic scripting language, a
               | "security foot-nuke"?
               | 
               | Not really. Apart from dangerous serialization formats
               | (e.g. Python's "pickle") it's not at all easy to eval a
               | string in modern scripting languages.
               | 
               | String evals are also not widely used anymore.
        
               | tptacek wrote:
               | Point to a real, exploitable, public vulnerability that
               | exploits this behavior, and then we'll all be talking
               | about the same thing.
        
           | tgv wrote:
           | About footguns, I'd like to mention an important one: in Go,
           | it's hard to deserialize data wrongly. It's not like python
           | and typescript where you declare your input data to be one
           | thing, and then receive something else. It's a feature that
           | makes server code, which after all is Go's niche,
           | considerably more reliable.
           | 
           | Safety isn't 0% or 100%, and the more a language offers, the
           | better the result. Go is performant, safe, and fairly easy to
           | read and write. What else do you need (in 99.9% of the
           | cases)?
        
             | js2 wrote:
             | > It's not like python and typescript where you declare
             | your input data to be one thing, and then receive something
             | else
             | 
             | In Python that's likely to lead to a runtime TypeError, not
             | so much in TS since at runtime it's JS and JS is weakly
             | typed.
             | 
             | Besides, Python has Pydantic which everyone should really
             | should be using. :-)
        
         | perryh2 wrote:
         | > Does go have a bad security reputation?
         | 
         | Depends on who's behind the keyboard.
        
         | tptacek wrote:
         | 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.
        
           | quietbritishjim wrote:
           | 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).
        
             | kibwen wrote:
             | 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 wrote:
               | 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.
        
             | plorkyeran wrote:
             | 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 wrote:
           | _> 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.
        
           | pants2 wrote:
           | 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
        
             | tptacek wrote:
             | For what it's worth, I don't believe there's any meaningful
             | security difference between Rust and Go.
        
       | pram wrote:
       | I've been maintaining a Go app for about 9 years now and I can
       | just upgrade the Go version + mod for vulnerabilities (GitHub
       | tells me about them automatically idk) and it works with no
       | changes 99% of the time. I can't overstate how this makes
       | maintaining it very stress-free.
       | 
       | My JS apps on the other hand...
        
         | valbaca wrote:
         | I shudder to think the amount of thousands of engineering hours
         | are spent in my FAANG to keep our Java services just running
         | as-is with updates.
         | 
         | And now we're moving more to Typescript on Node...UGH.
        
           | adhamsalama wrote:
           | I thought Java was robust. What's the hassle?
        
             | coredog64 wrote:
             | Not OP, but typically Spring and transitive dependencies.
             | Some package that you don't even use is pulled in and has a
             | CVE. Or you upgrade major Spring versions and the API
             | changes underneath you.
        
               | koito17 wrote:
               | Have people considered frameworks implementing JAX-RS
               | instead? Or does the breakage happen specifically in
               | extensions to Spring?
               | 
               | The only inconvenience I have experienced upgrading a
               | Quarkus backend is renaming javax.* package imports to
               | jakarta.*. Hopefully the next major version requires just
               | as little effort (if not less).
               | 
               | I am sure there would have been a lot more work if the
               | project used extensions like the Kubernetes client. But
               | overall, I have had the best experience with tools like
               | Maven (for Java) and Leiningen (for Clojure). It helps to
               | avoid libraries that hack or access JDK internals (e.g.
               | Lombok, or ancient libraries using sun.* internal
               | packages for reflection)
        
               | vbezhenar wrote:
               | The main problem is Spring Boot and some other Spring
               | projects like Security.
               | 
               | If you would use Spring MVC directly, it is very possible
               | that one could upgrade Spring versions for many years
               | with minimal or no changes at all.
               | 
               | However Spring Boot regularly breaks code. And given the
               | fact that it's very popular, it means that any Java
               | upgrade is pain. You need to rewrite code, sometimes a
               | lot of code.
               | 
               | If you just use JAX-RS, probably simple Spring setup
               | would suffice, but people usually want to slap database,
               | security stuff and other things and everything is
               | provided by Spring, so it's not apples-to-apples
               | comparison.
        
               | okeuro49 wrote:
               | I recommend Spring Boot, it provides a "blessed" set of
               | dependencies that work with each other. When you want to
               | upgrade, you just need to increase one version (the
               | Spring Boot version).
        
               | blibble wrote:
               | if you don't pull in 50 jars to replace the "new" keyword
               | (aka spring DI), then this ceases to be a problem
        
             | cyberax wrote:
             | Java is fairly robust and plenty of libraries are very low-
             | velocity.
             | 
             | JVM itself, however, has had several breaking changes
             | recently. So a lot of organizations are stuck on an ancient
             | version of the language.
        
               | akdev1l wrote:
               | Not really true imo.
               | 
               | I speak from the experience of supervised the upgrade of
               | thousands of services from JDK8 to JDK17
               | 
               | There's few quirks added but:
               | 
               | 1. JDK17 will happily run JDK8 code without any changes
               | 2. Most of the issues I observed were due to project
               | jigsaw (and were resolved by adding --add-opens as
               | needed)
               | 
               | I would expect 17 > 21 upgrade to have basically no
               | issues as an upgrade in place
               | 
               | I hate Java but backwards compatibility isn't one of the
               | reasons why I hate it
        
               | cyberax wrote:
               | This unfortunately is not true for large codebases. The
               | language and the basic library are extremely stable, but
               | the overall runtime is not. So the 8->17 switch resulted
               | in lots and lots of regressions.
               | 
               | So companies either pay Oracle to maintain the old JDK8,
               | or use something like Amazon Corretto. It's so bad that
               | there are companies promising JDK8 support until 2031 at
               | least.
               | 
               | And yeah, upgrades past 17 are easy.
        
               | kaba0 wrote:
               | > It's so bad that there are companies promising JDK8
               | 
               | Come on, that's absolutely not the reason behind. That
               | just means that there are banks and such that still run
               | goddamn windows XP completely firewalled off from the
               | internet just because. Similarly, for some companies not
               | touching that ancient codebase and just having it safely
               | run worth the hassle and the money.
               | 
               | Java is the most backwards compatible language and it is
               | not even a close competition.
        
             | alex-nt wrote:
             | I've been working with Java for the last decade and for the
             | past 5Y used the latest LTS versions in a very regulated
             | environment (we have very strict patch deadlines for most
             | CVEs). Rarely we hit issues with migrating to different
             | versions of our dependencies. The most painful one was a
             | small API change in Spring that revealed that we were doing
             | something _very bad_ so it took me 1-2D in between meetings
             | to investigate. It is true though that every few weeks we
             | are hit by a new CVE and we have to patch a lib version,
             | but TBH this is what I expect from a language that has so
             | many eyes on it 's ecosystem.
        
         | candiddevmike wrote:
         | You can run govulncheck as part of your CI pipeline too
        
         | stouset wrote:
         | > GitHub tells me about them automatically idk
         | 
         | GitHub tells you about _published CVEs_ which represent a small
         | fraction of actual patched security vulnerabilities in the
         | wild, which typically never get a CVE.
        
         | dakiol wrote:
         | Don't get it. Is it because your Go app relies in fewer
         | dependencies? If so, it's just a matter of numbers I believe.
         | JS apps tend to rely on more dependencies on average... but
         | that doesn't need to be that way. I have plain JS apps that
         | still work like the first day (even better than Go apps, since
         | there's no compilation step involved).
         | 
         | TypeScript apps on the other hand, yeah, they tend to be more
         | fragile (at least from my perspective: the tsc package has
         | dozen of dependencies, so anything can go wrong)
        
           | hnlmorg wrote:
           | You can do that in practically any language however that
           | doesn't mean it's easy nor the norm.
           | 
           | JavaScript has a culture of move fast and break things.
           | Whereas Go has a culture of moving slow and backwards
           | compatibility.
           | 
           | It also helps that Go has a pretty extensive stdlibs whereas
           | JavaScript is really more like several distinct language
           | ecosystems wrapped around a common specification. So what
           | works on one JavaScript runtime might not even work on
           | another.
        
         | dilap wrote:
         | The very first Go code I ever wrote, way back in 2011, still
         | compiles and runs perfectly. It's glorious.
        
         | rollulus wrote:
         | My few tiny steps in JS world were alienating in that sense:
         | having a brand new install of all tools, doing a "npx create-
         | react-app" and got greeted with "congrats, your app is
         | initialised, it has 13 vulnerable dependencies".
        
           | hombre_fatal wrote:
           | Tbf those are development deps rather than production server
           | deps, and the vuln will be something like "DOS possible if
           | you let users craft their own regex string as input to
           | lib.foo(re) in a server ctx" rather than "by using this in
           | development to build your static js app, people get remote
           | access to your dev machine."
        
             | robertlagrant wrote:
             | It is a bit silly then that it reports them as
             | vulnerabilities by default.
        
           | vdvsvwvwvwvwv wrote:
           | Worse CRA goes from saviour to deprecated, "use nextjs or
           | vite instead" in a blink. Meta should maintain it. Nextjs
           | will probably morph again in the future so you hope investing
           | in learning vite is the answer. JS has this way.
           | 
           | Meanwhile Rails is so old it is thinking it needs to find a
           | partner, settle down and buy a picket fenced house.
        
         | Quekid5 wrote:
         | If JS apps are the standard you measure against you'll be happy
         | with most things.
        
         | spmurrayzzz wrote:
         | I've had similar experiences, but I've noticed my Node.js
         | applications which have few-to-no dependencies behave in the
         | same way as my Go apps in that regard. I might get some
         | deprecation logs from Node letting me know about future
         | changes, but generally they do just work. The apps with a heavy
         | dependency graph are a different story however.
         | 
         | This is still a feather in Go's cap given the fact that the
         | standard library offers so much out of the box. I don't find
         | myself reaching for dependencies that often.
        
       | tapirl wrote:
       | Please note, currently, there are no tools to detect the new
       | footguns created by the new semantics of 3-clause "for;;" loops:
       | https://github.com/golang/go/issues/66156
       | 
       | > The second step is to keep the Go versions in our projects
       | current. Even though we don't use the latest and greatest
       | language features, bumping the Go version gives us all security
       | patches for discovered vulnerabilities.
       | 
       | It is not always a good strategy to use the latest toolchain
       | version. There are often some fresh bugs in it. From the security
       | perspective, it is better to use the previous version, which is
       | also still being maintained.
        
         | arccy wrote:
         | you're literally the only person who's making a mountain out of
         | a molehill coming up with ever more convoluted code to "prove"
         | the change was a bad thing.
        
           | tapirl wrote:
           | It indeed is. Please read
           | https://go101.org/blog/2024-03-01-for-loop-semantic-
           | changes-... and https://github.com/golang/go/issues/66156
        
             | arccy wrote:
             | a lot of words and yet no real world issues identified
        
               | tapirl wrote:
               | The problems are right there. But some people choose to
               | turn a blind eye.
        
           | candiddevmike wrote:
           | .0 is for local development, .1+ is for production
        
             | tapirl wrote:
             | There might be some bugs in .2:
             | https://github.com/golang/go/issues/70035
        
         | superb_dev wrote:
         | The examples in that ticket are convoluted, who would write
         | code like that? Has this issue been spotted in the wild?
         | 
         | I agree that there is some issue and a lint should probably
         | warn you about these, but I doubt a lot of people will run into
         | it.
        
         | 01119288523 wrote:
         | https://docs.google.com/document/d/1Cb282k7rrSi4awr0HizuRciX...
        
       | rollulus wrote:
       | As the article also mentions: instead of checking if your program
       | has a dependency on something that contains vulnerabilities,
       | govulncheck checks if vulnerable code is actually reached. I find
       | that so awesome. (And I know, someone is going to point out that
       | hipster language foo does this too and better -- it's not the
       | norm).
        
       | dakiol wrote:
       | Go is nice, but the recent trend of using generics for many stuff
       | is making harder and harder to keep Go code readable imho. See an
       | example here https://eli.thegreenplace.net/2024/ranging-over-
       | functions-in...
       | 
       | I'm not saying it's hard to read, but it's harder than previous
       | Go code that used little or no generics at all.
        
         | mervz wrote:
         | Meanwhile, error handling still can't get any sort of syntactic
         | sugar
        
           | divan wrote:
           | As with real sugar, we humans don't have sensors that would
           | tell us when there's "too much sugar".
        
         | timmytokyo wrote:
         | Your example of go code that's harder to read is iterators, and
         | I agree with you. There's no denying that code like this places
         | a high cognitive load on the reader:                 func (al
         | *AssocList[K, V]) All() iter.Seq2[K, V] {         return
         | func(yield func(K, V) bool) {           for _, p := range
         | al.lst {             if !yield(p.key, p.value) {
         | return             }           }         }       }
         | 
         | But the code that actually _uses_ iterators is in my opinion
         | more readable than its non-generic counterpart. So it 's really
         | a question of how often you're expected to write (or read)
         | iterators. And I don't expect that most programmers will be
         | writing (or reading) iterators that often.
        
           | chamomeal wrote:
           | I often feel this way about heavy use of typescript generics.
           | The more you lean into the crazy (and awesome) world of
           | generics, the more inscrutable the code becomes to anybody
           | who isn't a generics wiz. It's really like an extra language
           | stacked on top of JS. I'll come back to code I wrote a year
           | ago, and it'll take me a full day to figure out the types.
           | 
           | But the simplicity of using a library or set of functions
           | that have really nice generics? So awesome. The intellisense
           | and type errors alone can almost be a decent form of
           | documentation.
           | 
           | The source becomes hard and weird to change, but the end
           | result is a very nice DX
        
           | timmytokyo wrote:
           | On further reflection, I think what makes this example
           | particularly difficult to understand is not so much its use
           | of generics, but the way it uses functions. It's a function
           | that returns a function that takes another function as an
           | argument. The generic [K,V] type arguments are actually
           | pretty straightforward.
        
         | Arainach wrote:
         | I'm curious about your objection to the proposal. Sure,
         | generics mean that libraries need a bit more syntax - that's
         | true in all languages - but the actual consumption of the
         | AssociationList type here is clean and readable.
         | 
         | Most types don't need to be generics. Containers do, and I
         | prefer a bit of generics syntax to copy/pasting the container
         | ten times for ten types.
        
         | zapnuk wrote:
         | Writing custom iterators always looked bad and overly
         | complicated.
         | 
         | If it's not essentials I'd rather not allow code like this in
         | my codebase and use some other solution that is more readable.
        
         | imiric wrote:
         | Indeed. I still try to avoid generics whenever possible, and
         | prefer a solution that doesn't use them. Thankfully, there
         | aren't many scenarios where they're absolutely indispensable.
        
           | kubb wrote:
           | Write a generic instantiator that scans your codebase for
           | generic usage, and makes one copy of a generic for every type
           | it's used with. Then you can remove all the generics and go
           | back to copy and paste.
        
       | wepple wrote:
       | Don't forget about capslock: https://github.com/google/capslock
       | 
       | Assess your 3P modules for dangerous capabilities
        
       | K0nserv wrote:
       | Somewhat related, I learned a surprising fact recently: Go is not
       | actually memory safe. In particular because atomicity is only
       | guaranteed for word size values, double word values(interface
       | pointers, slices) can introduce memory unsafety in the presence
       | of concurrency[0].
       | 
       | It's one of those things that feels obvious when you see it.
       | 
       | 0: https://blog.stalkr.net/2015/04/golang-data-races-to-
       | break-m...
        
         | tptacek wrote:
         | It's easy to demonstrate contrived abuses of Go concurrency
         | that break memory safety, but despite the enormous popularity
         | of the language, actual shipping vulnerabilities --- _mistakes_
         | in concurrency, not deliberately-engineered pathological cases,
         | that yield attacker-controlled control over memory --- are
         | basically nonexistent (I can 't think of a single one; there
         | must be one somewhere!).
         | 
         | Basically this is about as credible an argument as claiming
         | that Rust isn't memory safe because its libraries have so much
         | `unsafe` code. And that claim: not super credible.
         | 
         | Basically, the takeaway in both cases is that it's not safe to
         | allow an attacker to _write code for you_ in the language. But
         | everybody assumes that 's the case anyways, because it's the
         | case with virtually every other language (with one very
         | notable, fraught, and well-understood exception), too.
        
           | shitter wrote:
           | What is that exception?
        
             | tptacek wrote:
             | Browser Javascript.
        
               | pansa2 wrote:
               | Also embeddable scripting languages like Lua
        
           | ViewTrick1002 wrote:
           | Instead there's a whole host of subtle footguns which while
           | not leading to true memory unsafety will lead to complete
           | junk data.
           | 
           | https://www.uber.com/en-SE/blog/data-race-patterns-in-go/
        
             | tptacek wrote:
             | I don't care to litigate program correctness and
             | ergonomics. Those are extremely subjective, and I don't
             | feel like I ever get anywhere useful in those kinds of
             | conversations. The most popular backend programming
             | language in the industry is almost certainly Python, and it
             | barely even has types. I still wouldn't dunk on it.
             | 
             | This thread is about a much narrower question, which is
             | code security. There, I feel like I'm on much firmer ground
             | drawing and defending conclusions, and my conclusion is
             | that there isn't a mainstream general-purpose modern
             | language that is meaningfully more secure than Go (or than
             | Rust, or than Python, etc).
        
         | atomic128 wrote:
         | Here is code to circumvent Go's memory safety without importing
         | unsafe.
         | 
         | get() reads a byte at an arbitrary address and set() writes a
         | byte at an arbitrary address.
         | 
         | This is excerpted from BUGFIX 66 ("Hack This Site"):
         | func racer() {           var (               ptr1 *uintptr
         | ptr2 *byte               race any               done =
         | make(chan struct{})           )           put := func(x any) {
         | for {                   select {                   case <-done:
         | return                   default:                       race =
         | x                   }               }           }           go
         | put(ptr1)           go put(&ptr2)           for {
         | var ok bool               ptr1, ok = race.(*uintptr)
         | if ok && ptr1 != nil {                   close(done)
         | break               }           }           get := func(addr
         | uintptr) byte {               *ptr1 = addr               return
         | *ptr2           }           set := func(addr uintptr, to byte)
         | {               *ptr1 = addr               *ptr2 = to
         | }           if get(0xdeadbeef) == 111 {
         | set(0xbaaaaaad, 222)           }       }
        
           | tptacek wrote:
           | "Without importing unsafe" is doing a lot of work for
           | examples like this.
        
             | atomic128 wrote:
             | This comes from a webpage where the challenge is to
             | compromise the site, despite the fact that Go imports are
             | disallowed (including unsafe). It's a puzzle game.
             | 
             | To clarify, I think Go is magnificent and I use it for
             | everything. The racer() code is just a curiosity.
        
               | tptacek wrote:
               | Right, it's a cool trick. It's just not material to real
               | threat models, which is what people imply when they say
               | "Go isn't memory safe".
        
               | atomic128 wrote:
               | I agree.
        
               | tialaramex wrote:
               | The fact Go has UB under data races has practical
               | implications for sufficiently complex concurrent
               | software. If you can induce a race on a non-trivial
               | object, that's UB instantly - you can probably blow up
               | the Go runtime and all bets are off.
               | 
               | I would not characterise this fact, which is a design
               | choice in Go, as similar to say a Rust soundness bug,
               | which will sooner or later just get fixed. They aren't
               | going to somehow magically fix this problem in Go, it's
               | part of the design.
        
               | tptacek wrote:
               | My point has nothing to do with whether the language will
               | achieve "soundness". It's that this is behavior that has
               | not over the last 15 years produced exploitable
               | vulnerabilities, despite extremely high incentives for
               | those vulnerabilities to be unearthed.
        
               | pkolaczk wrote:
               | You don't need to blow up the runtime to cause a
               | vulnerability due to a data race in Go:
               | 
               | https://security.snyk.io/vuln/SNYK-
               | DEBIAN13-GOLANGGITHUBGORE...
        
               | arp242 wrote:
               | That's a completely different type of vulnerability than
               | the UB that's being talked about.
               | 
               | > The call to sync.Pool.Get will then return a
               | bytes.Buffer that hasn't had bytes.Buffer.Reset called on
               | it. This dirty buffer will contain the HTTP request body
               | from an unrelated request.
               | 
               | This is just a good ol' logic error, that just so happens
               | to also be a race.
        
               | arp242 wrote:
               | I think these are "if a tree falls in a forest and no one
               | is around to hear it, does it make a sound?"-type
               | musings.
               | 
               | Whatever the case, it doesn't really affect anyone and it
               | doesn't really matter.
        
               | pkolaczk wrote:
               | It's a matter of time. Spectre / meltdown were also
               | considered extremely hard to use in practice. Yet they
               | are considered vulnerabilities.
               | 
               | In Golang case the data race window to corrupt memory is
               | extremely narrow, so it makes it very hard to trigger it.
               | That together with Go being still quite a niche language
               | results in the fact we see no exploits... yet.
        
               | Yoric wrote:
               | Well, time will tell. As Go usage increases, it becomes a
               | more tempting target, which means that more malicious
               | third-parties will start poring over the code of the std
               | library and the frameworks looking exactly for this kind
               | of vulnerability.
               | 
               | The same goes for Rust, Swift or Zig, of course.
        
               | kaba0 wrote:
               | How is it not material? You only need to accidentally
               | write and read a map at the same time in language that is
               | supposedly for concurrency (which is why not the same as
               | parallelism, in its case it does largely correlate).
               | 
               | This is a ridiculous design issue with big ramifications.
        
         | ronsor wrote:
         | You shouldn't be modifying any variable concurrently without a
         | mutex. The only exception to this is if the variable is (1)
         | less than or equal to the CPU word size; (2) is at a CPU word
         | size aligned address; and (3) atomic memory access functions
         | are used to read and write the variable.
        
           | saghm wrote:
           | Memory safety as long as you don't violate certain rules is
           | what C and C++ also have. The problem is that programmers
           | make mistakes because we're all human.
        
             | tptacek wrote:
             | No, the "mistakes" we talk about with C/C++ are so common
             | that it's hard to think of a major C/C++ project not to
             | have them, and the "mistakes" we're talking about with Go
             | or "unsafe" Rust are contrivances built to demonstrate
             | things an actively malicious programmer could do. Equating
             | the two is, obviously, a sleight of hand.
        
               | klabb3 wrote:
               | To add to this: the built in go race detector is very
               | good at catching data races. It's a runtime, but I've
               | never had a race that couldn't be reproduced in the race
               | detector trivially.
               | 
               | But yes, in theory Go has a memory safety problem because
               | of it. In practice though, it's that people don't use the
               | race detector, which is ridiculously easy to do.
        
               | tptacek wrote:
               | Ordinary non-race-checked Go code is memory-safe in the
               | sense that we mean it in the field of software security.
        
               | kaba0 wrote:
               | It's only on Go, leave Rust out of it. Rust's safe part
               | is entirely memory safe. Unsafe is the escape hatch,
               | which pretty much every language has in the form of FFI.
        
             | throwaway894345 wrote:
             | > Memory safety as long as you don't violate certain rules
             | is what C and C++ also have
             | 
             | There are numbers between 0% and 100%, thus it's possible
             | that Go can be less than 100% memory safe _and_ still far
             | safer than C or C++.
        
               | tptacek wrote:
               | "100% memory safe" is mostly not a thing; it's not a
               | concept that gets quantified this way. The closest thing
               | I think you get to a compromised notion of safety that's
               | actually noteworthy is Zig's allocator behavior (which
               | can in ordinary code theoretically still produce UAF
               | bugs, and UAF bugs --- lifecycle bugs more generally ---
               | are the most pernicious kind of memory safety
               | vulnerability). Most practitioners would still call Zig
               | "memory safe". You can see how much bigger a deal that
               | behavior is than the one we're talking about.
               | 
               | I think the basic takeaway here is not to tie yourself up
               | in nots trying to quantify memory safety. There's a
               | reason Prossimo calls Go memory safe (not "mostly memory
               | safe"), along with Rust, C#, Java, Swift, Python, and
               | JavaScript. Ordinary code written in any of these
               | languages is just not going to have exploitable memory
               | corruption vulnerabilities. Other vulnerabilities, yes!
        
           | kiitos wrote:
           | Even when a value satisfies these architecture-dependent
           | requirements, the language still does not guarantee atomicity
           | of concurrent reads/writes, and programs which rely on that
           | assumption are buggy.
        
             | Yoric wrote:
             | Isn't this part of the Go memory model
             | (https://go.dev/ref/mem)?
        
             | kaba0 wrote:
             | Logic bugs != memory safety bugs.
             | 
             | E.g. in java you can mess up your logic with data races,
             | but the racing itself is safe and can never cause the VM to
             | enter an invalid state.
        
         | Thaxll wrote:
         | Go is memory safe by modern standard.
         | 
         | If I show you a UB in Rust without the use of unsafe does it
         | means Rust is unsafe?
        
           | Yoric wrote:
           | What does that mean?
           | 
           | If I follow correctly, assuming that there are no bugs in the
           | compilers/interpreters, Go is less memory-safe than Java, C#,
           | Python (with GIL), JavaScript or Rust. The only languages
           | that are less memory safe would be C, C++ or Zig.
        
           | K0nserv wrote:
           | I believe UB without unsafe is considered a bug by the Rust
           | language team.
           | 
           | I should've said in my original comment, but I don't mean to
           | dunk on Go. In practice the issues illustrated in the blog
           | post I linked seem unlikely to cause problems in practice,
           | they are interesting nevertheless.
        
           | kaba0 wrote:
           | That would mean it, yes. And yeah there is a bug in rust's
           | borrow checker which can trigger something like that for some
           | very special, "no human will ever write code like that" case.
           | But this is an implementation detail for a semantically
           | memory safe language, while in go's case having UB is a
           | language primitive here.
        
             | Thaxll wrote:
             | The trigger for Go is exactly "no human will ever write
             | code like that".
        
       | isomorphismism wrote:
       | ,,Delve" spotted
        
       | jjcm wrote:
       | Great tips in here - I was not aware of `go vet` nor `go test
       | -race`.
       | 
       | FWIW, while go is not memory safe, I do find that it's much
       | easier to be safe in go than it is in other languages. Its
       | verboseness lends to a very clear understanding of what's
       | happening in any given function. I absolutely hated this at the
       | start, but now ~3 years into maintaining a go codebase, I find it
       | quite nice both for debugging as well as editing old code. I know
       | exactly what each function does, and what the structure of data
       | is in any given context.
       | 
       | Another interesting side effect is that AI tools seem to work
       | amazingly well with golang, given how context is often local to
       | the function.
        
         | tptacek wrote:
         | Go is memory-safe. It's not the definition of "memory-safe
         | language" that it's impossible to write memory-unsafe code,
         | only that ordinary code is memory-safe by default.
        
       | tbiehn wrote:
       | Semgrep is another great option to get value out of static
       | analysis checks against both the language and a few common
       | frameworks. It remains a popular choice for security folks
       | writing static detection rules (and contributing them to the
       | commons).
       | 
       | You can check the open rules here;
       | https://github.com/semgrep/semgrep-rules/tree/develop/go
        
       ___________________________________________________________________
       (page generated 2024-11-04 23:00 UTC)