Hacker News new | ask | show | jobs
by rakoo 3980 days ago
> If you build your static binary today and tomorrow there is a vulnerability in your libssl dependency of choice, don't you now have to recompile and redistribute a new binary

Technically there's only one ssl library you should use, it's the standard one. This doesn't change your overall point that when a part of the program must be upgraded, the whole binary must be upgraded as well and re-deployed, which I totally agree with. If your software is a server that you host yourself and you have full control over the deployment chain, as is the mindset behind Go, then re-deploying a dependency or re-deploying a binary is more or less the same.

Regarding logging, I'm really partial to the approach advertised by 12 factors (http://12factor.net/logs): let your software handle the business, let the supervisor handle the software's lifecycle, and handle the logfiles outside of the software, because there are factors specific to the hosting machine that in my opinion shouldn't be the concern of the software.

1 comments

Not sure what you mean by standard ssl library. There's OpenSSL, GNU TLS, LibreSSL, and a number of other implementations. If you mean the one that comes with the OS, then wouldn't that by dynamic linking?

Re-deploying the binary, and getting the updated binary are two different things. When OpenSSL has a vulnerability, they announce it after the fix is out, distro maintainers release updated packages, I run `apt-get update && apt-get upgrade` or equivalent. It is on OpenSSL and the distro maintainers to release the update, and on me to apply the update.

When we are talking about static linking, it's now suddenly on the software developer to release a new binary or on me to rebuild the binary I have from source. Now I have to keep track of (a) which dependencies each project uses, and (b) which vulnerabilities come out. Not being familiar with Go, does it have such dependency tracking framework where I can update all packages where dependencies have been updated? Of course once I know that I need to perform an update, it doesn't matter if I run `apt-get upgrade` or `go build foo`.

As for logging, I advocate doing both. I really should make a separate blog post about it, but here's what I expect a well-behaved daemon to do:

- Always start in foreground and log to stdout (otherwise it seems like it exited without any output)

- Use the -d and --daemon flags to go into background

- Use the -p and --pid options for specifying the PID file

- Use the -l and --log options for specifying the log file location (if not specified or is - use stdout)

- If uses a config file, use -c or --config for the location of the configuration file. Default to standard OS location.

This way all possible modes are supported (running under a supervisor process, in a container, as a stand-alone daemon, or in the foreground while in development/testing/experimentation), and it is very easy, even in C to write software that meets these requirements.

A bit of dynamic linking reading: http://harmful.cat-v.org/software/dynamic-linking/ http://harmful.cat-v.org/software/dynamic-linking/versioned-...

Dynamic linking is not synonymous with security or ease of use. It's known to significantly reduce performance, both when loading as well as runtime whenever an external symbol is used, and the memory savings aren't too great. One has to remember that static linking also means that unused symbols are left behind, as the compiler knows what is needed.

Ability to update components of an application is one of the ideas behind dynamic linking, but in practice it doesn't work that well, and often requires that the application is updated to link against a newer version, which of course can only happen when the distro updates. This also includes LTS distros and backports, which you either have to wait for or kill support for.

There's also a difference in how Go vs. things like C and C++ handles linking, due to Go actually knowing about multiple files, and who uses what. This is quite a bit different than the copy-pasta that C preprocessors generate. We just ported a major product from C++ to Go, which gave us significant performance boost with considerably less code and complexity (This is not a "Go is better than C++", Go just provided a lot of things we needed in the std library that were hard to get in C++). The "necessities" were dynamically linked (libstdc++, libgcc_s, pthread, libc, ...), but our own libs were statically linked in. The binary was 51MB. The equivalent, completely statically linked Go binary is 8MB. It also does a clean build in <500ms, rather than the 3-5 minutes it took for the C++ project.

... But do remember, that Go 1.5 brings dynamic linking for the ones that want it. While the Go creators don't seem super fond of dynamic linking, they are providing it.

Regarding updating, "go get -u packagename" will update all dependencies, assuming the dependencies are go-gettable. Vendoring changes the picture a bit, in the sense that applications will bundle their own version of things to simplify things a bit, but that doesn't really change the go get -u command. Follow with a go build, and your application is up to date.

Just to clarify, I am not saying anything bad against Go. It seems like a strict improvement over C++ in a lot of ways. I am simply arguing that in the world where distros are made up of third party software and are maintained by a small team of distro developers/maintainers, dynamic linking is better than static linking. It's not synonymous with security, nor is it the panacea of performance or memory saving (both of which I personally care less about than flexibility). It is simply more convenient from the point of view of a distro maintainer, and because of that the end user.
I understand, I just don't see where it is an improvement.

My rant is mainly triggered by dynamic linking being the standard without many people questioning the usability. It rarely it works as intended, especially with versioned symbols. If I depend on openssl, and an update comes in, then one of 3 things can have happened: 1. They updated the old symbols 2. They implemented new symbols, but left the old ones behind 3. They implemented new symbols, killing the old ones.

1. means that the behaviour of the library under my application changes, which can lead to unexpected bugs. 2. means that my application is not experiencing the fix at all. 3. means that my application won't start anymore, due to dyld errors. 3 is what happens when you update something to a new version in the normal manner.

This "multi-version" mess also makes the libs more complicated than they're supposed to be. My ubuntu libc, for example, includes symbols from 2.2.5, 2.3, 2.3.2, 2.4, 2.5, 2.7, 2.8, 2.11, and 2.15, just to check the very last symbols. It's a mess.

For users, it's mainly a headache when trying to get newer versions of packages that depend on newer libs. This isn't much of an issue if you're, say, a gentoo or arch linux user, but if you're maintaining Debian systems, and need a package from a newer repo but can't/don't want to dist-upgrade to testing/experimental, then you're practically screwed short of compiling things yourself.

For distro maintainers, it's a mess as all packages depending on the lib for that distro release needs to be recompiled when releasing a new version of the lib, which is a lot of work.

Dynamic linking and versioned symbols is also the very reason that causes sites with binary releases to have a binary for Windows, a binary for OS X, a binary for Ubuntu 14.04, a binary for Red hat 6.3, a binary for Arch Linux, a binary for..., further increasing the inconvenience for the user.

The only time you have benefits from dynamic linking is in the very rare scenario 1 of updated libraries, where everything is done exactly right when modifying old symbols so nothing breaks, which is a bit unlikely unless the bug fix was very simple. It also has to be serious enough that the library maintainers see the need to backport the bugfix to the old library versions, rather than release a new one. Otherwise, it's only dragging things down, both in performance, resource consumption and maintenance overhead.