Re: xz meltdown/Lasse Collin

Vegard Nossum <[email protected]> Sun, 14 Apr 2024 12:21:51 +0200
Newsgroups dev.linux.lists.tech-board-discuss
Message-ID <[email protected]>
On 13/04/2024 15:16, James Bottomley wrote:
>     2. We need better build artifact transparency generally but  I think
>        the kernel is fine here: we still use make so don't have the huge
>        build artifact issue that allowed the exploit in and we have a
>        documented signing process for our build artifacts (kernel
>        tarballs).
>     3. The indirect library dependency problem doesn't apply to us.

While this is technically true, there are many other ways to compromise
the kernel build process:

1) you can pass code in through the CFLAGS environment variable, one
example that I came up with together with Michael Ellerman would be:

   -DSET_ENDIAN(x,y)=-22,commit_creds((void*)init_task.cred)

when building kernel/sys.c on x86, this is will turn any userspace call
of prctl(PR_SET_ENDIAN), which normally just returns -EINVAL, into a
backdoor quietly making the calling process root.

All you need for an injection site is a preprocessor define that is
conditionally set with #ifndef FOO/#define FOO.

This does not appear in any source file or build output directly and so
likely wouldn't get caught by SBOM-type solutions. There are already
commit_creds() calls in kernel/sys.c so it does not look particularly
out of place when glancing over the object code either.

Fuzzers would likely not find this if you just conditioned the backdoor
on some config option like KCOV or KASAN.

There are also variations here where you might override CFLAGS_sys.o
instead of plain CFLAGS, or you could change LDFLAGS to link in other
(precompiled) object files, or you could place a malicious compiler
wrapper in PATH... the possibilities are endless.

2) you can pass a whole bunch of environment variables into Make, which
will be interpolated (!) by Make and do arbitrary things like running
shell scripts:

   $ MAKE_VERSION='$(shell echo hi >&2))' make kernel/fork.o
   hi
   ...

or:

   $ sub_make_done='$(eval $(warning asdf))' make kernel/fork.o
   Makefile:45: asdf
     CALL    scripts/checksyscalls.sh
     DESCEND objtool
   ...

3) there are many ways to set environment variables, it could be
anywhere in the chain leading up to the 'make' call, including
/etc/environment.d/ files installed by malicious packages or things like
/etc/bash_completion.d/ files (although these are typically only read by
interactive shells)

4) a kernel build relies on a huge amount of code run at build time. I
count 32 shared libraries on my machine. Any one of them could contain
malicious code to hijack the kernel build to insert a backdoor like the
one above. One of my favourites is probably pkg-config, which gets
called by the kernel build system and which has a search path where a
malicious third-party package (not related to the kernel or kernel build
at all!) could potentially install a malicious file that alters the
Cflags: property of a package to inject a Makefile or shell fragment
into the build.

5) If you have /bin/sh linked to bash, then Shellshock makes a reapparance:

   $ env 'BASH_FUNC_uname%%=() { echo foobar >&2; }' make kernel/fork.o
   foobar
     SYNC    include/config/auto.conf.cmd
   ..

So yes, while the kernel itself is "probably fine" to some extent, it
would be quite easy for a malicious package on the build host (and not
just the compiler/toolchain!) to inject malicious code into a kernel build.

I know it's impossible to protect against everything. I also know people
are likely already aware (at least on some level) that we can't really
do anything if the build host/configuration/environment or toolchain is
already compromised. I don't think most people realize exactly how easy
it would be, though, while also keeping it relatively stealthy.


Vegard