[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)