Hello,

On Thu, Apr 4, 2024, 09:00 Michael Matz <m...@suse.de> wrote:

> Hello,
>
> On Wed, 3 Apr 2024, Jonathon Anderson wrote:
>
> > Of course, this doesn't make the build system any less complex, but 
> > projects using newer build systems seem easier to secure and audit than 
> > those using overly flexible build systems like Autotools and maybe even 
> > CMake. IMHO using a late-model build system is a relatively low 
> > technical hurdle to overcome for the benefits noted above, switching 
> > should be considered and in a positive light.
>
> Note that we're talking not (only) about the build system itself, i.e. how 
> to declare dependencies within the sources, and how to declare how to 
> build them. [...]
>
> But Martin also specifically asked about alternatives for feature tests, 
> i.e. autoconfs purpose.  I simply don't see how any alternative to it 
> could be majorly "easier" or "less complex" at its core.  

My point was not that newer build systems are any less complex taken 
wholistically. The upthread has already discussed why configuration tests are 
necessary and aren't going away anytime soon. My horror stories would not add 
much to that conversation.

My point, or rather my opinion as a humble end-user, is that newer build 
systems (e.g. Meson) make identifying the xz backdoor and injections following 
the same pattern much easier than older build systems (e.g. 
autoconf+automake+libtool). Paraphrasing my original message to highlight the 
backdoor protections:

- This xz backdoor injection unpacked attacker-controlled files and ran them 
during `configure`. Newer build systems implement a build abstraction (aka DSL) 
that acts similar to a sandbox and enforces rules (e.g. the only code run 
during `meson setup` is from `meson.build` files and CMake). Generally speaking 
the only way to disobey those rules is via an "escape" command (e.g. 
`run_command()`) of which there are few. This reduces the task of auditing the 
build scripts for sandbox-breaking malicious intent significantly, only the 
"escapes" need investigation and they which should(tm) be rare for well-behaved 
projects.

- This xz backdoor injected worked due to altered versions of the build system 
implementation being shipped with the source. Newer build systems do not ship 
any part of the build system's implementation with the project's source, it 
somes as a separate package and the most the project does is specify a 
compatible version range. This removes the possibility of "hidden" code being 
mixed to the shipped source tarball, well-behaved projects will have source 
tarballs that can be byte-for-byte reproduced from the VCS (e.g. Git).

- This xz backdoor injected the payload code by altering the files in the 
source directory. Newer build systems do not allow altering the source 
directory (for good reason) in their build abstraction. For build workers/CI, 
this restriction can be enforced using low-level sandboxing (e.g. conainers or 
landlock) and well-behaved projects should(tm) be unaffected. This reduces the 
possibilities on how code can appear in the project, only code generation 
sequences (e.g. `custom_target()` or `generator()`) can produce arbitrary code.

Just by transitioning to a newer build system (and being "well-behaved") it 
seems like you get certain protections almost for free. And IMHO compared to 
other options to improve project security (e.g. automated fuzzing), 
transitioning build systems is a medium-effort, low-maintainence option with a 
comparatively high yield.

> make is just fine for that (as are many others).  (In a way 
> I think we meanwhile wouldn't really need automake and autogen, but 
> rewriting all that in pure GNUmake is a major undertaking).

Makefile (the language) is extremely complex, it is nigh impossible to ensure 
something won't expand to some malicious code injection when the right file 
arrangement is present. (IMHO Make also is not a great build system, it has 
none of the protections described above... and it's [just plain 
slow](https://david.rothlis.net/ninja-benchmark/).)

Newer build systems seem to be moving in a direction where the bulk of the 
build configuration/scripts are non-Turing-complete. This is a highly effective 
method for clarifying the high-level complexity of the DSL: simple things are 
short and sweet, complex things (and discouraged solutions) are long and 
complex. Less complex high-level build logic makes it easier to verify and 
audit for security (and other) purposes.

> Going with the 
> examples given upthread there is usually only one major solution: to check 
> if a given system supports FOOBAR you need to bite the bullet and compile 
> (and potentially run!) a small program using FOOBAR.  A configuration 
> system that can do that (and I don't see any real alternative to that), no 
> matter in which language it's written and how traditional or modern it is, 
> also gives you enough rope to hang yourself, if you so choose.

Both very true, as discussed upthread.

It may be a helpful policy to use templates for configure tests where possible. 
For example the [now-famous XZ landlock configure test (that contained a rouge 
syntax 
error)](https://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2bbb81307644efdb58db2c422d9ba7)
 could be written via Meson template configure tests as:

    cc = meson.get_compiler('c')
    if (
        cc.has_function('prctl', prefix: '#include <sys/prctl.h>')
        and cc.has_header_symbol('sys/syscall.h', 
'SYS_landlock_create_ruleset', prefix: '#include <linux/landlock.h>')
        and cc.has_header_symbol('sys/syscall.h', 'SYS_landlock_restrict', 
prefix: '#include <linux/landlock.h>')
        and cc.has_header_symbol('linux/landlock.h', 
'LANDLOCK_CREATE_RULESET_VERSION')
    )
    # ...
    endif

The templates are well-tested as part of the build system, so if the `prefix:` 
has no syntax error (and the spelling is right) I'm confident these checks are 
sane. And IMHO this expresses what is being checked much better than an actual 
snippet of C/C++ code.

>
> If you get away without many configuration tests in your project then this 
> is because what (e.g.) the compiler gives you, in the form of libstdc++ 
> for example, abstracts away many of the peculiarities of a system.  But 
> in order to be able to do that something (namely the config system of 
> libstdc++) needs to determine what is or isn't supported by the system in 
> order to correctly implement these abstractions.  I.e. things you depend 
> on did the major lifting of hiding system divergence.
>
> (Well, that, or you are very limited in the number of systems you support, 
> which can be the right thing as well!)

We do get away with less than a dozen configuration tests, in part because we 
are fairly limited in what we support (GNU/Linux) and we use the C, C++ and 
POSIX standards quite heavily. But we're also an end-user application with a 
heavy preference towards bleeding-edge systems, not a low-level library that 
can run on 1980-something technology, so our experience might be a bit... 
unique. ;)

Thanks,\
-Jonathon

Reply via email to