From patchwork Fri May 26 03:06:06 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AKASHI Takahiro X-Patchwork-Id: 100539 Delivered-To: patch@linaro.org Received: by 10.140.96.100 with SMTP id j91csp58889qge; Thu, 25 May 2017 20:02:49 -0700 (PDT) X-Received: by 10.98.211.202 with SMTP id z71mr48452677pfk.46.1495767769076; Thu, 25 May 2017 20:02:49 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1495767769; cv=none; d=google.com; s=arc-20160816; b=oskqdQNK00E2gbdyiLjkezeTzQuWmqqmHFzFTpYz/xn1MzlnlWk6Uwtg8VF2CipxP9 Kexfyvhb5S8aQxZLqKysDkvJlfmSuVt7uUG37UyRVwaKPxpwIMS27lZZBxCt6jlMTRqR 3pFJNw5DFL4bpmrM3poYNZFln5OwO91W6xaxG+PEyVBzeJg17HRqstXssDPovwOHSVlE MjiOJbYqO0Y3B7YgjDPJLWBz9dvqAHOkgzWsap9dVAmdUaEGd+xzHqnYwAMJP+3DfiP6 1tq6liL4grllwDyktBEGlOh5fy7VjOpGqGT7TisiPQGVWEBffotnvhhJSkSwZjn1zVXL PL3A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:dkim-signature:arc-authentication-results; bh=bExCwVIec8kX5zKC+kGsDZZruzL77t5A9SRRc9RdPuw=; b=oIcBn8CipmmR4UZFSR7ydpvDBNnsele3NMjCUv45l5A8x8puJm0jukfsEYdz3/n404 zPevOpCWYl+kx5dAU7IsvPqcs6lewlzFJS7lPurlO/7kKiYdVRMGQym371DKapd6hUFL Nib8HvC0Isg0SRYb/wbL7CVr3KixJvMMmXOT1yz7+L8jnUpvVW8UMybo8KUrggvjPm/i 193gQGeBn0GXblAP9hXm1mtRFwhP3MHTTUhTWkh3/g4kB2QfNmPTcLQpru066meR/TcO q+ylt6B/NLZllYPRMx3fmPSX6CGcLVpXjWNOFKeT0VmWw9c2VXdZYXfoICvzclWIrTWy vKWg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id d22si29899690pgn.220.2017.05.25.20.02.48; Thu, 25 May 2017 20:02:49 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1946698AbdEZDCm (ORCPT + 25 others); Thu, 25 May 2017 23:02:42 -0400 Received: from mail-pf0-f180.google.com ([209.85.192.180]:35481 "EHLO mail-pf0-f180.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S969428AbdEZDCV (ORCPT ); Thu, 25 May 2017 23:02:21 -0400 Received: by mail-pf0-f180.google.com with SMTP id n23so184340987pfb.2 for ; Thu, 25 May 2017 20:02:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=bExCwVIec8kX5zKC+kGsDZZruzL77t5A9SRRc9RdPuw=; b=QMSpGiodpVcXi0S/HWz3cHq1APWKEmazUT0nt5yfrhN8eQQ0eqBUXAsvH0MxQ0ESnq nhRhdck+mZb6pb55UslCjXMe1dpDvTjlDktb7mVnDqxjAue5sZTMVBT1nAvCe8Z31p/B QkLmHjK+vznaeMrLK/9thefuGI+gsDh7cN/ro= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=bExCwVIec8kX5zKC+kGsDZZruzL77t5A9SRRc9RdPuw=; b=VWnf8GoTE9NvxXTBbSU2XaMX+EboBqYp6wDTPIdtlB1xB/gsN1UVRjkZlSrvNNILwD BrxBTX8zig21dEhG+MEMiOP0+zeyYuQjfwJhYOfevrhgsq2rsAzvF3myJCXZzgZvrC53 i1oT6esyZvdVyujWiTgi4FXn59z/ZXxEP8zJ6t2e4ENRzyPxP56YmyQVoK/35Xdk0Iq9 ryvHJZ9pDrvrJPWb4EFKIb24R3E1UuHgwQ7q0Hmd3v4Cl8bH4lZw6ZKW989qy/PGGxAe ys554GSec8ODhEbS/nwSJrvD+rU9Q6pdTG4NniHx7gX9WQE8Cy8fHr4f4YUgppHE7bqV 8lZg== X-Gm-Message-State: AODbwcB3eNe3RS5IXICCZjkqxSH8CjSG/uw2/p5f0fF1JsbSOT/MNs6h o8Iv5Zfj1OLQVd39 X-Received: by 10.84.214.150 with SMTP id j22mr54495047pli.133.1495767740645; Thu, 25 May 2017 20:02:20 -0700 (PDT) Received: from linaro.org ([121.95.100.191]) by smtp.googlemail.com with ESMTPSA id s4sm2782354pgr.10.2017.05.25.20.02.19 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 25 May 2017 20:02:20 -0700 (PDT) From: AKASHI Takahiro To: mcgrof@kernel.org Cc: rusty@rustcorp.com.au, dhowells@redhat.com, ming.lei@canonical.com, seth.forshee@canonical.com, kyle@kernel.org, David.Woodhouse@intel.com, linux-kernel@vger.kernel.org, AKASHI Takahiro , "Luis R . Rodriguez" Subject: [PATCH 1/4] firmware: add firmware signing Date: Fri, 26 May 2017 12:06:06 +0900 Message-Id: <20170526030609.1414-2-takahiro.akashi@linaro.org> X-Mailer: git-send-email 2.11.1 In-Reply-To: <20170526030609.1414-1-takahiro.akashi@linaro.org> References: <20170526030609.1414-1-takahiro.akashi@linaro.org> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Systems that have module signing currently enabled may wish to extend vetting of firmware passed to the kernel as well. We can re-use most of the code for module signing for firmware signature verification and signing. This will also later enable re-use of this same code for subsystems that wish to provide their own cryptographic verification requirements on userspace data needed. Contrary to module signing, the signature files are expected to be completely detached for practical and licensing puposes. If you have foo.bin, you'll need foo.bin.p7s file present for firmware signing. Firmware signing is per-data per device and if this feature is enabled permissively (by default), both valid and invalid firmware, which can be unsigned, signed by non-trusted key, or even one with invalid digest, will be loaded, just leaving a warning message in the kernel log. On the othe hand, in enforcing mode, which is enabled by either a kernel configuration (CONFIG_FIRMWARE_SIG_FORCE) or a module parameter (fw_sig_enforce), only the verified firmware are allowed to be loaded. There is one driver data option, DRIVER_DATA_REQ_NO_SIG_CHECK, which will skip signature verification check at load time even in enforcing mode. This option is solely for non security-sensitive data. Please also note any firmware loaded with request_firmware() will not be affected by firmware signing. Contrary to module signing, we do not taint the kernel in the permissive fw signing mode due to restrictions on the firmware_class API, extensions to enable this are expected however in the future. Cc: Rusty Russell Cc: David Howells Cc: Ming Lei Cc: Seth Forshee Cc: Kyle McMartin Cc: David Woodhouse Signed-off-by: Luis R. Rodriguez [akashi:migrated to driver data APIs] Signed-off-by: AKASHI Takahiro --- drivers/base/Kconfig | 25 +++++ drivers/base/firmware_class.c | 211 +++++++++++++++++++++++++++++++++++++++--- include/linux/driver_data.h | 5 + 3 files changed, 229 insertions(+), 12 deletions(-) -- 2.11.1 diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 74779ee3d3b1..4c9600437396 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -79,6 +79,31 @@ config FW_LOADER require userspace firmware loading support, but a module built out-of-tree does. +config FIRMWARE_SIG + bool "Firmware signature verification" + depends on FW_LOADER + select SYSTEM_DATA_VERIFICATION + help + Check firmware files for valid signatures upon load: if the firmware + was called foo.bin, a respective foo.bin.p7s is expected to be + present as the signature in the same directory. + + This configuration only affects drivers with driver_data APIs, + disabling DRIVER_DATA_REQ_NO_SIG_CHECK. + + For more information see Documentation/driver-api/firmware/signing.rst + +config FIRMWARE_SIG_FORCE + bool "Require all firmware to be validly signed" + depends on FIRMWARE_SIG + help + Reject unsigned files or signed files for which we don't have + a trusted key. Without this, you'll only get a record on kernel + log and yet the firmware will be loaded. + + This configuration only affects drivers with driver_data APIs, + disabling DRIVER_DATA_REQ_NO_SIG_CHECK. + config FIRMWARE_IN_KERNEL bool "Include in-kernel firmware blobs in kernel binary" depends on FW_LOADER diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c index e87e91bcd8f8..590a2a834fec 100644 --- a/drivers/base/firmware_class.c +++ b/drivers/base/firmware_class.c @@ -43,6 +43,7 @@ #include #include #include +#include #include @@ -146,6 +147,9 @@ struct driver_data_params { * o request_firmware_nowait(): __DATA_REQ_FIRMWARE_NOWAIT() */ #define __DATA_REQ_FIRMWARE() \ + .req_params = { \ + .reqs = DRIVER_DATA_REQ_NO_SIG_CHECK, \ + }, \ .priv_params = { \ .priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \ DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT, \ @@ -153,7 +157,8 @@ struct driver_data_params { #define __DATA_REQ_FIRMWARE_DIRECT() \ .req_params = { \ - .reqs = DRIVER_DATA_REQ_OPTIONAL, \ + .reqs = DRIVER_DATA_REQ_OPTIONAL | \ + DRIVER_DATA_REQ_NO_SIG_CHECK, \ }, \ .priv_params = { \ .priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \ @@ -161,6 +166,9 @@ struct driver_data_params { } #define __DATA_REQ_FIRMWARE_BUF(buf, size) \ + .req_params = { \ + .reqs = DRIVER_DATA_REQ_NO_SIG_CHECK, \ + }, \ .priv_params = { \ .priv_reqs = DRIVER_DATA_PRIV_REQ_FALLBACK | \ DRIVER_DATA_PRIV_REQ_FALLBACK_UEVENT | \ @@ -177,6 +185,7 @@ struct driver_data_params { .found_cb = NULL, \ .found_ctx = async_ctx, \ }, \ + .reqs = DRIVER_DATA_REQ_NO_SIG_CHECK, \ }, \ .priv_params = { \ .mode = DRIVER_DATA_ASYNC, \ @@ -204,6 +213,8 @@ struct driver_data_params { (!!((params)->reqs & DRIVER_DATA_REQ_KEEP)) #define driver_data_param_uses_api(params) \ (!!((params)->reqs & DRIVER_DATA_REQ_USE_API_VERSIONING)) +#define driver_data_param_no_sig_check(params) \ + (!!((params)->reqs & DRIVER_DATA_REQ_NO_SIG_CHECK)) #define driver_data_sync_cb(param) ((params)->cbs.sync.found_cb) #define driver_data_sync_ctx(params) ((params)->cbs.sync.found_ctx) @@ -263,6 +274,11 @@ int driver_data_async_call_api_cb(const struct firmware *driver_data, error); } +static bool fw_sig_enforce = IS_ENABLED(CONFIG_FIRMWARE_SIG_FORCE); +#ifndef CONFIG_FIRMWARE_SIG_FORCE +module_param(fw_sig_enforce, bool_enable_only, 0644); +#endif /* !CONFIG_FIRMWARE_SIG_FORCE */ + /* Builtin firmware support */ #ifdef CONFIG_FW_LOADER @@ -445,6 +461,11 @@ struct firmware_buf { void *data; size_t size; size_t allocated_size; + bool sig_ok; +#ifdef CONFIG_FIRMWARE_SIG + void *sig_data; + size_t sig_size; +#endif #ifdef CONFIG_FW_LOADER_USER_HELPER bool is_paged_buf; bool need_uevent; @@ -611,6 +632,9 @@ static void __fw_free_buf(struct kref *ref) #endif if (!buf->allocated_size) vfree(buf->data); +#ifdef CONFIG_FIRMWARE_SIG + vfree(buf->sig_data); +#endif kfree_const(buf->fw_id); kfree(buf); } @@ -648,13 +672,16 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf) int i, len; int rc = -ENOENT; char *path; - enum kernel_read_file_id id = READING_FIRMWARE; - size_t msize = INT_MAX; + enum kernel_read_file_id id; + loff_t msize; /* Already populated data member means we're loading into a buffer */ if (buf->data) { id = READING_FIRMWARE_PREALLOC_BUFFER; msize = buf->allocated_size; + } else { + id = READING_FIRMWARE; + msize = LONG_MAX; } path = __getname(); @@ -673,7 +700,7 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf) break; } - buf->size = 0; + /* Load firmware */ rc = kernel_read_file_from_path(path, &buf->data, &size, msize, id); if (rc) { @@ -685,11 +712,38 @@ fw_get_filesystem_firmware(struct device *device, struct firmware_buf *buf) path, rc); continue; } - dev_dbg(device, "direct-loading %s\n", buf->fw_id); buf->size = size; + +#ifdef CONFIG_FIRMWARE_SIG + len = snprintf(path, PATH_MAX, "%s/%s.p7s", + fw_path[i], buf->fw_id); + if (len >= PATH_MAX) { + rc = -ENAMETOOLONG; + break; + } + + /* Load signature */ + rc = kernel_read_file_from_path(path, &buf->sig_data, &size, + LONG_MAX, READING_FIRMWARE); + if (!rc) + buf->sig_size = size; + else if (rc == -ENOENT) + dev_info(device, + "signature of %s doesn't exist\n", buf->fw_id); + else + dev_warn(device, + "loading signature of %s failed (%d)\n", + buf->fw_id, rc); + + /* caller should check existence of signature with sig_size */ + rc = 0; +#endif /* CONFIG_FIRMWARE_SIG */ + + dev_dbg(device, "direct-loading %s\n", buf->fw_id); fw_state_done(&buf->fw_st); break; } + __putname(path); return rc; @@ -716,9 +770,9 @@ static void fw_set_page_data(struct firmware_buf *buf, struct firmware *fw) fw->size = buf->size; fw->data = buf->data; - pr_debug("%s: fw-%s buf=%p data=%p size=%u\n", + pr_debug("%s: fw-%s buf=%p data=%p size=%u sig_ok=%s\n", __func__, buf->fw_id, buf, buf->data, - (unsigned int)buf->size); + (unsigned int)buf->size, buf->sig_ok ? "(yes)" : "(no)"); } #ifdef CONFIG_PM_SLEEP @@ -822,6 +876,25 @@ static int assign_firmware_buf(struct firmware *fw, struct device *device, return 0; } +#ifdef CONFIG_FIRMWARE_SIG +static int firmware_sig_check(struct firmware *fw) +{ + struct firmware_buf *buf = fw->priv; + int err; + + if (!buf->sig_size) + return -ENOENT; + + err = verify_pkcs7_signature(buf->data, buf->size, + buf->sig_data, buf->sig_size, + NULL, VERIFYING_FIRMWARE_SIGNATURE, NULL, NULL); + if (!err) + buf->sig_ok = true; + + return err; +} +#endif /* CONFIG_FIRMWARE_SIG */ + /* * user-mode helper code */ @@ -1224,6 +1297,83 @@ static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, return retval; } +static ssize_t firmware_sig_data_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct firmware_priv *fw_priv = to_firmware_priv(dev); + struct firmware_buf *buf; + ssize_t ret_count; + + mutex_lock(&fw_lock); + buf = fw_priv->buf; + if (!buf || fw_state_is_done(&buf->fw_st)) { + ret_count = -ENODEV; + goto out; + } + if (offset > buf->size) { + ret_count = 0; + goto out; + } + if (count > buf->size - offset) + ret_count = buf->size - offset; + else + ret_count = count; + + memcpy(buffer, buf->data + offset, ret_count); + +out: + mutex_unlock(&fw_lock); + return ret_count; +} + +static ssize_t firmware_sig_data_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buffer, loff_t offset, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct firmware_priv *fw_priv = to_firmware_priv(dev); + struct firmware_buf *buf; + void *buf_tmp; + size_t new_size; + ssize_t ret_count; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + mutex_lock(&fw_lock); + buf = fw_priv->buf; + if (!buf || fw_state_is_done(&buf->fw_st)) { + ret_count = -ENODEV; + goto out; + } + + if (buf->sig_data && (offset + count > buf->sig_size)) { + /* grow a buffer */ + new_size = min(size_t, PAGE_SIZE, offset + count); + buf_tmp = NULL; + if (new_size <= PAGE_SIZE) + buf_tmp = vmalloc(new_size); + if (!buf_tmp) + ret_count = -ENOMEM; + goto out; + } + memcpy(buf_tmp, buf->sig_data, buf->sig_size); + vfree(buf->sig_data); + buf->sig_data = buf_tmp; + buf->sig_size = new_size; + } + + ret_count = min(size_t, buf->sig_size - offset, count); + memcpy(buf->data + offset, buffer, ret_count); + +out: + mutex_unlock(&fw_lock); + return ret_count; +} + static struct bin_attribute firmware_attr_data = { .attr = { .name = "data", .mode = 0644 }, .size = 0, @@ -1231,6 +1381,13 @@ static struct bin_attribute firmware_attr_data = { .write = firmware_data_write, }; +static struct bin_attribute firmware_attr_sig_data = { + .attr = { .name = "sig_data", .mode = 0644 }, + .size = 0, + .read = firmware_sig_data_read, + .write = firmware_sig_data_write, +}; + static struct attribute *fw_dev_attrs[] = { &dev_attr_loading.attr, NULL @@ -1238,6 +1395,7 @@ static struct attribute *fw_dev_attrs[] = { static struct bin_attribute *fw_dev_bin_attrs[] = { &firmware_attr_data, + &firmware_attr_sig_data, NULL }; @@ -1364,9 +1522,6 @@ static int fw_load_from_user_helper(struct firmware *firmware, fw_priv->buf = firmware->priv; ret = _request_firmware_load(fw_priv, data_params, timeout); - if (!ret) - ret = assign_firmware_buf(firmware, device, data_params); - out_unlock: usermodehelper_read_unlock(); @@ -1490,11 +1645,43 @@ _request_firmware(const struct firmware **firmware_p, const char *name, "Direct firmware load for %s failed with error %d\n", name, ret); ret = driver_data_fallback(fw, name, device, data_params, ret); - } else - ret = assign_firmware_buf(fw, device, data_params); + } + if (ret) + goto out; + +#ifdef CONFIG_FIRMWARE_SIG + if (driver_data_param_no_sig_check(&data_params->req_params)) + goto skip_sig_check; + + /* Verify the data with its signature */ + ret = firmware_sig_check(fw); + if (ret) { + if (fw_sig_enforce) { + /* ENFORCING */ + dev_err(device, + "firmware [%s] verification failed (%d).\n", + name, ret); + + goto out; + } + + /* PERMISSIVE */ + dev_warn(device, + "firmware [%s] verification failed (%d), but this is ok.\n", + name, ret); + } else { + dev_info(device, "firmware [%s] verification succeeded.\n", + name); + } + + skip_sig_check: +#endif /* CONFIG_FIRMWARE_SIG */ + ret = assign_firmware_buf(fw, device, data_params); out: if (ret < 0) { + dev_warn(device, "Loading firmware failed (%d)\n", ret); + release_firmware(fw); fw = NULL; } diff --git a/include/linux/driver_data.h b/include/linux/driver_data.h index cfeaacfd74d3..039b77b82a91 100644 --- a/include/linux/driver_data.h +++ b/include/linux/driver_data.h @@ -123,11 +123,16 @@ union driver_data_cbs { * file to be present given the API range, it is only required for one * file in the API range to be present. If the %DRIVER_DATA_REQ_OPTIONAL * flag is also enabled then all files are treated as optional. + * @DRIVER_DATA_REQ_NO_SIG_CHECK: indicates that signature verification + * for this driver data is not required for any reason. + * So even if CONFIG_FIRMWARE_SIG_ENFORCE is enabled, the data will be + * successfully loaded if it does exist. */ enum driver_data_reqs { DRIVER_DATA_REQ_OPTIONAL = 1 << 0, DRIVER_DATA_REQ_KEEP = 1 << 1, DRIVER_DATA_REQ_USE_API_VERSIONING = 1 << 2, + DRIVER_DATA_REQ_NO_SIG_CHECK = 1 << 3, }; /**