Hey Eric,
Thank you for writing this! ELF binary signing has been on my
ever-growing list of things to research and develop. If you'd like help,
please let me know.
I have a few comments, which I've made inline.
On Mon, Mar 27, 2017 at 01:54:44PM -0400, Eric McCorkle
wrote:> Hello everyone,
>
> The following is a design proposal for signed kernel and kernel module
> loading, both at boot- and runtime (with the possibility open for signed
> executables and libraries if someone wanted to go that route). I'm
> interested in feedback on the idea before I start actually writing code
> for it.
>
> == Goals =>
> 1) Be able to check for a correct cryptographic signature for any kernel
> or modules loaded at boot time for some platforms (EFI at a minimum).
>
> 2) Be able to check for a correct cryptographic signature for any kernel
> module loaded during normal operations (whether or not to do this could
> be controlled by a sysctl, securelevel, or some similar mechanism)
>
> 3) Work with what's in base already and minimize new additions
(ideally,
> just a small utility to sign executables)
>
> 4) Minimize administrative overhead and ideally, require no changes at
> all to maintain signed kernel/modules
>
> 5) Have a clear path for supporting signed executables/libraries (I'm
> not planning on pursuing this, but it's worth considering that someone
> might want to)
>
> 6) The design *MUST* support the case where a system builds locally and
> uses its own key(s) for signing kernels and modules (and anything else)
> and *MUST* allow the administrator complete control over which key(s)
> are valid for a given system (ie. no "master keys" controlled by
central
> organizations)
>
> 7) The design must allow for the adoption of new ciphers (there is an
> inevitable shift to post-quantum ciphers coming in the near future)
As git has shown, having a modular/configurable crypto interface is the
best route. Right now, git is stuck using SHA1 because they didn't
support users being able to choose which hashing algorithm to use.
>
> == Non-Goals =>
> * Hardware/firmware-based attacks are considered out-of-scope (there is
> no viable method for defending against them at the OS level)
>
> * Boot platforms that don't provide their own signature-checking
> framework up to loader/kernel can't be properly secured, and are
> considered out-of-scope
>
> * Boot platforms that impose size restrictions prohibiting incorporation
> of RSA and ED25519 crypto code (ex. i386 BIOS) are considered out-of-scope
>
> * GRUB support is desirable, however it is not necessary to support GRUB
> out-of-the-box (meaning a design requiring reasonable modifications to
> GRUB is acceptable).
>
> * I am not aiming to support signed executables/libraries now, only to
> avoid shutting the door on them.
Since FreeBSD uses ELF for the kernel and its modules, it should be
rather straightforward to apply the same work towards userland binaries
and shared libraries.
>
> == Existing Solution(s) =>
> EFI has a decent design with regard to key management (platform key,
> which signs system keys, which sign the actual loader); however, its
> cipher suite is sorely lacking (many broken hashes and weak ciphers, RSA
> 2048 being the "strongest", no ECC). It also only works with the
COFF
> format, and is only available at boot time. However, it does provide a
> chain of custody up to loader (to the extent that anyone trusts
> closed-source firmware blobs, SHA1, and 512-2048 bit RSA keys...) Many
> implementations also have master keys "baked in" that would allow
> anything signed by random third parties (Microsoft) to boot regardless
> of local configurations, or they don't provide any sort of control over
> (or even access to) the keys at all.
>
> EFI obviously isn't viable beyond boot time, and misses most of the
> goals even there. Its key management hierarchy is an overall good
> design, however.
>
> GRUB currently supports signature checking. It can be configured to
> require signatures for any of its own modules as well as any kernel or
> modules that it loads. These signatures are stored *outside* the
> executable/library, in a file with an added .sig extension. The format
> is that of an external signature for the entire ELF file as produced by
> the gnupg program.
>
> Linux (I believe) also supports signature checking for modules using the
> same convention.
>
> While functional, this design doesn't meet the goals I outlined:
>
> * It relies on the gnupg framework, which is not part of FreeBSD base,
> and adding it would be a chore (and would end up duplicating a lot of
> functionality provided by OpenSSL)
>
> * It stores the signature separate from the file, which leads to x2 the
> number of files, would require modifying existing scripts, and
> complicates administrative tasks. It also leads to failure modes like
> stale signatures.
>
> * There are potential legitimate modifications to non-code parts of an
> ELF file (such as the .comment section or other similar sections) that
> would require re-signing the entire file.
>
> * The previous two problems really start to look bad when you consider
> signed executables and libraries, possibly with third-party
> build/install scripts...
>
> * Finally, the gnupg signature format doesn't actually seem to be
> documented anywhere, or at least not anywhere that doesn't require a
lot
> of digging...
>
>
> An alternate solution, which I believe is used in some places is to wrap
> the entire executable in a PGP envelope-like format. This solves the
> issue of an external signature file, but would require extensive
> modification to the ELF parsing code, let alone the binutils programs
> that read/modify ELF files. This solution also isn't
> backwards-compatible at all. Old loaders/kernels will choke on the
> signed libraries.
Whatever is chosen, it should be fully-functional with only the
utilities base provides.
>
> == Proposal=>
> My proposal is to store cryptographic signatures within the ELF files
> themselves in a non-loadable section (similar to the .comment section).
>
> As background, the ELF file format has a number of different section
> types, only some of which comprise the program/library/module's runtime
> state. The ELF specification and tools provide some "standard"
sections
> with defined meanings, but nothing stops anyone from adding their own
> sections. The ELF file format is quite flexible, and it is not
> difficult to add custom metadata to an ELF file. [0]
>
> In this proposal, cryptographic signatures would be stored in a
> .signature (or .sig) section. This section would contain an array of
> signature constructs: one for each loadable segment in the ELF file.
> Signatures are computed for the contents of the segment's file data
(ie.
> the data from p_offset to p_filesz, for the corresponding program header
> entry) along with all data from its program header entry except for
> p_offset and p_filesz. This scheme allows the actual data to be moved
> around in the file, so long as it (or the relevant program header data)
> isn't modified.
You might want to take a look at Microsoft's Authenticode. Microsoft
made some mistakes early on that allowed attackers to easily trojan
signed binaries. Your proposal up to this point makes those same
mistakes. It's been a few years since I researched Authenticode, so I
don't have any links or documentation handy.
The conclusion Microsoft came to is that the file as a whole must be
signed, including offset metadata. Essentially, you'd determine how
large the .sig section needs to be ahead of time, create it and fill it
with zeros, then sign the whole file, stuffing the signature in the
zeroed .sig section. Same concept as calculating checksums of ICMP
packets.
This prevents attackers from modifying critical pieces of metadata,
pointing them to maliciuos payloads. It also prevents attackers from
appending malicious data to the end of a loadable segment (something
Authenticode suffered from early on).
>
> The exact format of this data can be discussed, but a design where the
> signature array corresponds to the program header array seems quite
> reasonable. The format of the signatures themselves should be something
> from a well-defined standard, reasonably extensible, and supported by
> tools in base.
>
> == Summary of Changes =>
> The following changes would be required:
>
> 1) Add a userland utility for signing ELF files (call it
"signelf").
> This would be a pretty straightforward application of OpenSSL and libelf.
>
> 2) Modify ELF-parsing code in loader and kernel to check signatures and
> indicate whether a given file had good signatures for all of its
> loadable segments.
>
> 3) Have loader/kernel issue warnings or reject kernels/modules with
> incomplete/incorrect/no signatures
>
> 4) Decide how to go about building public key data into loader/kernel or
> how to register keys with the kernel (it is probably OK to implement a
> "bake it in" solution first, then figure out dynamic registration
of
> keys as a follow-up; somebody out there is sure to want just the "bake
> it in" solution with no dynamic registration for security reasons, and
> we need it for loader anyway).
>
> 5) Submit a patch against GRUB to support the ELF metadata method in
> addition to their existing method.
>
> The most involved part of this is adding the public-key crypto code into
> loader and the kernel. My recommendation for this is to grab the RSA
> and ED25519 code from NaCL. It's compact, self-contained, written by
> crypto people with a good handle on the systems side of things (DJB's
> group), and licensed under a BSD-compatible license. Also, the
> loader/kernel side code only needs signature-checking, not full
> public-key functionality.
>
> == Rationale =>
> The ELF metadata approach eliminates all of the disadvantages of the
> GRUB external signatures method, while maintaining compatibility with
> existing systems. Older systems will simply ignore the .signature
> metadata section and function normally and from a sysadmin standpoint,
> signed executables/libraries are just slightly larger versions of the
> unsigned variants. Moreover, ELF metadata that isn't part of the
> executable sections can be freely modified, and signed ELF files can be
> re-signed.
>
> Having a separate signature for each segment in the program header table
> is slightly more complicated than the simplest solution of having one
> signature for all program header sections. However, this approach
> provides more flexibility going forward. It also accounts for the fact
> that we might not want to sign all portions of the file. Finally, as
> designed, it allows the file to be modified freely as long as the
> runtime behavior isn't affected.
>
> There is a rather simple design possibility if anyone wanted to go the
> signed executable/library route: have an mmap variant with an additional
> parameter pointing to the signature would lead to a very simple
> modification of the userland dlopen functionality. Normal mmap would
> just become a wrapper around the secure variant, which passes in NULL
> for the signature (alternatively, you could pass in a default key built
> into the local libc, or something similar).
Userland shouldn't be trusted to enforce digital signatures. What if
someone at link time specifies a non-default RTLD? To enforce digital
signatures of userland binaries/libraries, the ELF image activator
should be modified to verify the DT_NEEDED entries.
>
> == Conclusion =>
> This seems like a good point in the design space: it doesn't break
> anything, it doesn't require massive changes or rearchitecting of
> anything, it provides everything I want to provide now, and it leaves
> the door open to things people might want to do in the future.
>
> Please provide feedback, comments, and suggestions.
>
>
> [0]: There actually is at least one example of something like this of
> which I'm aware. The Intel C Compiler (icc, "proton" by
Intel internal
> naming) has an interprocedural optimization mode which produces .o files
> containing the compiler's intermediate representation in a special
> section as well as object code in the usual sections (incidentally, in
> the distant past, icc would actually produce separate .o and .il files;
> this was later changed to the ELF metadata solution, for the very reason
> that it complicated build scripts quite a bit). This allows
"normal"
> compilers and compilation modes to use the object code, while icc uses
> the intermediate representation.
>
The only other major thing to discuss is supporting public key chaining.
Ideally, digital signature support should also support chaining multiple
keys (similar to X.509 PKI). If the accepted solution supported cert
chaining, then the solution would be more modular. I don't want to go
down the route of the SSL/TLS PKI mess, but supporting chaining is a
must in some enterprise environments.
If we were to support chaining, we could even stuff the pubkey half of
the key material into another ELF section, so that if a key becomes
compromised, the old pubkey can be revoked from the trust store and a
new binary can be generated with new key material. The trusted root
doesn't need to be cycled as often. HardenedBSD, for example,
distributes the pubkey that corresponds to the signing privkey inside
the update tarball for binary updates[1]. This allows us to change key
material often if desired without the user even noticing.
[1]:
https://hardenedbsd.org/article/shawn-webb/2015-12-31/introducing-hardenedbsds-new-binary-updater
Caveat with the above-linked article: hbsd-update has undergone
additional changes not reflected in the original article.
Thanks,
--
Shawn Webb
Cofounder and Security Engineer
HardenedBSD
GPG Key ID: 0x6A84658F52456EEE
GPG Key Fingerprint: 2ABA B6BD EF6A F486 BE89 3D9E 6A84 658F 5245 6EEE
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL:
<http://lists.freebsd.org/pipermail/freebsd-security/attachments/20170327/31117288/attachment.sig>