aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Wilcox <mawilcox@microsoft.com>2018-05-25 17:47:24 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2018-05-25 21:12:10 -0400
commit7a4deea1aa8bddfed4ef1b35fc2b6732563d8ad5 (patch)
tree99e90573169da0f9665c718ecda63d52d320b021
parent3373de209cb123462954740f41c324d03ecfb6d0 (diff)
idr: fix invalid ptr dereference on item delete
If the radix tree underlying the IDR happens to be full and we attempt to remove an id which is larger than any id in the IDR, we will call __radix_tree_delete() with an uninitialised 'slot' pointer, at which point anything could happen. This was easiest to hit with a single entry at id 0 and attempting to remove a non-0 id, but it could have happened with 64 entries and attempting to remove an id >= 64. Roman said: The syzcaller test boils down to opening /dev/kvm, creating an eventfd, and calling a couple of KVM ioctls. None of this requires superuser. And the result is dereferencing an uninitialized pointer which is likely a crash. The specific path caught by syzbot is via KVM_HYPERV_EVENTD ioctl which is new in 4.17. But I guess there are other user-triggerable paths, so cc:stable is probably justified. Matthew added: We have around 250 calls to idr_remove() in the kernel today. Many of them pass an ID which is embedded in the object they're removing, so they're safe. Picking a few likely candidates: drivers/firewire/core-cdev.c looks unsafe; the ID comes from an ioctl. drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c is similar drivers/atm/nicstar.c could be taken down by a handcrafted packet Link: http://lkml.kernel.org/r/20180518175025.GD6361@bombadil.infradead.org Fixes: 0a835c4f090a ("Reimplement IDR and IDA using the radix tree") Reported-by: <syzbot+35666cba7f0a337e2e79@syzkaller.appspotmail.com> Debugged-by: Roman Kagan <rkagan@virtuozzo.com> Signed-off-by: Matthew Wilcox <mawilcox@microsoft.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--lib/radix-tree.c4
-rw-r--r--tools/testing/radix-tree/idr-test.c7
2 files changed, 10 insertions, 1 deletions
diff --git a/lib/radix-tree.c b/lib/radix-tree.c
index 43e0cbedc3a0..a9e41aed6de4 100644
--- a/lib/radix-tree.c
+++ b/lib/radix-tree.c
@@ -2034,10 +2034,12 @@ void *radix_tree_delete_item(struct radix_tree_root *root,
2034 unsigned long index, void *item) 2034 unsigned long index, void *item)
2035{ 2035{
2036 struct radix_tree_node *node = NULL; 2036 struct radix_tree_node *node = NULL;
2037 void __rcu **slot; 2037 void __rcu **slot = NULL;
2038 void *entry; 2038 void *entry;
2039 2039
2040 entry = __radix_tree_lookup(root, index, &node, &slot); 2040 entry = __radix_tree_lookup(root, index, &node, &slot);
2041 if (!slot)
2042 return NULL;
2041 if (!entry && (!is_idr(root) || node_tag_get(root, node, IDR_FREE, 2043 if (!entry && (!is_idr(root) || node_tag_get(root, node, IDR_FREE,
2042 get_slot_offset(node, slot)))) 2044 get_slot_offset(node, slot))))
2043 return NULL; 2045 return NULL;
diff --git a/tools/testing/radix-tree/idr-test.c b/tools/testing/radix-tree/idr-test.c
index 6c645eb77d42..ee820fcc29b0 100644
--- a/tools/testing/radix-tree/idr-test.c
+++ b/tools/testing/radix-tree/idr-test.c
@@ -252,6 +252,13 @@ void idr_checks(void)
252 idr_remove(&idr, 3); 252 idr_remove(&idr, 3);
253 idr_remove(&idr, 0); 253 idr_remove(&idr, 0);
254 254
255 assert(idr_alloc(&idr, DUMMY_PTR, 0, 0, GFP_KERNEL) == 0);
256 idr_remove(&idr, 1);
257 for (i = 1; i < RADIX_TREE_MAP_SIZE; i++)
258 assert(idr_alloc(&idr, DUMMY_PTR, 0, 0, GFP_KERNEL) == i);
259 idr_remove(&idr, 1 << 30);
260 idr_destroy(&idr);
261
255 for (i = INT_MAX - 3UL; i < INT_MAX + 1UL; i++) { 262 for (i = INT_MAX - 3UL; i < INT_MAX + 1UL; i++) {
256 struct item *item = item_create(i, 0); 263 struct item *item = item_create(i, 0);
257 assert(idr_alloc(&idr, item, i, i + 10, GFP_KERNEL) == i); 264 assert(idr_alloc(&idr, item, i, i + 10, GFP_KERNEL) == i);