staging: android: ion: fix ION_IOC_{MAP,SHARE} use-after-free
Subsystems (2)
PATCH 1/1 staging: android: ion: fix ION_IOC_{MAP,SHARE} use-after-free
The ION_IOC_{MAP,SHARE} ioctls drop and reacquire client->lock several times while operating on one of the client's ion_handles. This creates windows where userspace can call ION_IOC_FREE on the same client with the same handle, and effectively make the kernel drop its own reference. For example:
- thread A: ION_IOC_ALLOC creates an ion_handle with refcount 1
- thread A: starts ION_IOC_MAP and increments the refcount to 2
- thread B: ION_IOC_FREE decrements the refcount to 1
- thread B: ION_IOC_FREE decrements the refcount to 0 and frees the handle
- thread A: continues ION_IOC_MAP with a dangling ion_handle * to freed memory
Fix this by holding client->lock for the duration of ION_IOC_{MAP,SHARE}, preventing the concurrent ION_IOC_FREE. Also remove ion_handle_get_by_id(), since there's literally no way to use it safely.
This patch is applied on top of 4.9.y. Kernels 4.12 and later are unaffected, since all the underlying ion_handle infrastructure has been ripped out.
--- drivers/staging/android/ion/ion-ioctl.c | 12 ++++--- drivers/staging/android/ion/ion.c | 48 +++++++++++++++---------- drivers/staging/android/ion/ion_priv.h | 6 ++-- 3 files changed, 40 insertions(+), 26 deletions(-)
drivers/staging/android/ion/ion-ioctl.c +8 -4
@@ -128,11 +128,15 @@ long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {struct ion_handle *handle; - handle =ion_handle_get_by_id (client, data.handle.handle); -if (IS_ERR (handle)) +mutex_lock (&client->lock); + handle =ion_handle_get_by_id_nolock (client, data.handle.handle); +if (IS_ERR (handle)) { +mutex_unlock (&client->lock);return PTR_ERR (handle); - data.fd.fd =ion_share_dma_buf_fd (client, handle); -ion_handle_put (handle); + } + data.fd.fd =ion_share_dma_buf_fd_nolock (client, handle); +ion_handle_put_nolock (handle); +mutex_unlock (&client->lock);if (data.fd.fd <0 ) ret = data.fd.fd;break ;
drivers/staging/android/ion/ion.c +29 -19
93 lines not on this page · raw message
drivers/staging/android/ion/ion_priv.h +3 -3
@@ -463,11 +463,11 @@ void ion_free_nolock(struct ion_client *client, struct ion_handle *handle);int ion_handle_put_nolock (struct ion_handle *handle); -struct ion_handle *ion_handle_get_by_id (struct ion_client *client, -int id); -int ion_handle_put (struct ion_handle *handle);int ion_query_heaps (struct ion_client *client,struct ion_heap_query *query); +int ion_share_dma_buf_fd_nolock (struct ion_client *client, +struct ion_handle *handle); +#endif /* _ION_PRIV_H */
Signature · 1 line
2.19.0.rc1.350.ge57e33dbd1-goog
Greg Kroah-Hartman
maintainerQuoted 14 lines · Greg Hackmann
The ION_IOC_{MAP,SHARE} ioctls drop and reacquire
client->lockseveral times while operating on one of the client'sion_handles. This creates windows where userspace can callION_IOC_FREEon the same client with the same handle, and effectively make the kernel drop its own reference. For example:- thread A:
ION_IOC_ALLOCcreates anion_handlewith refcount 1
- thread A: startsION_IOC_MAPand increments the refcount to 2
- thread B:ION_IOC_FREEdecrements the refcount to 1
- thread B:ION_IOC_FREEdecrements the refcount to 0 and frees the handle
- thread A: continuesION_IOC_MAPwith a danglingion_handle* to freed memory
Quoted 8 lines
Fix this by holding
client->lockfor the duration of ION_IOC_{MAP,SHARE}, preventing the concurrentION_IOC_FREE. Also removeion_handle_get_by_id(), since there's literally no way to use it safely.This patch is applied on top of 4.9.y. Kernels 4.12 and later are unaffected, since all the underlying
ion_handleinfrastructure has been ripped out.
Does 4.4.y or older also need this? If so, can you send backports, as this one does not apply there.
thanks,
greg k-h
Quoted 24 lines · Greg Kroah-Hartman
Quoted 14 lines · Greg Hackmann
The ION_IOC_{MAP,SHARE} ioctls drop and reacquire
client->lockseveral times while operating on one of the client'sion_handles. This creates windows where userspace can callION_IOC_FREEon the same client with the same handle, and effectively make the kernel drop its own reference. For example:- thread A:
ION_IOC_ALLOCcreates anion_handlewith refcount 1
- thread A: startsION_IOC_MAPand increments the refcount to 2
- thread B:ION_IOC_FREEdecrements the refcount to 1
- thread B:ION_IOC_FREEdecrements the refcount to 0 and frees the handle
- thread A: continuesION_IOC_MAPwith a danglingion_handle* to freed memoryQuoted 8 lines
Fix this by holding
client->lockfor the duration of ION_IOC_{MAP,SHARE}, preventing the concurrentION_IOC_FREE. Also removeion_handle_get_by_id(), since there's literally no way to use it safely.This patch is applied on top of 4.9.y. Kernels 4.12 and later are unaffected, since all the underlying
ion_handleinfrastructure has been ripped out.
Quoted 7 lines
Does 4.4.y or older also need this? If so, can you send backports, as this one does not apply there.
thanks,
greg k-h
Yes, 4.4.y and older will need this. If there are no objections to this patch, I'll send backports ASAP.
Greg Kroah-Hartman
maintainerQuoted 28 lines · Greg Hackmann
Quoted 27 lines · Greg Kroah-Hartman
Quoted 14 lines · Greg Hackmann
The ION_IOC_{MAP,SHARE} ioctls drop and reacquire
client->lockseveral times while operating on one of the client'sion_handles. This creates windows where userspace can callION_IOC_FREEon the same client with the same handle, and effectively make the kernel drop its own reference. For example:- thread A:
ION_IOC_ALLOCcreates anion_handlewith refcount 1
- thread A: startsION_IOC_MAPand increments the refcount to 2
- thread B:ION_IOC_FREEdecrements the refcount to 1
- thread B:ION_IOC_FREEdecrements the refcount to 0 and frees the handle
- thread A: continuesION_IOC_MAPwith a danglingion_handle* to freed memoryQuoted 8 lines
Fix this by holding
client->lockfor the duration of ION_IOC_{MAP,SHARE}, preventing the concurrentION_IOC_FREE. Also removeion_handle_get_by_id(), since there's literally no way to use it safely.This patch is applied on top of 4.9.y. Kernels 4.12 and later are unaffected, since all the underlying
ion_handleinfrastructure has been ripped out.Does 4.4.y or older also need this? If so, can you send backports, as this one does not apply there.
Quoted 7 lines
Quoted 4 lines
thanks,
greg k-h
Yes, 4.4.y and older will need this. If there are no objections to this patch, I'll send backports ASAP.
None from me, thanks!
greg k-h
PATCH 1/1 staging: android: ion: fix ION_IOC_{MAP,SHARE} use-after-free
The ION_IOC_{MAP,SHARE} ioctls drop and reacquire client->lock several times while operating on one of the client's ion_handles. This creates windows where userspace can call ION_IOC_FREE on the same client with the same handle, and effectively make the kernel drop its own reference. For example:
- thread A: ION_IOC_ALLOC creates an ion_handle with refcount 1
- thread A: starts ION_IOC_MAP and increments the refcount to 2
- thread B: ION_IOC_FREE decrements the refcount to 1
- thread B: ION_IOC_FREE decrements the refcount to 0 and frees the handle
- thread A: continues ION_IOC_MAP with a dangling ion_handle * to freed memory
Fix this by holding client->lock for the duration of ION_IOC_{MAP,SHARE}, preventing the concurrent ION_IOC_FREE. Also remove ion_handle_get_by_id(), since there's literally no way to use it safely.
This patch is applied on top of 4.4.y, and applies to older kernels too. 4.9.y was fixed separately. Kernels 4.12 and later are unaffected, since all the underlying ion_handle infrastructure has been ripped out.
--- drivers/staging/android/ion/ion.c | 60 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-)
drivers/staging/android/ion/ion.c +37 -23
@@ -449,18 +449,6 @@ static struct ion_handle *ion_handle_get_by_id_nolock(struct ion_client *client,return ERR_PTR (-EINVAL ); } -struct ion_handle *ion_handle_get_by_id (struct ion_client *client, -int id) -{ -struct ion_handle *handle; - -mutex_lock (&client->lock); - handle =ion_handle_get_by_id_nolock (client, id); -mutex_unlock (&client->lock); - -return handle; -} -static bool ion_handle_validate (struct ion_client *client,struct ion_handle *handle) {
@@ -1138,24 +1126,28 @@ static struct dma_buf_ops dma_buf_ops = { .kunmap = ion_dma_buf_kunmap, }; -struct dma_buf *ion_share_dma_buf (struct ion_client *client, -struct ion_handle *handle) +static struct dma_buf *__ion_share_dma_buf (struct ion_client *client, +struct ion_handle *handle, +bool lock_client) {DEFINE_DMA_BUF_EXPORT_INFO (exp_info);struct ion_buffer *buffer;struct dma_buf *dmabuf;bool valid_handle; -mutex_lock (&client->lock); +if (lock_client) +mutex_lock (&client->lock); valid_handle =ion_handle_validate (client, handle);if (!valid_handle) {WARN (1 ,"%s: invalid handle passed to share.\n" , __func__); -mutex_unlock (&client->lock); +if (lock_client) +mutex_unlock (&client->lock);return ERR_PTR (-EINVAL ); } buffer = handle->buffer;ion_buffer_get (buffer); -mutex_unlock (&client->lock); +if (lock_client) +mutex_unlock (&client->lock); exp_info.ops = &dma_buf_ops; exp_info.size = buffer->size;
@@ -1170,14 +1162,21 @@ struct dma_buf *ion_share_dma_buf(struct ion_client *client,return dmabuf; } + +struct dma_buf *ion_share_dma_buf (struct ion_client *client, +struct ion_handle *handle) +{ +return __ion_share_dma_buf (client, handle, true); +}EXPORT_SYMBOL (ion_share_dma_buf); -int ion_share_dma_buf_fd (struct ion_client *client,struct ion_handle *handle) +static int __ion_share_dma_buf_fd (struct ion_client *client, +struct ion_handle *handle,bool lock_client) {struct dma_buf *dmabuf;int fd; - dmabuf =ion_share_dma_buf (client, handle); + dmabuf =__ion_share_dma_buf (client, handle, lock_client);if (IS_ERR (dmabuf))return PTR_ERR (dmabuf);
@@ -1187,8 +1186,19 @@ int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle)return fd; } + +int ion_share_dma_buf_fd (struct ion_client *client,struct ion_handle *handle) +{ +return __ion_share_dma_buf_fd (client, handle, true); +}EXPORT_SYMBOL (ion_share_dma_buf_fd); +static int ion_share_dma_buf_fd_nolock (struct ion_client *client, +struct ion_handle *handle) +{ +return __ion_share_dma_buf_fd (client, handle, false); +} +struct ion_handle *ion_import_dma_buf (struct ion_client *client,int fd) {struct dma_buf *dmabuf;
@@ -1335,11 +1345,15 @@ static long ion_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {struct ion_handle *handle; - handle =ion_handle_get_by_id (client, data.handle.handle); -if (IS_ERR (handle)) +mutex_lock (&client->lock); + handle =ion_handle_get_by_id_nolock (client, data.handle.handle); +if (IS_ERR (handle)) { +mutex_unlock (&client->lock);return PTR_ERR (handle); - data.fd.fd =ion_share_dma_buf_fd (client, handle); -ion_handle_put (handle); + } + data.fd.fd =ion_share_dma_buf_fd_nolock (client, handle); +ion_handle_put_nolock (handle); +mutex_unlock (&client->lock);if (data.fd.fd <0 ) ret = data.fd.fd;break ;
Signature · 1 line
2.19.0.rc1.350.ge57e33dbd1-goog
Quoted 1 line · Greg Hackmann
Change-Id: Ia0542dd8134e81cd5e1412e126545303c766f738
Sorry, please disregard the Change-Id line. This is what I get for forgetting to re-run checkpatch after amending my commit message. :/
Greg Kroah-Hartman
maintainerQuoted 5 lines · Greg Hackmann
Quoted 1 line · Greg Hackmann
Change-Id: Ia0542dd8134e81cd5e1412e126545303c766f738
Sorry, please disregard the Change-Id line. This is what I get for forgetting to re-run checkpatch after amending my commit message. :/
Can you please resend with that fixed up. Having to hand-edit patches on my end is a royal pain...
thanks,
greg k-h