Re: [PATCH v2 2/2] driver core: add TAINT_FORCED_BIND for when userspace manually messes with devices and drivers

From: Aaron Tomlin

Date: Mon Aug 31 2026 - 07:56:29 EST


On Mon, Aug 31, 2026 at 12:51:47PM +0200, Greg Kroah-Hartman wrote:
> The ability to add and remove devices from a driver through the sysfs
> "bind" and "unbind" files was created all those decades ago as a way
> that kernel developers can iterate faster, and provide a debugging way
> for users to attempt to add a new device to a driver without having to
> rebuild their kernel.
>
> This api over the years has been abused and recently come under a major
> fuzzing "attack" through tools like syzbot which decided that it would
> attempt to just randomly bind any driver to any type of device, causing
> loads of unneeded errors and pointless kernel patches to be generated by
> unsuspecting new developers.
>
> Handle all of this by adding a new taint flag, TAINT_FORCED_BIND, which
> will be set on the driver if the bind/unbind sysfs files are ever
> written to. This lets kernel developers "know" that a user is
> attempting to do something that is not normal, and as such, if the
> kernel breaks they get to keep the shiny pieces laying around on the
> floor.
>
> The flag is 'Y' which was unused, and can remembered as the user is
> "yeeting" the device being operated on here (thrown with force without
> regard for the thing being thrown).
>
> Note, the taint flag gets set _BEFORE_ the bind/unbind callback happens,
> as many times crashes/oops/warnings/failures happen within the callback,
> and the taint flag needs to be there to show what was being attempted.
> If it were to be set after the callback happens, the oops report would
> not properly reflect what foolishness was being attempted.
>
> Fuzzing tools like syzbot, that doesn't have hand-crafted rules to keep
> the tool from hitting bind/unbind, should be run with panic_on_taint
> enabled so that they fall over and don't continue on, thinking that they
> actually found a real issue.
>
> Userspace operations that rely on the bind/unbind files to work around
> the lack of will to upgrade a kernel image to a newer version with
> proper support for new devices, or the lack of will to submit valid
> device ids to driver authors, will still work properly, but now the
> kernel will be flagged in a way that will show that perhaps those users
> should reconsider their behavior and work to have the drivers properly
> support these devices in a "native" manner.
>
> Finally, the bind/unbind files can find real use-after-free issues with
> some drivers by forcing the process to happen virtually without having
> to rely on manual removal processes. Those real bugs should still be
> worked on, but by adding this taint flag, developers can more easily
> determine bug reports that are actually worth looking at.
>
> Reviewed-by: Johan Hovold <johan@xxxxxxxxxx>
> Tested-by: Johan Hovold <johan@xxxxxxxxxx>
> Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> ---
> Documentation/admin-guide/tainted-kernels.rst | 52 ++++++++++++++-------------
> drivers/base/bus.c | 3 ++
> include/linux/panic.h | 3 +-
> include/trace/events/module.h | 3 +-
> kernel/panic.c | 5 +--
> tools/debugging/kernel-chktaint | 8 +++++
> 6 files changed, 46 insertions(+), 28 deletions(-)
>
> diff --git a/Documentation/admin-guide/tainted-kernels.rst b/Documentation/admin-guide/tainted-kernels.rst
> index 9ead927a37c0..abbf5e3dd749 100644
> --- a/Documentation/admin-guide/tainted-kernels.rst
> +++ b/Documentation/admin-guide/tainted-kernels.rst
> @@ -79,30 +79,31 @@ which bits are set::
> Table for decoding tainted state
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> -=== === ====== ========================================================
> -Bit Log Number Reason that got the kernel tainted
> -=== === ====== ========================================================
> - 0 G/P 1 proprietary module was loaded
> - 1 _/F 2 module was force loaded
> - 2 _/S 4 kernel running on an out of specification system
> - 3 _/R 8 module was force unloaded
> - 4 _/M 16 processor reported a Machine Check Exception (MCE)
> - 5 _/B 32 bad page referenced or some unexpected page flags
> - 6 _/U 64 taint requested by userspace application
> - 7 _/D 128 kernel died recently, i.e. there was an OOPS or BUG
> - 8 _/A 256 ACPI table overridden by user
> - 9 _/W 512 kernel issued warning
> - 10 _/C 1024 staging driver was loaded
> - 11 _/I 2048 workaround for bug in platform firmware applied
> - 12 _/O 4096 externally-built ("out-of-tree") module was loaded
> - 13 _/E 8192 unsigned module was loaded
> - 14 _/L 16384 soft lockup occurred
> - 15 _/K 32768 kernel has been live patched
> - 16 _/X 65536 auxiliary taint, defined for and used by distros
> - 17 _/T 131072 kernel was built with the struct randomization plugin
> - 18 _/N 262144 an in-kernel test has been run
> - 19 _/J 524288 userspace used a mutating debug operation in fwctl
> -=== === ====== ========================================================
> +=== === ======= ========================================================
> +Bit Log Number Reason that got the kernel tainted
> +=== === ======= ========================================================
> + 0 G/P 1 proprietary module was loaded
> + 1 _/F 2 module was force loaded
> + 2 _/S 4 kernel running on an out of specification system
> + 3 _/R 8 module was force unloaded
> + 4 _/M 16 processor reported a Machine Check Exception (MCE)
> + 5 _/B 32 bad page referenced or some unexpected page flags
> + 6 _/U 64 taint requested by userspace application
> + 7 _/D 128 kernel died recently, i.e. there was an OOPS or BUG
> + 8 _/A 256 ACPI table overridden by user
> + 9 _/W 512 kernel issued warning
> + 10 _/C 1024 staging driver was loaded
> + 11 _/I 2048 workaround for bug in platform firmware applied
> + 12 _/O 4096 externally-built ("out-of-tree") module was loaded
> + 13 _/E 8192 unsigned module was loaded
> + 14 _/L 16384 soft lockup occurred
> + 15 _/K 32768 kernel has been live patched
> + 16 _/X 65536 auxiliary taint, defined for and used by distros
> + 17 _/T 131072 kernel was built with the struct randomization plugin
> + 18 _/N 262144 an in-kernel test has been run
> + 19 _/J 524288 userspace used a mutating debug operation in fwctl
> + 20 _/Y 1048576 device was manually bound or unbound from a driver
> +=== === ======= ========================================================
>
> Note: The character ``_`` is representing a blank in this table to make reading
> easier.
> @@ -189,3 +190,6 @@ More detailed explanation for tainting
> 19) ``J`` if userspace opened /dev/fwctl/* and performed a FWTCL_RPC_DEBUG_WRITE
> to use the devices debugging features. Device debugging features could
> cause the device to malfunction in undefined ways.
> +
> + 20) ``Y`` If userspace wrote to the `bind` or `unbind` sysfs files and
> + successfully bound or removed a device from a driver.
> diff --git a/drivers/base/bus.c b/drivers/base/bus.c
> index d17bd91490ee..c51ad96d4de4 100644
> --- a/drivers/base/bus.c
> +++ b/drivers/base/bus.c
> @@ -19,6 +19,7 @@
> #include <linux/string.h>
> #include <linux/mutex.h>
> #include <linux/sysfs.h>
> +#include <linux/panic.h>
> #include "base.h"
> #include "power/power.h"
>
> @@ -241,6 +242,7 @@ static ssize_t unbind_store(struct device_driver *drv, const char *buf,
>
> dev = bus_find_device_by_name(bus, NULL, buf);
> if (dev && dev->driver == drv) {
> + add_taint_module(drv->owner, TAINT_FORCED_BIND, LOCKDEP_STILL_OK);
> device_driver_detach(dev);
> err = count;
> }
> @@ -264,6 +266,7 @@ static ssize_t bind_store(struct device_driver *drv, const char *buf,
>
> dev = bus_find_device_by_name(bus, NULL, buf);
> if (dev && driver_match_device(drv, dev)) {
> + add_taint_module(drv->owner, TAINT_FORCED_BIND, LOCKDEP_STILL_OK);
> err = device_driver_attach(drv, dev);
> if (!err) {
> /* success */
> diff --git a/include/linux/panic.h b/include/linux/panic.h
> index f1dd417e54b2..8e7250b0e913 100644
> --- a/include/linux/panic.h
> +++ b/include/linux/panic.h
> @@ -88,7 +88,8 @@ static inline void set_arch_panic_timeout(int timeout, int arch_default_timeout)
> #define TAINT_RANDSTRUCT 17
> #define TAINT_TEST 18
> #define TAINT_FWCTL 19
> -#define TAINT_FLAGS_COUNT 20
> +#define TAINT_FORCED_BIND 20
> +#define TAINT_FLAGS_COUNT 21
> #define TAINT_FLAGS_MAX ((1UL << TAINT_FLAGS_COUNT) - 1)
>
> struct taint_flag {
> diff --git a/include/trace/events/module.h b/include/trace/events/module.h
> index e5a006be9dc6..19df3e39bba4 100644
> --- a/include/trace/events/module.h
> +++ b/include/trace/events/module.h
> @@ -26,7 +26,8 @@ struct module;
> { (1UL << TAINT_OOT_MODULE), "O" }, \
> { (1UL << TAINT_FORCED_MODULE), "F" }, \
> { (1UL << TAINT_CRAP), "C" }, \
> - { (1UL << TAINT_UNSIGNED_MODULE), "E" })
> + { (1UL << TAINT_UNSIGNED_MODULE), "E" }, \
> + { (1UL << TAINT_FORCED_BIND), "Y" })
>
> TRACE_EVENT(module_load,
>
> diff --git a/kernel/panic.c b/kernel/panic.c
> index 213725b612aa..6bf60f9dd120 100644
> --- a/kernel/panic.c
> +++ b/kernel/panic.c
> @@ -826,6 +826,7 @@ const struct taint_flag taint_flags[TAINT_FLAGS_COUNT] = {
> TAINT_FLAG(RANDSTRUCT, 'T', ' '),
> TAINT_FLAG(TEST, 'N', ' '),
> TAINT_FLAG(FWCTL, 'J', ' '),
> + TAINT_FLAG(FORCED_BIND, 'Y', ' '),
> };
>
> #undef TAINT_FLAG
> @@ -862,9 +863,9 @@ static void print_tainted_seq(struct seq_buf *s, bool verbose)
> * exact size is allocated dynamically; the initial buffer remains
> * as a fallback if allocation fails.
> *
> - * The verbose taint string currently requires up to 327 characters.
> + * The verbose taint string currently requires up to 344 characters.
> */
> -#define INIT_TAINT_BUF_MAX 350
> +#define INIT_TAINT_BUF_MAX 370
>
> static char init_taint_buf[INIT_TAINT_BUF_MAX] __initdata;
> static char *taint_buf __refdata = init_taint_buf;
> diff --git a/tools/debugging/kernel-chktaint b/tools/debugging/kernel-chktaint
> index e1571c04afb5..d8628be37214 100755
> --- a/tools/debugging/kernel-chktaint
> +++ b/tools/debugging/kernel-chktaint
> @@ -211,6 +211,14 @@ else
> addout "J"
> echo " * fwctl's mutating debug interface was used (#19)"
> fi
> +
> +T=`expr $T / 2`
> +if [ `expr $T % 2` -eq 0 ]; then
> + addout " "
> +else
> + addout "Y"
> + echo " * device was manually bound or unbound from a driver (#20)"
> +fi
> echo "Raw taint value as int/string: $taint/'$out'"
>
> # report on any tainted loadable modules
>
> --
> 2.55.0
>

Hi Greg,

After you replace the accidental comma with a space in the parameter list
of add_taint_module() in module.h under !CONFIG_MODULES:

Reviewed-by: Aaron Tomlin <atomlin@xxxxxxxxxxx>

Kind regards,
--
Aaron Tomlin