Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

revocable: Fix races in revocable_alloc() using RCU

There are two race conditions when allocating a revocable instance:

1. After a struct revocable_provider is revoked, the caller might still
hold a dangling pointer to it. A subsequent call to
revocable_alloc() can trigger a use-after-free.
2. If revocable_provider_release() runs concurrently with
revocable_alloc(), the memory of struct revocable_provider can be
accessed during or after kfree().

To fix these:
- Manage the lifetime of struct revocable_provider using RCU. Annotate
pointers to it with __rcu and use kfree_rcu() for deallocation.
- Update revocable_alloc() to safely acquire a reference using RCU
primitives.
- Update revocable_provider_revoke() to take a double pointer (`**rp`).
It atomically NULLs out the caller's pointer before starting
revocation. This prevents the caller from holding a dangling pointer.
- Drop devm_revocable_provider_alloc(). The devm-managed model cannot
support the required double-pointer semantic for safe pointer nulling.

Reported-by: Johan Hovold <johan@kernel.org>
Closes: https://lore.kernel.org/all/aXdy-b3GOJkzGqYo@hovoldconsulting.com/
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
Link: https://patch.msgid.link/20260129143733.45618-2-tzungbi@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Tzung-Bi Shih and committed by
Greg Kroah-Hartman
4d7dc4d1 289b1459

