diff options
author | Sakari Ailus <sakari.ailus@linux.intel.com> | 2018-09-11 05:32:37 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab+samsung@kernel.org> | 2018-10-03 06:32:51 -0400 |
commit | ad608fbcf166fec809e402d548761768f602702c (patch) | |
tree | 7c0b71ffee7dfb6a03ba25afb532c7af732eaa4e | |
parent | 324493fba77500592bbaa66421729421f139d4b5 (diff) |
media: v4l: event: Prevent freeing event subscriptions while accessed
The event subscriptions are added to the subscribed event list while
holding a spinlock, but that lock is subsequently released while still
accessing the subscription object. This makes it possible to unsubscribe
the event --- and freeing the subscription object's memory --- while
the subscription object is simultaneously accessed.
Prevent this by adding a mutex to serialise the event subscription and
unsubscription. This also gives a guarantee to the callback ops that the
add op has returned before the del op is called.
This change also results in making the elems field less special:
subscriptions are only added to the event list once they are fully
initialised.
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Reviewed-by: Hans Verkuil <hans.verkuil@cisco.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: stable@vger.kernel.org # for 4.14 and up
Fixes: c3b5b0241f62 ("V4L/DVB: V4L: Events: Add backend")
Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
-rw-r--r-- | drivers/media/v4l2-core/v4l2-event.c | 38 | ||||
-rw-r--r-- | drivers/media/v4l2-core/v4l2-fh.c | 2 | ||||
-rw-r--r-- | include/media/v4l2-fh.h | 4 |
3 files changed, 26 insertions, 18 deletions
diff --git a/drivers/media/v4l2-core/v4l2-event.c b/drivers/media/v4l2-core/v4l2-event.c index 127fe6eb91d9..a3ef1f50a4b3 100644 --- a/drivers/media/v4l2-core/v4l2-event.c +++ b/drivers/media/v4l2-core/v4l2-event.c | |||
@@ -115,14 +115,6 @@ static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *e | |||
115 | if (sev == NULL) | 115 | if (sev == NULL) |
116 | return; | 116 | return; |
117 | 117 | ||
118 | /* | ||
119 | * If the event has been added to the fh->subscribed list, but its | ||
120 | * add op has not completed yet elems will be 0, treat this as | ||
121 | * not being subscribed. | ||
122 | */ | ||
123 | if (!sev->elems) | ||
124 | return; | ||
125 | |||
126 | /* Increase event sequence number on fh. */ | 118 | /* Increase event sequence number on fh. */ |
127 | fh->sequence++; | 119 | fh->sequence++; |
128 | 120 | ||
@@ -208,6 +200,7 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, | |||
208 | struct v4l2_subscribed_event *sev, *found_ev; | 200 | struct v4l2_subscribed_event *sev, *found_ev; |
209 | unsigned long flags; | 201 | unsigned long flags; |
210 | unsigned i; | 202 | unsigned i; |
203 | int ret = 0; | ||
211 | 204 | ||
212 | if (sub->type == V4L2_EVENT_ALL) | 205 | if (sub->type == V4L2_EVENT_ALL) |
213 | return -EINVAL; | 206 | return -EINVAL; |
@@ -225,31 +218,36 @@ int v4l2_event_subscribe(struct v4l2_fh *fh, | |||
225 | sev->flags = sub->flags; | 218 | sev->flags = sub->flags; |
226 | sev->fh = fh; | 219 | sev->fh = fh; |
227 | sev->ops = ops; | 220 | sev->ops = ops; |
221 | sev->elems = elems; | ||
222 | |||
223 | mutex_lock(&fh->subscribe_lock); | ||
228 | 224 | ||
229 | spin_lock_irqsave(&fh->vdev->fh_lock, flags); | 225 | spin_lock_irqsave(&fh->vdev->fh_lock, flags); |
230 | found_ev = v4l2_event_subscribed(fh, sub->type, sub->id); | 226 | found_ev = v4l2_event_subscribed(fh, sub->type, sub->id); |
231 | if (!found_ev) | ||
232 | list_add(&sev->list, &fh->subscribed); | ||
233 | spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); | 227 | spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); |
234 | 228 | ||
235 | if (found_ev) { | 229 | if (found_ev) { |
230 | /* Already listening */ | ||
236 | kvfree(sev); | 231 | kvfree(sev); |
237 | return 0; /* Already listening */ | 232 | goto out_unlock; |
238 | } | 233 | } |
239 | 234 | ||
240 | if (sev->ops && sev->ops->add) { | 235 | if (sev->ops && sev->ops->add) { |
241 | int ret = sev->ops->add(sev, elems); | 236 | ret = sev->ops->add(sev, elems); |
242 | if (ret) { | 237 | if (ret) { |
243 | sev->ops = NULL; | 238 | kvfree(sev); |
244 | v4l2_event_unsubscribe(fh, sub); | 239 | goto out_unlock; |
245 | return ret; | ||
246 | } | 240 | } |
247 | } | 241 | } |
248 | 242 | ||
249 | /* Mark as ready for use */ | 243 | spin_lock_irqsave(&fh->vdev->fh_lock, flags); |
250 | sev->elems = elems; | 244 | list_add(&sev->list, &fh->subscribed); |
245 | spin_unlock_irqrestore(&fh->vdev->fh_lock, flags); | ||
251 | 246 | ||
252 | return 0; | 247 | out_unlock: |
248 | mutex_unlock(&fh->subscribe_lock); | ||
249 | |||
250 | return ret; | ||
253 | } | 251 | } |
254 | EXPORT_SYMBOL_GPL(v4l2_event_subscribe); | 252 | EXPORT_SYMBOL_GPL(v4l2_event_subscribe); |
255 | 253 | ||
@@ -288,6 +286,8 @@ int v4l2_event_unsubscribe(struct v4l2_fh *fh, | |||
288 | return 0; | 286 | return 0; |
289 | } | 287 | } |
290 | 288 | ||
289 | mutex_lock(&fh->subscribe_lock); | ||
290 | |||
291 | spin_lock_irqsave(&fh->vdev->fh_lock, flags); | 291 | spin_lock_irqsave(&fh->vdev->fh_lock, flags); |
292 | 292 | ||
293 | sev = v4l2_event_subscribed(fh, sub->type, sub->id); | 293 | sev = v4l2_event_subscribed(fh, sub->type, sub->id); |
@@ -305,6 +305,8 @@ int v4l2_event_unsubscribe(struct v4l2_fh *fh, | |||
305 | if (sev && sev->ops && sev->ops->del) | 305 | if (sev && sev->ops && sev->ops->del) |
306 | sev->ops->del(sev); | 306 | sev->ops->del(sev); |
307 | 307 | ||
308 | mutex_unlock(&fh->subscribe_lock); | ||
309 | |||
308 | kvfree(sev); | 310 | kvfree(sev); |
309 | 311 | ||
310 | return 0; | 312 | return 0; |
diff --git a/drivers/media/v4l2-core/v4l2-fh.c b/drivers/media/v4l2-core/v4l2-fh.c index 3895999bf880..c91a7bd3ecfc 100644 --- a/drivers/media/v4l2-core/v4l2-fh.c +++ b/drivers/media/v4l2-core/v4l2-fh.c | |||
@@ -45,6 +45,7 @@ void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) | |||
45 | INIT_LIST_HEAD(&fh->available); | 45 | INIT_LIST_HEAD(&fh->available); |
46 | INIT_LIST_HEAD(&fh->subscribed); | 46 | INIT_LIST_HEAD(&fh->subscribed); |
47 | fh->sequence = -1; | 47 | fh->sequence = -1; |
48 | mutex_init(&fh->subscribe_lock); | ||
48 | } | 49 | } |
49 | EXPORT_SYMBOL_GPL(v4l2_fh_init); | 50 | EXPORT_SYMBOL_GPL(v4l2_fh_init); |
50 | 51 | ||
@@ -90,6 +91,7 @@ void v4l2_fh_exit(struct v4l2_fh *fh) | |||
90 | return; | 91 | return; |
91 | v4l_disable_media_source(fh->vdev); | 92 | v4l_disable_media_source(fh->vdev); |
92 | v4l2_event_unsubscribe_all(fh); | 93 | v4l2_event_unsubscribe_all(fh); |
94 | mutex_destroy(&fh->subscribe_lock); | ||
93 | fh->vdev = NULL; | 95 | fh->vdev = NULL; |
94 | } | 96 | } |
95 | EXPORT_SYMBOL_GPL(v4l2_fh_exit); | 97 | EXPORT_SYMBOL_GPL(v4l2_fh_exit); |
diff --git a/include/media/v4l2-fh.h b/include/media/v4l2-fh.h index ea73fef8bdc0..8586cfb49828 100644 --- a/include/media/v4l2-fh.h +++ b/include/media/v4l2-fh.h | |||
@@ -38,10 +38,13 @@ struct v4l2_ctrl_handler; | |||
38 | * @prio: priority of the file handler, as defined by &enum v4l2_priority | 38 | * @prio: priority of the file handler, as defined by &enum v4l2_priority |
39 | * | 39 | * |
40 | * @wait: event' s wait queue | 40 | * @wait: event' s wait queue |
41 | * @subscribe_lock: serialise changes to the subscribed list; guarantee that | ||
42 | * the add and del event callbacks are orderly called | ||
41 | * @subscribed: list of subscribed events | 43 | * @subscribed: list of subscribed events |
42 | * @available: list of events waiting to be dequeued | 44 | * @available: list of events waiting to be dequeued |
43 | * @navailable: number of available events at @available list | 45 | * @navailable: number of available events at @available list |
44 | * @sequence: event sequence number | 46 | * @sequence: event sequence number |
47 | * | ||
45 | * @m2m_ctx: pointer to &struct v4l2_m2m_ctx | 48 | * @m2m_ctx: pointer to &struct v4l2_m2m_ctx |
46 | */ | 49 | */ |
47 | struct v4l2_fh { | 50 | struct v4l2_fh { |
@@ -52,6 +55,7 @@ struct v4l2_fh { | |||
52 | 55 | ||
53 | /* Events */ | 56 | /* Events */ |
54 | wait_queue_head_t wait; | 57 | wait_queue_head_t wait; |
58 | struct mutex subscribe_lock; | ||
55 | struct list_head subscribed; | 59 | struct list_head subscribed; |
56 | struct list_head available; | 60 | struct list_head available; |
57 | unsigned int navailable; | 61 | unsigned int navailable; |