An introduction to deterministic builds with C/C++
blog.conan.io
blog.conan.io
I've looked more closely into Conan several times in the past and also added bug reports here and there, but it finally became somewhat usable (for me and my projects at least). Also, I'm quite picky when it comes to CMake integration, because I don't want my package manager to interfere with my build system configuration. Conan finally reached a state where one can simply check in a conanfile.txt in the repository and use the `cmake_paths` generator to fetch all dependencies before calling CMake to generate a project for you.
I also decided to setup my own Artifactory server as the official bintray comes with a max. size limitation of 500 MB per artifact which is way too low, considering that Qt5 with Core, Gui and Widgets statically built on Windows (release build) easily outgrows this limit. However, once you have your infrastructure setup, it's easy to add build flavors such as sanitizers etc. for a given set of thirdparty libraries.
I'm just curious.
I heard that Google has some tools internally that build the Python interpreter with Bazel and use that in order to guarantee hermeticity, but that doesn't seem to be possible with public tooling (at least not without some major hacks, for example https://github.com/bazelbuild/bazel/issues/4286 )
It would be interesting to see how Google manages languages such as Python at scale (and other languages that have similarly leaky package management).
Lots more info is here: https://reproducible-builds.org/
Ironically a few years later one of their customers had a multi hour outage and that was the end of (quite a large company) DSC.
Wouldn't a good and robust configuration management plan overcome this problem?
In some of the plans I have seen, some deviations are tolerated but those deviations are spelled out in excruciating detail.
How have you dealt with this issue?
If I'm debugging a production issue and want to create a debug build from the same source to step through things, it would be nice if I could just build both debug and release, check the hash on the release to confirm it matches production, then start debugging, without reference to any external records or other systems.
Reproducible builds reduce the number of links in the chain needed to verify what you're really doing.
...but what's really surprising is that it's surprising, since everybody is experienced with details in some context.
I mean, my response isn't entirely a rhetorical question. If there's a reason it wouldn't work I'd be curious what makes his situation different from mine.
This should have been the first question to ask yourself before trying to give advice.
I hope we agree that justifying these deviations can be a lot of work and having reproducible builds would be easier.
High-assurance systems is my thing btw. There's a few ways we approach this issue. One is a certifying compiler (eg CompCert C) that's verified and tested to basically never screw up. CompCert got SIL qualification recently with Solid Sands partnering to validate them, too.
Another I like is equivalence testing across multiple binaries from multiple compilers with a large, automatically-generated test suit and fuzzers. Such test generators are already great for bug hunting. You just rerun them through the same app compiled with other compilers. Helps catch app and compiler bugs. If you have CompCert, I have another trick: re-compile with CompCert, re-test each case that failed, and any that suddenly pass were likely compiler bugs. The multiple compilers approach should catch them, though.
Now, I should point out that what reproducible builds is addressing are often changes in hashes that aren't changes in functionality. Just stuff like timestamps or symbols. You should be able to show the regulators that the only thing that changed was that kind of stuff. Maybe we need tools that automate that, too, showing diffs annotated by what does or doesn't affect execution.
Anyway, what exactly do they require of you for source-to-object correspondence? And is this U.S. F.D.A. or a non-U.S. regulator?
In my case this is not really clear. You have quite a bit of leeway but you never know when you will get rejected. We had things that are approved a few years ago being rejected now because some rules have been tightened.
With reproducible builds you could save a lot of time validating build chains. It would be a huge timesaver and probably also allow us to be more creative with the build chain because if the binary is the same you know it worked.
If I have deterministic builds then the user can verify that the build matches the source. Otherwise it's potentially compromised (or the wrong version).
I was more arguing for use of Docker instead of "Conan" or whatever tool they were selling.
Me: "who cares about those corner cases, almost nobody would run into them. 99% of developers can achieve deterministic builds by standardizing compilers/dependencies inside of a docker image and using that to build the binaries."
Still, your "just do X" doesn't address the majority of the provided "sources of variation".
>which have the appropriate ... dependencies
What are your dependencies? Have you specified them precisely?
Does anything in the build process use timestamps at any point? If so, your build isn't deterministic.
Furthermore, if your goal is reproducible builds rather than simple determinism, you may then care about where e.g. the GCC binary in your Docker container came from.
For example the compiler backend is multi-threaded and spits out data in whatever order, doesn't matter normally because they designed it that way.
But in a determinstic scenario you have to change that.
Then you have to worry about timestamps in PE headers, macros in code, other tools messing with things, etc...
Then you have the issues of wanting to cache, that's where things like pathing come in to play.
> Security. Modifying binaries instead of the upstream source code can make the changes invisible for the original authors. This can be fatal in safety-critical environments such as medical, aerospace and automotive. Promising identical results for given inputs allows third parties to come to a consensus on a correct result.
I don't understand this argument, or rather what deterministic builds have to do with this. Isn't modifying the binary instead of the source just a very bad idea in a high stakes scenario?
Say you checkout "libmagnificent" from github, browse the source and like what you see. You can build a binary and see it matches the upstream build. This gives you some (more) confidence in upstream builds.
It also lets you know you're starting from a known good source, if you want to make modifications; the changes in the build come from the changes you made to the source - not through some side effect in the build process.
The real security concern is “you’ve audited the source code for some level of safety criticalness and validated it. What guarantee do you now have that the binary you’re told was built from that exact state of source code, actually was?”
A concrete example being the UK’s audit of some Huawei firmware source code, which basically concluded “in addition to the issues we found, the builds are highly non-deterministic and we have no way of verifying whether a given firmware blob was actually built from this source or may have differed in any number of ways”
I suppose it rules out including build date in the binaries.
Luckily, there are some good tools out there like bazel [1] (the open source version of what we use internally at Google) that can help you get this right.
The other advantages and why you may not need them :
- build acceleration : There are ways to do this without caching binaries. Incredibuild, for example does an excellent job (at least on Windows. Haven't used their Linux version). It costs money, so you have to figure out your priorities there. In my experience their support is excellent and it usually works very well.
- Storage savings : The premise here is that if a binary hasn't changed, there is no need to store it again with a new build. Maybe I'm getting rid of my old builds every 30 days (or whatever) so I don't really care about this either.
Absolutism is silly. You should analyze the advantages and costs of doing something instead of doing it because Google (or insert SV darling here) does it.
https://lobste.rs/s/7zbbzr/reproducing_linux_builds_firefox_...
Another argument is that reproducible builds, at least the final form being reproducible, creates a monoculture that lets people be hit with exploits more easily. Cutting-edge research in CompSci has produced numerous techniques to use compilers to automatically secure source either by addressing root causes (eg Softbound + CETS) or obfuscating the program (eg instruction set randomization by Barrantes et al.). The latter methods are affected in that the hashes won't match when it's working. If you're trying to understand, they usually do things similar to the kinds of mitigations in OpenBSD (below) just with compilers. Personally, the state of software security would make me prioritize running it through a hardening tool over it being reproducible.
https://www.openbsd.org/security.html
The main way reproducible builds help is with debugging. Design-by-contract with runtime checks on can do that, too, on top of a lot of other benefits. They can have a debug version with checks on that people use just for diagnosing problems. At this point, reproducible builds might be easier than getting that going, though, due to all the work already put it. It also looks like some compiler modifications upstream could help a lot, too.
http://se.ethz.ch/~meyer/publications/computer/contract.pdf
Wasn't full time for a year, but it was a lot of "try to build, oops compiler error contact them and then wait for a new compiler build. oh look found more non-determinism".
The fun was when we changed the PE header and started getting pushback from random teams.