+74 -73
-3
Documentation/driver-api/driver-model/revocable.rst
··· 77 77 :identifiers: revocable_provider_alloc 78 78 79 79 .. kernel-doc:: drivers/base/revocable.c 80 - :identifiers: devm_revocable_provider_alloc 81 - 82 - .. kernel-doc:: drivers/base/revocable.c 83 80 :identifiers: revocable_provider_revoke 84 81 85 82 For Resource Consumers
+49 -48
drivers/base/revocable.c
··· 64 64 * @srcu: The SRCU to protect the resource. 65 65 * @res: The pointer of resource. It can point to anything. 66 66 * @kref: The refcount for this handle. 67 + * @rcu: The RCU to protect pointer to itself. 67 68 */ 68 69 struct revocable_provider { 69 70 struct srcu_struct srcu; 70 71 void __rcu *res; 71 72 struct kref kref; 73 + struct rcu_head rcu; 72 74 }; 73 75 74 76 /** ··· 90 88 * This holds an initial refcount to the struct. 91 89 * 92 90 * Return: The pointer of struct revocable_provider. NULL on errors. 91 + * It enforces the caller handles the returned pointer in RCU ways. 93 92 */ 94 - struct revocable_provider *revocable_provider_alloc(void *res) 93 + struct revocable_provider __rcu *revocable_provider_alloc(void *res) 95 94 { 96 95 struct revocable_provider *rp; 97 96 ··· 101 98 return NULL; 102 99 103 100 init_srcu_struct(&rp->srcu); 104 - rcu_assign_pointer(rp->res, res); 101 + RCU_INIT_POINTER(rp->res, res); 105 102 kref_init(&rp->kref); 106 103 107 - return rp; 104 + return (struct revocable_provider __rcu *)rp; 108 105 } 109 106 EXPORT_SYMBOL_GPL(revocable_provider_alloc); 110 107 ··· 114 111 struct revocable_provider, kref); 115 112 116 113 cleanup_srcu_struct(&rp->srcu); 117 - kfree(rp); 114 + kfree_rcu(rp, rcu); 118 115 } 119 116 120 117 /** 121 118 * revocable_provider_revoke() - Revoke the managed resource. 122 - * @rp: The pointer of resource provider. 119 + * @rp_ptr: The pointer of pointer of resource provider. 123 120 * 124 121 * This sets the resource `(struct revocable_provider *)->res` to NULL to 125 122 * indicate the resource has gone. 126 123 * 127 124 * This drops the refcount to the resource provider. If it is the final 128 125 * reference, revocable_provider_release() will be called to free the struct. 126 + * 127 + * It enforces the caller to pass a pointer of pointer of resource provider so 128 + * that it sets \*rp_ptr to NULL to prevent from keeping a dangling pointer. 129 129 */ 130 - void revocable_provider_revoke(struct revocable_provider *rp) 130 + void revocable_provider_revoke(struct revocable_provider __rcu **rp_ptr) 131 131 { 132 + struct revocable_provider *rp; 133 + 134 + rp = rcu_replace_pointer(*rp_ptr, NULL, 1); 135 + if (!rp) 136 + return; 137 + 132 138 rcu_assign_pointer(rp->res, NULL); 133 139 synchronize_srcu(&rp->srcu); 134 140 kref_put(&rp->kref, revocable_provider_release); 135 141 } 136 142 EXPORT_SYMBOL_GPL(revocable_provider_revoke); 137 143 138 - static void devm_revocable_provider_revoke(void *data) 139 - { 140 - struct revocable_provider *rp = data; 141 - 142 - revocable_provider_revoke(rp); 143 - } 144 - 145 - /** 146 - * devm_revocable_provider_alloc() - Dev-managed revocable_provider_alloc(). 147 - * @dev: The device. 148 - * @res: The pointer of resource. 149 - * 150 - * It is convenient to allocate providers via this function if the @res is 151 - * also tied to the lifetime of the @dev. revocable_provider_revoke() will 152 - * be called automatically when the device is unbound. 153 - * 154 - * This holds an initial refcount to the struct. 155 - * 156 - * Return: The pointer of struct revocable_provider. NULL on errors. 157 - */ 158 - struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, 159 - void *res) 160 - { 161 - struct revocable_provider *rp; 162 - 163 - rp = revocable_provider_alloc(res); 164 - if (!rp) 165 - return NULL; 166 - 167 - if (devm_add_action_or_reset(dev, devm_revocable_provider_revoke, rp)) 168 - return NULL; 169 - 170 - return rp; 171 - } 172 - EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc); 173 - 174 144 /** 175 145 * revocable_alloc() - Allocate struct revocable. 176 - * @rp: The pointer of resource provider. 146 + * @_rp: The pointer of resource provider. 177 147 * 178 148 * This holds a refcount to the resource provider. 179 149 * 180 150 * Return: The pointer of struct revocable. NULL on errors. 181 151 */ 182 - struct revocable *revocable_alloc(struct revocable_provider *rp) 152 + struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp) 183 153 { 154 + struct revocable_provider *rp; 184 155 struct revocable *rev; 185 156 186 - rev = kzalloc(sizeof(*rev), GFP_KERNEL); 187 - if (!rev) 157 + if (!_rp) 188 158 return NULL; 189 159 190 - rev->rp = rp; 191 - kref_get(&rp->kref); 160 + /* 161 + * Enter a read-side critical section. 162 + * 163 + * This prevents kfree_rcu() from freeing the struct revocable_provider 164 + * memory, for the duration of this scope. 165 + */ 166 + scoped_guard(rcu) { 167 + rp = rcu_dereference(_rp); 168 + if (!rp) 169 + /* The revocable provider has been revoked. */ 170 + return NULL; 192 171 172 + if (!kref_get_unless_zero(&rp->kref)) 173 + /* 174 + * The revocable provider is releasing (i.e., 175 + * revocable_provider_release() has been called). 176 + */ 177 + return NULL; 178 + } 179 + /* At this point, `rp` is safe to access as holding a kref of it */ 180 + 181 + rev = kzalloc(sizeof(*rev), GFP_KERNEL); 182 + if (!rev) { 183 + kref_put(&rp->kref, revocable_provider_release); 184 + return NULL; 185 + } 186 + 187 + rev->rp = rp; 193 188 return rev; 194 189 } 195 190 EXPORT_SYMBOL_GPL(revocable_alloc);
+12 -8
drivers/base/revocable_test.c
··· 21 21 22 22 static void revocable_test_basic(struct kunit *test) 23 23 { 24 - struct revocable_provider *rp; 24 + struct revocable_provider __rcu *rp; 25 25 struct revocable *rev; 26 26 void *real_res = (void *)0x12345678, *res; 27 27 ··· 36 36 revocable_withdraw_access(rev); 37 37 38 38 revocable_free(rev); 39 - revocable_provider_revoke(rp); 39 + revocable_provider_revoke(&rp); 40 + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); 40 41 } 41 42 42 43 static void revocable_test_revocation(struct kunit *test) 43 44 { 44 - struct revocable_provider *rp; 45 + struct revocable_provider __rcu *rp; 45 46 struct revocable *rev; 46 47 void *real_res = (void *)0x12345678, *res; 47 48 ··· 56 55 KUNIT_EXPECT_PTR_EQ(test, res, real_res); 57 56 revocable_withdraw_access(rev); 58 57 59 - revocable_provider_revoke(rp); 58 + revocable_provider_revoke(&rp); 59 + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); 60 60 61 61 res = revocable_try_access(rev); 62 62 KUNIT_EXPECT_PTR_EQ(test, res, NULL); ··· 68 66 69 67 static void revocable_test_try_access_macro(struct kunit *test) 70 68 { 71 - struct revocable_provider *rp; 69 + struct revocable_provider __rcu *rp; 72 70 struct revocable *rev; 73 71 void *real_res = (void *)0x12345678, *res; 74 72 ··· 83 81 KUNIT_EXPECT_PTR_EQ(test, res, real_res); 84 82 } 85 83 86 - revocable_provider_revoke(rp); 84 + revocable_provider_revoke(&rp); 85 + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); 87 86 88 87 { 89 88 REVOCABLE_TRY_ACCESS_WITH(rev, res); ··· 96 93 97 94 static void revocable_test_try_access_macro2(struct kunit *test) 98 95 { 99 - struct revocable_provider *rp; 96 + struct revocable_provider __rcu *rp; 100 97 struct revocable *rev; 101 98 void *real_res = (void *)0x12345678, *res; 102 99 bool accessed; ··· 114 111 } 115 112 KUNIT_EXPECT_TRUE(test, accessed); 116 113 117 - revocable_provider_revoke(rp); 114 + revocable_provider_revoke(&rp); 115 + KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL); 118 116 119 117 accessed = false; 120 118 REVOCABLE_TRY_ACCESS_SCOPED(rev, res) {
+3 -5
include/linux/revocable.h
··· 13 13 struct revocable; 14 14 struct revocable_provider; 15 15 16 - struct revocable_provider *revocable_provider_alloc(void *res); 17 - void revocable_provider_revoke(struct revocable_provider *rp); 18 - struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, 19 - void *res); 16 + struct revocable_provider __rcu *revocable_provider_alloc(void *res); 17 + void revocable_provider_revoke(struct revocable_provider __rcu **rp); 20 18 21 - struct revocable *revocable_alloc(struct revocable_provider *rp); 19 + struct revocable *revocable_alloc(struct revocable_provider __rcu *rp); 22 20 void revocable_free(struct revocable *rev); 23 21 void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu); 24 22 void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu);
+10 -9
tools/testing/selftests/drivers/base/revocable/test_modules/revocable_test.c
··· 17 17 static struct dentry *debugfs_dir; 18 18 19 19 struct revocable_test_provider_priv { 20 - struct revocable_provider *rp; 20 + struct revocable_provider __rcu *rp; 21 21 struct dentry *dentry; 22 22 char res[16]; 23 23 }; ··· 25 25 static int revocable_test_consumer_open(struct inode *inode, struct file *filp) 26 26 { 27 27 struct revocable *rev; 28 - struct revocable_provider *rp = inode->i_private; 28 + struct revocable_provider __rcu *rp = inode->i_private; 29 29 30 30 rev = revocable_alloc(rp); 31 31 if (!rev) ··· 106 106 struct revocable_test_provider_priv *priv = filp->private_data; 107 107 108 108 debugfs_remove(priv->dentry); 109 - if (priv->rp) 110 - revocable_provider_revoke(priv->rp); 109 + if (unrcu_pointer(priv->rp)) 110 + revocable_provider_revoke(&priv->rp); 111 111 kfree(priv); 112 112 113 113 return 0; ··· 137 137 * gone. 138 138 */ 139 139 if (!strcmp(data, TEST_CMD_RESOURCE_GONE)) { 140 - revocable_provider_revoke(priv->rp); 141 - priv->rp = NULL; 140 + revocable_provider_revoke(&priv->rp); 141 + rcu_assign_pointer(priv->rp, NULL); 142 142 } else { 143 143 if (priv->res[0] != '\0') 144 144 return 0; ··· 146 146 strscpy(priv->res, data); 147 147 148 148 priv->rp = revocable_provider_alloc(&priv->res); 149 - if (!priv->rp) 149 + if (!unrcu_pointer(priv->rp)) 150 150 return -ENOMEM; 151 151 152 152 priv->dentry = debugfs_create_file("consumer", 0400, 153 - debugfs_dir, priv->rp, 153 + debugfs_dir, 154 + unrcu_pointer(priv->rp), 154 155 &revocable_test_consumer_fops); 155 156 if (!priv->dentry) { 156 - revocable_provider_revoke(priv->rp); 157 + revocable_provider_revoke(&priv->rp); 157 158 return -ENOMEM; 158 159 } 159 160 }