Why XZ is an illustration for why we need to end build scripting

The recent coverage around XZ vulnerabilities is an example at the forefront of why we need to secure the so-called software supply chain. Quxlang is one step in the direction of security by providing no binary execution for the build process(with the possible exception of tools later, but those will be a topic for another day).

In this post I’m going to detail the Quxlang build system. Note that several of these details are provisional and might be adjusted slightly by the time beta release rolls around, but it should provide a good enough overview that it will prove correct. Sometimes as I implement things, original ideas turn out to have issues and I tend to prefer to adjust the design at that point. (Hence all my recent pain and suffering around rewriting IR generation to generalize the expression IR generation for constexpr.)

Quxlang uses a source bundle as the buildable unit. A source bundle is passed to the compiler, and you can also supply a target and or an output.

An example of a target might be “linux_x64” and a target output combination might be “windows_x64/program.exe”.

The Quxlang compiler doesn’t take any other options. This is intended to discourage scripting. If you want to build something, you can put the description in the YML files, which provide a declarative (not scripted) way to define what gets built using what.

The source bundle consists of a modules/ subdirectory, as well as a quxbuild.yml file. Later, support for a resources/ and tools/ directory will be added (tools will run inside a deterministic virtual machine).

The quxbuild.yml file can be used to define targets. A target does a few things, first you can set the machine type and platform type information inside a target, as well as set the module mappings. Module mappings allow you to map source modules (e.g. foolib_0.1.4) to build modules (e.g. foolib).

Supposing a module imports foolib:

IMPORT foolib;

We can have some targets use foolib_0.1.4 and others use the foolib_0.1.7rc4 source module as the “foolib” build module. We can also override the per-target module map to rename module imports. There are two supported renaming methods. The first is an external rename. In the external rename, we can change the meaning of “IMPORT foo” to “IMPORT bar”, with all code references in the module using the “bar” build module instead of the foo build module, even though the source code references it as “foo”. The second is an internal rename, such as “IMPORT foo AS f”. This allows the module to use a different name to reference the “foo” build module inside the source code of the module.

External renames solve a major issue: Namely, I have two modules A and B that both depend on a module named C, but they require different incompatible versions of that module, or different modules that unfortunately share the same name (likely offenders here include libraries named “math”, “zip”, “net” etc.). We can use both build modules and perform an external rename in the module map in order to have A and B both “IMPORT c” but get different modules as a result of that import.

Declarative builds should hopefully eliminate the need for build scripting. I’m looking at different examples of build scripting as it exists now in C++ and trying to figure out different edge cases that might need to be solved in order to ensure Quxlang can build projects without any scripting. Aside from e.g. protobuf and shader compilation (maybe I embed a SPIR-V compiler in qxc?), I don’t see a lot of motivating use cases for it. And I am hoping that a source-code based “tools/” directory that runs tools in a virtual machine can satisfy the need for other tools in a way that prevents host system hijacking.

Declarative builds wont entirely prevent backdoors, but they will make it much harder for someone to sneak an injection of code in without someone noticing!

Leave a comment