diff options
| author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2026-10-09 20:54:28 -0700 |
|---|---|---|
| committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2026-10-14 10:25:00 -0700 |
| commit | 7f0b4c25fdab38f1c2dd11f8ec17db3f48891192 (patch) | |
| tree | d533846349c655d5459aa8c03ffdb038822237af /drivers | |
| parent | 2b4bcdd40ca00695ed21980f3bc7860823d1423e (diff) | |
Input: reject parent overrides on managed input devices
When an input device is allocated with devm_input_allocate_device(), the
owner struct device is set as dev->dev.parent and holds the
devm_input_device_release resource. Later, input_register_device()
attaches the devm_input_device_unregister resource to dev->dev.parent.
Clearing or overriding dev->dev.parent before registration splits devres
ownership across different devices, which breaks managed lifetime
assumptions.
Verify via devres_find() that dev->dev.parent is non-NULL and still
holds the matching devm_input_device_release entry before registering a
managed input device, rejecting registration with -EINVAL otherwise.
Also add KUnit test coverage in input_test.c verifying that reparented
and NULL-parent managed input devices are rejected while preserving the
owner parent succeeds.
Assisted-by: LLM
Link: https://patch.msgid.link/asmCc1VxVPLIMgY6@google.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/input/input.c | 8 | ||||
| -rw-r--r-- | drivers/input/tests/input_test.c | 34 |
2 files changed, 42 insertions, 0 deletions
diff --git a/drivers/input/input.c b/drivers/input/input.c index 78c10eea7328..1cca6d48f597 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -2396,6 +2396,14 @@ int input_register_device(struct input_dev *dev) } if (dev->devres_managed) { + if (!dev->dev.parent || + !devres_find(dev->dev.parent, devm_input_device_release, + devm_input_device_match, dev)) { + dev_err(&dev->dev, + "Parent of managed input device was overridden, refusing to register\n"); + return -EINVAL; + } + devres = devres_alloc(devm_input_device_unregister, sizeof(*devres), GFP_KERNEL); if (!devres) diff --git a/drivers/input/tests/input_test.c b/drivers/input/tests/input_test.c index e105ce71a920..b8c8f04c041b 100644 --- a/drivers/input/tests/input_test.c +++ b/drivers/input/tests/input_test.c @@ -8,6 +8,7 @@ #include <linux/delay.h> #include <linux/input.h> +#include <kunit/device.h> #include <kunit/test.h> #define POLL_INTERVAL 100 @@ -161,11 +162,44 @@ static void input_test_grab(struct kunit *test) input_put_device(input_dev); } +static void input_test_managed_parent_override(struct kunit *test) +{ + struct device *owner, *other_dev; + struct input_dev *input_dev; + + owner = kunit_device_register(test, "input-test-owner"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, owner); + + other_dev = kunit_device_register(test, "input-test-other"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, other_dev); + + /* Overriding dev.parent to another device must be rejected */ + input_dev = devm_input_allocate_device(owner); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, input_dev); + input_dev->name = "Managed input device (reparented)"; + input_dev->dev.parent = other_dev; + KUNIT_EXPECT_EQ(test, input_register_device(input_dev), -EINVAL); + + /* Clearing dev.parent to NULL must be rejected */ + input_dev = devm_input_allocate_device(owner); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, input_dev); + input_dev->name = "Managed input device (null parent)"; + input_dev->dev.parent = NULL; + KUNIT_EXPECT_EQ(test, input_register_device(input_dev), -EINVAL); + + /* Preserving original owner as dev.parent succeeds */ + input_dev = devm_input_allocate_device(owner); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, input_dev); + input_dev->name = "Managed input device (valid parent)"; + KUNIT_EXPECT_EQ(test, input_register_device(input_dev), 0); +} + static struct kunit_case input_tests[] = { KUNIT_CASE(input_test_polling), KUNIT_CASE(input_test_timestamp), KUNIT_CASE(input_test_match_device_id), KUNIT_CASE(input_test_grab), + KUNIT_CASE(input_test_managed_parent_override), { /* sentinel */ } }; |
