From patchwork Tue Jan 3 23:07:20 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kees Cook X-Patchwork-Id: 9495891 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id EAECE60405 for ; Tue, 3 Jan 2017 23:07:38 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DE37226E82 for ; Tue, 3 Jan 2017 23:07:38 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D2121271CB; Tue, 3 Jan 2017 23:07:38 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.1 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from mother.openwall.net (mother.openwall.net [195.42.179.200]) by mail.wl.linuxfoundation.org (Postfix) with SMTP id 96D7F27165 for ; Tue, 3 Jan 2017 23:07:37 +0000 (UTC) Received: (qmail 5212 invoked by uid 550); 3 Jan 2017 23:07:35 -0000 Mailing-List: contact kernel-hardening-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: kernel-hardening@lists.openwall.com Delivered-To: mailing list kernel-hardening@lists.openwall.com Received: (qmail 5173 invoked from network); 3 Jan 2017 23:07:33 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=date:from:to:cc:subject:message-id:mime-version:content-disposition; bh=sqLD85xqWlcYN3mZoBY1Fzxw1DqP+qHp4S+cxiD53ns=; b=eJt7JTUonNoOZkD/BUR9hqKtDSDL7wVWXtOQIgdc2bISAjSjz8CCNm5snki6OExnUn j1ha+fleDJqmV6HCvk7X8Uop9qmxvzsdYgb35PLPlmgmn0VzsZhXDLffIoB4fDeTuJtk HrFb/ufZ26kxKwlSjlWRwcQiVijyVqmy1o8+k= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:mime-version :content-disposition; bh=sqLD85xqWlcYN3mZoBY1Fzxw1DqP+qHp4S+cxiD53ns=; b=T93cxDa0xgFovsfLuDvPBd5BlTyCYug3i0JlKtV5vgBTCsFzoqfnzLSCuy4Wj4oLC7 zdvcK5La8LWW0YnA/KXU5fpkXe0CBCExbguz2b2SF3ZAv/3Tn9yfZyeq4Jp1Zuf45OU0 6mu+W8MNb9TGsXXrcP67vKwoWvH9D//e8Zi0D9khD3xaROfoF9vSMVFFdCbCcfehL0Hs xEzmqAVgnXmhUwUzH0VdRCzipvybmFPmCd75wSqqfle/NGu6CPURkVibSv11EMkF8B8R XLn7Gd8fU5Kv6IW1Y/GnN47IU6tDz0cdlcMp45Hi0vPthZzGQE+pr0ps4M4Ew3IHTFUC yJEg== X-Gm-Message-State: AIkVDXJ9dLW+Vov4mj7UeaPKhykXQlETDtnvPJeFWw8f0ejG8YPVSiCAoB03SFiTJXsSyNjR X-Received: by 10.84.218.204 with SMTP id g12mr137184324plm.78.1483484842070; Tue, 03 Jan 2017 15:07:22 -0800 (PST) Date: Tue, 3 Jan 2017 15:07:20 -0800 From: Kees Cook To: linux-kernel@vger.kernel.org Cc: Greg Kroah-Hartman , Matthew Garrett , kernel-hardening@lists.openwall.com Message-ID: <20170103230720.GA115084@beast> MIME-Version: 1.0 Content-Disposition: inline Subject: [kernel-hardening] [PATCH] Allow userspace to request device probing even if defer_all_probes is true X-Virus-Scanned: ClamAV using ClamSMTP From: Matthew Garrett Userspace may wish to make a policy decision to allow certain devices to be attached, such as keyboards. Add a force_probe sysfs node to each device, which if written will trigger a probe even if defer_all_probes is currently true. Signed-off-by: Matthew Garrett Signed-off-by: Kees Cook --- .../ABI/testing/sysfs-devices-force_probe | 10 +++++ drivers/base/base.h | 4 +- drivers/base/bus.c | 2 +- drivers/base/core.c | 7 ++- drivers/base/dd.c | 51 ++++++++++++++++++---- 5 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-devices-force_probe diff --git a/Documentation/ABI/testing/sysfs-devices-force_probe b/Documentation/ABI/testing/sysfs-devices-force_probe new file mode 100644 index 000000000000..3a69b9e3b86b --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-force_probe @@ -0,0 +1,10 @@ +What: /sys/devices/.../force_probe +Date: December 2016 +KernelVersion: 4.11 +Contact: Matthew Garrett +Description: + The /sys/devices/.../force_probe attribute is + present for all devices. If deferred probing is globally + enabled and the device has no driver bound, a write to this + node will trigger probing. This attribute reads as 1 if the + device currently has a driver bound, and 0 otherwise. diff --git a/drivers/base/base.h b/drivers/base/base.h index 7bee2e4e38ce..787ab5b9a16f 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -112,7 +112,8 @@ extern void device_release_driver_internal(struct device *dev, struct device *parent); extern void driver_detach(struct device_driver *drv); -extern int driver_probe_device(struct device_driver *drv, struct device *dev); +extern int driver_probe_device(struct device_driver *drv, struct device *dev, + bool force); extern void driver_deferred_probe_del(struct device *dev); static inline int driver_match_device(struct device_driver *drv, struct device *dev) @@ -140,6 +141,7 @@ extern struct kset *devices_kset; extern void devices_kset_move_last(struct device *dev); extern struct device_attribute dev_attr_deferred_probe; +extern struct device_attribute dev_attr_force_probe; #if defined(CONFIG_MODULES) && defined(CONFIG_SYSFS) extern void module_add_driver(struct module *mod, struct device_driver *drv); diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 6470eb8088f4..0d4a771abdd9 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -216,7 +216,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf, if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); - err = driver_probe_device(drv, dev); + err = driver_probe_device(drv, dev, true); device_unlock(dev); if (dev->parent) device_unlock(dev->parent); diff --git a/drivers/base/core.c b/drivers/base/core.c index 020ea7f05520..0c6469c57de6 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1064,8 +1064,13 @@ static int device_add_attrs(struct device *dev) if (error) goto err_remove_online; - return 0; + error = device_create_file(dev, &dev_attr_force_probe); + if (error) + goto err_remove_deferred_probe; + return 0; + err_remove_deferred_probe: + device_remove_file(dev, &dev_attr_deferred_probe); err_remove_online: device_remove_file(dev, &dev_attr_online); err_remove_dev_groups: diff --git a/drivers/base/dd.c b/drivers/base/dd.c index 4d70fa41132c..8270348b9dc7 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -344,14 +344,15 @@ EXPORT_SYMBOL_GPL(device_bind_driver); static atomic_t probe_count = ATOMIC_INIT(0); static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue); -static int really_probe(struct device *dev, struct device_driver *drv) +static int really_probe(struct device *dev, struct device_driver *drv, + bool force) { int ret = -EPROBE_DEFER; int local_trigger_count = atomic_read(&deferred_trigger_count); bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) && !drv->suppress_bind_attrs; - if (defer_all_probes) { + if (defer_all_probes && !force) { /* * Value of defer_all_probes can be set only by * device_defer_all_probes_enable() which, in turn, will call @@ -527,7 +528,8 @@ EXPORT_SYMBOL_GPL(wait_for_device_probe); * * If the device has a parent, runtime-resume the parent before driver probing. */ -int driver_probe_device(struct device_driver *drv, struct device *dev) +int driver_probe_device(struct device_driver *drv, struct device *dev, + bool force) { int ret = 0; @@ -542,7 +544,7 @@ int driver_probe_device(struct device_driver *drv, struct device *dev) pm_runtime_get_sync(dev->parent); pm_runtime_barrier(dev); - ret = really_probe(dev, drv); + ret = really_probe(dev, drv, force); pm_request_idle(dev); if (dev->parent) @@ -600,6 +602,12 @@ struct device_attach_data { * driver, we'll encounter one that requests asynchronous probing. */ bool have_async; + + /* + * Indicate whether probing should be forced even if defer_all_probes + * is set + */ + bool force; }; static int __device_attach_driver(struct device_driver *drv, void *_data) @@ -638,7 +646,7 @@ static int __device_attach_driver(struct device_driver *drv, void *_data) if (data->check_async && async_allowed != data->want_async) return 0; - return driver_probe_device(drv, dev); + return driver_probe_device(drv, dev, data->force); } static void __device_attach_async_helper(void *_dev, async_cookie_t cookie) @@ -648,6 +656,7 @@ static void __device_attach_async_helper(void *_dev, async_cookie_t cookie) .dev = dev, .check_async = true, .want_async = true, + .force = false, }; device_lock(dev); @@ -668,7 +677,7 @@ static void __device_attach_async_helper(void *_dev, async_cookie_t cookie) put_device(dev); } -static int __device_attach(struct device *dev, bool allow_async) +static int __device_attach(struct device *dev, bool allow_async, bool force) { int ret = 0; @@ -690,6 +699,7 @@ static int __device_attach(struct device *dev, bool allow_async) .dev = dev, .check_async = allow_async, .want_async = false, + .force = force, }; if (dev->parent) @@ -736,13 +746,36 @@ static int __device_attach(struct device *dev, bool allow_async) */ int device_attach(struct device *dev) { - return __device_attach(dev, false); + return __device_attach(dev, false, false); } EXPORT_SYMBOL_GPL(device_attach); +/* + * Allow userspace to trigger the probing of a device even if driver probing + * is currently forcibly deferred + */ +static ssize_t force_probe_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = __device_attach(dev, false, true); + if (ret < 0) + return ret; + return count; +} + +static ssize_t force_probe_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", device_is_bound(dev)); +} +DEVICE_ATTR_RW(force_probe); + void device_initial_probe(struct device *dev) { - __device_attach(dev, true); + __device_attach(dev, true, false); } static int __driver_attach(struct device *dev, void *data) @@ -776,7 +809,7 @@ static int __driver_attach(struct device *dev, void *data) device_lock(dev->parent); device_lock(dev); if (!dev->driver) - driver_probe_device(drv, dev); + driver_probe_device(drv, dev, false); device_unlock(dev); if (dev->parent) device_unlock(dev->parent);