diff --git a/Documentation/ABI/testing/evm b/Documentation/ABI/testing/evm index 44750a933db498a833791aa29a461a2623aab39c..2828e720d91eb175f0b9fcb31ba91a2030f0fcf9 100644 --- a/Documentation/ABI/testing/evm +++ b/Documentation/ABI/testing/evm @@ -69,8 +69,8 @@ Description: and the resulting value will be 3. - Note that once an HMAC key has been loaded, it will no longer - be possible to enable metadata modification. Signaling that an + Note that once a key has been loaded, it will no longer be + possible to enable metadata modification. Signaling that an HMAC key has been loaded will clear the corresponding flag. For example, if the current value is 6 (2 and 4 set):: diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 9e5bab29685ff0534fe82714dda5e20804c38ead..4a0261de0ed85a0254e152ad90e0942164404966 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1476,9 +1476,13 @@ has equivalent usage. See its documentation for details. evm= [EVM] - Format: { "fix" } - Permit 'security.evm' to be updated regardless of - current integrity status. + Format: { "fix" | "x509" | "allow_metadata_writes" | + "complete" } + fix: permit 'security.evm' to be updated regardless of + current integrity status; + x509: enable EVM by setting x509 flag; + allow_metadata_writes: permit metadata modificatons; + complete: don't allow further changes of the EVM status. early_page_ext [KNL] Enforces page_ext initialization to earlier stages so cover more early boot allocations. @@ -1941,9 +1945,19 @@ Set number of hash buckets for inode cache. ima_appraise= [IMA] appraise integrity measurements - Format: { "off" | "enforce" | "fix" | "log" } + Format: { "off" | "enforce" | "fix" | "log" | + "enforce-evm" | "log-evm" } default: "enforce" + ima_appraise_digest_list= [IMA] + Format: { "digest" | "digest-nometadata" } + + digest: enables appraisal of files with digest lists + until EVM is initialized. + + digest-nometadata: enables appraisal of files with + digest lists even after EVM is initialized. + ima_appraise_tcb [IMA] Deprecated. Use ima_policy= instead. The builtin appraise policy appraises all files owned by uid=0. @@ -1952,6 +1966,19 @@ Use the canonical format for the binary runtime measurements, instead of host native format. + ima_digest_list_pcr= + [IMA] + Specify which PCR is extended when file digests are + not found in the loaded digest lists. If '+' is not + specified, no measurement entry is created if the + digest is found. Otherwise, IMA creates also entries + with PCR 10, according to the existing behavior. + Format: { [+] } + + ima_digest_db_size=nn[M] + [IMA] + Sets the maximum data uploaded to IMA digest database. + ima_hash= [IMA] Format: { md5 | sha1 | rmd160 | sha256 | sha384 | sha512 | ... } @@ -1970,6 +1997,11 @@ mode bit set by either the effective uid (euid=0) or uid=0. + The "exec_tcb" policy is similar to the "tcb" policy + except for file open, which is not considered. Files + in the tmpfs filesystem are not excluded from + measurement. + The "appraise_tcb" policy appraises the integrity of all files owned by root. @@ -1977,6 +2009,15 @@ of files (eg. kexec kernel image, kernel modules, firmware, policy, etc) based on file signatures. + The "appraise_exec_tcb" includes the "secure_boot" + policy and additionally includes all programs exec'd and + files mmap'd for exec. Files in the tmpfs filesystem are + not excluded from appraisal. + + The "appraise_exec_immutable" policy requires immutable + metadata for executed files, if the "appraise_exec_tcb" + policy is selected. + The "fail_securely" policy forces file signature verification failure also on privileged mounted filesystems with the SB_I_UNVERIFIABLE_SIGNATURE diff --git a/Documentation/security/IMA-digest-lists.txt b/Documentation/security/IMA-digest-lists.txt new file mode 100644 index 0000000000000000000000000000000000000000..15f30deab3282818801a8e5a67bb5e275121f954 --- /dev/null +++ b/Documentation/security/IMA-digest-lists.txt @@ -0,0 +1,259 @@ +========================== +IMA Digest Lists Extension +========================== + +INTRODUCTION +============ + +Integrity Measurement Architecture (IMA) is a security module that performs +measurement of files accessed with the execve(), mmap() and open() system +calls. File measurements can be used for different purposes. They can be +added in a measurement list and sent to a remote verifier for an integrity +evaluation. They can be compared to reference values provided by the +software vendor and access can be denied if there is a mismatch. File +measurements can also be included in system logs for auditing purposes. + +IMA Digest Lists is an extension providing additional functionality for +the IMA submodules and EVM. Its main task is to load reference values for +file content and metadata in the kernel memory, and to communicate to IMA +submodules (measurement and appraisal) or EVM whether the digest of a file +being accessed is found in the uploaded digest lists. + +The IMA-Measure submodule uses the IMA Digest Lists extension to create +a new measurement list (with a different PCR, for example 11) which +contains the measurement of uploaded digest lists and unknown files. Since +the loading of digest lists is sequential, the chosen PCR will have a +predictable value for the whole boot cycle and can be used for sealing +policies based on the OS software integrity. + +Both the standard and the new measurement list can be generated at the same +time, allowing for implicit attestation based on the usage of a TPM key +sealed to the OS, and for explicit attestation when a more precise +integrity evaluation is necessary. + +The IMA-Appraise submodule uses the IMA Digest Lists extension to +grant/deny access to the files depending on whether the file digest is +found in the uploaded digest lists, instead of checking security.ima. + +EVM uses the IMA Digest Lists extension to check file metadata integrity. + +The main advantage of the extension is that reference measurements used for +the digest comparison can be extracted from already existing sources (for +example RPM headers). Another benefit is that the overhead compared to file +signatures is lower as only one signature is verified for all files +included in the digest list. + + + +WORKFLOW +======== + +The IMA workflow is modified as follows. The added steps are marked as +[NEW]. + + +------------+ + | IMA hook | + +------------+ + | + +---------+ + | collect | + +---------+ + | +---------------------+ ++---------------+ ------------| don't measure [NEW] | +| digest lookup | | yes +---------------------+ +| [NEW] | -------------- no +---------------------+ ++---------------+ -----/ digest \---| add to measurement | + | | \ found? [NEW] / | list (PCR 11) [NEW] | + | | -------------- +---------------------+ + +----------+ +-------------+ +--------------------+ + | switch |-------| IMA-Measure |----------------| add to measurement | + | (action) | +-------------+ | list (PCR 10) | + +----------+ +--------------------+ + | + | + | no xattr ++--------------+ --------------- yes +| IMA-Appraise |----/ filec created \------------------------------- ++--------------+ \ created [NEW] / | + xattr | --------------- | + | | no | + | ------------------ no -------------- yes +--------+ + | / EVM initialized? \----/ digest \------| grant | + | \ [NEW] / \ found? [NEW] / | access | + | ------------------ -------------- +--------+ + | | yes | no + | | | +--------+ + | ------------------- no ---------------| deny | + | / digest-nometadata \-------------------------| access | + | \ mode? [NEW] / +--------+ + | ------------------- + | ^ | yes +--------+ + | | -------------------------------------| grant | + | | | access | + | | +--------+ + | | yes + | ------------------------- no +--------+ + |----------/ security.ima (new type) \-------------------| deny | + | \ present and valid? / | access | + | ------------------------- +--------+ + | | + | -------------------------- no | + -----------/ security.ima (cur types) \----------------------- + \ present and valid? / + -------------------------- + | yes +--------+ + ------------------------------------| grant | + | access | + +--------+ + + ++-----+ -------------------------- yes +--------+ +| EVM |---------/ security.evm (cur types) \---------------------| grant | ++-----+ \ present and valid? / | access | + | -------------------------- +--------+ + | | + | | no +--------+ + | ------------------------------------| deny | + | | access | + | +--------+ + | | + | +-----------+ | + | ------------------------- | calculate | | + -------------/ security.evm (new type) \---| metadata | | + \ present? [NEW] / | digest | | + ------------------------- +-----------+ | + | | + -------------- no | + / digest \--------- + \ found? [NEW] / + -------------- +--------+ + | yes | grant | + | | access | + | +--------+ + | | + ------------------ yes | + / digest \-----| + \ immutable? [NEW] / | + ------------------ | + | no | + +---------+ | + | convert |------------ + | to HMAC | + +---------+ + + +After the file digest is calculated, it is searched in the hash table +containing all digests extracted from the uploaded digest lists. Then, if +the digest is found, a structure is returned to IMA with information +associated to that digest. The structure is returned only to the IMA +submodules that processed the digest lists (i.e. the action returned by +ima_get_action() was 'measure' or 'appraise'). + +IMA-Measure behavior depends on whether the digest list PCR has been +specified in the kernel command line. If the PCR was not specified, the +submodule behaves as before. If the PCR was specified, IMA-Measure creates +a new measurement with that PCR, only if the file digest is not found in +the digest lists. It additionally creates a measurement with the default +PCR if '+' is added as a prefix to the PCR. + +IMA-Appraise behavior depends on whether either the 'digest' or +'digest-nometadata' appraisal modes have been specified in the kernel +command line. If they were not specified, IMA-Appraise relies solely on the +security.ima xattr for verification. If the 'digest' mode was specified, +verification succeeds if the file digest is found in the digest lists and +EVM is not initialized, as there is no other way to verify file metadata. + +If the 'digest-nometadata' mode was specified, verification succeeds +regardless of the fact that EVM is initialized or not. However, after a +write, files for which access was granted without verifying metadata will +have a new security.ima type, so that they can be identified also after +reboot. Specifying 'digest-nometadata' is required also to access files +with the new security.ima type. + +EVM determines whether metadata digest should be searched in the digest +lists depending on the security.evm type. If the new type is set, EVM +calculates metadata digest and searches it in the digest lists. A structure +is returned to EVM if the digest is found and the digest lists were +appraised. + + + +ARCHITECTURE +============ + ++-----+ 6) digest lookup +| EVM |------------------------------------------------ ++-----+ | + | ++--------------+ 5) digest lookup | +| IMA-Appraise |--------------------------------------| ++--------------+ || + || ++-------------+ 4) digest lookup || +| IMA-Measure | -------------------------------------|| ++-------------+ ||| + ||| ++-------------+ 2) parse compact list +-------------------+ +| IMA (secfs) | ------------------------> | IMA Digests Lists | ++-------------+ +-------------------+ + ^ | 3) add digests + | +------------+ + | | hash table | + | +------------+ + | + | kernel space +---|------------------------------------------------------------------------- + | user space + | + | + | + | + | + | + | +1) echo + +The main addition to IMA is a new hash table (similar to that used to check +for duplicate measurement entries), which contains file content and metadata +digests extracted from the digest lists. + +Digest lists can be uploaded to the kernel by writing their path to +digest_list_data in the securityfs filesystem. After digest lists are +uploaded, they are parsed by the kernel and extracted digests are added to +the hash table. + +IMA submodules, Measure and Appraise, search the digest of an accessed file +in the hash table and perform actions depending on whether the digest was +found or not. IMA submodules can search digests in the hash table only if +they also processed the digest lists. + +EVM searches the metadata digest of an accessed file in the hash table and +returns the result to IMA, which perform actions depending on the result. +EVM can search digests in the hash table if IMA-Appraise processed the +digest lists. + + + +CONFIGURATION +============= + +The first step consists in generating digest lists with the +gen_digest_lists tool included in the digest-list-tools package. +digest-list-tools can be retrieved at the URL: + +https://gitee.com/openeuler/digest-list-tools + +gen_digest_lists can generate digest lists from different sources (for +example: RPM package DB). By default, it saves generated digest lists in +the /etc/ima/digest_lists directory. digest-list-tools includes also two +bash scripts setup_ima_digest_lists and setup_digest_lists_demo to simplify +the digest list generation for the users. + +To use digest lists during early boot, it is necessary to regenerate the +initial ram disk. Digest lists will be added to the ram disk by the new +dracut/initramfs-tools modules, included in the digest-list-tools package. To +include file signatures in the initial ram disk, it is necessary to have the +following patches applied: + +https://gitee.com/src-openeuler/cpio/blob/master/add-option-to-add-metadata-in-copy-out-mode.patch +https://gitee.com/src-openeuler/dracut/blob/master/add-option-to-include-file-metadata-in-initramfs.patch diff --git a/arch/arm64/configs/openeuler_defconfig b/arch/arm64/configs/openeuler_defconfig index 63abdb3f8c635e5ac12a3fcd9fcc71aca2c2f5e5..504a65e08247a43414bf7d00a78776a1a1a12af2 100644 --- a/arch/arm64/configs/openeuler_defconfig +++ b/arch/arm64/configs/openeuler_defconfig @@ -7003,6 +7003,13 @@ CONFIG_IMA_TRUSTED_KEYRING=y CONFIG_IMA_LOAD_X509=y CONFIG_IMA_X509_PATH="/etc/keys/x509_ima.der" # CONFIG_IMA_APPRAISE_SIGNED_INIT is not set +CONFIG_IMA_DIGEST_LIST=y +CONFIG_IMA_DIGEST_LISTS_DIR="/etc/ima/digest_lists" +CONFIG_IMA_STANDARD_DIGEST_DB_SIZE=y +# CONFIG_IMA_MAX_DIGEST_DB_SIZE is not set +# CONFIG_IMA_CUSTOM_DIGEST_DB_SIZE is not set +CONFIG_IMA_DIGEST_DB_MEGABYTES=16 +CONFIG_IMA_PARSER_BINARY_PATH="/usr/bin/upload_digest_lists" CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS=y CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS=y # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set @@ -7298,6 +7305,9 @@ CONFIG_PKCS7_MESSAGE_PARSER=y # CONFIG_PKCS7_TEST_KEY is not set CONFIG_SIGNED_PE_FILE_VERIFICATION=y # CONFIG_FIPS_SIGNATURE_SELFTEST is not set +CONFIG_PGP_LIBRARY=y +CONFIG_PGP_KEY_PARSER=y +CONFIG_PGP_PRELOAD=y # # Certificates for signature checking @@ -7314,6 +7324,7 @@ CONFIG_SYSTEM_BLACKLIST_HASH_LIST="" CONFIG_SYSTEM_REVOCATION_LIST=y CONFIG_SYSTEM_REVOCATION_KEYS="" # CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE is not set +CONFIG_PGP_PRELOAD_PUBLIC_KEYS=y # end of Certificates for signature checking CONFIG_BINARY_PRINTF=y diff --git a/arch/x86/configs/openeuler_defconfig b/arch/x86/configs/openeuler_defconfig index 0e05e7a15fdb6c360159b05f7e42db4523d910c5..be7fd428c1b62d0a364fd9823aa52abee2da0e99 100644 --- a/arch/x86/configs/openeuler_defconfig +++ b/arch/x86/configs/openeuler_defconfig @@ -3709,22 +3709,22 @@ CONFIG_TCG_TPM=y CONFIG_HW_RANDOM_TPM=y CONFIG_TCG_TIS_CORE=y CONFIG_TCG_TIS=y -CONFIG_TCG_TIS_SPI=m +CONFIG_TCG_TIS_SPI=y # CONFIG_TCG_TIS_SPI_CR50 is not set # CONFIG_TCG_TIS_I2C is not set # CONFIG_TCG_TIS_I2C_CR50 is not set -CONFIG_TCG_TIS_I2C_ATMEL=m -CONFIG_TCG_TIS_I2C_INFINEON=m -CONFIG_TCG_TIS_I2C_NUVOTON=m -CONFIG_TCG_NSC=m -CONFIG_TCG_ATMEL=m -CONFIG_TCG_INFINEON=m +CONFIG_TCG_TIS_I2C_ATMEL=y +CONFIG_TCG_TIS_I2C_INFINEON=y +CONFIG_TCG_TIS_I2C_NUVOTON=y +CONFIG_TCG_NSC=y +CONFIG_TCG_ATMEL=y +CONFIG_TCG_INFINEON=y # CONFIG_TCG_XEN is not set CONFIG_TCG_CRB=y # CONFIG_TCG_VTPM_PROXY is not set -CONFIG_TCG_TIS_ST33ZP24=m -CONFIG_TCG_TIS_ST33ZP24_I2C=m -CONFIG_TCG_TIS_ST33ZP24_SPI=m +CONFIG_TCG_TIS_ST33ZP24=y +CONFIG_TCG_TIS_ST33ZP24_I2C=y +CONFIG_TCG_TIS_ST33ZP24_SPI=y CONFIG_TELCLOCK=m # CONFIG_XILLYBUS is not set # CONFIG_XILLYUSB is not set @@ -8249,6 +8249,13 @@ CONFIG_IMA_TRUSTED_KEYRING=y CONFIG_IMA_LOAD_X509=y CONFIG_IMA_X509_PATH="/etc/keys/x509_ima.der" # CONFIG_IMA_APPRAISE_SIGNED_INIT is not set +CONFIG_IMA_DIGEST_LIST=y +CONFIG_IMA_DIGEST_LISTS_DIR="/etc/ima/digest_lists" +CONFIG_IMA_STANDARD_DIGEST_DB_SIZE=y +# CONFIG_IMA_MAX_DIGEST_DB_SIZE is not set +# CONFIG_IMA_CUSTOM_DIGEST_DB_SIZE is not set +CONFIG_IMA_DIGEST_DB_MEGABYTES=16 +CONFIG_IMA_PARSER_BINARY_PATH="/usr/bin/upload_digest_lists" CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS=y CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS=y # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set @@ -8539,6 +8546,9 @@ CONFIG_PKCS7_MESSAGE_PARSER=y # CONFIG_PKCS7_TEST_KEY is not set CONFIG_SIGNED_PE_FILE_VERIFICATION=y # CONFIG_FIPS_SIGNATURE_SELFTEST is not set +CONFIG_PGP_LIBRARY=y +CONFIG_PGP_KEY_PARSER=y +CONFIG_PGP_PRELOAD=y # # Certificates for signature checking @@ -8555,6 +8565,7 @@ CONFIG_SYSTEM_BLACKLIST_HASH_LIST="" CONFIG_SYSTEM_REVOCATION_LIST=y CONFIG_SYSTEM_REVOCATION_KEYS="" # CONFIG_SYSTEM_BLACKLIST_AUTH_UPDATE is not set +CONFIG_PGP_PRELOAD_PUBLIC_KEYS=y # end of Certificates for signature checking CONFIG_BINARY_PRINTF=y diff --git a/certs/Kconfig b/certs/Kconfig index 1f109b0708778bcc051b3840893dd1921acb92a5..3038adbaf58d1e639aae4168d8cb5ae4b38e56e4 100644 --- a/certs/Kconfig +++ b/certs/Kconfig @@ -138,4 +138,11 @@ config SYSTEM_BLACKLIST_AUTH_UPDATE keyring. The PKCS#7 signature of the description is set in the key payload. Blacklist keys cannot be removed. +config PGP_PRELOAD_PUBLIC_KEYS + bool "Preload PGP public keys" + select PGP_PRELOAD + default n + help + Provide a keyring of PGP public keys. + endmenu diff --git a/certs/Makefile b/certs/Makefile index 799ad7b9e68a0f684794a472c77ffdd706eef292..ab6da6f4e9530e1f3eb29d935355cdedd24c3864 100644 --- a/certs/Makefile +++ b/certs/Makefile @@ -65,6 +65,13 @@ endif # CONFIG_MODULE_SIG_KEY $(obj)/system_certificates.o: $(obj)/signing_key.x509 +ifdef CONFIG_PGP_PRELOAD_PUBLIC_KEYS +ifeq ($(shell ls $(srctree)/certs/pubring.gpg 2> /dev/null), $(srctree)/certs/pubring.gpg) +system_certificates.o += -DHAVE_PUBRING_GPG +$(obj)/system_certificates.o: $(srctree)/certs/pubring.gpg +endif +endif + PKCS11_URI := $(filter pkcs11:%, $(CONFIG_MODULE_SIG_KEY)) ifdef PKCS11_URI $(obj)/signing_key.x509: extract-cert-in := $(PKCS11_URI) diff --git a/certs/system_certificates.S b/certs/system_certificates.S index 003e25d4a17e297838ea0c9c8391b81af6449c98..b3cbf0811e3f95e0c47bbf004a1842e4c35568cf 100644 --- a/certs/system_certificates.S +++ b/certs/system_certificates.S @@ -44,3 +44,21 @@ module_cert_size: #else .long __module_cert_end - __module_cert_start #endif + + .align 8 + .globl pgp_public_keys +pgp_public_keys: +__pgp_key_list_start: +#ifdef HAVE_PUBRING_GPG + .incbin "certs/pubring.gpg" +#endif +__pgp_key_list_end: + + .align 8 + .globl pgp_public_keys_size +pgp_public_keys_size: +#ifdef CONFIG_64BIT + .quad __pgp_key_list_end - __pgp_key_list_start +#else + .long __pgp_key_list_end - __pgp_key_list_start +#endif diff --git a/certs/system_keyring.c b/certs/system_keyring.c index a7a49b17ceb1fe837b786597170826e87dac33e4..e9770f4e54491f8cb352cf4027c913833bb8bbd9 100644 --- a/certs/system_keyring.c +++ b/certs/system_keyring.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -216,6 +217,27 @@ static __init int load_system_certificate_list(void) } late_initcall(load_system_certificate_list); +#ifdef CONFIG_PGP_PRELOAD_PUBLIC_KEYS +extern __initconst const u8 pgp_public_keys[]; +extern __initconst const unsigned long pgp_public_keys_size; + +/* + * Load a list of PGP keys. + */ +static __init int load_pgp_public_keyring(void) +{ + pr_notice("Load PGP public keys\n"); + + if (preload_pgp_keys(pgp_public_keys, + pgp_public_keys_size, + builtin_trusted_keys) < 0) + pr_err("Can't load PGP public keys\n"); + + return 0; +} +late_initcall(load_pgp_public_keyring); +#endif /* CONFIG_PGP_PRELOAD_PUBLIC_KEYS */ + #ifdef CONFIG_SYSTEM_DATA_VERIFICATION /** @@ -339,6 +361,28 @@ int verify_pkcs7_signature(const void *data, size_t len, } EXPORT_SYMBOL_GPL(verify_pkcs7_signature); +struct key *search_trusted_key(struct key *trusted_keys, struct key_type *type, + char *name) +{ + key_ref_t kref; + + if (!trusted_keys) { + trusted_keys = builtin_trusted_keys; + } else if (trusted_keys == VERIFY_USE_SECONDARY_KEYRING) { +#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING + trusted_keys = secondary_trusted_keys; +#else + trusted_keys = builtin_trusted_keys; +#endif + } + kref = keyring_search(make_key_ref(trusted_keys, 1), type, name, true); + if (IS_ERR(kref)) + return ERR_CAST(kref); + + return key_ref_to_ptr(kref); +} +EXPORT_SYMBOL_GPL(search_trusted_key); + #endif /* CONFIG_SYSTEM_DATA_VERIFICATION */ #ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig index 1ef3b46d6f6e5ca2b2da611bf35dd9bb29b2c1cf..9b55ae747eafb97ede108f0ebb6026ffc988fd96 100644 --- a/crypto/asymmetric_keys/Kconfig +++ b/crypto/asymmetric_keys/Kconfig @@ -85,4 +85,29 @@ config FIPS_SIGNATURE_SELFTEST depends on ASYMMETRIC_KEY_TYPE depends on PKCS7_MESSAGE_PARSER=X509_CERTIFICATE_PARSER +config PGP_LIBRARY + tristate "PGP parsing library" + help + This option enables a library that provides a number of simple + utility functions for parsing PGP (RFC 4880) packet-based messages. + +config PGP_KEY_PARSER + tristate "PGP key parser" + depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select PGP_LIBRARY + select MD5 # V3 fingerprint generation + select SHA1 # V4 fingerprint generation + help + This option provides support for parsing PGP (RFC 4880) format blobs + for key data and provides the ability to instantiate a crypto key + from a public key packet found inside the blob. + +config PGP_PRELOAD + bool "PGP public key preloading facility" + select PGP_KEY_PARSER + help + This option provides a facility for the kernel to preload PGP-wrapped + bundles of keys during boot. It is used by module signing to load + the module signing keys for example. + endif # ASYMMETRIC_KEY_TYPE diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index 0d1fa1b692c6b23ae7508802b68fcf52d6dd9cd7..fd0b280892b6988714e3cc0a7718370d9869ca41 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -76,3 +76,13 @@ verify_signed_pefile-y := \ $(obj)/mscode_parser.o: $(obj)/mscode.asn1.h $(obj)/mscode.asn1.h $(obj)/mscode.asn1.o: $(obj)/mscode.asn1.c $(obj)/mscode.asn1.h + +# +# PGP handling +# +obj-$(CONFIG_PGP_LIBRARY) += pgp_library.o +obj-$(CONFIG_PGP_PRELOAD) += pgp_preload.o + +obj-$(CONFIG_PGP_KEY_PARSER) += pgp_key_parser.o +pgp_key_parser-y := \ + pgp_public_key.o diff --git a/crypto/asymmetric_keys/pgp_library.c b/crypto/asymmetric_keys/pgp_library.c new file mode 100644 index 0000000000000000000000000000000000000000..be307f426f75ae0912117e2d044eb461b3cba619 --- /dev/null +++ b/crypto/asymmetric_keys/pgp_library.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 +/* PGP packet parser (RFC 4880) + * + * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PGPL: "fmt +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); + +const char *const pgp_hash_algorithms[PGP_HASH__LAST] = { + [PGP_HASH_MD5] = "md5", + [PGP_HASH_SHA1] = "sha1", + [PGP_HASH_RIPE_MD_160] = "rmd160", + [PGP_HASH_SHA256] = "sha256", + [PGP_HASH_SHA384] = "sha384", + [PGP_HASH_SHA512] = "sha512", + [PGP_HASH_SHA224] = "sha224", +}; +EXPORT_SYMBOL_GPL(pgp_hash_algorithms); + +const char *pgp_to_public_key_algo[PGP_PUBKEY__LAST] = { + [PGP_PUBKEY_RSA_ENC_OR_SIG] = "rsa", + [PGP_PUBKEY_RSA_ENC_ONLY] = "rsa", + [PGP_PUBKEY_RSA_SIG_ONLY] = "rsa", + [PGP_PUBKEY_ELGAMAL] = NULL, + [PGP_PUBKEY_DSA] = NULL, +}; +EXPORT_SYMBOL_GPL(pgp_to_public_key_algo); + +/** + * pgp_parse_packet_header - Parse a PGP packet header + * @_data: Start of the PGP packet (updated to PGP packet data) + * @_datalen: Amount of data remaining in buffer (decreased) + * @_type: Where the packet type will be returned + * @_headerlen: Where the header length will be returned + * + * Parse a set of PGP packet header [RFC 4880: 4.2]. + * + * Returns packet data size on success; non-zero on error. If successful, + * *_data and *_datalen will have been updated and *_headerlen will be set to + * hold the length of the packet header. + */ +static ssize_t pgp_parse_packet_header(const u8 **_data, size_t *_datalen, + enum pgp_packet_tag *_type, + u8 *_headerlen) +{ + enum pgp_packet_tag type; + const u8 *data = *_data; + size_t size, datalen = *_datalen; + + pr_devel("-->%s(,%zu,,)\n", __func__, datalen); + + if (datalen < 2) + goto short_packet; + + pr_devel("pkthdr %02x, %02x\n", data[0], data[1]); + + type = *data++; + datalen--; + if (!(type & 0x80)) { + pr_debug("Packet type does not have MSB set\n"); + return -EBADMSG; + } + type &= ~0x80; + + if (type & 0x40) { + /* New packet length format */ + type &= ~0x40; + pr_devel("new format: t=%u\n", type); + switch (data[0]) { + case 0x00 ... 0xbf: + /* One-byte length */ + size = data[0]; + data++; + datalen--; + *_headerlen = 2; + break; + case 0xc0 ... 0xdf: + /* Two-byte length */ + if (datalen < 2) + goto short_packet; + size = (data[0] - 192) * 256; + size += data[1] + 192; + data += 2; + datalen -= 2; + *_headerlen = 3; + break; + case 0xff: + /* Five-byte length */ + if (datalen < 5) + goto short_packet; + size = data[1] << 24; + size |= data[2] << 16; + size |= data[3] << 8; + size |= data[4]; + data += 5; + datalen -= 5; + *_headerlen = 6; + break; + default: + pr_debug("Partial body length packet not supported\n"); + return -EBADMSG; + } + } else { + /* Old packet length format */ + u8 length_type = type & 0x03; + + type >>= 2; + pr_devel("old format: t=%u lt=%u\n", type, length_type); + + switch (length_type) { + case 0: + /* One-byte length */ + size = data[0]; + data++; + datalen--; + *_headerlen = 2; + break; + case 1: + /* Two-byte length */ + if (datalen < 2) + goto short_packet; + size = data[0] << 8; + size |= data[1]; + data += 2; + datalen -= 2; + *_headerlen = 3; + break; + case 2: + /* Four-byte length */ + if (datalen < 4) + goto short_packet; + size = data[0] << 24; + size |= data[1] << 16; + size |= data[2] << 8; + size |= data[3]; + data += 4; + datalen -= 4; + *_headerlen = 5; + break; + default: + pr_debug("Indefinite length packet not supported\n"); + return -EBADMSG; + } + } + + pr_devel("datalen=%zu size=%zu", datalen, size); + if (datalen < size) + goto short_packet; + if ((int)size < 0) + goto too_big; + + *_data = data; + *_datalen = datalen; + *_type = type; + pr_devel("Found packet type=%u size=%zd\n", type, size); + return size; + +short_packet: + pr_debug("Attempt to parse short packet\n"); + return -EBADMSG; +too_big: + pr_debug("Signature subpacket size >2G\n"); + return -EMSGSIZE; +} + +/** + * pgp_parse_packets - Parse a set of PGP packets + * @_data: Data to be parsed (updated) + * @_datalen: Amount of data (updated) + * @ctx: Parsing context + * + * Parse a set of PGP packets [RFC 4880: 4]. + */ +int pgp_parse_packets(const u8 *data, size_t datalen, + struct pgp_parse_context *ctx) +{ + enum pgp_packet_tag type; + ssize_t pktlen; + u8 headerlen; + int ret; + + while (datalen > 2) { + pktlen = pgp_parse_packet_header(&data, &datalen, &type, + &headerlen); + if (pktlen < 0) + return pktlen; + + if ((ctx->types_of_interest >> type) & 1) { + ret = ctx->process_packet(ctx, type, headerlen, + data, pktlen); + if (ret < 0) + return ret; + } + data += pktlen; + datalen -= pktlen; + } + + if (datalen != 0) { + pr_debug("Excess octets in packet stream\n"); + return -EBADMSG; + } + + return 0; +} +EXPORT_SYMBOL_GPL(pgp_parse_packets); + +/** + * pgp_parse_public_key - Parse the common part of a PGP pubkey packet + * @_data: Content of packet (updated) + * @_datalen: Length of packet remaining (updated) + * @pk: Public key data + * + * Parse the common data struct for a PGP pubkey packet [RFC 4880: 5.5.2]. + */ +int pgp_parse_public_key(const u8 **_data, size_t *_datalen, + struct pgp_parse_pubkey *pk) +{ + const u8 *data = *_data; + size_t datalen = *_datalen; + unsigned int tmp; + + if (datalen < 12) { + pr_debug("Public key packet too short\n"); + return -EBADMSG; + } + + pk->version = *data++; + switch (pk->version) { + case PGP_KEY_VERSION_2: + case PGP_KEY_VERSION_3: + case PGP_KEY_VERSION_4: + break; + default: + pr_debug("Public key packet with unhandled version %d\n", + pk->version); + return -EBADMSG; + } + + tmp = *data++ << 24; + tmp |= *data++ << 16; + tmp |= *data++ << 8; + tmp |= *data++; + pk->creation_time = tmp; + if (pk->version == PGP_KEY_VERSION_4) { + pk->expires_at = 0; /* Have to get it from the selfsignature */ + } else { + unsigned short ndays; + + ndays = *data++ << 8; + ndays |= *data++; + if (ndays) + pk->expires_at = pk->creation_time + ndays * 86400UL; + else + pk->expires_at = 0; + datalen -= 2; + } + + pk->pubkey_algo = *data++; + datalen -= 6; + + pr_devel("%x,%x,%lx,%lx\n", + pk->version, pk->pubkey_algo, pk->creation_time, + pk->expires_at); + + *_data = data; + *_datalen = datalen; + return 0; +} +EXPORT_SYMBOL_GPL(pgp_parse_public_key); diff --git a/crypto/asymmetric_keys/pgp_parser.h b/crypto/asymmetric_keys/pgp_parser.h new file mode 100644 index 0000000000000000000000000000000000000000..56f9a2b48679e3de2b04206d3dcb622f8aadc4c5 --- /dev/null +++ b/crypto/asymmetric_keys/pgp_parser.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* PGP crypto data parser internal definitions + * + * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include + +#define kenter(FMT, ...) \ + pr_devel("==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + pr_devel("<== %s()"FMT"\n", __func__, ##__VA_ARGS__) + +/* + * pgp_library.c + */ +extern const char *pgp_to_public_key_algo[PGP_PUBKEY__LAST]; diff --git a/crypto/asymmetric_keys/pgp_preload.c b/crypto/asymmetric_keys/pgp_preload.c new file mode 100644 index 0000000000000000000000000000000000000000..418cd4785efbc1b5d07c2c0460d68fb1f5971704 --- /dev/null +++ b/crypto/asymmetric_keys/pgp_preload.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Cryptographic key request handling + * + * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + * + * See Documentation/security/keys-crypto.txt + */ + +#include +#include +#include +#include +#include +#include + +struct preload_pgp_keys_context { + struct pgp_parse_context pgp; + key_ref_t keyring; + const u8 *key_start; + const u8 *key_end; + bool found_key; +}; + +/* + * Create a key. + */ +static int __init create_pgp_key(struct preload_pgp_keys_context *ctx) +{ + key_ref_t key; + + key = key_create_or_update(ctx->keyring, + "asymmetric", + NULL, + ctx->key_start, + ctx->key_end - ctx->key_start, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA | + KEY_ALLOC_BUILT_IN | + KEY_ALLOC_BYPASS_RESTRICTION); + if (IS_ERR(key)) + return PTR_ERR(key); + + pr_notice("Loaded PGP key '%s'\n", + key_ref_to_ptr(key)->description); + + key_ref_put(key); + return 0; +} + +/* + * Extract a public key or subkey from the PGP stream. + */ +static int __init found_pgp_key(struct pgp_parse_context *context, + enum pgp_packet_tag type, u8 headerlen, + const u8 *data, size_t datalen) +{ + struct preload_pgp_keys_context *ctx = + container_of(context, struct preload_pgp_keys_context, pgp); + int ret; + + if (ctx->found_key) { + ctx->key_end = data - headerlen; + ret = create_pgp_key(ctx); + if (ret < 0) + return ret; + } + + ctx->key_start = data - headerlen; + ctx->found_key = true; + return 0; +} + +/** + * preload_pgp_keys - Load keys from a PGP keyring blob + * @pgpdata: The PGP keyring blob containing the keys. + * @pgpdatalen: The size of the @pgpdata blob. + * @keyring: The keyring to add the new keys to. + * + * Preload a pack of keys from a PGP keyring blob. + * + * The keys have their descriptions generated from the user ID and fingerprint + * in the PGP stream. Since keys can be matched on their key IDs independently + * of the key description, the description is mostly irrelevant apart from the + * fact that keys of the same description displace one another from a keyring. + * + * The caller should override the current creds if they want the keys to be + * owned by someone other than the current process's owner. Keys will not be + * accounted towards the owner's quota. + * + * This function may only be called whilst the kernel is booting. + */ +int __init preload_pgp_keys(const u8 *pgpdata, size_t pgpdatalen, + struct key *keyring) +{ + struct preload_pgp_keys_context ctx; + int ret; + + ctx.pgp.types_of_interest = (1 << PGP_PKT_PUBLIC_KEY); + ctx.pgp.process_packet = found_pgp_key; + ctx.keyring = make_key_ref(keyring, 1); + ctx.found_key = false; + + ret = pgp_parse_packets(pgpdata, pgpdatalen, &ctx.pgp); + if (ret < 0) + return ret; + + if (ctx.found_key) { + ctx.key_end = pgpdata + pgpdatalen; + return create_pgp_key(&ctx); + } + return 0; +} diff --git a/crypto/asymmetric_keys/pgp_public_key.c b/crypto/asymmetric_keys/pgp_public_key.c new file mode 100644 index 0000000000000000000000000000000000000000..27b9efeafc4fc70ae484b25f536a062257aae28d --- /dev/null +++ b/crypto/asymmetric_keys/pgp_public_key.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Instantiate a public key crypto key from PGP format data [RFC 4880] + * + * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PGP: "fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pgp_parser.h" + +#define MAX_MPI 5 + +MODULE_LICENSE("GPL"); + +static inline void digest_putc(struct shash_desc *digest, uint8_t ch) +{ + crypto_shash_update(digest, &ch, 1); +} + +struct pgp_key_data_parse_context { + struct pgp_parse_context pgp; + struct public_key *pub; + unsigned char *raw_fingerprint; + char *fingerprint; + const char *user_id; + size_t user_id_len; + size_t fingerprint_len; +}; + +/* + * Calculate the public key ID (RFC4880 12.2) + */ +static int pgp_calc_pkey_keyid(struct shash_desc *digest, + struct pgp_parse_pubkey *pgp, + struct public_key *pub) +{ + unsigned int nb[MAX_MPI]; + unsigned int nn[MAX_MPI]; + unsigned int n; + size_t keylen = pub->keylen; + u8 *key_ptr = pub->key; + u8 *pp[MAX_MPI]; + u32 a32; + int npkey; + int i, ret; + + kenter(""); + + n = (pgp->version < PGP_KEY_VERSION_4) ? 8 : 6; + for (i = 0; i < MAX_MPI && keylen > 0; i++) { + ret = mpi_key_length(key_ptr, keylen, nb + i, nn + i); + if (ret < 0) + return ret; + + pp[i] = key_ptr + 2; + key_ptr += 2 + nn[i]; + keylen -= 2 + nn[i]; + n += 2 + nn[i]; + } + + if (keylen != 0) { + pr_debug("excess %zu\n", keylen); + return -EBADMSG; + } + + npkey = i; + + digest_putc(digest, 0x99); /* ctb */ + digest_putc(digest, n >> 8); /* 16-bit header length */ + digest_putc(digest, n); + digest_putc(digest, pgp->version); + + a32 = pgp->creation_time; + digest_putc(digest, a32 >> 24); + digest_putc(digest, a32 >> 16); + digest_putc(digest, a32 >> 8); + digest_putc(digest, a32 >> 0); + + if (pgp->version < PGP_KEY_VERSION_4) { + u16 a16; + + if (pgp->expires_at) + a16 = (pgp->expires_at - pgp->creation_time) / 86400UL; + else + a16 = 0; + digest_putc(digest, a16 >> 8); + digest_putc(digest, a16 >> 0); + } + + digest_putc(digest, pgp->pubkey_algo); + + for (i = 0; i < npkey; i++) { + digest_putc(digest, nb[i] >> 8); + digest_putc(digest, nb[i]); + crypto_shash_update(digest, pp[i], nn[i]); + } + ret = 0; + + kleave(" = %d", ret); + return ret; +} + +/* + * Calculate the public key ID fingerprint + */ +static int pgp_generate_fingerprint(struct pgp_key_data_parse_context *ctx, + struct pgp_parse_pubkey *pgp, + struct public_key *pub) +{ + struct crypto_shash *tfm; + struct shash_desc *digest; + char *fingerprint; + u8 *raw_fingerprint; + int digest_size, offset; + int ret, i; + + ret = -ENOMEM; + tfm = crypto_alloc_shash(pgp->version < PGP_KEY_VERSION_4 ? + "md5" : "sha1", 0, 0); + if (!tfm) + goto cleanup; + + digest = kmalloc(sizeof(*digest) + crypto_shash_descsize(tfm), + GFP_KERNEL); + if (!digest) + goto cleanup_tfm; + + digest->tfm = tfm; + crypto_shash_set_flags(digest->tfm, CRYPTO_TFM_REQ_MAY_SLEEP); + ret = crypto_shash_init(digest); + if (ret < 0) + goto cleanup_hash; + + ret = pgp_calc_pkey_keyid(digest, pgp, pub); + if (ret < 0) + goto cleanup_hash; + + digest_size = crypto_shash_digestsize(tfm); + + raw_fingerprint = kmalloc(digest_size, GFP_KERNEL); + if (!raw_fingerprint) + goto cleanup_hash; + + ret = crypto_shash_final(digest, raw_fingerprint); + if (ret < 0) + goto cleanup_raw_fingerprint; + + ctx->fingerprint_len = digest_size * 2; + fingerprint = kmalloc(digest_size * 2 + 1, GFP_KERNEL); + if (!fingerprint) + goto cleanup_raw_fingerprint; + + offset = digest_size - 8; + pr_debug("offset %u/%u\n", offset, digest_size); + + for (i = 0; i < digest_size; i++) + sprintf(fingerprint + i * 2, "%02x", raw_fingerprint[i]); + pr_debug("fingerprint %s\n", fingerprint); + + ctx->raw_fingerprint = raw_fingerprint; + ctx->fingerprint = fingerprint; + ret = 0; +cleanup_raw_fingerprint: + if (ret < 0) + kfree(raw_fingerprint); +cleanup_hash: + kfree(digest); +cleanup_tfm: + crypto_free_shash(tfm); +cleanup: + kleave(" = %d", ret); + return ret; +} + +/* + * Extract a public key or public subkey from the PGP stream. + */ +static int pgp_process_public_key(struct pgp_parse_context *context, + enum pgp_packet_tag type, + u8 headerlen, + const u8 *data, + size_t datalen) +{ + const char *algo; + struct pgp_key_data_parse_context *ctx = + container_of(context, struct pgp_key_data_parse_context, pgp); + struct pgp_parse_pubkey pgp; + struct public_key *pub; + int ret; + + kenter(",%u,%u,,%zu", type, headerlen, datalen); + + if (type == PGP_PKT_USER_ID) { + ctx->user_id = data; + ctx->user_id_len = datalen; + kleave(" = 0 [user ID]"); + return 0; + } + + if (ctx->fingerprint) { + kleave(" = -ENOKEY [already]"); + return -EBADMSG; + } + + pub = kzalloc(sizeof(struct public_key), GFP_KERNEL); + if (!pub) + return -ENOMEM; + pub->id_type = "PGP"; + + ret = pgp_parse_public_key(&data, &datalen, &pgp); + if (ret < 0) + goto cleanup; + + if (pgp.pubkey_algo >= PGP_PUBKEY__LAST) + goto cleanup_unsupported_pkey_algo; + algo = pgp_to_public_key_algo[pgp.pubkey_algo]; + if (!algo) + goto cleanup_unsupported_pkey_algo; + pub->pkey_algo = algo; + + pub->key = kmemdup(data, datalen, GFP_KERNEL); + if (!pub->key) + goto cleanup_nomem; + + pub->keylen = datalen; + + ret = pgp_generate_fingerprint(ctx, &pgp, pub); + if (ret < 0) + goto cleanup; + + ctx->pub = pub; + kleave(" = 0 [use]"); + return 0; + +cleanup_unsupported_pkey_algo: + pr_debug("Unsupported public key algorithm %u\n", + pgp.pubkey_algo); + ret = -ENOPKG; + goto cleanup; +cleanup_nomem: + ret = -ENOMEM; + goto cleanup; +cleanup: + pr_devel("cleanup"); + kfree(pub->key); + kfree(pub); + kleave(" = %d", ret); + return ret; +} + +static struct asymmetric_key_ids *pgp_key_generate_id( + struct pgp_key_data_parse_context *ctx) +{ + struct asymmetric_key_ids *kids; + struct asymmetric_key_id *kid; + int fingerprint_len = strlen(ctx->fingerprint) / 2; + + kids = kzalloc(sizeof(struct asymmetric_key_ids), GFP_KERNEL); + if (!kids) + return kids; + + kid = asymmetric_key_generate_id(ctx->raw_fingerprint, fingerprint_len, + NULL, 0); + if (IS_ERR(kid)) + goto error; + + kids->id[0] = kid; + kids->id[1] = kmemdup(kid, sizeof(kid) + fingerprint_len, GFP_KERNEL); + if (!kids->id[1]) + goto error; + + return kids; +error: + kfree(kids->id[0]); + kfree(kids); + + return NULL; +} + +/* + * Attempt to parse the instantiation data blob for a key as a PGP packet + * message holding a key. + */ +static int pgp_key_parse(struct key_preparsed_payload *prep) +{ + struct pgp_key_data_parse_context ctx; + int ret; + + kenter(""); + + memset(&ctx, 0, sizeof(ctx)); + ctx.pgp.types_of_interest = (1 << PGP_PKT_PUBLIC_KEY) | + (1 << PGP_PKT_USER_ID); + ctx.pgp.process_packet = pgp_process_public_key; + + ret = pgp_parse_packets(prep->data, prep->datalen, &ctx.pgp); + if (ret < 0) + goto error; + + if (ctx.user_id && ctx.user_id_len > 0) { + /* Propose a description for the key + * (user ID without the comment) + */ + size_t ulen = ctx.user_id_len, flen = ctx.fingerprint_len; + const char *p; + + p = memchr(ctx.user_id, '(', ulen); + if (p) { + /* Remove the comment */ + do { + p--; + } while (*p == ' ' && p > ctx.user_id); + if (*p != ' ') + p++; + ulen = p - ctx.user_id; + } + + if (ulen > 255 - 9) + ulen = 255 - 9; + prep->description = kmalloc(ulen + 1 + 8 + 1, GFP_KERNEL); + ret = -ENOMEM; + if (!prep->description) + goto error; + memcpy(prep->description, ctx.user_id, ulen); + prep->description[ulen] = ' '; + memcpy(prep->description + ulen + 1, + ctx.fingerprint + flen - 8, 8); + prep->description[ulen + 9] = 0; + pr_debug("desc '%s'\n", prep->description); + } + + /* We're pinning the module by being linked against it */ + __module_get(public_key_subtype.owner); + prep->payload.data[asym_subtype] = &public_key_subtype; + prep->payload.data[asym_key_ids] = pgp_key_generate_id(&ctx); + prep->payload.data[asym_crypto] = ctx.pub; + prep->quotalen = 100; + kfree(ctx.fingerprint); + kfree(ctx.raw_fingerprint); + return 0; + +error: + public_key_free(ctx.pub); + kfree(ctx.fingerprint); + kfree(ctx.raw_fingerprint); + return ret; +} + +static struct asymmetric_key_parser pgp_key_parser = { + .owner = THIS_MODULE, + .name = "pgp", + .parse = pgp_key_parse, +}; + +/* + * Module stuff + */ +static int __init pgp_key_init(void) +{ + return register_asymmetric_key_parser(&pgp_key_parser); +} + +static void __exit pgp_key_exit(void) +{ + unregister_asymmetric_key_parser(&pgp_key_parser); +} + +module_init(pgp_key_init); +module_exit(pgp_key_exit); diff --git a/crypto/rsa.c b/crypto/rsa.c index c50f2d2a4d0649fd01566b5438d91c7e324805dc..0ca65c1db3a94fa8d83c877e89f4bc4c69c1948c 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -216,8 +216,11 @@ static int rsa_set_pub_key(struct crypto_akcipher *tfm, const void *key, rsa_free_mpi_key(mpi_key); ret = rsa_parse_pub_key(&raw_key, key, keylen); - if (ret) - return ret; + if (ret) { + ret = rsa_parse_pub_key_raw(&raw_key, key, keylen); + if (ret) + return ret; + } mpi_key->e = mpi_read_raw_data(raw_key.e, raw_key.e_sz); if (!mpi_key->e) @@ -250,8 +253,11 @@ static int rsa_set_priv_key(struct crypto_akcipher *tfm, const void *key, rsa_free_mpi_key(mpi_key); ret = rsa_parse_priv_key(&raw_key, key, keylen); - if (ret) - return ret; + if (ret) { + ret = rsa_parse_priv_key_raw(&raw_key, key, keylen); + if (ret) + return ret; + } mpi_key->d = mpi_read_raw_data(raw_key.d, raw_key.d_sz); if (!mpi_key->d) diff --git a/crypto/rsa_helper.c b/crypto/rsa_helper.c index 94266f29049c925bcc9d7c555d383aea802b5466..fb9443df8f0b11f2925d3fe864537c0d5fc14b6e 100644 --- a/crypto/rsa_helper.c +++ b/crypto/rsa_helper.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "rsapubkey.asn1.h" #include "rsaprivkey.asn1.h" @@ -148,6 +149,32 @@ int rsa_get_qinv(void *context, size_t hdrlen, unsigned char tag, return 0; } +typedef int (*rsa_get_func)(void *, size_t, unsigned char, + const void *, size_t); + +static int rsa_parse_key_raw(struct rsa_key *rsa_key, + const void *key, unsigned int key_len, + rsa_get_func *func, int n_func) +{ + unsigned int nbytes, len = key_len; + const void *key_ptr = key; + int ret, i; + + for (i = 0; i < n_func; i++) { + ret = mpi_key_length(key_ptr, len, NULL, &nbytes); + if (ret < 0) + return ret; + + ret = func[i](rsa_key, 0, 0, key_ptr + 2, nbytes); + if (ret < 0) + return ret; + + key_ptr += nbytes + 2; + } + + return (key_ptr == key + key_len) ? 0 : -EINVAL; +} + /** * rsa_parse_pub_key() - decodes the BER encoded buffer and stores in the * provided struct rsa_key, pointers to the raw key as is, @@ -166,6 +193,27 @@ int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key, } EXPORT_SYMBOL_GPL(rsa_parse_pub_key); +/** + * rsa_parse_pub_key_raw() - parse the RAW key and store in the provided struct + * rsa_key, pointers to the raw key as is, so that + * the caller can copy it or MPI parse it, etc. + * + * @rsa_key: struct rsa_key key representation + * @key: key in RAW format + * @key_len: length of key + * + * Return: 0 on success or error code in case of error + */ +int rsa_parse_pub_key_raw(struct rsa_key *rsa_key, const void *key, + unsigned int key_len) +{ + rsa_get_func pub_func[] = {rsa_get_n, rsa_get_e}; + + return rsa_parse_key_raw(rsa_key, key, key_len, + pub_func, ARRAY_SIZE(pub_func)); +} +EXPORT_SYMBOL_GPL(rsa_parse_pub_key_raw); + /** * rsa_parse_priv_key() - decodes the BER encoded buffer and stores in the * provided struct rsa_key, pointers to the raw key @@ -184,3 +232,24 @@ int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key, return asn1_ber_decoder(&rsaprivkey_decoder, rsa_key, key, key_len); } EXPORT_SYMBOL_GPL(rsa_parse_priv_key); + +/** + * rsa_parse_priv_key_raw() - parse the RAW key and store in the provided struct + * rsa_key, pointers to the raw key as is, so that + * the caller can copy it or MPI parse it, etc. + * + * @rsa_key: struct rsa_key key representation + * @key: key in RAW format + * @key_len: length of key + * + * Return: 0 on success or error code in case of error + */ +int rsa_parse_priv_key_raw(struct rsa_key *rsa_key, const void *key, + unsigned int key_len) +{ + rsa_get_func priv_func[] = {rsa_get_n, rsa_get_e, rsa_get_d}; + + return rsa_parse_key_raw(rsa_key, key, key_len, + priv_func, ARRAY_SIZE(priv_func)); +} +EXPORT_SYMBOL_GPL(rsa_parse_priv_key_raw); diff --git a/include/crypto/internal/rsa.h b/include/crypto/internal/rsa.h index e870133f4b7751879d03b67820291c1241546a80..7141e806ceead5960f94058c87e760bc29e69642 100644 --- a/include/crypto/internal/rsa.h +++ b/include/crypto/internal/rsa.h @@ -50,8 +50,14 @@ struct rsa_key { int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key, unsigned int key_len); +int rsa_parse_pub_key_raw(struct rsa_key *rsa_key, const void *key, + unsigned int key_len); + int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key, unsigned int key_len); +int rsa_parse_priv_key_raw(struct rsa_key *rsa_key, const void *key, + unsigned int key_len); + extern struct crypto_template rsa_pkcs1pad_tmpl; #endif diff --git a/include/linux/initramfs.h b/include/linux/initramfs.h new file mode 100644 index 0000000000000000000000000000000000000000..2f8cee441236bf83473c26e8de41cf3d23ac59c3 --- /dev/null +++ b/include/linux/initramfs.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * include/linux/initramfs.h + * + * Include file for file metadata in the initial ram disk. + */ +#ifndef _LINUX_INITRAMFS_H +#define _LINUX_INITRAMFS_H + +#define METADATA_FILENAME "METADATA!!!" + +enum metadata_types { TYPE_NONE, TYPE_XATTR, TYPE__LAST }; + +struct metadata_hdr { + char c_size[8]; /* total size including c_size field */ + char c_version; /* header version */ + char c_type; /* metadata type */ + char c_metadata[]; /* metadata */ +} __packed; + +#endif /*LINUX_INITRAMFS_H*/ diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 90451e2e12bd19eeb436ad59ad83d7ab1168e26b..a7724cd7c870f4e5c023cb6d562c90405d0f3159 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -13,6 +13,7 @@ id(KEXEC_IMAGE, kexec-image) \ id(KEXEC_INITRAMFS, kexec-initramfs) \ id(POLICY, security-policy) \ + id(DIGEST_LIST, digest-list) \ id(X509_CERTIFICATE, x509-certificate) \ id(MAX_ID, ) diff --git a/include/linux/mpi.h b/include/linux/mpi.h index eb0d1c1db208eb8d06c62a26b38954d9e3d55286..a7dd4c9d8120c70cbd85f05c8361c85d406a8391 100644 --- a/include/linux/mpi.h +++ b/include/linux/mpi.h @@ -90,6 +90,8 @@ enum gcry_mpi_format { }; MPI mpi_read_raw_data(const void *xbuffer, size_t nbytes); +int mpi_key_length(const void *xbuffer, unsigned int ret_nread, + unsigned int *nbits_arg, unsigned int *nbytes_arg); MPI mpi_read_from_buffer(const void *buffer, unsigned *ret_nread); int mpi_fromstr(MPI val, const char *str); MPI mpi_scanval(const char *string); diff --git a/include/linux/pgp.h b/include/linux/pgp.h new file mode 100644 index 0000000000000000000000000000000000000000..9104939c477058acebad26b35768b810db8999ae --- /dev/null +++ b/include/linux/pgp.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* PGP definitions (RFC 4880) + * + * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#ifndef _LINUX_PGP_H +#define _LINUX_PGP_H + +#include +#include + +struct pgp_key_ID { + u8 id[8]; +} __packed; + +struct pgp_time { + u8 time[4]; +} __packed; + +/* + * PGP public-key algorithm identifiers [RFC4880: 9.1] + */ +enum pgp_pubkey_algo { + PGP_PUBKEY_RSA_ENC_OR_SIG = 1, + PGP_PUBKEY_RSA_ENC_ONLY = 2, + PGP_PUBKEY_RSA_SIG_ONLY = 3, + PGP_PUBKEY_ELGAMAL = 16, + PGP_PUBKEY_DSA = 17, + PGP_PUBKEY__LAST +}; + +/* + * PGP symmetric-key algorithm identifiers [RFC4880: 9.2] + */ +enum pgp_symkey_algo { + PGP_SYMKEY_PLAINTEXT = 0, + PGP_SYMKEY_IDEA = 1, + PGP_SYMKEY_3DES = 2, + PGP_SYMKEY_CAST5 = 3, + PGP_SYMKEY_BLOWFISH = 4, + PGP_SYMKEY_AES_128KEY = 7, + PGP_SYMKEY_AES_192KEY = 8, + PGP_SYMKEY_AES_256KEY = 9, + PGP_SYMKEY_TWOFISH_256KEY = 10, +}; + +/* + * PGP compression algorithm identifiers [RFC4880: 9.3] + */ +enum pgp_compr_algo { + PGP_COMPR_UNCOMPRESSED = 0, + PGP_COMPR_ZIP = 1, + PGP_COMPR_ZLIB = 2, + PGP_COMPR_BZIP2 = 3, +}; + +/* + * PGP hash algorithm identifiers [RFC4880: 9.4] + */ +enum pgp_hash_algo { + PGP_HASH_MD5 = 1, + PGP_HASH_SHA1 = 2, + PGP_HASH_RIPE_MD_160 = 3, + PGP_HASH_SHA256 = 8, + PGP_HASH_SHA384 = 9, + PGP_HASH_SHA512 = 10, + PGP_HASH_SHA224 = 11, + PGP_HASH__LAST +}; + +extern const char *const pgp_hash_algorithms[PGP_HASH__LAST]; + +/* + * PGP packet type tags [RFC4880: 4.3]. + */ +enum pgp_packet_tag { + PGP_PKT_RESERVED = 0, + PGP_PKT_PUBKEY_ENC_SESSION_KEY = 1, + PGP_PKT_SIGNATURE = 2, + PGP_PKT_SYMKEY_ENC_SESSION_KEY = 3, + PGP_PKT_ONEPASS_SIGNATURE = 4, + PGP_PKT_SECRET_KEY = 5, + PGP_PKT_PUBLIC_KEY = 6, + PGP_PKT_SECRET_SUBKEY = 7, + PGP_PKT_COMPRESSED_DATA = 8, + PGP_PKT_SYM_ENC_DATA = 9, + PGP_PKT_MARKER = 10, + PGP_PKT_LITERAL_DATA = 11, + PGP_PKT_TRUST = 12, + PGP_PKT_USER_ID = 13, + PGP_PKT_PUBLIC_SUBKEY = 14, + PGP_PKT_USER_ATTRIBUTE = 17, + PGP_PKT_SYM_ENC_AND_INTEG_DATA = 18, + PGP_PKT_MODIFY_DETECT_CODE = 19, + PGP_PKT_PRIVATE_0 = 60, + PGP_PKT_PRIVATE_3 = 63, + PGP_PKT__HIGHEST = 63 +}; + +/* + * Signature (tag 2) packet [RFC4880: 5.2]. + */ +enum pgp_signature_version { + PGP_SIG_VERSION_3 = 3, + PGP_SIG_VERSION_4 = 4, +}; + +enum pgp_signature_type { + PGP_SIG_BINARY_DOCUMENT_SIG = 0x00, + PGP_SIG_CANONICAL_TEXT_DOCUMENT_SIG = 0x01, + PGP_SIG_STANDALONE_SIG = 0x02, + PGP_SIG_GENERAL_CERT_OF_UID_PUBKEY = 0x10, + PGP_SIG_PERSONAL_CERT_OF_UID_PUBKEY = 0x11, + PGP_SIG_CASUAL_CERT_OF_UID_PUBKEY = 0x12, + PGP_SIG_POSTITIVE_CERT_OF_UID_PUBKEY = 0x13, + PGP_SIG_SUBKEY_BINDING_SIG = 0x18, + PGP_SIG_PRIMARY_KEY_BINDING_SIG = 0x19, + PGP_SIG_DIRECTLY_ON_KEY = 0x1F, + PGP_SIG_KEY_REVOCATION_SIG = 0x20, + PGP_SIG_SUBKEY_REVOCATION_SIG = 0x28, + PGP_SIG_CERT_REVOCATION_SIG = 0x30, + PGP_SIG_TIMESTAMP_SIG = 0x40, + PGP_SIG_THIRD_PARTY_CONFIRM_SIG = 0x50, +}; + +struct pgp_signature_v3_packet { + enum pgp_signature_version version : 8; /* == PGP_SIG_VERSION_3 */ + u8 length_of_hashed; /* == 5 */ + struct { + enum pgp_signature_type signature_type : 8; + struct pgp_time creation_time; + } __packed hashed; + struct pgp_key_ID issuer; + enum pgp_pubkey_algo pubkey_algo : 8; + enum pgp_hash_algo hash_algo : 8; +} __packed; + +struct pgp_signature_v4_packet { + enum pgp_signature_version version : 8; /* == PGP_SIG_VERSION_4 */ + enum pgp_signature_type signature_type : 8; + enum pgp_pubkey_algo pubkey_algo : 8; + enum pgp_hash_algo hash_algo : 8; +} __packed; + +/* + * V4 signature subpacket types [RFC4880: 5.2.3.1]. + */ +enum pgp_sig_subpkt_type { + PGP_SIG_CREATION_TIME = 2, + PGP_SIG_EXPIRATION_TIME = 3, + PGP_SIG_EXPORTABLE_CERT = 4, + PGP_SIG_TRUST_SIG = 5, + PGP_SIG_REGEXP = 6, + PGP_SIG_REVOCABLE = 7, + PGP_SIG_KEY_EXPIRATION_TIME = 9, + PGP_SIG_PREF_SYM_ALGO = 11, + PGP_SIG_REVOCATION_KEY = 12, + PGP_SIG_ISSUER = 16, + PGP_SIG_NOTATION_DATA = 20, + PGP_SIG_PREF_HASH_ALGO = 21, + PGP_SIG_PREF_COMPR_ALGO = 22, + PGP_SIG_KEY_SERVER_PREFS = 23, + PGP_SIG_PREF_KEY_SERVER = 24, + PGP_SIG_PRIMARY_USER_ID = 25, + PGP_SIG_POLICY_URI = 26, + PGP_SIG_KEY_FLAGS = 27, + PGP_SIG_SIGNERS_USER_ID = 28, + PGP_SIG_REASON_FOR_REVOCATION = 29, + PGP_SIG_FEATURES = 30, + PGP_SIG_TARGET = 31, + PGP_SIG_EMBEDDED_SIG = 32, + PGP_SIG__LAST +}; + +#define PGP_SIG_SUBPKT_TYPE_CRITICAL_MASK 0x80 + +/* + * Key (tag 5, 6, 7 and 14) packet + */ +enum pgp_key_version { + PGP_KEY_VERSION_2 = 2, + PGP_KEY_VERSION_3 = 3, + PGP_KEY_VERSION_4 = 4, +}; + +struct pgp_key_v3_packet { + enum pgp_key_version version : 8; + struct pgp_time creation_time; + u8 expiry[2]; /* 0 or time in days till expiry */ + enum pgp_pubkey_algo pubkey_algo : 8; + u8 key_material[0]; +} __packed; + +struct pgp_key_v4_packet { + enum pgp_key_version version : 8; + struct pgp_time creation_time; + enum pgp_pubkey_algo pubkey_algo : 8; + u8 key_material[0]; +} __packed; + +/* + * Literal Data (tag 11) packet + */ +enum pgp_literal_data_format { + PGP_LIT_FORMAT_BINARY = 0x62, + PGP_LIT_FORMAT_TEXT = 0x74, + PGP_LIT_FORMAT_TEXT_UTF8 = 0x75, +}; + +int __init preload_pgp_keys(const u8 *pgpdata, size_t pgpdatalen, + struct key *keyring); + +#endif /* _LINUX_PGP_H */ diff --git a/include/linux/pgplib.h b/include/linux/pgplib.h new file mode 100644 index 0000000000000000000000000000000000000000..a4068bcef8c5f9e5094df3e46c74e4732eb18585 --- /dev/null +++ b/include/linux/pgplib.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* PGP library definitions (RFC 4880) + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#ifndef _LINUX_PGPLIB_H +#define _LINUX_PGPLIB_H + +#if IS_ENABLED(CONFIG_PGP_LIBRARY) || IS_ENABLED(CONFIG_PGP_LIBRARY_MODULE) + +#include + +/* + * PGP library packet parser + */ +struct pgp_parse_context { + u64 types_of_interest; + int (*process_packet)(struct pgp_parse_context *context, + enum pgp_packet_tag type, + u8 headerlen, + const u8 *data, + size_t datalen); +}; + +extern int pgp_parse_packets(const u8 *data, size_t datalen, + struct pgp_parse_context *ctx); + +struct pgp_parse_pubkey { + enum pgp_key_version version : 8; + enum pgp_pubkey_algo pubkey_algo : 8; + __kernel_old_time_t creation_time; + __kernel_old_time_t expires_at; +}; + +extern int pgp_parse_public_key(const u8 **_data, size_t *_datalen, + struct pgp_parse_pubkey *pk); + + +#endif /* CONFIG_PGP_LIBRARY */ + +#endif /* _LINUX_PGPLIB_H */ diff --git a/include/linux/verification.h b/include/linux/verification.h index f34e50ebcf60a4377cbcafbc8960d898265b9a9c..4eac691f82b6ecc6be3723e0629ca77c94107e91 100644 --- a/include/linux/verification.h +++ b/include/linux/verification.h @@ -9,6 +9,7 @@ #define _LINUX_VERIFICATION_H #include +#include /* * Indicate that both builtin trusted keys and secondary trusted keys @@ -68,5 +69,8 @@ extern int verify_pefile_signature(const void *pebuf, unsigned pelen, enum key_being_used_for usage); #endif +struct key *search_trusted_key(struct key *trusted_keys, struct key_type *type, + char *name); + #endif /* CONFIG_SYSTEM_DATA_VERIFICATION */ #endif /* _LINUX_VERIFY_PEFILE_H */ diff --git a/init/do_mounts.c b/init/do_mounts.c index 811e94daf0a84af833eda996af765c1f05926680..4dcc3af8295e4a867a056a733959fdef35f31f6d 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -31,6 +31,7 @@ int root_mountflags = MS_RDONLY | MS_SILENT; static char * __initdata root_device_name; static char __initdata saved_root_name[64]; static int root_wait; +static int initramtmpfs; dev_t ROOT_DEV; @@ -333,9 +334,16 @@ static int __init root_delay_setup(char *str) return 1; } +static int __init initramtmpfs_setup(char *str) +{ + initramtmpfs = 1; + return 1; +} + __setup("rootflags=", root_data_setup); __setup("rootfstype=", fs_names_setup); __setup("rootdelay=", root_delay_setup); +__setup("initramtmpfs", initramtmpfs_setup); /* This can return zero length strings. Caller should check */ static int __init split_fs_names(char *page, size_t size, char *names) @@ -665,7 +673,8 @@ struct file_system_type rootfs_fs_type = { void __init init_rootfs(void) { - if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] && + if (IS_ENABLED(CONFIG_TMPFS) && + (!saved_root_name[0] || initramtmpfs) && (!root_fs_names || strstr(root_fs_names, "tmpfs"))) is_tmpfs = true; } diff --git a/init/initramfs.c b/init/initramfs.c index e7a01c2ccd1b0c33f795dd94a0c58ab57092bb93..0311acda22096beb5e889441c22f02ebb1417092 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -177,7 +179,7 @@ static __initdata time64_t mtime; static __initdata unsigned long ino, major, minor, nlink; static __initdata umode_t mode; -static __initdata unsigned long body_len, name_len; +static __initdata unsigned long body_len, name_len, metadata_len; static __initdata uid_t uid; static __initdata gid_t gid; static __initdata unsigned rdev; @@ -250,7 +252,8 @@ static void __init read_into(char *buf, unsigned size, enum state next) } } -static __initdata char *header_buf, *symlink_buf, *name_buf; +static __initdata char *header_buf, *symlink_buf, *name_buf, *metadata_buf; +static __initdata char *metadata_buf_ptr, *previous_name_buf; static int __init do_start(void) { @@ -351,8 +354,91 @@ static int __init maybe_link(void) return 0; } +static int __init do_setxattrs(char *pathname, char *buf, size_t size) +{ + struct path path; + char *xattr_name, *xattr_value; + uint32_t xattr_name_size, xattr_value_size; + int ret; + + xattr_name = buf; + xattr_name_size = strnlen(xattr_name, size); + if (xattr_name_size == size) { + error("malformed xattrs"); + return -EINVAL; + } + + xattr_value = xattr_name + xattr_name_size + 1; + xattr_value_size = buf + size - xattr_value; + + ret = kern_path(pathname, 0, &path); + if (!ret) { + ret = vfs_setxattr(&nop_mnt_idmap, path.dentry, xattr_name, xattr_value, + xattr_value_size, 0); + + path_put(&path); + } + + pr_debug("%s: %s size: %u val: %s (ret: %d)\n", pathname, + xattr_name, xattr_value_size, xattr_value, ret); + + return ret; +} + +static int __init __maybe_unused do_parse_metadata(char *pathname) +{ + char *buf = metadata_buf; + char *bufend = metadata_buf + metadata_len; + struct metadata_hdr *hdr; + char str[sizeof(hdr->c_size) + 1]; + uint32_t entry_size; + + if (!metadata_len) + return 0; + + str[sizeof(hdr->c_size)] = 0; + + while (buf < bufend) { + int ret; + + if (buf + sizeof(*hdr) > bufend) { + error("malformed metadata"); + break; + } + + hdr = (struct metadata_hdr *)buf; + if (hdr->c_version != 1) { + pr_debug("Unsupported header version\n"); + break; + } + + memcpy(str, hdr->c_size, sizeof(hdr->c_size)); + ret = kstrtou32(str, 16, &entry_size); + if (ret || buf + entry_size > bufend || + entry_size < sizeof(*hdr)) { + error("malformed xattrs"); + break; + } + + switch (hdr->c_type) { + case TYPE_XATTR: + do_setxattrs(pathname, buf + sizeof(*hdr), + entry_size - sizeof(*hdr)); + break; + default: + pr_debug("Unsupported metadata type\n"); + break; + } + + buf += entry_size; + } + + return 0; +} + static __initdata struct file *wfile; static __initdata loff_t wfile_pos; +static int metadata __initdata; static int __init do_name(void) { @@ -361,6 +447,10 @@ static int __init do_name(void) if (strcmp(collected, "TRAILER!!!") == 0) { free_hash(); return 0; + } else if (strcmp(collected, METADATA_FILENAME) == 0) { + metadata = 1; + } else { + memcpy(previous_name_buf, collected, strlen(collected) + 1); } clean_path(collected, mode); if (S_ISREG(mode)) { @@ -398,11 +488,47 @@ static int __init do_name(void) return 0; } +static int __init do_process_metadata(char *buf, int len, bool last) +{ + int ret = 0; + + if (!metadata_buf) { + metadata_buf_ptr = metadata_buf = kmalloc(body_len, GFP_KERNEL); + if (!metadata_buf_ptr) { + ret = -ENOMEM; + goto out; + } + + metadata_len = body_len; + } + + if (metadata_buf_ptr + len > metadata_buf + metadata_len) { + ret = -EINVAL; + goto out; + } + + memcpy(metadata_buf_ptr, buf, len); + metadata_buf_ptr += len; + + if (last) + do_parse_metadata(previous_name_buf); +out: + if (ret < 0 || last) { + kfree(metadata_buf); + metadata_buf = NULL; + metadata = 0; + } + + return ret; +} + static int __init do_copy(void) { if (byte_count >= body_len) { if (xwrite(wfile, victim, body_len, &wfile_pos) != body_len) error("write error"); + if (metadata) + do_process_metadata(victim, body_len, true); do_utime_path(&wfile->f_path, mtime); fput(wfile); @@ -414,6 +540,8 @@ static int __init do_copy(void) } else { if (xwrite(wfile, victim, byte_count, &wfile_pos) != byte_count) error("write error"); + if (metadata) + do_process_metadata(victim, byte_count, false); body_len -= byte_count; eat(byte_count); return 1; @@ -423,6 +551,7 @@ static int __init do_copy(void) static int __init do_symlink(void) { collected[N_ALIGN(name_len) + body_len] = '\0'; + memcpy(previous_name_buf, collected, strlen(collected) + 1); clean_path(collected, 0); init_symlink(collected + N_ALIGN(name_len), collected); init_chown(collected, uid, gid, AT_SYMLINK_NOFOLLOW); @@ -490,8 +619,10 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len) header_buf = kmalloc(110, GFP_KERNEL); symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL); name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL); + previous_name_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, + GFP_KERNEL); - if (!header_buf || !symlink_buf || !name_buf) + if (!header_buf || !symlink_buf || !name_buf || !previous_name_buf) panic_show_mem("can't allocate buffers"); state = Start; @@ -536,6 +667,7 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len) len -= my_inptr; } dir_utime(); + kfree(previous_name_buf); kfree(name_buf); kfree(symlink_buf); kfree(header_buf); diff --git a/lib/mpi/mpicoder.c b/lib/mpi/mpicoder.c index 3cb6bd148fa9e38e8634c592abb8bd85e142c480..c7461fa9d27271fed9c4b7a01a42f8f68857db60 100644 --- a/lib/mpi/mpicoder.c +++ b/lib/mpi/mpicoder.c @@ -79,22 +79,41 @@ MPI mpi_read_raw_data(const void *xbuffer, size_t nbytes) } EXPORT_SYMBOL_GPL(mpi_read_raw_data); -MPI mpi_read_from_buffer(const void *xbuffer, unsigned *ret_nread) +int mpi_key_length(const void *xbuffer, unsigned int ret_nread, + unsigned int *nbits_arg, unsigned int *nbytes_arg) { const uint8_t *buffer = xbuffer; - unsigned int nbits, nbytes; - MPI val; + unsigned int nbits; - if (*ret_nread < 2) - return ERR_PTR(-EINVAL); + if (ret_nread < 2) + return -EINVAL; nbits = buffer[0] << 8 | buffer[1]; if (nbits > MAX_EXTERN_MPI_BITS) { pr_info("MPI: mpi too large (%u bits)\n", nbits); - return ERR_PTR(-EINVAL); + return -EINVAL; } - nbytes = DIV_ROUND_UP(nbits, 8); + if (nbits_arg) + *nbits_arg = nbits; + if (nbytes_arg) + *nbytes_arg = DIV_ROUND_UP(nbits, 8); + + return 0; +} +EXPORT_SYMBOL_GPL(mpi_key_length); + +MPI mpi_read_from_buffer(const void *xbuffer, unsigned *ret_nread) +{ + const uint8_t *buffer = xbuffer; + unsigned int nbytes; + MPI val; + int ret; + + ret = mpi_key_length(xbuffer, *ret_nread, NULL, &nbytes); + if (ret < 0) + return ERR_PTR(ret); + if (nbytes + 2 > *ret_nread) { pr_info("MPI: mpi larger than buffer nbytes=%u ret_nread=%u\n", nbytes, *ret_nread); diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index 895f4b9ce8c6b2ea2785a2c8298ec11528013185..8c04cb433840fd0a3dcfde5e13ee90e2704292cc 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,15 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid) key = request_key(&key_type_asymmetric, name, NULL); } + if (IS_ERR(key)) { +#ifdef CONFIG_IMA_KEYRINGS_PERMIT_SIGNED_BY_BUILTIN_OR_SECONDARY + keyring = VERIFY_USE_SECONDARY_KEYRING; +#else + keyring = NULL; +#endif + key = search_trusted_key(keyring, &key_type_asymmetric, name); + } + if (IS_ERR(key)) { if (keyring) pr_err_ratelimited("Request for unknown key '%s' in '%s' keyring. err %ld\n", diff --git a/security/integrity/evm/Kconfig b/security/integrity/evm/Kconfig index a6e19d23e7006208a4ebeee39c680c5c2ba962e7..844f2686cce759c8b90cd531284eff5d3c51c5f1 100644 --- a/security/integrity/evm/Kconfig +++ b/security/integrity/evm/Kconfig @@ -13,6 +13,38 @@ config EVM If you are unsure how to answer this question, answer N. +choice + prompt "Default EVM hash algorithm" + default EVM_DEFAULT_HASH_SHA256 + depends on EVM + help + Select the default hash algorithm used for the HMAC. + + config EVM_DEFAULT_HASH_SHA1 + bool "SHA1 (default)" + depends on CRYPTO_SHA1=y + + config EVM_DEFAULT_HASH_SHA256 + bool "SHA256" + depends on CRYPTO_SHA256=y + + config EVM_DEFAULT_HASH_SHA512 + bool "SHA512" + depends on CRYPTO_SHA512=y + + config EVM_DEFAULT_HASH_WP512 + bool "WP512" + depends on CRYPTO_WP512=y +endchoice + +config EVM_DEFAULT_HASH + string + depends on EVM + default "sha1" if EVM_DEFAULT_HASH_SHA1 + default "sha256" if EVM_DEFAULT_HASH_SHA256 + default "sha512" if EVM_DEFAULT_HASH_SHA512 + default "wp512" if EVM_DEFAULT_HASH_WP512 + config EVM_ATTR_FSUUID bool "FSUUID (version 2)" default y diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index f8b8c5004fc7c6fadba3bcdc4a6f49c44b3641cd..72776e20c0a839fb8da2b4af493504a18dde1ccf 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -33,6 +33,7 @@ struct xattr_list { }; extern int evm_initialized; +extern enum hash_algo evm_hash_algo; #define EVM_ATTR_FSUUID 0x0001 diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index 033804f5a5f20daeceb048797e0094a3849d8d45..a6215dbe50b1fe7417734adaa68d7a9bf8a8a936 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -35,7 +35,7 @@ static DEFINE_MUTEX(mutex); static unsigned long evm_set_key_flags; -static const char evm_hmac[] = "hmac(sha1)"; +enum hash_algo evm_hash_algo __ro_after_init = HASH_ALGO_SHA1; /** * evm_set_key() - set EVM HMAC key from the kernel @@ -76,8 +76,12 @@ static struct shash_desc *init_desc(char type, uint8_t hash_algo) long rc; const char *algo; struct crypto_shash **tfm, *tmp_tfm; + char evm_hmac[CRYPTO_MAX_ALG_NAME]; struct shash_desc *desc; + snprintf(evm_hmac, sizeof(evm_hmac), "hmac(%s)", + CONFIG_EVM_DEFAULT_HASH); + if (type == EVM_XATTR_HMAC) { if (!(evm_initialized & EVM_INIT_HMAC)) { pr_err_once("HMAC key is not set\n"); @@ -154,7 +158,8 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, /* Don't include the inode or generation number in portable * signatures */ - if (type != EVM_XATTR_PORTABLE_DIGSIG) { + if (type != EVM_XATTR_PORTABLE_DIGSIG && + type != EVM_IMA_XATTR_DIGEST_LIST) { hmac_misc.ino = inode->i_ino; hmac_misc.generation = inode->i_generation; } @@ -171,7 +176,8 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, hmac_misc.mode = inode->i_mode; crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof(hmac_misc)); if ((evm_hmac_attrs & EVM_ATTR_FSUUID) && - type != EVM_XATTR_PORTABLE_DIGSIG) + type != EVM_XATTR_PORTABLE_DIGSIG && + type != EVM_IMA_XATTR_DIGEST_LIST) crypto_shash_update(desc, (u8 *)&inode->i_sb->s_uuid, UUID_SIZE); crypto_shash_final(desc, digest); @@ -337,7 +343,8 @@ static int evm_is_immutable(struct dentry *dentry, struct inode *inode) rc = 0; goto out; } - if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG) + if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG || + xattr_data->type == EVM_IMA_XATTR_DIGEST_LIST) rc = 1; else rc = 0; @@ -370,7 +377,7 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, if (rc) return -EPERM; - data.hdr.algo = HASH_ALGO_SHA1; + data.hdr.algo = evm_hash_algo; rc = evm_calc_hmac(dentry, xattr_name, xattr_value, xattr_value_len, &data); if (rc == 0) { @@ -378,7 +385,8 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, rc = __vfs_setxattr_noperm(&nop_mnt_idmap, dentry, XATTR_NAME_EVM, &data.hdr.xattr.data[1], - SHA1_DIGEST_SIZE + 1, 0); + hash_digest_size[evm_hash_algo] + 1, + 0); } else if (rc == -ENODATA && (inode->i_opflags & IOP_XATTR)) { rc = __vfs_removexattr(&nop_mnt_idmap, dentry, XATTR_NAME_EVM); } @@ -390,7 +398,7 @@ int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr, { struct shash_desc *desc; - desc = init_desc(EVM_XATTR_HMAC, HASH_ALGO_SHA1); + desc = init_desc(EVM_XATTR_HMAC, evm_hash_algo); if (IS_ERR(desc)) { pr_info("init_desc failed\n"); return PTR_ERR(desc); @@ -403,7 +411,7 @@ int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr, } /* - * Get the key from the TPM for the SHA1-HMAC + * Get the key from the TPM for the HMAC */ int evm_init_key(void) { diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index cf24c5255583cb7521582d46f61b951c82b33cc9..305afa22ab9c0989314c0943b04146a49b3eb725 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -73,16 +73,22 @@ static struct xattr_list evm_config_default_xattrnames[] = { LIST_HEAD(evm_config_xattrnames); static int evm_fixmode __ro_after_init; -static int __init evm_set_fixmode(char *str) +static int __init evm_set_param(char *str) { if (strncmp(str, "fix", 3) == 0) evm_fixmode = 1; + else if (strncmp(str, "x509", 4) == 0) + evm_initialized |= EVM_INIT_X509; + else if (strncmp(str, "allow_metadata_writes", 21) == 0) + evm_initialized |= EVM_ALLOW_METADATA_WRITES; + else if (strncmp(str, "complete", 8) == 0) + evm_initialized |= EVM_SETUP_COMPLETE; else pr_err("invalid \"%s\" mode", str); return 1; } -__setup("evm=", evm_set_fixmode); +__setup("evm=", evm_set_param); static void __init evm_init_config(void) { @@ -128,7 +134,7 @@ static bool evm_hmac_disabled(void) return true; } -static int evm_find_protected_xattrs(struct dentry *dentry) +static int evm_find_protected_xattrs(struct dentry *dentry, int *ima_present) { struct inode *inode = d_backing_inode(dentry); struct xattr_list *xattr; @@ -145,6 +151,8 @@ static int evm_find_protected_xattrs(struct dentry *dentry) continue; return error; } + if (!strcmp(xattr->name, XATTR_NAME_IMA)) + *ima_present = 1; count++; } @@ -173,9 +181,14 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, struct evm_ima_xattr_data *xattr_data = NULL; struct signature_v2_hdr *hdr; enum integrity_status evm_status = INTEGRITY_PASS; + enum integrity_status saved_evm_status = INTEGRITY_UNKNOWN; struct evm_digest digest; + struct ima_digest *found_digest; struct inode *inode; - int rc, xattr_len, evm_immutable = 0; + struct signature_v2_hdr evm_fake_xattr = { + .type = EVM_IMA_XATTR_DIGEST_LIST, + .version = 2, .hash_algo = HASH_ALGO_SHA256 }; + int rc, xattr_len, evm_immutable = 0, ima_present = 0; if (iint && (iint->evm_status == INTEGRITY_PASS || iint->evm_status == INTEGRITY_PASS_IMMUTABLE)) @@ -189,7 +202,7 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, if (rc <= 0) { evm_status = INTEGRITY_FAIL; if (rc == -ENODATA) { - rc = evm_find_protected_xattrs(dentry); + rc = evm_find_protected_xattrs(dentry, &ima_present); if (rc > 0) evm_status = INTEGRITY_NOLABEL; else if (rc == 0) @@ -197,7 +210,20 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, } else if (rc == -EOPNOTSUPP) { evm_status = INTEGRITY_UNKNOWN; } - goto out; + /* IMA added a fake xattr, set also EVM fake xattr */ + if (!ima_present && xattr_name && + !strcmp(xattr_name, XATTR_NAME_IMA) && + xattr_value_len > 2) { + evm_fake_xattr.hash_algo = + ((struct evm_ima_xattr_data *)xattr_value)->data[0]; + xattr_data = + (struct evm_ima_xattr_data *)&evm_fake_xattr; + rc = sizeof(evm_fake_xattr); + } + if (xattr_data != (struct evm_ima_xattr_data *)&evm_fake_xattr) + goto out; + + saved_evm_status = evm_status; } xattr_len = rc; @@ -205,18 +231,18 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, /* check value type */ switch (xattr_data->type) { case EVM_XATTR_HMAC: - if (xattr_len != sizeof(struct evm_xattr)) { + if (xattr_len != hash_digest_size[evm_hash_algo] + 1) { evm_status = INTEGRITY_FAIL; goto out; } - digest.hdr.algo = HASH_ALGO_SHA1; + digest.hdr.algo = evm_hash_algo; rc = evm_calc_hmac(dentry, xattr_name, xattr_value, xattr_value_len, &digest); if (rc) break; rc = crypto_memneq(xattr_data->data, digest.digest, - SHA1_DIGEST_SIZE); + hash_digest_size[evm_hash_algo]); if (rc) rc = -EINVAL; break; @@ -255,12 +281,54 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, } } break; + case EVM_IMA_XATTR_DIGEST_LIST: + /* At this point, we cannot determine whether metadata are + * immutable or not. However, it is safe to return the + * fail_immutable error, as HMAC will not be created for this + * security.evm type. + */ + evm_immutable = 1; + + if (xattr_len < offsetof(struct signature_v2_hdr, keyid)) { + evm_status = INTEGRITY_FAIL; + goto out; + } + + hdr = (struct signature_v2_hdr *)xattr_data; + digest.hdr.algo = hdr->hash_algo; + rc = evm_calc_hash(dentry, xattr_name, xattr_value, + xattr_value_len, xattr_data->type, &digest); + if (rc) + break; + + found_digest = ima_lookup_digest(digest.digest, hdr->hash_algo, + COMPACT_METADATA); + if (!found_digest) { + rc = -ENOENT; + break; + } + + if (!ima_digest_allow(found_digest, IMA_APPRAISE)) { + rc = -EACCES; + break; + } + + if (ima_digest_is_immutable(found_digest)) { + if (iint) + iint->flags |= EVM_IMMUTABLE_DIGSIG; + evm_status = INTEGRITY_PASS_IMMUTABLE; + } else { + evm_status = INTEGRITY_PASS; + } + break; default: rc = -EINVAL; break; } - if (rc) { + if (rc && xattr_data == (struct evm_ima_xattr_data *)&evm_fake_xattr) { + evm_status = saved_evm_status; + } else if (rc) { if (rc == -ENODATA) evm_status = INTEGRITY_NOXATTRS; else if (evm_immutable) @@ -273,7 +341,8 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, out: if (iint) iint->evm_status = evm_status; - kfree(xattr_data); + if (xattr_data != (struct evm_ima_xattr_data *)&evm_fake_xattr) + kfree(xattr_data); return evm_status; } @@ -581,7 +650,8 @@ int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, if (!xattr_value_len) return -EINVAL; if (xattr_data->type != EVM_IMA_XATTR_DIGSIG && - xattr_data->type != EVM_XATTR_PORTABLE_DIGSIG) + xattr_data->type != EVM_XATTR_PORTABLE_DIGSIG && + xattr_data->type != EVM_IMA_XATTR_DIGEST_LIST) return -EPERM; } return evm_protect_xattr(idmap, dentry, xattr_name, xattr_value, @@ -885,7 +955,7 @@ int evm_inode_init_security(struct inode *inode, goto out; evm_xattr->value = xattr_data; - evm_xattr->value_len = sizeof(*xattr_data); + evm_xattr->value_len = hash_digest_size[evm_hash_algo] + 1; evm_xattr->name = XATTR_EVM_SUFFIX; return 0; out: @@ -907,9 +977,14 @@ void __init evm_load_x509(void) static int __init init_evm(void) { - int error; + int error, i; struct list_head *pos, *q; + i = match_string(hash_algo_name, HASH_ALGO__LAST, + CONFIG_EVM_DEFAULT_HASH); + if (i >= 0) + evm_hash_algo = i; + evm_init_config(); error = integrity_init_keyring(INTEGRITY_KEYRING_EVM); diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index 9b907c2fee60b638b9033f8315a639d409953f6e..5a762ac6411d44156631f492538cd4043e4365c3 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -86,7 +86,7 @@ static ssize_t evm_write_key(struct file *file, const char __user *buf, * an HMAC key is loaded. */ if ((i & EVM_ALLOW_METADATA_WRITES) && - (evm_initialized & EVM_INIT_HMAC) != 0) + ((evm_initialized & EVM_KEY_MASK) != 0)) return -EPERM; if (i & EVM_INIT_HMAC) { diff --git a/security/integrity/iint.c b/security/integrity/iint.c index c73858e8c6d5137e2e811b2431dd46b1d36b4998..ecb9bd378a04352ab9315aed0de5e5756befe673 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -204,6 +204,8 @@ void __init integrity_load_keys(void) if (!IS_ENABLED(CONFIG_IMA_LOAD_X509)) evm_load_x509(); + + ima_load_digest_lists(); } static int __init integrity_fs_init(void) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 60a511c6b583e23b2b8c59f8407ce4bb16ee208c..d0d45a521083cd4abedddab7e439b81fdf79257c 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -338,3 +338,52 @@ config IMA_DISABLE_HTABLE default n help This option disables htable to allow measurement of duplicate records. + +config IMA_DIGEST_LIST + bool "Measure and appraise files with digest lists" + depends on IMA + default n + help + This option allows users to load digest lists. If calculated digests + of accessed files are found in one of those lists, no new entries are + added to the measurement list, and access to the file is granted if + appraisal is in enforcing mode. + +config IMA_DIGEST_LISTS_DIR + string "Path of the directory containing digest lists" + depends on IMA_DIGEST_LIST + default "/etc/ima/digest_lists" + help + This option defines the path of the directory containing digest + lists. + +choice + prompt "Defalut maximum data uploaded to the IMA digest database" + default IMA_STANDARD_DIGEST_DB_SIZE + depends on IMA_DIGEST_LIST + help + This option defines the maximum data uploaded to the IMA digest + database. The compiled default limit can be overwritten using the + kernel command line "ima_digest_db_size". + + config IMA_STANDARD_DIGEST_DB_SIZE + bool "standard (default)" + config IMA_MAX_DIGEST_DB_SIZE + bool "maximum" + config IMA_CUSTOM_DIGEST_DB_SIZE + bool "custom" +endchoice + +config IMA_DIGEST_DB_MEGABYTES + int + depends on IMA_DIGEST_LIST + range 0 64 + default 16 if IMA_STANDARD_DIGEST_DB_SIZE + default 64 if IMA_MAX_DIGEST_DB_SIZE + +config IMA_PARSER_BINARY_PATH + string "Path of the parser binary" + depends on IMA_DIGEST_LIST + default "/usr/bin/upload_digest_lists" + help + This option defines the path of the parser binary. diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 2499f2485c04f3819ab4dc91625a0089d35640aa..fb9744a1c3d40cdddf7c950a6f33c441ace9ec8e 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -14,6 +14,7 @@ ima-$(CONFIG_HAVE_IMA_KEXEC) += ima_kexec.o ima-$(CONFIG_IMA_BLACKLIST_KEYRING) += ima_mok.o ima-$(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) += ima_asymmetric_keys.o ima-$(CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS) += ima_queue_keys.o +ima-$(CONFIG_IMA_DIGEST_LIST) += ima_digest_list.o ifeq ($(CONFIG_EFI),y) ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index c29db699c996e5801177f234e12da0e45e4b9658..b62fdf712b2720036f443bcb983b2777af77f5e3 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -56,7 +56,14 @@ extern int ima_hash_algo_idx __ro_after_init; extern int ima_extra_slots __ro_after_init; extern int ima_appraise; extern struct tpm_chip *ima_tpm_chip; +extern int ima_digest_list_pcr; +extern bool ima_plus_standard_pcr; extern const char boot_aggregate_name[]; +extern int ima_digest_list_actions; +#ifdef CONFIG_IMA_DIGEST_LIST +extern int ima_digest_db_max_size __ro_after_init; +extern int ima_digest_db_size; +#endif /* IMA event related data */ struct ima_event_data { @@ -203,6 +210,7 @@ static inline unsigned int ima_hash_key(u8 *digest) hook(KEY_CHECK, key) \ hook(CRITICAL_DATA, critical_data) \ hook(SETXATTR_CHECK, setxattr_check) \ + hook(DIGEST_LIST_CHECK, digest_list) \ hook(MAX_CHECK, none) #define __ima_hook_enumify(ENUM, str) ENUM, @@ -268,7 +276,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, - struct ima_template_desc *template_desc); + struct ima_template_desc *template_desc, + struct ima_digest *digest); int process_buffer_measurement(struct mnt_idmap *idmap, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, @@ -280,8 +289,8 @@ int ima_alloc_init_template(struct ima_event_data *event_data, struct ima_template_entry **entry, struct ima_template_desc *template_desc); int ima_store_template(struct ima_template_entry *entry, int violation, - struct inode *inode, - const unsigned char *filename, int pcr); + struct inode *inode, const unsigned char *filename, + int pcr, struct ima_digest *digest); void ima_free_template_entry(struct ima_template_entry *entry); const char *ima_d_path(const struct path *path, char **pathbuf, char *filename); @@ -310,6 +319,7 @@ int ima_policy_show(struct seq_file *m, void *v); #define IMA_APPRAISE_FIRMWARE 0x10 #define IMA_APPRAISE_POLICY 0x20 #define IMA_APPRAISE_KEXEC 0x40 +#define IMA_APPRAISE_DIGEST_LIST 0x80 #ifdef CONFIG_IMA_APPRAISE int ima_check_blacklist(struct integrity_iint_cache *iint, @@ -318,16 +328,13 @@ int ima_appraise_measurement(enum ima_hooks func, struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, - int xattr_len, const struct modsig *modsig); + int xattr_len, const struct modsig *modsig, + struct ima_digest *found_digest); int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, enum ima_hooks func); -enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, - int xattr_len); -int ima_read_xattr(struct dentry *dentry, - struct evm_ima_xattr_data **xattr_value, int xattr_len); #else static inline int ima_check_blacklist(struct integrity_iint_cache *iint, @@ -342,7 +349,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, - const struct modsig *modsig) + const struct modsig *modsig, + struct ima_digest *found_digest) { return INTEGRITY_UNKNOWN; } @@ -366,19 +374,6 @@ static inline enum integrity_status ima_get_cache_status(struct integrity_iint_c return INTEGRITY_UNKNOWN; } -static inline enum hash_algo -ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len) -{ - return ima_hash_algo; -} - -static inline int ima_read_xattr(struct dentry *dentry, - struct evm_ima_xattr_data **xattr_value, - int xattr_len) -{ - return 0; -} - #endif /* CONFIG_IMA_APPRAISE */ #ifdef CONFIG_IMA_APPRAISE_MODSIG diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index d3662f4acadc1d355624a972af2d300f0ccc0231..c5b92b99a4b4cd9f701e17a49d40abfaaf46b8a1 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -102,11 +102,13 @@ int ima_alloc_init_template(struct ima_event_data *event_data, */ int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode, - const unsigned char *filename, int pcr) + const unsigned char *filename, int pcr, + struct ima_digest *digest) { static const char op[] = "add_template_measure"; static const char audit_cause[] = "hashing_error"; char *template_name = entry->template_desc->name; + struct ima_template_entry *duplicated_entry = NULL; int result; if (!violation) { @@ -119,8 +121,26 @@ int ima_store_template(struct ima_template_entry *entry, return result; } } + + if (ima_plus_standard_pcr && !digest) { + duplicated_entry = kmemdup(entry, + sizeof(*entry) + entry->template_desc->num_fields * + sizeof(struct ima_field_data), GFP_KERNEL); + if (duplicated_entry) + duplicated_entry->pcr = ima_digest_list_pcr; + } else if (!ima_plus_standard_pcr && ima_digest_list_pcr >= 0) { + pcr = ima_digest_list_pcr; + } + entry->pcr = pcr; result = ima_add_template_entry(entry, violation, op, inode, filename); + if (!result && duplicated_entry) { + result = ima_add_template_entry(duplicated_entry, violation, op, + inode, filename); + if (result < 0) + kfree(duplicated_entry); + } + return result; } @@ -152,8 +172,8 @@ void ima_add_violation(struct file *file, const unsigned char *filename, result = -ENOMEM; goto err_out; } - result = ima_store_template(entry, violation, inode, - filename, CONFIG_IMA_MEASURE_PCR_IDX); + result = ima_store_template(entry, violation, inode, filename, + CONFIG_IMA_MEASURE_PCR_IDX, NULL); if (result < 0) ima_free_template_entry(entry); err_out: @@ -341,13 +361,14 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, - struct ima_template_desc *template_desc) + struct ima_template_desc *template_desc, + struct ima_digest *digest) { static const char op[] = "add_template_measure"; static const char audit_cause[] = "ENOMEM"; int result = -ENOMEM; struct inode *inode = file_inode(file); - struct ima_template_entry *entry; + struct ima_template_entry *entry = NULL; struct ima_event_data event_data = { .iint = iint, .file = file, .filename = filename, @@ -365,6 +386,11 @@ void ima_store_measurement(struct integrity_iint_cache *iint, if (iint->measured_pcrs & (0x1 << pcr) && !modsig) return; + if (digest && !ima_plus_standard_pcr && ima_digest_list_pcr >= 0) { + result = -EEXIST; + goto out; + } + result = ima_alloc_init_template(&event_data, &entry, template_desc); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, @@ -372,12 +398,14 @@ void ima_store_measurement(struct integrity_iint_cache *iint, return; } - result = ima_store_template(entry, violation, inode, filename, pcr); + result = ima_store_template(entry, violation, inode, filename, pcr, + digest); +out: if ((!result || result == -EEXIST) && !(file->f_flags & O_DIRECT)) { iint->flags |= IMA_MEASURED; iint->measured_pcrs |= (0x1 << pcr); } - if (result < 0) + if (result < 0 && entry) ima_free_template_entry(entry); } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 491c1aca0b1ce52453bc068ba4566aa1c957a9e3..cdcc20339c8e760821ae1fb4f4cc4ac95cdbcda7 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -18,7 +18,9 @@ #include #include "ima.h" +#include "ima_digest_list.h" +static bool ima_appraise_req_evm __ro_after_init; #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM static char *ima_appraise_cmdline_default __initdata; core_param(ima_appraise, ima_appraise_cmdline_default, charp, 0); @@ -52,7 +54,27 @@ void __init ima_appraise_parse_cmdline(void) } else { ima_appraise = appraisal_state; } + + if (strcmp(str, "enforce-evm") == 0 || + strcmp(str, "log-evm") == 0) + ima_appraise_req_evm = true; +} +#endif + +static bool ima_appraise_no_metadata __ro_after_init; +#ifdef CONFIG_IMA_DIGEST_LIST +static int __init appraise_digest_list_setup(char *str) +{ + if (!strncmp(str, "digest", 6)) { + ima_digest_list_actions |= IMA_APPRAISE; + + if (!strcmp(str + 6, "-nometadata")) + ima_appraise_no_metadata = true; + } + + return 1; } +__setup("ima_appraise_digest_list=", appraise_digest_list_setup); #endif /* @@ -96,6 +118,9 @@ static int ima_fix_xattr(struct dentry *dentry, } else { offset = 0; iint->ima_hash->xattr.ng.type = IMA_XATTR_DIGEST_NG; + if (test_bit(IMA_DIGEST_LIST, &iint->atomic_flags)) + iint->ima_hash->xattr.ng.type = + EVM_IMA_XATTR_DIGEST_LIST; iint->ima_hash->xattr.ng.algo = algo; } rc = __vfs_setxattr_noperm(&nop_mnt_idmap, dentry, XATTR_NAME_IMA, @@ -223,18 +248,6 @@ enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, return ima_hash_algo; } -int ima_read_xattr(struct dentry *dentry, - struct evm_ima_xattr_data **xattr_value, int xattr_len) -{ - int ret; - - ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_IMA, - (char **)xattr_value, xattr_len, GFP_NOFS); - if (ret == -EOPNOTSUPP) - ret = 0; - return ret; -} - /* * calc_file_id_hash - calculate the hash of the ima_file_id struct data * @type: xattr type [enum evm_ima_xattr_type] @@ -278,20 +291,35 @@ static int calc_file_id_hash(enum evm_ima_xattr_type type, */ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, struct evm_ima_xattr_data *xattr_value, int xattr_len, - enum integrity_status *status, const char **cause) + enum integrity_status *status, const char **cause, + struct ima_digest *found_digest) { struct ima_max_digest_data hash; struct signature_v2_hdr *sig; int rc = -EINVAL, hash_start = 0; int mask; + if (found_digest && *status != INTEGRITY_PASS && + *status != INTEGRITY_PASS_IMMUTABLE) + set_bit(IMA_DIGEST_LIST, &iint->atomic_flags); + switch (xattr_value->type) { + case EVM_IMA_XATTR_DIGEST_LIST: + set_bit(IMA_DIGEST_LIST, &iint->atomic_flags); + + if (!ima_appraise_no_metadata) { + *cause = "IMA-xattr-untrusted"; + *status = INTEGRITY_FAIL; + break; + } + fallthrough; case IMA_XATTR_DIGEST_NG: /* first byte contains algorithm id */ hash_start = 1; fallthrough; case IMA_XATTR_DIGEST: - if (*status != INTEGRITY_PASS_IMMUTABLE) { + if (*status != INTEGRITY_PASS_IMMUTABLE && + (!found_digest || !ima_digest_is_immutable(found_digest))) { if (iint->flags & IMA_DIGSIG_REQUIRED) { if (iint->flags & IMA_VERITY_REQUIRED) *cause = "verity-signature-required"; @@ -479,20 +507,45 @@ int ima_appraise_measurement(enum ima_hooks func, struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, - int xattr_len, const struct modsig *modsig) + int xattr_len, const struct modsig *modsig, + struct ima_digest *found_digest) { static const char op[] = "appraise_data"; const char *cause = "unknown"; struct dentry *dentry = file_dentry(file); struct inode *inode = d_backing_inode(dentry); enum integrity_status status = INTEGRITY_UNKNOWN; - int rc = xattr_len; + int rc = xattr_len, rc_evm; + char _buf[sizeof(struct evm_ima_xattr_data) + 1 + SHA512_DIGEST_SIZE]; bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig; /* If not appraising a modsig, we need an xattr. */ if (!(inode->i_opflags & IOP_XATTR) && !try_modsig) return INTEGRITY_UNKNOWN; + if (xattr_value && xattr_value->type == EVM_IMA_XATTR_DIGSIG && + xattr_len == sizeof(struct signature_v2_hdr)) + rc = -ENODATA; + + if (rc == -ENODATA && found_digest && + !(file->f_mode & FMODE_CREATED)) { + struct evm_ima_xattr_data *xattr_data = NULL; + + rc_evm = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_EVM, + (char **)&xattr_data, 0, GFP_NOFS); + if (rc_evm > 0) { + kfree(xattr_data); + } else { + xattr_value = (struct evm_ima_xattr_data *)_buf; + xattr_value->type = IMA_XATTR_DIGEST_NG; + xattr_value->data[0] = found_digest->algo; + memcpy(&xattr_value->data[1], found_digest->digest, + hash_digest_size[found_digest->algo]); + xattr_len = hash_digest_size[found_digest->algo] + 2; + rc = xattr_len; + } + } + /* If reading the xattr failed and there's no modsig, error out. */ if (rc <= 0 && !try_modsig) { if (rc && rc != -ENODATA) @@ -522,7 +575,11 @@ int ima_appraise_measurement(enum ima_hooks func, switch (status) { case INTEGRITY_PASS: case INTEGRITY_PASS_IMMUTABLE: + break; case INTEGRITY_UNKNOWN: + if (ima_appraise_req_evm && + xattr_value->type != EVM_IMA_XATTR_DIGSIG && !found_digest) + goto out; break; case INTEGRITY_NOXATTRS: /* No EVM protected xattrs. */ /* It's fine not to have xattrs when using a modsig. */ @@ -530,6 +587,23 @@ int ima_appraise_measurement(enum ima_hooks func, break; fallthrough; case INTEGRITY_NOLABEL: /* No security.evm xattr. */ + /* + * If the digest-nometadata mode is selected, allow access + * without metadata check. EVM will eventually create an HMAC + * based on current xattr values. + */ + if (ima_appraise_no_metadata && found_digest) + break; + /* Allow access to digest lists without metadata, only if they + * are signed or found in a digest list (immutable) + */ + if (func == DIGEST_LIST_CHECK || ima_current_is_parser()) { + if (xattr_value->type == EVM_IMA_XATTR_DIGSIG) + break; + if (found_digest && + ima_digest_is_immutable(found_digest)) + break; + } cause = "missing-HMAC"; goto out; case INTEGRITY_FAIL_IMMUTABLE: @@ -543,9 +617,18 @@ int ima_appraise_measurement(enum ima_hooks func, WARN_ONCE(true, "Unexpected integrity status %d\n", status); } + if ((iint->flags & IMA_META_IMMUTABLE_REQUIRED) && + status != INTEGRITY_PASS_IMMUTABLE) { + status = INTEGRITY_FAIL; + cause = "metadata-modifiable"; + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, op, cause, rc, 0); + goto out; + } + if (xattr_value) rc = xattr_verify(func, iint, xattr_value, xattr_len, &status, - &cause); + &cause, found_digest); /* * If we have a modsig and either no imasig or the imasig's key isn't diff --git a/security/integrity/ima/ima_digest_list.c b/security/integrity/ima/ima_digest_list.c new file mode 100644 index 0000000000000000000000000000000000000000..067f79d8799f812973f94c62a8b3cbcf670fc5cf --- /dev/null +++ b/security/integrity/ima/ima_digest_list.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2019 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: ima_digest_list.c + * Functions to manage digest lists. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define MAX_DB_SIZE (64 * 1024 * 1024) +#define DEFAULT_DB_SIZE (CONFIG_IMA_DIGEST_DB_MEGABYTES * 1024 * 1024) + +#include +#include +#include +#include +#include +#include +#include + +#include "ima.h" +#include "ima_digest_list.h" + +int ima_digest_db_max_size __ro_after_init = DEFAULT_DB_SIZE; +int ima_digest_db_size; + +struct ima_h_table ima_digests_htable = { + .len = ATOMIC_LONG_INIT(0), + .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}; + +static int __init digest_db_max_size_setup(char *str) +{ + int size; + char *retptr; + + size = memparse(str, &retptr); + if (size < 0 || size > MAX_DB_SIZE || *retptr != '\0') { + pr_err("digest DB size should range from 0M to 64M\n"); + return 0; + } + + ima_digest_db_max_size = size; + pr_info_once("parse reserve digest DB memory: %s\n", str); + + return 1; +} +__setup("ima_digest_db_size=", digest_db_max_size_setup); + +static int __init digest_list_pcr_setup(char *str) +{ + int pcr, ret; + + ret = kstrtouint(str, 10, &pcr); + if (ret) { + pr_err("Invalid PCR number %s\n", str); + return 1; + } + + if (pcr == CONFIG_IMA_MEASURE_PCR_IDX) { + pr_err("Default PCR cannot be used for digest lists\n"); + return 1; + } + + ima_digest_list_pcr = pcr; + ima_digest_list_actions |= IMA_MEASURE; + + if (*str == '+') + ima_plus_standard_pcr = true; + + return 1; +} +__setup("ima_digest_list_pcr=", digest_list_pcr_setup); + +/************************* + * Get/add/del functions * + *************************/ +struct ima_digest *ima_lookup_digest(u8 *digest, enum hash_algo algo, + enum compact_types type) +{ + struct ima_digest *d = NULL; + int digest_len = hash_digest_size[algo]; + unsigned int key = ima_hash_key(digest); + + rcu_read_lock(); + hlist_for_each_entry_rcu(d, &ima_digests_htable.queue[key], hnext) + if (d->algo == algo && d->type == type && + !memcmp(d->digest, digest, digest_len)) + break; + + rcu_read_unlock(); + return d; +} + +static int ima_add_digest_data_entry(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 modifiers) +{ + struct ima_digest *d; + int digest_len = hash_digest_size[algo]; + unsigned int key = ima_hash_key(digest); + + d = ima_lookup_digest(digest, algo, type); + if (d) { + d->modifiers |= modifiers; + if (d->count < (u16)(~((u16)0))) + d->count++; + return -EEXIST; + } + + d = kmalloc(sizeof(*d) + digest_len, GFP_KERNEL); + if (d == NULL) + return -ENOMEM; + + ima_digest_db_size += sizeof(struct ima_digest); + ima_digest_db_size += digest_len; + + d->algo = algo; + d->type = type; + d->modifiers = modifiers; + d->count = 1; + + memcpy(d->digest, digest, digest_len); + hlist_add_head_rcu(&d->hnext, &ima_digests_htable.queue[key]); + atomic_long_inc(&ima_digests_htable.len); + return 0; +} + +static void ima_del_digest_data_entry(u8 *digest, enum hash_algo algo, + enum compact_types type) +{ + struct ima_digest *d; + + d = ima_lookup_digest(digest, algo, type); + if (!d) + return; + + if (--d->count > 0) + return; + + ima_digest_db_size -= sizeof(struct ima_digest); + ima_digest_db_size -= hash_digest_size[algo]; + + hlist_del_rcu(&d->hnext); + atomic_long_dec(&ima_digests_htable.len); + kfree(d); +} + +/*********************** + * Compact list parser * + ***********************/ +struct compact_list_hdr { + u8 version; + u8 _reserved; + u16 type; + u16 modifiers; + u16 algo; + u32 count; + u32 datalen; +} __packed; + +int ima_parse_compact_list(loff_t size, void *buf, int op) +{ + u8 *digest; + void *bufp = buf, *bufendp = buf + size; + struct compact_list_hdr *hdr; + size_t digest_len; + int ret = 0, i; + + if (!(ima_digest_list_actions & ima_policy_flag)) + return -EACCES; + + while (bufp < bufendp) { + if (bufp + sizeof(*hdr) > bufendp) { + pr_err("compact list, invalid data\n"); + return -EINVAL; + } + + hdr = bufp; + + if (hdr->version != 1) { + pr_err("compact list, unsupported version\n"); + return -EINVAL; + } + + if (ima_canonical_fmt) { + hdr->type = le16_to_cpu(hdr->type); + hdr->modifiers = le16_to_cpu(hdr->modifiers); + hdr->algo = le16_to_cpu(hdr->algo); + hdr->count = le32_to_cpu(hdr->count); + hdr->datalen = le32_to_cpu(hdr->datalen); + } + + if (hdr->algo >= HASH_ALGO__LAST) + return -EINVAL; + + digest_len = hash_digest_size[hdr->algo]; + + if (hdr->type >= COMPACT__LAST) { + pr_err("compact list, invalid type %d\n", hdr->type); + return -EINVAL; + } + + bufp += sizeof(*hdr); + + for (i = 0; i < hdr->count; i++) { + if (bufp + digest_len > bufendp) { + pr_err("compact list, invalid data\n"); + return -EINVAL; + } + + digest = bufp; + bufp += digest_len; + + if (op == DIGEST_LIST_OP_ADD) + ret = ima_add_digest_data_entry(digest, + hdr->algo, hdr->type, hdr->modifiers); + else if (op == DIGEST_LIST_OP_DEL) + ima_del_digest_data_entry(digest, hdr->algo, + hdr->type); + if (ret < 0 && ret != -EEXIST) + return ret; + } + + if (i != hdr->count || + bufp != (void *)hdr + sizeof(*hdr) + hdr->datalen) { + pr_err("compact list, invalid data\n"); + return -EINVAL; + } + } + + return bufp - buf; +} + +/*************************** + * Digest list usage check * + ***************************/ +void ima_check_measured_appraised(struct file *file) +{ + struct integrity_iint_cache *iint; + + if (!ima_digest_list_actions) + return; + + if (file_inode(file)->i_sb->s_magic == SECURITYFS_MAGIC || + S_ISDIR(file_inode(file)->i_mode)) + return; + + iint = integrity_iint_find(file_inode(file)); + if (!iint) { + pr_err("%s not processed, disabling digest lists lookup\n", + file_dentry(file)->d_name.name); + ima_digest_list_actions = 0; + return; + } + + mutex_lock(&iint->mutex); + if ((ima_digest_list_actions & IMA_MEASURE) && + !(iint->flags & IMA_MEASURED)) { + pr_err("%s not measured, disabling digest lists lookup " + "for measurement\n", file_dentry(file)->d_name.name); + ima_digest_list_actions &= ~IMA_MEASURE; + } + + if ((ima_digest_list_actions & IMA_APPRAISE) && + (!(iint->flags & IMA_APPRAISED) || + !test_bit(IMA_DIGSIG, &iint->atomic_flags))) { + pr_err("%s not appraised, disabling digest lists lookup " + "for appraisal\n", file_dentry(file)->d_name.name); + ima_digest_list_actions &= ~IMA_APPRAISE; + } + + mutex_unlock(&iint->mutex); +} + +struct ima_digest *ima_digest_allow(struct ima_digest *digest, int action) +{ + if (!(ima_digest_list_actions & action)) + return NULL; + + return digest; +} + +/************************************** + * Digest list loading at kernel init * + **************************************/ +struct readdir_callback { + struct dir_context ctx; + struct path *path; +}; + +static bool __init load_digest_list(struct dir_context *__ctx, const char *name, + int namelen, loff_t offset, u64 ino, + unsigned int d_type) +{ + struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx); + struct path *dir = ctx->path; + struct dentry *dentry; + struct file *file; + u8 *xattr_value = NULL; + char *type_start, *format_start, *format_end; + void *datap = NULL; + loff_t size; + int ret; + + if (!strcmp(name, ".") || !strcmp(name, "..")) + return true; + + type_start = strchr(name, '-'); + if (!type_start) + return true; + + format_start = strchr(type_start + 1, '-'); + if (!format_start) + return true; + + format_end = strchr(format_start + 1, '-'); + if (!format_end) + return true; + + if (format_end - format_start - 1 != strlen("compact") || + strncmp(format_start + 1, "compact", format_end - format_start - 1)) + return true; + + dentry = lookup_one_len(name, dir->dentry, strlen(name)); + if (IS_ERR(dentry)) + return true; + + size = vfs_getxattr(&nop_mnt_idmap, dentry, XATTR_NAME_EVM, NULL, 0); + if (size < 0) { + size = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_IMA, + (char **)&xattr_value, 0, GFP_NOFS); + if (size < 0 || xattr_value[0] != EVM_IMA_XATTR_DIGSIG) + goto out; + } + + file = file_open_root(dir, name, O_RDONLY, 0); + if (IS_ERR(file)) { + pr_err("Unable to open file: %s (%ld)", name, PTR_ERR(file)); + goto out; + } + + ret = kernel_read_file(file, 0, &datap, INT_MAX, NULL, + READING_DIGEST_LIST); + if (ret < 0) { + pr_err("Unable to read file: %s (%d)", name, ret); + goto out_fput; + } + + size = ret; + + if (size > ima_digest_db_max_size - ima_digest_db_size) { + pr_err_once("digest DB is full: %d\n", ima_digest_db_size); + goto out_fput; + } + + ima_check_measured_appraised(file); + + ret = ima_parse_compact_list(size, datap, DIGEST_LIST_OP_ADD); + if (ret < 0) + pr_err("Unable to parse file: %s (%d)", name, ret); + + vfree(datap); +out_fput: + fput(file); +out: + kfree(xattr_value); + return true; +} + +static void ima_exec_parser(void) +{ + char *argv[4] = {NULL}, *envp[1] = {NULL}; + + argv[0] = (char *)CONFIG_IMA_PARSER_BINARY_PATH; + argv[1] = "add"; + argv[2] = (char *)CONFIG_IMA_DIGEST_LISTS_DIR; + + call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC); +} + +void __init ima_load_digest_lists(void) +{ + struct path path; + struct file *file; + int ret; + struct readdir_callback buf = { + .ctx.actor = load_digest_list, + }; + + if (!(ima_digest_list_actions & ima_policy_flag)) + return; + + ret = kern_path(CONFIG_IMA_DIGEST_LISTS_DIR, 0, &path); + if (ret) + return; + + file = dentry_open(&path, O_RDONLY, current_cred()); + if (IS_ERR(file)) + goto out; + + buf.path = &path; + iterate_dir(file, &buf.ctx); + fput(file); +out: + path_put(&path); + + ima_exec_parser(); +} + +/**************** + * Parser check * + ****************/ +bool ima_check_current_is_parser(void) +{ + struct integrity_iint_cache *parser_iint; + struct file *parser_file; + struct mm_struct *mm; + + mm = get_task_mm(current); + if (!mm) + return false; + + parser_file = get_mm_exe_file(mm); + mmput(mm); + + if (!parser_file) + return false; + + parser_iint = integrity_iint_find(file_inode(parser_file)); + fput(parser_file); + + if (!parser_iint) + return false; + + /* flag cannot be cleared due to write protection of executables */ + if (!(parser_iint->flags & IMA_COLLECTED)) + return false; + + return ima_lookup_digest(parser_iint->ima_hash->digest, + parser_iint->ima_hash->algo, COMPACT_PARSER); +} + +struct task_struct *parser_task; + +void ima_set_parser(void) +{ + parser_task = current; +} + +void ima_unset_parser(void) +{ + parser_task = NULL; +} + +bool ima_current_is_parser(void) +{ + return (current == parser_task); +} diff --git a/security/integrity/ima/ima_digest_list.h b/security/integrity/ima/ima_digest_list.h new file mode 100644 index 0000000000000000000000000000000000000000..4a8b0e000ad3e1c87a0f18d0a527b0080609fbf0 --- /dev/null +++ b/security/integrity/ima/ima_digest_list.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2019 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: ima_digest_list.h + * Header of ima_digest_list.c + */ + +#ifndef __LINUX_IMA_DIGEST_LIST_H +#define __LINUX_IMA_DIGEST_LIST_H + +#define DIGEST_LIST_OP_ADD 0 +#define DIGEST_LIST_OP_DEL 1 + +#ifdef CONFIG_IMA_DIGEST_LIST +extern struct ima_h_table ima_digests_htable; + +int ima_parse_compact_list(loff_t size, void *buf, int op); +void ima_check_measured_appraised(struct file *file); +bool ima_check_current_is_parser(void); +void ima_set_parser(void); +void ima_unset_parser(void); +bool ima_current_is_parser(void); +#else +static inline int ima_parse_compact_list(loff_t size, void *buf, int op) +{ + return -EOPNOTSUPP; +} +static inline void ima_check_measured_appraised(struct file *file) +{ +} +static inline bool ima_check_current_is_parser(void) +{ + return false; +} +static inline void ima_set_parser(void) +{ +} +static inline void ima_unset_parser(void) +{ +} +static inline bool ima_current_is_parser(void) +{ + return false; +} +#endif /*CONFIG_IMA_DIGEST_LIST*/ +#endif /*LINUX_IMA_DIGEST_LIST_H*/ diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index cd1683dad3bf016e8d1189bef8616361c1eed80b..e8856d63df7bbd2986fcd2d90218d6a4716af17e 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -21,11 +21,25 @@ #include #include #include +#include +#include #include "ima.h" +#include "ima_digest_list.h" static DEFINE_MUTEX(ima_write_mutex); +static struct dentry *ima_dir; +static struct dentry *ima_symlink; +static struct dentry *binary_runtime_measurements; +static struct dentry *ascii_runtime_measurements; +static struct dentry *runtime_measurements_count; +static struct dentry *violations; +static struct dentry *ima_policy; +static struct dentry *digests_count; +static struct dentry *digest_list_data; +static struct dentry *digest_list_data_del; + bool ima_canonical_fmt; static int __init default_canonical_fmt_setup(char *str) { @@ -38,38 +52,28 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup); static int valid_policy = 1; -static ssize_t ima_show_htable_value(char __user *buf, size_t count, - loff_t *ppos, atomic_long_t *val) +static ssize_t ima_show_htable_value(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) { + atomic_long_t *val = NULL; char tmpbuf[32]; /* greater than largest 'long' string value */ ssize_t len; + if (filp->f_path.dentry == violations) + val = &ima_htable.violations; + else if (filp->f_path.dentry == runtime_measurements_count) + val = &ima_htable.len; +#ifdef CONFIG_IMA_DIGEST_LIST + else if (filp->f_path.dentry == digests_count) + val = &ima_digests_htable.len; +#endif + len = scnprintf(tmpbuf, sizeof(tmpbuf), "%li\n", atomic_long_read(val)); return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); } -static ssize_t ima_show_htable_violations(struct file *filp, - char __user *buf, - size_t count, loff_t *ppos) -{ - return ima_show_htable_value(buf, count, ppos, &ima_htable.violations); -} - -static const struct file_operations ima_htable_violations_ops = { - .read = ima_show_htable_violations, - .llseek = generic_file_llseek, -}; - -static ssize_t ima_show_measurements_count(struct file *filp, - char __user *buf, - size_t count, loff_t *ppos) -{ - return ima_show_htable_value(buf, count, ppos, &ima_htable.len); - -} - -static const struct file_operations ima_measurements_count_ops = { - .read = ima_show_measurements_count, +static const struct file_operations ima_htable_value_ops = { + .read = ima_show_htable_value, .llseek = generic_file_llseek, }; @@ -271,11 +275,14 @@ static const struct file_operations ima_ascii_measurements_ops = { .release = seq_release, }; -static ssize_t ima_read_policy(char *path) +static ssize_t ima_read_file(char *path, struct dentry *dentry) { void *data = NULL; char *datap; size_t size; + struct file *file; + enum kernel_read_file_id file_id = READING_POLICY; + int op = DIGEST_LIST_OP_ADD; int rc, pathlen = strlen(path); char *p; @@ -284,25 +291,69 @@ static ssize_t ima_read_policy(char *path) datap = path; strsep(&datap, "\n"); - rc = kernel_read_file_from_path(path, 0, &data, INT_MAX, NULL, - READING_POLICY); + if (dentry == digest_list_data || dentry == digest_list_data_del) + file_id = READING_DIGEST_LIST; + + file = filp_open(path, O_RDONLY, 0); + if (IS_ERR(file)) { + pr_err("Unable to open file: %s (%ld)", path, PTR_ERR(file)); + return PTR_ERR(file); + } + + rc = kernel_read_file(file, 0, &data, INT_MAX, NULL, file_id); if (rc < 0) { - pr_err("Unable to open file: %s (%d)", path, rc); + pr_err("Unable to read file: %s (%d)", path, rc); + fput(file); return rc; } size = rc; rc = 0; datap = data; - while (size > 0 && (p = strsep(&datap, "\n"))) { - pr_debug("rule: %s\n", p); - rc = ima_parse_add_rule(p); + while (size > 0) { + if (dentry == ima_policy) { + p = strsep(&datap, "\n"); + if (p == NULL) + break; + + pr_debug("rule: %s\n", p); + rc = ima_parse_add_rule(p); + } else if (dentry == digest_list_data || + dentry == digest_list_data_del) { +#ifdef CONFIG_IMA_DIGEST_LIST + /* Only check size when adding digest lists */ + if (dentry == digest_list_data && + size > ima_digest_db_max_size - ima_digest_db_size) { + pr_err("digest DB is full: %d\n", ima_digest_db_size); + rc = -ENOMEM; + break; + } +#endif + /* + * Disable usage of digest lists if not measured + * or appraised. + */ + ima_check_measured_appraised(file); + + if (dentry == digest_list_data_del) + op = DIGEST_LIST_OP_DEL; + + rc = ima_parse_compact_list(size, data, op); + } + if (rc < 0) break; +#ifdef CONFIG_IMA_DIGEST_LIST + else if (dentry == digest_list_data) + pr_debug("digest imported, current DB size: %d\n", ima_digest_db_size); + else if (dentry == digest_list_data_del) + pr_debug("digest deleted, current DB size: %d\n", ima_digest_db_size); +#endif size -= rc; } vfree(data); + fput(file); if (rc < 0) return rc; else if (size) @@ -311,63 +362,106 @@ static ssize_t ima_read_policy(char *path) return pathlen; } -static ssize_t ima_write_policy(struct file *file, const char __user *buf, - size_t datalen, loff_t *ppos) +static ssize_t ima_write_data(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) { char *data; ssize_t result; - - if (datalen >= PAGE_SIZE) - datalen = PAGE_SIZE - 1; + struct dentry *dentry = file_dentry(file); + int i; /* No partial writes. */ result = -EINVAL; if (*ppos != 0) goto out; - data = memdup_user_nul(buf, datalen); - if (IS_ERR(data)) { - result = PTR_ERR(data); + result = -EFBIG; + if (datalen > 64 * 1024 * 1024 - 1) + goto out; + + result = -ENOMEM; + data = vmalloc(datalen + 1); + if (!data) goto out; - } + + result = -EFAULT; + if (copy_from_user(data, buf, datalen) != 0) + goto out_free; + + data[datalen] = '\0'; result = mutex_lock_interruptible(&ima_write_mutex); if (result < 0) goto out_free; if (data[0] == '/') { - result = ima_read_policy(data); - } else if (ima_appraise & IMA_APPRAISE_POLICY) { - pr_err("signed policy file (specified as an absolute pathname) required\n"); - integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, - "policy_update", "signed policy required", - 1, 0); - result = -EACCES; + for (i = 0; data[i] != '\n' && data[i] != '\0'; i++) { + if (iscntrl(data[i])) { + pr_err_once("invalid path (control characters are not allowed)\n"); + result = -EINVAL; + mutex_unlock(&ima_write_mutex); + goto out_free; + } + } + + result = ima_read_file(data, dentry); + } else if (dentry == ima_policy) { + if (ima_appraise & IMA_APPRAISE_POLICY) { + pr_err("signed policy file (specified " + "as an absolute pathname) required\n"); + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, + "policy_update", + "signed policy required", 1, 0); + result = -EACCES; + } else { + result = ima_parse_add_rule(data); + } + } else if (dentry == digest_list_data) { + if (!ima_current_is_parser()) { + result = -EACCES; + } else { + result = ima_parse_compact_list(datalen, data, + DIGEST_LIST_OP_ADD); + } + } else if (dentry == digest_list_data_del) { + if (!ima_current_is_parser()) { + result = -EACCES; + } else { + result = ima_parse_compact_list(datalen, data, + DIGEST_LIST_OP_DEL); + } } else { - result = ima_parse_add_rule(data); + pr_err("Unknown data type\n"); + result = -EINVAL; } mutex_unlock(&ima_write_mutex); out_free: - kfree(data); + vfree(data); out: - if (result < 0) + if (dentry == ima_policy && result < 0) valid_policy = 0; return result; } -static struct dentry *ima_dir; -static struct dentry *ima_symlink; -static struct dentry *binary_runtime_measurements; -static struct dentry *ascii_runtime_measurements; -static struct dentry *runtime_measurements_count; -static struct dentry *violations; -static struct dentry *ima_policy; - enum ima_fs_flags { + IMA_POLICY_BUSY, + IMA_DIGEST_LIST_DATA_BUSY, IMA_FS_BUSY, }; +static enum ima_fs_flags ima_get_dentry_flag(struct dentry *dentry) +{ + enum ima_fs_flags flag = IMA_FS_BUSY; + + if (dentry == ima_policy) + flag = IMA_POLICY_BUSY; + else if (dentry == digest_list_data || dentry == digest_list_data_del) + flag = IMA_DIGEST_LIST_DATA_BUSY; + + return flag; +} + static unsigned long ima_fs_flags; #ifdef CONFIG_IMA_READ_POLICY @@ -380,40 +474,65 @@ static const struct seq_operations ima_policy_seqops = { #endif /* - * ima_open_policy: sequentialize access to the policy file + * ima_open_data_upload: sequentialize access to the data upload interface */ -static int ima_open_policy(struct inode *inode, struct file *filp) +static int ima_open_data_upload(struct inode *inode, struct file *filp) { + struct dentry *dentry = file_dentry(filp); + const struct seq_operations *seq_ops = NULL; + enum ima_fs_flags flag = ima_get_dentry_flag(dentry); + bool read_allowed = false; + + if (dentry == ima_policy) { +#ifdef CONFIG_IMA_READ_POLICY + read_allowed = true; + seq_ops = &ima_policy_seqops; +#endif + } + if (!(filp->f_flags & O_WRONLY)) { -#ifndef CONFIG_IMA_READ_POLICY - return -EACCES; -#else + if (!read_allowed) + return -EACCES; if ((filp->f_flags & O_ACCMODE) != O_RDONLY) return -EACCES; if (!capable(CAP_SYS_ADMIN)) return -EPERM; - return seq_open(filp, &ima_policy_seqops); -#endif + return seq_open(filp, seq_ops); } - if (test_and_set_bit(IMA_FS_BUSY, &ima_fs_flags)) + if (test_and_set_bit(flag, &ima_fs_flags)) return -EBUSY; + + if (dentry == digest_list_data || dentry == digest_list_data_del) + if (ima_check_current_is_parser()) + ima_set_parser(); + return 0; } /* - * ima_release_policy - start using the new measure policy rules. + * ima_release_data_upload - start using the new measure policy rules. * * Initially, ima_measure points to the default policy rules, now * point to the new policy rules, and remove the securityfs policy file, * assuming a valid policy. */ -static int ima_release_policy(struct inode *inode, struct file *file) +static int ima_release_data_upload(struct inode *inode, struct file *file) { + struct dentry *dentry = file_dentry(file); const char *cause = valid_policy ? "completed" : "failed"; + enum ima_fs_flags flag = ima_get_dentry_flag(dentry); if ((file->f_flags & O_ACCMODE) == O_RDONLY) return seq_release(inode, file); + if (dentry == digest_list_data || dentry == digest_list_data_del) + ima_unset_parser(); + + if (dentry != ima_policy) { + clear_bit(flag, &ima_fs_flags); + return 0; + } + if (valid_policy && ima_check_policy() < 0) { cause = "failed"; valid_policy = 0; @@ -426,7 +545,7 @@ static int ima_release_policy(struct inode *inode, struct file *file) if (!valid_policy) { ima_delete_rules(); valid_policy = 1; - clear_bit(IMA_FS_BUSY, &ima_fs_flags); + clear_bit(flag, &ima_fs_flags); return 0; } @@ -435,18 +554,18 @@ static int ima_release_policy(struct inode *inode, struct file *file) securityfs_remove(ima_policy); ima_policy = NULL; #elif defined(CONFIG_IMA_WRITE_POLICY) - clear_bit(IMA_FS_BUSY, &ima_fs_flags); + clear_bit(flag, &ima_fs_flags); #elif defined(CONFIG_IMA_READ_POLICY) inode->i_mode &= ~S_IWUSR; #endif return 0; } -static const struct file_operations ima_measure_policy_ops = { - .open = ima_open_policy, - .write = ima_write_policy, +static const struct file_operations ima_data_upload_ops = { + .open = ima_open_data_upload, + .write = ima_write_data, .read = seq_read, - .release = ima_release_policy, + .release = ima_release_data_upload, .llseek = generic_file_llseek, }; @@ -486,7 +605,7 @@ int __init ima_fs_init(void) runtime_measurements_count = securityfs_create_file("runtime_measurements_count", S_IRUSR | S_IRGRP, ima_dir, NULL, - &ima_measurements_count_ops); + &ima_htable_value_ops); if (IS_ERR(runtime_measurements_count)) { ret = PTR_ERR(runtime_measurements_count); goto out; @@ -494,7 +613,7 @@ int __init ima_fs_init(void) violations = securityfs_create_file("violations", S_IRUSR | S_IRGRP, - ima_dir, NULL, &ima_htable_violations_ops); + ima_dir, NULL, &ima_htable_value_ops); if (IS_ERR(violations)) { ret = PTR_ERR(violations); goto out; @@ -502,15 +621,36 @@ int __init ima_fs_init(void) ima_policy = securityfs_create_file("policy", POLICY_FILE_FLAGS, ima_dir, NULL, - &ima_measure_policy_ops); + &ima_data_upload_ops); if (IS_ERR(ima_policy)) { ret = PTR_ERR(ima_policy); goto out; } +#ifdef CONFIG_IMA_DIGEST_LIST + digests_count = securityfs_create_file("digests_count", + S_IRUSR | S_IRGRP, ima_dir, + NULL, &ima_htable_value_ops); + if (IS_ERR(digests_count)) + goto out; + digest_list_data = securityfs_create_file("digest_list_data", S_IWUSR, + ima_dir, NULL, + &ima_data_upload_ops); + if (IS_ERR(digest_list_data)) + goto out; + + digest_list_data_del = securityfs_create_file("digest_list_data_del", + S_IWUSR, ima_dir, NULL, + &ima_data_upload_ops); + if (IS_ERR(digest_list_data_del)) + goto out; +#endif return 0; out: securityfs_remove(ima_policy); + securityfs_remove(digest_list_data_del); + securityfs_remove(digest_list_data); + securityfs_remove(digests_count); securityfs_remove(violations); securityfs_remove(runtime_measurements_count); securityfs_remove(ascii_runtime_measurements); diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 63979aefc95f7e018852bc301a740468c75db742..be2aab51c2202758ee0bf4bf64bfae30111fbcfa 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -85,7 +85,7 @@ static int __init ima_add_boot_aggregate(void) result = ima_store_template(entry, violation, NULL, boot_aggregate_name, - CONFIG_IMA_MEASURE_PCR_IDX); + CONFIG_IMA_MEASURE_PCR_IDX, NULL); if (result < 0) { ima_free_template_entry(entry); audit_cause = "store_entry"; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index d66a0a36415e8cb495162e39521abdd559c05141..c61d58ab95975aabd295e65218e0657b735f4df7 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -28,6 +28,7 @@ #include #include "ima.h" +#include "ima_digest_list.h" #ifdef CONFIG_IMA_APPRAISE int ima_appraise = IMA_APPRAISE_ENFORCE; @@ -36,6 +37,14 @@ int ima_appraise; #endif int __ro_after_init ima_hash_algo = HASH_ALGO_SHA1; + +/* Actions (measure/appraisal) for which digest lists can be used */ +int ima_digest_list_actions; +/* PCR used for digest list measurements */ +int ima_digest_list_pcr = -1; +/* Flag to include standard measurement if digest list PCR is specified */ +bool ima_plus_standard_pcr; + static int hash_setup_done; static struct notifier_block ima_lsm_policy_notifier = { @@ -153,6 +162,65 @@ static void ima_rdwr_violation_check(struct file *file, "invalid_pcr", "open_writers"); } +static enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, + int xattr_len) +{ + struct signature_v2_hdr *sig; + enum hash_algo ret; + + if (!xattr_value || xattr_len < 2) + /* return default hash algo */ + return ima_hash_algo; + + switch (xattr_value->type) { + case IMA_VERITY_DIGSIG: + sig = (typeof(sig))xattr_value; + if (sig->version != 3 || xattr_len < sizeof(*sig) || + sig->hash_algo >= HASH_ALGO__LAST) + return ima_hash_algo; + return sig->hash_algo; + case EVM_IMA_XATTR_DIGSIG: + sig = (typeof(sig))xattr_value; + if (sig->version != 2 || xattr_len <= sizeof(*sig) + || sig->hash_algo >= HASH_ALGO__LAST) + return ima_hash_algo; + return sig->hash_algo; + case EVM_IMA_XATTR_DIGEST_LIST: + fallthrough; + case IMA_XATTR_DIGEST_NG: + /* first byte contains algorithm id */ + ret = xattr_value->data[0]; + if (ret < HASH_ALGO__LAST) + return ret; + break; + case IMA_XATTR_DIGEST: + /* this is for backward compatibility */ + if (xattr_len == 21) { + unsigned int zero = 0; + if (!memcmp(&xattr_value->data[16], &zero, 4)) + return HASH_ALGO_MD5; + else + return HASH_ALGO_SHA1; + } else if (xattr_len == 17) + return HASH_ALGO_MD5; + break; + } + + /* return default hash algo */ + return ima_hash_algo; +} + +static int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value, int xattr_len) +{ + int ret; + + ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_IMA, + (char **)xattr_value, xattr_len, GFP_NOFS); + if (ret == -EOPNOTSUPP) + ret = 0; + return ret; +} static void ima_check_last_writer(struct integrity_iint_cache *iint, struct inode *inode, struct file *file) { @@ -211,6 +279,7 @@ static int process_measurement(struct file *file, const struct cred *cred, const char *pathname = NULL; int rc = 0, action, must_appraise = 0; int pcr = CONFIG_IMA_MEASURE_PCR_IDX; + struct ima_digest *found_digest; struct evm_ima_xattr_data *xattr_value = NULL; struct modsig *modsig = NULL; int xattr_len = 0; @@ -345,17 +414,25 @@ static int process_measurement(struct file *file, const struct cred *cred, if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ pathname = ima_d_path(&file->f_path, &pathbuf, filename); + found_digest = ima_lookup_digest(iint->ima_hash->digest, hash_algo, + COMPACT_FILE); + if (action & IMA_MEASURE) ima_store_measurement(iint, file, pathname, xattr_value, xattr_len, modsig, pcr, - template_desc); + template_desc, + ima_digest_allow(found_digest, + IMA_MEASURE)); + if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) { rc = ima_check_blacklist(iint, modsig, pcr); if (rc != -EPERM) { inode_lock(inode); rc = ima_appraise_measurement(func, iint, file, - pathname, xattr_value, - xattr_len, modsig); + pathname, xattr_value, + xattr_len, modsig, + ima_digest_allow(found_digest, + IMA_APPRAISE)); inode_unlock(inode); } if (!rc) @@ -507,18 +584,18 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot) */ int ima_bprm_check(struct linux_binprm *bprm) { - int ret; - u32 secid; + int ret; + u32 secid; - security_current_getsecid_subj(&secid); - ret = process_measurement(bprm->file, current_cred(), secid, NULL, 0, - MAY_EXEC, BPRM_CHECK); - if (ret) - return ret; - - security_cred_getsecid(bprm->cred, &secid); - return process_measurement(bprm->file, bprm->cred, secid, NULL, 0, - MAY_EXEC, CREDS_CHECK); + security_current_getsecid_subj(&secid); + ret = process_measurement(bprm->file, current_cred(), secid, NULL, 0, + MAY_EXEC, BPRM_CHECK); + if (ret) + return ret; + + security_cred_getsecid(bprm->cred, &secid); + return process_measurement(bprm->file, bprm->cred, secid, NULL, 0, + MAY_EXEC, CREDS_CHECK); } /** @@ -533,12 +610,16 @@ int ima_bprm_check(struct linux_binprm *bprm) */ int ima_file_check(struct file *file, int mask) { - u32 secid; + u32 secid; + int rc; - security_current_getsecid_subj(&secid); - return process_measurement(file, current_cred(), secid, NULL, 0, - mask & (MAY_READ | MAY_WRITE | MAY_EXEC | - MAY_APPEND), FILE_CHECK); + security_current_getsecid_subj(&secid); + rc = process_measurement(file, current_cred(), secid, NULL, 0, + mask & (MAY_READ | MAY_WRITE | MAY_EXEC | + MAY_APPEND), FILE_CHECK); + if (ima_current_is_parser() && !rc) + ima_check_measured_appraised(file); + return rc; } EXPORT_SYMBOL_GPL(ima_file_check); @@ -767,7 +848,8 @@ const int read_idmap[READING_MAX_ID] = { [READING_MODULE] = MODULE_CHECK, [READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK, [READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK, - [READING_POLICY] = POLICY_CHECK + [READING_POLICY] = POLICY_CHECK, + [READING_DIGEST_LIST] = DIGEST_LIST_CHECK }; /** @@ -997,7 +1079,7 @@ int process_buffer_measurement(struct mnt_idmap *idmap, goto out; } - ret = ima_store_template(entry, violation, NULL, event_data.buf, pcr); + ret = ima_store_template(entry, violation, NULL, event_data.buf, pcr, NULL); if (ret < 0) { audit_cause = "store_entry"; ima_free_template_entry(entry); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 3ca8b7348c2e45ffc3cc607f8d4237ab50d8fc9c..582d81f8bfde9b737817b63b11b93d72a223df4d 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -20,6 +20,7 @@ #include #include "ima.h" +#include "ima_digest_list.h" /* flags definitions */ #define IMA_FUNC 0x0001 @@ -34,6 +35,7 @@ #define IMA_FSNAME 0x0200 #define IMA_KEYRINGS 0x0400 #define IMA_LABEL 0x0800 +#define IMA_PARSER 0x10000 #define IMA_VALIDATE_ALGOS 0x1000 #define IMA_GID 0x2000 #define IMA_EGID 0x4000 @@ -62,7 +64,7 @@ enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE }; -enum policy_types { ORIGINAL_TCB = 1, DEFAULT_TCB }; +enum policy_types { ORIGINAL_TCB = 1, DEFAULT_TCB, EXEC_TCB }; enum policy_rule_list { IMA_DEFAULT_POLICY = 1, IMA_CUSTOM_POLICY }; @@ -188,6 +190,11 @@ static struct ima_rule_entry default_measurement_rules[] __ro_after_init = { {.action = MEASURE, .func = MODULE_CHECK, .flags = IMA_FUNC}, {.action = MEASURE, .func = FIRMWARE_CHECK, .flags = IMA_FUNC}, {.action = MEASURE, .func = POLICY_CHECK, .flags = IMA_FUNC}, + {.action = MEASURE, .func = DIGEST_LIST_CHECK, .flags = IMA_FUNC}, +}; + +static struct ima_rule_entry ima_parser_measure_rule __ro_after_init = { + .action = MEASURE, .flags = IMA_PARSER }; static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { @@ -219,6 +226,13 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { #endif }; +static struct ima_rule_entry appraise_exec_rules[] __ro_after_init = { + {.action = APPRAISE, .func = BPRM_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = MMAP_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +}; + static struct ima_rule_entry build_appraise_rules[] __ro_after_init = { #ifdef CONFIG_IMA_APPRAISE_REQUIRE_MODULE_SIGS {.action = APPRAISE, .func = MODULE_CHECK, @@ -247,6 +261,13 @@ static struct ima_rule_entry secure_boot_rules[] __ro_after_init = { .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, {.action = APPRAISE, .func = POLICY_CHECK, .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = DIGEST_LIST_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +}; + +static struct ima_rule_entry ima_parser_appraise_rule __ro_after_init = { + .action = APPRAISE, + .flags = IMA_PARSER | IMA_DIGSIG_REQUIRED }; static struct ima_rule_entry critical_data_rules[] __ro_after_init = { @@ -274,6 +295,8 @@ static int __init default_measure_policy_setup(char *str) __setup("ima_tcb", default_measure_policy_setup); static bool ima_use_appraise_tcb __initdata; +static bool ima_use_appraise_exec_tcb __initdata; +static bool ima_use_appraise_exec_immutable __initdata; static bool ima_use_secure_boot __initdata; static bool ima_use_critical_data __initdata; static bool ima_fail_unverifiable_sigs __ro_after_init; @@ -286,8 +309,14 @@ static int __init policy_setup(char *str) continue; if ((strcmp(p, "tcb") == 0) && !ima_policy) ima_policy = DEFAULT_TCB; + else if ((strcmp(p, "exec_tcb") == 0) && !ima_policy) + ima_policy = EXEC_TCB; else if (strcmp(p, "appraise_tcb") == 0) ima_use_appraise_tcb = true; + else if (strcmp(p, "appraise_exec_tcb") == 0) + ima_use_appraise_exec_tcb = true; + else if (strcmp(p, "appraise_exec_immutable") == 0) + ima_use_appraise_exec_immutable = true; else if (strcmp(p, "secure_boot") == 0) ima_use_secure_boot = true; else if (strcmp(p, "critical_data") == 0) @@ -627,6 +656,9 @@ static bool ima_match_rules(struct ima_rule_entry *rule, !rule->fowner_op(i_uid_into_vfsuid(idmap, inode), rule->fowner)) return false; + if ((rule->flags & IMA_PARSER) && + !ima_current_is_parser()) + return false; if ((rule->flags & IMA_FGROUP) && !rule->fgroup_op(i_gid_into_vfsgid(idmap, inode), rule->fgroup)) @@ -855,17 +887,47 @@ static int ima_appraise_flag(enum ima_hooks func) return IMA_APPRAISE_POLICY; else if (func == KEXEC_KERNEL_CHECK) return IMA_APPRAISE_KEXEC; + else if (func == DIGEST_LIST_CHECK) + return IMA_APPRAISE_DIGEST_LIST; return 0; } -static void add_rules(struct ima_rule_entry *entries, int count, - enum policy_rule_list policy_rule) +static void __init add_rules(struct ima_rule_entry *entries, int count, + enum policy_rule_list policy_rule) { int i = 0; for (i = 0; i < count; i++) { struct ima_rule_entry *entry; + if (ima_policy == EXEC_TCB) { + if (entries == dont_measure_rules) + if ((entries[i].flags & IMA_FSMAGIC) && + entries[i].fsmagic == TMPFS_MAGIC) + continue; + + if (entries == default_measurement_rules) + if ((entries[i].flags & IMA_FUNC) && + entries[i].func == FILE_CHECK) + continue; + } + + if (ima_use_appraise_exec_tcb) { + if (entries == default_appraise_rules) { + if (entries[i].action != DONT_APPRAISE) + continue; + if ((entries[i].flags & IMA_FSMAGIC) && + entries[i].fsmagic == TMPFS_MAGIC) + continue; + } + } + + if (ima_use_appraise_exec_immutable) + if (entries == appraise_exec_rules && + (entries[i].flags & IMA_FUNC) && + entries[i].func == BPRM_CHECK) + entries[i].flags |= IMA_META_IMMUTABLE_REQUIRED; + if (policy_rule & IMA_DEFAULT_POLICY) list_add_tail(&entries[i].list, &ima_default_rules); @@ -951,6 +1013,8 @@ void __init ima_init_policy(void) ARRAY_SIZE(original_measurement_rules), IMA_DEFAULT_POLICY); break; + case EXEC_TCB: + fallthrough; case DEFAULT_TCB: add_rules(default_measurement_rules, ARRAY_SIZE(default_measurement_rules), @@ -960,6 +1024,9 @@ void __init ima_init_policy(void) break; } + if (ima_policy) + add_rules(&ima_parser_measure_rule, 1, IMA_DEFAULT_POLICY); + /* * Based on runtime secure boot flags, insert arch specific measurement * and appraise rules requiring file signatures for both the initial @@ -977,7 +1044,7 @@ void __init ima_init_policy(void) * Insert the builtin "secure_boot" policy rules requiring file * signatures, prior to other appraise rules. */ - if (ima_use_secure_boot) + if (ima_use_secure_boot || ima_use_appraise_exec_tcb) add_rules(secure_boot_rules, ARRAY_SIZE(secure_boot_rules), IMA_DEFAULT_POLICY); @@ -997,11 +1064,16 @@ void __init ima_init_policy(void) IMA_DEFAULT_POLICY | IMA_CUSTOM_POLICY); } - if (ima_use_appraise_tcb) + if (ima_use_appraise_tcb || ima_use_appraise_exec_tcb) add_rules(default_appraise_rules, ARRAY_SIZE(default_appraise_rules), IMA_DEFAULT_POLICY); + if (ima_use_appraise_exec_tcb) + add_rules(appraise_exec_rules, + ARRAY_SIZE(appraise_exec_rules), + IMA_DEFAULT_POLICY); + if (ima_use_critical_data) add_rules(critical_data_rules, ARRAY_SIZE(critical_data_rules), @@ -1009,6 +1081,10 @@ void __init ima_init_policy(void) atomic_set(&ima_setxattr_allowed_hash_algorithms, 0); + if (ima_use_secure_boot || ima_use_appraise_tcb || + ima_use_appraise_exec_tcb) + add_rules(&ima_parser_appraise_rule, 1, IMA_DEFAULT_POLICY); + ima_update_policy_flags(); } @@ -1072,7 +1148,7 @@ enum policy_opt { Opt_digest_type, Opt_appraise_type, Opt_appraise_flag, Opt_appraise_algos, Opt_permit_directio, Opt_pcr, Opt_template, Opt_keyrings, - Opt_label, Opt_err + Opt_label, Opt_parser, Opt_err }; static const match_table_t policy_tokens = { @@ -1121,6 +1197,7 @@ static const match_table_t policy_tokens = { {Opt_template, "template=%s"}, {Opt_keyrings, "keyrings=%s"}, {Opt_label, "label=%s"}, + {Opt_parser, "parser"}, {Opt_err, NULL} }; @@ -1246,7 +1323,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) if (entry->action != APPRAISE && entry->flags & (IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED | - IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS)) + IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS | + IMA_META_IMMUTABLE_REQUIRED | IMA_PARSER)) return false; /* @@ -1273,13 +1351,14 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) case POST_SETATTR: case FIRMWARE_CHECK: case POLICY_CHECK: + case DIGEST_LIST_CHECK: if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC | IMA_UID | IMA_FOWNER | IMA_FSUUID | IMA_INMASK | IMA_EUID | IMA_PCR | IMA_FSNAME | IMA_GID | IMA_EGID | IMA_FGROUP | IMA_DIGSIG_REQUIRED | IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS | - IMA_VERITY_REQUIRED)) + IMA_VERITY_REQUIRED | IMA_META_IMMUTABLE_REQUIRED)) return false; break; @@ -1292,7 +1371,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry) IMA_FSNAME | IMA_GID | IMA_EGID | IMA_FGROUP | IMA_DIGSIG_REQUIRED | IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED | - IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS)) + IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS | + IMA_META_IMMUTABLE_REQUIRED)) return false; break; @@ -1529,6 +1609,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->func = CRITICAL_DATA; else if (strcmp(args[0].from, "SETXATTR_CHECK") == 0) entry->func = SETXATTR_CHECK; + else if (strcmp(args[0].from, "DIGEST_LIST_CHECK") == 0) + entry->func = DIGEST_LIST_CHECK; else result = -EINVAL; if (!result) @@ -1816,7 +1898,9 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) else entry->flags |= IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED; - } else { + } else if (strcmp(args[0].from, "meta_immutable") == 0) + entry->flags |= IMA_META_IMMUTABLE_REQUIRED; + else { result = -EINVAL; } break; @@ -1854,7 +1938,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) ima_log_string(ab, "pcr", args[0].from); result = kstrtoint(args[0].from, 10, &entry->pcr); - if (result || INVALID_PCR(entry->pcr)) + if (result || INVALID_PCR(entry->pcr) || + entry->pcr == ima_digest_list_pcr) result = -EINVAL; else entry->flags |= IMA_PCR; @@ -1882,6 +1967,10 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) &(template_desc->num_fields)); entry->template = template_desc; break; + case Opt_parser: + audit_log_format(ab, "parser "); + entry->flags |= IMA_PARSER; + break; case Opt_err: ima_log_string(ab, "UNKNOWN", p); result = -EINVAL; @@ -2227,6 +2316,9 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, " "); } + if (entry->flags & IMA_PARSER) + seq_puts(m, "parser "); + for (i = 0; i < MAX_LSM_RULES; i++) { if (entry->lsm[i].rule) { switch (i) { @@ -2272,6 +2364,8 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, "digest_type=verity "); if (entry->flags & IMA_CHECK_BLACKLIST) seq_puts(m, "appraise_flag=check_blacklist "); + if (entry->flags & IMA_META_IMMUTABLE_REQUIRED) + seq_puts(m, "appraise_type=meta_immutable "); if (entry->flags & IMA_PERMIT_DIRECTIO) seq_puts(m, "permit_directio "); rcu_read_unlock(); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7167a6e99bdc02c9093b70eb90354008262aa486..4f0167052b26fabc86e7c2f6276d471bb74f7616 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include /* iint action cache flags */ #define IMA_MEASURE 0x00000001 @@ -41,6 +43,7 @@ #define IMA_MODSIG_ALLOWED 0x20000000 #define IMA_CHECK_BLACKLIST 0x40000000 #define IMA_VERITY_REQUIRED 0x80000000 +#define IMA_META_IMMUTABLE_REQUIRED 0x100000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ IMA_HASH | IMA_APPRAISE_SUBMASK) @@ -72,6 +75,7 @@ #define IMA_CHANGE_ATTR 2 #define IMA_DIGSIG 3 #define IMA_MUST_MEASURE 4 +#define IMA_DIGEST_LIST 5 enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, @@ -80,6 +84,7 @@ enum evm_ima_xattr_type { IMA_XATTR_DIGEST_NG, EVM_XATTR_PORTABLE_DIGSIG, IMA_VERITY_DIGSIG, + EVM_IMA_XATTR_DIGEST_LIST, IMA_XATTR_LAST }; @@ -91,7 +96,7 @@ struct evm_ima_xattr_data { /* Only used in the EVM HMAC code. */ struct evm_xattr { struct evm_ima_xattr_data data; - u8 digest[SHA1_DIGEST_SIZE]; + u8 digest[SHA512_DIGEST_SIZE]; } __packed; #define IMA_MAX_DIGEST_SIZE HASH_MAX_DIGESTSIZE @@ -173,6 +178,46 @@ struct integrity_iint_cache { struct ima_digest_data *ima_hash; }; +enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE, + COMPACT_METADATA, COMPACT__LAST }; +enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST }; + +struct ima_digest { + struct hlist_node hnext; + enum hash_algo algo; + enum compact_types type; + u16 modifiers; + u16 count; + u8 digest[0]; +}; + +static inline bool ima_digest_is_immutable(struct ima_digest *digest) +{ + return (digest->modifiers & (1 << COMPACT_MOD_IMMUTABLE)); +} + +#ifdef CONFIG_IMA_DIGEST_LIST +struct ima_digest *ima_lookup_digest(u8 *digest, enum hash_algo algo, + enum compact_types type); +struct ima_digest *ima_digest_allow(struct ima_digest *digest, int action); +void __init ima_load_digest_lists(void); +#else +static inline struct ima_digest *ima_lookup_digest(u8 *digest, + enum hash_algo algo, + enum compact_types type) +{ + return NULL; +} +static inline struct ima_digest *ima_digest_allow(struct ima_digest *digest, + int action) +{ + return NULL; +} +static inline void ima_load_digest_lists(void) +{ +} +#endif + /* rbtree tree calls to lookup, insert, delete * integrity data associated with an inode. */ diff --git a/usr/Kconfig b/usr/Kconfig index 8bbcf699fe3bebecca9436167a2c57a24d505215..be7a8ba66c449b4d34db9ab9e58a2bb9bd1ff091 100644 --- a/usr/Kconfig +++ b/usr/Kconfig @@ -227,3 +227,11 @@ config INITRAMFS_COMPRESSION_NONE filesystem image will be present in memory simultaneously endchoice + +config INITRAMFS_FILE_METADATA + string "File metadata type" + default "" + help + Specify xattr to include xattrs in the image. + + If you are not sure, leave it blank. diff --git a/usr/Makefile b/usr/Makefile index 59d9e8b07a017ce2fe26833d32d941e20d59d954..f82b6f74aa7bdf3e9c939cdef65539669b4cfb0d 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -64,7 +64,9 @@ quiet_cmd_initfs = GEN $@ $(CONFIG_SHELL) $< -o $@ -l $(obj)/.initramfs_data.cpio.d \ $(if $(CONFIG_INITRAMFS_ROOT_UID), -u $(CONFIG_INITRAMFS_ROOT_UID)) \ $(if $(CONFIG_INITRAMFS_ROOT_GID), -g $(CONFIG_INITRAMFS_ROOT_GID)) \ - $(ramfs-input) + $(if $(CONFIG_INITRAMFS_ROOT_GID), -g $(CONFIG_INITRAMFS_ROOT_GID)) \ + $(if $(filter-out "",$(CONFIG_INITRAMFS_FILE_METADATA)), \ + -e $(CONFIG_INITRAMFS_FILE_METADATA)) $(ramfs-input) # We rebuild initramfs_data.cpio if: # 1) Any included file is newer than initramfs_data.cpio diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c index 61230532fef10f7261db75e5757a8b2c4366d2d9..c9d403403bd3d5fb9e74800a3d74b9260c8ee2d2 100644 --- a/usr/gen_init_cpio.c +++ b/usr/gen_init_cpio.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include "../include/linux/initramfs.h" /* * Original work by Jeff Garzik @@ -28,6 +30,115 @@ static unsigned int offset; static unsigned int ino = 721; static time_t default_mtime; static bool do_csum = false; +static char metadata_path[] = "/tmp/cpio-metadata-XXXXXX"; +static int metadata_fd = -1; + +static enum metadata_types parse_metadata_type(char *arg) +{ + static char *metadata_type_str[TYPE__LAST] = { + [TYPE_NONE] = "none", + [TYPE_XATTR] = "xattr", + }; + int i; + + for (i = 0; i < TYPE__LAST; i++) + if (!strcmp(metadata_type_str[i], arg)) + return i; + + return TYPE_NONE; +} + +static int cpio_mkfile(const char *name, const char *location, + unsigned int mode, uid_t uid, gid_t gid, + unsigned int nlinks); + +static int write_xattrs(const char *path) +{ + struct metadata_hdr hdr = { .c_version = 1, .c_type = TYPE_XATTR }; + char str[sizeof(hdr.c_size) + 1]; + char *xattr_list, *list_ptr, *xattr_value; + ssize_t list_len, name_len, value_len, len; + int ret = -EINVAL; + + if (metadata_fd < 0) + return 0; + + if (path == metadata_path) + return 0; + + list_len = listxattr(path, NULL, 0); + if (list_len <= 0) + return 0; + + list_ptr = xattr_list = malloc(list_len); + if (!list_ptr) { + fprintf(stderr, "out of memory\n"); + return ret; + } + + len = listxattr(path, xattr_list, list_len); + if (len != list_len) + goto out; + + if (ftruncate(metadata_fd, 0)) + goto out; + + lseek(metadata_fd, 0, SEEK_SET); + + while (list_ptr < xattr_list + list_len) { + name_len = strlen(list_ptr); + + value_len = getxattr(path, list_ptr, NULL, 0); + if (value_len < 0) { + fprintf(stderr, "cannot get xattrs\n"); + break; + } + + if (value_len) { + xattr_value = malloc(value_len); + if (!xattr_value) { + fprintf(stderr, "out of memory\n"); + break; + } + } else { + xattr_value = NULL; + } + + len = getxattr(path, list_ptr, xattr_value, value_len); + if (len != value_len) + break; + + snprintf(str, sizeof(str), "%.8lx", + sizeof(hdr) + name_len + 1 + value_len); + + memcpy(hdr.c_size, str, sizeof(hdr.c_size)); + + if (write(metadata_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + break; + + if (write(metadata_fd, list_ptr, name_len + 1) != name_len + 1) + break; + + if (write(metadata_fd, xattr_value, value_len) != value_len) + break; + + if (fsync(metadata_fd)) + break; + + list_ptr += name_len + 1; + free(xattr_value); + xattr_value = NULL; + } + + free(xattr_value); +out: + if (list_ptr != xattr_list + list_len) + return ret; + + free(xattr_list); + + return cpio_mkfile(METADATA_FILENAME, metadata_path, S_IFREG, 0, 0, 1); +} struct file_handler { const char *type; @@ -132,7 +243,7 @@ static int cpio_mkslink(const char *name, const char *target, push_pad(); push_string(target); push_pad(); - return 0; + return write_xattrs(name); } static int cpio_mkslink_line(const char *line) @@ -178,7 +289,7 @@ static int cpio_mkgeneric(const char *name, unsigned int mode, 0); /* chksum */ push_hdr(s); push_rest(name); - return 0; + return write_xattrs(name); } enum generic_types { @@ -272,7 +383,7 @@ static int cpio_mknod(const char *name, unsigned int mode, 0); /* chksum */ push_hdr(s); push_rest(name); - return 0; + return write_xattrs(name); } static int cpio_mknod_line(const char *line) @@ -422,7 +533,7 @@ static int cpio_mkfile(const char *name, const char *location, name += namesize; } ino++; - rc = 0; + rc = write_xattrs(location); error: if (file >= 0) @@ -577,10 +688,11 @@ int main (int argc, char *argv[]) int ec = 0; int line_nr = 0; const char *filename; + enum metadata_types metadata_type = TYPE_NONE; default_mtime = time(NULL); while (1) { - int opt = getopt(argc, argv, "t:ch"); + int opt = getopt(argc, argv, "t:e:ch"); char *invalid; if (opt == -1) @@ -595,6 +707,9 @@ int main (int argc, char *argv[]) exit(1); } break; + case 'e': + metadata_type = parse_metadata_type(optarg); + break; case 'c': do_csum = true; break; @@ -629,6 +744,14 @@ int main (int argc, char *argv[]) exit(1); } + if (metadata_type != TYPE_NONE) { + metadata_fd = mkstemp(metadata_path); + if (metadata_fd < 0) { + fprintf(stderr, "cannot create temporary file\n"); + exit(1); + } + } + while (fgets(line, LINE_SIZE, cpio_list)) { int type_idx; size_t slen = strlen(line); @@ -684,5 +807,8 @@ int main (int argc, char *argv[]) if (ec == 0) cpio_trailer(); + if (metadata_type != TYPE_NONE) + close(metadata_fd); + exit(ec); } diff --git a/usr/gen_initramfs.sh b/usr/gen_initramfs.sh index 63476bb70b41ef3f90d4fe1649461ba7160787a6..36648620d071134265cd145dcfbc48197c3a3774 100755 --- a/usr/gen_initramfs.sh +++ b/usr/gen_initramfs.sh @@ -26,6 +26,7 @@ $0 [-o ] [-l ] [-u ] [-g ] {-d | } ... File list or directory for cpio archive. If is a .cpio file it will be used as direct input to initramfs. + -e File metadata type to include in the cpio archive. All options except -o and -l may be repeated and are interpreted sequentially and immediately. -u and -g states are preserved across @@ -218,6 +219,10 @@ while [ $# -gt 0 ]; do [ "$root_gid" = "-1" ] && root_gid=$(id -g || echo 0) shift ;; + "-e") # file metadata type + metadata_arg="-e $1" + shift + ;; "-h") usage exit 0 @@ -244,4 +249,4 @@ if test -n "$KBUILD_BUILD_TIMESTAMP"; then timestamp="-t $timestamp" fi fi -usr/gen_init_cpio $timestamp $cpio_list > $output +usr/gen_init_cpio $metadata_arg $timestamp $cpio_list > $output